@overdeck/core 0.45.10 → 0.45.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/dist/{agent-state-KVbJEG5f.js → agent-state-RLhS7ckO.js} +1 -1
  2. package/dist/{agents-pp8FO7Uj.js → agents-By3u_fQ8.js} +1 -1
  3. package/dist/{agents-CQHK0CSA.js → agents-CgVr77Qq.js} +12 -12
  4. package/dist/{agents-CQHK0CSA.js.map → agents-CgVr77Qq.js.map} +1 -1
  5. package/dist/{backlog-auto-trigger-CyuoS0nl.js → backlog-auto-trigger-B_-1tk4e.js} +5 -5
  6. package/dist/{backlog-auto-trigger-CyuoS0nl.js.map → backlog-auto-trigger-B_-1tk4e.js.map} +1 -1
  7. package/dist/cli/index.js +45 -45
  8. package/dist/{cli-Cewlu3yU.js → cli-DGeSW85B.js} +2 -2
  9. package/dist/{cli-Cewlu3yU.js.map → cli-DGeSW85B.js.map} +1 -1
  10. package/dist/cli-MTB9IOoR.js +3 -0
  11. package/dist/{close-issue-BJGX9_z7.js → close-issue-Djajb6Sv.js} +2 -2
  12. package/dist/{close-issue-BJGX9_z7.js.map → close-issue-Djajb6Sv.js.map} +1 -1
  13. package/dist/{concurrency-Ca-fC7tA.js → concurrency-4rzjHAQA.js} +1 -1
  14. package/dist/{conversation-compaction-B_6s9EvM.js → conversation-compaction-BQvZh09I.js} +4 -4
  15. package/dist/{conversation-compaction-B_6s9EvM.js.map → conversation-compaction-BQvZh09I.js.map} +1 -1
  16. package/dist/dashboard/{advancing-selfheal-CMlGznKv.js → advancing-selfheal-BezNfM3P.js} +3 -3
  17. package/dist/dashboard/{advancing-selfheal-CMlGznKv.js.map → advancing-selfheal-BezNfM3P.js.map} +1 -1
  18. package/dist/dashboard/{agent-state-xk_G8t90.js → agent-state-BUANcaA7.js} +1 -1
  19. package/dist/dashboard/{agent-state-service-C1iUF9Ft.js → agent-state-service-DmFpIMbP.js} +1 -1
  20. package/dist/dashboard/{agent-state-service-5ge9xxhx.js → agent-state-service-tXUdp6UF.js} +2 -2
  21. package/dist/dashboard/{agent-state-service-5ge9xxhx.js.map → agent-state-service-tXUdp6UF.js.map} +1 -1
  22. package/dist/dashboard/{agents-BIggLvi_.js → agents-C-vqmKDi.js} +1 -1
  23. package/dist/dashboard/{agents-DHvsjsuP.js → agents-C7V4odQ6.js} +11 -11
  24. package/dist/dashboard/{agents-DHvsjsuP.js.map → agents-C7V4odQ6.js.map} +1 -1
  25. package/dist/dashboard/{backlog-auto-trigger-COm_R96V.js → backlog-auto-trigger-Cb8PR-5w.js} +2 -2
  26. package/dist/dashboard/{backlog-auto-trigger-COm_R96V.js.map → backlog-auto-trigger-Cb8PR-5w.js.map} +1 -1
  27. package/dist/dashboard/{cli-CvK9xuSS.js → cli-BgzbB6h3.js} +2 -2
  28. package/dist/dashboard/{cli-CvK9xuSS.js.map → cli-BgzbB6h3.js.map} +1 -1
  29. package/dist/dashboard/cli-DCqy_wFa.js +3 -0
  30. package/dist/dashboard/{close-issue-BnpErnD1.js → close-issue-BWf3pxn4.js} +2 -2
  31. package/dist/dashboard/{close-issue-BnpErnD1.js.map → close-issue-BWf3pxn4.js.map} +1 -1
  32. package/dist/dashboard/{closed-issue-reaper-DRqK5k39.js → closed-issue-reaper-D7XPq5pY.js} +4 -4
  33. package/dist/dashboard/{closed-issue-reaper-DRqK5k39.js.map → closed-issue-reaper-D7XPq5pY.js.map} +1 -1
  34. package/dist/dashboard/{closed-issue-reaper-BLCQwTpF.js → closed-issue-reaper-VSTSTROK.js} +1 -1
  35. package/dist/dashboard/{conflict-gate-JdFuhKuF.js → conflict-gate-DwOslTij.js} +1 -1
  36. package/dist/dashboard/{conflict-gate-BhKvrPxn.js → conflict-gate-FFEvTDVA.js} +3 -3
  37. package/dist/dashboard/{conflict-gate-BhKvrPxn.js.map → conflict-gate-FFEvTDVA.js.map} +1 -1
  38. package/dist/dashboard/{conversation-compaction-C5o_eKEm.js → conversation-compaction-CCqN4hWk.js} +4 -4
  39. package/dist/dashboard/{conversation-compaction-C5o_eKEm.js.map → conversation-compaction-CCqN4hWk.js.map} +1 -1
  40. package/dist/dashboard/{conversation-compaction-Ba5aZdrb.js → conversation-compaction-CL5Ti4j4.js} +1 -1
  41. package/dist/dashboard/{deacon-j3qSZ7--.js → deacon-CGHl90eC.js} +107 -50
  42. package/dist/dashboard/{deacon-j3qSZ7--.js.map → deacon-CGHl90eC.js.map} +1 -1
  43. package/dist/dashboard/{deacon-Cv2RMfHs.js → deacon-DMPh40Im.js} +2 -2
  44. package/dist/dashboard/deacon-swarm-CVTiNL0N.js +2 -0
  45. package/dist/dashboard/{deacon-swarm-DadWNi5J.js → deacon-swarm-aMMJTq6t.js} +4 -4
  46. package/dist/dashboard/{deacon-swarm-DadWNi5J.js.map → deacon-swarm-aMMJTq6t.js.map} +1 -1
  47. package/dist/dashboard/deacon.js +4 -4
  48. package/dist/dashboard/{feedback-target-oE4-Esxl.js → feedback-target-dJjFfITP.js} +4 -4
  49. package/dist/dashboard/{feedback-target-oE4-Esxl.js.map → feedback-target-dJjFfITP.js.map} +1 -1
  50. package/dist/dashboard/{feedback-target-C4DaDSZ8.js → feedback-target-nQSM6ygL.js} +1 -1
  51. package/dist/dashboard/{flywheel-ClMjGW_u.js → flywheel-BLoVNtJV.js} +6 -6
  52. package/dist/dashboard/{flywheel-ClMjGW_u.js.map → flywheel-BLoVNtJV.js.map} +1 -1
  53. package/dist/dashboard/{flywheel-DefHRzY4.js → flywheel-Cqf8vu7N.js} +6 -6
  54. package/dist/dashboard/{flywheel-DefHRzY4.js.map → flywheel-Cqf8vu7N.js.map} +1 -1
  55. package/dist/dashboard/{flywheel-BqDdGvrQ.js → flywheel-PJsbrXNY.js} +1 -1
  56. package/dist/dashboard/{flywheel-merge-order-CuDcqTRJ.js → flywheel-merge-order-B7BH8mZ3.js} +2 -2
  57. package/dist/dashboard/{flywheel-merge-order-CuDcqTRJ.js.map → flywheel-merge-order-B7BH8mZ3.js.map} +1 -1
  58. package/dist/dashboard/{flywheel-merge-order-B6YDSElh.js → flywheel-merge-order-CCwF60b-.js} +1 -1
  59. package/dist/dashboard/{harness-resolve-DKeSVsJL.js → harness-resolve-B3f9G1Kp.js} +2 -2
  60. package/dist/dashboard/{harness-resolve-DKeSVsJL.js.map → harness-resolve-B3f9G1Kp.js.map} +1 -1
  61. package/dist/dashboard/{inspect-agent-BKKc0XnB.js → inspect-agent-CKF5-SJr.js} +3 -3
  62. package/dist/dashboard/{inspect-agent-BKKc0XnB.js.map → inspect-agent-CKF5-SJr.js.map} +1 -1
  63. package/dist/dashboard/inspect-agent-CqpEWGWy.js +2 -0
  64. package/dist/dashboard/{issue-lifecycle-DnhQgddB.js → issue-lifecycle-5LbOcATP.js} +7 -7
  65. package/dist/dashboard/{issue-lifecycle-DnhQgddB.js.map → issue-lifecycle-5LbOcATP.js.map} +1 -1
  66. package/dist/dashboard/{issue-lifecycle-nY4esZn8.js → issue-lifecycle-CjYqQ669.js} +1 -1
  67. package/dist/dashboard/{issue-service-singleton-Bnqv1uaa.js → issue-service-singleton-B8iu_TKV.js} +1 -1
  68. package/dist/dashboard/{issue-service-singleton-_AquSBTl.js → issue-service-singleton-CDGt__oQ.js} +2 -2
  69. package/dist/dashboard/{issue-service-singleton-_AquSBTl.js.map → issue-service-singleton-CDGt__oQ.js.map} +1 -1
  70. package/dist/dashboard/{lifecycle-CGKm7doE.js → lifecycle-CEg4sHZH.js} +4 -4
  71. package/dist/dashboard/{lifecycle-CGKm7doE.js.map → lifecycle-CEg4sHZH.js.map} +1 -1
  72. package/dist/dashboard/{merge-agent-Dh8Y9PDO.js → merge-agent-7nBiCytp.js} +1 -1
  73. package/dist/dashboard/{merge-agent-BcgAYStl.js → merge-agent-C81xPIGd.js} +6 -6
  74. package/dist/dashboard/{merge-agent-BcgAYStl.js.map → merge-agent-C81xPIGd.js.map} +1 -1
  75. package/dist/dashboard/merge-ops-DE_6CnmZ.js +2 -0
  76. package/dist/dashboard/{merge-train-DmxlYbEo.js → merge-train-D3qzRccL.js} +2 -2
  77. package/dist/dashboard/{merge-train-DmxlYbEo.js.map → merge-train-D3qzRccL.js.map} +1 -1
  78. package/dist/dashboard/{merge-train-deps-DBHsOWt9.js → merge-train-deps-Be5ElC_P.js} +2 -2
  79. package/dist/dashboard/{merge-train-deps-DBHsOWt9.js.map → merge-train-deps-Be5ElC_P.js.map} +1 -1
  80. package/dist/dashboard/{orphan-proposed-reconciler-Db8tUhce.js → orphan-proposed-reconciler-Boq_XaRe.js} +3 -3
  81. package/dist/dashboard/{orphan-proposed-reconciler-Db8tUhce.js.map → orphan-proposed-reconciler-Boq_XaRe.js.map} +1 -1
  82. package/dist/dashboard/{orphan-proposed-reconciler-mXV--tcB.js → orphan-proposed-reconciler-C0sPnDq_.js} +1 -1
  83. package/dist/dashboard/{reconstruct-cache-_tDZ2D72.js → reconstruct-cache-C2pCltOq.js} +3 -3
  84. package/dist/dashboard/{reconstruct-cache-_tDZ2D72.js.map → reconstruct-cache-C2pCltOq.js.map} +1 -1
  85. package/dist/dashboard/{remote-completion-Bklh6loJ.js → remote-completion-BmxD5UMQ.js} +3 -3
  86. package/dist/dashboard/{remote-completion-Bklh6loJ.js.map → remote-completion-BmxD5UMQ.js.map} +1 -1
  87. package/dist/dashboard/resume-C-nxu9Y4.js +3 -0
  88. package/dist/dashboard/{review-agent-phzqNqIs.js → review-agent-D6P6d3pz.js} +26 -17
  89. package/dist/dashboard/review-agent-D6P6d3pz.js.map +1 -0
  90. package/dist/dashboard/review-agent-DTINEffB.js +3 -0
  91. package/dist/dashboard/{review-context-CCq56O_Y.js → review-context-Dyo0tsBa.js} +2 -2
  92. package/dist/dashboard/{review-context-CCq56O_Y.js.map → review-context-Dyo0tsBa.js.map} +1 -1
  93. package/dist/dashboard/{review-status-BQjqQXZG.js → review-status-B5ZASOo4.js} +18 -5
  94. package/dist/dashboard/review-status-B5ZASOo4.js.map +1 -0
  95. package/dist/dashboard/{review-status-bBkqUj5i.js → review-status-h7BXIA4z.js} +1 -1
  96. package/dist/dashboard/{review-verdict-feedback-wqnikhMK.js → review-verdict-feedback-B70VbfRL.js} +4 -4
  97. package/dist/dashboard/{review-verdict-feedback-wqnikhMK.js.map → review-verdict-feedback-B70VbfRL.js.map} +1 -1
  98. package/dist/dashboard/{review-verdict-feedback-cZh9Wi9G.js → review-verdict-feedback-CorgH49f.js} +1 -1
  99. package/dist/dashboard/{sequence-io-B2GmilSI.js → sequence-io-6YP8fVpH.js} +2 -2
  100. package/dist/dashboard/{sequence-io-B2GmilSI.js.map → sequence-io-6YP8fVpH.js.map} +1 -1
  101. package/dist/dashboard/{sequencer-agent-D8pW2aU_.js → sequencer-agent-ja-nkEc1.js} +5 -5
  102. package/dist/dashboard/{sequencer-agent-D8pW2aU_.js.map → sequencer-agent-ja-nkEc1.js.map} +1 -1
  103. package/dist/dashboard/server.js +66 -55
  104. package/dist/dashboard/server.js.map +1 -1
  105. package/dist/dashboard/{service-CILd-uOt.js → service-HKDrIy0h.js} +24 -24
  106. package/dist/dashboard/{service-CILd-uOt.js.map → service-HKDrIy0h.js.map} +1 -1
  107. package/dist/dashboard/{smart-compaction-Bgp_gwcA.js → smart-compaction-L0LSnF0_.js} +1 -1
  108. package/dist/dashboard/{smart-compaction-CrP90OFE.js → smart-compaction-uke1npUz.js} +2 -2
  109. package/dist/dashboard/{smart-compaction-CrP90OFE.js.map → smart-compaction-uke1npUz.js.map} +1 -1
  110. package/dist/dashboard/{spawn-planning-session-D15KbPOy.js → spawn-planning-session-CSMWco8B.js} +1 -1
  111. package/dist/dashboard/{spawn-planning-session-BmJWHSCu.js → spawn-planning-session-CXc0NkwV.js} +3 -3
  112. package/dist/dashboard/{spawn-planning-session-BmJWHSCu.js.map → spawn-planning-session-CXc0NkwV.js.map} +1 -1
  113. package/dist/dashboard/{specialist-context-CwKkcXL9.js → specialist-context-BGCJ24LH.js} +3 -3
  114. package/dist/dashboard/{specialist-context-CwKkcXL9.js.map → specialist-context-BGCJ24LH.js.map} +1 -1
  115. package/dist/dashboard/{specialist-logs-CvDxquV_.js → specialist-logs--Uz-r8ia.js} +2 -2
  116. package/dist/dashboard/{specialist-logs-CvDxquV_.js.map → specialist-logs--Uz-r8ia.js.map} +1 -1
  117. package/dist/dashboard/{specialists-BCjcG4uE.js → specialists-Ca9XSBCS.js} +1 -1
  118. package/dist/dashboard/{specialists-D07ybnam.js → specialists-DEfrAbWs.js} +36 -36
  119. package/dist/dashboard/{specialists-D07ybnam.js.map → specialists-DEfrAbWs.js.map} +1 -1
  120. package/dist/dashboard/{specialists-F2uMO4QS.js → specialists-DeZx9xom.js} +1 -1
  121. package/dist/dashboard/{specialists-BYi0k8CZ.js → specialists-nZ0mRwpZ.js} +12 -12
  122. package/dist/dashboard/{specialists-BYi0k8CZ.js.map → specialists-nZ0mRwpZ.js.map} +1 -1
  123. package/dist/dashboard/{summary-fork-DedqguZb.js → summary-fork-2a1L2DIU.js} +2 -2
  124. package/dist/dashboard/{summary-fork-EjvOkEPl.js → summary-fork-Xn0QUNmb.js} +3 -3
  125. package/dist/dashboard/{summary-fork-EjvOkEPl.js.map → summary-fork-Xn0QUNmb.js.map} +1 -1
  126. package/dist/dashboard/{teardown-workspace-5LCVbjfq.js → teardown-workspace-DhA6oAIK.js} +2 -2
  127. package/dist/dashboard/{teardown-workspace-5LCVbjfq.js.map → teardown-workspace-DhA6oAIK.js.map} +1 -1
  128. package/dist/dashboard/{test-agent-queue-qfsq6uaC.js → test-agent-queue-B_ukPyIK.js} +3 -3
  129. package/dist/dashboard/{test-agent-queue-qfsq6uaC.js.map → test-agent-queue-B_ukPyIK.js.map} +1 -1
  130. package/dist/dashboard/{uat-train-CtNGgXxj.js → uat-train-BLBAbJ8Y.js} +10 -10
  131. package/dist/dashboard/{uat-train-CtNGgXxj.js.map → uat-train-BLBAbJ8Y.js.map} +1 -1
  132. package/dist/dashboard/{uat-train-C3hWXtip.js → uat-train-DPfGR9_I.js} +1 -1
  133. package/dist/dashboard/{verification-runner-DuZRHjPU.js → verification-runner-KTGTNugM.js} +1 -1
  134. package/dist/dashboard/{verification-runner-BPAhPWR7.js → verification-runner-R2-4XgMO.js} +4 -4
  135. package/dist/dashboard/{verification-runner-BPAhPWR7.js.map → verification-runner-R2-4XgMO.js.map} +1 -1
  136. package/dist/dashboard/{workflows-H-COJ84X.js → workflows-CSKX5MhC.js} +7 -7
  137. package/dist/dashboard/{workflows-H-COJ84X.js.map → workflows-CSKX5MhC.js.map} +1 -1
  138. package/dist/dashboard/workflows-DVwXauef.js +2 -0
  139. package/dist/dashboard/{workspaces-CRU9_vRn.js → workspaces-DXLDgXo2.js} +32 -32
  140. package/dist/dashboard/{workspaces-CRU9_vRn.js.map → workspaces-DXLDgXo2.js.map} +1 -1
  141. package/dist/docs-index.sqlite +0 -0
  142. package/dist/{feedback-target-DTGAhFkM.js → feedback-target-BsKN4Tr_.js} +4 -4
  143. package/dist/{feedback-target-DTGAhFkM.js.map → feedback-target-BsKN4Tr_.js.map} +1 -1
  144. package/dist/{feedback-target-DutF088V.js → feedback-target-DM1WzOKy.js} +1 -1
  145. package/dist/{harness-resolve-Bz1prk6B.js → harness-resolve-B-YDNwNW.js} +2 -2
  146. package/dist/{harness-resolve-Bz1prk6B.js.map → harness-resolve-B-YDNwNW.js.map} +1 -1
  147. package/dist/{issue-lifecycle-fVqvCdKM.js → issue-lifecycle-Bc1l3U9f.js} +7 -7
  148. package/dist/{issue-lifecycle-fVqvCdKM.js.map → issue-lifecycle-Bc1l3U9f.js.map} +1 -1
  149. package/dist/{issue-service-singleton-CZm8p6Rj.js → issue-service-singleton-D-8Q6tdT.js} +1 -1
  150. package/dist/{issue-service-singleton-Cr73JAaR.js → issue-service-singleton-D1433I7n.js} +2 -2
  151. package/dist/{issue-service-singleton-Cr73JAaR.js.map → issue-service-singleton-D1433I7n.js.map} +1 -1
  152. package/dist/{lifecycle-CR6tZfl1.js → lifecycle-SmrqysDf.js} +7 -7
  153. package/dist/{lifecycle-CR6tZfl1.js.map → lifecycle-SmrqysDf.js.map} +1 -1
  154. package/dist/{lifecycle-D_EWiUjk.js → lifecycle-mRMyiIXs.js} +2 -2
  155. package/dist/{merge-agent-LM4avOtA.js → merge-agent-B9hIZ6Z0.js} +6 -6
  156. package/dist/{merge-agent-LM4avOtA.js.map → merge-agent-B9hIZ6Z0.js.map} +1 -1
  157. package/dist/{merge-agent-Bj046HE1.js → merge-agent-DUj22cXL.js} +1 -1
  158. package/dist/{merge-fulfillment-DrnC1Kmg.js → merge-fulfillment-__P4WE-T.js} +3 -3
  159. package/dist/{merge-fulfillment-DrnC1Kmg.js.map → merge-fulfillment-__P4WE-T.js.map} +1 -1
  160. package/dist/{reconstruct-cache-NPqoAUg-.js → reconstruct-cache-9SWp82SQ.js} +3 -3
  161. package/dist/{reconstruct-cache-NPqoAUg-.js.map → reconstruct-cache-9SWp82SQ.js.map} +1 -1
  162. package/dist/{records-BIkN1LQ4.js → records-CTmvuRzz.js} +14 -1
  163. package/dist/{records-BIkN1LQ4.js.map → records-CTmvuRzz.js.map} +1 -1
  164. package/dist/records-backfill-BQcmTmwF.js +2 -0
  165. package/dist/{records-backfill-HM0Pln6N.js → records-backfill-BrNsv_zT.js} +2 -2
  166. package/dist/{records-backfill-HM0Pln6N.js.map → records-backfill-BrNsv_zT.js.map} +1 -1
  167. package/dist/resume-Bn4kDk9x.js +3 -0
  168. package/dist/{review-agent-DFosQzCG.js → review-agent-PhqOhpx3.js} +24 -16
  169. package/dist/{review-agent-DFosQzCG.js.map → review-agent-PhqOhpx3.js.map} +1 -1
  170. package/dist/review-agent-hLbRRxZF.js +3 -0
  171. package/dist/{review-status-iJwfEoNX.js → review-status-B_XXomV0.js} +6 -6
  172. package/dist/{review-status-iJwfEoNX.js.map → review-status-B_XXomV0.js.map} +1 -1
  173. package/dist/{review-status-BbNVlXh1.js → review-status-Bo-EFVKh.js} +1 -1
  174. package/dist/{review-verdict-feedback-BGPw3z1X.js → review-verdict-feedback-CXHkoiKZ.js} +1 -1
  175. package/dist/{review-verdict-feedback-CCZ05atV.js → review-verdict-feedback-DGV13LDb.js} +4 -4
  176. package/dist/{review-verdict-feedback-CCZ05atV.js.map → review-verdict-feedback-DGV13LDb.js.map} +1 -1
  177. package/dist/{sequence-io-CcxZNHcD.js → sequence-io-Cg4NHAco.js} +1 -1
  178. package/dist/{sequence-io-Cx1usxG3.js → sequence-io-CyxvNHfA.js} +2 -2
  179. package/dist/{sequence-io-Cx1usxG3.js.map → sequence-io-CyxvNHfA.js.map} +1 -1
  180. package/dist/{smart-compaction-ByUEgQkF.js → smart-compaction-ByKXTQNy.js} +2 -2
  181. package/dist/{smart-compaction-ByUEgQkF.js.map → smart-compaction-ByKXTQNy.js.map} +1 -1
  182. package/dist/{smart-compaction-DfLe_CtH.js → smart-compaction-U6e_uB1E.js} +1 -1
  183. package/dist/{specialist-context-McuGkaeR.js → specialist-context-BUTadO-U.js} +3 -3
  184. package/dist/{specialist-context-McuGkaeR.js.map → specialist-context-BUTadO-U.js.map} +1 -1
  185. package/dist/{specialist-logs-CUUJ4eiB.js → specialist-logs-pEMCTtGZ.js} +2 -2
  186. package/dist/{specialist-logs-CUUJ4eiB.js.map → specialist-logs-pEMCTtGZ.js.map} +1 -1
  187. package/dist/{specialists-BSrylD1S.js → specialists-BGFnrQ8n.js} +1 -1
  188. package/dist/{specialists-B0elgInc.js → specialists-DeTkkp8b.js} +12 -12
  189. package/dist/{specialists-B0elgInc.js.map → specialists-DeTkkp8b.js.map} +1 -1
  190. package/dist/{summary-fork-DskMRnyb.js → summary-fork-CgzThE2-.js} +2 -2
  191. package/dist/{summary-fork-CPUTKcAT.js → summary-fork-Dx1RMuvU.js} +3 -3
  192. package/dist/{summary-fork-CPUTKcAT.js.map → summary-fork-Dx1RMuvU.js.map} +1 -1
  193. package/dist/{sync-BiNVQHBf.js → sync-Bsww9tuj.js} +2 -2
  194. package/dist/{sync-BiNVQHBf.js.map → sync-Bsww9tuj.js.map} +1 -1
  195. package/dist/sync-CylE_SjB.js +2 -0
  196. package/dist/{verdict-restore-DQiV7_fe.js → verdict-restore-DGpX0ZOJ.js} +5 -5
  197. package/dist/{verdict-restore-DQiV7_fe.js.map → verdict-restore-DGpX0ZOJ.js.map} +1 -1
  198. package/dist/{webhook-handlers-DHBSEDEm.js → webhook-handlers-BjsfIL7u.js} +3 -3
  199. package/dist/{webhook-handlers-DHBSEDEm.js.map → webhook-handlers-BjsfIL7u.js.map} +1 -1
  200. package/dist/{workspace-migrate-duUsWNpd.js → workspace-migrate-BBQ8nAnP.js} +2 -2
  201. package/dist/{workspace-migrate-duUsWNpd.js.map → workspace-migrate-BBQ8nAnP.js.map} +1 -1
  202. package/dist/{workspace-reap-DLCIoJmn.js → workspace-reap-12O9354k.js} +2 -2
  203. package/dist/{workspace-reap-DLCIoJmn.js.map → workspace-reap-12O9354k.js.map} +1 -1
  204. package/package.json +1 -1
  205. package/scripts/file-size-baseline.txt +1 -1
  206. package/dist/cli-DBWG12XK.js +0 -3
  207. package/dist/dashboard/cli-CJDWXE_Y.js +0 -3
  208. package/dist/dashboard/deacon-swarm-D1iAUX-P.js +0 -2
  209. package/dist/dashboard/inspect-agent-vm88mLYr.js +0 -2
  210. package/dist/dashboard/merge-ops-QdBiQ8XB.js +0 -2
  211. package/dist/dashboard/resume-DnIsmIgC.js +0 -3
  212. package/dist/dashboard/review-agent-DgySDb-G.js +0 -3
  213. package/dist/dashboard/review-agent-phzqNqIs.js.map +0 -1
  214. package/dist/dashboard/review-status-BQjqQXZG.js.map +0 -1
  215. package/dist/dashboard/workflows-Cz8OtiB4.js +0 -2
  216. package/dist/records-backfill-DEgtRyKL.js +0 -2
  217. package/dist/resume-DGymuXZM.js +0 -3
  218. package/dist/review-agent-DjVadURa.js +0 -3
  219. package/dist/sync-4jqukkxz.js +0 -2
@@ -1 +1 @@
1
- {"version":3,"file":"workspaces-CRU9_vRn.js","names":["resolve","join","sep","existsSync","join","existsSync","dirname","execAsync","join","dirname","readFileSync","repairFlywayIfNeeded","existsSync","statSync","execAsync","existsSync","join","basename","execAsync","getAgentsDir","getClaudeProjectsDir","inferIssueId","recordCostEventsThroughOverdeck","toOverdeckCostEvent","readdir","readFile","execAsync","execFileAsync","join","existsSync","basename","dirname","resolveGitHubIssueShared","getAllActiveQueues","execAsync","execFileAsync","join","stat","existsSync","execAsync","existsSync","join","execAsync","join","execAsync","join","execFileAsync","join","existsSync","dirname","join","homedir","existsSync","loadConfigSync","dirname","setReviewStatusBase"],"sources":["../../src/dashboard/server/workspace-paths.ts","../../src/dashboard/server/routes/dashboard-auth.ts","../../src/dashboard/server/services/issue-cost-resolver.ts","../../src/dashboard/server/services/running-agents-cache.ts","../../src/dashboard/server/pending-feedback.ts","../../src/lib/db-provisioners/types.ts","../../src/lib/db-provisioners/flyway-postgres.ts","../../src/lib/db-provisioners/index.ts","../../src/dashboard/server/routes/workspaces/container-ops.ts","../../src/lib/stashes.ts","../../src/lib/costs/aggregator.ts","../../src/lib/costs/migration.ts","../../src/lib/costs/attribution.ts","../../src/lib/costs/reconciler.ts","../../src/lib/costs/retention.ts","../../src/lib/costs/sync-wal.ts","../../src/lib/costs/index.ts","../../src/dashboard/server/services/merge-queue-service.ts","../../src/dashboard/server/routes/workspaces/git-recovery-advice.ts","../../src/dashboard/server/routes/workspaces/merge-ops.ts","../../src/dashboard/server/routes/workspaces/workspace-data.ts","../../src/dashboard/server/routes/workspaces/stash-clean.ts","../../src/dashboard/server/routes/workspaces/review-pipeline.ts","../../src/dashboard/server/routes/workspaces/review-control.ts","../../src/dashboard/server/routes/workspaces/uat-stack-actions.ts","../../src/dashboard/server/routes/workspaces.ts"],"sourcesContent":["import { join, resolve, sep } from 'node:path';\n\nimport { parseIssueIdSync } from '../../lib/issue-id.js';\n\nexport function getWorkspacePathForIssue(projectPath: string, rawIssueId: string): { parsedIssueId: string; workspacePath: string } {\n const parsed = parseIssueIdSync(rawIssueId);\n if (!parsed) {\n throw new Error('Invalid issue ID');\n }\n\n const workspaceRoot = resolve(join(projectPath, 'workspaces'));\n const workspacePath = resolve(join(workspaceRoot, `feature-${parsed.normalized}`));\n\n if (workspacePath !== workspaceRoot && !workspacePath.startsWith(`${workspaceRoot}${sep}`)) {\n throw new Error('Invalid workspace path');\n }\n\n return {\n parsedIssueId: parsed.raw,\n workspacePath,\n };\n}\n","import { createHash, createHmac, randomBytes, timingSafeEqual } from 'node:crypto';\nimport { existsSync } from 'node:fs';\nimport { networkInterfaces, type NetworkInterfaceInfo } from 'node:os';\n\nimport { Option } from 'effect';\nimport { HttpServerRequest, HttpServerResponse } from 'effect/unstable/http';\n\nimport { getInternalTokenSync, INTERNAL_TOKEN_HEADER } from '../../../lib/internal-token.js';\nimport { jsonResponse } from '../http-helpers.js';\nimport { getHeaderFromMap, getTrustedOrigins, normalizeOrigin, type HeaderMap } from './origin-validation.js';\n\nexport const DASHBOARD_SESSION_COOKIE = 'overdeck_session';\nexport const DASHBOARD_CSRF_HEADER = 'x-overdeck-csrf-token';\n// Session cookie lifetime. Without Max-Age the cookie was a *session* cookie that\n// died when the browser fully closed — so a reopened tab on a trusted origin had\n// no cookie, its mint 401'd, and every mutation failed with \"CSRF token\n// unavailable\" until the operator re-bootstrapped via the one-time URL token.\n// A 30-day rolling expiry means the operator bootstraps once per browser.\nconst DASHBOARD_SESSION_MAX_AGE_SECONDS = 60 * 60 * 24 * 30;\n\nlet browserSessionToken: string | undefined;\nlet browserCsrfToken: string | undefined;\n\nexport function _resetDashboardSessionTokenForTests(): void {\n browserSessionToken = undefined;\n browserCsrfToken = undefined;\n}\n\nfunction getDashboardSessionToken(): string {\n if (browserSessionToken) return browserSessionToken;\n const override = process.env['OVERDECK_DASHBOARD_SESSION_TOKEN'];\n if (override) {\n browserSessionToken = override;\n return browserSessionToken;\n }\n // Derive a stable session token from the persisted internal token so the\n // browser's session cookie survives dashboard restarts. Without this the\n // token was random per-process, so every restart invalidated open tabs'\n // cookies — and a plain refresh could no longer re-mint, since the one-time\n // bootstrap hash token is already consumed on first load. Falls back to a\n // random token only when no internal token is configured, in which case the\n // auth gate already 503s before the session token is ever consulted.\n const internal = getInternalTokenSync();\n browserSessionToken = internal\n ? createHmac('sha256', internal).update('overdeck-dashboard-session-v1').digest('base64url')\n : randomBytes(32).toString('base64url');\n return browserSessionToken;\n}\n\nexport function dashboardCsrfToken(): string {\n if (browserCsrfToken) return browserCsrfToken;\n const override = process.env['OVERDECK_DASHBOARD_CSRF_TOKEN'];\n if (override) {\n browserCsrfToken = override;\n return browserCsrfToken;\n }\n // Same restart-survival derivation as the session token above: the frontend\n // caches this token once per page load (wsTransport session mint), so a\n // random-per-process value 403'd every mutation from open tabs after each\n // dashboard restart — and the flywheel restarts the dashboard on every\n // post-merge deploy. Distinct context string keeps it independent of the\n // session token.\n const internal = getInternalTokenSync();\n browserCsrfToken = internal\n ? createHmac('sha256', internal).update('overdeck-dashboard-csrf-v1').digest('base64url')\n : randomBytes(32).toString('base64url');\n return browserCsrfToken;\n}\n\nfunction getHeader(\n request: HttpServerRequest.HttpServerRequest,\n name: string,\n): string | undefined {\n return getHeaderFromMap(request.headers as HeaderMap, name);\n}\n\nfunction constantTimeTokenEqual(provided: string | undefined, expected: string): boolean {\n if (!provided) return false;\n const providedHash = createHash('sha256').update(provided).digest();\n const expectedHash = createHash('sha256').update(expected).digest();\n return timingSafeEqual(providedHash, expectedHash);\n}\n\nfunction cookieValue(cookieHeader: string | undefined, name: string): string | undefined {\n if (!cookieHeader) return undefined;\n for (const part of cookieHeader.split(';')) {\n const [rawKey, ...rawValue] = part.trim().split('=');\n if (rawKey !== name) continue;\n try {\n return decodeURIComponent(rawValue.join('='));\n } catch {\n return undefined;\n }\n }\n return undefined;\n}\n\nexport function dashboardSessionCookieHeader(options: { secure?: boolean } = {}): string {\n const secure = options.secure ? '; Secure' : '';\n return `${DASHBOARD_SESSION_COOKIE}=${encodeURIComponent(getDashboardSessionToken())}; Path=/; HttpOnly; SameSite=Strict; Max-Age=${DASHBOARD_SESSION_MAX_AGE_SECONDS}${secure}`;\n}\n\nexport function hasDashboardInternalTokenHeaders(headers: HeaderMap): boolean {\n const expected = getInternalTokenSync();\n if (!expected) return false;\n\n const internalHeader = getHeaderFromMap(headers, INTERNAL_TOKEN_HEADER);\n if (constantTimeTokenEqual(internalHeader, expected)) return true;\n\n const authorization = getHeaderFromMap(headers, 'authorization');\n if (authorization) {\n const [scheme, token] = authorization.split(/\\s+/);\n if (scheme?.toLowerCase() === 'bearer' && constantTimeTokenEqual(token, expected)) return true;\n }\n\n return false;\n}\n\nexport function hasDashboardInternalToken(request: HttpServerRequest.HttpServerRequest): boolean {\n return hasDashboardInternalTokenHeaders(request.headers as HeaderMap);\n}\n\nexport function hasDashboardAuthHeaders(headers: HeaderMap): boolean {\n return hasDashboardInternalTokenHeaders(headers) || constantTimeTokenEqual(cookieValue(getHeaderFromMap(headers, 'cookie'), DASHBOARD_SESSION_COOKIE), getDashboardSessionToken());\n}\n\nexport function hasDashboardAuth(request: HttpServerRequest.HttpServerRequest): boolean {\n return hasDashboardAuthHeaders(request.headers as HeaderMap);\n}\n\nfunction isJsonContentType(headers: HeaderMap): boolean {\n const contentType = getHeaderFromMap(headers, 'content-type');\n if (!contentType) return false;\n const [mime] = contentType.toLowerCase().split(';');\n return mime.trim() === 'application/json';\n}\n\nfunction hasTrustedExactOrigin(headers: HeaderMap): boolean {\n const origin = getHeaderFromMap(headers, 'origin');\n if (!origin) return false;\n const normalized = normalizeOrigin(origin);\n return !!normalized && getTrustedOrigins().includes(normalized);\n}\n\nfunction hasValidCsrfToken(headers: HeaderMap): boolean {\n return constantTimeTokenEqual(getHeaderFromMap(headers, DASHBOARD_CSRF_HEADER), dashboardCsrfToken());\n}\n\nexport function rejectUnsafeDashboardMutationRequest(\n request: HttpServerRequest.HttpServerRequest,\n): HttpServerResponse.HttpServerResponse | null {\n const authError = rejectUnauthorizedDashboardRequest(request);\n if (authError) return authError;\n\n const headers = request.headers as HeaderMap;\n if (!isJsonContentType(headers)) {\n return jsonResponse({ error: 'Content-Type must be application/json' }, { status: 400 });\n }\n\n if (hasDashboardInternalToken(request)) return null;\n\n const origin = getHeaderFromMap(headers, 'origin');\n if (origin && !hasTrustedExactOrigin(headers)) {\n return jsonResponse({ error: 'Invalid origin' }, { status: 403 });\n }\n if (!hasValidCsrfToken(headers)) {\n return jsonResponse({ error: 'Invalid CSRF token' }, { status: 403 });\n }\n return null;\n}\n\nfunction ipv4ToInt(value: string): number | null {\n const parts = value.split('.');\n if (parts.length !== 4) return null;\n let result = 0;\n for (const part of parts) {\n const octet = Number(part);\n if (!Number.isInteger(octet) || octet < 0 || octet > 255) return null;\n result = ((result << 8) | octet) >>> 0;\n }\n return result;\n}\n\n/** RFC1918 private ranges — Docker bridges always fall inside one of these. */\nfunction isRfc1918(ip: number): boolean {\n return (\n (ip & 0xff000000) >>> 0 === 0x0a000000 || // 10.0.0.0/8\n (ip & 0xfff00000) >>> 0 === 0xac100000 || // 172.16.0.0/12\n (ip & 0xffff0000) >>> 0 === 0xc0a80000 // 192.168.0.0/16\n );\n}\n\n/**\n * True when `addr` is the IP of a container on one of THIS host's Docker bridge\n * interfaces (docker0 / br-*). Such a peer can only be a process running on this\n * machine — our own host-local Traefik fronting overdeck.localhost, or another\n * Overdeck container — never a LAN client (a direct LAN hit to the 0.0.0.0\n * API port arrives with a LAN peer that is not inside any Docker bridge subnet).\n *\n * Pure for testability: the interface map is injected. We only ever trust\n * RFC1918 bridge subnets, so a misconfigured public-range br-* cannot widen\n * trust beyond the local Docker fabric.\n */\nexport function peerIsHostLocalDockerBridge(\n addr: string,\n interfaces: NodeJS.Dict<NetworkInterfaceInfo[]>,\n): boolean {\n const peer = ipv4ToInt(addr);\n if (peer === null) return false;\n for (const [name, addrs] of Object.entries(interfaces)) {\n if (name !== 'docker0' && !name.startsWith('br-')) continue;\n for (const info of addrs ?? []) {\n if (info.family !== 'IPv4') continue;\n const ifaceIp = ipv4ToInt(info.address);\n const mask = ipv4ToInt(info.netmask);\n if (ifaceIp === null || mask === null) continue;\n const network = (ifaceIp & mask) >>> 0;\n if (!isRfc1918(network)) continue;\n if (((peer & mask) >>> 0) === network) return true;\n }\n }\n return false;\n}\n\n/**\n * True when `addr` shares an RFC1918 subnet with one of THIS process's own\n * network interfaces. This is the in-container counterpart of\n * peerIsHostLocalDockerBridge: inside a container the Docker bridge is\n * invisible — the container sees only its veth endpoints (eth0/eth1), so the\n * docker0/br-* name filter never matches and the host-local Traefik fronting\n * the stack 401'd every session mint (first hit live on the PAN-1737 UAT\n * stack). A peer inside one of the container's attached Docker-network\n * subnets can only be another container on those networks — the server\n * publishes no host port (Traefik labels only), so no LAN client can ever\n * present such an address. Only consulted when running inside a container.\n */\nexport function peerIsLocalContainerNetwork(\n addr: string,\n interfaces: NodeJS.Dict<NetworkInterfaceInfo[]>,\n): boolean {\n const peer = ipv4ToInt(addr);\n if (peer === null) return false;\n for (const addrs of Object.values(interfaces)) {\n for (const info of addrs ?? []) {\n if (info.family !== 'IPv4' || info.internal) continue;\n const ifaceIp = ipv4ToInt(info.address);\n const mask = ipv4ToInt(info.netmask);\n if (ifaceIp === null || mask === null) continue;\n const network = (ifaceIp & mask) >>> 0;\n if (!isRfc1918(network)) continue;\n if (((peer & mask) >>> 0) === network) return true;\n }\n }\n return false;\n}\n\n/** Docker creates /.dockerenv in every container — stable in-container signal. */\nconst runningInContainer = existsSync('/.dockerenv');\n\n/**\n * A request we trust to mint a session because it originated from this machine —\n * the local browser hitting the dashboard directly (127.0.0.1), or the\n * host-local Traefik that fronts overdeck.localhost. In Docker, that Traefik reaches\n * the host server from a Docker-bridge IP (e.g. 172.18.0.2), NOT 127.0.0.1, so\n * a literal-loopback check alone silently breaks the zero-step bootstrap for\n * every overdeck.localhost user — their session mint 401s and no cookie is ever set.\n *\n * This is the auto-bootstrap that removes the manual one-time #overdeck_token\n * step: any browser on overdeck.localhost gets a session with no user action. It is\n * NOT a security downgrade for the LAN — the raw API ports bind 0.0.0.0, but a\n * direct LAN hit has a non-loopback, non-Docker-bridge peer and is rejected. We\n * read ONLY the real TCP peer (request.remoteAddress), never X-Forwarded-For,\n * which a caller could spoof.\n */\nfunction isLoopbackPeer(request: HttpServerRequest.HttpServerRequest): boolean {\n const remoteAddress = (request as { remoteAddress?: unknown }).remoteAddress;\n const raw = remoteAddress && typeof remoteAddress === 'object' && '_tag' in remoteAddress\n ? Option.getOrElse(remoteAddress as Option.Option<string>, () => '')\n : typeof remoteAddress === 'string'\n ? remoteAddress\n : '';\n if (!raw) return false;\n // Node reports dual-stack IPv4 peers as IPv4-mapped IPv6 (::ffff:a.b.c.d).\n const addr = raw.startsWith('::ffff:') ? raw.slice('::ffff:'.length) : raw;\n if (addr === '127.0.0.1' || addr === '::1') return true;\n if (peerIsHostLocalDockerBridge(addr, networkInterfaces())) return true;\n return runningInContainer && peerIsLocalContainerNetwork(addr, networkInterfaces());\n}\n\nexport function rejectUnauthorizedDashboardSessionMintRequest(\n request: HttpServerRequest.HttpServerRequest,\n): HttpServerResponse.HttpServerResponse | null {\n const expected = getInternalTokenSync();\n if (!expected) {\n return jsonResponse({ error: 'dashboard session token not configured' }, { status: 503 });\n }\n if (!hasDashboardInternalToken(request) && !isLoopbackPeer(request)) {\n return jsonResponse({ error: 'unauthorized' }, { status: 401 });\n }\n return null;\n}\n\nexport function rejectUnauthorizedDashboardRequest(\n request: HttpServerRequest.HttpServerRequest,\n): HttpServerResponse.HttpServerResponse | null {\n const expected = getInternalTokenSync();\n if (!expected) {\n return jsonResponse({ error: 'dashboard session token not configured' }, { status: 503 });\n }\n if (!hasDashboardAuth(request)) {\n return jsonResponse({ error: 'unauthorized' }, { status: 401 });\n }\n return null;\n}\n","import { AgentStatus, type AgentSnapshot } from '@overdeck/contracts';\n\ninterface ResolveIssueHeadlineCostOptions {\n issueId: string;\n aggregateCost?: number;\n agents: ReadonlyArray<Pick<AgentSnapshot, 'issueId' | 'costSoFar' | 'status'>>;\n}\n\nexport interface ResolvedIssueHeadlineCost {\n aggregateCost: number | null;\n liveCost: number | null;\n resolvedTotalCost: number | null;\n}\n\nconst LIVE_AGENT_STATUSES = new Set<AgentStatus>(['starting', 'running']);\n\nfunction normalizeIssueId(issueId: string): string {\n return issueId.trim().toUpperCase();\n}\n\nfunction normalizePositiveCost(value: number | null | undefined): number | null {\n return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : null;\n}\n\nexport function getLiveIssueCostForIssue(\n issueId: string,\n agents: ReadonlyArray<Pick<AgentSnapshot, 'issueId' | 'costSoFar' | 'status'>>,\n): number | null {\n const normalizedIssueId = normalizeIssueId(issueId);\n let maxLiveCost: number | null = null;\n\n for (const agent of agents) {\n if (normalizeIssueId(agent.issueId) !== normalizedIssueId) continue;\n if (!LIVE_AGENT_STATUSES.has(agent.status)) continue;\n const candidate = normalizePositiveCost(agent.costSoFar);\n if (candidate === null) continue;\n if (maxLiveCost === null || candidate > maxLiveCost) {\n maxLiveCost = candidate;\n }\n }\n\n return maxLiveCost;\n}\n\nexport function resolveIssueHeadlineCost({\n issueId,\n aggregateCost,\n agents,\n}: ResolveIssueHeadlineCostOptions): ResolvedIssueHeadlineCost {\n const normalizedAggregateCost = normalizePositiveCost(aggregateCost);\n const liveCost = getLiveIssueCostForIssue(issueId, agents);\n\n return {\n aggregateCost: normalizedAggregateCost,\n liveCost,\n resolvedTotalCost:\n normalizedAggregateCost === null\n ? liveCost\n : liveCost === null\n ? normalizedAggregateCost\n : Math.max(normalizedAggregateCost, liveCost),\n };\n}\n","import { Effect } from 'effect';\nimport { listRunningAgents, type AgentState } from '../../../lib/agents.js';\n\ntype RunningAgent = AgentState & { tmuxActive: boolean };\ntype RunningAgents = RunningAgent[];\ntype ListAgentsProgram = () => Effect.Effect<RunningAgents, unknown>;\n\nconst RUNNING_AGENTS_CACHE_TTL_MS = 3_000;\nconst GLOBAL_RUNNING_AGENTS_CACHE_KEY = '__all_agents__';\n\nconst runningAgentsCache = new Map<string, {\n timestamp: number;\n agents: RunningAgents;\n}>();\nlet inflightRunningAgents: Promise<RunningAgents> | null = null;\n\nfunction sweepExpired<T extends { timestamp: number }>(cache: Map<string, T>, ttlMs: number): void {\n const cutoff = Date.now() - ttlMs;\n for (const [key, entry] of cache) {\n if (entry.timestamp < cutoff) {\n cache.delete(key);\n }\n }\n}\n\nexport async function getCachedRunningAgents(\n listAgents: ListAgentsProgram = listRunningAgents,\n): Promise<RunningAgents> {\n sweepExpired(runningAgentsCache, RUNNING_AGENTS_CACHE_TTL_MS);\n const cached = runningAgentsCache.get(GLOBAL_RUNNING_AGENTS_CACHE_KEY);\n if (cached && cached.timestamp > Date.now() - RUNNING_AGENTS_CACHE_TTL_MS) {\n return cached.agents;\n }\n\n if (!inflightRunningAgents) {\n inflightRunningAgents = Effect.runPromise(listAgents()).then((agents) => {\n runningAgentsCache.set(GLOBAL_RUNNING_AGENTS_CACHE_KEY, {\n timestamp: Date.now(),\n agents,\n });\n return agents;\n }).finally(() => {\n inflightRunningAgents = null;\n });\n }\n\n return inflightRunningAgents;\n}\n\nexport function clearRunningAgentsCache(): void {\n runningAgentsCache.clear();\n inflightRunningAgents = null;\n}\n","/**\n * Pending feedback recovery for dashboard restarts (PAN-585).\n *\n * Feedback files and review-status rows are persistent, but tmux delivery is not.\n * When the dashboard dies after writing feedback and before messageAgent() completes,\n * startup replays the queued delivery so the work agent is notified.\n */\n\nimport { existsSync } from 'node:fs';\nimport { mkdir, readFile, unlink, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { Effect } from 'effect';\nimport { messageAgent, getAgentState, type AgentState } from '../../lib/agents.js';\nimport { getReviewStatusSync, loadReviewStatuses, type ReviewStatus } from '../../lib/review-status.js';\nimport { getOverdeckHome } from '../../lib/paths.js';\nimport { emitActivityEntrySync } from '../../lib/activity-logger.js';\n\nconst PENDING_FEEDBACK_FILE = join(getOverdeckHome(), 'pending-feedback-deliveries.json');\nconst STALE_THRESHOLD_MS = 7 * 24 * 60 * 60 * 1000;\n\ntype FeedbackKind = 'review-blocked' | 'review-failed' | 'test-failed';\n\nexport interface PendingFeedbackDelivery {\n issueId: string;\n agentId: string;\n kind: FeedbackKind;\n filePath: string;\n message: string;\n createdAt: string;\n}\n\ninterface PendingFeedbackStore {\n deliveries: PendingFeedbackDelivery[];\n}\n\nasync function readStore(filePath: string): Promise<PendingFeedbackStore> {\n if (!existsSync(filePath)) {\n return { deliveries: [] };\n }\n\n try {\n const raw = await readFile(filePath, 'utf-8');\n if (!raw.trim()) return { deliveries: [] };\n const parsed = JSON.parse(raw) as PendingFeedbackStore;\n return { deliveries: Array.isArray(parsed.deliveries) ? parsed.deliveries : [] };\n } catch {\n return { deliveries: [] };\n }\n}\n\nasync function writeStore(filePath: string, store: PendingFeedbackStore): Promise<void> {\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(filePath, JSON.stringify(store, null, 2), 'utf-8');\n}\n\nfunction isDeliveryStillRelevant(delivery: PendingFeedbackDelivery, status: ReviewStatus | null | undefined): boolean {\n switch (delivery.kind) {\n case 'review-blocked':\n return status?.reviewStatus === 'blocked';\n case 'review-failed':\n return status?.reviewStatus === 'failed';\n case 'test-failed':\n return status?.testStatus === 'failed';\n default:\n return false;\n }\n}\n\nexport async function enqueuePendingFeedbackDelivery(\n delivery: PendingFeedbackDelivery,\n options?: { filePath?: string }\n): Promise<void> {\n const filePath = options?.filePath ?? PENDING_FEEDBACK_FILE;\n const store = await readStore(filePath);\n store.deliveries = store.deliveries.filter(\n (existing) => !(existing.issueId === delivery.issueId && existing.kind === delivery.kind)\n );\n store.deliveries.push(delivery);\n await writeStore(filePath, store);\n}\n\nexport async function markPendingFeedbackDelivered(\n issueId: string,\n kind: FeedbackKind,\n options?: { filePath?: string }\n): Promise<void> {\n const filePath = options?.filePath ?? PENDING_FEEDBACK_FILE;\n const store = await readStore(filePath);\n const next = store.deliveries.filter(\n (delivery) => !(delivery.issueId === issueId && delivery.kind === kind)\n );\n\n if (next.length === store.deliveries.length) return;\n\n if (next.length === 0) {\n if (existsSync(filePath)) {\n await unlink(filePath).catch(() => {});\n }\n return;\n }\n\n await writeStore(filePath, { deliveries: next });\n}\n\nexport async function processPendingFeedbackDeliveries(options?: {\n filePath?: string;\n staleThresholdMs?: number;\n now?: number;\n _deliver?: (agentId: string, message: string) => Promise<void>;\n _getAgentState?: (agentId: string) => Promise<AgentState | null>;\n _loadStatuses?: typeof loadReviewStatuses;\n _getStatus?: typeof getReviewStatusSync;\n}): Promise<void> {\n const filePath = options?.filePath ?? PENDING_FEEDBACK_FILE;\n const staleThresholdMs = options?.staleThresholdMs ?? STALE_THRESHOLD_MS;\n const now = options?.now ?? Date.now();\n const deliver = options?._deliver ?? messageAgent;\n const getAgentState = options?._getAgentState ?? ((agentId: string) => Effect.runPromise(getAgentState(agentId)));\n const loadStatuses = options?._loadStatuses ?? loadReviewStatuses;\n const getStatus = options?._getStatus ?? getReviewStatusSync;\n\n if (!existsSync(filePath)) return;\n\n const store = await readStore(filePath);\n if (store.deliveries.length === 0) {\n await unlink(filePath).catch(() => {});\n return;\n }\n\n const statuses = loadStatuses();\n const remaining: PendingFeedbackDelivery[] = [];\n\n for (const delivery of store.deliveries) {\n const createdAt = Date.parse(delivery.createdAt);\n if (Number.isFinite(createdAt) && now - createdAt > staleThresholdMs) {\n continue;\n }\n\n const status = statuses[delivery.issueId] ?? getStatus(delivery.issueId);\n if (!isDeliveryStillRelevant(delivery, status)) {\n continue;\n }\n\n const agentState = await getAgentState(delivery.agentId);\n if (!agentState) {\n remaining.push(delivery);\n continue;\n }\n\n try {\n await deliver(delivery.agentId, delivery.message);\n emitActivityEntrySync({\n source: 'dashboard',\n level: 'warn',\n message: `${delivery.issueId} — replayed missed ${delivery.kind} feedback after restart`,\n issueId: delivery.issueId,\n details: delivery.filePath,\n });\n } catch {\n remaining.push(delivery);\n }\n }\n\n if (remaining.length === 0) {\n await unlink(filePath).catch(() => {});\n return;\n }\n\n await writeStore(filePath, { deliveries: remaining });\n}\n","import type { ProjectConfig, DatabaseConfig } from '../workspace-config.js';\n\nexport interface DatabaseProvisionerLogger {\n setText(message: string): void;\n info(message: string): void;\n warn(message: string): void;\n fail(message: string): void;\n succeed(message: string): void;\n log(message: string): void;\n}\n\nexport interface DatabaseProvisionerErrorOptions {\n status?: number;\n}\n\nexport class DatabaseProvisionerError extends Error {\n readonly status: number;\n\n constructor(message: string, options: DatabaseProvisionerErrorOptions = {}) {\n super(message);\n this.name = 'DatabaseProvisionerError';\n this.status = options.status ?? 500;\n }\n}\n\nexport interface RefreshDatabaseContext {\n issueId: string;\n projectConfig: ProjectConfig;\n workspacePath: string;\n pgContainer: string;\n apiContainer: string;\n databaseName: string;\n seedFile: string;\n seedVerifyQuery?: string;\n logger?: DatabaseProvisionerLogger;\n}\n\nexport interface RefreshDatabaseResult {\n seedVerifyResult?: string;\n}\n\nexport interface SeedDatabaseContext {\n workspaceOrIssue: string;\n projectConfig: ProjectConfig;\n workspacePath: string;\n pgContainer: string;\n databaseName: string;\n seedFile: string;\n force?: boolean;\n logger?: DatabaseProvisionerLogger;\n}\n\nexport interface RepairMigrationsContext {\n issueId: string;\n projectConfig: ProjectConfig;\n workspacePath: string;\n pgContainer: string;\n databaseName: string;\n logger?: DatabaseProvisionerLogger;\n log?: (message: string) => void;\n}\n\nexport interface RepairMigrationsResult {\n repaired: boolean;\n message: string;\n}\n\nexport interface SnapshotDatabaseContext {\n projectConfig: ProjectConfig;\n dbConfig: DatabaseConfig;\n output?: string;\n sanitize?: boolean;\n logger?: DatabaseProvisionerLogger;\n}\n\nexport interface SnapshotDatabaseResult {\n outputPath: string;\n sizeBytes: number;\n}\n\nexport interface CleanSnapshotContext {\n file: string;\n output?: string;\n dryRun?: boolean;\n logger?: DatabaseProvisionerLogger;\n}\n\nexport interface CleanSnapshotResult {\n outputPath?: string;\n originalLines: number;\n cleanedLines: number;\n removedLines: number;\n removedSample: string[];\n}\n\nexport interface StatusDatabaseContext {\n projectConfig: ProjectConfig;\n pgContainer: string;\n databaseName: string;\n logger?: DatabaseProvisionerLogger;\n}\n\nexport interface DatabaseProvisioner {\n readonly id: string;\n validateRefreshDatabase(ctx: Pick<RefreshDatabaseContext, 'seedFile'>): void;\n refreshDatabase(ctx: RefreshDatabaseContext): Promise<RefreshDatabaseResult>;\n seed(ctx: SeedDatabaseContext): Promise<void>;\n repairMigrations(ctx: RepairMigrationsContext): Promise<RepairMigrationsResult>;\n snapshot(ctx: SnapshotDatabaseContext): Promise<SnapshotDatabaseResult>;\n cleanSnapshot(ctx: CleanSnapshotContext): Promise<CleanSnapshotResult>;\n status(ctx: StatusDatabaseContext): Promise<void>;\n}\n","import { exec } from 'node:child_process';\nimport { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';\nimport { readdir, readFile, unlink, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { promisify } from 'node:util';\nimport { crc32 } from 'node:zlib';\n\nimport type { DatabaseConfig } from '../workspace-config.js';\nimport {\n DatabaseProvisionerError,\n type CleanSnapshotContext,\n type CleanSnapshotResult,\n type DatabaseProvisioner,\n type DatabaseProvisionerLogger,\n type RefreshDatabaseContext,\n type RefreshDatabaseResult,\n type RepairMigrationsContext,\n type RepairMigrationsResult,\n type SeedDatabaseContext,\n type SnapshotDatabaseContext,\n type SnapshotDatabaseResult,\n type StatusDatabaseContext,\n} from './types.js';\n\nconst execAsync = promisify(exec);\n\nfunction shellQuote(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\n\nfunction sqlIdentifier(value: string): string {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n}\n\nfunction sqlString(value: string): string {\n return value.replace(/'/g, \"''\");\n}\n\nfunction defaultLogger(prefix: string): DatabaseProvisionerLogger {\n return {\n setText(message) { console.log(`${prefix} ${message}`); },\n info(message) { console.log(`${prefix} ${message}`); },\n warn(message) { console.warn(`${prefix} ${message}`); },\n fail(message) { console.error(`${prefix} ${message}`); },\n succeed(message) { console.log(`${prefix} ${message}`); },\n log(message) { console.log(`${prefix} ${message}`); },\n };\n}\n\nfunction requireLogger(\n logger: DatabaseProvisionerLogger | undefined,\n prefix: string\n): DatabaseProvisionerLogger {\n return logger ?? defaultLogger(prefix);\n}\n\nfunction getFlywayBaselineFile(seedFile: string): string {\n return join(dirname(seedFile), 'zzz-flyway-workspace-baseline.sql');\n}\n\nfunction getSnapshotCommand(dbConfig: DatabaseConfig): string | null {\n if (dbConfig.snapshot_command) {\n return dbConfig.snapshot_command;\n }\n\n if (dbConfig.external_db) {\n const ext = dbConfig.external_db;\n const password = ext.password_env ? process.env[ext.password_env] : '';\n if (ext.password_env && !password) {\n throw new DatabaseProvisionerError(`Environment variable ${ext.password_env} not set`);\n }\n return `PGPASSWORD=\"${password}\" pg_dump -h ${ext.host} -p ${ext.port || 5432} -U ${ext.user || 'postgres'} ${ext.database}`;\n }\n\n return null;\n}\n\nfunction getSnapshotHelp(projectKey: string): string {\n return `\n ${projectKey}:\n workspace:\n database:\n name: myapp\n snapshot_command: \"kubectl exec -n prod pod/postgres -- pg_dump -U app mydb\"\n # or\n external_db:\n host: prod-db.example.com\n database: myapp\n user: readonly\n password_env: PROD_DB_PASSWORD\n`;\n}\n\nfunction kubectlNoisePatterns(): RegExp[] {\n return [\n /^Defaulted container/,\n /^Unable to use a TTY/,\n /^All commands and output from this session/,\n /^If you don't see a command prompt/,\n /^warning: couldn't attach to pod/,\n /^pod \".*\" deleted from .* namespace$/,\n /^\\[stderr\\]/,\n /^error: timed out waiting/,\n ];\n}\n\nasync function cleanFile(filePath: string): Promise<void> {\n const content = readFileSync(filePath, 'utf-8');\n const lines = content.split('\\n');\n\n let startIndex = 0;\n for (let i = 0; i < lines.length; i++) {\n if (lines[i].startsWith('--') && lines[i].includes('PostgreSQL database dump')) {\n startIndex = i;\n break;\n }\n }\n\n const patternsToRemove = kubectlNoisePatterns();\n const cleanedLines = lines\n .slice(startIndex)\n .filter((line) => !patternsToRemove.some((p) => p.test(line)));\n\n writeFileSync(filePath, cleanedLines.join('\\n'));\n}\n\nexport async function repairFlywayIfNeeded(\n issueId: string,\n pgContainer: string,\n dbName: string,\n projectConfig: RepairMigrationsContext['projectConfig'],\n workspacePath: string,\n log?: (msg: string) => void\n): Promise<RepairMigrationsResult> {\n void issueId;\n const emit = log || ((msg: string) => console.log(`[flyway-repair] ${msg}`));\n\n try {\n await execAsync(`docker exec \"${pgContainer}\" pg_isready -U postgres`, {\n encoding: 'utf-8',\n timeout: 5000,\n });\n } catch {\n return { repaired: false, message: 'Postgres container not ready, skipping Flyway check' };\n }\n\n let rowCount = 0;\n try {\n const { stdout } = await execAsync(\n `docker exec \"${pgContainer}\" psql -U postgres -d ${dbName} -t -A -c \"SELECT count(*) FROM flyway_schema_history;\"`,\n { encoding: 'utf-8', timeout: 10000 }\n );\n rowCount = parseInt(stdout.trim(), 10) || 0;\n } catch {\n rowCount = 0;\n }\n\n if (rowCount >= 10) {\n return { repaired: false, message: `Flyway schema_history has ${rowCount} entries, no repair needed` };\n }\n\n emit(`Flyway schema_history has only ${rowCount} entries — repairing`);\n\n const seedRelPath = projectConfig.workspace?.database?.seed_file;\n if (!seedRelPath) {\n return { repaired: false, message: 'No seed_file configured, cannot locate Flyway baseline' };\n }\n\n const seedFile = join(projectConfig.path, seedRelPath);\n const flywayFile = getFlywayBaselineFile(seedFile);\n if (!existsSync(flywayFile)) {\n return { repaired: false, message: `Flyway baseline not found: ${flywayFile}` };\n }\n\n emit(`Loading Flyway baseline from ${flywayFile}`);\n await execAsync(\n `docker exec -i \"${pgContainer}\" psql -U postgres -d ${dbName} < \"${flywayFile}\"`,\n { encoding: 'utf-8', timeout: 60000 }\n );\n\n const migrationsRelPath = projectConfig.workspace?.database?.migrations?.path;\n if (migrationsRelPath) {\n const migrationsDir = join(workspacePath, migrationsRelPath);\n if (existsSync(migrationsDir)) {\n emit(`Syncing Flyway checksums from workspace migrations`);\n const migrationFiles = (await readdir(migrationsDir)).filter(f => /^V\\d+__.*\\.sql$/.test(f));\n const updates: string[] = [];\n\n for (const file of migrationFiles) {\n const version = file.match(/^V(\\d+)__/)?.[1];\n if (!version) continue;\n let content = await readFile(join(migrationsDir, file));\n if (content[0] === 0xEF && content[1] === 0xBB && content[2] === 0xBF) {\n content = content.slice(3);\n }\n const lines = content.toString('utf-8').split(/\\r?\\n/);\n const checksum = crc32(Buffer.from(lines.join(''), 'utf-8')) | 0;\n updates.push(\n `UPDATE flyway_schema_history SET checksum = ${checksum} WHERE version = '${version}' AND checksum IS NOT NULL;`\n );\n }\n\n if (updates.length > 0) {\n const tmpSql = `/tmp/flyway-checksum-sync-${Date.now()}.sql`;\n await writeFile(tmpSql, updates.join('\\n'));\n try {\n const { stdout } = await execAsync(\n `docker exec -i \"${pgContainer}\" psql -U postgres -d ${dbName} < \"${tmpSql}\"`,\n { encoding: 'utf-8', timeout: 30000 }\n );\n const updatedCount = (stdout.match(/UPDATE \\d+/g) || [])\n .reduce((sum, m) => sum + parseInt(m.replace('UPDATE ', ''), 10), 0);\n emit(`Synced ${migrationFiles.length} migration checksums (${updatedCount} rows updated)`);\n } finally {\n try { await unlink(tmpSql); } catch {}\n }\n }\n }\n }\n\n try {\n const { stdout } = await execAsync(\n `docker exec \"${pgContainer}\" psql -U postgres -d ${dbName} -t -A -c \"SELECT count(*) FROM flyway_schema_history;\"`,\n { encoding: 'utf-8', timeout: 10000 }\n );\n const newCount = parseInt(stdout.trim(), 10) || 0;\n emit(`Repair complete: flyway_schema_history now has ${newCount} entries (was ${rowCount})`);\n return { repaired: true, message: `Repaired Flyway schema_history: ${rowCount} → ${newCount} entries` };\n } catch (err: unknown) {\n return {\n repaired: false,\n message: `Repair may have failed: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n}\n\nexport const flywayPostgresProvisioner: DatabaseProvisioner = {\n id: 'flyway-postgres',\n\n validateRefreshDatabase(ctx): void {\n const flywayFile = getFlywayBaselineFile(ctx.seedFile);\n if (!existsSync(flywayFile)) {\n throw new DatabaseProvisionerError(`Flyway baseline not found: ${flywayFile}`, {\n status: 400,\n });\n }\n },\n\n async refreshDatabase(ctx: RefreshDatabaseContext): Promise<RefreshDatabaseResult> {\n const logger = requireLogger(ctx.logger, '[refresh-db]');\n\n logger.log(`Starting DB refresh for ${ctx.issueId}`);\n\n try {\n await execAsync(`docker stop \"${ctx.apiContainer}\"`, { encoding: 'utf-8', timeout: 30000 });\n } catch {\n logger.log('API container not running or already stopped');\n }\n\n await execAsync(\n `docker exec ${shellQuote(ctx.pgContainer)} psql -U postgres -d postgres -c \"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '${sqlString(ctx.databaseName)}' AND pid <> pg_backend_pid();\"`,\n { encoding: 'utf-8', timeout: 10000 }\n );\n await execAsync(\n `docker exec ${shellQuote(ctx.pgContainer)} psql -U postgres -d postgres -c \"DROP DATABASE IF EXISTS ${sqlIdentifier(ctx.databaseName)};\"`,\n { encoding: 'utf-8', timeout: 10000 }\n );\n await execAsync(\n `docker exec ${shellQuote(ctx.pgContainer)} psql -U postgres -d postgres -c \"CREATE DATABASE ${sqlIdentifier(ctx.databaseName)} OWNER postgres;\"`,\n { encoding: 'utf-8', timeout: 10000 }\n );\n\n logger.log(`Loading seed file: ${ctx.seedFile}`);\n await execAsync(\n `docker exec -i ${shellQuote(ctx.pgContainer)} psql -U postgres -d ${shellQuote(ctx.databaseName)} < ${shellQuote(ctx.seedFile)}`,\n { encoding: 'utf-8', timeout: 600000 }\n );\n\n const repairResult = await repairFlywayIfNeeded(\n ctx.issueId,\n ctx.pgContainer,\n ctx.databaseName,\n ctx.projectConfig,\n ctx.workspacePath,\n (msg) => logger.log(msg)\n );\n logger.log(`Flyway setup: ${repairResult.message}`);\n\n try {\n await execAsync(`docker start \"${ctx.apiContainer}\"`, { encoding: 'utf-8', timeout: 30000 });\n } catch {\n logger.log('Could not start API container (may need manual start)');\n }\n\n let seedVerifyResult: string | undefined;\n if (ctx.seedVerifyQuery) {\n try {\n const { stdout } = await execAsync(\n `docker exec ${shellQuote(ctx.pgContainer)} psql -U postgres -d ${shellQuote(ctx.databaseName)} -t -A -c ${shellQuote(ctx.seedVerifyQuery)}`,\n { encoding: 'utf-8', timeout: 10000 }\n );\n seedVerifyResult = stdout.trim();\n } catch {}\n }\n\n logger.log(`DB refresh complete for ${ctx.issueId}`);\n return { seedVerifyResult };\n },\n\n async seed(ctx: SeedDatabaseContext): Promise<void> {\n const logger = requireLogger(ctx.logger, '[db-seed]');\n\n logger.setText(`Finding database container ${ctx.pgContainer}...`);\n\n try {\n const { stdout } = await execAsync(`docker ps --filter \"name=${ctx.pgContainer}\" --format \"{{.Names}}\"`);\n if (!stdout.trim()) {\n logger.fail(`Database container not running: ${ctx.pgContainer}`);\n logger.log('\\nStart the workspace containers first:');\n logger.log(` pan workspace create ${ctx.workspaceOrIssue} --docker`);\n return;\n }\n } catch {\n logger.fail('Failed to check Docker containers');\n return;\n }\n\n if (!ctx.force) {\n try {\n const { stdout } = await execAsync(\n `docker exec ${shellQuote(ctx.pgContainer)} psql -U postgres -d ${shellQuote(ctx.databaseName)} -c \"SELECT count(*) FROM flyway_schema_history\" -t 2>/dev/null`\n );\n const count = parseInt(stdout.trim(), 10);\n if (count > 0) {\n logger.info(`Database already seeded (${count} migrations). Use --force to reseed.`);\n return;\n }\n } catch {\n // Table doesn't exist, proceed with seeding\n }\n }\n\n if (ctx.force) {\n logger.setText('Dropping existing database...');\n try {\n await execAsync(\n `docker exec ${shellQuote(ctx.pgContainer)} psql -U postgres -c \"DROP DATABASE IF EXISTS ${sqlIdentifier(ctx.databaseName)}; CREATE DATABASE ${sqlIdentifier(ctx.databaseName)};\"`\n );\n } catch (error: unknown) {\n logger.warn(`Could not drop database: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n logger.setText('Copying seed file to container...');\n await execAsync(`docker cp ${shellQuote(ctx.seedFile)} ${shellQuote(`${ctx.pgContainer}:/tmp/seed.sql`)}`);\n\n logger.setText('Executing seed...');\n try {\n await execAsync(`docker exec ${shellQuote(ctx.pgContainer)} psql -U postgres -d ${shellQuote(ctx.databaseName)} -f /tmp/seed.sql`, {\n timeout: 600000,\n });\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n if (message.includes('ERROR')) {\n logger.fail(`Seed failed: ${message}`);\n return;\n }\n }\n\n await execAsync(`docker exec ${shellQuote(ctx.pgContainer)} rm /tmp/seed.sql`);\n\n logger.succeed('Database seeded successfully');\n\n try {\n const { stdout } = await execAsync(\n `docker exec ${shellQuote(ctx.pgContainer)} psql -U postgres -d ${shellQuote(ctx.databaseName)} -c \"SELECT version, description FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 3\" -t`\n );\n logger.log('\\nRecent migrations:');\n stdout\n .trim()\n .split('\\n')\n .forEach((line) => logger.log(` ${line.trim()}`));\n } catch {\n // Ignore\n }\n },\n\n repairMigrations(ctx: RepairMigrationsContext): Promise<RepairMigrationsResult> {\n return repairFlywayIfNeeded(\n ctx.issueId,\n ctx.pgContainer,\n ctx.databaseName,\n ctx.projectConfig,\n ctx.workspacePath,\n ctx.log\n );\n },\n\n async snapshot(ctx: SnapshotDatabaseContext): Promise<SnapshotDatabaseResult> {\n const logger = requireLogger(ctx.logger, '[db-snapshot]');\n\n if (!ctx.dbConfig.snapshot_command && !ctx.dbConfig.external_db) {\n throw new DatabaseProvisionerError(\n `No snapshot configuration for project ${'key' in ctx.projectConfig ? String(ctx.projectConfig.key) : ctx.projectConfig.name}`\n );\n }\n\n const outputPath =\n ctx.output ||\n ctx.dbConfig.seed_file ||\n join(ctx.projectConfig.path, 'infra', 'seed', 'seed.sql');\n\n const outputDir = dirname(outputPath);\n if (!existsSync(outputDir)) {\n mkdirSync(outputDir, { recursive: true });\n }\n\n logger.setText('Running snapshot command...');\n\n const snapshotCmd = getSnapshotCommand(ctx.dbConfig);\n if (!snapshotCmd) {\n throw new DatabaseProvisionerError('No snapshot configuration found');\n }\n\n const fullCmd = `${snapshotCmd} > \"${outputPath}\" 2>&1`;\n\n try {\n await execAsync(fullCmd, { timeout: 300000 });\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n if (existsSync(outputPath)) {\n const content = readFileSync(outputPath, 'utf-8');\n if (content.includes('PostgreSQL database dump')) {\n logger.warn('Snapshot completed with warnings (stderr captured)');\n logger.log(' Run `pan db clean` to remove stderr noise from the file');\n } else {\n throw new DatabaseProvisionerError(`Snapshot failed: ${message}`);\n }\n } else {\n throw new DatabaseProvisionerError(`Snapshot failed: ${message}`);\n }\n }\n\n const content = readFileSync(outputPath, 'utf-8');\n if (content.includes('Defaulted container') || content.includes('Unable to use a TTY')) {\n logger.setText('Cleaning kubectl output from snapshot...');\n await cleanFile(outputPath);\n }\n\n if (ctx.sanitize && ctx.dbConfig.seed_command) {\n logger.setText('Running sanitization...');\n try {\n await execAsync(ctx.dbConfig.seed_command, { cwd: ctx.projectConfig.path });\n } catch (error: unknown) {\n logger.warn(`Sanitization warning: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n const stats = statSync(outputPath);\n return { outputPath, sizeBytes: stats.size };\n },\n\n async cleanSnapshot(ctx: CleanSnapshotContext): Promise<CleanSnapshotResult> {\n if (!existsSync(ctx.file)) {\n throw new DatabaseProvisionerError(`File not found: ${ctx.file}`);\n }\n\n const content = readFileSync(ctx.file, 'utf-8');\n const lines = content.split('\\n');\n const patternsToRemove = kubectlNoisePatterns();\n\n let startIndex = 0;\n for (let i = 0; i < lines.length; i++) {\n if (lines[i].startsWith('--') && lines[i].includes('PostgreSQL')) {\n startIndex = i;\n break;\n }\n }\n void startIndex;\n\n const dumpStarts: number[] = [];\n const dumpEnds: number[] = [];\n\n for (let i = 0; i < lines.length; i++) {\n if (lines[i].includes('PostgreSQL database dump') && lines[i].startsWith('--')) {\n if (!lines[i].includes('complete')) {\n dumpStarts.push(i);\n }\n }\n if (lines[i].includes('PostgreSQL database dump complete')) {\n dumpEnds.push(i);\n }\n }\n\n let cleanedLines: string[];\n if (dumpStarts.length > 1 && dumpEnds.length > 0) {\n const lastEnd = dumpEnds[dumpEnds.length - 1];\n let useStart = dumpStarts[0];\n for (const start of dumpStarts) {\n if (start < lastEnd) {\n useStart = start;\n } else {\n break;\n }\n }\n cleanedLines = lines.slice(useStart, lastEnd + 1);\n ctx.logger?.setText(`Found ${dumpStarts.length} dump headers, using last complete dump`);\n } else {\n cleanedLines = lines.filter((line) => !patternsToRemove.some((p) => p.test(line)));\n }\n\n cleanedLines = cleanedLines.filter((line) => !patternsToRemove.some((p) => p.test(line)));\n\n const cleanedContent = cleanedLines.join('\\n');\n const removedLines = lines.length - cleanedLines.length;\n const removedSample = lines\n .filter((line) => patternsToRemove.some((p) => p.test(line)))\n .slice(0, 5);\n\n if (ctx.dryRun) {\n return {\n originalLines: lines.length,\n cleanedLines: cleanedLines.length,\n removedLines,\n removedSample,\n };\n }\n\n const outputPath = ctx.output || ctx.file;\n writeFileSync(outputPath, cleanedContent);\n\n return {\n outputPath,\n originalLines: lines.length,\n cleanedLines: cleanedLines.length,\n removedLines,\n removedSample,\n };\n },\n\n async status(ctx: StatusDatabaseContext): Promise<void> {\n const logger = requireLogger(ctx.logger, '[db-status]');\n\n logger.setText(`Checking container ${ctx.pgContainer}...`);\n\n const { stdout: containerStatus } = await execAsync(\n `docker ps --filter \"name=${ctx.pgContainer}\" --format \"{{.Status}}\"`\n );\n\n if (!containerStatus.trim()) {\n logger.fail(`Container not running: ${ctx.pgContainer}`);\n return;\n }\n\n logger.succeed(`Container: ${ctx.pgContainer}`);\n logger.log(` Status: ${containerStatus.trim()}`);\n\n try {\n const { stdout: version } = await execAsync(\n `docker exec ${shellQuote(ctx.pgContainer)} psql -U postgres -d ${shellQuote(ctx.databaseName)} -c \"SELECT version, description FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 1\" -t`\n );\n const [ver, desc] = version.trim().split('|').map((s) => s.trim());\n logger.log(` Flyway: V${ver} - ${desc}`);\n } catch {\n logger.warn(' Flyway: Not initialized');\n }\n\n try {\n const { stdout: tableCount } = await execAsync(\n `docker exec ${shellQuote(ctx.pgContainer)} psql -U postgres -d ${shellQuote(ctx.databaseName)} -c \"SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public'\" -t`\n );\n logger.log(` Tables: ${tableCount.trim()}`);\n } catch {\n // Ignore\n }\n\n try {\n const { stdout: dbSize } = await execAsync(\n `docker exec ${shellQuote(ctx.pgContainer)} psql -U postgres -d ${shellQuote(ctx.databaseName)} -c \"SELECT pg_size_pretty(pg_database_size('${sqlString(ctx.databaseName)}'))\" -t`\n );\n logger.log(` Size: ${dbSize.trim()}`);\n } catch {\n // Ignore\n }\n },\n};\n\nexport function getFlywayPostgresSnapshotHelp(projectKey: string): string {\n return getSnapshotHelp(projectKey);\n}\n","import type { DatabaseConfig } from '../workspace-config.js';\nimport { flywayPostgresProvisioner } from './flyway-postgres.js';\nimport type { DatabaseProvisioner } from './types.js';\n\nexport function getDatabaseProvisioner(\n dbConfig: DatabaseConfig | undefined\n): DatabaseProvisioner | null {\n if (!dbConfig) return null;\n if (dbConfig.provisioner === 'flyway-postgres') return flywayPostgresProvisioner;\n if (dbConfig.migrations?.type === 'flyway') return flywayPostgresProvisioner;\n return null;\n}\n\nexport function getSnapshotCleanerProvisioner(): DatabaseProvisioner {\n return flywayPostgresProvisioner;\n}\n\nexport type {\n DatabaseProvisioner,\n DatabaseProvisionerLogger,\n RefreshDatabaseContext,\n RefreshDatabaseResult,\n SeedDatabaseContext,\n RepairMigrationsContext,\n RepairMigrationsResult,\n SnapshotDatabaseContext,\n SnapshotDatabaseResult,\n CleanSnapshotContext,\n CleanSnapshotResult,\n StatusDatabaseContext,\n} from './types.js';\n\nexport { DatabaseProvisionerError } from './types.js';\n","/**\n * Container-ops route module — extracted from routes/workspaces.ts (B2).\n *\n * Containerization + Docker control + DB refresh endpoints:\n * POST /api/workspaces/:issueId/containerize\n * POST /api/workspaces/:issueId/containers/:containerName/:action\n * POST /api/workspaces/:issueId/memory-summary\n * POST /api/workspaces/:issueId/refresh-db\n *\n * Shared singletons (activity log, pending ops, project helpers)\n * stay owned by ../workspaces.js and are imported here.\n */\n\nimport { exec, spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { basename, join } from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { Effect, Layer } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\n\nimport { buildChildEnvWithoutTmuxSync } from '../../../../lib/child-env.js';\nimport { extractPrefixSync, parseIssueIdSync } from '../../../../lib/issue-id.js';\nimport { generateDailySummary } from '../../../../lib/memory/cli.js';\nimport {\n extractTeamPrefix,\n findProjectByTeamSync,\n listProjectsSync,\n} from '../../../../lib/projects.js';\nimport {\n DatabaseProvisionerError,\n getDatabaseProvisioner,\n} from '../../../../lib/db-provisioners/index.js';\nimport { DEVCONTAINER_DIRNAME } from '../../../../lib/workspace/devcontainer-renderer.js';\nimport type { DatabaseConfig } from '../../../../lib/workspace-config.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { httpHandler } from '../http-handler.js';\nimport {\n appendActivityOutput,\n getProjectPath,\n logActivity,\n requireTrustedMutationOrigin,\n updateActivity,\n} from '../workspaces.js';\n\nconst execAsync = promisify(exec);\n\nfunction requireDatabaseName(dbConfig: DatabaseConfig | undefined): string {\n if (!dbConfig) {\n throw new Error('No database configuration found in projects.yaml');\n }\n const name = dbConfig.name?.trim();\n if (!name) {\n throw new Error('Missing required database.name in projects.yaml database config');\n }\n return name;\n}\n\n// ─── Route: POST /api/workspaces/:issueId/containerize ───────────────────────\n\nconst postWorkspaceContainerizeRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces/:issueId/containerize',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const issueLower = issueId.toLowerCase();\n\n const newFeatureScript = join(projectPath, 'infra', 'new-feature');\n if (!existsSync(newFeatureScript)) {\n return jsonResponse(\n { error: 'Project does not support containerization (no infra/new-feature script)' },\n { status: 400 }\n );\n }\n\n const workspaceName = `feature-${issueLower}`;\n const workspacePath = join(projectPath, 'workspaces', workspaceName);\n if (existsSync(join(workspacePath, '.devcontainer'))) {\n return jsonResponse({ error: 'Workspace is already containerized' }, { status: 400 });\n }\n\n try {\n yield* Effect.promise(() => execAsync('docker info >/dev/null 2>&1', { encoding: 'utf-8' }));\n } catch {\n return jsonResponse(\n { error: 'Docker is not running. Start Docker Desktop first.' },\n { status: 400 }\n );\n }\n\n if (existsSync(workspacePath)) {\n return jsonResponse(\n { error: 'Workspace already exists. Use the workspace inspector to manage it, or remove it first with: pan workspace destroy ' + issueId },\n { status: 409 }\n );\n }\n\n const featureName = issueLower;\n const activityId = Date.now().toString();\n\n logActivity({\n id: activityId,\n timestamp: new Date().toISOString(),\n command: `./new-feature ${featureName}`,\n status: 'running',\n output: [],\n });\n\n const child = spawn('./new-feature', [featureName], {\n cwd: join(projectPath, 'infra'),\n detached: true,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n child.stdout?.on('data', (data) => {\n data.toString().split('\\n').filter(Boolean).forEach((line: string) => {\n appendActivityOutput(activityId, line);\n });\n });\n child.stderr?.on('data', (data) => {\n data.toString().split('\\n').filter(Boolean).forEach((line: string) => {\n appendActivityOutput(activityId, `[stderr] ${line}`);\n });\n });\n\n child.on('close', (code) => {\n appendActivityOutput(\n activityId,\n `[${new Date().toISOString()}] new-feature exited with code ${code}`\n );\n if (code === 0) {\n appendActivityOutput(activityId, '');\n appendActivityOutput(activityId, '=== Starting containers ===');\n\n const workspaceDir = join(projectPath, 'workspaces', `feature-${featureName}`);\n const uid = process.getuid?.() ?? 1000;\n const gid = process.getgid?.() ?? 1000;\n const devUp = spawn('./dev', ['all'], {\n cwd: workspaceDir,\n detached: true,\n stdio: ['ignore', 'pipe', 'pipe'],\n env: buildChildEnvWithoutTmuxSync(process.env, { UID: String(uid), GID: String(gid), DOCKER_USER: `${uid}:${gid}` }),\n });\n\n devUp.stdout?.on('data', (data) => {\n data.toString().split('\\n').filter(Boolean).forEach((line: string) => {\n appendActivityOutput(activityId, line);\n });\n });\n devUp.stderr?.on('data', (data) => {\n data.toString().split('\\n').filter(Boolean).forEach((line: string) => {\n appendActivityOutput(activityId, `[stderr] ${line}`);\n });\n });\n devUp.on('close', (devCode) => {\n appendActivityOutput(\n activityId,\n `[${new Date().toISOString()}] ./dev all exited with code ${devCode}`\n );\n updateActivity(activityId, { status: devCode === 0 ? 'completed' : 'failed' });\n });\n devUp.on('error', (err) => {\n appendActivityOutput(activityId, `[error] ${err.message}`);\n updateActivity(activityId, { status: 'failed' });\n });\n } else {\n updateActivity(activityId, { status: 'failed' });\n }\n });\n\n child.on('error', (err) => {\n appendActivityOutput(activityId, `[error] ${err.message}`);\n updateActivity(activityId, { status: 'failed' });\n });\n\n return jsonResponse({\n success: true,\n message: `Containerizing workspace for ${issueId}`,\n activityId,\n projectPath,\n });\n }))\n);\n\n// ─── Route: POST /api/workspaces/:issueId/containers/:containerName/:action ───\n\nconst postWorkspaceContainerActionRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces/:issueId/containers/:containerName/:action',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const containerName = params['containerName'] ?? '';\n const action = params['action'] ?? '';\n\n if (!['start', 'stop', 'restart'].includes(action)) {\n return jsonResponse(\n { error: 'Invalid action. Must be start, stop, or restart.' },\n { status: 400 }\n );\n }\n\n const teamPrefix = extractTeamPrefix(issueId);\n const containerProjectConfig = teamPrefix ? findProjectByTeamSync(teamPrefix) : null;\n const projectPaths = containerProjectConfig\n ? [\n join(\n containerProjectConfig.path,\n 'workspaces',\n `feature-${issueId.toLowerCase()}`\n ),\n ]\n : listProjectsSync().map(p =>\n join(p.config.path, 'workspaces', `feature-${issueId.toLowerCase()}`)\n );\n\n let workspacePath: string | null = null;\n let composeFile: string | null = null;\n\n for (const path of projectPaths) {\n if (existsSync(path)) {\n workspacePath = path;\n const composePaths = [\n join(path, '.devcontainer/docker-compose.devcontainer.yml'),\n join(path, 'docker-compose.yml'),\n join(path, 'docker-compose.yaml'),\n ];\n for (const cp of composePaths) {\n if (existsSync(cp)) {\n composeFile = cp;\n break;\n }\n }\n break;\n }\n }\n\n if (!workspacePath) {\n return jsonResponse(\n { error: `Workspace not found for ${issueId}` },\n { status: 404 }\n );\n }\n\n // Self-heal: if .devcontainer/ is missing for start/restart, re-render\n // from the project template so docker compose can operate on containers.\n if (!composeFile && ['start', 'restart'].includes(action)) {\n const { ensureDevcontainerSync } = yield* Effect.promise(() =>\n import('../../../../lib/workspace/ensure-devcontainer.js')\n );\n const ensure = ensureDevcontainerSync({ workspacePath, issueId });\n if (ensure.rendered) {\n console.log(`[container-control] Re-rendered ${DEVCONTAINER_DIRNAME}/ from project template`);\n }\n // Re-scan for compose file after re-render\n const composePaths = [\n join(workspacePath, '.devcontainer/docker-compose.devcontainer.yml'),\n join(workspacePath, 'docker-compose.yml'),\n join(workspacePath, 'docker-compose.yaml'),\n ];\n for (const cp of composePaths) {\n if (existsSync(cp)) {\n composeFile = cp;\n break;\n }\n }\n }\n\n if (!composeFile) {\n return jsonResponse(\n { error: `No docker-compose file found in workspace` },\n { status: 404 }\n );\n }\n\n try {\n yield* Effect.promise(() => execAsync('docker info >/dev/null 2>&1', { encoding: 'utf-8' }));\n } catch {\n return jsonResponse(\n { error: 'Docker is not running. Start Docker Desktop first.' },\n { status: 400 }\n );\n }\n\n const serviceMap: Record<string, string[]> = {\n frontend: ['fe', 'frontend'],\n api: ['api'],\n dev: ['dev'],\n postgres: ['postgres'],\n redis: ['redis'],\n fe: ['fe', 'frontend'],\n };\n\n const serviceNames = serviceMap[containerName.toLowerCase()];\n if (!serviceNames) {\n return jsonResponse(\n {\n error: `Unknown container: ${containerName}. Valid: ${Object.keys(serviceMap).join(', ')}`,\n },\n { status: 400 }\n );\n }\n\n const { stdout: projectNameOut } = yield* Effect.promise(() => execAsync(\n `docker compose -f \"${composeFile}\" config --format json 2>/dev/null | jq -r '.name // empty'`,\n { encoding: 'utf-8' }\n ));\n const projectName = projectNameOut.trim();\n\n // Pre-start migration repair for API containers\n if (\n containerName.toLowerCase() === 'api' &&\n ['start', 'restart'].includes(action)\n ) {\n const tPrefix = extractTeamPrefix(issueId);\n const pConfig = tPrefix ? findProjectByTeamSync(tPrefix) : null;\n const dbConfig = pConfig?.workspace?.database;\n const provisioner = getDatabaseProvisioner(dbConfig);\n if (pConfig && dbConfig && provisioner && projectName) {\n const databaseName = requireDatabaseName(pConfig.workspace.database);\n const pgContainer = `${projectName}-postgres-1`;\n try {\n const result = yield* Effect.promise(() => provisioner.repairMigrations({\n issueId,\n pgContainer,\n databaseName,\n projectConfig: pConfig,\n workspacePath: workspacePath!,\n }));\n if (result.repaired) {\n console.log(`[container-control] ${result.message}`);\n }\n } catch (repairErr: unknown) {\n console.warn(\n `[container-control] migration pre-check failed (non-fatal): ${repairErr instanceof Error ? repairErr.message : String(repairErr)}`\n );\n }\n }\n }\n\n let success = false;\n let lastError = '';\n\n for (const serviceName of serviceNames) {\n try {\n const cmd = `docker compose -f \"${composeFile}\" ${projectName ? `--project-name \"${projectName}\"` : ''} ${action} ${serviceName}`;\n console.log(`[container-control] Running: ${cmd}`);\n yield* Effect.promise(() => execAsync(cmd, { encoding: 'utf-8', timeout: 30000 }));\n success = true;\n console.log(\n `[container-control] Successfully ${action}ed ${serviceName} for ${issueId}`\n );\n break;\n } catch (err: unknown) {\n lastError = (err instanceof Error ? err.message : '') || String(err);\n }\n }\n\n if (success) {\n return jsonResponse({\n success: true,\n message: `Container ${containerName} ${action}ed successfully`,\n });\n } else {\n return jsonResponse(\n { error: `Failed to ${action} ${containerName}: ${lastError}` },\n { status: 500 }\n );\n }\n }))\n);\n\n// ─── Route: POST /api/workspaces/:issueId/memory-summary ─────────────────────\n\nconst postWorkspaceMemorySummaryRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces/:issueId/memory-summary',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const originError = requireTrustedMutationOrigin(request);\n if (originError) return originError;\n\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: 'Invalid issue ID' }, { status: 400 });\n }\n\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const result = yield* Effect.promise(() => generateDailySummary({\n projectId: basename(projectPath),\n issueId,\n }));\n return jsonResponse(result);\n }))\n);\n\n// ─── Route: POST /api/workspaces/:issueId/refresh-db ─────────────────────────\n\nconst postWorkspaceRefreshDbRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces/:issueId/refresh-db',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n\n const teamPrefix = extractTeamPrefix(issueId);\n const projectConfig = teamPrefix ? findProjectByTeamSync(teamPrefix) : null;\n\n if (!projectConfig) {\n return jsonResponse(\n { error: `No project found for issue prefix: ${issueId}` },\n { status: 404 }\n );\n }\n\n const dbConfig = projectConfig.workspace?.database;\n const provisioner = getDatabaseProvisioner(dbConfig);\n if (!provisioner) {\n return jsonResponse(\n { error: 'No database configuration found in projects.yaml' },\n { status: 400 }\n );\n }\n\n if (!dbConfig?.seed_file) {\n return jsonResponse(\n { error: 'No seed_file configured in projects.yaml database config' },\n { status: 400 }\n );\n }\n const databaseName = requireDatabaseName(dbConfig);\n\n const seedFile = join(projectConfig.path, dbConfig.seed_file);\n if (!existsSync(seedFile)) {\n return jsonResponse(\n { error: `Seed file not found: ${seedFile}` },\n { status: 400 }\n );\n }\n\n try {\n provisioner.validateRefreshDatabase({ seedFile });\n } catch (err: unknown) {\n if (err instanceof DatabaseProvisionerError) {\n return jsonResponse({ error: err.message }, { status: err.status });\n }\n throw err;\n }\n\n const issueLower = issueId.toLowerCase();\n const featureFolder = `feature-${issueLower}`;\n const workspacesDir = projectConfig.workspace?.workspaces_dir || 'workspaces';\n const workspacePath = join(projectConfig.path, workspacesDir, featureFolder);\n\n if (!existsSync(workspacePath)) {\n return jsonResponse(\n { error: `Workspace not found: ${featureFolder}` },\n { status: 404 }\n );\n }\n\n const composePaths = [\n join(workspacePath, '.devcontainer/docker-compose.devcontainer.yml'),\n join(workspacePath, 'docker-compose.yml'),\n join(workspacePath, 'docker-compose.yaml'),\n ];\n let composeFile: string | null = null;\n for (const cp of composePaths) {\n if (existsSync(cp)) { composeFile = cp; break; }\n }\n\n if (!composeFile) {\n return jsonResponse(\n { error: 'No docker-compose file found in workspace' },\n { status: 404 }\n );\n }\n\n const { stdout: projectNameOut } = yield* Effect.promise(() => execAsync(\n `docker compose -f \"${composeFile}\" config --format json 2>/dev/null | jq -r '.name // empty'`,\n { encoding: 'utf-8' }\n ));\n const projectName = projectNameOut.trim();\n\n if (!projectName) {\n return jsonResponse(\n { error: 'Could not determine docker compose project name' },\n { status: 500 }\n );\n }\n\n const pgContainer = `${projectName}-postgres-1`;\n const apiContainer = `${projectName}-api-1`;\n\n console.log(`[refresh-db] Starting DB refresh for ${issueId} (project: ${projectName})`);\n\n const { seedVerifyResult } = yield* Effect.promise(() => provisioner.refreshDatabase({\n issueId,\n projectConfig,\n workspacePath,\n pgContainer,\n apiContainer,\n databaseName,\n seedFile,\n seedVerifyQuery: dbConfig.seedVerifyQuery,\n logger: {\n setText: (msg) => console.log(`[refresh-db] ${msg}`),\n info: (msg) => console.log(`[refresh-db] ${msg}`),\n warn: (msg) => console.warn(`[refresh-db] ${msg}`),\n fail: (msg) => console.error(`[refresh-db] ${msg}`),\n succeed: (msg) => console.log(`[refresh-db] ${msg}`),\n log: (msg) => console.log(`[refresh-db] ${msg}`),\n },\n }));\n\n return jsonResponse({\n success: true,\n message: `Database refreshed successfully`,\n seedVerifyResult,\n });\n }))\n);\n\nexport const containerOpsRouteLayer = Layer.mergeAll(\n postWorkspaceContainerizeRoute,\n postWorkspaceContainerActionRoute,\n postWorkspaceMemorySummaryRoute,\n postWorkspaceRefreshDbRoute,\n);\n\nexport default containerOpsRouteLayer;\n","import { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { Effect } from 'effect';\nimport { GitError } from './errors.js';\n\nconst execAsync = promisify(exec);\n\n/**\n * Canonical stash kinds Overdeck creates.\n *\n * PAN-1531: collapsed from four kinds to one. The retired kinds (`pre-merge`,\n * `pre-spawn`, `review-temp`) are no longer created by Overdeck code, but\n * `parseCanonicalStashMessage` still recognizes them so existing stashes in\n * `refs/stash` parse correctly during cleanup. Only `salvageable` may be\n * created going forward — see /home/eltmon/.overdeck/context/global.md\n * \"Stash Discipline\" and docs/MERGE-WORKFLOW.md.\n */\nexport type CanonicalStashKind = 'salvageable';\nexport type LegacyStashKind = 'pre-merge' | 'pre-spawn' | 'review-temp';\nexport type AnyStashKind = CanonicalStashKind | LegacyStashKind;\n\nexport interface ParsedStashEntry {\n /** Stable stash object SHA for persisted references. */\n ref: string;\n /** Current stack position (e.g. stash@{0}) for display and index-based ordering. */\n stackRef?: string;\n message: string;\n createdAt?: Date;\n issueId?: string;\n /**\n * `'salvageable'` for canonical stashes Overdeck creates today.\n * `'pre-merge' | 'pre-spawn' | 'review-temp'` for stashes left behind by\n * the pre-PAN-1531 pipeline; recognized for cleanup but never created anew.\n * `'unknown'` for hand-typed or otherwise unrecognized stashes.\n */\n kind: AnyStashKind | 'unknown';\n shortDescription?: string;\n sequence?: number;\n}\n\nexport interface SalvageableStashEntry extends ParsedStashEntry {\n kind: 'salvageable';\n issueId: string;\n shortDescription: string;\n}\n\nconst ISO_STASH_TIMESTAMP_PATTERN = '(\\\\d{4}-\\\\d{2}-\\\\d{2}T\\\\d{2}:\\\\d{2}:\\\\d{2}(?:\\\\.\\\\d+)?(?:Z|[+-]\\\\d{2}:\\\\d{2}))';\nconst ISSUE_ID_PATTERN = '([A-Z]+(?:-[A-Z]+)*-\\\\d+)';\nconst ISSUE_ID_REGEX = new RegExp(`^${ISSUE_ID_PATTERN}$`);\n\nfunction isoForStash(date = new Date()): string {\n return date.toISOString().replace(/\\.\\d{3}Z$/, 'Z');\n}\n\nfunction normalizeStashTimestamp(value: string): string {\n return value\n .replace(/\\.\\d+(Z|[+-]\\d{2}:\\d{2})$/, '$1')\n .replace(/\\+00:00$/, 'Z');\n}\n\nfunction sanitizeShortDescription(value: string): string {\n return value\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 48) || 'recovery';\n}\n\nfunction validateIssueId(issueId: string): string {\n const normalizedIssueId = issueId.toUpperCase();\n if (!ISSUE_ID_REGEX.test(normalizedIssueId)) {\n throw new Error(`Invalid issue ID format: ${issueId}`);\n }\n return normalizedIssueId;\n}\n\n/**\n * Build a canonical stash message.\n *\n * PAN-1531: `salvageable` is the only canonical kind Overdeck creates.\n * Retired overloads for `pre-merge` / `pre-spawn` / `review-temp` were\n * removed when their callers were deleted. Any human who wants a stash\n * Overdeck will leave alone uses `salvageable:`.\n */\nexport function buildStashMessage(\n kind: CanonicalStashKind,\n issueId: string,\n date: Date = new Date(),\n shortDescription: string = 'recovery',\n): string {\n const normalizedIssueId = validateIssueId(issueId);\n if (kind === 'salvageable') {\n return `salvageable:${normalizedIssueId}:${isoForStash(date)}:${sanitizeShortDescription(shortDescription)}`;\n }\n // Unreachable: type system narrows `kind` to 'salvageable'. Defensive throw\n // so type widening at the call site is caught at runtime rather than\n // silently producing an invalid message.\n throw new Error(`buildStashMessage: unsupported kind ${kind as string}`);\n}\n\nexport function parseCanonicalStashMessage(message: string): ParsedStashEntry {\n const preTimedMatch = new RegExp(`^(pre-merge|pre-spawn):${ISSUE_ID_PATTERN}:${ISO_STASH_TIMESTAMP_PATTERN}$`).exec(message);\n if (preTimedMatch) {\n return {\n ref: '',\n message,\n kind: preTimedMatch[1] as CanonicalStashKind,\n issueId: preTimedMatch[2].toUpperCase(),\n createdAt: new Date(normalizeStashTimestamp(preTimedMatch[3])),\n };\n }\n\n const reviewTempMatch = new RegExp(`^review-temp:${ISSUE_ID_PATTERN}:(\\\\d+)$`).exec(message);\n if (reviewTempMatch) {\n return {\n ref: '',\n message,\n kind: 'review-temp',\n issueId: reviewTempMatch[1].toUpperCase(),\n sequence: parseInt(reviewTempMatch[2], 10),\n };\n }\n\n const salvageableMatch = new RegExp(`^salvageable:${ISSUE_ID_PATTERN}:${ISO_STASH_TIMESTAMP_PATTERN}:(.+)$`).exec(message);\n if (salvageableMatch) {\n return {\n ref: '',\n message,\n kind: 'salvageable',\n issueId: salvageableMatch[1].toUpperCase(),\n createdAt: new Date(normalizeStashTimestamp(salvageableMatch[2])),\n shortDescription: salvageableMatch[3],\n };\n }\n\n return { ref: '', message, kind: 'unknown' };\n}\n\nexport function parseStashListLine(line: string): ParsedStashEntry | null {\n const trimmed = line.trim();\n if (!trimmed) return null;\n\n const formattedMatch = /^(stash@\\{\\d+\\})\\t([0-9a-f]{40})\\t([^\\t]+)\\t(.*)$/.exec(trimmed);\n if (formattedMatch) {\n const [, stackRef, ref, createdAtRaw, decoratedMessage] = formattedMatch;\n const messageMatch = /^(?:On|WIP on)\\s+[^:]*:\\s*(.*)$/.exec(decoratedMessage.trim());\n const message = (messageMatch?.[1] ?? decoratedMessage).trim();\n const parsed = parseCanonicalStashMessage(message);\n const createdAt = new Date(normalizeStashTimestamp(createdAtRaw));\n return {\n ...parsed,\n ref,\n stackRef,\n message,\n createdAt: Number.isNaN(createdAt.getTime()) ? parsed.createdAt : createdAt,\n };\n }\n\n const legacyMatch = /^(stash@\\{\\d+\\}):\\s*(?:On|WIP on)\\s+[^:]*:\\s*(.*)$/.exec(trimmed);\n if (!legacyMatch) return null;\n const stackRef = legacyMatch[1];\n const message = legacyMatch[2].trim();\n const parsed = parseCanonicalStashMessage(message);\n return { ...parsed, ref: stackRef, stackRef, message };\n}async function listStashesPromise(repoPath: string): Promise<ParsedStashEntry[]> {\n const { stdout } = await execAsync('git stash list --format=\"%gd%x09%H%x09%cI%x09%gs\"', {\n cwd: repoPath,\n encoding: 'utf-8',\n });\n return stdout\n .split('\\n')\n .map((line) => parseStashListLine(line))\n .filter((entry): entry is ParsedStashEntry => entry !== null);\n}async function createNamedStashPromise(repoPath: string, message: string, includeUntracked = true): Promise<string | null> {\n const command = includeUntracked\n ? `git stash push -u -m ${JSON.stringify(message)}`\n : `git stash push -m ${JSON.stringify(message)}`;\n const { stdout } = await execAsync(command, { cwd: repoPath, encoding: 'utf-8' });\n if (/No local changes to save/i.test(stdout)) return null;\n\n const { stdout: stashRef } = await execAsync('git rev-parse --verify stash@{0}', {\n cwd: repoPath,\n encoding: 'utf-8',\n });\n const normalizedRef = stashRef.trim();\n return normalizedRef || null;\n}\n\nasync function resolveStashOperationRef(repoPath: string, ref: string, stackRef?: string): Promise<string> {\n // Per-workspace stash operations must not run concurrently; callers may\n // resolve a stack slot and immediately consume it, but another stash mutation in between\n // can still shift indices. The rev-parse checks below re-validate the resolved slot\n // against the expected SHA immediately before use.\n const candidateRef = /^stash@\\{\\d+\\}$/.test(ref) ? ref : stackRef;\n if (candidateRef) {\n const { stdout: resolvedSha } = await execAsync(`git rev-parse --verify ${JSON.stringify(candidateRef)}`, {\n cwd: repoPath,\n encoding: 'utf-8',\n });\n if (/^stash@\\{\\d+\\}$/.test(ref) || resolvedSha.trim() === ref) {\n return candidateRef;\n }\n throw new Error(`Stash ${ref} no longer matches ${candidateRef}`);\n }\n\n const stashes = await Effect.runPromise(listStashes(repoPath));\n const matchingEntry = stashes.find((entry) => entry.ref === ref);\n if (!matchingEntry?.stackRef) {\n throw new Error(`Stash ${ref} not found`);\n }\n\n const { stdout: resolvedSha } = await execAsync(`git rev-parse --verify ${JSON.stringify(matchingEntry.stackRef)}`, {\n cwd: repoPath,\n encoding: 'utf-8',\n });\n if (resolvedSha.trim() !== ref) {\n throw new Error(`Stash ${ref} no longer matches ${matchingEntry.stackRef}`);\n }\n\n return matchingEntry.stackRef;\n}async function popStashPromise(repoPath: string, ref: string, stackRef?: string): Promise<void> {\n const operationRef = await resolveStashOperationRef(repoPath, ref, stackRef);\n await execAsync(`git stash pop ${JSON.stringify(operationRef)}`, { cwd: repoPath, encoding: 'utf-8' });\n}async function dropStashPromise(repoPath: string, ref: string, stackRef?: string): Promise<void> {\n const operationRef = await resolveStashOperationRef(repoPath, ref, stackRef);\n await execAsync(`git stash drop ${JSON.stringify(operationRef)}`, { cwd: repoPath, encoding: 'utf-8' });\n}async function applyStashPromise(repoPath: string, ref: string, stackRef?: string): Promise<void> {\n const operationRef = await resolveStashOperationRef(repoPath, ref, stackRef);\n await execAsync(`git stash apply ${JSON.stringify(operationRef)}`, { cwd: repoPath, encoding: 'utf-8' });\n}async function createRecoveryBranchFromStashPromise(\n repoPath: string,\n stashRef: string,\n issueId: string,\n shortDescription: string,\n stackRef?: string,\n): Promise<string> {\n const operationRef = await resolveStashOperationRef(repoPath, stashRef, stackRef);\n const normalizedIssueId = validateIssueId(issueId);\n const branchName = `recovery/${normalizedIssueId}-${sanitizeShortDescription(shortDescription)}`;\n await execAsync(`git branch ${JSON.stringify(branchName)} ${JSON.stringify(operationRef)}`, {\n cwd: repoPath,\n encoding: 'utf-8',\n });\n return branchName;\n}\n\nexport function getNextReviewTempSequence(entries: ParsedStashEntry[], issueId: string): number {\n const normalizedIssueId = issueId.toUpperCase();\n const maxSequence = entries.reduce((max, entry) => {\n if (entry.kind !== 'review-temp' || entry.issueId !== normalizedIssueId || entry.sequence === undefined) {\n return max;\n }\n return Math.max(max, entry.sequence);\n }, 0);\n return maxSequence + 1;\n}\n\nexport function isOlderThanDays(entry: ParsedStashEntry, days: number, now = new Date()): boolean {\n if (!entry.createdAt) return false;\n return now.getTime() - entry.createdAt.getTime() > days * 24 * 60 * 60 * 1000;\n}\n\nexport function isSalvageableStash(entry: ParsedStashEntry): entry is SalvageableStashEntry {\n return entry.kind === 'salvageable' && !!entry.issueId && !!entry.shortDescription;\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\nconst toGitError = (op: string, cause: unknown): GitError =>\n new GitError({\n command: ['git', 'stash', op],\n stderr: cause instanceof Error ? cause.message : String(cause),\n exitCode: 1,\n cause,\n });\n\n/** List parsed stash entries (newest first per git's natural ordering). */\nexport const listStashes = (\n repoPath: string,\n): Effect.Effect<readonly ParsedStashEntry[], GitError> =>\n Effect.tryPromise({\n try: () => listStashesPromise(repoPath),\n catch: (cause) => toGitError('list', cause),\n });\n\n/** Create a named stash; returns null if there were no changes to save. */\nexport const createNamedStash = (\n repoPath: string,\n message: string,\n includeUntracked = true,\n): Effect.Effect<string | null, GitError> =>\n Effect.tryPromise({\n try: () => createNamedStashPromise(repoPath, message, includeUntracked),\n catch: (cause) => toGitError('push', cause),\n });\n\n/** Pop a stash by SHA (preferred) or stack ref. */\nexport const popStash = (\n repoPath: string,\n ref: string,\n stackRef?: string,\n): Effect.Effect<void, GitError> =>\n Effect.tryPromise({\n try: () => popStashPromise(repoPath, ref, stackRef),\n catch: (cause) => toGitError('pop', cause),\n });\n\n/** Drop a stash by SHA (preferred) or stack ref. */\nexport const dropStash = (\n repoPath: string,\n ref: string,\n stackRef?: string,\n): Effect.Effect<void, GitError> =>\n Effect.tryPromise({\n try: () => dropStashPromise(repoPath, ref, stackRef),\n catch: (cause) => toGitError('drop', cause),\n });\n\n/** Apply a stash without removing it. */\nexport const applyStash = (\n repoPath: string,\n ref: string,\n stackRef?: string,\n): Effect.Effect<void, GitError> =>\n Effect.tryPromise({\n try: () => applyStashPromise(repoPath, ref, stackRef),\n catch: (cause) => toGitError('apply', cause),\n });\n\n/** Materialise a recovery branch from a stash. Returns the new branch name. */\nexport const createRecoveryBranchFromStash = (\n repoPath: string,\n stashRef: string,\n issueId: string,\n shortDescription: string,\n stackRef?: string,\n): Effect.Effect<string, GitError> =>\n Effect.tryPromise({\n try: () =>\n createRecoveryBranchFromStashPromise(repoPath, stashRef, issueId, shortDescription, stackRef),\n catch: (cause) => toGitError('branch', cause),\n });\n","/**\n * Aggregation Cache Management for Cost Tracking\n *\n * Manages the by-issue.json cache that stores pre-computed cost aggregations.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { Effect } from 'effect';\nimport { CostEvent, readEventsSync, readEventsFromLineSync, getLastEventMetadataSync } from './events.js';\nimport { FsError } from '../errors.js';\n\n// ============== Types ==============\n\nexport interface ModelStats {\n cost: number;\n calls: number;\n tokens: number;\n}\n\nexport interface StageStats {\n cost: number;\n tokens: number;\n calls: number;\n}\n\nexport interface IssueStats {\n totalCost: number;\n budget?: number;\n budgetWarning: boolean;\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens: number;\n cacheWriteTokens: number;\n models: Record<string, ModelStats>;\n providers: Record<string, number>;\n stages: Record<string, StageStats>;\n lastUpdated: string;\n}\n\nexport interface CostCache {\n version: number;\n status: 'live' | 'migrating' | 'stale';\n lastEventTs: string | null;\n lastEventLine: number;\n retentionDays: number;\n issues: Record<string, IssueStats>;\n}\n\n// ============== Constants ==============\n\nconst CACHE_VERSION = 3;\nconst DEFAULT_RETENTION_DAYS = 90;\n\n// Use functions for paths to allow test mocking via process.env.HOME\nfunction getCostsDir(): string {\n return join(process.env.HOME || homedir(), '.overdeck', 'costs');\n}\n\nfunction getCacheFile(): string {\n return join(getCostsDir(), 'by-issue.json');\n}\n\n// ============== Cache Loading ==============\n\n/**\n * Load the cache from disk\n */\nexport function loadCacheSync(): CostCache {\n const cacheFile = getCacheFile();\n if (!existsSync(cacheFile)) {\n return createEmptyCache();\n }\n\n try {\n const content = readFileSync(cacheFile, 'utf-8');\n const cache = JSON.parse(content) as CostCache;\n\n // Validate version\n if (cache.version !== CACHE_VERSION) {\n console.warn(`Cache version mismatch: expected ${CACHE_VERSION}, got ${cache.version}. Rebuilding cache.`);\n return createEmptyCache();\n }\n\n return cache;\n } catch (err) {\n console.error('Error loading cache:', err);\n return createEmptyCache();\n }\n}\n\n/**\n * Create an empty cache structure\n */\nfunction createEmptyCache(): CostCache {\n return {\n version: CACHE_VERSION,\n status: 'live',\n lastEventTs: null,\n lastEventLine: 0,\n retentionDays: DEFAULT_RETENTION_DAYS,\n issues: {},\n };\n}\n\n// ============== Cache Saving ==============\n\n/**\n * Save the cache to disk atomically\n */\nexport function saveCacheSync(cache: CostCache): void {\n const costsDir = getCostsDir();\n const cacheFile = getCacheFile();\n mkdirSync(costsDir, { recursive: true });\n\n // Write to temp file first\n const tempFile = cacheFile + '.tmp';\n const content = JSON.stringify(cache, null, 2);\n writeFileSync(tempFile, content, 'utf-8');\n\n // Atomic rename\n renameSync(tempFile, cacheFile);\n}\n\n// ============== Cache Updates ==============\n\n/**\n * Update cache incrementally from new events\n * @param events Array of events to add\n * @param newLineNumber Optional new line number (for correct tracking with malformed lines)\n */\nexport function updateCacheFromEventsSync(events: CostEvent[], newLineNumber?: number): CostCache {\n const cache = loadCacheSync();\n\n for (const event of events) {\n addEventToCache(cache, event);\n }\n\n // Update metadata\n if (events.length > 0) {\n const lastEvent = events[events.length - 1];\n cache.lastEventTs = lastEvent.ts;\n\n // Use provided line number if available (handles malformed lines correctly)\n // Otherwise fall back to incrementing by event count (for backward compatibility)\n if (newLineNumber !== undefined) {\n cache.lastEventLine = newLineNumber;\n } else {\n cache.lastEventLine += events.length;\n }\n }\n\n cache.status = 'live';\n\n saveCacheSync(cache);\n return cache;\n}\n\n/**\n * Add a single event to the cache\n */\nfunction addEventToCache(cache: CostCache, event: CostEvent): void {\n const issueKey = event.issueId.toUpperCase();\n\n // Initialize issue stats if needed\n if (!cache.issues[issueKey]) {\n cache.issues[issueKey] = {\n totalCost: 0,\n budgetWarning: false,\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n models: {},\n providers: {},\n stages: {},\n lastUpdated: event.ts,\n };\n }\n\n const issue = cache.issues[issueKey];\n\n // Update totals\n issue.totalCost += event.cost;\n issue.inputTokens += event.input;\n issue.outputTokens += event.output;\n issue.cacheReadTokens += event.cacheRead;\n issue.cacheWriteTokens += event.cacheWrite;\n issue.lastUpdated = event.ts;\n\n // Update model stats\n if (!issue.models[event.model]) {\n issue.models[event.model] = {\n cost: 0,\n calls: 0,\n tokens: 0,\n };\n }\n\n const modelStats = issue.models[event.model];\n modelStats.cost += event.cost;\n modelStats.calls += 1;\n modelStats.tokens += event.input + event.output + event.cacheRead + event.cacheWrite;\n\n // Update provider stats\n if (!issue.providers[event.provider]) {\n issue.providers[event.provider] = 0;\n }\n issue.providers[event.provider] += event.cost;\n\n // Update stage stats (using sessionType as stage)\n const stage = event.sessionType || 'unknown';\n if (!issue.stages[stage]) {\n issue.stages[stage] = {\n cost: 0,\n tokens: 0,\n calls: 0,\n };\n }\n const stageStats = issue.stages[stage];\n stageStats.cost += event.cost;\n stageStats.calls += 1;\n stageStats.tokens += event.input + event.output + event.cacheRead + event.cacheWrite;\n\n // Check budget warning\n if (issue.budget) {\n issue.budgetWarning = issue.totalCost >= issue.budget * 0.8;\n }\n\n // Round costs to avoid floating point errors\n issue.totalCost = Math.round(issue.totalCost * 1000000) / 1000000;\n modelStats.cost = Math.round(modelStats.cost * 1000000) / 1000000;\n issue.providers[event.provider] = Math.round(issue.providers[event.provider] * 1000000) / 1000000;\n stageStats.cost = Math.round(stageStats.cost * 1000000) / 1000000;\n}\n\n/**\n * Rebuild the entire cache from all events\n */\nexport function rebuildCacheSync(): CostCache {\n console.log('Rebuilding cost cache from events...');\n\n const cache = createEmptyCache();\n cache.status = 'migrating';\n\n // Read all events\n const events = readEventsSync();\n console.log(`Processing ${events.length} events...`);\n\n for (const event of events) {\n addEventToCache(cache, event);\n }\n\n // Update metadata\n const metadata = getLastEventMetadataSync();\n cache.lastEventTs = metadata.lastEventTs;\n cache.lastEventLine = metadata.lastEventLine;\n cache.status = 'live';\n\n console.log(`Cache rebuilt: ${Object.keys(cache.issues).length} issues, ${events.length} events`);\n\n saveCacheSync(cache);\n return cache;\n}\n\n/**\n * Sync cache with latest events\n * Reads events since the last processed event and updates cache\n */\nexport function syncCacheSync(): CostCache {\n const cache = loadCacheSync();\n\n // Check if there are new events\n const metadata = getLastEventMetadataSync();\n\n if (metadata.lastEventLine === cache.lastEventLine) {\n // Already up to date\n return cache;\n }\n\n if (metadata.lastEventLine < cache.lastEventLine) {\n // Events file was truncated (retention cleanup) - rebuild\n console.log('Events file was truncated, rebuilding cache...');\n return rebuildCacheSync();\n }\n\n // Read new events (returns both events and new line position)\n const { events: newEvents, newLine } = readEventsFromLineSync(cache.lastEventLine);\n\n if (newEvents.length > 0) {\n console.log(`Syncing cache with ${newEvents.length} new events...`);\n return updateCacheFromEventsSync(newEvents, newLine);\n }\n\n // Even if no events, update line number in case file was appended with only malformed lines\n if (newLine !== cache.lastEventLine) {\n cache.lastEventLine = newLine;\n saveCacheSync(cache);\n }\n\n return cache;\n}\n\n// ============== Cache Queries ==============\n\n/**\n * Get costs for all issues\n */\nexport function getCostsByIssueSync(): Record<string, IssueStats> {\n const cache = syncCacheSync();\n return cache.issues;\n}\n\n/**\n * Get costs for a specific issue\n */\nexport function getCostsForIssueSync(issueId: string): IssueStats | null {\n const cache = syncCacheSync();\n const issueKey = issueId.toUpperCase();\n return cache.issues[issueKey] || null;\n}\n\n/**\n * Set budget for an issue\n */\nexport function setIssueBudgetSync(issueId: string, budget: number): void {\n const cache = loadCacheSync();\n const issueKey = issueId.toUpperCase();\n\n if (!cache.issues[issueKey]) {\n cache.issues[issueKey] = {\n totalCost: 0,\n budgetWarning: false,\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n models: {},\n providers: {},\n stages: {},\n lastUpdated: new Date().toISOString(),\n };\n }\n\n cache.issues[issueKey].budget = budget;\n cache.issues[issueKey].budgetWarning = cache.issues[issueKey].totalCost >= budget * 0.8;\n\n saveCacheSync(cache);\n}\n\n/**\n * Get cache status\n */\nexport function getCacheStatus(): {\n status: 'live' | 'migrating' | 'stale';\n lastEventTs: string | null;\n eventCount: number;\n issueCount: number;\n needsSync: boolean;\n} {\n const cache = loadCacheSync();\n const metadata = getLastEventMetadataSync();\n\n return {\n status: cache.status,\n lastEventTs: cache.lastEventTs,\n eventCount: cache.lastEventLine,\n issueCount: Object.keys(cache.issues).length,\n needsSync: metadata.lastEventLine !== cache.lastEventLine,\n };\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\n/** Effect variant of loadCache. Failures surface as FsError. */\nexport const loadCache = (): Effect.Effect<CostCache, FsError> =>\n Effect.try({\n try: () => loadCacheSync(),\n catch: (cause) => new FsError({ path: getCacheFile(), operation: 'loadCache', cause }),\n });\n\n/** Effect variant of saveCache. */\nexport const saveCache = (cache: CostCache): Effect.Effect<void, FsError> =>\n Effect.try({\n try: () => saveCacheSync(cache),\n catch: (cause) => new FsError({ path: getCacheFile(), operation: 'saveCache', cause }),\n });\n\n/** Effect variant of updateCacheFromEvents. */\nexport const updateCacheFromEvents = (\n events: CostEvent[],\n newLineNumber?: number,\n): Effect.Effect<CostCache, FsError> =>\n Effect.try({\n try: () => updateCacheFromEventsSync(events, newLineNumber),\n catch: (cause) => new FsError({ path: getCacheFile(), operation: 'updateCacheFromEvents', cause }),\n });\n\n/** Effect variant of rebuildCache. */\nexport const rebuildCache = (): Effect.Effect<CostCache, FsError> =>\n Effect.try({\n try: () => rebuildCacheSync(),\n catch: (cause) => new FsError({ path: getCacheFile(), operation: 'rebuildCache', cause }),\n });\n\n/** Effect variant of syncCache. */\nexport const syncCache = (): Effect.Effect<CostCache, FsError> =>\n Effect.try({\n try: () => syncCacheSync(),\n catch: (cause) => new FsError({ path: getCacheFile(), operation: 'syncCache', cause }),\n });\n\n/** Effect variant of getCostsByIssue. */\nexport const getCostsByIssue = (): Effect.Effect<Record<string, IssueStats>, FsError> =>\n Effect.try({\n try: () => getCostsByIssueSync(),\n catch: (cause) => new FsError({ path: getCacheFile(), operation: 'getCostsByIssue', cause }),\n });\n\n/** Effect variant of getCostsForIssue. */\nexport const getCostsForIssue = (\n issueId: string,\n): Effect.Effect<IssueStats | null, FsError> =>\n Effect.try({\n try: () => getCostsForIssueSync(issueId),\n catch: (cause) => new FsError({ path: getCacheFile(), operation: 'getCostsForIssue', cause }),\n });\n\n/** Effect variant of setIssueBudget. */\nexport const setIssueBudget = (\n issueId: string,\n budget: number,\n): Effect.Effect<void, FsError> =>\n Effect.try({\n try: () => setIssueBudgetSync(issueId, budget),\n catch: (cause) => new FsError({ path: getCacheFile(), operation: 'setIssueBudget', cause }),\n });\n","/**\n * Historical Data Migration for Cost Tracking\n *\n * Migrates historical session data to the event-sourced format.\n * Includes both main session files and subagent session files.\n */\n\nimport { existsSync, readdirSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { Effect } from 'effect';\nimport { encodeClaudeProjectDir } from '../paths.js';\nimport { appendCostEventSync, CostEvent, eventsFileExists, getLastEventMetadataSync } from './events.js';\nimport { getPricingSync, calculateCostSync, TokenUsage } from '../cost.js';\nimport { FsError } from '../errors.js';\n\n// ============== Types ==============\n\nexport interface MigrationStats {\n agentsProcessed: number;\n sessionFilesProcessed: number;\n subagentFilesProcessed: number;\n eventsCreated: number;\n errors: Array<{ file: string; error: string }>;\n warnings: Array<{ file: string; message: string }>;\n totalCost: number;\n totalTokens: number;\n}\n\ninterface SessionUsage {\n model: string;\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens: number;\n cacheWriteTokens: number;\n timestamp?: string;\n}\n\ninterface AgentContext {\n agentId: string;\n issueId: string;\n sessionType: string;\n workspace: string;\n}\n\n// ============== Path Helpers ==============\n// Use functions for paths to allow test mocking via process.env.HOME\nfunction getAgentsDir(): string {\n return join(process.env.HOME || homedir(), '.overdeck', 'agents');\n}\n\nfunction getClaudeProjectsDir(): string {\n return join(process.env.HOME || homedir(), '.claude', 'projects');\n}\n\nfunction getProjectsYamlPath(): string {\n return join(process.env.HOME || homedir(), '.overdeck', 'projects.yaml');\n}\n\n// ============== Workspace Resolution ==============\n\n/**\n * Infer issueId from agent directory name\n * e.g., 'agent-pan-105' -> 'PAN-105', 'agent-min-663' -> 'MIN-663'\n */\nfunction inferIssueId(agentDir: string): string | null {\n // Strip 'agent-' or 'planning-' prefix\n const stripped = agentDir.replace(/^(agent|planning)-/, '');\n // Match pattern like 'pan-105' or 'min-663'\n const match = stripped.match(/^([a-z]+)-(\\d+)$/i);\n if (match) {\n return `${match[1].toUpperCase()}-${match[2]}`;\n }\n return null;\n}\n\n/**\n * Load project paths from projects.yaml\n */\nfunction getProjectPaths(): string[] {\n const paths: string[] = [];\n try {\n const yamlPath = getProjectsYamlPath();\n if (existsSync(yamlPath)) {\n const content = readFileSync(yamlPath, 'utf-8');\n // Simple YAML parsing for path: values\n const pathMatches = content.match(/^\\s+path:\\s+(.+)$/gm);\n if (pathMatches) {\n for (const m of pathMatches) {\n const pathMatch = m.match(/path:\\s+(.+)/);\n if (pathMatch) {\n paths.push(pathMatch[1].trim());\n }\n }\n }\n }\n } catch {\n // Ignore errors\n }\n return paths;\n}\n\n/**\n * Try to find workspace directory for an issue\n * Searches known project paths for workspaces/feature-{issue-id}\n */\nfunction resolveWorkspace(issueId: string): string | null {\n const issueLower = issueId.toLowerCase();\n const projectPaths = getProjectPaths();\n\n for (const projectPath of projectPaths) {\n const wsPath = join(projectPath, 'workspaces', `feature-${issueLower}`);\n if (existsSync(wsPath)) {\n return wsPath;\n }\n }\n\n return null;\n}\n\n/**\n * Find Claude session directories by scanning the Claude projects dir\n * This is a fallback when workspace path is not known\n */\nfunction findSessionDirsByIssue(issueId: string): string[] {\n const dirs: string[] = [];\n const issueLower = issueId.toLowerCase();\n const claudeDir = getClaudeProjectsDir();\n\n try {\n const entries = readdirSync(claudeDir);\n for (const entry of entries) {\n // Match directories that contain the issue ID pattern\n if (entry.includes(`feature-${issueLower}`)) {\n const fullPath = join(claudeDir, entry);\n dirs.push(fullPath);\n }\n }\n } catch {\n // Ignore errors\n }\n\n return dirs;\n}\n\n// ============== Session Parsing ==============\n\n/**\n * Parse a single session JSONL file and extract usage data\n */\nfunction parseSessionFile(filePath: string): SessionUsage[] {\n const usages: SessionUsage[] = [];\n\n try {\n const content = readFileSync(filePath, 'utf-8');\n const lines = content.split('\\n').filter(l => l.trim());\n\n for (const line of lines) {\n try {\n const entry = JSON.parse(line);\n\n // Extract timestamp\n const timestamp = entry.timestamp || entry.ts || entry.created_at;\n\n // Extract model\n const model = entry.message?.model || entry.model;\n\n // Extract usage - can be at top level or in message\n const usage = entry.usage || entry.message?.usage;\n\n if (usage && model) {\n usages.push({\n model,\n inputTokens: usage.input_tokens || 0,\n outputTokens: usage.output_tokens || 0,\n cacheReadTokens: usage.cache_read_input_tokens || 0,\n cacheWriteTokens: usage.cache_creation_input_tokens || 0,\n timestamp,\n });\n }\n } catch {\n // Skip malformed lines\n }\n }\n } catch (err) {\n throw new Error(`Failed to read file: ${err}`);\n }\n\n return usages;\n}\n\n/**\n * Convert session usage to cost events\n */\nfunction usageToCostEvents(\n usages: SessionUsage[],\n context: AgentContext\n): CostEvent[] {\n const events: CostEvent[] = [];\n\n for (const usage of usages) {\n // Determine provider from model name\n let provider = 'anthropic';\n if (usage.model.includes('gpt')) {\n provider = 'openai';\n } else if (usage.model.includes('gemini')) {\n provider = 'google';\n } else if (usage.model.includes('kimi')) {\n provider = 'custom';\n }\n\n // Get pricing and calculate cost\n const pricing = getPricingSync(provider as any, usage.model);\n if (!pricing) {\n continue; // Skip if no pricing found\n }\n\n const tokenUsage: TokenUsage = {\n inputTokens: usage.inputTokens,\n outputTokens: usage.outputTokens,\n cacheReadTokens: usage.cacheReadTokens,\n cacheWriteTokens: usage.cacheWriteTokens,\n cacheTTL: '5m',\n };\n\n const cost = calculateCostSync(tokenUsage, pricing);\n\n events.push({\n ts: usage.timestamp || new Date().toISOString(),\n type: 'cost',\n agentId: context.agentId,\n issueId: context.issueId,\n sessionType: context.sessionType,\n provider,\n model: usage.model,\n input: usage.inputTokens,\n output: usage.outputTokens,\n cacheRead: usage.cacheReadTokens,\n cacheWrite: usage.cacheWriteTokens,\n cost,\n });\n }\n\n return events;\n}\n\n/**\n * Find session directory for a workspace\n */\nfunction getSessionDir(workspacePath: string): string | null {\n // Claude Code session directory name format: path with leading / removed and / replaced by -\n // e.g., /home/user/projects/foo -> -home-user-projects-foo\n const sessionDirName = encodeClaudeProjectDir(workspacePath);\n const sessionDir = join(getClaudeProjectsDir(), sessionDirName);\n\n if (existsSync(sessionDir)) {\n return sessionDir;\n }\n\n return null;\n}\n\n/**\n * Migrate a single agent's session data\n */\nfunction migrateAgent(agentDir: string, stats: MigrationStats): void {\n const stateFile = join(getAgentsDir(), agentDir, 'state.json');\n\n if (!existsSync(stateFile)) {\n stats.warnings.push({\n file: stateFile,\n message: 'No state.json found',\n });\n return;\n }\n\n // Read agent state\n let state: any;\n try {\n state = JSON.parse(readFileSync(stateFile, 'utf-8'));\n } catch (err) {\n stats.errors.push({\n file: stateFile,\n error: `Failed to parse state.json: ${err}`,\n });\n return;\n }\n\n // Extract context - infer issueId and workspace when not in state.json\n const inferredIssueId = inferIssueId(agentDir);\n const context: AgentContext = {\n agentId: agentDir,\n issueId: state.issueId || inferredIssueId || 'UNKNOWN',\n sessionType: state.sessionType || (agentDir.startsWith('planning-') ? 'planning' : 'implementation'),\n workspace: state.workspace,\n };\n\n // Try to resolve workspace if not in state\n if (!context.workspace && context.issueId !== 'UNKNOWN') {\n context.workspace = resolveWorkspace(context.issueId) || '';\n }\n\n // Find session directory - try workspace path first, then scan by issue\n let sessionDir: string | null = null;\n\n if (context.workspace) {\n // GUARD: Reject broad parent directories that contain multiple projects.\n // Planning agents sometimes have workspace set to a parent dir like /home/user/Projects\n // instead of a specific workspace. Using such a path would attribute ALL sessions from\n // every project to a single issue, causing massive cost inflation.\n const isSpecificWorkspace = context.workspace.includes('workspaces/feature-') ||\n context.workspace.includes(`/${context.issueId.toLowerCase()}`) ||\n context.workspace.includes(`/${context.issueId.toUpperCase()}`);\n\n if (isSpecificWorkspace) {\n sessionDir = getSessionDir(context.workspace);\n } else {\n stats.warnings.push({\n file: stateFile,\n message: `Skipped broad workspace path \"${context.workspace}\" for ${context.issueId} (would attribute unrelated sessions)`,\n });\n }\n }\n\n // Fallback: scan Claude projects directory for matching session dirs\n if (!sessionDir && context.issueId !== 'UNKNOWN') {\n const sessionDirs = findSessionDirsByIssue(context.issueId);\n if (sessionDirs.length > 0) {\n sessionDir = sessionDirs[0];\n }\n }\n\n if (!sessionDir) {\n stats.warnings.push({\n file: stateFile,\n message: `No session directory found for ${context.issueId} (workspace: ${context.workspace || 'unknown'})`,\n });\n return;\n }\n\n // Process main session files\n try {\n const files = readdirSync(sessionDir).filter(f => f.endsWith('.jsonl'));\n\n for (const file of files) {\n const filePath = join(sessionDir, file);\n\n try {\n const usages = parseSessionFile(filePath);\n const events = usageToCostEvents(usages, context);\n\n for (const event of events) {\n appendCostEventSync(event);\n stats.eventsCreated++;\n stats.totalCost += event.cost;\n stats.totalTokens += event.input + event.output + event.cacheRead + event.cacheWrite;\n }\n\n stats.sessionFilesProcessed++;\n } catch (err) {\n stats.errors.push({\n file: filePath,\n error: `${err}`,\n });\n }\n }\n } catch (err) {\n stats.errors.push({\n file: sessionDir,\n error: `Failed to read session directory: ${err}`,\n });\n }\n\n // Process subagent files (CRITICAL - this was missing in old code)\n const subagentsDir = join(sessionDir, 'subagents');\n if (existsSync(subagentsDir)) {\n try {\n const subagentFiles = readdirSync(subagentsDir).filter(f => f.endsWith('.jsonl'));\n\n for (const file of subagentFiles) {\n const filePath = join(subagentsDir, file);\n\n try {\n const usages = parseSessionFile(filePath);\n\n // Create subagent context\n const subagentContext: AgentContext = {\n ...context,\n agentId: `${context.agentId}-subagent-${file.replace('.jsonl', '')}`,\n };\n\n const events = usageToCostEvents(usages, subagentContext);\n\n for (const event of events) {\n appendCostEventSync(event);\n stats.eventsCreated++;\n stats.totalCost += event.cost;\n stats.totalTokens += event.input + event.output + event.cacheRead + event.cacheWrite;\n }\n\n stats.subagentFilesProcessed++;\n } catch (err) {\n stats.errors.push({\n file: filePath,\n error: `${err}`,\n });\n }\n }\n } catch (err) {\n stats.warnings.push({\n file: subagentsDir,\n message: `Failed to read subagents directory: ${err}`,\n });\n }\n }\n\n stats.agentsProcessed++;\n}\n\n// ============== Migration Entry Points ==============\n\n/**\n * Migrate all historical session data to events.jsonl\n */\nexport function migrateAllSessionsSync(): MigrationStats {\n const stats: MigrationStats = {\n agentsProcessed: 0,\n sessionFilesProcessed: 0,\n subagentFilesProcessed: 0,\n eventsCreated: 0,\n errors: [],\n warnings: [],\n totalCost: 0,\n totalTokens: 0,\n };\n\n console.log('Starting migration of historical session data...');\n\n const agentsDir = getAgentsDir();\n\n // Check if agents directory exists\n if (!existsSync(agentsDir)) {\n console.log('No agents directory found - nothing to migrate');\n return stats;\n }\n\n // Get all agent directories\n const agentDirs = readdirSync(agentsDir).filter(\n name => name.startsWith('agent-') || name.startsWith('planning-')\n );\n\n console.log(`Found ${agentDirs.length} agent directories to process`);\n\n // Process each agent\n for (const agentDir of agentDirs) {\n try {\n migrateAgent(agentDir, stats);\n } catch (err) {\n stats.errors.push({\n file: agentDir,\n error: `Failed to migrate agent: ${err}`,\n });\n }\n }\n\n // Log summary\n console.log('\\nMigration complete:');\n console.log(` Agents processed: ${stats.agentsProcessed}`);\n console.log(` Session files: ${stats.sessionFilesProcessed}`);\n console.log(` Subagent files: ${stats.subagentFilesProcessed}`);\n console.log(` Events created: ${stats.eventsCreated}`);\n console.log(` Total cost: $${stats.totalCost.toFixed(4)}`);\n console.log(` Total tokens: ${stats.totalTokens.toLocaleString()}`);\n console.log(` Errors: ${stats.errors.length}`);\n console.log(` Warnings: ${stats.warnings.length}`);\n\n return stats;\n}\n\n/**\n * Check if migration is needed\n */\nexport function needsMigrationSync(): boolean {\n // If events file doesn't exist, we need migration\n if (!eventsFileExists()) {\n return true;\n }\n\n // If events file is empty, we need migration\n const metadata = getLastEventMetadataSync();\n if (metadata.totalEvents === 0) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Migrate only if needed\n */\nexport function migrateIfNeededSync(): MigrationStats | null {\n if (!needsMigrationSync()) {\n console.log('Migration not needed - events file already exists with data');\n return null;\n }\n\n return migrateAllSessionsSync();\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\n/** Effect variant of migrateAllSessions. Failures surface as FsError. */\nexport const migrateAllSessions = (): Effect.Effect<MigrationStats, FsError> =>\n Effect.try({\n try: () => migrateAllSessionsSync(),\n catch: (cause) => new FsError({ path: '<all sessions>', operation: 'migrateAllSessions', cause }),\n });\n\n/** Effect variant of needsMigration. */\nexport const needsMigration = (): Effect.Effect<boolean, FsError> =>\n Effect.try({\n try: () => needsMigrationSync(),\n catch: (cause) => new FsError({ path: '<events>', operation: 'needsMigration', cause }),\n });\n\n/** Effect variant of migrateIfNeeded. */\nexport const migrateIfNeeded = (): Effect.Effect<MigrationStats | null, FsError> =>\n Effect.try({\n try: () => migrateIfNeededSync(),\n catch: (cause) => new FsError({ path: '<all sessions>', operation: 'migrateIfNeeded', cause }),\n });\n","import { findConversationForCostSessionSync } from '../overdeck/conversations.js';\nimport { getOverdeckDatabaseSync } from '../overdeck/infra.js';\n\nexport const NO_ISSUE_BUCKETS = {\n conversations: 'CONVERSATIONS',\n flywheel: 'FLYWHEEL',\n unattributed: 'UNATTRIBUTED',\n} as const;\n\nexport type NoIssueBucket = typeof NO_ISSUE_BUCKETS[keyof typeof NO_ISSUE_BUCKETS];\n\nexport type ConversationSessionLookup = (input: {\n sessionId?: string | null;\n agentId?: string | null;\n}) => { name: string } | null;\n\nexport function classifySessionBucket(\n input: { sessionId?: string | null; agentId?: string | null },\n lookup: ConversationSessionLookup,\n): NoIssueBucket {\n const conversation = lookup(input);\n if (!conversation) return NO_ISSUE_BUCKETS.unattributed;\n if (conversation.name === 'flywheel-orchestrator') return NO_ISSUE_BUCKETS.flywheel;\n return NO_ISSUE_BUCKETS.conversations;\n}\n\ninterface UnknownCostSessionRow {\n sessionId: string | null;\n agentId: string | null;\n}\n\nexport function reclassifyUnknownCostEventsSync(): { updated: number } {\n const db = getOverdeckDatabaseSync();\n const rows = db.prepare(\n `SELECT DISTINCT session_id AS sessionId, agent_id AS agentId\n FROM cost_events\n WHERE issue_id = 'UNKNOWN'`,\n ).all() as UnknownCostSessionRow[];\n let updated = 0;\n const update = db.prepare(\n `UPDATE cost_events\n SET issue_id = ?\n WHERE issue_id = 'UNKNOWN'\n AND session_id IS ?\n AND agent_id IS ?`,\n );\n for (const row of rows) {\n const bucket = classifySessionBucket(row, findConversationForCostSessionSync);\n const result = update.run(bucket, row.sessionId, row.agentId);\n updated += result.changes;\n }\n return { updated };\n}\n","/**\n * Cost Reconciler — Periodic catch-up sweep for cost tracking\n *\n * The live recording hook (heartbeat-hook → record-cost-event.js) captures costs\n * in real time, but can miss events due to hook failures, process crashes, or\n * system reboots. The reconciler ensures completeness by periodically sweeping\n * ALL Claude transcript files and importing any events not yet in SQLite.\n *\n * v2 Architecture: Scans ~/.claude/projects/ directly instead of going through\n * agent state.json files. This catches everything:\n * - Work agent sessions (from worktree workspaces)\n * - Planning agent sessions (from main project dirs)\n * - Specialist sessions (review, test, merge)\n * - Interactive/manual Claude sessions\n *\n * Key properties:\n * - Idempotent: dedup on request_id — safe to run any number of times\n * - Incremental: tracks byte offset per session — only reads new bytes\n * - Non-destructive: never deletes from SQLite, append-only\n * - Catches everything: scans all transcript files regardless of source\n */\n\nimport { readFileSync, existsSync, readdirSync, openSync, readSync, fstatSync, closeSync } from 'fs';\nimport { join, basename } from 'path';\nimport { homedir } from 'os';\nimport { Effect } from 'effect';\nimport { calculateCostSync, getPricingSync, type AIProvider, type TokenUsage } from '../cost.js';\nimport { FsError } from '../errors.js';\nimport { CostDoorLive, CostWriter, type CostEvent as OverdeckCostEvent } from '../overdeck/cost.js';\nimport { findConversationForCostSessionSync } from '../overdeck/conversations.js';\nimport type { IssueId } from '../overdeck/issues.js';\nimport { classifySessionBucket, type ConversationSessionLookup } from './attribution.js';\nimport type { CostEvent } from './events.js';\n\n// ============== Types ==============\n\nexport interface ReconcileResult {\n sessionsScanned: number;\n sessionsWithNewData: number;\n eventsImported: number;\n duplicatesSkipped: number;\n errors: Array<{ path: string; error: string }>;\n earliestEventTs: string | null;\n latestEventTs: string | null;\n}\n\ninterface SessionMapping {\n agentId: string;\n issueId: string | null;\n sessionType: string; // planning, implementation, review, test, merge\n}\n\ninterface TranscriptEntry {\n type?: string;\n requestId?: string;\n timestamp?: string;\n ts?: string;\n created_at?: string;\n message?: {\n model?: string;\n usage?: {\n input_tokens?: number;\n output_tokens?: number;\n cache_read_input_tokens?: number;\n cache_creation_input_tokens?: number;\n };\n };\n}\n\n/**\n * Pi/oh-my-pi harness transcript entry (PAN-1935).\n *\n * Pi writes session transcripts to ~/.overdeck/agents/<id>/*.jsonl with a\n * different shape than Claude Code: top-level `type: 'message'` rows whose\n * `message.usage` uses short camelCase keys (`input`, `output`, `cacheRead`,\n * `cacheWrite`) — NOT the Anthropic `input_tokens`/`cache_read_input_tokens`\n * shape the Claude path expects. `message.provider` and `message.model` carry\n * the real routed provider/model (e.g. zai/glm-5.2); `message.responseId` is\n * the provider's unique response id, used for dedup.\n */\ninterface PiTranscriptEntry {\n type?: string;\n id?: string;\n timestamp?: string;\n message?: {\n role?: string;\n model?: string;\n provider?: string;\n responseId?: string;\n usage?: {\n input?: number;\n output?: number;\n cacheRead?: number;\n cacheWrite?: number;\n totalTokens?: number;\n };\n };\n}\n\n/**\n * Map a pi-reported provider string to the AIProvider union used by the pricing\n * layer. zai/kimi/minimax/mimo and anything unknown collapse to 'custom' (which\n * is how these providers are keyed in DEFAULT_PRICING).\n */\nfunction piProviderToAiProvider(provider?: string): AIProvider {\n if (provider === 'anthropic') return 'anthropic';\n if (provider === 'openai') return 'openai';\n if (provider === 'google') return 'google';\n return 'custom';\n}\n\n// ============== Path Helpers ==============\n\nfunction getAgentsDir(): string {\n return join(process.env.HOME || homedir(), '.overdeck', 'agents');\n}\n\nfunction getClaudeProjectsDir(): string {\n return join(process.env.HOME || homedir(), '.claude', 'projects');\n}\n\n/**\n * Extract session UUID from a transcript filename.\n * e.g., \"678c8d8d-cefe-45ca-bebc-bafe602aff93.jsonl\" → \"678c8d8d-cefe-45ca-bebc-bafe602aff93\"\n */\nfunction extractSessionId(filename: string): string {\n return basename(filename, '.jsonl');\n}\n\nexport function resolveUnmappedSessionIssueId(\n input: { sessionId?: string | null; agentId?: string | null },\n lookup: ConversationSessionLookup = findConversationForCostSessionSync,\n): string {\n return classifySessionBucket(input, lookup);\n}\n\n/**\n * Decode a Claude projects directory name back to the original cwd path.\n * e.g., \"-home-eltmon-Projects-krux-workspaces-feature-krux-4\" → \"/home/eltmon/Projects/krux/workspaces/feature-krux-4\"\n */\nfunction decodeClaudeDirName(dirName: string): string {\n // Strip leading dash, replace dashes with slashes, add leading slash\n // This is imperfect (directory names with dashes get mangled) but works for inference\n return '/' + dirName.replace(/^-/, '').replace(/-/g, '/');\n}\n\n// ============== Session-to-Agent Mapping ==============\n\n/**\n * Build a reverse index: session UUID → agent context.\n * Sources:\n * 1. sessions.json files in agent directories (authoritative — written by heartbeat hook)\n * 2. Agent state.json for issue/workspace context\n */\nfunction buildSessionIndex(): Map<string, SessionMapping> {\n const index = new Map<string, SessionMapping>();\n const agentsDir = getAgentsDir();\n\n if (!existsSync(agentsDir)) return index;\n\n let entries: string[];\n try {\n entries = readdirSync(agentsDir);\n } catch {\n return index;\n }\n\n for (const agentDir of entries) {\n const agentPath = join(agentsDir, agentDir);\n\n // Read sessions.json for the session UUID list\n const sessionsFile = join(agentPath, 'sessions.json');\n let sessionIds: string[] = [];\n if (existsSync(sessionsFile)) {\n try {\n sessionIds = JSON.parse(readFileSync(sessionsFile, 'utf-8'));\n } catch { /* skip */ }\n }\n\n // Read state.json for issue/workspace context and role.\n const stateFile = join(agentPath, 'state.json');\n let issueId: string | null = inferIssueId(agentDir);\n let stateRole: string | undefined;\n if (existsSync(stateFile)) {\n try {\n const state = JSON.parse(readFileSync(stateFile, 'utf-8'));\n if (state.issueId) issueId = state.issueId;\n if (state.role) stateRole = state.role;\n } catch { /* use inferred */ }\n }\n\n // Determine session type: prefer state.json role, then infer from agent directory name\n let sessionType = stateRole || 'work';\n const reviewSubRole = agentDir.match(/-review-(security|correctness|performance|requirements)$/i)?.[1]?.toLowerCase();\n if (reviewSubRole) {\n sessionType = `review.${reviewSubRole}`;\n } else if (agentDir.startsWith('planning-')) {\n sessionType = 'planning';\n } else if (agentDir.includes('review')) {\n sessionType = 'review';\n } else if (agentDir.includes('test')) {\n sessionType = 'test';\n } else if (agentDir.includes('merge')) {\n sessionType = 'merge';\n }\n\n // Map each session UUID to this agent\n for (const sid of sessionIds) {\n index.set(sid, {\n agentId: agentDir,\n issueId: issueId ?? resolveUnmappedSessionIssueId({ sessionId: sid, agentId: agentDir }),\n sessionType,\n });\n }\n }\n\n return index;\n}\n\n/**\n * Infer issue ID from a decoded path.\n * Looks for patterns like \"feature-min-787\", \"feature/pan-208\", etc.\n */\nfunction inferIssueFromPath(decodedPath: string): string | null {\n const match = decodedPath.match(/(?:feature[-/])?(pan|min|aud|krux|cli)[-/](\\d+)/i);\n if (match) {\n return `${match[1].toUpperCase()}-${match[2]}`;\n }\n return null;\n}\n\n/**\n * Infer issue ID from agent directory name.\n * e.g., 'agent-pan-105' → 'PAN-105', 'planning-min-663' → 'MIN-663'\n */\nfunction inferIssueId(agentDir: string): string | null {\n const stripped = agentDir.replace(/^(agent|planning|specialist)-/, '');\n const match = stripped.match(/^(?:.*-)?(pan|min|aud|krux|cli)-(\\d+)$/i);\n if (match) {\n return `${match[1].toUpperCase()}-${match[2]}`;\n }\n return null;\n}\n\n// ============== Offset Tracking ==============\n\nfunction getSessionOffset(_sessionId: string): number {\n return 0;\n}\n\nfunction saveSessionOffset(\n sessionId: string,\n byteOffset: number,\n newEvents: number,\n agentId: string,\n issueId: string,\n transcriptPath: string,\n): void {\n void sessionId;\n void byteOffset;\n void newEvents;\n void agentId;\n void issueId;\n void transcriptPath;\n}\n\n// ============== Transcript Processing ==============\n\n/**\n * Read new bytes from a transcript file starting at the given offset.\n */\nfunction readNewBytes(filePath: string, fromOffset: number): { content: string; newSize: number } | null {\n let fd: number;\n try {\n fd = openSync(filePath, 'r');\n } catch {\n return null;\n }\n\n try {\n const stat = fstatSync(fd);\n if (stat.size <= fromOffset) {\n return { content: '', newSize: stat.size };\n }\n\n const bytesToRead = stat.size - fromOffset;\n const buffer = Buffer.alloc(bytesToRead);\n readSync(fd, buffer, 0, bytesToRead, fromOffset);\n return { content: buffer.toString('utf-8'), newSize: stat.size };\n } finally {\n closeSync(fd);\n }\n}\n\n/**\n * Parse transcript content and extract cost events.\n * Only processes assistant messages with usage data and a requestId.\n */\nexport function extractCostEvents(\n content: string,\n agentId: string,\n issueId: string,\n sessionType: string,\n sessionId: string,\n): CostEvent[] {\n const events: CostEvent[] = [];\n const lines = content.split('\\n');\n\n for (const line of lines) {\n if (!line.trim()) continue;\n\n try {\n const entry = JSON.parse(line) as TranscriptEntry;\n\n if (entry.type !== 'assistant' || !entry.message?.usage) continue;\n\n const requestId: string | undefined = entry.requestId ?? undefined;\n\n const usage = entry.message.usage;\n const model = entry.message.model || 'claude-sonnet-4';\n const inputTokens = usage.input_tokens || 0;\n const outputTokens = usage.output_tokens || 0;\n const cacheReadTokens = usage.cache_read_input_tokens || 0;\n const cacheWriteTokens = usage.cache_creation_input_tokens || 0;\n\n if (inputTokens === 0 && outputTokens === 0 && cacheReadTokens === 0 && cacheWriteTokens === 0) continue;\n\n let provider: AIProvider = 'anthropic';\n if (model.includes('gpt')) provider = 'openai';\n else if (model.includes('gemini')) provider = 'google';\n else if (model.includes('kimi')) provider = 'custom' as AIProvider;\n else if (model.toLowerCase().startsWith('minimax')) provider = 'custom' as AIProvider;\n\n // Strip claudish prefix for pricing lookup: \"oai@gpt-5.4\" → \"gpt-5.4\"\n const pricingModel = model.replace(/^(?:oai|cx|go)@/, '');\n const pricing = getPricingSync(provider, pricingModel);\n if (!pricing) continue;\n\n const tokenUsage: TokenUsage = { inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, cacheTTL: '5m' };\n const cost = calculateCostSync(tokenUsage, pricing);\n const timestamp = entry.timestamp || entry.ts || entry.created_at || new Date().toISOString();\n\n events.push({\n ts: timestamp,\n type: 'cost',\n agentId,\n issueId,\n sessionType,\n provider,\n model,\n input: inputTokens,\n output: outputTokens,\n cacheRead: cacheReadTokens,\n cacheWrite: cacheWriteTokens,\n cost,\n requestId,\n sessionId,\n });\n } catch {\n // Skip malformed lines\n }\n }\n\n return events;\n}\n\n/**\n * Parse Pi/oh-my-pi transcript content and extract cost events (PAN-1935).\n *\n * Pi's `message.usage` uses short camelCase keys (`input`, `output`,\n * `cacheRead`, `cacheWrite`) and the real model/provider live on `message`\n * (not inferred from a Claude-style `model` field). Cost is always computed\n * from tokens × pricing because pi reports `cost.total = 0` for non-Anthropic\n * providers. Exported for unit testing.\n */\nexport function extractPiCostEvents(\n content: string,\n agentId: string,\n issueId: string,\n sessionType: string,\n sessionId: string,\n): CostEvent[] {\n const events: CostEvent[] = [];\n for (const line of content.split('\\n')) {\n if (!line.trim()) continue;\n let entry: PiTranscriptEntry;\n try {\n entry = JSON.parse(line) as PiTranscriptEntry;\n } catch {\n continue;\n }\n if (entry.type !== 'message') continue;\n const usage = entry.message?.usage;\n if (!usage) continue;\n\n const inputTokens = usage.input ?? 0;\n const outputTokens = usage.output ?? 0;\n const cacheReadTokens = usage.cacheRead ?? 0;\n const cacheWriteTokens = usage.cacheWrite ?? 0;\n if (inputTokens === 0 && outputTokens === 0 && cacheReadTokens === 0 && cacheWriteTokens === 0) continue;\n\n const model = entry.message?.model || 'unknown';\n const provider = piProviderToAiProvider(entry.message?.provider);\n // Strip any routing prefix (oai@/cx@/go@) for pricing lookup.\n const pricingModel = model.replace(/^(?:oai|cx|go)@/, '');\n const pricing = getPricingSync(provider, pricingModel);\n if (!pricing) continue;\n\n const tokenUsage: TokenUsage = { inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, cacheTTL: '5m' };\n const cost = calculateCostSync(tokenUsage, pricing);\n // Prefer the provider response id for precise dedup; fall back to a\n // session-scoped synthetic id so re-runs are idempotent.\n const requestId = entry.message?.responseId ?? (entry.id ? `${sessionId}#${entry.id}` : undefined);\n const timestamp = entry.timestamp || new Date().toISOString();\n\n events.push({\n ts: timestamp,\n type: 'cost',\n agentId,\n issueId,\n sessionType,\n provider,\n model,\n input: inputTokens,\n output: outputTokens,\n cacheRead: cacheReadTokens,\n cacheWrite: cacheWriteTokens,\n cost,\n requestId,\n sessionId,\n });\n }\n return events;\n}\n\n/**\n * Pi session transcripts in an agent dir. Pi transcripts begin with a\n * `{\"type\":\"session\",\"version\":3,...}` header; we detect that and skip the\n * sibling non-transcripts (activity.jsonl, cost-events.jsonl, pending-events).\n */\nfunction findPiTranscriptFiles(agentDir: string): string[] {\n let files: string[];\n try {\n files = readdirSync(agentDir).filter((f) => f.endsWith('.jsonl'));\n } catch {\n return [];\n }\n const transcripts: string[] = [];\n for (const f of files) {\n if (f === 'activity.jsonl' || f === 'cost-events.jsonl' || f === 'pending-events.jsonl') continue;\n const full = join(agentDir, f);\n try {\n const fd = openSync(full, 'r');\n const buf = Buffer.alloc(128);\n const bytesRead = readSync(fd, buf, 0, 128, 0);\n closeSync(fd);\n const head = buf.toString('utf-8', 0, bytesRead);\n if (/\"type\"\\s*:\\s*\"session\"/.test(head)) transcripts.push(full);\n } catch {\n // skip unreadable\n }\n }\n return transcripts;\n}\n\n/**\n * Sweep pi session transcripts under the per-agent dirs (~/.overdeck/agents)\n * and import their cost events through the same Overdeck cost writer the Claude\n * path uses (PAN-1935).\n * Independent of the pi extension hook, so it captures cost even when the\n * extension emits null usage or the wrong model label.\n */\nexport async function reconcilePiTranscripts(): Promise<ReconcileResult> {\n const result: ReconcileResult = {\n sessionsScanned: 0,\n sessionsWithNewData: 0,\n eventsImported: 0,\n duplicatesSkipped: 0,\n errors: [],\n earliestEventTs: null,\n latestEventTs: null,\n };\n\n const agentsDir = getAgentsDir();\n if (!existsSync(agentsDir)) return result;\n\n let agentDirs: string[];\n try {\n agentDirs = readdirSync(agentsDir);\n } catch {\n return result;\n }\n\n for (const agentDirName of agentDirs) {\n const agentPath = join(agentsDir, agentDirName);\n\n // Resolve issueId + sessionType from state.json (authoritative), falling\n // back to inference from the directory name.\n let issueId: string | null = inferIssueId(agentDirName);\n let sessionType = 'work';\n const stateFile = join(agentPath, 'state.json');\n if (existsSync(stateFile)) {\n try {\n const st = JSON.parse(readFileSync(stateFile, 'utf-8')) as { issueId?: string; role?: string };\n if (st.issueId) issueId = st.issueId;\n if (st.role) sessionType = st.role;\n } catch {\n // use inferred\n }\n }\n if (agentDirName.startsWith('planning-')) sessionType = 'planning';\n\n const transcripts = findPiTranscriptFiles(agentPath);\n for (const transcriptPath of transcripts) {\n const sessionId = basename(transcriptPath, '.jsonl');\n const resolvedIssueId = issueId ?? resolveUnmappedSessionIssueId({ sessionId, agentId: agentDirName });\n result.sessionsScanned++;\n try {\n const content = readFileSync(transcriptPath, 'utf-8');\n const events = extractPiCostEvents(content, agentDirName, resolvedIssueId, sessionType, sessionId);\n if (events.length === 0) continue;\n result.sessionsWithNewData++;\n const { inserted, duplicates, earliestEventTs, latestEventTs } = await recordCostEventsThroughOverdeck(events, `reconciler:${transcriptPath}`);\n result.eventsImported += inserted;\n result.duplicatesSkipped += duplicates;\n mergeCoverage(result, { earliestEventTs, latestEventTs });\n } catch (err) {\n result.errors.push({\n path: transcriptPath,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n }\n\n return result;\n}\n\nfunction toOverdeckCostEvent(event: CostEvent, sourceFile: string): OverdeckCostEvent {\n return {\n ts: new Date(event.ts),\n issueId: event.issueId ? (event.issueId as IssueId) : null,\n agentId: event.agentId ?? null,\n sessionId: event.sessionId ?? null,\n sessionType: event.sessionType ?? null,\n provider: event.provider ?? null,\n model: event.model ?? null,\n input: event.input ?? 0,\n output: event.output ?? 0,\n cacheRead: event.cacheRead ?? 0,\n cacheWrite: event.cacheWrite ?? 0,\n cost: event.cost ?? 0,\n requestId: event.requestId ?? null,\n sourceFile,\n };\n}\n\nasync function recordCostEventsThroughOverdeck(\n events: CostEvent[],\n sourceFile: string,\n opts: { dryRun?: boolean } = {},\n): Promise<{ inserted: number; duplicates: number; earliestEventTs: string | null; latestEventTs: string | null }> {\n let inserted = 0;\n let duplicates = 0;\n let earliestEventTs: string | null = null;\n let latestEventTs: string | null = null;\n for (const event of events) {\n const didInsert = await Effect.runPromise(\n CostWriter.use((writer) => writer.record(toOverdeckCostEvent(event, sourceFile), { dryRun: opts.dryRun })).pipe(\n Effect.provide(CostDoorLive),\n ),\n );\n if (didInsert) {\n inserted++;\n const iso = new Date(event.ts).toISOString();\n if (earliestEventTs == null || iso < earliestEventTs) earliestEventTs = iso;\n if (latestEventTs == null || iso > latestEventTs) latestEventTs = iso;\n } else {\n duplicates++;\n }\n }\n return { inserted, duplicates, earliestEventTs, latestEventTs };\n}\n\nfunction mergeCoverage(\n result: Pick<ReconcileResult, 'earliestEventTs' | 'latestEventTs'>,\n coverage: Pick<ReconcileResult, 'earliestEventTs' | 'latestEventTs'>,\n): void {\n if (coverage.earliestEventTs != null && (result.earliestEventTs == null || coverage.earliestEventTs < result.earliestEventTs)) {\n result.earliestEventTs = coverage.earliestEventTs;\n }\n if (coverage.latestEventTs != null && (result.latestEventTs == null || coverage.latestEventTs > result.latestEventTs)) {\n result.latestEventTs = coverage.latestEventTs;\n }\n}\n\nasync function reconcilePromise(opts: { dryRun?: boolean; includePi?: boolean } = {}): Promise<ReconcileResult> {\n const result: ReconcileResult = {\n sessionsScanned: 0,\n sessionsWithNewData: 0,\n eventsImported: 0,\n duplicatesSkipped: 0,\n errors: [],\n earliestEventTs: null,\n latestEventTs: null,\n };\n\n const claudeProjectsDir = getClaudeProjectsDir();\n if (!existsSync(claudeProjectsDir)) return result;\n\n // Build reverse index: session UUID → (agentId, issueId, sessionType)\n const sessionIndex = buildSessionIndex();\n\n // Scan all Claude project directories\n let projectDirs: string[];\n try {\n projectDirs = readdirSync(claudeProjectsDir);\n } catch {\n return result;\n }\n\n for (const dirName of projectDirs) {\n const projectDir = join(claudeProjectsDir, dirName);\n\n // Skip non-directories\n try {\n const stat = fstatSync(openSync(projectDir, 'r'));\n // fstatSync on a dir fd doesn't work well, use readdirSync as canary\n } catch { /* skip */ }\n\n // Infer issue ID from the directory name (decoded path)\n const decodedPath = decodeClaudeDirName(dirName);\n const pathIssueId = inferIssueFromPath(decodedPath);\n\n // Find all transcript JSONL files (top-level)\n let files: string[];\n try {\n files = readdirSync(projectDir).filter(f => f.endsWith('.jsonl'));\n } catch {\n continue;\n }\n\n // Process each transcript file\n for (const file of files) {\n const sessionId = extractSessionId(file);\n const transcriptPath = join(projectDir, file);\n result.sessionsScanned++;\n\n try {\n // Look up agent mapping for this session\n const mapping = sessionIndex.get(sessionId);\n const agentId = mapping?.agentId || 'unattributed';\n const issueId = mapping?.issueId || pathIssueId || resolveUnmappedSessionIssueId({ sessionId, agentId });\n const sessionType = mapping?.sessionType || 'implementation';\n\n // Get last processed offset\n const lastOffset = getSessionOffset(sessionId);\n\n // Read new bytes\n const readResult = readNewBytes(transcriptPath, lastOffset);\n if (!readResult || !readResult.content) {\n if (!opts.dryRun && readResult && readResult.newSize > lastOffset) {\n saveSessionOffset(sessionId, readResult.newSize, 0, agentId, issueId, transcriptPath);\n }\n continue;\n }\n\n // Extract cost events\n const events = extractCostEvents(readResult.content, agentId, issueId, sessionType, sessionId);\n\n if (events.length === 0) {\n if (!opts.dryRun) {\n saveSessionOffset(sessionId, readResult.newSize, 0, agentId, issueId, transcriptPath);\n }\n continue;\n }\n\n result.sessionsWithNewData++;\n\n const { inserted, duplicates, earliestEventTs, latestEventTs } = await recordCostEventsThroughOverdeck(events, `reconciler:${transcriptPath}`, { dryRun: opts.dryRun });\n result.eventsImported += inserted;\n result.duplicatesSkipped += duplicates;\n mergeCoverage(result, { earliestEventTs, latestEventTs });\n\n if (!opts.dryRun) {\n saveSessionOffset(sessionId, readResult.newSize, inserted, agentId, issueId, transcriptPath);\n }\n } catch (err) {\n result.errors.push({\n path: transcriptPath,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n // Also check subagents directory\n const subagentsDir = join(projectDir, 'subagents');\n if (existsSync(subagentsDir)) {\n try {\n const subFiles = readdirSync(subagentsDir).filter(f => f.endsWith('.jsonl'));\n for (const file of subFiles) {\n const sessionId = `sub-${extractSessionId(file)}`;\n const transcriptPath = join(subagentsDir, file);\n result.sessionsScanned++;\n\n try {\n const mapping = sessionIndex.get(sessionId);\n const agentId = mapping?.agentId || 'unattributed-subagent';\n const issueId = mapping?.issueId || pathIssueId || resolveUnmappedSessionIssueId({ sessionId, agentId });\n const sessionType = mapping?.sessionType || 'implementation';\n\n const lastOffset = getSessionOffset(sessionId);\n const readResult = readNewBytes(transcriptPath, lastOffset);\n if (!readResult || !readResult.content) continue;\n\n const events = extractCostEvents(readResult.content, agentId, issueId, sessionType, sessionId);\n\n if (events.length === 0) {\n if (!opts.dryRun) {\n saveSessionOffset(sessionId, readResult.newSize, 0, agentId, issueId, transcriptPath);\n }\n continue;\n }\n\n result.sessionsWithNewData++;\n const { inserted, duplicates, earliestEventTs, latestEventTs } = await recordCostEventsThroughOverdeck(events, `reconciler:${transcriptPath}`, { dryRun: opts.dryRun });\n result.eventsImported += inserted;\n result.duplicatesSkipped += duplicates;\n mergeCoverage(result, { earliestEventTs, latestEventTs });\n if (!opts.dryRun) {\n saveSessionOffset(sessionId, readResult.newSize, inserted, agentId, issueId, transcriptPath);\n }\n } catch (err) {\n result.errors.push({\n path: transcriptPath,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n } catch {\n // Non-fatal\n }\n }\n }\n\n // PAN-1935: also sweep pi/oh-my-pi harness transcripts under\n // ~/.overdeck/agents/*/ (the Claude scan above only covers ~/.claude/projects).\n if (opts.includePi ?? true) {\n const piResult = await reconcilePiTranscripts();\n result.sessionsScanned += piResult.sessionsScanned;\n result.sessionsWithNewData += piResult.sessionsWithNewData;\n result.eventsImported += piResult.eventsImported;\n result.duplicatesSkipped += piResult.duplicatesSkipped;\n result.errors.push(...piResult.errors);\n mergeCoverage(result, piResult);\n }\n\n return result;\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\n/**\n * Effect variant of reconcile. Per-file errors are still surfaced via\n * `result.errors`; only catastrophic failures (e.g. SQLite open failure)\n * surface on the Effect error channel.\n */\nexport const reconcile = (opts: { dryRun?: boolean; includePi?: boolean } = {}): Effect.Effect<ReconcileResult, FsError> =>\n Effect.tryPromise({\n try: () => reconcilePromise(opts),\n catch: (cause) => new FsError({ path: '<reconciler>', operation: 'reconcile', cause }),\n });\n","/**\n * Event Retention Management for Cost Tracking\n *\n * Manages the rolling 90-day retention window for cost events.\n */\n\nimport { Effect } from 'effect';\nimport { readEventsSync, replaceEventsFileSync, getLastEventMetadataSync, CostEvent } from './events.js';\nimport { rebuildCacheSync } from './aggregator.js';\nimport { FsError } from '../errors.js';\n\n// ============== Types ==============\n\nexport interface RetentionStats {\n totalEvents: number;\n eventsRemoved: number;\n eventsRetained: number;\n oldestEventTs: string | null;\n newestEventTs: string | null;\n}\n\n// ============== Retention Logic ==============\n\n/**\n * Prune events older than the specified retention period\n * Returns stats about what was pruned\n */\nexport function pruneOldEventsSync(retentionDays: number = 90): RetentionStats {\n console.log(`Pruning events older than ${retentionDays} days...`);\n\n // Calculate cutoff date using milliseconds (not setDate, which is DST-sensitive)\n const cutoffTs = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000).toISOString();\n\n // Read all events\n const allEvents = readEventsSync();\n const totalEvents = allEvents.length;\n\n if (totalEvents === 0) {\n return {\n totalEvents: 0,\n eventsRemoved: 0,\n eventsRetained: 0,\n oldestEventTs: null,\n newestEventTs: null,\n };\n }\n\n // Filter events to keep only those within retention window\n const retainedEvents = allEvents.filter(event => event.ts >= cutoffTs);\n const eventsRemoved = totalEvents - retainedEvents.length;\n\n // Get timestamps\n const oldestEventTs = retainedEvents.length > 0 ? retainedEvents[0].ts : null;\n const newestEventTs = retainedEvents.length > 0 ? retainedEvents[retainedEvents.length - 1].ts : null;\n\n // If we removed any events, write the pruned file\n if (eventsRemoved > 0) {\n console.log(`Removing ${eventsRemoved} events older than ${cutoffTs}...`);\n replaceEventsFileSync(retainedEvents);\n\n // Rebuild cache after pruning\n console.log('Rebuilding cache after pruning...');\n rebuildCacheSync();\n\n console.log(`Pruning complete: removed ${eventsRemoved} events, retained ${retainedEvents.length} events`);\n } else {\n console.log('No events to prune - all events are within retention window');\n }\n\n return {\n totalEvents,\n eventsRemoved,\n eventsRetained: retainedEvents.length,\n oldestEventTs,\n newestEventTs,\n };\n}\n\n/**\n * Check if pruning is needed based on oldest event\n */\nexport function needsPruningSync(retentionDays: number = 90): boolean {\n const allEvents = readEventsSync();\n\n if (allEvents.length === 0) {\n return false;\n }\n\n // Check oldest event\n const oldestEvent = allEvents[0];\n const oldestDate = new Date(oldestEvent.ts);\n const cutoffDate = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000);\n\n return oldestDate < cutoffDate;\n}\n\n/**\n * Get retention status\n */\nexport function getRetentionStatusSync(retentionDays: number = 90): {\n totalEvents: number;\n oldestEventTs: string | null;\n oldestEventAge: number; // days\n needsPruning: boolean;\n eventsToRemove: number;\n} {\n const allEvents = readEventsSync();\n\n if (allEvents.length === 0) {\n return {\n totalEvents: 0,\n oldestEventTs: null,\n oldestEventAge: 0,\n needsPruning: false,\n eventsToRemove: 0,\n };\n }\n\n const oldestEvent = allEvents[0];\n const oldestDate = new Date(oldestEvent.ts);\n const now = new Date();\n const oldestEventAge = Math.floor((now.getTime() - oldestDate.getTime()) / (1000 * 60 * 60 * 24));\n\n const cutoffTs = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000).toISOString();\n\n const eventsToRemove = allEvents.filter(e => e.ts < cutoffTs).length;\n\n return {\n totalEvents: allEvents.length,\n oldestEventTs: oldestEvent.ts,\n oldestEventAge,\n needsPruning: oldestEventAge > retentionDays,\n eventsToRemove,\n };\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\n/** Effect variant of pruneOldEvents. */\nexport const pruneOldEvents = (\n retentionDays: number = 90,\n): Effect.Effect<RetentionStats, FsError> =>\n Effect.try({\n try: () => pruneOldEventsSync(retentionDays),\n catch: (cause) => new FsError({ path: '<events>', operation: 'pruneOldEvents', cause }),\n });\n\n/** Effect variant of needsPruning. */\nexport const needsPruning = (\n retentionDays: number = 90,\n): Effect.Effect<boolean, FsError> =>\n Effect.try({\n try: () => needsPruningSync(retentionDays),\n catch: (cause) => new FsError({ path: '<events>', operation: 'needsPruning', cause }),\n });\n\n/** Effect variant of getRetentionStatus. */\nexport const getRetentionStatus = (\n retentionDays: number = 90,\n): Effect.Effect<\n {\n totalEvents: number;\n oldestEventTs: string | null;\n oldestEventAge: number;\n needsPruning: boolean;\n eventsToRemove: number;\n },\n FsError\n> =>\n Effect.try({\n try: () => getRetentionStatusSync(retentionDays),\n catch: (cause) => new FsError({ path: '<events>', operation: 'getRetentionStatus', cause }),\n });\n","/**\n * WAL Import — scan project repos and import cost events into panopticon.db\n *\n * Called:\n * - On `pan up` (after startup) to pick up events written by other developers\n * - By `pan sync-costs` CLI command for on-demand sync\n */\n\nimport { existsSync } from 'fs';\nimport { readdir, readFile } from 'fs/promises';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport { listProjectsSync } from '../projects.js';\nimport { FsError } from '../errors.js';\nimport type { CostEvent } from './events.js';\nimport { CostDoorLive, CostWriter, type CostEvent as OverdeckCostEvent } from '../overdeck/cost.js';\nimport type { IssueId } from '../overdeck/issues.js';\n\nconst DEFAULT_EVENTS_SUBDIR = '.pan/events';\n\nexport interface SyncResult {\n /** Total events imported across all projects */\n imported: number;\n /** Total duplicates skipped (already in DB) */\n duplicates: number;\n /** Files processed */\n filesScanned: number;\n /** Per-project breakdown */\n byProject: Record<string, { imported: number; duplicates: number; files: number }>;\n /** Any errors encountered (non-fatal) */\n errors: string[];\n}async function syncWalFromAllProjectsPromise(): Promise<SyncResult> {\n const result: SyncResult = {\n imported: 0,\n duplicates: 0,\n filesScanned: 0,\n byProject: {},\n errors: [],\n };\n\n const projects = listProjectsSync();\n\n for (const { key, config } of projects) {\n const repoPath = config.events_repo ?? config.path;\n const eventsSubdir = config.events_path ?? DEFAULT_EVENTS_SUBDIR;\n const eventsDir = join(repoPath, eventsSubdir);\n\n if (!existsSync(eventsDir)) continue;\n\n const projectStats = { imported: 0, duplicates: 0, files: 0 };\n\n let files: string[];\n try {\n files = (await readdir(eventsDir)).filter(f => f.endsWith('.jsonl'));\n } catch (err) {\n result.errors.push(`${key}: failed to read events dir: ${err}`);\n continue;\n }\n\n for (const file of files) {\n const filePath = join(eventsDir, file);\n const events = await parseWalFile(filePath, result.errors);\n if (events.length === 0) continue;\n\n try {\n const { inserted, duplicates } = await recordCostEventsThroughOverdeck(events, filePath);\n projectStats.imported += inserted;\n projectStats.duplicates += duplicates;\n projectStats.files++;\n result.filesScanned++;\n } catch (err) {\n result.errors.push(`${key}/${file}: import failed: ${err}`);\n }\n }\n\n if (projectStats.files > 0 || projectStats.imported > 0) {\n result.byProject[key] = projectStats;\n result.imported += projectStats.imported;\n result.duplicates += projectStats.duplicates;\n }\n }\n\n return result;\n}async function syncWalFromDirPromise(eventsDir: string): Promise<{ imported: number; duplicates: number; files: number; errors: string[] }> {\n const stats = { imported: 0, duplicates: 0, files: 0, errors: [] as string[] };\n\n if (!existsSync(eventsDir)) return stats;\n\n let files: string[];\n try {\n files = (await readdir(eventsDir)).filter(f => f.endsWith('.jsonl'));\n } catch (err) {\n stats.errors.push(`Failed to read dir ${eventsDir}: ${err}`);\n return stats;\n }\n\n for (const file of files) {\n const filePath = join(eventsDir, file);\n const events = await parseWalFile(filePath, stats.errors);\n if (events.length === 0) continue;\n\n try {\n const { inserted, duplicates } = await recordCostEventsThroughOverdeck(events, filePath);\n stats.imported += inserted;\n stats.duplicates += duplicates;\n stats.files++;\n } catch (err) {\n stats.errors.push(`${file}: import failed: ${err}`);\n }\n }\n\n return stats;\n}\n\n// ============== Helpers ==============\n\nfunction toOverdeckCostEvent(event: CostEvent, sourceFile: string): OverdeckCostEvent {\n return {\n ts: new Date(event.ts),\n issueId: event.issueId ? (event.issueId as IssueId) : null,\n agentId: event.agentId ?? null,\n sessionId: event.sessionId ?? null,\n sessionType: event.sessionType ?? null,\n provider: event.provider ?? null,\n model: event.model ?? null,\n input: event.input ?? 0,\n output: event.output ?? 0,\n cacheRead: event.cacheRead ?? 0,\n cacheWrite: event.cacheWrite ?? 0,\n cost: event.cost ?? 0,\n requestId: event.requestId ?? null,\n sourceFile,\n };\n}\n\nasync function recordCostEventsThroughOverdeck(\n events: CostEvent[],\n sourceFile: string,\n): Promise<{ inserted: number; duplicates: number }> {\n let inserted = 0;\n let duplicates = 0;\n for (const event of events) {\n const didInsert = await Effect.runPromise(\n CostWriter.use((writer) => writer.record(toOverdeckCostEvent(event, sourceFile))).pipe(\n Effect.provide(CostDoorLive),\n ),\n );\n if (didInsert) inserted++;\n else duplicates++;\n }\n return { inserted, duplicates };\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\n/**\n * Effect variant of syncWalFromAllProjects. The underlying impl already\n * collects per-file errors in `result.errors` rather than throwing, so the\n * Effect channel only sees catastrophic failures (e.g. listProjects throwing).\n */\nexport const syncWalFromAllProjects = (): Effect.Effect<SyncResult, FsError> =>\n Effect.tryPromise({\n try: () => syncWalFromAllProjectsPromise(),\n catch: (cause) => new FsError({ path: '<all projects>', operation: 'syncWalFromAllProjects', cause }),\n });\n\n/** Effect variant of syncWalFromDir. */\nexport const syncWalFromDir = (\n eventsDir: string,\n): Effect.Effect<{ imported: number; duplicates: number; files: number; errors: string[] }, FsError> =>\n Effect.tryPromise({\n try: () => syncWalFromDirPromise(eventsDir),\n catch: (cause) => new FsError({ path: eventsDir, operation: 'syncWalFromDir', cause }),\n });\n\nasync function parseWalFile(filePath: string, errors: string[]): Promise<CostEvent[]> {\n let content: string;\n try {\n content = await readFile(filePath, 'utf-8');\n } catch (err) {\n errors.push(`Failed to read ${filePath}: ${err}`);\n return [];\n }\n\n const events: CostEvent[] = [];\n const lines = content.split('\\n').filter(l => l.trim());\n\n for (const line of lines) {\n try {\n const event = JSON.parse(line) as CostEvent;\n // Basic validation\n if (event.ts && event.agentId && event.issueId && event.model) {\n events.push(event);\n }\n } catch {\n // Skip malformed lines\n }\n }\n\n return events;\n}\n","/**\n * Cost Tracking Module - Event-Sourced Architecture\n *\n * Exports all public functions for cost tracking, aggregation, and migration.\n */\n\n// Event log management\nexport {\n appendCostEventSync,\n readEventsSync,\n tailEventsSync,\n readEventsFromLineSync,\n getLastEventMetadataSync,\n replaceEventsFileSync,\n deduplicateEventsSync,\n eventsFileExists,\n getEventsFilePath,\n // Effect variants (PAN-1249)\n appendCostEvent,\n readEvents,\n tailEvents,\n readEventsFromLine,\n getLastEventMetadata,\n replaceEventsFile,\n deduplicateEvents,\n type CostEvent,\n type EventMetadata,\n type ReadEventsOptions,\n} from './events.js';\n\n// Aggregation cache management\nexport {\n loadCacheSync,\n saveCacheSync,\n updateCacheFromEventsSync,\n rebuildCacheSync,\n syncCacheSync,\n getCostsByIssueSync,\n getCostsForIssueSync,\n setIssueBudgetSync,\n getCacheStatus,\n // Effect variants (PAN-1249)\n loadCache,\n saveCache,\n updateCacheFromEvents,\n rebuildCache,\n syncCache,\n getCostsByIssue,\n getCostsForIssue,\n setIssueBudget,\n type CostCache,\n type IssueStats,\n type ModelStats,\n type StageStats,\n} from './aggregator.js';\n\n// Historical data migration\nexport {\n migrateAllSessionsSync,\n needsMigrationSync,\n migrateIfNeededSync,\n // Effect variants (PAN-1249)\n migrateAllSessions,\n needsMigration,\n migrateIfNeeded,\n type MigrationStats,\n} from './migration.js';\n\n// Cost reconciler — periodic catch-up sweep\nexport {\n reconcile,\n type ReconcileResult,\n} from './reconciler.js';\n\n// Event retention\nexport {\n pruneOldEventsSync,\n needsPruningSync,\n getRetentionStatusSync,\n // Effect variants (PAN-1249)\n pruneOldEvents,\n needsPruning,\n getRetentionStatus,\n type RetentionStats,\n} from './retention.js';\n\n// WAL writers / sync (PAN-1249)\nexport {\n appendToWalSync,\n resolveWalDir,\n appendToWal,\n} from './wal.js';\nexport {\n syncWalFromAllProjects,\n syncWalFromDir,\n type SyncResult,\n} from './sync-wal.js';\n","import { getAllActiveQueues } from '../../../lib/overdeck/merge-sync.js';\n\nexport type MergeTriggerHandler = (issueId: string) => Promise<unknown>;\n\nlet mergeTriggerHandler: MergeTriggerHandler | null = null;\n\nexport function setMergeQueueTriggerHandler(handler: MergeTriggerHandler): void {\n mergeTriggerHandler = handler;\n}\n\nexport async function resumeQueuedMerges(): Promise<void> {\n if (!mergeTriggerHandler) {\n console.warn('[overdeck] Merge queue resume skipped: trigger handler not registered');\n return;\n }\n\n const queues = getAllActiveQueues();\n const resumableIssues = queues\n .filter(queue => !queue.current && queue.queue.length > 0)\n .map(queue => queue.queue[0]!)\n .filter((issueId): issueId is string => Boolean(issueId));\n\n for (const issueId of resumableIssues) {\n console.log(`[overdeck] Resuming queued merge for ${issueId}`);\n mergeTriggerHandler(issueId).catch((err: any) => {\n console.error(`[overdeck] Failed to resume queued merge for ${issueId}: ${err.message}`);\n });\n }\n}\n","export function buildLocalMainRecoveryError(\n projectPath: string,\n aheadCount: number,\n behindCount: number,\n): string {\n if (aheadCount > 0 && behindCount > 0) {\n return `Local main has diverged from origin/main: ${aheadCount} local commit(s) and ${behindCount} remote commit(s) are unique. `\n + `A previous approve attempt left unpushed work while origin/main also advanced. To recover, preserve it:\\n`\n + ` cd ${projectPath} && git fetch origin main && git merge origin/main\\n`\n + 'Resolve any conflicts, then push main. If the local commits should not land, revert them explicitly instead of resetting. '\n + 'Then unstick the workspace and retry.';\n }\n return `Local main is ${aheadCount} commit(s) ahead of origin/main — a previous approve attempt left unpushed work. `\n + `To recover, preserve it:\\n cd ${projectPath} && git push origin main\\n`\n + 'If those commits should not land, revert them explicitly instead of resetting. Then unstick the workspace and retry.';\n}\n","/**\n * Merge-ops route module — extracted from routes/workspaces.ts.\n *\n * Merge / approve / queue endpoints:\n * POST /api/issues/:issueId/sync-main\n * POST /api/issues/:issueId/merge\n * POST /api/issues/:issueId/forge-approve\n * POST /api/issues/:issueId/forge-merge\n * POST /api/issues/:issueId/approve\n * GET /api/merge-queue\n * POST /api/internal/pipeline/notify\n *\n * Shared singletons (pending operations, project path, workspace info, readJsonBody)\n * stay owned by ../workspaces.js and are imported here.\n */\nimport { exec, execFile } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { basename, dirname, join } from 'node:path';\nimport { promisify } from 'node:util';\nimport { Effect, Layer } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\nimport { messageAgent, getAgentState, spawnAgent } from '../../../../lib/agents.js';\nimport { queryBeadsForIssue, type BeadEntry } from '../../../../lib/beads-query.js';\nimport { syncMainIntoWorkspace } from '../../../../lib/cloister/merge-agent.js';\nimport { MainDivergedError, gitPush } from '../../../../lib/git/operations.js';\nimport { listGitOperationsSync } from '../../../../lib/git-activity.js';\nimport { extractNumberSync, extractPrefixSync, parseIssueIdSync } from '../../../../lib/issue-id.js';\nimport { enqueueMerge, getCurrentMerge, markMergeProcessing, dequeueMerge, getAllActiveQueues } from '../../../../lib/overdeck/merge.js';\nimport { findProjectByTeamSync } from '../../../../lib/projects.js';\nimport { getReviewStatusSync, markWorkspaceStuck, setReviewStatusSync as setReviewStatusBase, type ReviewStatus } from '../../../../lib/review-status.js';\nimport { isStatePlaneOnlyStatus } from '../../../../lib/state-plane.js';\nimport { getWorkAgentLifecycleStateSync } from '../../../../lib/work-agent-lifecycle.js';\nimport { findPlan } from '../../../../lib/vbrief/io.js';\nimport { isIntegrationPermissionError, verifyAppCanMerge } from '../../../../lib/github-app.js';\nimport { resolveGitHubIssueSync as resolveGitHubIssueShared } from '../../../../lib/tracker-utils.js';\nimport { sessionExists } from '../../../../lib/tmux.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { EventStoreService } from '../../services/domain-services.js';\nimport { setMergeQueueTriggerHandler } from '../../services/merge-queue-service.js';\nimport { httpHandler } from '../http-handler.js';\nimport { _serverManagedMerges } from '../specialists.js';\nimport { completePendingOperation, getPendingOperation, getProjectPath, getWorkspaceInfoForIssue, readJsonBody, setPendingOperation, setReviewStatus } from '../workspaces.js';\nimport { buildLocalMainRecoveryError } from './git-recovery-advice.js';\nconst execAsync = promisify(exec);\nconst execFileAsync = promisify(execFile);\nexport const shouldBlockApproveForDirtyStatus = (status: string): boolean =>\n status.trim() !== '' && !isStatePlaneOnlyStatus(status);\n\nasync function ensureWorkAgentReadyForMerge(\n issueId: string,\n workspacePath: string,\n rebaseMsg: string,\n): Promise<{ recovered: boolean; agentId: string; detail: string }> {\n const agentId = `agent-${issueId.toLowerCase()}`;\n const lifecycle = getWorkAgentLifecycleStateSync(agentId);\n\n if (lifecycle.hasLiveTmuxSession) {\n await messageAgent(agentId, rebaseMsg);\n return { recovered: true, agentId, detail: 'Work agent already running; sent merge preparation request.' };\n }\n\n const agentState = await Effect.runPromise(getAgentState(agentId));\n if (agentState) {\n try {\n await messageAgent(agentId, rebaseMsg);\n const updatedLifecycle = getWorkAgentLifecycleStateSync(agentId);\n return {\n recovered: true,\n agentId,\n detail: updatedLifecycle.canResumeSession\n ? 'Resumed work agent and sent merge preparation request.'\n : 'Restarted work agent and sent merge preparation request.',\n };\n } catch (err: any) {\n if (!lifecycle.canStartFresh) {\n throw err;\n }\n }\n }\n\n if (!lifecycle.canStartFresh) {\n throw new Error(lifecycle.reason || `Work agent ${agentId} cannot be resumed or started for merge preparation.`);\n }\n\n const state = await spawnAgent({\n issueId,\n workspace: workspacePath,\n role: 'work',\n prompt: rebaseMsg,\n });\n\n return {\n recovered: true,\n agentId,\n detail: `Started fresh work agent ${state.id} and sent merge preparation request.`,\n };\n}\n\n/**\n * Check whether origin/branchName already contains origin/targetBranch.\n * If true, no rebase is needed — the branch is already up to date with target.\n */\nexport async function isBranchAlreadyRebased(\n workspacePath: string,\n branchName: string,\n targetBranch: string,\n): Promise<{ alreadyRebased: boolean; currentHead?: string }> {\n try {\n await Promise.all([\n execFileAsync('git', ['fetch', 'origin', targetBranch], { cwd: workspacePath, encoding: 'utf-8', timeout: 15000 }),\n execFileAsync('git', ['fetch', 'origin', branchName], { cwd: workspacePath, encoding: 'utf-8', timeout: 15000 }),\n ]);\n await execFileAsync(\n 'git',\n ['merge-base', '--is-ancestor', `origin/${targetBranch}`, `origin/${branchName}`],\n { cwd: workspacePath, encoding: 'utf-8', timeout: 5000 }\n );\n const { stdout: currentHead } = await execFileAsync(\n 'git',\n ['rev-parse', `origin/${branchName}`],\n { cwd: workspacePath, encoding: 'utf-8', timeout: 5000 }\n );\n return { alreadyRebased: true, currentHead: currentHead.trim() };\n } catch {\n return { alreadyRebased: false };\n }\n}\n\n\nfunction parseGitHubPullRequestUrl(url?: string | null): { owner: string; repo: string; number: number } | null {\n if (!url) return null;\n const match = url.match(/github\\.com\\/([^/]+)\\/([^/]+)\\/pull\\/(\\d+)/);\n if (!match) return null;\n return {\n owner: match[1],\n repo: match[2],\n number: Number.parseInt(match[3], 10),\n };\n}\n\n// Exported for unit tests covering late-success merge reconciliation guards.\nexport async function reconcileGitHubMergeStatus(issueId: string, status: Pick<ReviewStatus, 'prUrl' | 'mergeStatus' | 'readyForMerge'> | null | undefined): Promise<boolean> {\n if (!status?.prUrl) return false;\n\n const prRef = parseGitHubPullRequestUrl(status.prUrl);\n if (!prRef) return false;\n\n try {\n const { getPullRequestState, isGitHubAppConfigured } = await import('../../../../lib/github-app.js');\n if (!isGitHubAppConfigured()) return false;\n\n const prState = await Effect.runPromise(getPullRequestState(prRef.owner, prRef.repo, prRef.number));\n console.log(`[merge] reconcileGitHubMergeStatus: ${issueId} PR #${prRef.number} merged=${prState.merged} state=${prState.state}`);\n if (!prState.merged) return false;\n\n setReviewStatus(issueId, {\n mergeStatus: 'merged',\n mergeNotes: undefined,\n readyForMerge: false,\n });\n completePendingOperation(issueId, null);\n return true;\n } catch (err: any) {\n console.warn(`[merge] Failed to reconcile PR state for ${issueId}: ${err.message}`);\n return false;\n }\n}\n\n\nasync function readBeadsFromJsonl(workspacePath: string, issueId: string): Promise<BeadEntry[]> {\n try {\n const jsonlPath = join(workspacePath, '.beads', 'issues.jsonl');\n if (!existsSync(jsonlPath)) return [];\n const raw = await readFile(jsonlPath, 'utf-8');\n const issueLower = issueId.toLowerCase();\n const beads: BeadEntry[] = [];\n for (const line of raw.split('\\n')) {\n if (!line.trim()) continue;\n try {\n const entry = JSON.parse(line);\n const labels = Array.isArray(entry.labels) ? entry.labels : [];\n if (labels.some((l: string) => l.toLowerCase() === issueLower)) {\n beads.push({\n id: String(entry.id ?? ''),\n title: String(entry.title ?? ''),\n status: String(entry.status ?? 'open'),\n labels: labels as string[],\n });\n }\n } catch { /* skip malformed lines */ }\n }\n return beads;\n } catch {\n return [];\n }\n}\n\n/**\n * Build a rich PR body with issue link, beads task summary, and AC checklist\n * from the vBRIEF plan. Exported for testing.\n */\nexport async function buildRichPRBody(issueId: string, workspacePath: string): Promise<string> {\n const lines: string[] = [];\n\n // Non-closing reference on purpose: a closing keyword (\"Closes #N\") hands\n // close authority to GitHub, which fires the moment the PR's head becomes\n // reachable from main and races the pipeline's verifying_on_main → close-out\n // lifecycle (the first UAT batch promote closed 2 of 3 member issues\n // mid-handoff, 2026-06-11). Overdeck's close-out owns issue closing.\n lines.push(`**Issue:** #${extractNumberSync(issueId) ?? issueId}`);\n lines.push('');\n\n // Acceptance criteria checklist from vBRIEF plan items\n try {\n const planPath = await Effect.runPromise(findPlan(workspacePath));\n if (planPath && existsSync(planPath)) {\n const raw = await readFile(planPath, 'utf-8');\n const doc = JSON.parse(raw);\n const items: Array<{ status: string; title: string }> = doc?.plan?.items ?? [];\n if (items.length > 0) {\n lines.push('## Acceptance Criteria');\n lines.push('');\n for (const item of items) {\n const checked = item.status === 'completed' ? 'x' : ' ';\n lines.push(`- [${checked}] ${item.title}`);\n }\n lines.push('');\n }\n }\n } catch {\n // No vBRIEF plan — omit checklist\n }\n\n // Beads task summary from live Dolt database via bd CLI\n try {\n // Use a short lock timeout so PR-body generation does not block behind CLI\n // processes that hold the cross-process bd lock. queryBeadsForIssue already\n // falls back to JSONL on failure, so the explicit fallback here is a safety net.\n const queryResult = await Effect.runPromise(\n queryBeadsForIssue(workspacePath, issueId, { acquisitionTimeoutMs: 500 }),\n );\n let beads = queryResult.beads;\n if (beads.length === 0) {\n // Fallback: read from .beads/issues.jsonl when bd CLI is unavailable\n beads = await readBeadsFromJsonl(workspacePath, issueId);\n }\n if (beads.length > 0) {\n lines.push('## Implementation Tasks');\n lines.push('');\n for (const bead of beads) {\n const checked = bead.status === 'closed' ? 'x' : ' ';\n lines.push(`- [${checked}] ${bead.title.replace(/^[^:]+:\\s*/, '')}`);\n }\n lines.push('');\n }\n } catch {\n // No beads — omit task list\n }\n\n return lines.join('\\n') || `Automated PR for ${issueId}`;\n}\n\nasync function ensurePRExists(\n issueId: string,\n options?: { cwd?: string; branchName?: string; targetBranch?: string }\n): Promise<{ created: boolean; prUrl?: string; error?: string }> {\n try {\n const issueLower = issueId.toLowerCase();\n const branchName = options?.branchName ?? `feature/${issueLower}`;\n const targetBranch = options?.targetBranch ?? 'main';\n const execOptions: Parameters<typeof execFileAsync>[2] = { encoding: 'utf-8' };\n if (options?.cwd) execOptions.cwd = options.cwd;\n\n // Check for existing PR\n let existingOut: string = '';\n try {\n const { stdout } = await execFileAsync('gh', ['pr', 'view', branchName, '--json', 'url', '--jq', '.url'], execOptions);\n existingOut = String(stdout);\n } catch { /* no existing PR */ }\n const existing = existingOut.trim();\n if (existing) return { created: false, prUrl: existing };\n\n // Build rich PR body if workspace path is available\n const prBody = options?.cwd ? await buildRichPRBody(issueId, options.cwd) : `Automated PR for ${issueId}`;\n\n // Write body to a temp file to avoid shell escaping issues\n const { tmpdir } = await import('os');\n const { join: pathJoin } = await import('path');\n const { writeFile: writeFileAsync, unlink: unlinkAsync } = await import('fs/promises');\n const bodyFile = pathJoin(tmpdir(), `pan-pr-body-${issueId}-${Date.now()}.md`);\n await writeFileAsync(bodyFile, prBody, 'utf-8');\n\n try {\n const { stdout: rawOut } = await execFileAsync('gh', ['pr', 'create', '--head', branchName, '--base', targetBranch, '--title', issueId, '--body-file', bodyFile], execOptions);\n const createOut = String(rawOut);\n // gh pr create prints the PR URL as the last line of stdout\n const prUrl = createOut.trim().split('\\n').pop()?.trim() || createOut.trim();\n return { created: true, prUrl };\n } finally {\n unlinkAsync(bodyFile).catch(() => {});\n }\n } catch (err: any) {\n return { created: false, error: err.message };\n }\n}\n\n\nexport async function pushApproveMain(\n issueId: string,\n projectPath: string,\n): Promise<ApprovePushResult> {\n try {\n await Effect.runPromise(gitPush(projectPath, 'origin', 'main', { issueId }));\n return { pushed: true };\n } catch (pushErr: unknown) {\n if (pushErr instanceof MainDivergedError) {\n // Mark the workspace stuck so Deacon skips it — no automatic retry.\n // Do NOT hard-reset local main here: that is a destructive operation that\n // must be explicit/user-confirmed, not a silent side-effect of a failed push.\n // The stuck flag prevents any further automatic approve attempts; when the\n // user manually unsticks and retries, the approve route's git pull --ff-only\n // step will detect the orphaned merge commit and surface a recoverable error\n // with instructions to preserve and reconcile the local commits.\n markWorkspaceStuck(issueId, 'main_diverged', {\n localSha: pushErr.localSha,\n remoteSha: pushErr.remoteSha,\n });\n const error = `Push aborted: origin/main has advanced past your local ancestor (remote: ${pushErr.remoteSha?.slice(0, 7)}, local: ${pushErr.localSha?.slice(0, 7)}). A hotfix may have landed. Workspace marked stuck — to recover, preserve local commits: cd ${projectPath} && git fetch origin main && git merge origin/main, resolve any conflicts, push main, then unstick and retry.`;\n return { pushed: false, httpStatus: 409, error };\n }\n const message = pushErr instanceof Error ? pushErr.message : String(pushErr);\n const error = `Merge succeeded but push failed! Your work is safe locally.\\nPlease push manually: cd ${projectPath} && git push origin main\\nError: ${message}`;\n return { pushed: false, httpStatus: 400, error };\n }\n}\n\n// ─── Route: POST /api/issues/:issueId/sync-main ──────────────────────────\n\nconst postWorkspaceSyncMainRoute = HttpRouter.add(\n 'POST',\n '/api/issues/:issueId/sync-main',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const issueLower = issueId.toLowerCase();\n\n const workspaceInfo = getWorkspaceInfoForIssue(issueId);\n if (workspaceInfo.isRemote) {\n return jsonResponse(\n {\n success: false,\n error: 'Sync with Main is not supported for remote workspaces',\n },\n { status: 400 }\n );\n }\n\n const workspacePath =\n workspaceInfo.localPath ||\n join(projectPath, 'workspaces', `feature-${issueLower}`);\n\n if (!existsSync(workspacePath)) {\n return jsonResponse(\n { success: false, error: 'Workspace does not exist' },\n { status: 400 }\n );\n }\n\n console.log(`[sync-main] Starting sync for ${issueId} at ${workspacePath}`);\n\n const result = yield* Effect.promise(() => syncMainIntoWorkspace(workspacePath, issueId));\n\n if (result.success) {\n if (result.alreadyUpToDate) {\n return jsonResponse({\n success: true,\n alreadyUpToDate: true,\n message: 'Already up to date with main',\n });\n }\n return jsonResponse({\n success: true,\n commitCount: result.commitCount || 0,\n changedFiles: result.changedFiles || [],\n message: `Synced ${result.commitCount || 0} commit(s) from main`,\n });\n } else {\n const status = result.reason?.includes('uncommitted') ? 400 : 500;\n return jsonResponse(\n {\n success: false,\n error: result.reason || 'Sync failed',\n conflictFiles: result.conflictFiles,\n },\n { status }\n );\n }\n }))\n);\n\n// ─── Shared triggerMerge logic ────────────────────────────────────────────────\n\nexport interface TriggerMergeResult {\n success: boolean;\n statusCode: number;\n error?: string;\n message?: string;\n reviewStatus?: string;\n testStatus?: string;\n mergeStatus?: string;\n prUrl?: string;\n remote?: boolean;\n repos?: Array<{ repo: string; success: boolean; message: string; testsStatus?: string }>;\n testsStatus?: string;\n note?: string;\n mergeResult?: unknown;\n}\n\n// Per-project merge queue backed by SQLite (PAN-632).\n// Replaces the in-memory _mergeQueues Map — survives server restarts.\n\n/** Dequeue the next merge after current completes (success or failure). */\nfunction dequeueNextMerge(projectKey: string, completedIssueId?: string): void {\n const nextIssueId = dequeueMerge(projectKey, completedIssueId);\n if (nextIssueId) {\n console.log(`[merge] Dequeuing next merge: ${nextIssueId}`);\n triggerMerge(nextIssueId).catch(err =>\n console.error(`[merge] Queue error for ${nextIssueId}: ${err}`)\n );\n }\n}\n\nexport async function triggerMerge(issueId: string): Promise<TriggerMergeResult> {\n const reviewStatus = getReviewStatusSync(issueId);\n if (!reviewStatus?.readyForMerge) {\n return {\n success: false,\n statusCode: 400,\n error: 'Cannot merge: review and tests have not passed yet',\n reviewStatus: reviewStatus?.reviewStatus || 'pending',\n testStatus: reviewStatus?.testStatus || 'pending',\n };\n }\n\n // NOTE: Commit status reporting moved to AFTER rebase — see below.\n // The rebase changes the HEAD SHA, so statuses must be reported on the new commit.\n if (false && reviewStatus?.prUrl) {\n try {\n const { isGitHubAppConfigured, reportCommitStatus } = await import('../../../../lib/github-app.js');\n if (isGitHubAppConfigured()) {\n const prMatch = reviewStatus!.prUrl!.match(/\\/pull\\/(\\d+)/);\n if (prMatch) {\n const { stdout } = await execAsync(\n `gh pr view ${prMatch![1]} --json headRefOid --jq .headRefOid`,\n { encoding: 'utf-8', timeout: 10000 }\n );\n const sha = stdout.trim();\n if (sha) {\n await reportCommitStatus('eltmon', 'overdeck', sha, 'success', 'overdeck/review', 'Review passed');\n await reportCommitStatus('eltmon', 'overdeck', sha, 'success', 'overdeck/test', 'Tests passed');\n console.log(`[merge] Reported commit statuses for ${issueId} (${sha.slice(0, 8)})`);\n }\n }\n }\n } catch (err: any) {\n console.warn(`[merge] Failed to report commit statuses: ${err.message}`);\n }\n }\n\n if (reviewStatus?.mergeStatus === 'merging') {\n const pendingOp = getPendingOperation(issueId);\n const activelyMerging = pendingOp?.type === 'merge' && pendingOp?.status === 'running';\n if (activelyMerging) {\n return {\n success: false,\n statusCode: 400,\n error: 'Merge already in progress',\n mergeStatus: 'merging',\n };\n }\n console.log(\n `[merge] Clearing stuck mergeStatus for ${issueId} (pending op: ${pendingOp?.status ?? 'absent'})`\n );\n setReviewStatus(issueId, { mergeStatus: undefined });\n }\n\n if (reviewStatus?.mergeStatus === 'merged') {\n return { success: false, statusCode: 400, error: 'Already merged', mergeStatus: 'merged' };\n }\n\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const issueLower = issueId.toLowerCase();\n\n // Serialize merges per project via persistent SQLite queue (PAN-632).\n // Survives server restarts — no more lost queues.\n const projectKey = issuePrefix.toLowerCase();\n const normalizedId = issueId.toUpperCase();\n const currentlyMerging = getCurrentMerge(projectKey);\n if (currentlyMerging && currentlyMerging !== normalizedId) {\n // Another merge is in progress — queue this one\n const position = enqueueMerge(projectKey, normalizedId);\n setReviewStatus(issueId, { mergeStatus: 'queued', mergeStep: 'queued' });\n console.log(`[merge] Queued ${issueId} (position ${position}, waiting for ${currentlyMerging})`);\n return {\n success: true,\n statusCode: 200,\n message: `Queued for merge (position ${position}, waiting for ${currentlyMerging})`,\n mergeStatus: 'queued',\n };\n }\n // Mark as processing IMMEDIATELY — before any async work — to prevent race conditions.\n // SQLite write is atomic — no window for concurrent calls to both pass the check.\n enqueueMerge(projectKey, normalizedId);\n markMergeProcessing(projectKey, normalizedId);\n\n const workspaceInfo = getWorkspaceInfoForIssue(issueId);\n\n // Use the actual resolved workspace path (handles legacy feature-484 naming)\n const workspacePath = (!workspaceInfo.isRemote && workspaceInfo.localPath)\n ? workspaceInfo.localPath\n : join(projectPath, 'workspaces', `feature-${issueLower}`);\n const workspaceDirName = basename(workspacePath);\n const branchName = workspaceDirName.startsWith('feature-')\n ? `feature/${workspaceDirName.slice('feature-'.length)}`\n : `feature/${issueLower}`;\n\n setReviewStatus(issueId, { mergeStatus: 'merging', mergeStep: 'validating-pr' });\n\n const normalizedMergeId = issueId.toUpperCase();\n _serverManagedMerges.add(normalizedMergeId);\n setPendingOperation(issueId, 'merge');\n let queueAdvanced = false;\n\n const advanceQueue = (): void => {\n if (queueAdvanced) return;\n queueAdvanced = true;\n _serverManagedMerges.delete(normalizedMergeId);\n dequeueNextMerge(projectKey, normalizedId);\n };\n\n try {\n if (workspaceInfo.isRemote && workspaceInfo.vmName) {\n console.log(\n `[merge] Remote workspace detected for ${issueId}, using review artifact merge...`\n );\n const { getMergeSetSync, ensureMergeSetForIssueSync } = await import('../../../../lib/merge-set.js');\n const { getForgeAdapter } = await import('../../../../lib/forge.js');\n const remoteMergeSet = getMergeSetSync(issueId) || ensureMergeSetForIssueSync(issueId);\n const remotePrimaryRepo = remoteMergeSet?.repos[0];\n const remoteTargetBranch = remotePrimaryRepo?.targetBranch || 'main';\n const remoteForge = remotePrimaryRepo?.forge || 'github';\n\n const prResult = await ensurePRExists(issueId, { targetBranch: remoteTargetBranch });\n if (!prResult.prUrl) {\n const error = `Failed to create PR: ${prResult.error || 'Unknown error'}`;\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: error });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 400, error };\n }\n const artifactUrl = remotePrimaryRepo?.artifactUrl || prResult.prUrl;\n const artifactId = remotePrimaryRepo?.artifactId;\n\n try {\n console.log(`[merge] Merging ${remoteForge} review artifact for ${issueId}...`);\n await getForgeAdapter(remoteForge).mergeReviewArtifact({\n forge: remoteForge,\n url: artifactUrl,\n id: artifactId,\n method: 'squash',\n });\n\n setReviewStatus(issueId, { mergeStatus: 'merged', mergeNotes: undefined, readyForMerge: false });\n completePendingOperation(issueId, null);\n\n const { postMergeLifecycle } = await import('../../../../lib/cloister/merge-agent.js');\n await postMergeLifecycle(issueId, projectPath);\n\n const remotePrNumber = prResult.prUrl.match(/\\/pull\\/(\\d+)/)?.[1] ?? '?';\n return {\n success: true,\n statusCode: 200,\n message: `Successfully merged PR #${remotePrNumber} for ${issueId}`,\n mergeStatus: 'merged',\n prUrl: prResult.prUrl,\n remote: true,\n };\n } catch (remoteErr: any) {\n const mergeErrorMessage = `Remote merge failed: ${remoteErr.message}`;\n console.error(`[merge] Remote merge failed for ${issueId}:`, remoteErr);\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: mergeErrorMessage });\n completePendingOperation(issueId, remoteErr.message);\n return {\n success: false,\n statusCode: 500,\n error: mergeErrorMessage,\n };\n }\n }\n\n if (!existsSync(workspacePath)) {\n completePendingOperation(issueId, 'Workspace does not exist');\n return { success: false, statusCode: 400, error: 'Workspace does not exist' };\n }\n\n const projectConfig = findProjectByTeamSync(issuePrefix);\n const isPolyrepo = projectConfig?.workspace?.type === 'polyrepo';\n\n if (isPolyrepo && projectConfig?.workspace?.repos) {\n console.log(`[merge] Polyrepo detected for ${issueId}, coordinating merge set...`);\n const { getMergeSetSync, ensureMergeSetForIssueSync, upsertMergeSetSync, withRepoStateSync } = await import('../../../../lib/merge-set.js');\n const { runQualityGates } = await import('../../../../lib/cloister/validation.js');\n const { getForgeAdapter } = await import('../../../../lib/forge.js');\n const { messageAgent } = await import('../../../../lib/agents.js');\n let mergeSet = getMergeSetSync(issueId) || ensureMergeSetForIssueSync(issueId);\n if (!mergeSet) {\n const error = `No merge set found for ${issueId}`;\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: error });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 400, error };\n }\n\n const activeRepos = mergeSet.repos\n .filter(repo => repo.mergeStatus !== 'skipped' && !!repo.artifactUrl)\n .sort((a, b) => a.mergeOrder - b.mergeOrder);\n\n if (activeRepos.length === 0) {\n const error = `No changed repos are marked ready for coordinated merge in ${issueId}`;\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: error });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 400, error };\n }\n\n const agentId = `agent-${issueId.toLowerCase()}`;\n\n mergeSet = {\n ...mergeSet,\n status: 'merging',\n updatedAt: new Date().toISOString(),\n };\n upsertMergeSetSync(mergeSet);\n\n const mergeResults: Array<{\n repo: string;\n success: boolean;\n message: string;\n testsStatus?: string;\n }> = [];\n const repoHeadsBefore = new Map<string, string>();\n // PAN-2461: repos whose source branch already contains the target branch need no\n // rebase round-trip — a retry after the work agent already rebased+pushed must not\n // wait 30 minutes for a push that will never come (mirrors the monorepo\n // \"already contains origin/main\" skip).\n const alreadyRebased = new Set<string>();\n\n for (const repo of activeRepos) {\n const repoWorkspacePath = join(workspacePath, repo.repoKey);\n if (!existsSync(repoWorkspacePath) || !existsSync(join(repoWorkspacePath, '.git'))) {\n const error = `Workspace repo ${repo.repoKey} is missing`;\n mergeResults.push({ repo: repo.repoKey, success: false, message: error });\n continue;\n }\n\n const { stdout: headBefore } = await execAsync(\n `git rev-parse origin/${repo.sourceBranch} 2>/dev/null || echo NONE`,\n { cwd: repoWorkspacePath, encoding: 'utf-8', timeout: 10000 }\n );\n repoHeadsBefore.set(repo.repoKey, headBefore.trim());\n try {\n await execAsync(`git fetch origin ${repo.targetBranch} ${repo.sourceBranch}`, {\n cwd: repoWorkspacePath, encoding: 'utf-8', timeout: 30000,\n });\n await execAsync(\n `git merge-base --is-ancestor origin/${repo.targetBranch} origin/${repo.sourceBranch}`,\n { cwd: repoWorkspacePath, encoding: 'utf-8', timeout: 10000 }\n );\n alreadyRebased.add(repo.repoKey);\n mergeSet = withRepoStateSync(mergeSet, repo.repoKey, { rebaseStatus: 'passed' });\n console.log(`[merge] ${repo.repoKey} feature branch already contains origin/${repo.targetBranch} — skipping rebase request for ${issueId}`);\n continue;\n } catch {\n // Not an ancestor (or fetch failed) — a rebase is genuinely required.\n }\n mergeSet = withRepoStateSync(mergeSet, repo.repoKey, { rebaseStatus: 'requested' });\n }\n upsertMergeSetSync(mergeSet);\n\n if (mergeResults.some(result => !result.success)) {\n const failedDetails = mergeResults.filter(r => !r.success).map(r => `${r.repo}: ${r.message}`).join('; ');\n const error = `Polyrepo merge prerequisites failed for ${issueId}: ${failedDetails}`;\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: error });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 400, error, repos: mergeResults };\n }\n\n const reposNeedingRebase = activeRepos.filter(repo => !alreadyRebased.has(repo.repoKey));\n\n if (reposNeedingRebase.length > 0 && !await Effect.runPromise(sessionExists(agentId))) {\n const error = `Work agent ${agentId} is not running. Polyrepo merge requires the work agent to rebase every affected repo and push.`;\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: error });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 400, error };\n }\n\n if (reposNeedingRebase.length > 0) {\n const rebaseInstructions = reposNeedingRebase.map((repo, index) => (\n `${index + 1}. cd ${repo.repoKey}\\n git fetch origin ${repo.targetBranch}\\n git rebase origin/${repo.targetBranch}\\n git push --force-with-lease`\n )).join('\\n');\n const rebaseMsg = `MERGE REQUESTED: The human has clicked MERGE for ${issueId}. Rebase and push every affected repo in this merge set:\\n\\n${rebaseInstructions}\\n\\nResolve any conflicts in the workspaces above, complete every rebase, and push all affected branches. Do NOT merge PRs/MRs yourself.`;\n await messageAgent(agentId, rebaseMsg);\n }\n\n const REBASE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes — complex polyrepo rebases need time for conflict resolution\n const POLL_INTERVAL_MS = 5000;\n const pushedRepos = new Set<string>(alreadyRebased);\n const rebaseStart = Date.now();\n\n while (Date.now() - rebaseStart < REBASE_TIMEOUT_MS && pushedRepos.size < activeRepos.length) {\n await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL_MS));\n\n for (const repo of activeRepos) {\n if (pushedRepos.has(repo.repoKey)) continue;\n\n const repoWorkspacePath = join(workspacePath, repo.repoKey);\n try {\n await execAsync('git fetch origin', { cwd: repoWorkspacePath, encoding: 'utf-8', timeout: 15000 });\n const { stdout: headNow } = await execAsync(\n `git rev-parse origin/${repo.sourceBranch}`,\n { cwd: repoWorkspacePath, encoding: 'utf-8', timeout: 5000 }\n );\n if (headNow.trim() !== repoHeadsBefore.get(repo.repoKey)) {\n pushedRepos.add(repo.repoKey);\n mergeSet = withRepoStateSync(mergeSet, repo.repoKey, { rebaseStatus: 'passed' });\n upsertMergeSetSync(mergeSet);\n }\n } catch {\n // Retry until timeout or agent exit.\n }\n }\n\n if (!await Effect.runPromise(sessionExists(agentId))) break;\n }\n\n if (pushedRepos.size !== activeRepos.length) {\n const remaining = activeRepos\n .filter(repo => !pushedRepos.has(repo.repoKey))\n .map(repo => repo.repoKey);\n const agentRunning = await Effect.runPromise(sessionExists(agentId));\n const error = !agentRunning\n ? `Work agent ${agentId} stopped before completing polyrepo rebases for ${remaining.join(', ')}`\n : `Work agent did not push rebased branches for ${remaining.join(', ')} within ${REBASE_TIMEOUT_MS / 60000} minutes`;\n for (const repoKey of remaining) {\n mergeSet = withRepoStateSync(mergeSet, repoKey, { rebaseStatus: 'failed' });\n }\n upsertMergeSetSync(mergeSet);\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: error });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 500, error };\n }\n\n setReviewStatus(issueId, { mergeStatus: 'verifying', mergeNotes: undefined });\n // PAN-2461: gates must run with rendered placeholders and the workspace ROOT.\n // The previous call passed the repo subdir (doubling gate.path → <ws>/fe/fe)\n // and no placeholders (docker exec hit the literal \"myn-{{FEATURE_FOLDER}}-fe-1\").\n const featureFolder = basename(workspacePath);\n const gatePlaceholders = {\n FEATURE_NAME: featureFolder.replace(/^feature-/, ''),\n FEATURE_FOLDER: featureFolder,\n BRANCH_NAME: `feature/${featureFolder.replace(/^feature-/, '')}`,\n COMPOSE_PROJECT: `${basename(projectPath)}-${featureFolder}`,\n DOMAIN: projectConfig?.workspace?.dns?.domain || 'localhost',\n PROJECT_NAME: basename(projectPath),\n PROJECT_PATH: projectPath,\n PROJECTS_DIR: dirname(projectPath),\n WORKSPACE_PATH: workspacePath,\n };\n\n for (const repo of activeRepos) {\n const repoConfig = projectConfig.workspace.repos.find(configRepo => configRepo.name === repo.repoKey);\n const gateIdentifiers = new Set<string>([\n repo.repoKey,\n repoConfig?.path || '',\n ].filter(Boolean));\n const gates = Object.fromEntries(\n Object.entries(projectConfig.quality_gates || {}).filter(\n ([, gate]) => gate.path && gateIdentifiers.has(gate.path)\n )\n );\n\n mergeSet = withRepoStateSync(mergeSet, repo.repoKey, { verificationStatus: 'running' });\n upsertMergeSetSync(mergeSet);\n\n if (Object.keys(gates).length === 0) {\n mergeSet = withRepoStateSync(mergeSet, repo.repoKey, { verificationStatus: 'skipped' });\n upsertMergeSetSync(mergeSet);\n continue;\n }\n\n const gateResults = await Effect.runPromise(runQualityGates(gates, workspacePath, 'pre_push', {\n placeholders: { ...gatePlaceholders, CHANGED_BASE: `origin/${repo.targetBranch}` },\n }));\n const failedGate = gateResults.find(result => !result.passed && result.required !== false);\n if (failedGate) {\n const error = `Polyrepo post-rebase verification failed for ${repo.repoKey} at ${failedGate.name}`;\n mergeSet = withRepoStateSync(mergeSet, repo.repoKey, { verificationStatus: 'failed' });\n upsertMergeSetSync(mergeSet);\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: error });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 500, error };\n }\n\n mergeSet = withRepoStateSync(mergeSet, repo.repoKey, { verificationStatus: 'passed' });\n upsertMergeSetSync(mergeSet);\n }\n\n setReviewStatus(issueId, { mergeStatus: 'merging' });\n for (const repo of activeRepos) {\n const repoWorkspacePath = join(workspacePath, repo.repoKey);\n try {\n mergeSet = withRepoStateSync(mergeSet, repo.repoKey, { mergeStatus: 'merging' });\n upsertMergeSetSync(mergeSet);\n await getForgeAdapter(repo.forge).mergeReviewArtifact({\n forge: repo.forge,\n url: repo.artifactUrl,\n id: repo.artifactId,\n cwd: repoWorkspacePath,\n method: 'squash',\n });\n mergeSet = withRepoStateSync(mergeSet, repo.repoKey, { mergeStatus: 'merged' });\n upsertMergeSetSync(mergeSet);\n mergeResults.push({\n repo: repo.repoKey,\n success: true,\n message: `Merged via ${repo.forge}`,\n });\n } catch (mergeErr: any) {\n const error = mergeErr.message || 'Artifact merge failed';\n mergeSet = withRepoStateSync(mergeSet, repo.repoKey, { mergeStatus: 'failed' });\n upsertMergeSetSync(mergeSet);\n mergeResults.push({ repo: repo.repoKey, success: false, message: error });\n break;\n }\n }\n\n const failedRepos = mergeResults.filter(r => !r.success);\n\n if (failedRepos.length > 0) {\n const error = `Polyrepo merge failed for: ${failedRepos\n .map(r => `${r.repo} (${r.message})`)\n .join(', ')}`;\n mergeSet = {\n ...mergeSet,\n status: 'failed',\n updatedAt: new Date().toISOString(),\n };\n upsertMergeSetSync(mergeSet);\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: error });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 500, error, repos: mergeResults };\n }\n\n mergeSet = {\n ...mergeSet,\n status: 'merged',\n updatedAt: new Date().toISOString(),\n };\n upsertMergeSetSync(mergeSet);\n setReviewStatus(issueId, { mergeStatus: 'merged', mergeNotes: undefined, readyForMerge: false });\n completePendingOperation(issueId, null);\n\n const { postMergeLifecycle } = await import('../../../../lib/cloister/merge-agent.js');\n advanceQueue();\n await postMergeLifecycle(issueId, projectPath);\n\n return {\n success: true,\n statusCode: 200,\n message: `Polyrepo merge complete for ${issueId}`,\n mergeStatus: 'merged',\n repos: mergeResults,\n };\n }\n\n // Monorepo / single-repo merge: PR-based flow\n const { getMergeSetSync, ensureMergeSetForIssueSync } = await import('../../../../lib/merge-set.js');\n const { getForgeAdapter } = await import('../../../../lib/forge.js');\n const monorepoMergeSet = getMergeSetSync(issueId) || ensureMergeSetForIssueSync(issueId);\n const primaryRepo = monorepoMergeSet?.repos[0];\n const targetBranch = primaryRepo?.targetBranch || 'main';\n const primaryForge = primaryRepo?.forge || 'github';\n\n // Step 1: Ensure PR exists (creates if needed)\n const prResult = await ensurePRExists(issueId, { cwd: workspacePath, branchName, targetBranch });\n if (!prResult.prUrl) {\n const error = `Failed to create PR: ${prResult.error || 'Unknown error'}`;\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: error });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 400, error };\n }\n\n const artifactUrl = primaryRepo?.artifactUrl || prResult.prUrl;\n const artifactId = primaryRepo?.artifactId;\n const githubPrRef = primaryForge === 'github' ? parseGitHubPullRequestUrl(artifactUrl) : null;\n const prNumber = githubPrRef ? String(githubPrRef.number) : undefined;\n if (primaryForge === 'github' && !prNumber) {\n const error = `Could not parse PR number from URL: ${artifactUrl}`;\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: error });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 400, error };\n }\n\n // Step 1b: Validate that the PR is still OPEN before rebasing/merging.\n // A cancel-flow or manual `gh pr close` can leave stale `prUrl` pointing at a\n // CLOSED PR while Overdeck state still shows readyForMerge=true. Without this\n // check, the rebase + merge pipeline runs against a dead PR and dies silently\n // inside `gh pr merge` (see PAN-509 cancel-flow divergence).\n if (githubPrRef) {\n try {\n const { getPullRequestState, isGitHubAppConfigured } = await import('../../../../lib/github-app.js');\n if (isGitHubAppConfigured()) {\n const prState = await Effect.runPromise(getPullRequestState(githubPrRef.owner, githubPrRef.repo, githubPrRef.number));\n if (prState.state !== 'OPEN' && !prState.merged) {\n const error = `PR #${githubPrRef.number} is ${prState.state} (not OPEN). Overdeck state is out of sync — likely a cancel-flow left a stale prUrl. Re-open the work agent to create a fresh PR, or reset review state.`;\n console.error(`[merge] ${error}`);\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: error });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 409, error };\n }\n // Defense-in-depth: refuse to merge when required CI checks are failing on the\n // PR's current HEAD. Without this gate, we attempt a rebase and `gh pr merge`\n // against a branch whose CI is red; branch protection blocks the merge and we\n // get a generic error. Surface the real blocker (failing CI) up-front so the\n // work-agent can fix it instead of us churning the queue. See PAN-611/PAN-544\n // (Run 7): feature branches had gitignored source + stale bun.lock; local\n // verification passed but CI failed — the divergence was invisible until merge.\n if (prState.checksFailed && !prState.merged) {\n const error = `GitHub PR #${githubPrRef.number} has failing required checks on HEAD ${prState.headSha.slice(0, 8)}. Fix CI before merging — see ${prState.url || 'the PR page'} for details.`;\n console.error(`[merge] ${error}`);\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: error });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 409, error };\n }\n // Defense-in-depth: refuse to merge when the PR is CONFLICTING with its base.\n // Without this, a clean-CI-but-conflicting PR sails into the rebase/retry loop in\n // forge.ts and churns for minutes before a generic timeout. The mergeable/\n // mergeableState fields are already on prState from getPullRequestState. Writing a\n // merge_conflict blocker back into review status drops the row out of the\n // \"Awaiting Merge\" queue and into \"Blocked from Merge\" with a clear reason even if\n // the GitHub webhook that normally populates blockerReasons was delayed or dropped\n // (the reactive-only gap that let PAN-1574 show a live MERGE button). PAN-1619-followup.\n if ((prState.mergeable === false || prState.mergeableState === 'dirty') && !prState.merged) {\n const error = `GitHub PR #${githubPrRef.number} is CONFLICTING with ${prState.baseBranch}. Resolve conflicts before merging — see ${prState.url || 'the PR page'}.`;\n console.error(`[merge] ${error}`);\n setReviewStatus(issueId, {\n mergeStatus: 'failed',\n readyForMerge: false,\n mergeNotes: error,\n blockerReasons: [{\n type: 'merge_conflict',\n summary: `Conflicts with ${prState.baseBranch}`,\n detectedAt: new Date().toISOString(),\n }],\n });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 409, error };\n }\n if (prState.merged) {\n console.log(`[merge] PR #${githubPrRef.number} for ${issueId} is already merged — running post-merge lifecycle`);\n setReviewStatus(issueId, { mergeStatus: 'merged', mergeNotes: undefined, readyForMerge: false });\n completePendingOperation(issueId, null);\n const { postMergeLifecycle } = await import('../../../../lib/cloister/merge-agent.js');\n await postMergeLifecycle(issueId, projectPath, branchName);\n return {\n success: true,\n statusCode: 200,\n message: `PR #${githubPrRef.number} for ${issueId} was already merged`,\n mergeStatus: 'merged',\n prUrl: prResult.prUrl,\n };\n }\n }\n } catch (prStateErr: any) {\n console.warn(`[merge] Pre-merge PR state check failed for ${issueId}: ${prStateErr.message} — proceeding (check is best-effort)`);\n }\n }\n\n // Step 2: Tell the WORK AGENT to rebase onto the target branch and push.\n // The server coordinates; the work agent owns all code-changing git operations.\n const { postMergeLifecycle } = await import(\n '../../../../lib/cloister/merge-agent.js'\n );\n const agentId = `agent-${issueId.toLowerCase()}`;\n const rebaseMsg = `MERGE REQUESTED: The human has clicked MERGE for ${issueId}. Please rebase onto ${targetBranch} and push:\\n\\n1. git fetch origin ${targetBranch}\\n2. git rebase origin/${targetBranch}\\n3. If conflicts: resolve them, git add, git rebase --continue\\n4. git push --force-with-lease\\n\\nAfter pushing, the server will handle verification and merge automatically. Do NOT run gh pr merge yourself.`;\n\n setReviewStatus(issueId, { mergeStep: 'rebasing' });\n console.log(`[merge] Rebasing ${branchName} onto ${targetBranch} for ${issueId} (agent=${await Effect.runPromise(sessionExists(agentId)) ? 'running' : 'stopped'})...`);\n {\n const { beginShipLog, appendShipLog } = await import('../../../../lib/cloister/ship-log.js');\n beginShipLog(issueId);\n appendShipLog(issueId, `Ship started: rebasing ${branchName} onto ${targetBranch}…`, 'rebasing');\n }\n\n let rebaseResult: { success: boolean; reason?: string; conflictFiles?: string[]; newHead?: string };\n\n // Pre-check: if origin/<branch> already contains origin/<target>, the branch\n // is already rebased — no rebase or push is needed.\n const { alreadyRebased, currentHead } = await isBranchAlreadyRebased(workspacePath, branchName, targetBranch);\n\n if (alreadyRebased && currentHead) {\n console.log(`[merge] ${branchName} already contains origin/${targetBranch} — skipping rebase request for ${issueId}`);\n rebaseResult = { success: true, newHead: currentHead };\n } else {\n try {\n const recovery = await ensureWorkAgentReadyForMerge(issueId, workspacePath, rebaseMsg);\n console.log(`[merge] ${recovery.detail}`);\n\n // Poll for the push: check if remote HEAD changed\n const { stdout: headBefore } = await execAsync(\n `git rev-parse origin/${branchName} 2>/dev/null || echo NONE`,\n { cwd: workspacePath, encoding: 'utf-8', timeout: 10000 }\n );\n\n const REBASE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes — complex rebases with conflicts need time\n const POLL_INTERVAL_MS = 5000;\n const startTime = Date.now();\n let newHead: string | null = null;\n\n while (Date.now() - startTime < REBASE_TIMEOUT_MS) {\n await new Promise(r => setTimeout(r, POLL_INTERVAL_MS));\n\n try {\n await execAsync('git fetch origin', { cwd: workspacePath, encoding: 'utf-8', timeout: 15000 });\n const { stdout: headNow } = await execAsync(\n `git rev-parse origin/${branchName}`,\n { cwd: workspacePath, encoding: 'utf-8', timeout: 5000 }\n );\n if (headNow.trim() !== headBefore.trim()) {\n newHead = headNow.trim();\n console.log(`[merge] Work agent pushed rebased branch for ${issueId} (new HEAD: ${newHead.slice(0, 8)})`);\n break;\n }\n } catch { /* fetch failed, retry */ }\n\n if (!await Effect.runPromise(sessionExists(agentId))) {\n console.log(`[merge] Work agent ${agentId} stopped during rebase`);\n break;\n }\n }\n\n if (newHead) {\n rebaseResult = { success: true, newHead };\n } else if (!await Effect.runPromise(sessionExists(agentId))) {\n rebaseResult = {\n success: false,\n reason: `Work agent ${agentId} stopped before completing the rebase onto ${targetBranch}`,\n };\n } else {\n rebaseResult = { success: false, reason: `Work agent did not push the rebased branch within ${REBASE_TIMEOUT_MS / 60000} minutes` };\n }\n } catch (recoveryErr: any) {\n rebaseResult = {\n success: false,\n reason: recoveryErr.message || `Work agent ${agentId} could not be prepared for merge`,\n };\n }\n }\n\n if (!rebaseResult.success) {\n const error = rebaseResult.reason || 'Rebase failed';\n setReviewStatus(issueId, { mergeStatus: 'failed', mergeNotes: error, readyForMerge: false });\n completePendingOperation(issueId, error);\n\n // Post PR comment about failure\n try {\n if (artifactUrl) {\n const body = rebaseResult.conflictFiles?.length\n ? `## Merge Failed — Rebase Conflicts\\n\\nConflicts in: ${rebaseResult.conflictFiles.join(', ')}\\n\\nThe work agent has been notified to resolve conflicts.`\n : `## Merge Failed\\n\\n${error}`;\n await getForgeAdapter(primaryForge).commentOnArtifact({\n forge: primaryForge,\n url: artifactUrl,\n id: artifactId,\n cwd: workspacePath,\n body,\n });\n }\n } catch { /* non-fatal */ }\n\n return { success: false, statusCode: 500, error };\n }\n\n setReviewStatus(issueId, { mergeStep: 'stripping-planning' });\n // Strip .planning/ artifacts before merge — these are workspace-local\n // scratch files (STATE.md, feedback/) that must never land on main (#888).\n try {\n const { stdout: hasPlanning } = await execAsync(\n 'git ls-files -- .planning/ 2>/dev/null || true',\n { cwd: workspacePath, encoding: 'utf-8', timeout: 10000 }\n );\n if (hasPlanning.trim()) {\n console.log(`[merge] Stripping .planning/ artifacts from ${branchName} before merge...`);\n await execAsync('git rm -r --cached .planning/', { cwd: workspacePath, encoding: 'utf-8', timeout: 10000 });\n await execAsync(\n `git commit -m \"chore: strip .planning/ before merge\"`,\n { cwd: workspacePath, encoding: 'utf-8', timeout: 10000 }\n );\n await execAsync(\n `git push --force-with-lease origin HEAD:${branchName}`,\n { cwd: workspacePath, encoding: 'utf-8', timeout: 30000 }\n );\n console.log(`[merge] Stripped .planning/ from ${branchName}`);\n }\n } catch (stripErr: any) {\n console.warn(`[merge] Failed to strip .planning/ from ${branchName}: ${stripErr.message}`);\n // Non-fatal: proceed to verification. The no-planning-on-main guardrail\n // will catch any .planning/ files that slip through.\n }\n\n // Step 3: Post-rebase verification gate (typecheck, lint, test)\n // Ensures the rebase didn't introduce issues before merging.\n setReviewStatus(issueId, { mergeStatus: 'verifying', mergeStep: 'verifying', mergeNotes: undefined });\n\n // PAN-2487: when the tip SHA being merged already has GREEN CI on GitHub,\n // the local gate suite re-proves what CI already proved on the identical\n // tree — 10-20 min of redundant wall-clock per merge. Skip local gates in\n // that case; any rebase above produced a NEW SHA, whose CI can't be green\n // yet, so post-rebase combinations still verify locally.\n let skipLocalVerification = false;\n if (primaryForge === 'github' && artifactUrl) {\n try {\n const { parsePullRequestRef, getCiCheckRunsStatePromise, isGitHubAppConfigured } = await import('../../../../lib/github-app.js');\n if (isGitHubAppConfigured()) {\n const ref = parsePullRequestRef({ url: artifactUrl });\n if (ref) {\n const { stdout: tipShaRaw } = await execAsync('git rev-parse HEAD', { cwd: workspacePath, encoding: 'utf-8', timeout: 10000 });\n const tipSha = tipShaRaw.trim();\n const ci = await getCiCheckRunsStatePromise(ref.owner, ref.repo, tipSha);\n if (ci.green && ci.total > 0) {\n skipLocalVerification = true;\n console.log(`[merge] CI is green on ${tipSha.slice(0, 8)} (${ci.successCount}/${ci.total} checks) — skipping redundant local verification for ${issueId} (PAN-2487)`);\n setReviewStatus(issueId, { mergeNotes: `Local verification skipped: CI green on ${tipSha.slice(0, 8)} (${ci.successCount}/${ci.total} checks)` });\n } else {\n console.log(`[merge] CI on tip not green (verdict=${ci.verdict}, total=${ci.total}) — running local verification for ${issueId}`);\n }\n }\n }\n } catch (ciErr: any) {\n console.warn(`[merge] CI-state check failed (${ciErr.message?.slice(0, 120)}) — falling back to local verification for ${issueId}`);\n }\n }\n\n const { appendShipLog } = await import('../../../../lib/cloister/ship-log.js');\n if (skipLocalVerification) {\n appendShipLog(issueId, '✓ Local verification skipped — CI already green on this exact commit', 'verifying');\n }\n const verifyResult = skipLocalVerification\n ? { outcome: 'passed' as const }\n : await (async () => {\n console.log(`[merge] Running post-rebase verification for ${issueId}...`);\n appendShipLog(issueId, 'Running post-rebase verification (full quality-gate suite)…', 'verifying');\n const { runVerificationForIssue } = await import(\n '../../../../lib/cloister/verification-runner.js'\n );\n return Effect.runPromise(runVerificationForIssue(\n issueId,\n workspacePath,\n { isRemote: false },\n 'merge-verify',\n { syncTargetBranch: false, onGateLog: (line) => appendShipLog(issueId, line, 'verifying') },\n ));\n })();\n\n if (verifyResult.outcome === 'failed') {\n const error = `Post-rebase verification failed at ${verifyResult.failedCheck}`;\n console.log(`[merge] ${error}`);\n setReviewStatus(issueId, { mergeStatus: 'failed', mergeNotes: error, readyForMerge: false });\n completePendingOperation(issueId, error);\n\n // Post comment on PR so failure is visible\n try {\n if (artifactUrl) {\n await getForgeAdapter(primaryForge).commentOnArtifact({\n forge: primaryForge,\n url: artifactUrl,\n id: artifactId,\n cwd: workspacePath,\n body: `## Merge Blocked — Post-Rebase Verification Failed\\n\\nFailed check: ${verifyResult.failedCheck}\\n\\nThe branch was rebased successfully but verification failed. The work agent needs to fix the errors and resubmit.`,\n });\n }\n } catch { /* non-fatal */ }\n\n return { success: false, statusCode: 500, error };\n }\n console.log(`[merge] Post-rebase verification ${verifyResult.outcome} for ${issueId}`);\n\n // Step 4a: Report commit statuses on post-rebase HEAD (branch protection requires them).\n // Must happen AFTER rebase because rebase changes the HEAD SHA.\n setReviewStatus(issueId, { mergeStep: 'reporting-statuses' });\n try {\n const { getPullRequestState, isGitHubAppConfigured, reportCommitStatus } = await import('../../../../lib/github-app.js');\n if (githubPrRef && isGitHubAppConfigured()) {\n const prState = await Effect.runPromise(getPullRequestState(githubPrRef.owner, githubPrRef.repo, githubPrRef.number));\n const sha = prState.headSha.trim();\n if (sha) {\n await reportCommitStatus(githubPrRef.owner, githubPrRef.repo, sha, 'success', 'overdeck/review', 'Review passed');\n await reportCommitStatus(githubPrRef.owner, githubPrRef.repo, sha, 'success', 'overdeck/test', 'Tests passed');\n console.log(`[merge] Reported commit statuses on post-rebase HEAD for ${issueId} (${sha.slice(0, 8)})`);\n }\n }\n } catch (statusErr: any) {\n console.warn(`[merge] Failed to report commit statuses: ${statusErr.message}`);\n }\n\n // Step 4b: Merge the review artifact via the configured forge.\n setReviewStatus(issueId, { mergeStep: 'squash-merging' });\n appendShipLog(issueId, `Verification passed — squash-merging the ${primaryForge} PR…`, 'squash-merging');\n let artifactMerged = false;\n try {\n console.log(`[merge] Merging ${primaryForge} review artifact for ${issueId}...`);\n await getForgeAdapter(primaryForge).mergeReviewArtifact({\n forge: primaryForge,\n url: artifactUrl,\n id: artifactId,\n cwd: workspacePath,\n method: 'squash',\n });\n artifactMerged = true;\n } catch (prMergeErr: any) {\n console.error(`[merge] Review artifact merge threw for ${issueId}:`, prMergeErr);\n try {\n const { getPullRequestState, isGitHubAppConfigured } = await import('../../../../lib/github-app.js');\n if (githubPrRef && isGitHubAppConfigured()) {\n const prState = await Effect.runPromise(getPullRequestState(githubPrRef.owner, githubPrRef.repo, githubPrRef.number));\n artifactMerged = prState.merged;\n if (artifactMerged) {\n console.log(`[merge] Race-detected: PR #${githubPrRef.number} for ${issueId} was already merged despite thrown error; proceeding`);\n }\n }\n } catch (stateCheckErr: any) {\n console.warn(`[merge] Post-error PR state check failed for ${issueId}: ${stateCheckErr.message}`);\n }\n\n if (!artifactMerged) {\n const error = `${primaryForge} merge failed: ${prMergeErr.message}`;\n console.error(`[merge] ${error}`);\n\n if (isIntegrationPermissionError(prMergeErr.message)) {\n let scopes = 'pull_requests:write, contents:write';\n try {\n const cap = await verifyAppCanMerge();\n if (cap.missing && cap.missing.length > 0) {\n scopes = cap.missing.join(', ');\n }\n } catch (permCheckErr: any) {\n console.warn(`[merge] verifyAppCanMerge failed for ${issueId}: ${permCheckErr.message}`);\n }\n const note = `GitHub App cannot merge — installation is missing ${scopes}. Grant these scopes to the overdeck-agent installation, then re-merge.`;\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: note });\n completePendingOperation(issueId, note);\n return { success: false, statusCode: 403, error: note };\n }\n\n const isTransient =\n prMergeErr.message?.includes('Timed out waiting for GitHub PR') ||\n prMergeErr.message?.includes('ECONNRESET') ||\n prMergeErr.message?.includes('ETIMEDOUT') ||\n prMergeErr.message?.includes('ECONNREFUSED');\n if (isTransient) {\n const reconciled = await reconcileGitHubMergeStatus(issueId, getReviewStatusSync(issueId));\n if (reconciled) {\n artifactMerged = true;\n console.log(`[merge] Reconciliation confirmed PR merged for ${issueId} after transient error; proceeding to success path`);\n } else {\n setReviewStatus(issueId, { mergeStatus: 'verifying', mergeNotes: error });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 500, error };\n }\n // readyForMerge stays true while reconciliation catches up or the operator retries.\n } else {\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: error });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 500, error };\n }\n }\n }\n\n // Step 5: Mark merged and dequeue next BEFORE post-merge lifecycle.\n // postMergeLifecycle spawns a deploy script that may kill this server process,\n // so queue processing must happen before that point.\n appendShipLog(issueId, `✓ MERGED — running post-merge cleanup (labels, docker teardown, verify-on-main)`, 'post-merge-cleanup');\n setReviewStatus(issueId, { mergeStatus: 'merged', mergeStep: 'post-merge-cleanup', mergeNotes: undefined, readyForMerge: false });\n completePendingOperation(issueId, null);\n\n // Dequeue next merge before lifecycle (which may kill the process)\n advanceQueue();\n\n // Post-merge lifecycle runs last — may spawn deploy script that kills this server\n await postMergeLifecycle(issueId, projectPath, branchName);\n\n return {\n success: true,\n statusCode: 200,\n message: `Successfully merged ${primaryForge} review artifact for ${issueId}`,\n mergeStatus: 'merged',\n prUrl: prResult.prUrl,\n };\n } catch (error: any) {\n const mergeErrorMessage = `Merge pipeline error: ${error.message}`;\n console.error(`[merge] Error for ${issueId}:`, error);\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: mergeErrorMessage });\n completePendingOperation(issueId, error.message);\n return { success: false, statusCode: 500, error: error.message };\n } finally {\n advanceQueue();\n }\n\n}\n\nsetMergeQueueTriggerHandler(triggerMerge);\n\n// ─── Route: POST /api/issues/:issueId/merge ───────────────────────────────\n\nconst postWorkspaceMergeRoute = HttpRouter.add(\n 'POST',\n '/api/issues/:issueId/merge',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n if (!/^[A-Z]+-\\d+$/i.test(issueId)) {\n return jsonResponse({ error: 'Invalid issue ID format' }, { status: 400 });\n }\n const eventStore = yield* EventStoreService;\n\n const result = yield* Effect.promise(() => triggerMerge(issueId));\n if (result.success) {\n yield* Effect.promise(() => Effect.runPromise(eventStore.append({\n type: 'merge.ready',\n timestamp: new Date().toISOString(),\n payload: { issueId },\n })));\n }\n const { statusCode, ...body } = result;\n return jsonResponse(body, { status: statusCode });\n }))\n);\n\n// ─── Route: POST /api/issues/:issueId/forge-approve ──────────────────────\n// Approves the PR/MR on GitHub/GitLab (submits an approving review).\n// This is distinct from the Overdeck /approve endpoint which runs the\n// full merge flow. This just clicks \"Approve\" on the forge.\n\nconst postForgeApproveRoute = HttpRouter.add(\n 'POST',\n '/api/issues/:issueId/forge-approve',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n if (!/^[A-Z]+-\\d+$/i.test(issueId)) {\n return jsonResponse({ error: 'Invalid issue ID format' }, { status: 400 });\n }\n\n return yield* Effect.promise(async () => {\n const { getMergeSetSync, upsertMergeSetSync, withRepoArtifactUrlSync, withRepoStateSync } = await import('../../../../lib/merge-set.js');\n const { getForgeAdapter } = await import('../../../../lib/forge.js');\n\n let mergeSet = getMergeSetSync(issueId);\n if (!mergeSet) {\n return jsonResponse({ error: `No merge set found for ${issueId}` }, { status: 404 });\n }\n\n const results: Array<{ repoKey: string; approved: boolean; error?: string }> = [];\n for (const repo of mergeSet.repos) {\n if (repo.mergeStatus === 'merged' || repo.mergeStatus === 'skipped') {\n results.push({ repoKey: repo.repoKey, approved: true });\n continue;\n }\n\n const adapter = getForgeAdapter(repo.forge);\n const workspacePath = mergeSet.workspaceType === 'polyrepo'\n ? join(mergeSet.projectPath, 'workspaces', `feature-${issueId.toLowerCase()}`, repo.repoKey)\n : join(mergeSet.projectPath, 'workspaces', `feature-${issueId.toLowerCase()}`);\n\n let artifactUrl = repo.artifactUrl;\n let artifactId = repo.artifactId;\n\n if (!artifactUrl && !artifactId) {\n try {\n const discovered = await adapter.discoverArtifact({\n sourceBranch: repo.sourceBranch,\n cwd: existsSync(workspacePath) ? workspacePath : repo.repoPath,\n });\n if (discovered?.url || discovered?.id) {\n artifactUrl = discovered.url;\n artifactId = discovered.id;\n mergeSet = withRepoArtifactUrlSync(mergeSet, repo.repoKey, artifactUrl ?? '', artifactId);\n upsertMergeSetSync(mergeSet);\n console.log(`[forge-approve] Discovered artifact for ${issueId}/${repo.repoKey}: ${artifactUrl}`);\n } else {\n results.push({ repoKey: repo.repoKey, approved: true });\n continue;\n }\n } catch {\n results.push({ repoKey: repo.repoKey, approved: true });\n continue;\n }\n }\n\n try {\n await adapter.approveReviewArtifact({\n forge: repo.forge,\n url: artifactUrl,\n id: artifactId,\n cwd: existsSync(workspacePath) ? workspacePath : repo.repoPath,\n });\n results.push({ repoKey: repo.repoKey, approved: true });\n } catch (err: any) {\n results.push({ repoKey: repo.repoKey, approved: false, error: err.message });\n }\n }\n\n const approvedCount = results.filter(r => r.approved).length;\n if (approvedCount > 0) {\n const { emitActivityEntrySync, emitActivityTtsSync } = await import('../../../../lib/activity-logger.js');\n emitActivityEntrySync({\n source: 'dashboard',\n level: 'success',\n message: `Merge approved for ${issueId}`,\n issueId,\n });\n emitActivityTtsSync({\n utterance: `Merge approved for ${issueId}`,\n priority: 1,\n issueId,\n source: 'dashboard',\n eventType: 'merge.approved',\n });\n }\n\n const allApproved = results.every(r => r.approved);\n return jsonResponse(\n { success: allApproved, results },\n { status: allApproved ? 200 : 207 }\n );\n });\n }))\n);\n\n// ─── Route: POST /api/issues/:issueId/forge-merge ────────────────────────\n// Merges the PR/MR directly on GitHub/GitLab via the forge adapter.\n// This is a lightweight forge-level merge — it does NOT run Overdeck's\n// full post-merge lifecycle (label cleanup, workspace teardown, etc.).\n\nconst postForgeMergeRoute = HttpRouter.add(\n 'POST',\n '/api/issues/:issueId/forge-merge',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n if (!/^[A-Z]+-\\d+$/i.test(issueId)) {\n return jsonResponse({ error: 'Invalid issue ID format' }, { status: 400 });\n }\n\n return yield* Effect.promise(async () => {\n const { getMergeSetSync, upsertMergeSetSync, withRepoArtifactUrlSync, withRepoStateSync } = await import('../../../../lib/merge-set.js');\n const { getForgeAdapter } = await import('../../../../lib/forge.js');\n\n let mergeSet = getMergeSetSync(issueId);\n if (!mergeSet) {\n return jsonResponse({ error: `No merge set found for ${issueId}` }, { status: 404 });\n }\n\n const results: Array<{ repoKey: string; merged: boolean; error?: string }> = [];\n for (const repo of mergeSet.repos) {\n if (repo.mergeStatus === 'merged' || repo.mergeStatus === 'skipped') {\n results.push({ repoKey: repo.repoKey, merged: true });\n continue;\n }\n\n const adapter = getForgeAdapter(repo.forge);\n const workspacePath = mergeSet.workspaceType === 'polyrepo'\n ? join(mergeSet.projectPath, 'workspaces', `feature-${issueId.toLowerCase()}`, repo.repoKey)\n : join(mergeSet.projectPath, 'workspaces', `feature-${issueId.toLowerCase()}`);\n\n let artifactUrl = repo.artifactUrl;\n let artifactId = repo.artifactId;\n\n if (!artifactUrl && !artifactId) {\n try {\n const discovered = await adapter.discoverArtifact({\n sourceBranch: repo.sourceBranch,\n cwd: existsSync(workspacePath) ? workspacePath : repo.repoPath,\n });\n if (discovered?.url || discovered?.id) {\n artifactUrl = discovered.url;\n artifactId = discovered.id;\n mergeSet = withRepoArtifactUrlSync(mergeSet, repo.repoKey, artifactUrl ?? '', artifactId);\n upsertMergeSetSync(mergeSet);\n console.log(`[forge-merge] Discovered artifact for ${issueId}/${repo.repoKey}: ${artifactUrl}`);\n } else {\n mergeSet = withRepoStateSync(mergeSet, repo.repoKey, { mergeStatus: 'skipped' });\n upsertMergeSetSync(mergeSet);\n results.push({ repoKey: repo.repoKey, merged: true });\n continue;\n }\n } catch {\n mergeSet = withRepoStateSync(mergeSet, repo.repoKey, { mergeStatus: 'skipped' });\n upsertMergeSetSync(mergeSet);\n results.push({ repoKey: repo.repoKey, merged: true });\n continue;\n }\n }\n\n try {\n await adapter.mergeReviewArtifact({\n forge: repo.forge,\n url: artifactUrl,\n id: artifactId,\n method: 'squash',\n cwd: existsSync(workspacePath) ? workspacePath : repo.repoPath,\n });\n mergeSet = withRepoStateSync(mergeSet, repo.repoKey, { mergeStatus: 'merged' });\n upsertMergeSetSync(mergeSet);\n results.push({ repoKey: repo.repoKey, merged: true });\n } catch (err: any) {\n results.push({ repoKey: repo.repoKey, merged: false, error: err.message });\n }\n }\n\n const mergedCount = results.filter(r => r.merged).length;\n if (mergedCount > 0) {\n const { emitActivityEntrySync, emitActivityTtsSync } = await import('../../../../lib/activity-logger.js');\n emitActivityEntrySync({\n source: 'dashboard',\n level: 'success',\n message: `Merged ${issueId} on ${mergeSet.repos[0]?.forge ?? 'forge'}`,\n issueId,\n });\n emitActivityTtsSync({\n utterance: `${issueId} has been merged`,\n priority: 1,\n issueId,\n source: 'dashboard',\n eventType: 'forgeMerge.merged',\n });\n }\n\n const allMerged = results.every(r => r.merged);\n return jsonResponse(\n { success: allMerged, results },\n { status: allMerged ? 200 : 207 }\n );\n });\n }))\n);\n\n// ─── Route: POST /api/issues/:issueId/approve ────────────────────────────\n\nconst postWorkspaceApproveRoute = HttpRouter.add(\n 'POST',\n '/api/issues/:issueId/approve',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n\n const existingStatus = getReviewStatusSync(issueId);\n if (\n existingStatus?.readyForMerge &&\n existingStatus.reviewStatus === 'passed' &&\n existingStatus.testStatus === 'passed'\n ) {\n console.log(\n `[approve] Review+test already passed for ${issueId}, forwarding to merge endpoint...`\n );\n const apiPort = process.env.API_PORT || process.env.PORT || '3011';\n try {\n const mergeRes = yield* Effect.promise(() => fetch(\n `http://localhost:${apiPort}/api/issues/${issueId}/merge`,\n { method: 'POST', headers: { 'Content-Type': 'application/json' } }\n ));\n const mergeData = (yield* Effect.promise(() => mergeRes.json())) as any;\n return jsonResponse(mergeData, { status: mergeRes.status });\n } catch (err: any) {\n return jsonResponse(\n { error: `Failed to forward to merge: ${err.message}` },\n { status: 500 }\n );\n }\n }\n\n return yield* Effect.promise(async () => {\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const issueLower = issueId.toLowerCase();\n const workspacePath = join(projectPath, 'workspaces', `feature-${issueLower}`);\n const branchName = `feature/${issueLower}`;\n\n setPendingOperation(issueId, 'approve');\n\n if (!existsSync(workspacePath)) {\n completePendingOperation(issueId, 'Workspace does not exist');\n return jsonResponse({ error: 'Workspace does not exist' }, { status: 400 });\n }\n\n try {\n await execAsync(`git rev-parse --verify ${branchName}`, {\n cwd: projectPath,\n encoding: 'utf-8',\n });\n } catch {\n completePendingOperation(issueId, `Branch ${branchName} does not exist`);\n return jsonResponse(\n { error: `Branch ${branchName} does not exist` },\n { status: 400 }\n );\n }\n\n try {\n const { stdout: status } = await execAsync(\n 'git status --porcelain -uno',\n { cwd: workspacePath, encoding: 'utf-8' }\n );\n // STATE-PLANE-COMMIT-POLICY rules 3/6: state-plane-only dirt is not agent work.\n if (shouldBlockApproveForDirtyStatus(status)) {\n const error = `Workspace has uncommitted changes. Please commit the changes, explicitly discard them, or surface them to the operator first:\\ncd ${workspacePath}\\ngit status`;\n completePendingOperation(issueId, error);\n return jsonResponse({ error }, { status: 400 });\n }\n } catch {}\n\n try {\n await execAsync(`git push origin ${branchName}`, {\n cwd: workspacePath,\n encoding: 'utf-8',\n });\n } catch (pushErr: any) {\n console.log(`Feature branch push note: ${pushErr.message}`);\n }\n\n // Concurrent-merge detection: warn if another push to main succeeded in the last 30s.\n // recentPushWarning is included in the success response body below (line ~4146) so\n // the caller can surface it to the operator without a separate lookup.\n const recentCutoff = new Date(Date.now() - 30_000).toISOString();\n const recentMainPushes = listGitOperationsSync({ operation: 'push', since: recentCutoff })\n .filter((op) => op.status === 'success' && op.branch === 'main' && op.issueId !== issueId);\n const recentPushWarning = recentMainPushes.length > 0\n ? `Another workspace pushed to main ${Math.round((Date.now() - new Date(recentMainPushes[0].ts).getTime()) / 1000)}s ago — divergence possible`\n : undefined;\n if (recentPushWarning) {\n console.warn(`[approve] ${recentPushWarning} (${issueId})`);\n }\n\n try {\n await execAsync('git checkout main', { cwd: projectPath, encoding: 'utf-8' });\n await execAsync('git fetch origin main', { cwd: projectPath, encoding: 'utf-8' });\n // Detect local-only main commits without silently hard-resetting.\n const { stdout: divergenceRaw } = await execAsync(\n 'git rev-list --left-right --count HEAD...origin/main',\n { cwd: projectPath, encoding: 'utf-8' }\n );\n const [aheadRaw = '0', behindRaw = '0'] = divergenceRaw.trim().split(/\\s+/);\n const aheadCount = parseInt(aheadRaw, 10) || 0;\n const behindCount = parseInt(behindRaw, 10) || 0;\n if (aheadCount > 0) {\n const error = buildLocalMainRecoveryError(projectPath, aheadCount, behindCount);\n completePendingOperation(issueId, error);\n return jsonResponse({ error }, { status: 409 });\n }\n await execAsync('git pull origin main --ff-only', {\n cwd: projectPath,\n encoding: 'utf-8',\n });\n } catch (checkoutErr: any) {\n const error = `Failed to checkout/update main branch: ${checkoutErr.message}`;\n completePendingOperation(issueId, error);\n return jsonResponse({ error }, { status: 400 });\n }\n\n // Divergence preview: count how many commits main has advanced past the feature branch\n let mainAdvancedBy = 0;\n try {\n const { stdout: aheadRaw } = await execAsync(\n `git rev-list ${branchName}..main --count`,\n { cwd: projectPath, encoding: 'utf-8' }\n );\n mainAdvancedBy = parseInt(aheadRaw.trim(), 10) || 0;\n if (mainAdvancedBy > 0) {\n console.log(`[approve] main has advanced ${mainAdvancedBy} commit(s) past ${branchName}`);\n }\n } catch {}\n\n console.log(`[approve] Starting role pipeline for ${issueId}...`);\n\n // PAN-1048 R3: route through the same wrapper every other approve path\n // uses (idempotency + feedback archive + status flip\n // + pipeline event). The role agent loads roles/review.md, fans out the\n // four code-review-* convoy reviewers via Agent tool, synthesizes, and\n // posts the verdict via /api/review/:id/status. Test dispatch is NOT\n // part of the review prompt — reactive Cloister picks up the\n // review.approved lifecycle event and spawns the test role.\n let reviewResult: { success: boolean; message: string; error?: string; gated?: boolean };\n try {\n const { spawnReviewRoleForIssue } = await import('../../../../lib/cloister/review-agent.js');\n reviewResult = await Effect.runPromise(spawnReviewRoleForIssue({\n issueId,\n workspace: workspacePath,\n branch: branchName,\n prUrl: getReviewStatusSync(issueId)?.prUrl,\n }));\n } catch (err: any) {\n reviewResult = {\n success: false,\n message: err?.message ?? 'Failed to start review role',\n error: err?.message,\n };\n }\n\n if (!reviewResult.success) {\n if (reviewResult.gated) {\n console.log(`[approve] review dispatch deferred for ${issueId}: ${reviewResult.message}`);\n completePendingOperation(issueId, reviewResult.message);\n setReviewStatusBase(issueId, {\n reviewStatus: 'pending',\n reviewNotes: reviewResult.message,\n });\n return jsonResponse({\n success: false,\n gated: true,\n message: reviewResult.message,\n pipeline: 'deferred',\n ...(recentPushWarning && { recentPushWarning }),\n ...(mainAdvancedBy > 0 && { mainAdvancedBy }),\n }, { status: 409 });\n }\n\n console.warn(`[approve] review role failed to start: ${reviewResult.message}`);\n console.log(`[approve] Falling back to direct merge...`);\n } else {\n console.log(\n `[approve] Pipeline started - review role will synthesize convoy findings`\n );\n completePendingOperation(issueId, null);\n return jsonResponse({\n success: true,\n message: `Approval pipeline started for ${issueId}. Role: review`,\n pipeline: 'running',\n note: 'Watch the role run for progress. Click Merge when review+test pass.',\n ...(recentPushWarning && { recentPushWarning }),\n ...(mainAdvancedBy > 0 && { mainAdvancedBy }),\n });\n }\n\n // Fallback (PAN-1531): direct server-side rebase via rebaseFeatureBranch.\n // The ship-role LLM agent was retired — rebase is deterministic mechanical\n // work and runs in-process. On success the workspace branch is pushed to\n // origin with --force-with-lease and the dashboard flips readyForMerge so\n // the human Merge button renders. On conflict the operator resolves\n // manually in the workspace and re-requests review.\n console.log(`[approve] Step 3/3: Running server-side rebase for ${issueId}...`);\n\n try {\n const { rebaseFeatureBranch } = await import(\n '../../../../lib/cloister/merge-rebase.js'\n );\n const workspacePathForRebase = join(projectPath, 'workspaces', `feature-${issueId.toLowerCase()}`);\n const rebaseResult = await Effect.runPromise(\n rebaseFeatureBranch(workspacePathForRebase, branchName, 'main', issueId),\n );\n\n if (!rebaseResult.success) {\n const conflictDetail = rebaseResult.conflictFiles?.length\n ? `\\nConflict files: ${rebaseResult.conflictFiles.join(', ')}`\n : '';\n const error = `Rebase blocked for ${issueId}.\\nReason: ${rebaseResult.reason ?? 'Unknown'}${conflictDetail}\\n\\nResume in workspace:\\n cd ${workspacePathForRebase}\\n git rebase origin/main\\n # resolve conflicts, then\\n git push --force-with-lease`;\n completePendingOperation(issueId, error);\n return jsonResponse({ error }, { status: 400 });\n }\n\n console.log(`[approve] Rebase complete for ${issueId} (${rebaseResult.skipped ? 'no-op' : 'rebased'}); ready for human Merge button`);\n } catch (rebaseError: any) {\n const error = `Server-side rebase failed: ${rebaseError.message}\\n\\nResolve manually:\\n cd <workspace>\\n git rebase origin/main\\n git push --force-with-lease`;\n completePendingOperation(issueId, error);\n return jsonResponse({ error }, { status: 400 });\n }\n\n // Push merged main (with divergence guard — pushApproveMain catches MainDivergedError\n // and marks workspace stuck if origin/main advanced past our local ancestor)\n const pushResult = await pushApproveMain(issueId, projectPath);\n if (!pushResult.pushed) {\n completePendingOperation(issueId, pushResult.error);\n return jsonResponse({ error: pushResult.error }, { status: pushResult.httpStatus });\n }\n\n // Post-merge lifecycle\n const { approve: lifecycleApprove } = await import('../../../../lib/lifecycle/index.js');\n const ghResolved = resolveGitHubIssueShared(issueId);\n const isGitHubIssueFlag = ghResolved.isGitHub;\n const lifecycleCtx = {\n issueId,\n projectPath,\n ...(ghResolved.isGitHub\n ? {\n github: {\n owner: ghResolved.owner,\n repo: ghResolved.repo,\n number: ghResolved.number,\n },\n }\n : {}),\n };\n\n const lifecycleResult = await Effect.runPromise(lifecycleApprove(lifecycleCtx));\n console.log(\n `[approve] Lifecycle completed for ${issueId}: ${lifecycleResult.steps\n .filter((s: any) => s.success && !s.skipped)\n .map((s: any) => s.step)\n .join(', ')}`\n );\n\n if (isGitHubIssueFlag) {\n try {\n await execAsync('pan sync', { encoding: 'utf-8', timeout: 30000 });\n } catch (syncError: any) {\n console.error('pan sync failed (non-fatal):', syncError.message);\n }\n }\n\n completePendingOperation(issueId);\n\n return jsonResponse({\n success: true,\n message: `Approved ${issueId}: ${lifecycleResult.steps\n .filter((s: any) => s.success && !s.skipped)\n .map((s: any) => s.step)\n .join(', ')}${isGitHubIssueFlag ? ', skills synced' : ''}`,\n });\n });\n }))\n);\n\n\n// ─── Route: GET /api/merge-queue ─────────────────────────────────────────────\n\nconst getMergeQueueRoute = HttpRouter.add(\n 'GET',\n '/api/merge-queue',\n httpHandler(Effect.gen(function* () {\n const queues = getAllActiveQueues();\n return jsonResponse({ queues });\n })),\n);\n\n// ─── Route: POST /api/internal/pipeline/notify ────────────────────────────────\n//\n// Cross-process bridge for `notifyPipeline()` (PAN-891, expanded in PAN-915).\n//\n// `notifyPipeline` is an in-process handler registry; only the dashboard server\n// registers a handler. CLI processes (e.g. `pan review run`) write to shared\n// state and call `notifyPipeline()`, which is a no-op in their own process.\n// This endpoint lets them poke the dashboard so it re-emits the corresponding\n// domain event into the live event stream.\n//\n// Accepted bodies (PAN-915):\n// { type: 'status_changed', issueId }\n// — Server re-reads ReviewStatus from SQLite (avoids stale snapshots) and\n// dispatches via in-process handler.\n// { type: 'review.approved', issueId }\n// { type: 'test.passed', issueId }\n// { type: 'task_queued', specialist, issueId }\n// { type: 'reviewer_started', issueId, role, sessionName }\n// { type: 'reviewer_completed', issueId, role }\n// { type: 'reviewer_timed_out', issueId, role, sessionName, attempt, maxRetries, willRetry }\n// { type: 'coordinator_started', issueId, sessionName }\n// { type: 'coordinator_died', issueId, sessionName, reason }\n// — Forwarded verbatim to the in-process handler.\n\nconst postInternalPipelineNotifyRoute = HttpRouter.add(\n 'POST',\n '/api/internal/pipeline/notify',\n httpHandler(Effect.gen(function* () {\n // Shared-secret check (PAN-891 review feedback). The dashboard binds 0.0.0.0\n // by default, so this stateful endpoint must be unreachable without the\n // server-issued token. Same token is read by CLI senders via getInternalToken().\n const request = yield* HttpServerRequest.HttpServerRequest;\n const { INTERNAL_TOKEN_HEADER, getInternalTokenSync } = yield* Effect.promise(() =>\n import('../../../../lib/internal-token.js'),\n );\n const expected = getInternalTokenSync();\n if (!expected) {\n return jsonResponse({ ok: false, error: 'internal token not configured' }, 503);\n }\n const headers = request.headers as Record<string, string | string[] | undefined>;\n const raw = headers[INTERNAL_TOKEN_HEADER];\n const provided = Array.isArray(raw) ? raw[0] : raw;\n if (!provided || provided !== expected) {\n return jsonResponse({ ok: false, error: 'forbidden' }, 403);\n }\n\n const body = yield* readJsonBody;\n const event = body as Record<string, unknown>;\n const type = event.type as string | undefined;\n\n const { notifyPipelineSync } = yield* Effect.promise(() =>\n import('../../../../lib/pipeline-notifier.js'),\n );\n\n switch (type) {\n case 'status_changed': {\n const issueId = event.issueId as string | undefined;\n if (!issueId) {\n return jsonResponse({ ok: false, error: 'status_changed requires issueId' }, 400);\n }\n const status = getReviewStatusSync(issueId);\n if (!status) {\n return jsonResponse({ ok: false, error: `no review status found for ${issueId}` }, 404);\n }\n notifyPipelineSync({ type: 'status_changed', issueId, status });\n return jsonResponse({ ok: true });\n }\n case 'review.approved':\n case 'test.passed': {\n const issueId = event.issueId as string | undefined;\n if (!issueId) {\n return jsonResponse({ ok: false, error: `${type} requires issueId` }, 400);\n }\n // PAN-1988: this MUST be notifyPipelineSync (the imported function). The bare\n // `notifyPipeline` (the Effect variant) is not imported here, so it threw\n // \"notifyPipeline is not defined\" and silently dropped EVERY forwarded review.approved /\n // test.passed event — breaking the reactive review→test and test→ship handoffs for any\n // CLI-originated verdict. The in-process dashboard handler routes these to reactive Cloister.\n notifyPipelineSync({ type, issueId });\n return jsonResponse({ ok: true });\n }\n case 'task_queued': {\n const issueId = event.issueId as string | undefined;\n const specialist = event.specialist as string | undefined;\n if (!issueId || !specialist) {\n return jsonResponse({ ok: false, error: 'task_queued requires issueId and specialist' }, 400);\n }\n notifyPipelineSync({ type: 'task_queued', specialist, issueId });\n return jsonResponse({ ok: true });\n }\n case 'reviewer_started': {\n const issueId = event.issueId as string | undefined;\n const role = event.role as string | undefined;\n const sessionName = event.sessionName as string | undefined;\n if (!issueId || !role || !sessionName) {\n return jsonResponse({ ok: false, error: 'reviewer_started requires issueId, role, sessionName' }, 400);\n }\n notifyPipelineSync({ type: 'reviewer_started', issueId, role, sessionName });\n return jsonResponse({ ok: true });\n }\n case 'reviewer_completed': {\n const issueId = event.issueId as string | undefined;\n const role = event.role as string | undefined;\n if (!issueId || !role) {\n return jsonResponse({ ok: false, error: 'reviewer_completed requires issueId, role' }, 400);\n }\n notifyPipelineSync({ type: 'reviewer_completed', issueId, role });\n return jsonResponse({ ok: true });\n }\n case 'reviewer_timed_out': {\n const issueId = event.issueId as string | undefined;\n const role = event.role as string | undefined;\n const sessionName = event.sessionName as string | undefined;\n const attempt = typeof event.attempt === 'number' ? event.attempt : undefined;\n const maxRetries = typeof event.maxRetries === 'number' ? event.maxRetries : undefined;\n const willRetry = typeof event.willRetry === 'boolean' ? event.willRetry : undefined;\n if (!issueId || !role || !sessionName || attempt === undefined || maxRetries === undefined || willRetry === undefined) {\n return jsonResponse({ ok: false, error: 'reviewer_timed_out requires issueId, role, sessionName, attempt, maxRetries, willRetry' }, 400);\n }\n notifyPipelineSync({ type: 'reviewer_timed_out', issueId, role, sessionName, attempt, maxRetries, willRetry });\n return jsonResponse({ ok: true });\n }\n case 'coordinator_started': {\n const issueId = event.issueId as string | undefined;\n const sessionName = event.sessionName as string | undefined;\n if (!issueId || !sessionName) {\n return jsonResponse({ ok: false, error: 'coordinator_started requires issueId, sessionName' }, 400);\n }\n notifyPipelineSync({ type: 'coordinator_started', issueId, sessionName });\n return jsonResponse({ ok: true });\n }\n case 'coordinator_died': {\n const issueId = event.issueId as string | undefined;\n const sessionName = event.sessionName as string | undefined;\n const reason = event.reason as string | undefined;\n if (!issueId || !sessionName || !reason) {\n return jsonResponse({ ok: false, error: 'coordinator_died requires issueId, sessionName, reason' }, 400);\n }\n notifyPipelineSync({ type: 'coordinator_died', issueId, sessionName, reason });\n return jsonResponse({ ok: true });\n }\n default:\n return jsonResponse({ ok: false, error: `unknown pipeline event type: ${type}` }, 400);\n }\n })),\n);\n\n\nexport const mergeOpsRouteLayer = Layer.mergeAll(\n postWorkspaceSyncMainRoute,\n postWorkspaceMergeRoute,\n postForgeApproveRoute,\n postForgeMergeRoute,\n postWorkspaceApproveRoute,\n getMergeQueueRoute,\n postInternalPipelineNotifyRoute,\n);\n\nexport default mergeOpsRouteLayer;\n","/**\n * Workspace-data route module — extracted from routes/workspaces.ts (B / wave 2, seam 1).\n *\n * Read-only workspace query + plan/UAT + TLDR endpoints:\n * GET /api/workspace-stack-health\n * GET /api/workspaces/:issueId\n * POST /api/workspaces\n * GET /api/workspaces/:issueId/plan\n * GET /api/workspaces/:issueId/uat-context\n * PATCH /api/workspaces/:issueId/plan/inspection-policy\n * GET /api/workspaces/:issueId/tldr\n *\n * Shared singletons (project path, workspace info, container status, pending ops,\n * review reconciliation, readJsonBody, spawnPanCommand, requireTrustedMutationOrigin)\n * stay owned by ../workspaces.js and are imported here.\n */\n\nimport { exec, execFile } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { access, readFile, stat, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { Effect, Layer } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\n\nimport { parseIssueIdSync, extractPrefixSync } from '../../../../lib/issue-id.js';\nimport {\n resolveProjectFromIssueSync,\n getProjectSync,\n findProjectByTeamSync,\n} from '../../../../lib/projects.js';\nimport { loadWorkspaceMetadataSync } from '../../../../lib/remote/workspace-metadata.js';\nimport {\n collectDockerContainerLifecycleSnapshot,\n getWorkspaceStackHealth,\n} from '../../../../lib/workspace/stack-health.js';\nimport { listSessionNames, capturePane } from '../../../../lib/tmux.js';\nimport { getActiveSessionModelSync } from '../../../../lib/cost-parsers/jsonl-parser.js';\nimport { getReviewStatusSync } from '../../../../lib/review-status.js';\nimport { listStashes, isSalvageableStash } from '../../../../lib/stashes.js';\nimport { findPlan, isPlanningComplete, mergeRecordStatusOverrides, readPlan } from '../../../../lib/vbrief/io.js';\nimport { getCostsForIssueSync } from '../../../../lib/costs/index.js';\nimport { resolveIssueHeadlineCost } from '../../services/issue-cost-resolver.js';\nimport { getCachedRunningAgents } from '../../services/running-agents-cache.js';\nimport { PAN_CONTINUE_FILENAME, PAN_DIRNAME } from '../../../../lib/pan-dir/types.js';\nimport { getWorkspacePathForIssue } from '../../workspace-paths.js';\nimport { criticalPath, actionableDoc } from '../../../../lib/vbrief/dag.js';\nimport { findVBriefByIssue, readVBriefDocument } from '../../../../lib/vbrief/vbrief-index.js';\nimport { VBRIEF_INSPECTION_POLICIES } from '../../../../lib/vbrief/types.js';\nimport type { VBriefDocument, VBriefInspectionPolicy } from '../../../../lib/vbrief/types.js';\nimport { getChangedFiles, getDiffBase, getDiffStat } from '../../../../lib/cloister/review-context.js';\nimport type { ChangedFile } from '../../../../lib/cloister/review-context.js';\nimport { getTldrDaemonServiceSync } from '../../../../lib/tldr-daemon.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { httpHandler } from '../http-handler.js';\nimport {\n getProjectPath,\n getWorkspaceInfoForIssue,\n getContainerStatusAsync,\n getPendingOperation,\n readJsonBody,\n spawnPanCommand,\n requireTrustedMutationOrigin,\n} from '../workspaces.js';\nimport { reconcileGitHubMergeStatus } from './merge-ops.js';\n\nconst execAsync = promisify(exec);\nconst execFileAsync = promisify(execFile);\n\nfunction getWorkspaceLocation(issueId: string): 'local' | 'remote' | undefined {\n try {\n const meta = loadWorkspaceMetadataSync(issueId);\n if (meta?.location) return meta.location as 'local' | 'remote';\n } catch { /* non-fatal */ }\n return undefined;\n}\nasync function getGitStatusAsync(workspacePath: string): Promise<{\n branch: string;\n uncommittedFiles: number;\n latestCommit: string;\n} | null> {\n try {\n const [branchResult, statusResult, logResult] = await Promise.all([\n execAsync('git rev-parse --abbrev-ref HEAD', { cwd: workspacePath, encoding: 'utf-8' }),\n execAsync('git status --porcelain', { cwd: workspacePath, encoding: 'utf-8' }),\n execAsync('git log -1 --format=\"%s\"', { cwd: workspacePath, encoding: 'utf-8' }),\n ]);\n return {\n branch: branchResult.stdout.trim(),\n uncommittedFiles: statusResult.stdout.trim() ? statusResult.stdout.trim().split('\\n').length : 0,\n latestCommit: logResult.stdout.trim(),\n };\n } catch {\n return null;\n }\n}\nasync function getRepoGitStatusAsync(workspacePath: string): Promise<{\n ahead: number;\n behind: number;\n hasOrigin: boolean;\n} | null> {\n try {\n const { stdout: remoteOut } = await execAsync('git remote -v', { cwd: workspacePath, encoding: 'utf-8' });\n if (!remoteOut.includes('origin')) return { ahead: 0, behind: 0, hasOrigin: false };\n const { stdout: revlistOut } = await execAsync(\n 'git rev-list --left-right --count HEAD...origin/HEAD 2>/dev/null || echo \"0\\t0\"',\n { cwd: workspacePath, encoding: 'utf-8' }\n );\n const parts = revlistOut.trim().split('\\t');\n return {\n ahead: parseInt(parts[0] || '0', 10),\n behind: parseInt(parts[1] || '0', 10),\n hasOrigin: true,\n };\n } catch {\n return null;\n }\n}\nasync function getMrUrlAsync(issueId: string, workspacePath: string): Promise<string | null> {\n try {\n const issueLower = issueId.toLowerCase();\n const branchName = `feature/${issueLower}`;\n const { stdout } = await execFileAsync('gh', ['pr', 'view', branchName, '--json', 'url', '--jq', '.url'], { cwd: workspacePath, encoding: 'utf-8' });\n const url = stdout.trim();\n return url || null;\n } catch {\n return null;\n }\n}\nasync function getIndexStats(workspacePath: string): Promise<{\n fileCount?: number;\n indexAge?: string;\n edgeCount?: number;\n}> {\n const tldrPath = join(workspacePath, '.tldr');\n const tldrExists = await access(tldrPath).then(() => true, () => false);\n if (!tldrExists) return {};\n try {\n let indexAge: string | undefined;\n const langPath = join(tldrPath, 'languages.json');\n const langContent = await readFile(langPath, 'utf-8').catch(() => null);\n if (langContent) {\n const langData = JSON.parse(langContent);\n if (langData.timestamp) {\n const ageMs = Date.now() - langData.timestamp * 1000;\n const ageHours = Math.floor(ageMs / (1000 * 60 * 60));\n indexAge =\n ageHours === 0 ? 'now' : ageHours < 24 ? `${ageHours}h ago` : `${Math.floor(ageHours / 24)}d ago`;\n }\n }\n if (!indexAge) {\n const stats = await stat(tldrPath);\n const ageMs = Date.now() - stats.mtimeMs;\n const ageHours = Math.floor(ageMs / (1000 * 60 * 60));\n indexAge =\n ageHours === 0 ? 'now' : ageHours < 24 ? `${ageHours}h ago` : `${Math.floor(ageHours / 24)}d ago`;\n }\n let fileCount: number | undefined;\n let edgeCount: number | undefined;\n const cgPath = join(tldrPath, 'cache', 'call_graph.json');\n const cgContent = await readFile(cgPath, 'utf-8').catch(() => null);\n if (cgContent) {\n const cg = JSON.parse(cgContent);\n edgeCount = Array.isArray(cg.edges) ? cg.edges.length : undefined;\n if (Array.isArray(cg.edges)) {\n const files = new Set<string>();\n for (const e of cg.edges) {\n if (e.from_file) files.add(e.from_file);\n if (e.to_file) files.add(e.to_file);\n }\n fileCount = files.size;\n }\n }\n return { fileCount, indexAge, edgeCount };\n } catch (err) {\n console.error(`[getIndexStats] Error for ${workspacePath}:`, err);\n return {};\n }\n}\nfunction resolvePlanLocation(projectPath: string, issueId: string): Effect.Effect<{ path: string; lifecycleDir: string; doc: VBriefDocument } | null, unknown> {\n return Effect.gen(function* () {\n // PAN-2401: every doc this route returns gets the per-issue record's\n // statusOverrides applied — merged beads must read 'completed', not the\n // spec's immutable 'pending'.\n const issueLower = issueId.toLowerCase();\n const workspacePath = join(projectPath, 'workspaces', `feature-${issueLower}`);\n\n const found = yield* findVBriefByIssue(projectPath, issueId);\n if (found) {\n const doc = yield* readVBriefDocument(found.path);\n return {\n path: found.path,\n lifecycleDir: found.lifecycleDir,\n doc: mergeRecordStatusOverrides(doc, workspacePath),\n };\n }\n\n const planPath = yield* findPlan(workspacePath);\n if (!planPath) return null;\n const doc = yield* readPlan(planPath);\n return {\n path: planPath,\n lifecycleDir: 'workspace',\n doc: mergeRecordStatusOverrides(doc, workspacePath),\n };\n });\n}\n\nexport interface UatContextAcceptanceCriterion {\n id: string;\n title: string;\n status: string;\n itemId: string;\n itemTitle: string;\n}\n\nexport interface UatContextDeliverable {\n id: string;\n title: string;\n status: string;\n action?: string;\n}\n\nexport interface UatContextChangedFile {\n path: string;\n status: 'M' | 'A' | 'D' | 'R' | 'C' | 'U';\n additions: number;\n deletions: number;\n}\n\ninterface UatContextPlanFields {\n acceptanceCriteria: UatContextAcceptanceCriterion[];\n deliverables: UatContextDeliverable[];\n proposal: string | null;\n}\n\ninterface UatContextGitFields {\n changedFiles: UatContextChangedFile[];\n changedFilesTotal: number;\n changedFilesOmitted: number;\n diffStat: { stat: string; truncated: boolean } | null;\n source: { files: 'git' | 'none' };\n}\n\nconst MAX_UAT_CONTEXT_CHANGED_FILES = 12;\nconst MAX_UAT_CONTEXT_DIFF_STAT_LENGTH = 4_000;\n\nexport function assembleUatContextPlanFields(doc: VBriefDocument | null): UatContextPlanFields {\n if (!doc) {\n return { acceptanceCriteria: [], deliverables: [], proposal: null };\n }\n\n const acceptanceCriteria: UatContextAcceptanceCriterion[] = [];\n const deliverables: UatContextDeliverable[] = [];\n\n for (const item of doc.plan.items ?? []) {\n deliverables.push({\n id: item.id,\n title: item.title,\n status: item.status,\n ...(item.narrative?.Action ? { action: item.narrative.Action } : {}),\n });\n\n const subItems = item.subItems ?? [];\n if (subItems.length === 0) continue;\n\n const taggedAcceptanceCriteria = subItems.filter(\n (subItem) => subItem.metadata?.kind === 'acceptance_criterion',\n );\n const selectedSubItems = taggedAcceptanceCriteria.length > 0\n ? taggedAcceptanceCriteria\n : subItems;\n\n for (const subItem of selectedSubItems) {\n acceptanceCriteria.push({\n id: subItem.id,\n title: subItem.title,\n status: subItem.status,\n itemId: item.id,\n itemTitle: item.title,\n });\n }\n }\n\n return {\n acceptanceCriteria,\n deliverables,\n proposal: doc.plan.narratives?.Proposal ?? null,\n };\n}\n\nexport function emptyUatContextGitFields(): UatContextGitFields {\n return {\n changedFiles: [],\n changedFilesTotal: 0,\n changedFilesOmitted: 0,\n diffStat: null,\n source: { files: 'none' },\n };\n}\n\nexport function assembleUatContextGitFields(\n changedFiles: ChangedFile[],\n diffStat: { stat: string; truncated: boolean },\n): UatContextGitFields {\n if (diffStat.stat === 'Unable to compute diff stat') {\n return emptyUatContextGitFields();\n }\n\n const limitedChangedFiles = changedFiles\n .slice(0, MAX_UAT_CONTEXT_CHANGED_FILES)\n .map((file) => ({\n path: file.path,\n status: file.status,\n additions: file.additions,\n deletions: file.deletions,\n }));\n\n const isDiffStatTruncated = diffStat.truncated || diffStat.stat.length > MAX_UAT_CONTEXT_DIFF_STAT_LENGTH;\n const limitedDiffStat = {\n stat: diffStat.stat.slice(0, MAX_UAT_CONTEXT_DIFF_STAT_LENGTH),\n truncated: isDiffStatTruncated,\n };\n\n return {\n changedFiles: limitedChangedFiles,\n changedFilesTotal: changedFiles.length,\n changedFilesOmitted: Math.max(0, changedFiles.length - limitedChangedFiles.length),\n diffStat: limitedDiffStat,\n source: { files: 'git' },\n };\n}\n\nasync function readWorkspaceUatChangedFiles(workspacePath: string): Promise<UatContextGitFields> {\n if (!existsSync(workspacePath)) return emptyUatContextGitFields();\n\n try {\n const base = await getDiffBase(workspacePath);\n const [changedFiles, diffStat] = await Promise.all([\n getChangedFiles(workspacePath, base),\n getDiffStat(workspacePath, base),\n ]);\n\n return assembleUatContextGitFields(changedFiles, diffStat);\n } catch {\n return emptyUatContextGitFields();\n }\n}\n// ─── Route: GET /api/workspace-stack-health ──────────────────────────────────\n\nconst getWorkspaceStackHealthBatchRoute = HttpRouter.add(\n 'GET',\n '/api/workspace-stack-health',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const url = new URL(request.url, 'http://localhost');\n const issueIds = Array.from(new Set((url.searchParams.get('issueIds') ?? '')\n .split(',')\n .map((id) => id.trim())\n .filter(Boolean)))\n .slice(0, 100);\n\n const parsedIds = issueIds.map((issueId) => ({ issueId, parsed: parseIssueIdSync(issueId) }));\n const invalid = parsedIds.find(({ parsed }) => !parsed);\n if (invalid) {\n return jsonResponse({ error: `Invalid issue ID: ${invalid.issueId}` }, { status: 400 });\n }\n\n const workspaceRequests = parsedIds.map(({ parsed }) => {\n const normalizedIssueId = parsed!.raw.toUpperCase();\n const workspaceMetadata = (() => {\n try {\n return loadWorkspaceMetadataSync(normalizedIssueId);\n } catch {\n return null;\n }\n })();\n if (workspaceMetadata?.location === 'remote') {\n return {\n kind: 'response' as const,\n normalizedIssueId,\n response: { exists: true, issueId: normalizedIssueId, location: 'remote', isRemote: true },\n };\n }\n\n const resolved = resolveProjectFromIssueSync(normalizedIssueId);\n if (!resolved) {\n return {\n kind: 'response' as const,\n normalizedIssueId,\n response: { exists: false, issueId: normalizedIssueId },\n };\n }\n\n const projectConfig = getProjectSync(resolved.projectKey);\n if (!projectConfig) {\n return {\n kind: 'response' as const,\n normalizedIssueId,\n response: { exists: false, issueId: normalizedIssueId },\n };\n }\n const workspacePath = join(\n resolved.projectPath,\n projectConfig.workspace?.workspaces_dir ?? 'workspaces',\n `feature-${parsed!.normalized}`,\n );\n if (!existsSync(workspacePath)) {\n return {\n kind: 'response' as const,\n normalizedIssueId,\n response: { exists: false, issueId: normalizedIssueId },\n };\n }\n\n return {\n kind: 'local' as const,\n normalizedIssueId,\n projectConfig,\n projectPath: resolved.projectPath,\n workspacePath,\n };\n });\n\n const entries = yield* Effect.promise(async () => {\n const containers = workspaceRequests.some((request) =>\n request.kind === 'local' && Boolean(request.projectConfig.workspace?.docker?.compose_template)\n )\n ? await Effect.runPromise(collectDockerContainerLifecycleSnapshot())\n : undefined;\n\n return Promise.all(workspaceRequests.map(async (request) => {\n if (request.kind === 'response') {\n return [request.normalizedIssueId, request.response] as const;\n }\n\n const stackHealth = await Effect.runPromise(getWorkspaceStackHealth(request.normalizedIssueId, {\n projectConfig: { ...request.projectConfig, path: request.projectPath },\n workspacePath: request.workspacePath,\n containers,\n }));\n\n return [request.normalizedIssueId, {\n exists: true,\n issueId: request.normalizedIssueId,\n path: request.workspacePath,\n stackHealth,\n hasDocker: Boolean(request.projectConfig.workspace?.docker?.compose_template),\n location: 'local',\n }] as const;\n }));\n });\n\n return jsonResponse({ workspaces: Object.fromEntries(entries) });\n })),\n);\n// ─── Route: GET /api/workspaces/:issueId ─────────────────────────────────────\n\nconst getWorkspaceRoute = HttpRouter.add(\n 'GET',\n '/api/workspaces/:issueId',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const issueLower = issueId.toLowerCase();\n\n const workspaceInfo = getWorkspaceInfoForIssue(issueId);\n\n if (workspaceInfo.isRemote && workspaceInfo.vmName) {\n return jsonResponse({\n exists: true,\n issueId,\n isRemote: true,\n vmName: workspaceInfo.vmName,\n remotePath: workspaceInfo.remotePath,\n agentId: workspaceInfo.agentId,\n path: `${workspaceInfo.vmName}:${workspaceInfo.remotePath}`,\n location: 'remote',\n message: `Workspace is on remote Fly machine: ${workspaceInfo.vmName}`,\n });\n }\n\n const workspaceName = `feature-${issueLower}`;\n const workspacePath = join(projectPath, 'workspaces', workspaceName);\n\n if (!existsSync(workspacePath)) {\n return jsonResponse({ exists: false, issueId });\n }\n\n const gitFile = join(workspacePath, '.git');\n const apiGit = join(workspacePath, 'api', '.git');\n const feGit = join(workspacePath, 'fe', '.git');\n const srcGit = join(workspacePath, 'src', '.git');\n const devcontainer = join(workspacePath, '.devcontainer');\n const claudeMd = join(workspacePath, 'CLAUDE.md');\n\n const hasValidStructure =\n existsSync(gitFile) ||\n existsSync(apiGit) ||\n existsSync(feGit) ||\n existsSync(srcGit) ||\n existsSync(devcontainer) ||\n existsSync(claudeMd);\n\n if (!hasValidStructure) {\n const location = getWorkspaceLocation(issueId);\n return jsonResponse({\n exists: true,\n corrupted: true,\n issueId,\n path: workspacePath,\n message: 'Workspace exists but is not a valid git worktree or containerized workspace',\n location,\n });\n }\n\n const projectConfig = findProjectByTeamSync(issuePrefix);\n const dnsDomain = projectConfig?.workspace?.dns?.domain || 'localhost';\n const featureFolder = `feature-${issueLower}`;\n\n let frontendUrl = `https://${featureFolder}.${dnsDomain}`;\n let apiUrl = `https://api-${featureFolder}.${dnsDomain}`;\n\n if (projectConfig?.workspace?.dns?.entries) {\n const entries = projectConfig.workspace.dns.entries;\n if (entries[0]) {\n frontendUrl = `https://${entries[0]\n .replace('{{FEATURE_FOLDER}}', featureFolder)\n .replace('{{DOMAIN}}', dnsDomain)}`;\n }\n if (entries[1]) {\n apiUrl = `https://${entries[1]\n .replace('{{FEATURE_FOLDER}}', featureFolder)\n .replace('{{DOMAIN}}', dnsDomain)}`;\n }\n }\n\n let services: { name: string; url?: string }[] = [];\n const panContinueFile = join(workspacePath, PAN_DIRNAME, PAN_CONTINUE_FILENAME);\n const workspaceMd = join(workspacePath, 'WORKSPACE.md');\n const dockerCompose = join(workspacePath, 'docker-compose.yml');\n\n const urlSourceFile = existsSync(panContinueFile)\n ? panContinueFile\n : existsSync(workspaceMd)\n ? workspaceMd\n : null;\n\n if (urlSourceFile) {\n try {\n const content = yield* Effect.promise(() => readFile(urlSourceFile, 'utf-8'));\n const urlMatches = content.matchAll(/(\\w+):\\s*(https?:\\/\\/[^\\s\\n]+)/gi);\n for (const match of urlMatches) {\n services.push({ name: match[1], url: match[2] });\n }\n } catch {}\n }\n\n if (services.length === 0) {\n services = [\n { name: 'Frontend', url: frontendUrl },\n { name: 'API', url: apiUrl },\n ];\n }\n\n const devcontainerPath = join(workspacePath, '.devcontainer');\n let hasDocker =\n existsSync(dockerCompose) ||\n existsSync(join(workspacePath, 'compose.yaml')) ||\n existsSync(join(devcontainerPath, 'docker-compose.yml')) ||\n existsSync(join(devcontainerPath, 'docker-compose.devcontainer.yml')) ||\n existsSync(join(devcontainerPath, 'compose.yaml')) ||\n existsSync(join(devcontainerPath, 'compose.infra.yml')) ||\n existsSync(devcontainerPath);\n\n // For polyrepo workspaces, also check compose files inside sub-repos\n if (!hasDocker && projectConfig?.workspace?.repos) {\n for (const repo of projectConfig.workspace.repos) {\n const repoPath = join(workspacePath, repo.path);\n if (\n existsSync(join(repoPath, 'docker-compose.yml')) ||\n existsSync(join(repoPath, 'docker-compose.yaml')) ||\n existsSync(join(repoPath, 'compose.yaml'))\n ) {\n hasDocker = true;\n break;\n }\n }\n }\n\n const canContainerize = false;\n\n const agentSession = `agent-${issueLower}`;\n const [git, repoGit, containers, stackHealth, mrUrl] = yield* Effect.promise(() => Promise.all([\n getGitStatusAsync(workspacePath),\n getRepoGitStatusAsync(workspacePath),\n hasDocker ? getContainerStatusAsync(issueId, projectPath) : Promise.resolve(null),\n Effect.runPromise(getWorkspaceStackHealth(issueId, { projectConfig, emitTransitionActivity: true })),\n getMrUrlAsync(issueId, workspacePath),\n ]));\n const sessionNames = yield* listSessionNames();\n const paneOutput = yield* capturePane(agentSession, 50).pipe(Effect.orElseSucceed(() => ''));\n\n let hasAgent = false;\n let agentSessionId: string | null = null;\n let agentModel: string | undefined;\n let agentModelFull: string | undefined;\n\n if (sessionNames.includes(agentSession)) {\n hasAgent = true;\n agentSessionId = agentSession;\n\n // Match Anthropic models: [Opus], [Sonnet 4.6], [Haiku 4.5]\n // Also match OpenAI models: [gpt-5.4], [oai@gpt-5.4], [o3], [cx@o3], [o4-mini]\n const modelMatch = paneOutput.match(\n /\\[((?:oai|cx|go)?@?(?:gpt-[0-9.]+(?:-mini|-nano|-pro)?|o[1-4](?:-mini)?(?:-high)?|gemini-[0-9.]+(?:-pro|-flash|-lite)?))[^\\]]*\\]/i\n ) || paneOutput.match(/\\[(Opus|Sonnet|Haiku)[^\\]]*\\]/i);\n agentModel = modelMatch ? modelMatch[1] : undefined;\n\n const fullModel = getActiveSessionModelSync(workspacePath);\n if (fullModel) agentModelFull = fullModel;\n }\n\n const pendingOperation = getPendingOperation(issueId);\n const location = getWorkspaceLocation(issueId);\n const reviewStatus = getReviewStatusSync(issueId);\n\n if (\n pendingOperation?.type === 'merge' &&\n pendingOperation.status === 'failed' &&\n reviewStatus?.mergeStatus !== 'merged'\n ) {\n yield* Effect.promise(() => reconcileGitHubMergeStatus(issueId, reviewStatus));\n }\n\n const stashes = yield* listStashes(workspacePath);\n const salvageableStashes = stashes\n .filter(isSalvageableStash)\n .filter((entry) => entry.issueId === issueId.toUpperCase());\n\n const planPath = yield* findPlan(workspacePath);\n const hasPlan = planPath !== null;\n const planningComplete = hasPlan ? yield* isPlanningComplete(workspacePath) : false;\n const hasBeads = planningComplete;\n\n const issueData = getCostsForIssueSync(issueId);\n const agents = yield* Effect.promise(() => getCachedRunningAgents());\n const resolvedCost = resolveIssueHeadlineCost({\n issueId: issueId,\n aggregateCost: issueData?.totalCost,\n agents,\n });\n\n return jsonResponse({\n exists: true,\n issueId,\n path: workspacePath,\n frontendUrl,\n apiUrl,\n mrUrl,\n hasAgent,\n agentSessionId,\n agentModel,\n agentModelFull,\n git,\n repoGit,\n services,\n containers,\n stackHealth,\n hasDocker,\n canContainerize,\n pendingOperation,\n location,\n salvageableStashes,\n planningState: {\n hasPlan,\n hasBeads,\n beadsCount: 0,\n planningComplete,\n workspacePath,\n },\n costs: issueData\n ? {\n issueId: issueId.toUpperCase(),\n totalCost: issueData.totalCost,\n resolvedTotalCost: resolvedCost.resolvedTotalCost,\n aggregateCost: resolvedCost.aggregateCost,\n liveCost: resolvedCost.liveCost,\n totalTokens: issueData.inputTokens + issueData.outputTokens + issueData.cacheReadTokens + issueData.cacheWriteTokens,\n inputTokens: issueData.inputTokens,\n outputTokens: issueData.outputTokens,\n cacheReadTokens: issueData.cacheReadTokens,\n cacheWriteTokens: issueData.cacheWriteTokens,\n models: issueData.models,\n providers: issueData.providers,\n byModel: Object.fromEntries(\n Object.entries(issueData.models).map(([model, stats]: [string, { cost: number; tokens: number }]) => [\n model,\n { cost: stats.cost, tokens: stats.tokens },\n ])\n ),\n sessions: (issueData as unknown as { sessions?: unknown[] }).sessions ?? [],\n byStage: Object.fromEntries(\n Object.entries(issueData.stages || {}).map(([stage, stats]: [string, { cost: number; tokens: number }]) => [\n stage,\n { cost: stats.cost, tokens: stats.tokens },\n ])\n ),\n budget: issueData.budget,\n budgetWarning: issueData.budgetWarning,\n lastUpdated: issueData.lastUpdated,\n }\n : {\n issueId: issueId.toUpperCase(),\n totalCost: 0,\n resolvedTotalCost: resolvedCost.resolvedTotalCost,\n aggregateCost: resolvedCost.aggregateCost,\n liveCost: resolvedCost.liveCost,\n totalTokens: 0,\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n models: {},\n providers: {},\n byModel: {},\n sessions: [],\n byStage: {},\n budget: undefined,\n budgetWarning: false,\n },\n });\n }))\n);\n// ─── Route: POST /api/workspaces ─────────────────────────────────────────────\n\nconst postWorkspacesRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces',\n httpHandler(Effect.gen(function* () {\n const body = yield* readJsonBody;\n const { issueId, projectId } = body as { issueId?: string; projectId?: string };\n\n if (!issueId) {\n return jsonResponse({ error: 'issueId required' }, { status: 400 });\n }\n\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(projectId, issuePrefix);\n const activityId = spawnPanCommand(\n ['workspace', 'create', issueId],\n `Create workspace for ${issueId}`,\n projectPath\n );\n return jsonResponse({\n success: true,\n message: `Creating workspace for ${issueId}`,\n activityId,\n projectPath,\n });\n }))\n);\nconst getWorkspacePlanRoute = HttpRouter.add(\n 'GET',\n '/api/workspaces/:issueId/plan',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n\n const location = yield* resolvePlanLocation(projectPath, issueId);\n if (!location) {\n return jsonResponse(\n { error: 'No vBRIEF plan found for this workspace' },\n { status: 404 }\n );\n }\n\n const cp = criticalPath(actionableDoc(location.doc));\n return jsonResponse({ ...location.doc, criticalPath: cp, lifecycleDir: location.lifecycleDir });\n }))\n);\nconst getWorkspaceUatContextRoute = HttpRouter.add(\n 'GET',\n '/api/workspaces/:issueId/uat-context',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n const parsed = parseIssueIdSync(issueId);\n if (!parsed) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n\n const issuePrefix = parsed.prefix ?? extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const { parsedIssueId, workspacePath } = getWorkspacePathForIssue(projectPath, issueId);\n\n const location = yield* Effect.promise(() =>\n Effect.runPromise(resolvePlanLocation(projectPath, parsedIssueId)).catch(() => null)\n );\n const planFields = assembleUatContextPlanFields(location?.doc ?? null);\n const gitFields = yield* Effect.promise(() => readWorkspaceUatChangedFiles(workspacePath));\n\n return jsonResponse({\n issueId: parsedIssueId,\n ...planFields,\n changedFiles: gitFields.changedFiles,\n changedFilesTotal: gitFields.changedFilesTotal,\n changedFilesOmitted: gitFields.changedFilesOmitted,\n diffStat: gitFields.diffStat,\n source: {\n plan: location ? 'vbrief' : 'none',\n files: gitFields.source.files,\n },\n });\n }))\n);\nconst patchWorkspacePlanInspectionPolicyRoute = HttpRouter.add(\n 'PATCH',\n '/api/workspaces/:issueId/plan/inspection-policy',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const originError = requireTrustedMutationOrigin(request);\n if (originError) return originError;\n\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n\n const body = yield* readJsonBody;\n const policy = (body as { inspectionPolicy?: unknown }).inspectionPolicy;\n if (!VBRIEF_INSPECTION_POLICIES.includes(policy as VBriefInspectionPolicy)) {\n return jsonResponse({ error: 'Invalid inspection policy' }, { status: 400 });\n }\n\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const location = yield* resolvePlanLocation(projectPath, issueId);\n if (!location) {\n return jsonResponse(\n { error: 'No vBRIEF plan found for this workspace' },\n { status: 404 }\n );\n }\n\n const now = new Date().toISOString();\n const updated: VBriefDocument = {\n ...location.doc,\n vBRIEFInfo: {\n ...location.doc.vBRIEFInfo,\n inspectionPolicy: policy as VBriefInspectionPolicy,\n updated: now,\n },\n plan: {\n ...location.doc.plan,\n updated: now,\n },\n };\n\n yield* Effect.promise(() => writeFile(location.path, JSON.stringify(updated, null, 2) + '\\n', 'utf-8'));\n const cp = criticalPath(updated);\n return jsonResponse({ ...updated, criticalPath: cp, lifecycleDir: location.lifecycleDir });\n }))\n);\n// ─── Route: GET /api/workspaces/:issueId/tldr ─────────────────────────────────\n\nconst getWorkspaceTldrRoute = HttpRouter.add(\n 'GET',\n '/api/workspaces/:issueId/tldr',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n\n return yield* Effect.promise(async () => {\n const projectRoot = process.cwd();\n const workspacePath = join(projectRoot, 'workspaces', `feature-${issueId.toLowerCase()}`);\n const venvPath = join(workspacePath, '.venv');\n\n if (!existsSync(workspacePath)) {\n return jsonResponse({ error: 'Workspace not found' }, { status: 404 });\n }\n\n if (!existsSync(venvPath)) {\n return jsonResponse({\n available: false,\n reason: 'No .venv found in workspace',\n });\n }\n\n const service = getTldrDaemonServiceSync(workspacePath, venvPath);\n const status = await service.getStatus();\n const { fileCount, indexAge, edgeCount } = await getIndexStats(workspacePath);\n\n return jsonResponse({\n available: true,\n running: status.running,\n pid: status.pid,\n healthy: status.healthy,\n workspacePath,\n fileCount,\n indexAge,\n edgeCount,\n });\n })\n }))\n);\n\n// ─── Route: PATCH /api/workspaces/:issueId/tiered-execution ──────────────────\n\nconst patchWorkspaceTieredExecutionRoute = HttpRouter.add(\n 'PATCH',\n '/api/workspaces/:issueId/tiered-execution',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const originError = requireTrustedMutationOrigin(request);\n if (originError) return originError;\n\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: 'Invalid issue ID' }, { status: 400 });\n }\n\n const body = yield* readJsonBody;\n const override = (body as { override?: unknown }).override;\n if (override !== 'on' && override !== 'off' && override !== null && override !== undefined) {\n return jsonResponse({ error: 'Invalid tiered-execution override' }, { status: 400 });\n }\n\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const project = getProjectSync(issuePrefix);\n if (!project) {\n return jsonResponse({ error: 'Project not found' }, { status: 404 });\n }\n\n // Persist via the record write door\n yield* Effect.promise(() =>\n import('../../../../lib/pan-dir/record.js').then(m =>\n m.writeRecordTieredExecutionOverride(project, issueId, override as 'on' | 'off' | null)\n )\n );\n\n // Return the updated computed tieredExecution block (same shape as read door)\n const projectPath = project.path;\n const location = yield* resolvePlanLocation(projectPath, issueId);\n if (!location) {\n return jsonResponse(\n { error: 'No vBRIEF plan found for this workspace' },\n { status: 404 }\n );\n }\n\n // Compute tieredExecution block using the same logic as the read door\n const config = loadConfigSync().config;\n const tieredExecutionConfig = config.tieredExecution;\n const globalEnabled = tieredExecutionConfig?.enabled ?? false;\n const planMetadata = location.doc.plan?.metadata;\n\n // Determine source and effective state with record override precedence\n let source: 'issue-override' | 'plan-metadata' | 'global';\n let effective: boolean;\n\n if (override !== null && override !== undefined) {\n source = 'issue-override';\n effective = override === 'on';\n } else if (planMetadata?.tiered_execution === 'on') {\n source = 'plan-metadata';\n effective = true;\n } else if (planMetadata?.tiered_execution === 'off') {\n source = 'plan-metadata';\n effective = false;\n } else {\n source = 'global';\n effective = globalEnabled;\n }\n\n const tieredExecution = {\n effective,\n source,\n override: (override as 'on' | 'off' | null) ?? null,\n };\n\n return jsonResponse({ ...location.doc, tieredExecution, lifecycleDir: location.lifecycleDir });\n }))\n);\n\nexport const workspaceDataRouteLayer = Layer.mergeAll(\n getWorkspaceStackHealthBatchRoute,\n getWorkspaceRoute,\n postWorkspacesRoute,\n getWorkspacePlanRoute,\n getWorkspaceUatContextRoute,\n patchWorkspacePlanInspectionPolicyRoute,\n patchWorkspaceTieredExecutionRoute,\n getWorkspaceTldrRoute,\n);\n\nexport default workspaceDataRouteLayer;\n","/**\n * Stash-clean route module — extracted from routes/workspaces.ts (B / wave 2, seam 2).\n *\n * Git-stash recovery + corrupted-workspace clean endpoints:\n * GET /api/workspaces/:issueId/stashes\n * POST /api/workspaces/:issueId/stashes/:stashRef/recover\n * DELETE /api/workspaces/:issueId/stashes/:stashRef\n * GET /api/workspaces/:issueId/clean/preview\n * POST /api/workspaces/:issueId/clean\n *\n * Shared singletons (project path, trusted-origin guard, readJsonBody,\n * spawnPanCommand) stay owned by ../workspaces.js and are imported here.\n */\n\nimport { exec } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { Effect, Layer } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\n\nimport { parseIssueIdSync, extractPrefixSync } from '../../../../lib/issue-id.js';\nimport {\n listStashes,\n isSalvageableStash,\n createRecoveryBranchFromStash,\n dropStash,\n} from '../../../../lib/stashes.js';\nimport { getContainersReferencingWorkspacePath } from '../../../../lib/workspace-manager.js';\nimport { DEVCONTAINER_DIRNAME } from '../../../../lib/workspace/devcontainer-renderer.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { httpHandler } from '../http-handler.js';\nimport {\n getProjectPath,\n requireTrustedMutationOrigin,\n readJsonBody,\n spawnPanCommand,\n getWorkspaceInfoForIssue,\n} from '../workspaces.js';\n\nconst execAsync = promisify(exec);\n\nfunction resolveWorkspacePath(issueId: string): string | null {\n const info = getWorkspaceInfoForIssue(issueId);\n if (info.isRemote || !info.localPath) return null;\n return info.localPath;\n}\nconst getWorkspaceStashesRoute = HttpRouter.add(\n 'GET',\n '/api/workspaces/:issueId/stashes',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const workspacePath = resolveWorkspacePath(issueId);\n\n if (!workspacePath || !existsSync(workspacePath)) {\n return jsonResponse({ error: 'Workspace not found' }, { status: 404 });\n }\n\n const stashes = yield* listStashes(workspacePath);\n const salvageableStashes = stashes\n .filter(isSalvageableStash)\n .filter((entry) => entry.issueId === issueId.toUpperCase())\n .map((entry) => ({\n ref: entry.ref,\n stackRef: entry.stackRef,\n issueId: entry.issueId,\n message: entry.message,\n shortDescription: entry.shortDescription,\n createdAt: entry.createdAt?.toISOString(),\n }));\n\n return jsonResponse({ salvageableStashes });\n }))\n);\nconst postWorkspaceRecoverStashRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces/:issueId/stashes/:stashRef/recover',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const originError = requireTrustedMutationOrigin(request);\n if (originError) return originError;\n\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const stashRef = decodeURIComponent(params['stashRef'] ?? '');\n const workspacePath = resolveWorkspacePath(issueId);\n\n if (!workspacePath || !existsSync(workspacePath)) {\n return jsonResponse({ error: 'Workspace not found' }, { status: 404 });\n }\n\n const stashes = yield* listStashes(workspacePath);\n const stash = stashes.find((entry) => entry.ref === stashRef);\n if (!stash || !isSalvageableStash(stash) || stash.issueId !== issueId.toUpperCase()) {\n return jsonResponse({ error: 'Salvageable stash not found for this workspace' }, { status: 404 });\n }\n\n const branchName = yield* createRecoveryBranchFromStash(\n workspacePath,\n stash.ref,\n stash.issueId,\n stash.shortDescription,\n );\n\n return jsonResponse({ success: true, branchName });\n }))\n);\nconst deleteWorkspaceStashRoute = HttpRouter.add(\n 'DELETE',\n '/api/workspaces/:issueId/stashes/:stashRef',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const originError = requireTrustedMutationOrigin(request);\n if (originError) return originError;\n\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const stashRef = decodeURIComponent(params['stashRef'] ?? '');\n const workspacePath = resolveWorkspacePath(issueId);\n\n if (!workspacePath || !existsSync(workspacePath)) {\n return jsonResponse({ error: 'Workspace not found' }, { status: 404 });\n }\n\n const stashes = yield* listStashes(workspacePath);\n const stash = stashes.find((entry) => entry.ref === stashRef);\n if (!stash || !isSalvageableStash(stash) || stash.issueId !== issueId.toUpperCase()) {\n return jsonResponse({ error: 'Salvageable stash not found for this workspace' }, { status: 404 });\n }\n\n yield* dropStash(workspacePath, stash.ref);\n return jsonResponse({ success: true });\n }))\n);\n// ─── Route: GET /api/workspaces/:issueId/clean/preview ───────────────────────\n\nconst getWorkspaceCleanPreviewRoute = HttpRouter.add(\n 'GET',\n '/api/workspaces/:issueId/clean/preview',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const issueLower = issueId.toLowerCase();\n const workspaceName = `feature-${issueLower}`;\n const workspacePath = join(projectPath, 'workspaces', workspaceName);\n\n if (!existsSync(workspacePath)) {\n return jsonResponse({ error: 'Workspace does not exist' }, { status: 404 });\n }\n\n return yield* Effect.promise(async () => {\n const excludeDirs = [\n 'node_modules', 'target', 'dist', 'build', '.git', '__pycache__', '.cache', '.next', 'coverage',\n ];\n const excludePattern = excludeDirs.map(d => `-name \"${d}\" -prune`).join(' -o ');\n const findCmd = `find \"${workspacePath}\" \\\\( ${excludePattern} \\\\) -o -type f -print 2>/dev/null | head -500`;\n const { stdout: filesOutput } = await execAsync(findCmd, {\n encoding: 'utf-8',\n maxBuffer: 10 * 1024 * 1024,\n });\n const files = filesOutput.trim()\n ? filesOutput.trim().split('\\n').map(f => f.replace(workspacePath + '/', ''))\n : [];\n\n let totalSize = '0';\n try {\n const duCmd = `du -sh \"${workspacePath}\" --exclude=node_modules --exclude=target --exclude=dist --exclude=.git 2>/dev/null | cut -f1`;\n const { stdout: sizeOutput } = await execAsync(duCmd, {\n encoding: 'utf-8',\n maxBuffer: 10 * 1024 * 1024,\n });\n totalSize = sizeOutput.trim() || '0';\n } catch {\n totalSize = 'unknown';\n }\n\n const codeFiles = files.filter(f =>\n /\\.(ts|tsx|js|jsx|java|py|rs|go|rb|php|cs|swift|kt)$/.test(f)\n );\n const configFiles = files.filter(\n f => /\\.(json|yaml|yml|toml|xml|env|md)$/.test(f) || f.includes('config')\n );\n const otherFiles = files.filter(f => !codeFiles.includes(f) && !configFiles.includes(f));\n\n let diffAnalysis: {\n modifiedFiles: string[];\n newFiles: string[];\n unchangedFiles: string[];\n comparedAgainst: string;\n error?: string;\n } = { modifiedFiles: [], newFiles: [], unchangedFiles: [], comparedAgainst: 'main' };\n\n try {\n const subrepos: { prefix: string; gitRoot: string }[] = [];\n const possibleSubrepos = ['fe', 'api', 'frontend', 'backend', 'web', 'server'];\n for (const subdir of possibleSubrepos) {\n const subdirPath = join(workspacePath, subdir);\n if (existsSync(join(subdirPath, '.git'))) {\n subrepos.push({ prefix: subdir + '/', gitRoot: subdirPath });\n }\n }\n\n let mainGitRoot: string | null = null;\n const possibleRoots = [projectPath, join(projectPath, '..'), workspacePath];\n for (const root of possibleRoots) {\n if (existsSync(join(root, '.git'))) {\n mainGitRoot = root;\n break;\n }\n }\n\n const filesToCheck = codeFiles.slice(0, 100);\n const reposUsed: string[] = [];\n\n for (const file of filesToCheck) {\n const workspaceFilePath = join(workspacePath, file);\n let gitRoot: string | null = null;\n let relativePath = file;\n\n for (const { prefix, gitRoot: subGitRoot } of subrepos) {\n if (file.startsWith(prefix)) {\n gitRoot = subGitRoot;\n relativePath = file.slice(prefix.length);\n if (!reposUsed.includes(prefix)) reposUsed.push(prefix);\n break;\n }\n }\n\n if (!gitRoot && mainGitRoot) {\n gitRoot = mainGitRoot;\n if (!reposUsed.includes('main')) reposUsed.push('main');\n }\n\n if (!gitRoot) {\n diffAnalysis.newFiles.push(file);\n continue;\n }\n\n try {\n const branchName = `feature/${issueLower}`;\n let compareRef = 'main';\n try {\n await execAsync(`git rev-parse --verify ${branchName} 2>/dev/null`, {\n cwd: gitRoot,\n encoding: 'utf-8',\n maxBuffer: 10 * 1024 * 1024,\n });\n compareRef = branchName;\n } catch {\n try {\n await execAsync(`git rev-parse --verify main 2>/dev/null`, {\n cwd: gitRoot,\n encoding: 'utf-8',\n maxBuffer: 10 * 1024 * 1024,\n });\n } catch {\n compareRef = 'master';\n }\n }\n\n const { stdout: gitContent } = await execAsync(\n `git show ${compareRef}:${relativePath} 2>/dev/null`,\n { cwd: gitRoot, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 }\n );\n const workspaceContent = await readFile(workspaceFilePath, 'utf-8');\n if (gitContent === workspaceContent) {\n diffAnalysis.unchangedFiles.push(file);\n } else {\n diffAnalysis.modifiedFiles.push(file);\n }\n } catch {\n diffAnalysis.newFiles.push(file);\n }\n }\n\n diffAnalysis.comparedAgainst =\n reposUsed.length > 0 ? `${reposUsed.join(', ')} repos (main branch)` : 'main';\n\n if (subrepos.length === 0 && !mainGitRoot) {\n diffAnalysis.error = 'Could not find git repository to compare against';\n }\n } catch (diffError: unknown) {\n diffAnalysis.error = `Diff analysis failed: ${diffError instanceof Error ? diffError.message : String(diffError)}`;\n }\n\n return jsonResponse({\n workspacePath,\n totalSize,\n fileCount: files.length,\n codeFiles: codeFiles.slice(0, 50),\n configFiles: configFiles.slice(0, 30),\n otherFiles: otherFiles.slice(0, 20),\n hasMore: files.length > 100,\n backupPath: join(\n projectPath,\n 'workspaces',\n `.backup-${workspaceName}-${Date.now()}`\n ),\n diffAnalysis,\n });\n });\n }))\n);\n// ─── Route: POST /api/workspaces/:issueId/clean ───────────────────────────────\n\nconst postWorkspaceCleanRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces/:issueId/clean',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const body = yield* readJsonBody;\n const { createBackup } = body as { createBackup?: boolean };\n\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const issueLower = issueId.toLowerCase();\n const workspaceName = `feature-${issueLower}`;\n const workspacePath = join(projectPath, 'workspaces', workspaceName);\n\n if (!existsSync(workspacePath)) {\n return jsonResponse({ error: 'Workspace does not exist' }, { status: 404 });\n }\n\n let backupPath: string | null = null;\n\n if (createBackup) {\n backupPath = join(\n projectPath,\n 'workspaces',\n `.backup-${workspaceName}-${Date.now()}`\n );\n console.log(`Creating backup: ${workspacePath} -> ${backupPath}`);\n yield* Effect.promise(() => execAsync(\n `rsync -a --quiet --exclude=node_modules --exclude=target --exclude=dist --exclude=.git --exclude=__pycache__ --exclude=.cache --exclude=.next --exclude=coverage \"${workspacePath}/\" \"${backupPath}/\"`,\n { encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024 }\n ));\n }\n\n console.log(`Removing corrupted workspace: ${workspacePath}`);\n\n // Guard: never delete workspace while containers still reference its compose path\n const orphanedContainers = yield* Effect.promise(() =>\n getContainersReferencingWorkspacePath(workspacePath)\n );\n if (orphanedContainers.length > 0) {\n return jsonResponse(\n {\n error: `Cannot remove workspace: ${orphanedContainers.length} Docker container(s) still reference compose paths in ${DEVCONTAINER_DIRNAME}/. Stop the containers first.`,\n },\n { status: 409 },\n );\n }\n\n try {\n yield* Effect.promise(() => execAsync(`rm -rf \"${workspacePath}\"`, {\n encoding: 'utf-8',\n maxBuffer: 50 * 1024 * 1024,\n }));\n } catch {\n console.log('Regular rm failed, using Docker to clean up root-owned files...');\n yield* Effect.promise(() => execAsync(\n `docker run --rm -v \"${workspacePath}:/cleanup\" alpine sh -c \"rm -rf /cleanup/* /cleanup/.[!.]* /cleanup/..?* 2>/dev/null || true\"`,\n { encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024 }\n ));\n yield* Effect.promise(() => execAsync(`rmdir \"${workspacePath}\"`, { encoding: 'utf-8' }));\n }\n\n const activityId = spawnPanCommand(\n ['workspace', 'create', issueId],\n `Recreate workspace for ${issueId}`,\n projectPath\n );\n\n return jsonResponse({\n success: true,\n message: createBackup\n ? `Backed up to ${backupPath} and recreating workspace for ${issueId}`\n : `Cleaned corrupted workspace and recreating for ${issueId}`,\n activityId,\n projectPath,\n backupPath,\n });\n }))\n);\n\nexport const stashCleanRouteLayer = Layer.mergeAll(\n getWorkspaceStashesRoute,\n postWorkspaceRecoverStashRoute,\n deleteWorkspaceStashRoute,\n getWorkspaceCleanPreviewRoute,\n postWorkspaceCleanRoute,\n);\n\nexport default stashCleanRouteLayer;\n","/**\n * Review-pipeline route module — extracted from routes/workspaces.ts (B / wave 2, seam 3a).\n *\n * Review dispatch endpoints (start / re-run the review+test pipeline):\n * POST /api/review/:issueId/trigger\n * POST /api/review/:issueId/request\n *\n * The cancel/control routes (reset, purge, abort, pending, unstick,\n * deacon-ignore, auto-merge) live in review-control.ts. Shared singletons\n * (review-status wrapper, pending-ops cluster, project path, readJsonBody,\n * workspace info, flyExecCmd) stay owned by ../workspaces.js.\n */\n\nimport { exec } from 'node:child_process';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { Effect, Layer, Option } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\n\nimport { parseIssueIdSync, extractPrefixSync } from '../../../../lib/issue-id.js';\nimport { resolveProjectFromIssueSync } from '../../../../lib/projects.js';\nimport { isStatePlaneOnlyStatus } from '../../../../lib/state-plane.js';\nimport { EventStoreService } from '../../services/domain-services.js';\nimport { getReviewStatusSync, type ReviewStatus } from '../../../../lib/review-status.js';\nimport { getCachedConflictGateMergeability } from '../../../../lib/cloister/conflict-gate.js';\nimport { restoreTrackedBeadsExport } from '../../../../lib/beads-restore.js';\nimport { transitionIssueToInReview, spawnRun } from '../../../../lib/agents.js';\nimport { runVerificationForIssue } from '../../../../lib/cloister/verification-runner.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { httpHandler } from '../http-handler.js';\nimport {\n getProjectPath,\n readJsonBody,\n getWorkspaceInfoForIssue,\n setReviewStatus,\n setPendingOperation,\n completePendingOperation,\n clearPendingOperation,\n flyExecCmd,\n type WorkspaceInfo,\n} from '../workspaces.js';\n\nconst execAsync = promisify(exec);\nconst MAX_AUTO_REQUEUE = 25;\n\n/** Safe `.message` read for caught values of unknown shape. */\nconst errorMessage = (e: unknown): string | undefined => e instanceof Error ? e.message : undefined;\n\nfunction shouldTreatAsRerun(status: Pick<ReviewStatus, 'readyForMerge' | 'reviewStatus' | 'testStatus' | 'mergeStatus'> | null | undefined): boolean {\n if (!status) return false;\n return status.readyForMerge === true\n || status.reviewStatus === 'passed'\n || status.testStatus === 'passed'\n || status.mergeStatus === 'failed';\n}\n\nexport function getDirtyWorkspaceErrorForReviewRequestStatus(\n status: string,\n workspacePath: string,\n): string | null {\n // STATE-PLANE-COMMIT-POLICY rules 3/6: pipeline-owned state-plane dirt is\n // not agent work, and the path list must come from src/lib/state-plane.ts.\n if (!status.trim() || isStatePlaneOnlyStatus(status)) {\n return null;\n }\n\n return `Workspace has uncommitted changes. Commit the changes, explicitly discard them, or surface them to the operator before requesting review:\\ncd ${workspacePath}\\ngit status`;\n}\n\nasync function getDirtyWorkspaceErrorForReviewRequest(\n workspacePath: string,\n workspaceInfo: WorkspaceInfo,\n): Promise<string | null> {\n try {\n if (!workspaceInfo.isRemote) {\n await Effect.runPromise(restoreTrackedBeadsExport(workspacePath));\n }\n\n const statusCmd = 'git status --porcelain -uno';\n const status = workspaceInfo.isRemote && workspaceInfo.vmName\n ? (await execAsync(\n flyExecCmd(workspaceInfo.vmName, `cd ${workspacePath} && ${statusCmd}`),\n { encoding: 'utf-8', timeout: 30000 },\n )).stdout\n : (await execAsync(statusCmd, { cwd: workspacePath, encoding: 'utf-8' })).stdout;\n\n return getDirtyWorkspaceErrorForReviewRequestStatus(status, workspacePath);\n } catch {\n return null;\n }\n}\n// ─── Route: POST /api/review/:issueId/trigger ─────────────────────────────\n\nconst postWorkspaceReviewRoute = HttpRouter.add(\n 'POST',\n '/api/review/:issueId/trigger',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const request = yield* HttpServerRequest.HttpServerRequest;\n const body = yield* readJsonBody;\n const eventStore = yield* EventStoreService;\n\n const urlOpt = HttpServerRequest.toURL(request);\n const forceReview =\n (Option.isSome(urlOpt) && urlOpt.value.searchParams.get('force') === 'true') ||\n (body as { force?: unknown })?.force === true;\n\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const issueLower = issueId.toLowerCase();\n const numericSuffix = issueLower.replace(/^[a-z]+-/, '');\n // Use numeric-suffix form (feature/1034) as canonical branch name\n const branchName = `feature/${numericSuffix}`;\n\n const workspaceInfo = getWorkspaceInfoForIssue(issueId);\n // Review runs against the local worktree only. Remote (fly.io) workspaces\n // exist for the work phase; the reap flow retires them and materializes\n // the local worktree before review (PAN-1676).\n if (workspaceInfo.isRemote) {\n return jsonResponse({\n error: `${issueId} is executing remotely on ${workspaceInfo.vmName ?? 'a fly machine'} — run 'pan admin remote reap --issue ${issueId}' after the agent finishes, then review.`,\n }, { status: 409 });\n }\n const workspacePath = workspaceInfo.localPath || join(projectPath, 'workspaces', `feature-${numericSuffix}`);\n\n const existingStatus = getReviewStatusSync(issueId);\n\n if (existingStatus?.reviewNotes && ['blocked', 'failed'].includes(existingStatus.reviewStatus || '')) {\n const infraFailurePatterns = [\n 'Failed to send task',\n \"can't find pane\",\n 'Command failed: tmux',\n 'Operation timed out',\n 'specialist.*not running',\n 'specialist.*busy',\n 'legacy specialist wake',\n ];\n const isInfraFailure = infraFailurePatterns.some(pattern =>\n new RegExp(pattern, 'i').test(existingStatus.reviewNotes || '')\n );\n\n if (!isInfraFailure && !forceReview) {\n return jsonResponse({\n success: false,\n alreadyReviewed: true,\n message: `Review already completed with status: ${existingStatus.reviewStatus}`,\n reviewNotes: existingStatus.reviewNotes,\n hint: 'Address the review feedback before requesting another review, or use force=true to override',\n });\n }\n\n console.log(\n `[review] Re-triggering review for ${issueId} (${isInfraFailure ? 'infrastructure failure' : 'forced'})`\n );\n }\n\n if (existingStatus?.reviewStatus === 'passed' && !forceReview) {\n console.log(`[review] Skipping ${issueId}: already passed review`);\n return jsonResponse({\n success: false,\n alreadyReviewed: true,\n message: `Review already passed for ${issueId}`,\n hint: 'Issue already passed review — proceed to testing or merge',\n });\n }\n\n if (existingStatus?.mergeStatus === 'merged') {\n console.log(`[review] Skipping ${issueId}: already merged`);\n return jsonResponse({\n success: false,\n alreadyMerged: true,\n message: `${issueId} is already merged`,\n });\n }\n\n if (!workspaceInfo.exists) {\n return jsonResponse({ error: 'Workspace does not exist' }, { status: 400 });\n }\n\n // Reset review status — keep 'pending' until dispatch succeeds (PAN-511 atomicity fix).\n // reviewStatus is set to 'reviewing' only after the specialist is successfully dispatched\n // or queued, not before. This prevents stuck 'reviewing' state if Cloister crashes mid-dispatch.\n setPendingOperation(issueId, 'review');\n const reviewReset: Record<string, unknown> = {\n reviewStatus: 'pending',\n testStatus: 'pending',\n autoRequeueCount: 0,\n verificationCycleCount: 0,\n verificationStatus: 'pending',\n verificationNotes: undefined,\n };\n if (forceReview) {\n reviewReset.readyForMerge = false;\n reviewReset.mergeStatus = 'pending';\n reviewReset.reviewNotes = undefined;\n reviewReset.testNotes = undefined;\n }\n setReviewStatus(issueId, reviewReset);\n\n // PAN-1765: short-circuit conflict-gated dispatches before responding so the\n // HTTP client gets a 409 with the deferral message instead of a false 200.\n // Use only the synchronous probe cache here: if a fresh cached result says\n // the branch is not mergeable, return 409 immediately. When the cache is\n // absent/stale, fall through to the background block below, which runs the\n // async probe inside spawnReviewRoleForIssue without holding the HTTP response.\n const cachedMergeability = getCachedConflictGateMergeability(issueId);\n if (cachedMergeability === 'conflicts' || cachedMergeability === 'unknown') {\n const message = cachedMergeability === 'conflicts'\n ? `Review deferred: merge conflict with main must be resolved before review dispatch`\n : `Review deferred: mergeability against main could not be verified; deferring review conservatively`;\n setReviewStatus(issueId, { reviewStatus: 'pending', reviewNotes: message });\n completePendingOperation(issueId, message);\n return jsonResponse({\n success: false,\n gated: true,\n message,\n pipeline: 'deferred',\n }, { status: 409 });\n }\n\n // Respond immediately\n // Run pipeline in background\n (async () => {\n try {\n transitionIssueToInReview(issueId, workspacePath).catch((err: unknown) => {\n console.warn(`[review] Could not transition ${issueId} to in_review: ${errorMessage(err)}`);\n });\n\n try {\n await execAsync(`git push origin ${branchName}`, {\n cwd: workspacePath,\n encoding: 'utf-8',\n });\n } catch (pushErr: unknown) {\n console.log(`Feature branch push note: ${errorMessage(pushErr)}`);\n }\n\n\t // Ensure review artifacts exist so review/test agents have stable URLs.\n\t let reviewTargetBranch: string | undefined;\n\t try {\n\t const { createReviewArtifactsForIssue } = await import('../../../../lib/review-artifacts.js');\n\t const artifactResult = await Effect.runPromise(createReviewArtifactsForIssue(issueId, workspacePath));\n\t const primaryArtifact = artifactResult.mergeSet?.repos.find(repo => !!repo.artifactUrl);\n\t reviewTargetBranch = artifactResult.mergeSet?.repos.find(repo => repo.mergeStatus !== 'skipped')?.targetBranch;\n\t if (primaryArtifact?.artifactUrl) {\n\t setReviewStatus(issueId, { prUrl: primaryArtifact.artifactUrl });\n\t console.log(`[review] Review artifact ready for ${issueId}: ${primaryArtifact.artifactUrl}`);\n\t } else {\n\t console.warn(`[review] No review artifact URL available for ${issueId}`);\n\t }\n\t } catch (artifactErr: unknown) {\n\t console.warn(`[review] Review artifact creation failed for ${issueId}: ${errorMessage(artifactErr)}`);\n\t }\n\n try {\n (await Effect.runPromise(eventStore.append({\n type: 'pipeline.verification-started',\n timestamp: new Date().toISOString(),\n payload: { issueId },\n })));\n } catch { /* non-fatal */ }\n\n const verifyOutcome = await Effect.runPromise(runVerificationForIssue(\n issueId,\n workspacePath,\n workspaceInfo,\n 'review'\n ));\n if (verifyOutcome.outcome === 'failed') {\n completePendingOperation(\n issueId,\n `Verification failed at ${verifyOutcome.failedCheck}`\n );\n setReviewStatus(issueId, {\n reviewStatus: 'failed',\n reviewNotes: `Verification failed at ${verifyOutcome.failedCheck}`,\n });\n try {\n (await Effect.runPromise(eventStore.append({\n type: 'pipeline.verification-failed',\n timestamp: new Date().toISOString(),\n payload: { issueId, failedCheck: verifyOutcome.failedCheck },\n })));\n } catch { /* non-fatal */ }\n return;\n }\n if (verifyOutcome.outcome === 'error') {\n completePendingOperation(\n issueId,\n `Verification infrastructure error: ${verifyOutcome.message}`\n );\n setReviewStatus(issueId, {\n reviewStatus: 'failed',\n reviewNotes: `Verification error: ${verifyOutcome.message}`,\n });\n try {\n (await Effect.runPromise(eventStore.append({\n type: 'pipeline.verification-failed',\n timestamp: new Date().toISOString(),\n payload: { issueId, message: verifyOutcome.message },\n })));\n } catch { /* non-fatal */ }\n return;\n }\n\n // PAN-1048 C1/R3: review now runs as the role primitive via spawnRun\n // (loads roles/review.md → Agent tool fans out to code-review-* sub-agents).\n // The wrapper preserves dispatchParallelReview's orchestration concerns\n // (idempotency, feedback archive, status flip,\n // pipeline event) but the review itself is no longer a detached\n // `pan review run` coordinator process.\n const { spawnReviewRoleForIssue } = await import('../../../../lib/cloister/review-agent.js');\n const prUrl = getReviewStatusSync(issueId)?.prUrl;\n const reviewResult = await Effect.runPromise(spawnReviewRoleForIssue({\n issueId,\n branch: branchName,\n workspace: workspacePath,\n prUrl,\n force: forceReview,\n }));\n\n if (!reviewResult.success) {\n if (reviewResult.gated) {\n console.log(`[review] review dispatch deferred for ${issueId}: ${reviewResult.message}`);\n completePendingOperation(issueId, reviewResult.message);\n setReviewStatus(issueId, {\n reviewStatus: 'pending',\n reviewNotes: reviewResult.message,\n });\n return;\n }\n\n console.warn(\n `[review] review dispatch failed: ${reviewResult.message}`\n );\n completePendingOperation(issueId, `Failed to start review: ${reviewResult.message}`);\n setReviewStatus(issueId, {\n reviewStatus: 'pending',\n reviewNotes: reviewResult.message,\n });\n return;\n }\n\n console.log(`[review] Parallel review dispatched for ${issueId}`);\n // PAN-511's \"set 'reviewing' only after dispatch succeeds\" invariant now lives\n // inside spawnReviewRoleForIssue (it writes reviewing + reviewSpawnedAt right\n // before spawning). PAN-2578: do NOT repeat a bare 'reviewing' write here — a\n // fast review agent may have already recorded its verdict by the time this line\n // runs, and the redundant write clobbered PAN-399's BLOCKED verdict.\n completePendingOperation(issueId, null);\n try {\n (await Effect.runPromise(eventStore.append({\n type: 'pipeline.review-started',\n timestamp: new Date().toISOString(),\n payload: { issueId },\n })));\n } catch { /* non-fatal */ }\n } catch (error: unknown) {\n console.error(`[review] Error starting review:`, error);\n completePendingOperation(issueId, errorMessage(error));\n setReviewStatus(issueId, { reviewStatus: 'pending', reviewNotes: errorMessage(error) });\n }\n })();\n\n return jsonResponse({\n success: true,\n message: `Review pipeline starting for ${issueId}`,\n pipeline: 'verification → review → test',\n note: 'Watch the status panel for progress.',\n });\n }))\n);\n// ─── Route: POST /api/review/:issueId/request ─────────────────────\n\nconst postWorkspaceRequestReviewRoute = HttpRouter.add(\n 'POST',\n '/api/review/:issueId/request',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n const parsedIssueId = parseIssueIdSync(issueId);\n if (!parsedIssueId) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const canonicalIssueId = issueId.toUpperCase();\n const request = yield* HttpServerRequest.HttpServerRequest;\n const body = yield* readJsonBody;\n const { message } = body as { message?: string };\n const eventStore = yield* EventStoreService;\n\n const urlOpt = HttpServerRequest.toURL(request);\n const forceReview =\n (Option.isSome(urlOpt) && urlOpt.value.searchParams.get('force') === 'true') ||\n (body as { force?: unknown })?.force === true;\n const nudgeReview =\n (Option.isSome(urlOpt) && urlOpt.value.searchParams.get('nudge') === 'true') ||\n (body as { nudge?: unknown })?.nudge === true;\n\n const existingStatus = getReviewStatusSync(issueId);\n\n if (existingStatus?.mergeStatus === 'merged') {\n console.log(`[request-review] Rejecting ${issueId}: already merged`);\n return jsonResponse({\n success: false,\n alreadyMerged: true,\n message: `${issueId} is already merged. Use Reopen or Reset Reviews first.`,\n });\n }\n\n if (existingStatus?.reviewStatus === 'passed') {\n if (forceReview) {\n console.log(`[request-review] FORCE: full reset requested by operator for ${canonicalIssueId}`);\n } else if (nudgeReview) {\n if (existingStatus.testStatus !== 'passed') {\n return jsonResponse(\n {\n success: false,\n error: 'Cannot nudge — tests have not passed',\n hint: 'Use ?force=true for a full re-review or wait for tests to complete',\n },\n { status: 400 },\n );\n }\n\n console.log(`[request-review] NUDGE: re-emitting test.passed for ${canonicalIssueId} without state reset`);\n yield* Effect.promise(() => Effect.runPromise(eventStore.append({\n type: 'test.passed',\n timestamp: new Date().toISOString(),\n payload: { issueId: canonicalIssueId },\n })));\n return jsonResponse({\n success: true,\n nudged: true,\n message: `Re-emitted test.passed for ${canonicalIssueId}`,\n });\n }\n\n if (forceReview && shouldTreatAsRerun(existingStatus)) {\n const issueLowerRerun = canonicalIssueId.toLowerCase();\n const issuePrefixRerun = extractPrefixSync(canonicalIssueId) ?? canonicalIssueId.split('-')[0];\n const projectPathRerun = getProjectPath(undefined, issuePrefixRerun);\n const wsInfoRerun = getWorkspaceInfoForIssue(canonicalIssueId);\n // Review runs against the local worktree only (PAN-1676) — see the\n // matching guard in /api/review/:issueId/trigger.\n if (wsInfoRerun.isRemote) {\n return jsonResponse({\n success: false,\n message: `${canonicalIssueId} is executing remotely on ${wsInfoRerun.vmName ?? 'a fly machine'} — run 'pan admin remote reap --issue ${canonicalIssueId}' after the agent finishes, then review.`,\n }, { status: 409 });\n }\n const workspacePathRerun = wsInfoRerun.localPath || join(projectPathRerun, 'workspaces', `feature-${issueLowerRerun}`);\n\n if (!wsInfoRerun.isRemote) {\n yield* restoreTrackedBeadsExport(workspacePathRerun);\n }\n const dirtyError = yield* Effect.promise(() => getDirtyWorkspaceErrorForReviewRequest(workspacePathRerun, wsInfoRerun));\n if (dirtyError) {\n console.log(`[request-review] Rejecting ${issueId}: dirty workspace on rerun path`);\n return jsonResponse({ success: false, error: dirtyError }, { status: 400 });\n }\n\n console.log(`[request-review] ${issueId}: forcing full review/test rerun from passed state`);\n setPendingOperation(issueId, 'review');\n setReviewStatus(issueId, {\n reviewStatus: 'pending',\n testStatus: 'pending',\n mergeStatus: 'pending',\n readyForMerge: false,\n autoRequeueCount: 0,\n verificationCycleCount: 0,\n verificationStatus: 'pending',\n verificationNotes: undefined,\n reviewNotes: undefined,\n testNotes: undefined,\n mergeNotes: undefined,\n });\n\n (async () => {\n try {\n // Resolve workspace info locally — outer scope vars (workspacePath, branchName)\n // are declared after the early return below and must not be relied on here.\n const branchNameRerun = `feature/${issueLowerRerun}`;\n\n transitionIssueToInReview(issueId, workspacePathRerun).catch((err: unknown) => {\n console.warn(`[request-review] Could not transition ${issueId} to in_review: ${errorMessage(err)}`);\n });\n\n try {\n await execAsync(`git push origin ${branchNameRerun}`, {\n cwd: workspacePathRerun,\n encoding: 'utf-8',\n });\n } catch (pushErr: unknown) {\n console.log(`[request-review] Feature branch push note: ${errorMessage(pushErr)}`);\n }\n\n const prUrl = getReviewStatusSync(issueId)?.prUrl;\n const { spawnReviewRoleForIssue } = await import('../../../../lib/cloister/review-agent.js');\n const result = await Effect.runPromise(spawnReviewRoleForIssue({\n issueId,\n workspace: workspacePathRerun,\n branch: branchNameRerun,\n prUrl,\n force: true,\n }));\n\n if (result.success) {\n // reviewStatus transitions ('reviewing' → passed/blocked/failed) are\n // managed by the review role itself via /api/review/:id/status.\n console.log(`[request-review] Review role spawned for ${issueId}`);\n } else if (result.gated) {\n console.log(`[request-review] Review deferred for ${issueId}: ${result.message}`);\n setReviewStatus(issueId, { reviewStatus: 'pending', reviewNotes: result.message });\n } else {\n const errorMsg = result.error || result.message || 'Failed to dispatch review';\n console.error(`[request-review] Dispatch failed for ${issueId}: ${errorMsg}`);\n setReviewStatus(issueId, { reviewStatus: 'pending', reviewNotes: errorMsg });\n }\n } catch (error: unknown) {\n console.error(`[request-review] Error:`, error);\n setReviewStatus(issueId, {\n reviewStatus: 'pending',\n reviewNotes: errorMessage(error) || 'Unknown error',\n });\n }\n })();\n\n return jsonResponse({\n success: true,\n rerun: true,\n message: `Re-running review & test pipeline for ${issueId}`,\n });\n }\n\n if (existingStatus.testStatus === 'failed' || existingStatus.testStatus === 'pending' || existingStatus.testStatus === 'dispatch_failed') {\n console.log(\n `[request-review] ${issueId}: review passed but tests ${existingStatus.testStatus} — dispatching test role`\n );\n setReviewStatus(issueId, { testStatus: 'pending' });\n\n try {\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) {\n console.error(\n `[request-review] No project configured for ${issueId} — cannot spawn test role`\n );\n setReviewStatus(issueId, {\n testStatus: 'dispatch_failed',\n testNotes: 'No project configured',\n });\n } else {\n const workspacePath = join(\n resolved.projectPath,\n 'workspaces',\n `feature-${issueId.toLowerCase()}`\n );\n // PAN-1048 R1: spawn the test role via the role primitive instead\n // of the legacy spawnEphemeralSpecialist machinery. Reactive\n // Cloister normally drives this on lifecycle transitions; this\n // path is a manual re-dispatch for already-approved reviews.\n // PAN-2579: set 'testing' only AFTER the spawn succeeds — a pre-set\n // non-terminal status would make spawnRun's warm-idle reaper treat a\n // leftover warm test session as active and refuse the dispatch.\n const { spawnRun } = yield* Effect.promise(() => import('../../../../lib/agents.js'));\n try {\n const testRun = yield* Effect.promise(() => spawnRun(issueId, 'test', {\n workspace: workspacePath,\n }));\n setReviewStatus(issueId, { testStatus: 'testing' });\n console.log(\n `[request-review] Test role spawned for ${issueId} as ${testRun.id}`\n );\n } catch (testErr) {\n const msg = testErr instanceof Error ? testErr.message : String(testErr);\n console.error(\n `[request-review] Test role spawn failed for ${issueId}: ${msg}`\n );\n setReviewStatus(issueId, {\n testStatus: 'dispatch_failed',\n testNotes: `Test dispatch failed: ${msg}`,\n });\n }\n }\n } catch (err: unknown) {\n console.warn(\n `[request-review] Failed to queue test role for ${issueId}: ${errorMessage(err)}`\n );\n }\n return jsonResponse({\n success: true,\n requeued: true,\n message: `Tests re-queued for ${issueId} (review already passed)`,\n });\n }\n console.log(\n `[request-review] ${issueId}: review already passed — returning success no-op`\n );\n return jsonResponse({\n success: true,\n alreadyPassed: true,\n message: `Review already passed for ${issueId}`,\n });\n }\n\n const currentCount = existingStatus?.autoRequeueCount || 0;\n\n if (currentCount >= MAX_AUTO_REQUEUE) {\n console.log(\n `[request-review] Circuit breaker: ${issueId} exceeded max auto-requeues (${currentCount}/${MAX_AUTO_REQUEUE})`\n );\n return jsonResponse(\n {\n success: false,\n error: 'Circuit breaker triggered',\n message: `Maximum automatic re-review requests (${MAX_AUTO_REQUEUE}) exceeded. Human intervention required.`,\n autoRequeueCount: currentCount,\n hint: 'A human must click the Review button to continue.',\n },\n { status: 429 }\n );\n }\n\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const issueLower = issueId.toLowerCase();\n const branchName = `feature/${issueLower}`;\n\n const workspaceInfo = getWorkspaceInfoForIssue(issueId);\n const workspacePath = workspaceInfo.isRemote\n ? workspaceInfo.remotePath!\n : workspaceInfo.localPath || join(projectPath, 'workspaces', `feature-${issueLower}`);\n\n if (!workspaceInfo.exists) {\n return jsonResponse(\n { success: false, error: 'Workspace does not exist' },\n { status: 400 }\n );\n }\n\n if (!workspaceInfo.isRemote) {\n yield* restoreTrackedBeadsExport(workspacePath);\n }\n\n const dirtyWorkspaceError = yield* Effect.promise(() => getDirtyWorkspaceErrorForReviewRequest(workspacePath, workspaceInfo));\n if (dirtyWorkspaceError) {\n return jsonResponse(\n { success: false, error: dirtyWorkspaceError },\n { status: 400 }\n );\n }\n\n transitionIssueToInReview(issueId, workspacePath).catch((err: unknown) => {\n console.warn(\n `[request-review] Could not transition ${issueId} to in_review: ${errorMessage(err)}`\n );\n });\n\n const newCount = currentCount + 1;\n const reviewNotes = message\n ? `Agent re-review request (${newCount}/${MAX_AUTO_REQUEUE}): ${message}`\n : undefined;\n\n const reqVerifyOutcome = yield* runVerificationForIssue(\n issueId,\n workspacePath,\n workspaceInfo,\n 'request-review'\n );\n if (reqVerifyOutcome.outcome === 'failed') {\n return jsonResponse({\n success: false,\n verificationFailed: true,\n failedCheck: reqVerifyOutcome.failedCheck,\n message: `Verification failed at ${reqVerifyOutcome.failedCheck} — fix and resubmit`,\n cycleCount: reqVerifyOutcome.cycleCount,\n maxCycles: reqVerifyOutcome.maxCycles,\n });\n }\n if (reqVerifyOutcome.outcome === 'error') {\n return jsonResponse(\n {\n success: false,\n error: `Verification infrastructure error: ${reqVerifyOutcome.message}`,\n autoRequeueCount: currentCount,\n },\n { status: 500 }\n );\n }\n\n // PAN-511: set metadata fields but keep reviewStatus='pending' until dispatch succeeds.\n // reviewStatus is set to 'reviewing' only after specialist is dispatched or queued.\n setReviewStatus(issueId, {\n reviewStatus: 'pending',\n testStatus: 'pending',\n autoRequeueCount: newCount,\n reviewNotes,\n });\n\n console.log(\n `[request-review] Agent requested re-review for ${issueId} (${newCount}/${MAX_AUTO_REQUEUE})${workspaceInfo.isRemote ? ` (remote: ${workspaceInfo.vmName})` : ''}`\n );\n\n try {\n const resolved = resolveProjectFromIssueSync(issueId);\n\n if (!resolved) {\n return jsonResponse(\n {\n success: false,\n error: `No project configured for ${issueId}. Add it to projects.yaml.`,\n autoRequeueCount: newCount,\n },\n { status: 500 }\n );\n }\n\n const result = yield* Effect.promise(async () => {\n const { spawnReviewRoleForIssue } = await import('../../../../lib/cloister/review-agent.js');\n return (await Effect.runPromise(spawnReviewRoleForIssue({\n issueId,\n workspace: workspacePath,\n branch: branchName,\n force: true,\n })));\n });\n\n if (result.success) {\n console.log(`[request-review] Review role spawned for ${issueId}`);\n // spawnReviewRoleForIssue already flips reviewStatus to 'reviewing' (with\n // reviewSpawnedAt) internally. PAN-2578: do not repeat a bare 'reviewing' write —\n // it can clobber a fast agent's terminal verdict. Only record the requeue count.\n setReviewStatus(issueId, { autoRequeueCount: newCount });\n yield* Effect.promise(() => Effect.runPromise(eventStore.append({\n type: 'pipeline.review-started',\n timestamp: new Date().toISOString(),\n payload: { issueId },\n })));\n return jsonResponse({\n success: true,\n queued: false,\n message: `Review started (${newCount}/${MAX_AUTO_REQUEUE} auto-requeues used)`,\n autoRequeueCount: newCount,\n remainingRequeues: MAX_AUTO_REQUEUE - newCount,\n });\n } else if (result.gated) {\n console.log(`[request-review] Review deferred for ${issueId}: ${result.message}`);\n setReviewStatus(issueId, {\n reviewStatus: 'pending',\n reviewNotes: result.message,\n autoRequeueCount: currentCount,\n });\n return jsonResponse(\n {\n success: false,\n gated: true,\n message: result.message,\n autoRequeueCount: currentCount,\n remainingRequeues: MAX_AUTO_REQUEUE - currentCount,\n },\n { status: 409 }\n );\n } else {\n console.warn(\n `[request-review] Dispatch failed for ${issueId}: ${result.error}`\n );\n setReviewStatus(issueId, {\n reviewStatus: 'pending',\n reviewNotes: `Dispatch failed: ${result.error || result.message}`,\n autoRequeueCount: currentCount,\n });\n return jsonResponse(\n {\n success: false,\n error: result.error || 'Failed to dispatch review',\n autoRequeueCount: currentCount,\n remainingRequeues: MAX_AUTO_REQUEUE - currentCount,\n },\n { status: 500 }\n );\n }\n } catch (error: unknown) {\n console.error(`[request-review] Error:`, error);\n setReviewStatus(issueId, {\n reviewStatus: 'pending',\n reviewNotes: `Dispatch error: ${errorMessage(error)}`,\n });\n return jsonResponse(\n { success: false, error: errorMessage(error), autoRequeueCount: newCount },\n { status: 500 }\n );\n }\n }))\n);\n\nexport const reviewPipelineRouteLayer = Layer.mergeAll(\n postWorkspaceReviewRoute,\n postWorkspaceRequestReviewRoute,\n);\n\nexport default reviewPipelineRouteLayer;\n","/**\n * Review-control route module — extracted from routes/workspaces.ts (B / wave 2, seam 3b).\n *\n * Operator control endpoints over the review/worker lifecycle:\n * POST /api/review/:issueId/reset\n * POST /api/review/:issueId/purge\n * POST /api/review/:issueId/abort\n * DELETE /api/review/:issueId/pending\n * POST /api/workspaces/:issueId/unstick\n * POST /api/workspaces/:issueId/deacon-ignore\n * POST /api/workspaces/:issueId/auto-merge\n *\n * The dispatch routes (trigger, request) live in review-pipeline.ts. Shared\n * singletons (review-status wrapper, project path, readJsonBody, workspace info)\n * stay owned by ../workspaces.js.\n */\n\nimport { exec } from 'node:child_process';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { Effect, Layer } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\n\nimport { parseIssueIdSync, extractPrefixSync } from '../../../../lib/issue-id.js';\nimport { findWorkspacePath } from '../../../../lib/lifecycle/archive-planning.js';\nimport { resolveProjectFromIssueSync } from '../../../../lib/projects.js';\nimport {\n getReviewStatusSync,\n setReviewStatusSync as setReviewStatusBase,\n markWorkspaceStuck,\n setDeaconIgnored,\n setAutoMerge,\n} from '../../../../lib/review-status.js';\nimport { getAgentRuntimeStateSync } from '../../../../lib/agents.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { httpHandler } from '../http-handler.js';\nimport {\n getProjectPath,\n readJsonBody,\n getWorkspaceInfoForIssue,\n setReviewStatus,\n requireTrustedMutationOrigin,\n} from '../workspaces.js';\n\nconst execAsync = promisify(exec);\n\nexport type ResetReviewResult =\n | { httpStatus: 400; body: { success: false; error: string } }\n | {\n httpStatus: 200;\n body: {\n success: true;\n /** Work-agent runtime state observed before the reset — informational, logged for debugging. */\n preservedResolution?: { agentId: string; resolution?: string; resolutionCount?: number };\n };\n };\n\n/**\n * Core logic for POST /api/review/:issueId/reset (synchronous, testable).\n *\n * Resets the specialist pipeline state (review / test / merge / verification) for\n * a workspace. Writes ONE setReviewStatus() call, reads the work-agent's runtime\n * state purely for logging, and returns a structured result that the route\n * handler maps to an HTTP response.\n *\n * CRITICAL — resolution preservation:\n * This function deliberately does NOT mutate the work-agent's runtime state\n * (resolution / resolutionCount / activity). `resolution` tracks the WORK\n * agent's own lifecycle (working/done/unclear/stuck/needs_input) and is written\n * exclusively by `work-agent-stop-hook` based on the agent's tail. The pipeline\n * reset is about specialist state — wiping resolution here previously erased\n * legitimate unclear/stuck counts when `pan done`'s self-heal path triggered a\n * reset, which prevented the deacon from noticing genuinely confused agents.\n * (Root cause of PAN-805 never escalating to stuck.)\n *\n * Regression test: tests/unit/dashboard/server/routes/reset-review-route.test.ts\n *\n * Exported so a unit test can assert the sync mutation set is exactly\n * {reset review status} — nothing else.\n */\nexport function processResetReviewPipeline(\n issueId: string,\n workspaceExists: boolean,\n): ResetReviewResult {\n if (!workspaceExists) {\n return {\n httpStatus: 400,\n body: { success: false, error: 'Workspace does not exist' },\n };\n }\n\n const agentId = `agent-${issueId.toLowerCase()}`;\n const priorRuntime = getAgentRuntimeStateSync(agentId);\n\n console.log(\n `[reset-review] Human-initiated pipeline reset for ${issueId} ` +\n `(work-agent ${agentId} resolution=${priorRuntime?.resolution ?? 'none'}/` +\n `${priorRuntime?.resolutionCount ?? 0} — preserved, not reset)`\n );\n\n setReviewStatus(issueId, {\n reviewStatus: 'pending',\n testStatus: 'pending',\n mergeStatus: 'pending',\n reviewNotes: undefined,\n testNotes: undefined,\n mergeNotes: undefined,\n readyForMerge: false,\n autoRequeueCount: 0,\n verificationStatus: 'pending',\n verificationNotes: undefined,\n verificationCycleCount: 0,\n // A human-initiated reset is an explicit circuit-breaker override: clear\n // the stuck marker and the review/test retry counters too. Without this,\n // a workspace stuck on review_infrastructure_failure (or with exhausted\n // retry budgets) is reset to `pending` but immediately re-skipped by the\n // deacon's stuck guard / retry-budget checks — the \"override\" is a no-op.\n stuck: false,\n stuckReason: undefined,\n stuckAt: undefined,\n stuckDetails: undefined,\n reviewRetryCount: 0,\n testRetryCount: 0,\n mergeRetryCount: 0,\n recoveryStartedAt: undefined,\n });\n\n return {\n httpStatus: 200,\n body: {\n success: true,\n preservedResolution: priorRuntime\n ? {\n agentId,\n resolution: priorRuntime.resolution,\n resolutionCount: priorRuntime.resolutionCount,\n }\n : undefined,\n },\n };\n}\n\n/**\n * Resolve whether a workspace exists for the reset endpoint, including\n * strike workspaces (feature-<id>-strike) that getWorkspaceInfoForIssue does\n * not yet know about. PAN-2270 regression test hook.\n */\nexport function resolveResetWorkspace(\n issueId: string,\n workspaceInfo: { exists: boolean; localPath?: string },\n resolved: { projectPath: string } | null,\n): { exists: boolean; localPath: string | null } {\n const issueLower = issueId.toLowerCase();\n const workspacePath = resolved ? findWorkspacePath(resolved.projectPath, issueLower) : null;\n return {\n exists: workspaceInfo.exists || workspacePath !== null,\n localPath: workspaceInfo.localPath || workspacePath,\n };\n}\n\n/**\n * Build the workspace/branch pair for a review re-dispatch, handling\n * strike workspaces (feature-<id>-strike -> strike/<id>) and preserving the\n * existing feature/<numeric> convention for non-strike workspaces.\n * PAN-2270 regression test hook.\n */\nexport function buildReviewRedispatchArgs(\n issueId: string,\n resetWorkspace: { localPath: string | null },\n workspaceInfo: { localPath?: string },\n resolved: { projectPath: string } | null,\n): { workspace: string; branch: string } | null {\n if (!resolved) return null;\n const issueLower = issueId.toLowerCase();\n const numericSuffix = issueLower.replace(/^[a-z]+-/, '');\n const wsPath =\n resetWorkspace.localPath ||\n workspaceInfo.localPath ||\n findWorkspacePath(resolved.projectPath, issueLower) ||\n join(resolved.projectPath, 'workspaces', `feature-${numericSuffix}`);\n const branchName = wsPath.endsWith('-strike')\n ? `strike/${issueLower}`\n : `feature/${numericSuffix}`;\n return { workspace: wsPath, branch: branchName };\n}\nexport type UnstickResult =\n | { httpStatus: 404; body: { success: false; error: string } }\n | { httpStatus: 400; body: { success: false; error: string } }\n | { httpStatus: 409; body: { success: false; error: string } }\n | { httpStatus: 200; body: { success: true; issueId: string; previousReason?: string } };\n\nexport type UnstickGitRepairState =\n | { safe: true }\n | { safe: false; advice: string };\n\n/**\n * Core logic for POST /api/workspaces/:issueId/unstick.\n *\n * Validates preconditions (workspace exists, workspace is stuck, git state is\n * repaired), clears the persistent stuck marker, and invalidates stale\n * review/test results by resetting the lifecycle to pending.\n *\n * The recovery path changes the project's main-branch state by committing,\n * pushing, fast-forwarding, or otherwise reconciling it. Keeping\n * reviewStatus=passed after that would let the UI present a stale approval.\n * One atomic setReviewStatus() call clears stuck state and resets the\n * lifecycle in a single DB write and a single notifyPipeline event.\n *\n * gitRepairState must be pre-verified by the caller (async git check). An\n * unsafe result returns 409 with recovery instructions before any DB mutation.\n *\n * Exported for unit testing — the route handler calls this and maps the result\n * directly to an HTTP response.\n */\nexport function processUnstickRequest(\n issueId: string,\n workspaceExists: boolean,\n currentStatus: ReturnType<typeof getReviewStatusSync>,\n gitRepairState: UnstickGitRepairState,\n): UnstickResult {\n if (!workspaceExists) {\n return { httpStatus: 404, body: { success: false, error: 'Workspace does not exist' } };\n }\n if (!currentStatus?.stuck) {\n return { httpStatus: 400, body: { success: false, error: `Workspace ${issueId} is not stuck` } };\n }\n // Enforce that the operator has actually repaired the git state before we\n // clear the stuck flag. If project main still has local-only commits, remote\n // commits, or uncommitted changes, Deacon would immediately re-enter the same\n // broken approve/merge path.\n if (!gitRepairState.safe) {\n return {\n httpStatus: 409,\n body: {\n success: false,\n error: `Workspace git state is not yet repaired. ${gitRepairState.advice} Then retry Unstick.`,\n },\n };\n }\n // Single atomic write: clear stuck fields and reset lifecycle to pending.\n setReviewStatusBase(issueId, {\n reviewStatus: 'pending',\n testStatus: 'pending',\n mergeStatus: 'pending',\n readyForMerge: false,\n stuck: undefined,\n stuckReason: undefined,\n stuckAt: undefined,\n stuckDetails: undefined,\n reviewedAtCommit: undefined,\n // PAN-794: unstick opens a fresh recovery cycle — arm the breaker budget\n // again so legitimate transient failures don't inherit prior cycle counts.\n reviewRetryCount: 0,\n recoveryStartedAt: undefined,\n });\n console.log(`[unstick] Cleared stuck flag and reset lifecycle for ${issueId} (was: ${currentStatus.stuckReason ?? 'unknown'})`);\n return { httpStatus: 200, body: { success: true, issueId, previousReason: currentStatus.stuckReason } };\n}\n\nfunction buildUnstickRepairAdvice(aheadCount: number, behindCount: number, dirty: boolean): string | null {\n const steps: string[] = [];\n if (dirty) {\n steps.push('commit the project repo changes that should be preserved, or explicitly discard only changes known to be disposable');\n }\n if (aheadCount > 0 && behindCount > 0) {\n steps.push(`reconcile local main with origin/main while preserving the ${aheadCount} local commit(s), then push the reconciled main`);\n } else if (aheadCount > 0) {\n steps.push(`push the ${aheadCount} local main commit(s) to origin/main`);\n } else if (behindCount > 0) {\n steps.push(`fast-forward local main from origin/main (${behindCount} commit(s) behind)`);\n }\n return steps.length > 0 ? `In the project repo, ${steps.join('; ')}.` : null;\n}\n\n/**\n * Check whether the project repo's local main branch matches origin/main and\n * has no uncommitted changes. Unsafe states return recoverable instructions:\n * push local-only commits, commit dirty work, fast-forward behind branches, or\n * reconcile a true divergence. Never prescribe destructive reset.\n *\n * Returns safe for any git error so a transient failure doesn't permanently\n * block unstick.\n */\nasync function checkProjectGitRepairState(projectPath: string): Promise<UnstickGitRepairState> {\n try {\n const [divergence, status] = await Promise.all([\n execAsync(\n 'git rev-list --left-right --count main...origin/main',\n { cwd: projectPath, encoding: 'utf-8', timeout: 5000 }\n ),\n execAsync(\n 'git status --porcelain',\n { cwd: projectPath, encoding: 'utf-8', timeout: 5000 }\n ),\n ]);\n const [aheadRaw = '0', behindRaw = '0'] = divergence.stdout.trim().split(/\\s+/);\n const aheadCount = parseInt(aheadRaw, 10) || 0;\n const behindCount = parseInt(behindRaw, 10) || 0;\n const dirty = status.stdout.trim().length > 0;\n const advice = buildUnstickRepairAdvice(aheadCount, behindCount, dirty);\n return advice ? { safe: false, advice } : { safe: true };\n } catch {\n // If we can't check (no git repo, no origin/main), don't block the operator.\n return { safe: true };\n }\n}\n\nexport const __testInternals = {\n buildUnstickRepairAdvice,\n};\nconst postWorkspaceResetReviewRoute = HttpRouter.add(\n 'POST',\n '/api/review/:issueId/reset',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const body = yield* readJsonBody;\n\n const workspaceInfo = getWorkspaceInfoForIssue(issueId);\n const resolved = resolveProjectFromIssueSync(issueId);\n const resetWorkspace = resolveResetWorkspace(issueId, workspaceInfo, resolved);\n const result = processResetReviewPipeline(issueId, resetWorkspace.exists);\n if (result.httpStatus !== 200) {\n return jsonResponse(result.body, { status: result.httpStatus });\n }\n\n try {\n const { resetPostMergeState } = yield* Effect.promise(() => import(\n '../../../../lib/cloister/merge-agent.js'\n ));\n resetPostMergeState(issueId);\n } catch (err) {\n console.warn(`[reset-review] resetPostMergeState best-effort failed for ${issueId}:`, err);\n }\n\n console.log(\n `[reset-review] Pipeline state reset for ${issueId} — awaiting agent to request review`\n );\n\n const rerun = (body as { rerun?: unknown })?.rerun === true;\n if (rerun) {\n try {\n yield* Effect.promise(async () => {\n const { spawnReviewRoleForIssue } = await import('../../../../lib/cloister/review-agent.js');\n const dispatchArgs = buildReviewRedispatchArgs(issueId, resetWorkspace, workspaceInfo, resolved);\n if (dispatchArgs) {\n const result = await Effect.runPromise(spawnReviewRoleForIssue({\n issueId,\n workspace: dispatchArgs.workspace,\n branch: dispatchArgs.branch,\n prUrl: getReviewStatusSync(issueId)?.prUrl,\n }));\n\n if (result.success) {\n // spawnReviewRoleForIssue already set 'reviewing' + reviewSpawnedAt.\n // PAN-2578: no bare 'reviewing' repeat — it can clobber a fast verdict.\n console.log(`[reset-review] Re-dispatched review for ${issueId}`);\n } else {\n console.warn(\n `[reset-review] Re-dispatch failed for ${issueId}: ${result.message || result.error}`\n );\n setReviewStatus(issueId, { reviewStatus: 'pending' });\n }\n } else {\n console.warn(\n `[reset-review] Could not resolve project for ${issueId}, skipping re-dispatch`\n );\n }\n });\n } catch (rerunErr) {\n console.warn(`[reset-review] Re-dispatch error for ${issueId}: ${rerunErr}`);\n setReviewStatus(issueId, { reviewStatus: 'pending' });\n }\n }\n\n return jsonResponse({\n success: true,\n message: rerun\n ? `Pipeline reset and review re-dispatched for ${issueId}.`\n : `Review cycles reset for ${issueId}. Agent can now request review when ready.`,\n rerun,\n });\n }))\n);\n// ─── Route: POST /api/review/:issueId/purge ────────────────────────────────\n//\n// COMPLETE review reset. Tears down the issue's entire review fleet — the\n// agent-<id>-review parent PLUS any leftover extended-review (convoy) sub-reviewers\n// (-correctness/-security/-performance/-requirements) — by killing their tmux sessions\n// and removing each agent via removeAgentSync (overdeck.db row + state dir, never the\n// JSONL transcript), then resets review_status (the pipeline verdict block re-derives\n// from it). Use this to clear stale review ghosts left by a prior cycle so a fresh\n// review runs clean. Destructive to review-agent state only; confirmed via a dialog.\n\nconst postWorkspaceReviewPurgeRoute = HttpRouter.add(\n 'POST',\n '/api/review/:issueId/purge',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: 'Invalid issue ID' }, { status: 400 });\n }\n\n const { purgeReviewAgentsForIssue } = yield* Effect.promise(\n () => import('../../../../lib/cloister/review-agent.js'),\n );\n const projectKey = resolveProjectFromIssueSync(issueId)?.projectKey;\n const purge = yield* Effect.promise(() => purgeReviewAgentsForIssue(projectKey, issueId));\n\n // Reset review_status (pipeline verdict block re-derives from it). Only meaningful\n // when the workspace still exists; ghost removal above runs regardless.\n const workspaceInfo = getWorkspaceInfoForIssue(issueId);\n let reviewStatusReset = false;\n if (workspaceInfo.exists) {\n processResetReviewPipeline(issueId, true);\n reviewStatusReset = true;\n }\n\n console.log(\n `[review-purge] ${issueId}: removed=[${purge.removed.join(', ')}] ` +\n `killed=[${purge.killed.join(', ')}] reviewStatusReset=${reviewStatusReset}`,\n );\n\n return jsonResponse({\n success: true,\n issueId,\n removed: purge.removed,\n killed: purge.killed,\n reviewStatusReset,\n message: `Purged ${purge.removed.length} review agent(s) for ${issueId}.`,\n });\n })),\n);\n// ─── Route: POST /api/review/:issueId/abort ────────────────────────────────\n//\n// Kill all running reviewer tmux sessions for an issue and reset reviewStatus\n// to 'pending'. Does NOT message the work agent — leaves the worker idle.\n// Use this to stop a runaway or stuck review without triggering a resubmit.\n\nconst postWorkspaceAbortReviewRoute = HttpRouter.add(\n 'POST',\n '/api/review/:issueId/abort',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = (params['issueId'] ?? '').toUpperCase();\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n if (!issueId) {\n return jsonResponse({ success: false, error: 'Missing issueId' }, { status: 400 });\n }\n\n const workspaceInfo = getWorkspaceInfoForIssue(issueId);\n if (!workspaceInfo.exists) {\n return jsonResponse({ success: false, error: 'Workspace does not exist' }, { status: 400 });\n }\n\n const { resolveProjectFromIssueSync } = yield* Effect.promise(() =>\n import('../../../../lib/projects.js'),\n );\n const { killAllReviewerSessions } = yield* Effect.promise(() =>\n import('../../../../lib/cloister/review-agent.js'),\n );\n const resolved = resolveProjectFromIssueSync(issueId);\n const { killed, failed } = yield* killAllReviewerSessions(resolved?.projectKey, issueId);\n\n // Reset only reviewStatus — leave test/merge/verification untouched\n setReviewStatus(issueId, {\n reviewStatus: 'pending',\n reviewNotes: undefined,\n });\n\n console.log(\n `[abort-review] Aborted ${killed.length} reviewer session(s) for ${issueId}` +\n (failed.length ? ` (${failed.length} kill failed)` : '')\n );\n\n return jsonResponse({\n success: true,\n message: `Aborted ${killed.length} reviewer session(s) for ${issueId}. Worker left idle.`,\n killed,\n failed,\n });\n }))\n);\nconst postWorkspaceUnstickRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces/:issueId/unstick',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n\n const workspaceInfo = getWorkspaceInfoForIssue(issueId);\n const current = getReviewStatusSync(issueId);\n\n // Pre-verify git state before mutating stuck flag.\n // For main_diverged: check that local main is not ahead of origin/main.\n // PAN-794: review_infrastructure_failure is unrelated to git divergence —\n // skip the git safe-state check so operators can unstick review-infra\n // workspaces without touching the project's main branch.\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const skipGitCheck = current?.stuckReason === 'review_infrastructure_failure';\n const gitRepairState = skipGitCheck\n ? { safe: true } as const\n : yield* Effect.promise(() => checkProjectGitRepairState(projectPath));\n\n const result = processUnstickRequest(issueId, workspaceInfo.exists, current, gitRepairState);\n return jsonResponse(result.body, result.httpStatus !== 200 ? { status: result.httpStatus } : undefined);\n }))\n);\nconst postWorkspaceDeaconIgnoreRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces/:issueId/deacon-ignore',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = (params['issueId'] ?? '').toUpperCase();\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n if (!issueId) {\n return jsonResponse({ success: false, error: 'Missing issueId' }, { status: 400 });\n }\n\n const body = (yield* readJsonBody) as { ignored?: unknown; reason?: unknown };\n if (typeof body.ignored !== 'boolean') {\n return jsonResponse(\n { success: false, error: 'Body must include { ignored: boolean }' },\n { status: 400 },\n );\n }\n const reason = typeof body.reason === 'string' && body.reason.trim().length > 0\n ? body.reason.trim()\n : undefined;\n\n setDeaconIgnored(issueId, body.ignored, reason);\n const updated = getReviewStatusSync(issueId);\n return jsonResponse({\n success: true,\n issueId,\n deaconIgnored: updated?.deaconIgnored ?? body.ignored,\n deaconIgnoredAt: updated?.deaconIgnoredAt,\n deaconIgnoredReason: updated?.deaconIgnoredReason,\n });\n }))\n);\nconst postWorkspaceAutoMergeRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces/:issueId/auto-merge',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = (params['issueId'] ?? '').toUpperCase();\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n\n const body = (yield* readJsonBody) as { autoMerge?: unknown };\n if (typeof body.autoMerge !== 'boolean' && body.autoMerge !== null) {\n return jsonResponse(\n { success: false, error: 'Body must include { autoMerge: boolean | null }' },\n { status: 400 },\n );\n }\n\n setAutoMerge(issueId, body.autoMerge);\n const updated = getReviewStatusSync(issueId);\n return jsonResponse({\n success: true,\n issueId,\n autoMerge: updated?.autoMerge ?? null,\n });\n }))\n);\n// ─── Route: DELETE /api/review/:issueId/pending ──────────────────────────\n\nconst deleteWorkspacePendingRoute = HttpRouter.add(\n 'DELETE',\n '/api/review/:issueId/pending',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n clearPendingOperation(issueId);\n return jsonResponse({ success: true });\n }))\n);\n\n\n// ─── Route: GET/POST /api/review/:issueId/config (PAN-1874) ─────────────────\n//\n// Per-issue review configuration override: reviewMode (quick|full|none) and\n// reReviewScope (all|changed|blockers), persisted on the per-issue record —\n// the same override `pan review mode` / `pan review scope` write. Resolution\n// order at review-spawn time: per-issue record → per-project roles.review →\n// global config (resolveReviewMode / resolveReReviewScope). Passing null for\n// a field clears the override back to config resolution.\nconst getReviewConfigRoute = HttpRouter.add(\n 'GET',\n '/api/review/:issueId/config',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = (params['issueId'] ?? '').toUpperCase();\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const result = yield* Effect.promise(async () => {\n const { readIssueRecordSync, resolveProjectForIssue } = await import('../../../../lib/pan-dir/record.js');\n const { resolveReviewMode, resolveReReviewScope } = await import('../../../../lib/cloister/review-agent.js');\n const project = resolveProjectForIssue(issueId);\n const record = project ? readIssueRecordSync(project, issueId) : null;\n return {\n issueId,\n override: { reviewMode: record?.reviewMode ?? null, reReviewScope: record?.reReviewScope ?? null },\n resolved: { reviewMode: resolveReviewMode(issueId), reReviewScope: resolveReReviewScope(issueId) },\n };\n });\n return jsonResponse(result);\n }))\n);\n\nconst postReviewConfigRoute = HttpRouter.add(\n 'POST',\n '/api/review/:issueId/config',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = (params['issueId'] ?? '').toUpperCase();\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const body = (yield* readJsonBody) as { reviewMode?: string | null; reReviewScope?: string | null };\n const hasMode = Object.prototype.hasOwnProperty.call(body ?? {}, 'reviewMode');\n const hasScope = Object.prototype.hasOwnProperty.call(body ?? {}, 'reReviewScope');\n if (!hasMode && !hasScope) {\n return jsonResponse({ error: 'Provide reviewMode and/or reReviewScope (null clears the override)' }, { status: 400 });\n }\n if (hasMode && body.reviewMode !== null && body.reviewMode !== 'quick' && body.reviewMode !== 'full' && body.reviewMode !== 'none') {\n return jsonResponse({ error: \"reviewMode must be quick, full, none, or null\" }, { status: 400 });\n }\n if (hasScope && body.reReviewScope !== null && body.reReviewScope !== 'all' && body.reReviewScope !== 'changed' && body.reReviewScope !== 'blockers') {\n return jsonResponse({ error: \"reReviewScope must be all, changed, blockers, or null\" }, { status: 400 });\n }\n const result = yield* Effect.promise(async () => {\n const { ensureIssueRecordSync, readIssueRecordSync, resolveProjectForIssue, writeIssueRecordSync } = await import('../../../../lib/pan-dir/record.js');\n const { resolveReviewMode, resolveReReviewScope } = await import('../../../../lib/cloister/review-agent.js');\n const project = resolveProjectForIssue(issueId);\n const record = readIssueRecordSync(project, issueId) ?? ensureIssueRecordSync(project, issueId);\n if (hasMode) {\n if (body.reviewMode === null) delete record.reviewMode;\n else record.reviewMode = body.reviewMode as 'quick' | 'full' | 'none';\n }\n if (hasScope) {\n if (body.reReviewScope === null) delete record.reReviewScope;\n else record.reReviewScope = body.reReviewScope as 'all' | 'changed' | 'blockers';\n }\n writeIssueRecordSync(project, issueId, record);\n return {\n success: true,\n issueId,\n override: { reviewMode: record.reviewMode ?? null, reReviewScope: record.reReviewScope ?? null },\n resolved: { reviewMode: resolveReviewMode(issueId), reReviewScope: resolveReReviewScope(issueId) },\n };\n });\n return jsonResponse(result);\n }))\n);\n\nexport const reviewControlRouteLayer = Layer.mergeAll(\n postWorkspaceResetReviewRoute,\n postWorkspaceReviewPurgeRoute,\n postWorkspaceAbortReviewRoute,\n postWorkspaceUnstickRoute,\n postWorkspaceDeaconIgnoreRoute,\n postWorkspaceAutoMergeRoute,\n deleteWorkspacePendingRoute,\n getReviewConfigRoute,\n postReviewConfigRoute,\n);\n\nexport default reviewControlRouteLayer;\n","import { execFile, spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { Effect, Layer } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\n\nimport { emitActivityEntrySync } from '../../../../lib/activity-logger.js';\nimport { teardownWorkspace } from '../../../../lib/lifecycle/teardown-workspace.js';\nimport { extractPrefixSync, parseIssueIdSync } from '../../../../lib/issue-id.js';\nimport { getProjectSync, resolveProjectFromIssueSync } from '../../../../lib/projects.js';\nimport { DEVCONTAINER_DIRNAME } from '../../../../lib/workspace/devcontainer-renderer.js';\nimport { composeProjectNameForWorkspace } from '../../../../lib/workspace/rebuild-stack.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { rejectUnauthorizedDashboardRequest, rejectUnsafeDashboardMutationRequest } from '../dashboard-auth.js';\nimport { httpHandler } from '../http-handler.js';\nimport {\n appendActivityOutput,\n completePendingOperation,\n getProjectPath,\n logActivity,\n setPendingOperation,\n updateActivity,\n} from '../workspaces.js';\n\nconst execFileAsync = promisify(execFile);\n\ntype UatStackPendingOperation = 'start-stack' | 'stop-stack' | 'restart-stack' | 'reap-workspace';\n\nconst WORKSPACE_COMPOSE_FILES = [\n 'docker-compose.devcontainer.yml',\n 'docker-compose.yml',\n 'docker-compose.yaml',\n 'compose.yml',\n 'compose.yaml',\n];\n\ninterface WorkspaceStackContext {\n issueId: string;\n projectPath: string;\n workspacePath: string;\n composeFile: string;\n composeProjectName: string;\n projectName?: string;\n}\n\nfunction findWorkspaceComposeFile(workspacePath: string): string | null {\n const devcontainerDir = join(workspacePath, DEVCONTAINER_DIRNAME);\n for (const file of WORKSPACE_COMPOSE_FILES) {\n const fullPath = join(devcontainerDir, file);\n if (existsSync(fullPath)) return fullPath;\n }\n return null;\n}\n\nfunction resolveWorkspaceStackContext(issueId: string): WorkspaceStackContext | { error: string; status: number } {\n const parsed = parseIssueIdSync(issueId);\n if (!parsed) return { error: 'Invalid issue ID', status: 400 };\n\n const normalizedIssueId = parsed.raw.toUpperCase();\n const issueLower = normalizedIssueId.toLowerCase();\n const resolvedProject = resolveProjectFromIssueSync(normalizedIssueId);\n const projectConfig = resolvedProject ? getProjectSync(resolvedProject.projectKey) : null;\n const issuePrefix = extractPrefixSync(normalizedIssueId) ?? normalizedIssueId.split('-')[0];\n const projectPath = resolvedProject?.projectPath ?? getProjectPath(undefined, issuePrefix);\n const workspacePath = join(\n projectPath,\n projectConfig?.workspace?.workspaces_dir ?? 'workspaces',\n `feature-${issueLower}`,\n );\n\n if (!existsSync(workspacePath)) {\n return { error: `Workspace not found for ${normalizedIssueId}`, status: 404 };\n }\n\n const composeFile = findWorkspaceComposeFile(workspacePath);\n if (!composeFile) {\n return { error: `No workspace compose file found for ${normalizedIssueId}`, status: 404 };\n }\n\n try {\n return {\n issueId: normalizedIssueId,\n projectPath,\n workspacePath,\n composeFile,\n composeProjectName: composeProjectNameForWorkspace(workspacePath, normalizedIssueId),\n projectName: projectConfig?.name,\n };\n } catch (err: unknown) {\n return {\n error: err instanceof Error ? err.message : String(err),\n status: 409,\n };\n }\n}\n\nfunction spawnCommandActivity(\n command: string,\n args: string[],\n description: string,\n cwd: string,\n options: { issueId: string; pendingOperation: UatStackPendingOperation },\n): string {\n const activityId = Date.now().toString();\n setPendingOperation(options.issueId, options.pendingOperation);\n emitActivityEntrySync({\n source: 'dashboard',\n level: 'info',\n issueId: options.issueId.toUpperCase(),\n message: `${description} started`,\n });\n logActivity({\n id: activityId,\n timestamp: new Date().toISOString(),\n command: [command, ...args].join(' '),\n status: 'running',\n output: [],\n });\n\n const child = spawn(command, args, {\n cwd,\n detached: true,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n child.stdout?.on('data', (data) => {\n data.toString().split('\\n').filter(Boolean).forEach((line: string) => {\n appendActivityOutput(activityId, line);\n });\n });\n child.stderr?.on('data', (data) => {\n data.toString().split('\\n').filter(Boolean).forEach((line: string) => {\n appendActivityOutput(activityId, `[stderr] ${line}`);\n });\n });\n child.on('close', (code) => {\n const ok = code === 0;\n updateActivity(activityId, { status: ok ? 'completed' : 'failed' });\n completePendingOperation(options.issueId, ok ? null : `${command} exited ${code ?? 'unknown'}`);\n emitActivityEntrySync({\n source: 'dashboard',\n level: ok ? 'success' : 'error',\n issueId: options.issueId.toUpperCase(),\n message: `${description} ${ok ? 'completed' : 'failed'}`,\n });\n });\n\n return activityId;\n}\n\nfunction spawnEffectActivity(\n description: string,\n issueId: string,\n pendingOperation: UatStackPendingOperation,\n run: (activityId: string) => Promise<void>,\n): string {\n const activityId = Date.now().toString();\n setPendingOperation(issueId, pendingOperation);\n emitActivityEntrySync({\n source: 'dashboard',\n level: 'info',\n issueId: issueId.toUpperCase(),\n message: `${description} started`,\n });\n logActivity({\n id: activityId,\n timestamp: new Date().toISOString(),\n command: description,\n status: 'running',\n output: [],\n });\n\n void run(activityId)\n .then(() => {\n updateActivity(activityId, { status: 'completed' });\n completePendingOperation(issueId, null);\n emitActivityEntrySync({\n source: 'dashboard',\n level: 'success',\n issueId: issueId.toUpperCase(),\n message: `${description} completed`,\n });\n })\n .catch((err: unknown) => {\n const message = err instanceof Error ? err.message : String(err);\n appendActivityOutput(activityId, message);\n updateActivity(activityId, { status: 'failed' });\n completePendingOperation(issueId, message);\n emitActivityEntrySync({\n source: 'dashboard',\n level: 'error',\n issueId: issueId.toUpperCase(),\n message: `${description} failed`,\n });\n });\n\n return activityId;\n}\n\nconst postWorkspaceStackActionRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces/:issueId/stack/:action',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const authError = rejectUnsafeDashboardMutationRequest(request);\n if (authError) return authError;\n\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n const action = params['action'] ?? '';\n if (action !== 'start' && action !== 'stop' && action !== 'restart') {\n return jsonResponse({ error: 'Invalid stack action' }, { status: 400 });\n }\n\n const context = resolveWorkspaceStackContext(issueId);\n if ('error' in context) {\n return jsonResponse({ error: context.error }, { status: context.status });\n }\n\n const pendingOperation = action === 'start'\n ? 'start-stack'\n : action === 'stop'\n ? 'stop-stack'\n : 'restart-stack';\n const description = `${action[0].toUpperCase()}${action.slice(1)} stack for ${context.issueId}`;\n const activityId = spawnCommandActivity(\n 'docker',\n ['compose', '-f', context.composeFile, '-p', context.composeProjectName, action],\n description,\n dirname(context.composeFile),\n { issueId: context.issueId, pendingOperation },\n );\n\n return jsonResponse({\n success: true,\n message: `${description} started`,\n activityId,\n });\n })),\n);\n\nconst getWorkspaceStackLogsRoute = HttpRouter.add(\n 'GET',\n '/api/workspaces/:issueId/stack-logs',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const authError = rejectUnauthorizedDashboardRequest(request);\n if (authError) return authError;\n\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n const context = resolveWorkspaceStackContext(issueId);\n if ('error' in context) {\n return jsonResponse({ error: context.error }, { status: context.status });\n }\n\n const { stdout } = yield* Effect.promise(() => execFileAsync('docker', [\n 'ps',\n '-a',\n '--filter',\n `label=com.docker.compose.project=${context.composeProjectName}`,\n '--format',\n '{{.Names}}',\n ], { encoding: 'utf-8', timeout: 10_000 }));\n const names = stdout.split('\\n').map(line => line.trim()).filter(Boolean);\n const chunks: string[] = [];\n for (const name of names) {\n try {\n const { stdout: logs, stderr } = yield* Effect.promise(() => execFileAsync('docker', [\n 'logs',\n '--tail',\n '200',\n '--timestamps',\n name,\n ], { encoding: 'utf-8', timeout: 10_000, maxBuffer: 2 * 1024 * 1024 }));\n chunks.push(`--- ${name} ---\\n${logs}${stderr ? `\\n${stderr}` : ''}`.trimEnd());\n } catch (err: unknown) {\n chunks.push(`--- ${name} ---\\n${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n return jsonResponse({\n success: true,\n issueId: context.issueId,\n stackName: context.composeProjectName,\n logs: chunks.join('\\n\\n'),\n });\n })),\n);\n\nconst getWorkspaceStateDirRoute = HttpRouter.add(\n 'GET',\n '/api/workspaces/:issueId/state-dir',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const authError = rejectUnauthorizedDashboardRequest(request);\n if (authError) return authError;\n\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n const context = resolveWorkspaceStackContext(issueId);\n if ('error' in context) {\n return jsonResponse({ error: context.error }, { status: context.status });\n }\n return jsonResponse({\n success: true,\n issueId: context.issueId,\n path: context.workspacePath,\n stackName: context.composeProjectName,\n });\n })),\n);\n\nconst postWorkspaceReapRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces/:issueId/reap',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const authError = rejectUnsafeDashboardMutationRequest(request);\n if (authError) return authError;\n\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n const context = resolveWorkspaceStackContext(issueId);\n if ('error' in context) {\n return jsonResponse({ error: context.error }, { status: context.status });\n }\n\n const activityId = spawnEffectActivity(\n `Reap workspace for ${context.issueId}`,\n context.issueId,\n 'reap-workspace',\n async (id) => {\n const steps = await Effect.runPromise(teardownWorkspace({\n issueId: context.issueId,\n projectPath: context.projectPath,\n projectName: context.projectName,\n }, {\n deleteBranches: false,\n clearBeads: false,\n }));\n for (const step of steps) {\n const detail = step.details?.length ? `: ${step.details.join('; ')}` : '';\n appendActivityOutput(id, `${step.success ? '✓' : step.skipped ? '-' : '✗'} ${step.step}${detail}${step.error ? ` (${step.error})` : ''}`);\n }\n const failed = steps.find(step => !step.success && !step.skipped);\n if (failed) {\n throw new Error(failed.error ?? `${failed.step} failed`);\n }\n },\n );\n\n return jsonResponse({\n success: true,\n message: `Reaping workspace for ${context.issueId}`,\n activityId,\n });\n })),\n);\n\nexport const uatStackActionRouteLayer = Layer.mergeAll(\n postWorkspaceStackActionRoute,\n getWorkspaceStackLogsRoute,\n getWorkspaceStateDirRoute,\n postWorkspaceReapRoute,\n);\n","import { jsonResponse } from \"../http-helpers.js\";\nimport { httpHandler } from './http-handler.js';\nimport { buildChildEnvWithoutTmuxSync } from '../../../lib/child-env.js';\n/**\n * Workspaces route module — Effect HttpRouter.Layer (PAN-428 B8)\n *\n * Workspaces + lifecycle + review HTTP routes.\n *\n * Workspace data endpoints (/api/workspaces/):\n * GET /api/workspace-stack-health\n * GET /api/workspaces/:issueId\n * POST /api/workspaces\n * GET /api/workspaces/:issueId/plan\n * GET /api/workspaces/:issueId/uat-context\n * PATCH /api/workspaces/:issueId/plan/inspection-policy\n * GET /api/workspaces/:issueId/clean/preview\n * POST /api/workspaces/:issueId/clean\n * POST /api/workspaces/:issueId/containerize\n * POST /api/workspaces/:issueId/containers/:containerName/:action\n * POST /api/workspaces/:issueId/memory-summary\n * POST /api/workspaces/:issueId/refresh-db\n * GET /api/workspaces/:issueId/stashes\n * POST /api/workspaces/:issueId/stashes/:stashRef/recover\n * DELETE /api/workspaces/:issueId/stashes/:stashRef\n * GET /api/workspaces/:issueId/tldr\n *\n * Lifecycle endpoints (/api/issues/):\n * POST /api/issues/:issueId/start\n * POST /api/issues/:issueId/sync-main\n * POST /api/issues/:issueId/approve\n * POST /api/issues/:issueId/merge\n *\n * Review endpoints (/api/review/):\n * GET /api/review/:issueId/status\n * POST /api/review/:issueId/status\n * POST /api/review/:issueId/trigger\n * POST /api/review/:issueId/request\n * POST /api/review/:issueId/reset\n * DELETE /api/review/:issueId/pending\n *\n * Stuck-state endpoints (/api/workspaces/):\n * POST /api/workspaces/:issueId/unstick\n */\n\nimport { exec, execFile, spawn } from 'node:child_process';\nimport { createConnection } from 'node:net';\nimport { existsSync } from 'node:fs';\nimport { access, chmod, mkdir, readdir, readFile, stat, symlink, unlink, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { basename, dirname, join } from 'node:path';\nimport { promisify } from 'node:util';\nimport { crc32 } from 'node:zlib';\n\nimport { Effect, Layer, Option } from 'effect';\nimport { HttpRouter, HttpServerRequest, HttpServerResponse } from 'effect/unstable/http';\n\nimport {\n resolveProjectFromIssueSync,\n getProjectSync,\n listProjectsSync,\n findProjectByTeamSync,\n extractTeamPrefix,\n} from '../../../lib/projects.js';\nimport { resolveGitHubIssueSync as resolveGitHubIssueShared } from '../../../lib/tracker-utils.js';\nimport { getGitHubConfig } from '../services/tracker-config.js';\nimport { EventStoreService } from '../services/domain-services.js';\nimport {\n enqueuePendingFeedbackDelivery,\n markPendingFeedbackDelivered,\n} from '../pending-feedback.js';\nimport {\n getReviewStatusSync,\n setReviewStatusSync as setReviewStatusBase,\n markWorkspaceStuck,\n setDeaconIgnored,\n setAutoMerge,\n type ReviewStatus,\n} from '../../../lib/review-status.js';\nimport {\n getCachedConflictGateMergeability,\n} from '../../../lib/cloister/conflict-gate.js';\nimport { restoreTrackedBeadsExport } from '../../../lib/beads-restore.js';\nimport {\n computeQueuePositionFromStatusSync,\n findPositionInQueueSync,\n} from '../../../lib/queue-position.js';\nimport {\n messageAgent,\n saveAgentRuntimeState,\n getAgentRuntimeStateSync,\n transitionIssueToInReview,\n getAgentStateSync,\n spawnRun,\n} from '../../../lib/agents.js';\nimport { getActiveSessionModelSync } from '../../../lib/cost-parsers/jsonl-parser.js';\nimport { getCostsForIssueSync } from '../../../lib/costs/index.js';\nimport { resolveIssueHeadlineCost } from '../services/issue-cost-resolver.js';\nimport { getCachedRunningAgents } from '../services/running-agents-cache.js';\nimport { readPlan, isPlanningComplete } from '../../../lib/vbrief/io.js';\nimport { VBRIEF_INSPECTION_POLICIES } from '../../../lib/vbrief/types.js';\nimport type { VBriefDocument, VBriefInspectionPolicy } from '../../../lib/vbrief/types.js';\nimport { findVBriefByIssue, readVBriefDocument } from '../../../lib/vbrief/vbrief-index.js';\nimport { criticalPath, actionableDoc } from '../../../lib/vbrief/dag.js';\nimport { getChangedFiles, getDiffBase, getDiffStat, type ChangedFile } from '../../../lib/cloister/review-context.js';\nimport { capturePane, listSessionNames, sessionExists } from '../../../lib/tmux.js';\nimport { syncBeadStatusToVBrief } from '../../../lib/vbrief/beads.js';\nimport { getUnblockedItemsSync } from '../../../lib/cloister/task-readiness.js';\nimport { runVerificationForIssue } from '../../../lib/cloister/verification-runner.js';\nimport { getTldrDaemonServiceSync } from '../../../lib/tldr-daemon.js';\nimport { loadWorkspaceMetadataSync, listWorkspaceMetadataSync } from '../../../lib/remote/workspace-metadata.js';\nimport { loadConfigSync } from '../../../lib/config.js';\nimport { extractPrefixSync, parseIssueIdSync } from '../../../lib/issue-id.js';\nimport { getContainersReferencingWorkspacePath } from '../../../lib/workspace-manager.js';\nimport { collectDockerContainerLifecycleSnapshot, getWorkspaceStackHealth } from '../../../lib/workspace/stack-health.js';\nimport { emitActivityEntrySync } from '../../../lib/activity-logger.js';\nimport { enrichReviewStatusFromSessions } from '../../../lib/review-status-enrichment.js';\nimport { createRecoveryBranchFromStash, dropStash, isSalvageableStash, listStashes } from '../../../lib/stashes.js';\nimport { PAN_CONTINUE_FILENAME, PAN_DIRNAME } from '../../../lib/pan-dir/types.js';\nimport { getWorkspacePathForIssue } from '../workspace-paths.js';\nimport { containerOpsRouteLayer } from './workspaces/container-ops.js';\nimport { workspaceDataRouteLayer } from './workspaces/workspace-data.js';\nimport { stashCleanRouteLayer } from './workspaces/stash-clean.js';\nimport { reviewPipelineRouteLayer } from './workspaces/review-pipeline.js';\nimport { reviewControlRouteLayer } from './workspaces/review-control.js';\nimport { mergeOpsRouteLayer } from './workspaces/merge-ops.js';\nimport { uatStackActionRouteLayer } from './workspaces/uat-stack-actions.js';\n\nconst execAsync = promisify(exec);\nconst execFileAsync = promisify(execFile);\nconst MAX_PROBED_PORTS = 5;\nconst MAX_PROBED_CONTAINERS = 10;\nconst PROBE_CACHE_TTL_MS = 30_000;\nconst PROBE_CACHE_MAX_ENTRIES = MAX_PROBED_CONTAINERS * MAX_PROBED_PORTS * 20;\n\nfunction safeToISOString(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined;\n const d = new Date(value);\n if (Number.isNaN(d.getTime())) return undefined;\n return d.toISOString();\n}\n\ninterface ProbeCacheEntry {\n result: { healthy: boolean; reason?: string };\n cachedAt: number;\n}\n\nconst probeCache = new Map<string, ProbeCacheEntry>();\n\nfunction pruneProbeCache(\n now = Date.now(),\n runningNames?: Set<string>,\n shouldPruneContainer?: (containerName: string) => boolean,\n): void {\n for (const [key, entry] of probeCache) {\n const containerName = key.split('::', 1)[0] ?? '';\n const expired = now - entry.cachedAt > PROBE_CACHE_TTL_MS;\n const absent = runningNames && shouldPruneContainer?.(containerName) === true && !runningNames.has(containerName);\n if (expired || absent) {\n probeCache.delete(key);\n }\n }\n\n while (probeCache.size > PROBE_CACHE_MAX_ENTRIES) {\n const oldestKey = probeCache.keys().next().value;\n if (typeof oldestKey !== 'string') break;\n probeCache.delete(oldestKey);\n }\n}\n\nfunction getCachedProbe(key: string): { healthy: boolean; reason?: string } | undefined {\n const entry = probeCache.get(key);\n if (!entry) return undefined;\n const now = Date.now();\n if (now - entry.cachedAt > PROBE_CACHE_TTL_MS) {\n probeCache.delete(key);\n return undefined;\n }\n probeCache.delete(key);\n probeCache.set(key, entry);\n return entry.result;\n}\n\nfunction setCachedProbe(key: string, result: { healthy: boolean; reason?: string }): void {\n const now = Date.now();\n pruneProbeCache(now);\n probeCache.set(key, { result, cachedAt: now });\n pruneProbeCache(now);\n}\n\nasync function readWorkspacePlanningMarkdown(\n issueId: string,\n fileName: 'INFERENCE.md',\n): Promise<{ issueId: string; body: string }> {\n const parsed = parseIssueIdSync(issueId);\n const issuePrefix = parsed?.prefix ?? extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const { parsedIssueId, workspacePath } = getWorkspacePathForIssue(projectPath, issueId);\n\n const content = await readFile(join(workspacePath, PAN_DIRNAME, fileName), 'utf-8');\n return {\n issueId: parsedIssueId,\n body: content,\n };\n}\n\n/**\n * Read per-issue record continue view and return it as normalized JSON text,\n * or null when the record does not exist.\n */\nasync function readWorkspaceContinueFile(\n _projectPath: string,\n workspacePath: string,\n issueId: string,\n): Promise<string | null> {\n try {\n const { readRecordContinueViewSync, getProjectConfigFromWorkspacePath, resolveProjectForIssue } =\n await import('../../../lib/pan-dir/record.js');\n const project = resolveProjectForIssue(issueId) ?? getProjectConfigFromWorkspacePath(workspacePath);\n const recordView = readRecordContinueViewSync(project, issueId);\n return recordView ? JSON.stringify(recordView, null, 2) : null;\n } catch {\n return null;\n }\n}\n\nasync function deliverQueuedFeedback(\n issueId: string,\n kind: 'review-blocked' | 'review-failed' | 'test-failed',\n filePath: string,\n message: string,\n): Promise<void> {\n const agentId = `agent-${issueId.toLowerCase()}`;\n await enqueuePendingFeedbackDelivery({\n issueId,\n agentId,\n kind,\n filePath,\n message,\n createdAt: new Date().toISOString(),\n });\n await messageAgent(agentId, message);\n await markPendingFeedbackDelivered(issueId, kind);\n}\n\n// ─── Constants ────────────────────────────────────────────────────────────────\n\nconst PORT = parseInt(process.env.API_PORT || process.env.PORT || '3011', 10);\n\n// ─── Activity log (in-memory, shared with server startup) ─────────────────────\n\ninterface ActivityEntry {\n id: string;\n timestamp: string;\n command: string;\n status: 'running' | 'completed' | 'failed';\n output: string[];\n}\n\nconst activityLog: ActivityEntry[] = [];\n\nexport function logActivity(entry: ActivityEntry): void {\n activityLog.unshift(entry);\n if (activityLog.length > 100) activityLog.pop();\n}\n\nexport function updateActivity(id: string, updates: Partial<ActivityEntry>): void {\n const entry = activityLog.find(e => e.id === id);\n if (entry) Object.assign(entry, updates);\n}\n\nexport function appendActivityOutput(id: string, line: string): void {\n const entry = activityLog.find(e => e.id === id);\n if (entry) {\n entry.output.push(line);\n if (entry.output.length > 500) entry.output.shift();\n }\n}\n\n// ─── Pending operations (in-memory) ──────────────────────────────────────────\n\ninterface PendingOperation {\n type:\n | 'review'\n | 'merge'\n | 'approve'\n | 'start'\n | 'clean'\n | 'containerize'\n | 'refresh-db'\n | 'rebuild-stack'\n | 'start-stack'\n | 'stop-stack'\n | 'restart-stack'\n | 'reap-workspace';\n status: 'running' | 'completed' | 'failed';\n startedAt: string;\n error?: string;\n}\n\nconst pendingOperations = new Map<string, PendingOperation>();\n\nexport function setPendingOperation(issueId: string, type: PendingOperation['type']): void {\n pendingOperations.set(issueId.toLowerCase(), {\n type,\n status: 'running',\n startedAt: new Date().toISOString(),\n });\n}\n\nexport function completePendingOperation(issueId: string, error?: string | null): void {\n const op = pendingOperations.get(issueId.toLowerCase());\n if (op) {\n op.status = error ? 'failed' : 'completed';\n if (error) op.error = error;\n }\n}\n\nexport function getPendingOperation(issueId: string): PendingOperation | null {\n return pendingOperations.get(issueId.toLowerCase()) ?? null;\n}\n\nexport function clearPendingOperation(issueId: string): void {\n pendingOperations.delete(issueId.toLowerCase());\n}\n\n// ─── Local helpers ────────────────────────────────────────────────────────────\n\nexport function getProjectPath(linearProjectId?: string, issuePrefix?: string): string {\n if (issuePrefix) {\n const issueId = `${issuePrefix}-1`;\n const resolved = resolveProjectFromIssueSync(issueId);\n if (resolved) return resolved.projectPath;\n\n const config = getGitHubConfig();\n if (config) {\n for (const { owner, repo, prefix } of config.repos) {\n const repoPrefix = prefix || repo.toUpperCase().replace(/-CLI$/, '').replace(/-/g, '');\n if (repoPrefix.toUpperCase() === issuePrefix.toUpperCase()) {\n const possiblePaths = [\n join(homedir(), 'Projects', repo),\n join(homedir(), 'Projects', repo.replace(/-cli$/, '')),\n join(homedir(), 'Projects', owner, repo),\n ];\n for (const path of possiblePaths) {\n if (existsSync(path)) return path;\n }\n }\n }\n }\n }\n return join(homedir(), 'Projects');\n}\n\nexport function requireTrustedMutationOrigin(request: HttpServerRequest.HttpServerRequest): HttpServerResponse.HttpServerResponse | null {\n const origin = (() => {\n const value = (request.headers as Record<string, string | string[] | undefined>)['origin'];\n return Array.isArray(value) ? value[0] : value;\n })();\n const referer = (() => {\n const value = (request.headers as Record<string, string | string[] | undefined>)['referer'];\n return Array.isArray(value) ? value[0] : value;\n })();\n\n const port = parseInt(process.env['API_PORT'] ?? process.env['PORT'] ?? '3011', 10);\n const dashboardUrl = process.env['DASHBOARD_URL'] ?? `http://localhost:${port}`;\n const trustedOrigins = new Set<string>([dashboardUrl]);\n // Always trust direct localhost access — the dashboard may be reached via\n // reverse proxy (e.g. https://overdeck.localhost) OR directly (http://localhost:3011).\n trustedOrigins.add(`http://localhost:${port}`);\n trustedOrigins.add(`http://127.0.0.1:${port}`);\n if (process.env['NODE_ENV'] === 'development') {\n trustedOrigins.add('http://localhost:3000');\n trustedOrigins.add('http://127.0.0.1:3000');\n }\n\n const normalize = (value?: string): string | null => {\n if (!value) return null;\n try {\n const url = new URL(value);\n return `${url.protocol}//${url.host}`;\n } catch {\n return null;\n }\n };\n\n const normalizedOrigin = normalize(origin);\n if (normalizedOrigin) {\n return trustedOrigins.has(normalizedOrigin)\n ? null\n : jsonResponse({ error: 'Invalid origin' }, { status: 403 });\n }\n\n const normalizedReferer = normalize(referer);\n if (normalizedReferer) {\n return trustedOrigins.has(normalizedReferer)\n ? null\n : jsonResponse({ error: 'Invalid referer' }, { status: 403 });\n }\n\n return jsonResponse({ error: 'Missing origin' }, { status: 403 });\n}\n\nexport interface WorkspaceInfo {\n exists: boolean;\n isRemote: boolean;\n vmName?: string;\n remotePath?: string;\n localPath?: string;\n agentId?: string;\n}\n\nexport function getWorkspaceInfoForIssue(issueId: string): WorkspaceInfo {\n try {\n const meta = loadWorkspaceMetadataSync(issueId);\n if (meta?.location === 'remote' && meta.vmName) {\n const metaRecord = meta as unknown as Record<string, unknown>;\n return {\n exists: true,\n isRemote: true,\n vmName: meta.vmName,\n remotePath: typeof metaRecord['remotePath'] === 'string' ? metaRecord['remotePath'] : undefined,\n agentId: typeof metaRecord['agentId'] === 'string' ? metaRecord['agentId'] : undefined,\n };\n }\n } catch { /* non-fatal */ }\n\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const issueLower = issueId.toLowerCase();\n const numericSuffix = issueLower.replace(/^[a-z]+-/, '');\n\n for (const { config } of listProjectsSync()) {\n if (!config.path) continue;\n for (const candidate of [`feature-${issueLower}`, `feature-${numericSuffix}`]) {\n const p = join(config.path, 'workspaces', candidate);\n if (existsSync(p)) return { exists: true, isRemote: false, localPath: p };\n }\n }\n\n const projectPath = getProjectPath(undefined, issuePrefix);\n const workspacePath = join(projectPath, 'workspaces', `feature-${issueLower}`);\n if (existsSync(workspacePath)) return { exists: true, isRemote: false, localPath: workspacePath };\n\n return { exists: false, isRemote: false };\n}\n\nfunction isGitHubIssue(issueId: string): {\n isGitHub: boolean;\n owner?: string;\n repo?: string;\n number?: number;\n} {\n const resolved = resolveGitHubIssueShared(issueId);\n if (resolved.isGitHub) {\n return { isGitHub: true, owner: resolved.owner, repo: resolved.repo, number: resolved.number };\n }\n return { isGitHub: false };\n}\n\nfunction attachPanOutputStreams(\n child: { stdout?: NodeJS.ReadableStream | null; stderr?: NodeJS.ReadableStream | null },\n activityId: string,\n): void {\n child.stdout?.on('data', (data) => {\n data.toString().split('\\n').filter(Boolean).forEach((line: string) => {\n appendActivityOutput(activityId, line);\n });\n });\n child.stderr?.on('data', (data) => {\n data.toString().split('\\n').filter(Boolean).forEach((line: string) => {\n appendActivityOutput(activityId, `[stderr] ${line}`);\n });\n });\n}\n\ninterface ChainPanOnSuccess {\n args: string[];\n /** Activity message emitted when the first command succeeds and the chained\n * command begins (e.g. \"Stack rebuilt — starting agent\"). */\n phaseLabel: string;\n}\n\nexport function spawnPanCommand(\n args: string[],\n description: string,\n cwd?: string,\n options?: {\n issueId?: string;\n pendingOperation?: PendingOperation['type'];\n /** When set, this second `pan` invocation runs only if the first exits 0,\n * streaming into the same activity. Used by the rebuild-and-start route to\n * chain `pan start` after a successful `pan workspace rebuild`. */\n chainOnSuccess?: ChainPanOnSuccess;\n },\n): string {\n const activityId = Date.now().toString();\n const issueId = options?.issueId;\n const pendingOp = options?.pendingOperation;\n const chain = options?.chainOnSuccess;\n const commandLine = chain ? `pan ${args.join(' ')} && pan ${chain.args.join(' ')}` : `pan ${args.join(' ')}`;\n\n if (issueId && pendingOp) {\n setPendingOperation(issueId, pendingOp);\n emitActivityEntrySync({\n source: 'dashboard',\n level: 'info',\n issueId: issueId.toUpperCase(),\n message: `${description} started`,\n });\n }\n logActivity({\n id: activityId,\n timestamp: new Date().toISOString(),\n command: commandLine,\n status: 'running',\n output: [],\n });\n\n const finalize = (code: number | null, failedCommand: string) => {\n updateActivity(activityId, { status: code === 0 ? 'completed' : 'failed' });\n if (issueId && pendingOp) {\n completePendingOperation(issueId, code === 0 ? null : `${failedCommand} exited ${code ?? 'unknown'}`);\n emitActivityEntrySync({\n source: 'dashboard',\n level: code === 0 ? 'success' : 'error',\n issueId: issueId.toUpperCase(),\n message: `${description} ${code === 0 ? 'completed' : 'failed'}`,\n });\n }\n };\n\n const child = spawn('pan', args, {\n cwd: cwd || process.cwd(),\n detached: true,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n attachPanOutputStreams(child, activityId);\n\n child.on('close', (code) => {\n if (code === 0 && chain) {\n if (issueId) {\n emitActivityEntrySync({\n source: 'dashboard',\n level: 'info',\n issueId: issueId.toUpperCase(),\n message: chain.phaseLabel,\n });\n }\n appendActivityOutput(activityId, `--- ${chain.phaseLabel} ---`);\n const next = spawn('pan', chain.args, {\n cwd: cwd || process.cwd(),\n detached: true,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n attachPanOutputStreams(next, activityId);\n next.on('close', (nextCode) => finalize(nextCode, `pan ${chain.args.join(' ')}`));\n } else {\n finalize(code, `pan ${args.join(' ')}`);\n }\n });\n\n return activityId;\n}\n\nexport interface WorkspaceContainerStatus {\n running: boolean;\n uptime: string | null;\n status?: string;\n health?: 'healthy' | 'unhealthy' | 'starting' | 'unknown';\n ports?: number[];\n lastProbeAt?: string;\n lastFailureReason?: string;\n}\n\nexport async function getContainerStatusAsync(\n issueId: string,\n projectPath?: string\n): Promise<Record<string, WorkspaceContainerStatus>> {\n const result: Record<string, WorkspaceContainerStatus> = {};\n try {\n const { stdout } = await execFileAsync('docker', ['ps', '-a', '--format', '{{.Names}}\\t{{.Status}}'], { encoding: 'utf-8' });\n const search = issueId.toLowerCase();\n const lines = stdout.trim().split('\\n').filter(Boolean);\n for (const line of lines) {\n if (!line.toLowerCase().includes(search)) continue;\n const [name, ...statusParts] = line.split('\\t');\n const statusStr = statusParts.join('\\t');\n const running = statusStr.toLowerCase().startsWith('up');\n const uptimeMatch = statusStr.match(/Up (.+)/);\n result[name] = {\n running,\n uptime: running ? (uptimeMatch ? uptimeMatch[1] : null) : null,\n status: statusStr,\n };\n }\n\n // Batch docker inspect for all running containers to avoid serial O(n) latency\n const runningNames = Object.entries(result)\n .filter(([, info]) => info.running)\n .map(([name]) => name);\n pruneProbeCache(\n Date.now(),\n new Set(runningNames),\n (containerName) => containerName.toLowerCase().includes(search),\n );\n\n if (runningNames.length > 0) {\n let inspectByName = new Map<string, any>();\n try {\n const { stdout: inspectStdout } = await execFileAsync(\n 'docker',\n ['inspect', ...runningNames],\n { encoding: 'utf-8', timeout: 10000 }\n );\n const inspects: Array<{\n Name?: string;\n Config?: { Labels?: Record<string, string>; ExposedPorts?: Record<string, unknown> };\n NetworkSettings?: { Ports?: Record<string, unknown> };\n State?: { Health?: { Status?: string; LastExecution?: { End?: string; ExitCode?: number }; FailingStreak?: number; ExitCode?: number } };\n }> = JSON.parse(inspectStdout);\n inspectByName = new Map(\n inspects\n .map((i): [string | undefined, typeof i] => [i.Name?.replace(/^\\//, ''), i])\n .filter((entry): entry is [string, (typeof inspects)[number]] => typeof entry[0] === 'string')\n );\n } catch (err: any) {\n // Docker inspect may return partial JSON on stderr even when one container is missing.\n // Try to salvage valid results from stdout so one missing container doesn't drop all health data.\n const partial = err?.stdout;\n if (typeof partial === 'string' && partial.trim().startsWith('[')) {\n try {\n const inspects = JSON.parse(partial);\n if (Array.isArray(inspects)) {\n inspectByName = new Map(inspects.map((i: any) => [i.Name?.replace(/^\\//, ''), i]));\n }\n } catch {\n // Partial JSON unreadable — fall through to empty inspectByName\n }\n }\n }\n\n // Cap total probed containers to prevent unbounded fan-out per request\n const namesToProbe = runningNames.slice(0, MAX_PROBED_CONTAINERS);\n\n await Promise.all(\n namesToProbe.map(async (name) => {\n const info = result[name];\n const inspect = inspectByName.get(name);\n const serviceHealth = extractContainerServiceHealth(inspect);\n let health: WorkspaceContainerStatus['health'] = serviceHealth.health;\n let lastFailureReason = serviceHealth.lastFailureReason;\n let lastProbeAt = serviceHealth.lastProbeAt;\n\n // If docker healthcheck is unknown but we have host-mapped ports, probe them\n if (health === 'unknown' && serviceHealth.bindings.length > 0) {\n // Deduplicate and cap probe targets\n const dedupedBindings = Array.from(\n new Map(\n serviceHealth.bindings.map((b) => [`${b.hostIp}:${b.hostPort}`, b])\n ).values()\n ).slice(0, MAX_PROBED_PORTS);\n\n const probeResults = await Promise.all(\n dedupedBindings.map(async (b) => {\n const cacheKey = `${name}::${b.hostIp}:${b.hostPort}`;\n const cached = getCachedProbe(cacheKey);\n if (cached) return cached;\n const probeResult = await probeContainerPortAsync(b.hostPort, b.hostIp);\n setCachedProbe(cacheKey, probeResult);\n return probeResult;\n })\n );\n const anyHealthy = probeResults.some((r) => r.healthy);\n health = anyHealthy ? 'healthy' : 'unhealthy';\n lastProbeAt = new Date().toISOString();\n if (!anyHealthy) {\n const firstFailure = probeResults.find((r) => !r.healthy);\n lastFailureReason = firstFailure?.reason ?? 'probe failed';\n }\n }\n\n result[name] = {\n ...info,\n health,\n ports: serviceHealth.ports,\n lastProbeAt,\n lastFailureReason,\n };\n })\n );\n }\n } catch { /* non-fatal */ }\n return result;\n}\n\ninterface ContainerPortBinding {\n containerPort: number;\n hostIp: string;\n hostPort: number;\n}\n\ninterface ContainerServiceHealth {\n health: 'healthy' | 'unhealthy' | 'starting' | 'unknown';\n ports: number[];\n bindings: ContainerPortBinding[];\n lastProbeAt?: string;\n lastFailureReason?: string;\n}\n\nfunction extractContainerServiceHealth(\n inspect?: {\n Config?: { Labels?: Record<string, string>; ExposedPorts?: Record<string, unknown> };\n NetworkSettings?: { Ports?: Record<string, unknown> };\n State?: { Health?: { Status?: string; LastExecution?: { End?: string; ExitCode?: number }; FailingStreak?: number; ExitCode?: number } };\n }\n): ContainerServiceHealth {\n if (!inspect) return { health: 'unknown', ports: [], bindings: [] };\n const labels = inspect?.Config?.Labels ?? {};\n const healthState = inspect?.State?.Health;\n const exposedPorts = inspect?.Config?.ExposedPorts ?? {};\n const portBindings = inspect?.NetworkSettings?.Ports ?? {};\n\n // Parse exposed ports\n const ports = Object.keys(exposedPorts)\n .map((p) => parseInt(p.split('/')[0], 10))\n .filter((n) => !Number.isNaN(n));\n\n // Parse Traefik loadbalancer port labels\n const traefikPorts: number[] = [];\n for (const [key, value] of Object.entries(labels)) {\n if (key.endsWith('.loadbalancer.server.port') && value) {\n const port = parseInt(value, 10);\n if (!Number.isNaN(port)) traefikPorts.push(port);\n }\n }\n\n const allPorts = traefikPorts.length > 0 ? traefikPorts : ports;\n\n // Extract host-mapped ports from NetworkSettings.Ports\n const bindings: ContainerPortBinding[] = [];\n for (const [containerPortProto, hostBindings] of Object.entries(portBindings)) {\n if (!Array.isArray(hostBindings)) continue;\n const containerPort = parseInt(containerPortProto.split('/')[0], 10);\n if (Number.isNaN(containerPort)) continue;\n for (const hb of hostBindings) {\n if (!hb || typeof hb !== 'object') continue;\n const hostPort = parseInt((hb as any).HostPort, 10);\n if (Number.isNaN(hostPort) || hostPort < 1 || hostPort > 65535) continue;\n bindings.push({\n containerPort,\n hostIp: String((hb as any).HostIp || '127.0.0.1'),\n hostPort,\n });\n }\n }\n\n // If container has a Docker healthcheck, use its state\n if (healthState?.Status) {\n const status = String(healthState.Status).toLowerCase();\n const health: ContainerServiceHealth['health'] =\n status === 'healthy' ? 'healthy' :\n status === 'unhealthy' ? 'unhealthy' :\n status === 'starting' ? 'starting' : 'unknown';\n const lastProbeAt = safeToISOString(healthState?.LastExecution?.End);\n const exitCode = healthState?.LastExecution?.ExitCode;\n const failingStreak = healthState?.FailingStreak;\n const lastFailureReason = failingStreak && failingStreak > 0\n ? (typeof exitCode === 'number' && exitCode !== 0 ? `exit code ${exitCode}` : 'healthcheck failed')\n : undefined;\n return {\n health,\n ports: allPorts,\n bindings,\n lastProbeAt,\n lastFailureReason,\n };\n }\n\n return {\n health: 'unknown',\n ports: allPorts,\n bindings,\n };\n}\n\nasync function probeContainerPortAsync(hostPort: number, hostIp = '127.0.0.1'): Promise<{ healthy: boolean; reason?: string }> {\n return new Promise((resolve) => {\n const socket = createConnection(hostPort, hostIp);\n socket.setTimeout(5000);\n\n socket.on('connect', () => {\n socket.end();\n resolve({ healthy: true });\n });\n\n socket.on('error', (err) => {\n resolve({ healthy: false, reason: err.message.slice(0, 200) });\n });\n\n socket.on('timeout', () => {\n socket.destroy();\n resolve({ healthy: false, reason: 'connection timeout' });\n });\n });\n}\n\nfunction getFlyAppName(vmName: string): string {\n // Resolve via workspace metadata — deriving from the vmName prefix is wrong\n // ('pan-pan-1712-ws' → 'pan-pan'). Fall back to the configured app.\n try {\n const meta = listWorkspaceMetadataSync().find((m) => m.vmName === vmName);\n if (meta?.appName) return meta.appName;\n } catch { /* fall through */ }\n try {\n return loadConfigSync().remote?.fly?.app ?? 'pan-workspaces';\n } catch {\n return 'pan-workspaces';\n }\n}\n\nfunction shellQuote(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\n\nexport function flyExecCmd(vmName: string, command: string): string {\n const appName = getFlyAppName(vmName);\n return `fly ssh console -a ${appName} -C \"${command.replace(/\"/g, '\\\\\"')}\"`;\n}\n\nexport async function repairFlywayIfNeeded(\n issueId: string,\n pgContainer: string,\n dbName: string,\n projectConfig: any,\n workspacePath: string,\n log?: (msg: string) => void\n): Promise<{ repaired: boolean; message: string }> {\n const emit = log || ((msg: string) => console.log(`[flyway-repair] ${msg}`));\n\n try {\n await execAsync(`docker exec \"${pgContainer}\" pg_isready -U postgres`, {\n encoding: 'utf-8',\n timeout: 5000,\n });\n } catch {\n return { repaired: false, message: 'Postgres container not ready, skipping Flyway check' };\n }\n\n let rowCount = 0;\n try {\n const { stdout } = await execAsync(\n `docker exec \"${pgContainer}\" psql -U postgres -d ${dbName} -t -A -c \"SELECT count(*) FROM flyway_schema_history;\"`,\n { encoding: 'utf-8', timeout: 10000 }\n );\n rowCount = parseInt(stdout.trim(), 10) || 0;\n } catch {\n rowCount = 0;\n }\n\n if (rowCount >= 10) {\n return { repaired: false, message: `Flyway schema_history has ${rowCount} entries, no repair needed` };\n }\n\n emit(`Flyway schema_history has only ${rowCount} entries — repairing`);\n\n const seedRelPath = projectConfig.workspace?.database?.seed_file;\n if (!seedRelPath) {\n return { repaired: false, message: 'No seed_file configured, cannot locate Flyway baseline' };\n }\n\n const seedFile = join(projectConfig.path, seedRelPath);\n const flywayFile = join(dirname(seedFile), 'zzz-flyway-workspace-baseline.sql');\n if (!existsSync(flywayFile)) {\n return { repaired: false, message: `Flyway baseline not found: ${flywayFile}` };\n }\n\n emit(`Loading Flyway baseline from ${flywayFile}`);\n await execAsync(\n `docker exec -i \"${pgContainer}\" psql -U postgres -d ${dbName} < \"${flywayFile}\"`,\n { encoding: 'utf-8', timeout: 60000 }\n );\n\n const migrationsRelPath = projectConfig.workspace?.database?.migrations?.path;\n if (migrationsRelPath) {\n const migrationsDir = join(workspacePath, migrationsRelPath);\n if (existsSync(migrationsDir)) {\n emit(`Syncing Flyway checksums from workspace migrations`);\n const migrationFiles = (await readdir(migrationsDir)).filter(f => /^V\\d+__.*\\.sql$/.test(f));\n const updates: string[] = [];\n\n for (const file of migrationFiles) {\n const version = file.match(/^V(\\d+)__/)?.[1];\n if (!version) continue;\n let content = await readFile(join(migrationsDir, file));\n if (content[0] === 0xEF && content[1] === 0xBB && content[2] === 0xBF) {\n content = content.slice(3);\n }\n const lines = content.toString('utf-8').split(/\\r?\\n/);\n const checksum = crc32(Buffer.from(lines.join(''), 'utf-8')) | 0;\n updates.push(\n `UPDATE flyway_schema_history SET checksum = ${checksum} WHERE version = '${version}' AND checksum IS NOT NULL;`\n );\n }\n\n if (updates.length > 0) {\n const tmpSql = `/tmp/flyway-checksum-sync-${Date.now()}.sql`;\n await writeFile(tmpSql, updates.join('\\n'));\n try {\n const { stdout } = await execAsync(\n `docker exec -i \"${pgContainer}\" psql -U postgres -d ${dbName} < \"${tmpSql}\"`,\n { encoding: 'utf-8', timeout: 30000 }\n );\n const updatedCount = (stdout.match(/UPDATE \\d+/g) || [])\n .reduce((sum, m) => sum + parseInt(m.replace('UPDATE ', ''), 10), 0);\n emit(`Synced ${migrationFiles.length} migration checksums (${updatedCount} rows updated)`);\n } finally {\n try { await unlink(tmpSql); } catch {}\n }\n }\n }\n }\n\n try {\n const { stdout } = await execAsync(\n `docker exec \"${pgContainer}\" psql -U postgres -d ${dbName} -t -A -c \"SELECT count(*) FROM flyway_schema_history;\"`,\n { encoding: 'utf-8', timeout: 10000 }\n );\n const newCount = parseInt(stdout.trim(), 10) || 0;\n emit(`Repair complete: flyway_schema_history now has ${newCount} entries (was ${rowCount})`);\n return { repaired: true, message: `Repaired Flyway schema_history: ${rowCount} → ${newCount} entries` };\n } catch (err: any) {\n return { repaired: false, message: `Repair may have failed: ${err.message}` };\n }\n}\n\n// setReviewStatus wrapper (mirrors the index.ts version; side-effects are\n// intentionally omitted here — the server-side side-effects (auto-PR, auto-merge)\n// live in the Express server until full migration is complete).\nexport function setReviewStatus(issueId: string, update: Partial<ReviewStatus>): ReviewStatus {\n return setReviewStatusBase(issueId, update);\n}\n\n// ─── Read JSON body helper ────────────────────────────────────────────────────\n\nexport const readJsonBody = Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const text = yield* request.text;\n try {\n return text ? JSON.parse(text) : {};\n } catch {\n return {};\n }\n});\n\n// ─── Route: POST /api/workspaces/:issueId/rebuild-stack ───────────────────────\n// Bring a workspace's Docker stack back up (e.g. so the \"UAT\" action on the\n// Awaiting-Merge page works when the stack is down). Fire-and-forget like the\n// create route: spawns `pan workspace rebuild`, returns an activityId so the\n// dashboard streams progress.\nconst postWorkspaceRebuildRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces/:issueId/rebuild-stack',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: 'Invalid issue ID' }, { status: 400 });\n }\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const activityId = spawnPanCommand(\n ['workspace', 'rebuild', issueId],\n `Rebuild stack for ${issueId}`,\n projectPath,\n { issueId, pendingOperation: 'rebuild-stack' },\n );\n return jsonResponse({\n success: true,\n message: `Rebuilding stack for ${issueId}`,\n activityId,\n });\n }))\n);\n\n// ─── Route: POST /api/workspaces/:issueId/rebuild-and-start ──────────────────\n// Recovery action for the `stack-unhealthy` work-agent spawn block: rebuild the\n// workspace's Docker stack, then spawn the work agent once the stack is healthy.\n// Fire-and-forget like the rebuild-stack route, but chains `pan start` after a\n// successful `pan workspace rebuild` under a single activityId so the dashboard\n// streams both phases as one operation.\nconst postWorkspaceRebuildAndStartRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces/:issueId/rebuild-and-start',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: 'Invalid issue ID' }, { status: 400 });\n }\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const activityId = spawnPanCommand(\n ['workspace', 'rebuild', issueId],\n `Rebuild & start for ${issueId}`,\n projectPath,\n {\n issueId,\n pendingOperation: 'rebuild-stack',\n chainOnSuccess: {\n args: ['start', issueId],\n phaseLabel: `Stack rebuilt — starting agent for ${issueId.toUpperCase()}`,\n },\n },\n );\n return jsonResponse({\n success: true,\n message: `Rebuilding stack and starting agent for ${issueId}`,\n activityId,\n });\n }))\n);\n\n// ─── Route: GET /api/workspaces/:issueId/plan ─────────────────────────────────\n\nconst getWorkspaceStateMdRoute = HttpRouter.add(\n 'GET',\n '/api/workspaces/:issueId/state-md',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n\n const parsed = parseIssueIdSync(issueId);\n const issuePrefix = parsed?.prefix ?? extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const { parsedIssueId, workspacePath } = getWorkspacePathForIssue(projectPath, issueId);\n\n const continueBody = yield* Effect.promise(() => readWorkspaceContinueFile(projectPath, workspacePath, issueId));\n if (continueBody) {\n return jsonResponse({ issueId: parsedIssueId, body: continueBody });\n }\n\n return jsonResponse({ error: 'Planning state not found for this workspace' }, { status: 404 });\n }))\n);\n\nconst getWorkspaceInferenceMdRoute = HttpRouter.add(\n 'GET',\n '/api/workspaces/:issueId/inference-md',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n\n return yield* Effect.promise(() =>\n readWorkspacePlanningMarkdown(issueId, 'INFERENCE.md')\n .then((result) => jsonResponse(result))\n .catch((err: unknown) => {\n if (\n typeof err === 'object'\n && err !== null\n && ('code' in err || 'message' in err)\n && ((err as { code?: unknown }).code === 'ENOENT'\n || String((err as { message?: unknown }).message ?? '').includes('Invalid issue ID'))\n ) {\n return jsonResponse({ error: 'INFERENCE.md not found for this workspace' }, { status: 404 });\n }\n console.error('[workspaces] Failed to read INFERENCE.md:', err);\n return jsonResponse({ error: 'Internal server error' }, { status: 500 });\n })\n );\n }))\n);\n\n// ─── Route: POST /api/issues/:issueId/start ───────────────────────────────\n\nconst postWorkspaceStartRoute = HttpRouter.add(\n 'POST',\n '/api/issues/:issueId/start',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const issueLower = issueId.toLowerCase();\n const workspacePath = join(projectPath, 'workspaces', `feature-${issueLower}`);\n\n if (!existsSync(workspacePath)) {\n return jsonResponse({ error: 'Workspace does not exist' }, { status: 400 });\n }\n\n const workspaceBeadsDir = join(workspacePath, '.beads');\n if (!existsSync(workspaceBeadsDir)) {\n const projectRootBeadsDir = join(projectPath, '.beads');\n if (existsSync(projectRootBeadsDir)) {\n try {\n yield* Effect.promise(() => execAsync(`cp -r \"${projectRootBeadsDir}\" \"${workspaceBeadsDir}\"`, {\n encoding: 'utf-8',\n }));\n console.log(\n `[workspace/start] Copied beads from project root to workspace for ${issueId}`\n );\n } catch (e) {\n console.warn(`[workspace/start] Could not copy beads: ${e}`);\n }\n }\n }\n\n // Check for ./dev script\n const devScript = join(workspacePath, 'dev');\n const devScriptInContainer = join(workspacePath, '.devcontainer', 'dev');\n\n if (!existsSync(devScript)) {\n if (existsSync(devScriptInContainer)) {\n try {\n yield* Effect.promise(() => symlink('.devcontainer/dev', devScript));\n yield* Effect.promise(() => chmod(devScriptInContainer, 0o755));\n console.log(`[workspace/start] Repaired: created ./dev symlink for ${issueId}`);\n } catch (repairErr) {\n return jsonResponse(\n {\n error: `Workspace has no ./dev script and repair failed: ${repairErr}`,\n },\n { status: 400 }\n );\n }\n } else {\n return jsonResponse(\n { error: 'Workspace has no ./dev script (checked root and .devcontainer/)' },\n { status: 400 }\n );\n }\n }\n\n // Repair .env if needed\n const envFilePath = join(workspacePath, '.env');\n const teamPrefix = extractTeamPrefix(issueId);\n const projectConfig = teamPrefix ? findProjectByTeamSync(teamPrefix) : null;\n\n if (projectConfig?.workspace?.ports && projectConfig?.workspace?.env?.template) {\n const featureFolder = `feature-${issueLower}`;\n let needsRepair = !existsSync(envFilePath);\n\n if (!needsRepair && existsSync(envFilePath)) {\n const existingEnv = yield* Effect.promise(() => readFile(envFilePath, 'utf-8'));\n for (const portName of Object.keys(projectConfig.workspace.ports)) {\n const portVar = `${portName.toUpperCase()}_PORT`;\n if (!existingEnv.includes(portVar)) {\n needsRepair = true;\n break;\n }\n }\n }\n\n if (needsRepair) {\n try {\n const placeholders: Record<string, string> = { FEATURE_FOLDER: featureFolder };\n for (const [portName, portConfig] of Object.entries(\n projectConfig.workspace.ports\n )) {\n const portFile = join(projectPath, `.${portName}-ports`);\n const range = (portConfig as any).range as [number, number];\n let content = '';\n if (existsSync(portFile)) content = yield* Effect.promise(() => readFile(portFile, 'utf-8'));\n const lines = content.split('\\n').filter(Boolean);\n let port: number | null = null;\n for (const line of lines) {\n const [folder, p] = line.split(':');\n if (folder === featureFolder) {\n port = parseInt(p, 10);\n break;\n }\n }\n if (!port) {\n const usedPorts = new Set(lines.map(l => parseInt(l.split(':')[1], 10)));\n for (let p = range[0]; p <= range[1]; p++) {\n if (!usedPorts.has(p)) {\n port = p;\n yield* Effect.promise(() => writeFile(\n portFile,\n content +\n (content.endsWith('\\n') || !content ? '' : '\\n') +\n `${featureFolder}:${port}\\n`\n ));\n break;\n }\n }\n }\n if (port) placeholders[`${portName.toUpperCase()}_PORT`] = String(port);\n }\n let envContent = projectConfig.workspace.env.template;\n for (const [key, value] of Object.entries(placeholders)) {\n envContent = envContent.replace(\n new RegExp(`\\\\{\\\\{${key}\\\\}\\\\}`, 'g'),\n value\n );\n }\n yield* Effect.promise(() => writeFile(envFilePath, envContent));\n console.log(\n `[workspace/start] Repaired: created .env with port assignments for ${issueId}`\n );\n } catch (envErr) {\n console.warn(\n `[workspace/start] Could not repair .env for ${issueId}: ${envErr}`\n );\n }\n }\n }\n\n // Check Docker is running\n try {\n yield* Effect.promise(() => execAsync('docker info >/dev/null 2>&1', { encoding: 'utf-8' }));\n } catch {\n return jsonResponse(\n { error: 'Docker is not running. Start Docker Desktop first.' },\n { status: 400 }\n );\n }\n\n // Pre-start Flyway repair if applicable\n if (projectConfig?.workspace?.database?.migrations?.type === 'flyway') {\n try {\n const composePaths = [\n join(workspacePath, '.devcontainer/docker-compose.devcontainer.yml'),\n join(workspacePath, 'docker-compose.yml'),\n ];\n let compFile: string | undefined;\n for (const cp of composePaths) {\n if (existsSync(cp)) { compFile = cp; break; }\n }\n if (compFile) {\n const { stdout: pnOut } = yield* Effect.promise(() => execAsync(\n `docker compose -f \"${compFile}\" config --format json 2>/dev/null | jq -r '.name // empty'`,\n { encoding: 'utf-8' }\n ));\n const composeName = pnOut.trim();\n if (composeName) {\n const pgContainer = `${composeName}-postgres-1`;\n const result = yield* Effect.promise(() => repairFlywayIfNeeded(\n issueId,\n pgContainer,\n 'myn',\n projectConfig,\n workspacePath\n ));\n if (result.repaired) {\n console.log(`[workspace/start] Pre-start Flyway repair: ${result.message}`);\n }\n }\n }\n } catch (preCheckErr: any) {\n console.log(\n `[workspace/start] Pre-start Flyway check skipped: ${preCheckErr.message}`\n );\n }\n }\n\n const activityId = Date.now().toString();\n logActivity({\n id: activityId,\n timestamp: new Date().toISOString(),\n command: `./dev all (${issueId})`,\n status: 'running',\n output: [],\n });\n\n const uid = process.getuid?.() ?? 1000;\n const gid = process.getgid?.() ?? 1000;\n\n const child = spawn('./dev', ['all'], {\n cwd: workspacePath,\n detached: true,\n stdio: ['ignore', 'pipe', 'pipe'],\n env: buildChildEnvWithoutTmuxSync(process.env, { UID: String(uid), GID: String(gid), DOCKER_USER: `${uid}:${gid}` }),\n });\n\n child.stdout?.on('data', (data) => {\n data.toString().split('\\n').filter(Boolean).forEach((line: string) => {\n appendActivityOutput(activityId, line);\n });\n });\n child.stderr?.on('data', (data) => {\n data.toString().split('\\n').filter(Boolean).forEach((line: string) => {\n appendActivityOutput(activityId, `[stderr] ${line}`);\n });\n });\n\n child.on('close', (code) => {\n appendActivityOutput(\n activityId,\n `[${new Date().toISOString()}] ./dev all exited with code ${code}`\n );\n\n if (code !== 0 && projectConfig?.workspace?.database?.migrations?.type === 'flyway') {\n (async () => {\n try {\n const composePaths = [\n join(workspacePath, '.devcontainer/docker-compose.devcontainer.yml'),\n join(workspacePath, 'docker-compose.yml'),\n ];\n let composeFile: string | undefined;\n for (const cp of composePaths) {\n if (existsSync(cp)) { composeFile = cp; break; }\n }\n if (!composeFile) return;\n\n const { stdout: pnOut } = await execAsync(\n `docker compose -f \"${composeFile}\" config --format json 2>/dev/null | jq -r '.name // empty'`,\n { encoding: 'utf-8' }\n );\n const composeName = pnOut.trim();\n if (!composeName) return;\n\n const apiContainer = `${composeName}-api-1`;\n const pgContainer = `${composeName}-postgres-1`;\n\n const { stdout: apiStatus } = await execAsync(\n `docker ps -a --filter \"name=^${apiContainer}$\" --format \"{{.Status}}\" 2>/dev/null`,\n { encoding: 'utf-8' }\n );\n if (!apiStatus.trim().startsWith('Exited')) return;\n\n const { stdout: logs } = await execAsync(\n `docker logs --tail 50 \"${apiContainer}\" 2>&1 || true`,\n { encoding: 'utf-8', timeout: 10000 }\n );\n if (!logs.toLowerCase().includes('flyway')) return;\n\n appendActivityOutput(activityId, '');\n appendActivityOutput(\n activityId,\n '=== Detected Flyway failure — attempting auto-repair ==='\n );\n\n const result = await repairFlywayIfNeeded(\n issueId,\n pgContainer,\n 'myn',\n projectConfig,\n workspacePath,\n (msg) => appendActivityOutput(activityId, `[flyway-repair] ${msg}`)\n );\n\n if (result.repaired) {\n appendActivityOutput(activityId, `[flyway-repair] Restarting API container...`);\n await execAsync(`docker start \"${apiContainer}\"`, {\n encoding: 'utf-8',\n timeout: 30000,\n });\n appendActivityOutput(\n activityId,\n `[flyway-repair] API container restarted successfully`\n );\n updateActivity(activityId, { status: 'completed' });\n return;\n }\n } catch (repairErr: any) {\n appendActivityOutput(\n activityId,\n `[flyway-repair] Auto-repair failed: ${repairErr.message}`\n );\n }\n updateActivity(activityId, { status: 'failed' });\n })();\n } else {\n updateActivity(activityId, { status: code === 0 ? 'completed' : 'failed' });\n }\n });\n\n child.on('error', (err) => {\n appendActivityOutput(activityId, `[error] ${err.message}`);\n updateActivity(activityId, { status: 'failed' });\n });\n\n return jsonResponse({\n success: true,\n message: `Starting containers for ${issueId}`,\n activityId,\n });\n }))\n);\n\n// ─── Route: GET /api/review/:issueId/status ───────────────────────\n\nconst getWorkspaceReviewStatusRoute = HttpRouter.add(\n 'GET',\n '/api/review/:issueId/status',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n\n const status = getReviewStatusSync(issueId);\n const base: ReviewStatus = status || {\n issueId,\n reviewStatus: 'pending',\n testStatus: 'pending',\n mergeStatus: 'pending',\n readyForMerge: false,\n updatedAt: new Date().toISOString(),\n };\n\n let { queuePosition, activeSpecialist } = computeQueuePositionFromStatusSync(status);\n\n // Discover active parallel review sessions for this issue\n let reviewCoordinatorSessionName: string | undefined;\n let reviewSessionNames: string[] | undefined;\n let reviewSubStatuses: Record<string, 'running' | 'done'> | undefined;\n try {\n const allSessions = yield* listSessionNames();\n const enriched = enrichReviewStatusFromSessions(issueId, base, allSessions);\n reviewCoordinatorSessionName = enriched.reviewCoordinatorSessionName;\n reviewSessionNames = enriched.reviewSessionNames;\n reviewSubStatuses = enriched.reviewSubStatuses;\n } catch { /* non-fatal: tmux may not be available */ }\n\n // Only the merge queue is persistent — check it when no active phase is detected\n if (queuePosition === null) {\n try {\n const resolved = resolveProjectFromIssueSync(issueId);\n if (resolved) {\n const { getQueueForProject } = yield* Effect.promise(() =>\n import('../../../lib/overdeck/merge.js')\n );\n const mergeQueue = getQueueForProject(resolved.projectKey);\n const mergePos = findPositionInQueueSync(issueId, mergeQueue.map(e => ({\n id: String(e.id),\n type: 'task' as const,\n priority: 'normal' as const,\n source: 'merge-queue',\n payload: { issueId: e.issueId },\n createdAt: e.queuedAt,\n })));\n if (mergePos > 0) {\n queuePosition = mergePos;\n activeSpecialist = 'merge';\n }\n }\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[review-status] Merge queue lookup failed for ${issueId} (non-fatal): ${msg}`);\n }\n }\n\n return jsonResponse({ ...base, queuePosition, activeSpecialist, reviewCoordinatorSessionName, reviewSessionNames, reviewSubStatuses });\n }))\n);\n\n// ─── Route: POST /api/review/:issueId/status ──────────────────────\n\nconst postWorkspaceReviewStatusRoute = HttpRouter.add(\n 'POST',\n '/api/review/:issueId/status',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const body = yield* readJsonBody;\n const eventStore = yield* EventStoreService;\n const { reviewStatus, testStatus, mergeStatus, reviewNotes, testNotes, verificationStatus, readyForMerge } = body as {\n reviewStatus?: string;\n testStatus?: string;\n mergeStatus?: string;\n reviewNotes?: string;\n testNotes?: string;\n verificationStatus?: string;\n readyForMerge?: boolean;\n };\n\n // Snapshot reviewedAtCommit BEFORE the first setReviewStatus call so canSkipTests\n // fires correctly in that same call — setting it afterward is too late (the\n // async test-agent dispatch is already scheduled).\n const update: Partial<ReviewStatus> = {};\n if (reviewStatus === 'passed') {\n const workspaceInfo = getWorkspaceInfoForIssue(issueId);\n if (workspaceInfo.exists && workspaceInfo.localPath) {\n const localPath = workspaceInfo.localPath;\n const { getWorkspaceGitInfo } = yield* Effect.promise(() => import('../../../lib/git-utils.js'));\n try {\n const gitInfo = yield* getWorkspaceGitInfo(localPath);\n if (gitInfo.HEAD) {\n update.reviewedAtCommit = gitInfo.HEAD;\n }\n } catch { /* non-fatal */ }\n }\n }\n if (reviewStatus) update.reviewStatus = reviewStatus as any;\n if (testStatus) update.testStatus = testStatus as any;\n if (mergeStatus) update.mergeStatus = mergeStatus as any;\n if (reviewNotes) update.reviewNotes = reviewNotes;\n if (testNotes) update.testNotes = testNotes;\n if (verificationStatus) update.verificationStatus = verificationStatus as any;\n if (readyForMerge !== undefined) update.readyForMerge = readyForMerge;\n\n const status = setReviewStatus(issueId, update);\n\n const { getTmuxSessionName } =\n yield* Effect.promise(() => import('../../../lib/cloister/specialists.js'));\n\n const resolvedProject = resolveProjectFromIssueSync(issueId);\n const projectKey = resolvedProject?.projectKey;\n\n if (reviewStatus && ['passed', 'blocked', 'failed'].includes(reviewStatus)) {\n const tmuxSession = getTmuxSessionName('review-agent', projectKey);\n saveAgentRuntimeState(tmuxSession, {\n state: 'idle',\n currentIssue: undefined,\n lastActivity: new Date().toISOString(),\n });\n console.log(`[review-status] Set review-agent (${tmuxSession}) to idle`);\n\n if (['blocked', 'failed'].includes(reviewStatus) && reviewNotes) {\n const agentId = `agent-${issueId.toLowerCase()}`;\n const feedbackBody = `CODE REVIEW ${reviewStatus.toUpperCase()} for ${issueId}:\\n\\n${reviewNotes}\\n\\n## REQUIRED: Fix ALL issues above, then invoke the /rebase-and-submit skill\\n\\n1. Read each blocking issue carefully\\n2. Fix the code for EVERY issue listed\\n3. Run tests locally to verify your fixes\\n4. Commit every change\\n5. Invoke the /rebase-and-submit skill for ${issueId} — this is an atomic task that runs pan done (which handles rebase + push + re-submit internally)\\n\\nDo NOT stop between steps. Do NOT run git push manually — the skill handles it. Do NOT stop until pan done has completed successfully.`;\n try {\n const { writeFeedbackFile } = yield* Effect.promise(() => import(\n '../../../lib/cloister/feedback-writer.js'\n ));\n const wsInfo = getWorkspaceInfoForIssue(issueId);\n const fileResult = yield* Effect.promise(() => writeFeedbackFile({\n issueId,\n workspacePath: wsInfo.localPath,\n specialist: 'review-agent',\n outcome: reviewStatus === 'blocked' ? 'changes-requested' : 'failed',\n summary: `Review ${reviewStatus.toUpperCase()}: ${(reviewNotes || '').slice(0, 80)}`,\n markdownBody: feedbackBody,\n }));\n if (!fileResult.success) {\n console.error(\n `[review-status] Failed to write feedback file for ${issueId}: ${fileResult.error}`\n );\n } else {\n const msg = `SPECIALIST FEEDBACK: review-agent reported ${reviewStatus.toUpperCase()} for ${issueId}.\\n\\nMUST READ: ${fileResult.filePath}\\n\\nUse your Read tool to open this file, read every line, then fix ALL issues. Do NOT stop at the prompt — keep working until every blocking issue is resolved and you have invoked /rebase-and-submit.`;\n const deliveryKind = reviewStatus === 'blocked' ? 'review-blocked' : 'review-failed';\n yield* Effect.promise(() => deliverQueuedFeedback(issueId, deliveryKind, fileResult.filePath!, msg));\n console.log(\n `[review-status] Auto-sent feedback to ${agentId} (file: ${fileResult.relativePath})`\n );\n }\n } catch (err) {\n console.error(`[review-status] Failed to send feedback to ${agentId}:`, err);\n }\n }\n\n if (reviewStatus === 'passed') {\n yield* Effect.promise(() => Effect.runPromise(eventStore.append({\n type: 'pipeline.review-completed',\n timestamp: new Date().toISOString(),\n payload: { issueId, passed: true },\n })));\n console.log(`[review-status] ${issueId} review approved; reactive Cloister will dispatch the test role`);\n } else if (['blocked', 'failed'].includes(reviewStatus)) {\n yield* Effect.promise(() => Effect.runPromise(eventStore.append({\n type: 'pipeline.review-completed',\n timestamp: new Date().toISOString(),\n payload: { issueId, passed: false },\n })));\n }\n }\n\n if (testStatus && ['passed', 'failed', 'skipped'].includes(testStatus)) {\n const tmuxSession = getTmuxSessionName('test-agent', projectKey);\n saveAgentRuntimeState(tmuxSession, {\n state: 'idle',\n currentIssue: undefined,\n lastActivity: new Date().toISOString(),\n });\n console.log(`[review-status] Set test-agent (${tmuxSession}) to idle`);\n\n if (testStatus === 'failed') {\n yield* Effect.promise(() => Effect.runPromise(eventStore.append({\n type: 'pipeline.test-completed',\n timestamp: new Date().toISOString(),\n payload: { issueId, passed: false },\n })));\n }\n\n if (testStatus === 'failed' && testNotes) {\n const agentId = `agent-${issueId.toLowerCase()}`;\n const feedbackBody = `TESTS FAILED for ${issueId}:\\n\\n${testNotes}\\n\\n## REQUIRED: Fix ALL test failures, then invoke the /rebase-and-submit skill\\n\\n1. Read each test failure carefully\\n2. Fix the code causing EVERY failure\\n3. Run the test suite locally to verify your fixes pass\\n4. Commit every change\\n5. Invoke the /rebase-and-submit skill for ${issueId} — this is an atomic task that runs pan done (which handles rebase + push + re-submit internally)\\n\\nDo NOT stop between steps. Do NOT run git push manually — the skill handles it. Do NOT stop until pan done has completed successfully.`;\n try {\n const { writeFeedbackFile } = yield* Effect.promise(() => import(\n '../../../lib/cloister/feedback-writer.js'\n ));\n const wsInfo = getWorkspaceInfoForIssue(issueId);\n const fileResult = yield* Effect.promise(() => writeFeedbackFile({\n issueId,\n workspacePath: wsInfo.localPath,\n specialist: 'test-agent',\n outcome: 'failed',\n summary: `Tests FAILED: ${(testNotes || '').slice(0, 80)}`,\n markdownBody: feedbackBody,\n }));\n if (!fileResult.success) {\n console.error(\n `[review-status] Failed to write test feedback file for ${issueId}: ${fileResult.error}`\n );\n } else {\n const msg = `SPECIALIST FEEDBACK: test-agent reported FAILED for ${issueId}.\\n\\nMUST READ: ${fileResult.filePath}\\n\\nUse your Read tool to open this file, read every line, then fix the failing tests and re-submit. Do NOT stop at the prompt — keep working until all tests pass and you have invoked /rebase-and-submit.`;\n yield* Effect.promise(() => deliverQueuedFeedback(issueId, 'test-failed', fileResult.filePath!, msg));\n console.log(\n `[review-status] Auto-sent test failure to ${agentId} (file: ${fileResult.relativePath})`\n );\n }\n } catch (err) {\n console.error(`[review-status] Failed to send test feedback to ${agentId}:`, err);\n }\n }\n\n if (testStatus === 'passed') {\n // Mark ready for merge when tests pass. Post-rebase verification in\n // triggerMerge() is the real quality gate — don't block on stale pre-merge verification.\n setReviewStatus(issueId, { readyForMerge: true });\n console.log(`[review-status] ${issueId} marked ready for merge after test=passed`);\n\n // Post overdeck/tests=success so the CI test job self-skips on this\n // commit. Mirrors what verification-runner does at the pre-review gate.\n yield* Effect.promise(async () => {\n try {\n const { resolveProjectFromIssueSync, getProjectSync } = await import('../../../lib/projects.js');\n const project = resolveProjectFromIssueSync(issueId);\n const projectCfg = project ? getProjectSync(project.projectKey) : null;\n const repo = projectCfg?.github_repo;\n if (!repo || !repo.includes('/')) return;\n const [owner, name] = repo.split('/');\n const wsInfo = getWorkspaceInfoForIssue(issueId);\n if (!wsInfo?.localPath) return;\n const { postOverdeckTestsStatus } = await import('../../../lib/github-app.js');\n await postOverdeckTestsStatus(wsInfo.localPath, owner!, name!, 'success', 'Test specialist passed');\n } catch (err: any) {\n console.warn(`[review-status] Failed to post overdeck/tests for ${issueId}: ${err.message}`);\n }\n });\n\n yield* Effect.promise(() => Effect.runPromise(eventStore.append({\n type: 'pipeline.test-completed',\n timestamp: new Date().toISOString(),\n payload: { issueId, passed: true },\n })));\n // Notify the whole-issue work agent when one exists. This MUST be an\n // Effect-level catch: a try/catch around `yield*` does not intercept a\n // failed Effect — the failure propagated and 500'd the entire verdict\n // POST after readyForMerge was only partially applied. Swarm issues\n // have no `agent-<issue>` at all, so this path failed on every\n // swarm test-pass (observed live on PAN-1791).\n const notifyAgentId = `agent-${issueId.toLowerCase()}`;\n yield* Effect.tryPromise(() => messageAgent(\n notifyAgentId,\n `ALL CHECKS PASSED for ${issueId}. Review: passed. Tests: passed. Your work is complete — ready for merge. You may stop working on this issue.`\n )).pipe(\n Effect.tap(() => Effect.sync(() => console.log(`[review-status] Notified ${notifyAgentId} that all checks passed`))),\n Effect.catch((err) => Effect.sync(() => console.log(\n `[review-status] Could not notify work agent for ${issueId} (may not be running): ${err instanceof Error ? err.message : String(err)}`\n ))),\n );\n }\n }\n\n return jsonResponse(status);\n }))\n);\n\n// ─── Route: POST /api/review/:issueId/reset ───────────────────────\n\n/** HTTP-contract result from the reset-review endpoint. Exported for unit testing. */\n\n// ─── Route: POST /api/workspaces/:issueId/unstick ────────────────────────\n//\n// Clears the persistent stuck flag set by markWorkspaceStuck() so Deacon\n// resumes normal patrol for this workspace. Does NOT restart the agent —\n// the user should do that separately via the start-agent UI once they have\n// resolved the divergence (e.g. by syncing main and re-approving).\n\n/** HTTP-contract result from the unstick endpoint. Exported for unit testing. */\n\n// ─── Route: POST /api/workspaces/:issueId/deacon-ignore ──────────────────\n\n/**\n * Operator toggle: tell Deacon to stop patrolling this issue. Body:\n * { ignored: boolean, reason?: string }\n *\n * Idempotent — calling with ignored=true repeatedly refreshes the timestamp\n * but otherwise no-ops. Separate from stuck/unstick: stuck is a system-set\n * failure marker, deaconIgnored is an explicit human \"hands off\".\n */\n\n// ─── Route: POST /api/workspaces/:issueId/auto-merge ─────────────────────\n\n/**\n * PAN-1691: operator toggle for the per-issue auto-merge routing key. Body:\n * { autoMerge: boolean | null }\n * `true` = auto-merge (fast lane), `false` = hold for UAT (manual lane),\n * `null` = clear back to the project default. Emits status_changed via the\n * setAutoMerge wrapper so open dashboards reflect the toggle live.\n */\n\n// ─── Route: POST /api/workspaces/:issueId/refresh-token ───────────────────────\n\nconst postWorkspaceRefreshTokenRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces/:issueId/refresh-token',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const issueLower = issueId.toLowerCase();\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const workspacePath = join(projectPath, 'workspaces', `feature-${issueLower}`);\n\n const { refreshWorkspaceToken, isGitHubAppConfigured } = yield* Effect.promise(() => import('../../../lib/github-app.js'));\n if (!isGitHubAppConfigured()) {\n return jsonResponse({ success: false, error: 'GitHub App not configured' }, { status: 400 });\n }\n\n yield* Effect.promise(() => refreshWorkspaceToken(workspacePath));\n return jsonResponse({ success: true, message: `Token refreshed for ${issueId}` });\n })),\n);\n\n// ─── Compose all routes into a single Layer ───────────────────────────────────\n\nexport const workspacesRouteLayer = Layer.mergeAll(\n workspaceDataRouteLayer,\n postWorkspaceRebuildRoute,\n postWorkspaceRebuildAndStartRoute,\n uatStackActionRouteLayer,\n getWorkspaceStateMdRoute,\n getWorkspaceInferenceMdRoute,\n stashCleanRouteLayer,\n containerOpsRouteLayer,\n postWorkspaceStartRoute,\n getWorkspaceReviewStatusRoute,\n postWorkspaceReviewStatusRoute,\n reviewPipelineRouteLayer,\n reviewControlRouteLayer,\n mergeOpsRouteLayer,\n postWorkspaceRefreshTokenRoute,\n);\n\nexport default workspacesRouteLayer;\n"],"mappings":"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"}
1
+ {"version":3,"file":"workspaces-DXLDgXo2.js","names":["resolve","join","sep","existsSync","join","existsSync","dirname","execAsync","join","dirname","readFileSync","repairFlywayIfNeeded","existsSync","statSync","execAsync","existsSync","join","basename","execAsync","getAgentsDir","getClaudeProjectsDir","inferIssueId","recordCostEventsThroughOverdeck","toOverdeckCostEvent","readdir","readFile","execAsync","execFileAsync","join","existsSync","basename","dirname","resolveGitHubIssueShared","getAllActiveQueues","execAsync","execFileAsync","join","stat","existsSync","execAsync","existsSync","join","execAsync","join","execAsync","join","execFileAsync","join","existsSync","dirname","join","homedir","existsSync","loadConfigSync","dirname","setReviewStatusBase"],"sources":["../../src/dashboard/server/workspace-paths.ts","../../src/dashboard/server/routes/dashboard-auth.ts","../../src/dashboard/server/services/issue-cost-resolver.ts","../../src/dashboard/server/services/running-agents-cache.ts","../../src/dashboard/server/pending-feedback.ts","../../src/lib/db-provisioners/types.ts","../../src/lib/db-provisioners/flyway-postgres.ts","../../src/lib/db-provisioners/index.ts","../../src/dashboard/server/routes/workspaces/container-ops.ts","../../src/lib/stashes.ts","../../src/lib/costs/aggregator.ts","../../src/lib/costs/migration.ts","../../src/lib/costs/attribution.ts","../../src/lib/costs/reconciler.ts","../../src/lib/costs/retention.ts","../../src/lib/costs/sync-wal.ts","../../src/lib/costs/index.ts","../../src/dashboard/server/services/merge-queue-service.ts","../../src/dashboard/server/routes/workspaces/git-recovery-advice.ts","../../src/dashboard/server/routes/workspaces/merge-ops.ts","../../src/dashboard/server/routes/workspaces/workspace-data.ts","../../src/dashboard/server/routes/workspaces/stash-clean.ts","../../src/dashboard/server/routes/workspaces/review-pipeline.ts","../../src/dashboard/server/routes/workspaces/review-control.ts","../../src/dashboard/server/routes/workspaces/uat-stack-actions.ts","../../src/dashboard/server/routes/workspaces.ts"],"sourcesContent":["import { join, resolve, sep } from 'node:path';\n\nimport { parseIssueIdSync } from '../../lib/issue-id.js';\n\nexport function getWorkspacePathForIssue(projectPath: string, rawIssueId: string): { parsedIssueId: string; workspacePath: string } {\n const parsed = parseIssueIdSync(rawIssueId);\n if (!parsed) {\n throw new Error('Invalid issue ID');\n }\n\n const workspaceRoot = resolve(join(projectPath, 'workspaces'));\n const workspacePath = resolve(join(workspaceRoot, `feature-${parsed.normalized}`));\n\n if (workspacePath !== workspaceRoot && !workspacePath.startsWith(`${workspaceRoot}${sep}`)) {\n throw new Error('Invalid workspace path');\n }\n\n return {\n parsedIssueId: parsed.raw,\n workspacePath,\n };\n}\n","import { createHash, createHmac, randomBytes, timingSafeEqual } from 'node:crypto';\nimport { existsSync } from 'node:fs';\nimport { networkInterfaces, type NetworkInterfaceInfo } from 'node:os';\n\nimport { Option } from 'effect';\nimport { HttpServerRequest, HttpServerResponse } from 'effect/unstable/http';\n\nimport { getInternalTokenSync, INTERNAL_TOKEN_HEADER } from '../../../lib/internal-token.js';\nimport { jsonResponse } from '../http-helpers.js';\nimport { getHeaderFromMap, getTrustedOrigins, normalizeOrigin, type HeaderMap } from './origin-validation.js';\n\nexport const DASHBOARD_SESSION_COOKIE = 'overdeck_session';\nexport const DASHBOARD_CSRF_HEADER = 'x-overdeck-csrf-token';\n// Session cookie lifetime. Without Max-Age the cookie was a *session* cookie that\n// died when the browser fully closed — so a reopened tab on a trusted origin had\n// no cookie, its mint 401'd, and every mutation failed with \"CSRF token\n// unavailable\" until the operator re-bootstrapped via the one-time URL token.\n// A 30-day rolling expiry means the operator bootstraps once per browser.\nconst DASHBOARD_SESSION_MAX_AGE_SECONDS = 60 * 60 * 24 * 30;\n\nlet browserSessionToken: string | undefined;\nlet browserCsrfToken: string | undefined;\n\nexport function _resetDashboardSessionTokenForTests(): void {\n browserSessionToken = undefined;\n browserCsrfToken = undefined;\n}\n\nfunction getDashboardSessionToken(): string {\n if (browserSessionToken) return browserSessionToken;\n const override = process.env['OVERDECK_DASHBOARD_SESSION_TOKEN'];\n if (override) {\n browserSessionToken = override;\n return browserSessionToken;\n }\n // Derive a stable session token from the persisted internal token so the\n // browser's session cookie survives dashboard restarts. Without this the\n // token was random per-process, so every restart invalidated open tabs'\n // cookies — and a plain refresh could no longer re-mint, since the one-time\n // bootstrap hash token is already consumed on first load. Falls back to a\n // random token only when no internal token is configured, in which case the\n // auth gate already 503s before the session token is ever consulted.\n const internal = getInternalTokenSync();\n browserSessionToken = internal\n ? createHmac('sha256', internal).update('overdeck-dashboard-session-v1').digest('base64url')\n : randomBytes(32).toString('base64url');\n return browserSessionToken;\n}\n\nexport function dashboardCsrfToken(): string {\n if (browserCsrfToken) return browserCsrfToken;\n const override = process.env['OVERDECK_DASHBOARD_CSRF_TOKEN'];\n if (override) {\n browserCsrfToken = override;\n return browserCsrfToken;\n }\n // Same restart-survival derivation as the session token above: the frontend\n // caches this token once per page load (wsTransport session mint), so a\n // random-per-process value 403'd every mutation from open tabs after each\n // dashboard restart — and the flywheel restarts the dashboard on every\n // post-merge deploy. Distinct context string keeps it independent of the\n // session token.\n const internal = getInternalTokenSync();\n browserCsrfToken = internal\n ? createHmac('sha256', internal).update('overdeck-dashboard-csrf-v1').digest('base64url')\n : randomBytes(32).toString('base64url');\n return browserCsrfToken;\n}\n\nfunction getHeader(\n request: HttpServerRequest.HttpServerRequest,\n name: string,\n): string | undefined {\n return getHeaderFromMap(request.headers as HeaderMap, name);\n}\n\nfunction constantTimeTokenEqual(provided: string | undefined, expected: string): boolean {\n if (!provided) return false;\n const providedHash = createHash('sha256').update(provided).digest();\n const expectedHash = createHash('sha256').update(expected).digest();\n return timingSafeEqual(providedHash, expectedHash);\n}\n\nfunction cookieValue(cookieHeader: string | undefined, name: string): string | undefined {\n if (!cookieHeader) return undefined;\n for (const part of cookieHeader.split(';')) {\n const [rawKey, ...rawValue] = part.trim().split('=');\n if (rawKey !== name) continue;\n try {\n return decodeURIComponent(rawValue.join('='));\n } catch {\n return undefined;\n }\n }\n return undefined;\n}\n\nexport function dashboardSessionCookieHeader(options: { secure?: boolean } = {}): string {\n const secure = options.secure ? '; Secure' : '';\n return `${DASHBOARD_SESSION_COOKIE}=${encodeURIComponent(getDashboardSessionToken())}; Path=/; HttpOnly; SameSite=Strict; Max-Age=${DASHBOARD_SESSION_MAX_AGE_SECONDS}${secure}`;\n}\n\nexport function hasDashboardInternalTokenHeaders(headers: HeaderMap): boolean {\n const expected = getInternalTokenSync();\n if (!expected) return false;\n\n const internalHeader = getHeaderFromMap(headers, INTERNAL_TOKEN_HEADER);\n if (constantTimeTokenEqual(internalHeader, expected)) return true;\n\n const authorization = getHeaderFromMap(headers, 'authorization');\n if (authorization) {\n const [scheme, token] = authorization.split(/\\s+/);\n if (scheme?.toLowerCase() === 'bearer' && constantTimeTokenEqual(token, expected)) return true;\n }\n\n return false;\n}\n\nexport function hasDashboardInternalToken(request: HttpServerRequest.HttpServerRequest): boolean {\n return hasDashboardInternalTokenHeaders(request.headers as HeaderMap);\n}\n\nexport function hasDashboardAuthHeaders(headers: HeaderMap): boolean {\n return hasDashboardInternalTokenHeaders(headers) || constantTimeTokenEqual(cookieValue(getHeaderFromMap(headers, 'cookie'), DASHBOARD_SESSION_COOKIE), getDashboardSessionToken());\n}\n\nexport function hasDashboardAuth(request: HttpServerRequest.HttpServerRequest): boolean {\n return hasDashboardAuthHeaders(request.headers as HeaderMap);\n}\n\nfunction isJsonContentType(headers: HeaderMap): boolean {\n const contentType = getHeaderFromMap(headers, 'content-type');\n if (!contentType) return false;\n const [mime] = contentType.toLowerCase().split(';');\n return mime.trim() === 'application/json';\n}\n\nfunction hasTrustedExactOrigin(headers: HeaderMap): boolean {\n const origin = getHeaderFromMap(headers, 'origin');\n if (!origin) return false;\n const normalized = normalizeOrigin(origin);\n return !!normalized && getTrustedOrigins().includes(normalized);\n}\n\nfunction hasValidCsrfToken(headers: HeaderMap): boolean {\n return constantTimeTokenEqual(getHeaderFromMap(headers, DASHBOARD_CSRF_HEADER), dashboardCsrfToken());\n}\n\nexport function rejectUnsafeDashboardMutationRequest(\n request: HttpServerRequest.HttpServerRequest,\n): HttpServerResponse.HttpServerResponse | null {\n const authError = rejectUnauthorizedDashboardRequest(request);\n if (authError) return authError;\n\n const headers = request.headers as HeaderMap;\n if (!isJsonContentType(headers)) {\n return jsonResponse({ error: 'Content-Type must be application/json' }, { status: 400 });\n }\n\n if (hasDashboardInternalToken(request)) return null;\n\n const origin = getHeaderFromMap(headers, 'origin');\n if (origin && !hasTrustedExactOrigin(headers)) {\n return jsonResponse({ error: 'Invalid origin' }, { status: 403 });\n }\n if (!hasValidCsrfToken(headers)) {\n return jsonResponse({ error: 'Invalid CSRF token' }, { status: 403 });\n }\n return null;\n}\n\nfunction ipv4ToInt(value: string): number | null {\n const parts = value.split('.');\n if (parts.length !== 4) return null;\n let result = 0;\n for (const part of parts) {\n const octet = Number(part);\n if (!Number.isInteger(octet) || octet < 0 || octet > 255) return null;\n result = ((result << 8) | octet) >>> 0;\n }\n return result;\n}\n\n/** RFC1918 private ranges — Docker bridges always fall inside one of these. */\nfunction isRfc1918(ip: number): boolean {\n return (\n (ip & 0xff000000) >>> 0 === 0x0a000000 || // 10.0.0.0/8\n (ip & 0xfff00000) >>> 0 === 0xac100000 || // 172.16.0.0/12\n (ip & 0xffff0000) >>> 0 === 0xc0a80000 // 192.168.0.0/16\n );\n}\n\n/**\n * True when `addr` is the IP of a container on one of THIS host's Docker bridge\n * interfaces (docker0 / br-*). Such a peer can only be a process running on this\n * machine — our own host-local Traefik fronting overdeck.localhost, or another\n * Overdeck container — never a LAN client (a direct LAN hit to the 0.0.0.0\n * API port arrives with a LAN peer that is not inside any Docker bridge subnet).\n *\n * Pure for testability: the interface map is injected. We only ever trust\n * RFC1918 bridge subnets, so a misconfigured public-range br-* cannot widen\n * trust beyond the local Docker fabric.\n */\nexport function peerIsHostLocalDockerBridge(\n addr: string,\n interfaces: NodeJS.Dict<NetworkInterfaceInfo[]>,\n): boolean {\n const peer = ipv4ToInt(addr);\n if (peer === null) return false;\n for (const [name, addrs] of Object.entries(interfaces)) {\n if (name !== 'docker0' && !name.startsWith('br-')) continue;\n for (const info of addrs ?? []) {\n if (info.family !== 'IPv4') continue;\n const ifaceIp = ipv4ToInt(info.address);\n const mask = ipv4ToInt(info.netmask);\n if (ifaceIp === null || mask === null) continue;\n const network = (ifaceIp & mask) >>> 0;\n if (!isRfc1918(network)) continue;\n if (((peer & mask) >>> 0) === network) return true;\n }\n }\n return false;\n}\n\n/**\n * True when `addr` shares an RFC1918 subnet with one of THIS process's own\n * network interfaces. This is the in-container counterpart of\n * peerIsHostLocalDockerBridge: inside a container the Docker bridge is\n * invisible — the container sees only its veth endpoints (eth0/eth1), so the\n * docker0/br-* name filter never matches and the host-local Traefik fronting\n * the stack 401'd every session mint (first hit live on the PAN-1737 UAT\n * stack). A peer inside one of the container's attached Docker-network\n * subnets can only be another container on those networks — the server\n * publishes no host port (Traefik labels only), so no LAN client can ever\n * present such an address. Only consulted when running inside a container.\n */\nexport function peerIsLocalContainerNetwork(\n addr: string,\n interfaces: NodeJS.Dict<NetworkInterfaceInfo[]>,\n): boolean {\n const peer = ipv4ToInt(addr);\n if (peer === null) return false;\n for (const addrs of Object.values(interfaces)) {\n for (const info of addrs ?? []) {\n if (info.family !== 'IPv4' || info.internal) continue;\n const ifaceIp = ipv4ToInt(info.address);\n const mask = ipv4ToInt(info.netmask);\n if (ifaceIp === null || mask === null) continue;\n const network = (ifaceIp & mask) >>> 0;\n if (!isRfc1918(network)) continue;\n if (((peer & mask) >>> 0) === network) return true;\n }\n }\n return false;\n}\n\n/** Docker creates /.dockerenv in every container — stable in-container signal. */\nconst runningInContainer = existsSync('/.dockerenv');\n\n/**\n * A request we trust to mint a session because it originated from this machine —\n * the local browser hitting the dashboard directly (127.0.0.1), or the\n * host-local Traefik that fronts overdeck.localhost. In Docker, that Traefik reaches\n * the host server from a Docker-bridge IP (e.g. 172.18.0.2), NOT 127.0.0.1, so\n * a literal-loopback check alone silently breaks the zero-step bootstrap for\n * every overdeck.localhost user — their session mint 401s and no cookie is ever set.\n *\n * This is the auto-bootstrap that removes the manual one-time #overdeck_token\n * step: any browser on overdeck.localhost gets a session with no user action. It is\n * NOT a security downgrade for the LAN — the raw API ports bind 0.0.0.0, but a\n * direct LAN hit has a non-loopback, non-Docker-bridge peer and is rejected. We\n * read ONLY the real TCP peer (request.remoteAddress), never X-Forwarded-For,\n * which a caller could spoof.\n */\nfunction isLoopbackPeer(request: HttpServerRequest.HttpServerRequest): boolean {\n const remoteAddress = (request as { remoteAddress?: unknown }).remoteAddress;\n const raw = remoteAddress && typeof remoteAddress === 'object' && '_tag' in remoteAddress\n ? Option.getOrElse(remoteAddress as Option.Option<string>, () => '')\n : typeof remoteAddress === 'string'\n ? remoteAddress\n : '';\n if (!raw) return false;\n // Node reports dual-stack IPv4 peers as IPv4-mapped IPv6 (::ffff:a.b.c.d).\n const addr = raw.startsWith('::ffff:') ? raw.slice('::ffff:'.length) : raw;\n if (addr === '127.0.0.1' || addr === '::1') return true;\n if (peerIsHostLocalDockerBridge(addr, networkInterfaces())) return true;\n return runningInContainer && peerIsLocalContainerNetwork(addr, networkInterfaces());\n}\n\nexport function rejectUnauthorizedDashboardSessionMintRequest(\n request: HttpServerRequest.HttpServerRequest,\n): HttpServerResponse.HttpServerResponse | null {\n const expected = getInternalTokenSync();\n if (!expected) {\n return jsonResponse({ error: 'dashboard session token not configured' }, { status: 503 });\n }\n if (!hasDashboardInternalToken(request) && !isLoopbackPeer(request)) {\n return jsonResponse({ error: 'unauthorized' }, { status: 401 });\n }\n return null;\n}\n\nexport function rejectUnauthorizedDashboardRequest(\n request: HttpServerRequest.HttpServerRequest,\n): HttpServerResponse.HttpServerResponse | null {\n const expected = getInternalTokenSync();\n if (!expected) {\n return jsonResponse({ error: 'dashboard session token not configured' }, { status: 503 });\n }\n if (!hasDashboardAuth(request)) {\n return jsonResponse({ error: 'unauthorized' }, { status: 401 });\n }\n return null;\n}\n","import { AgentStatus, type AgentSnapshot } from '@overdeck/contracts';\n\ninterface ResolveIssueHeadlineCostOptions {\n issueId: string;\n aggregateCost?: number;\n agents: ReadonlyArray<Pick<AgentSnapshot, 'issueId' | 'costSoFar' | 'status'>>;\n}\n\nexport interface ResolvedIssueHeadlineCost {\n aggregateCost: number | null;\n liveCost: number | null;\n resolvedTotalCost: number | null;\n}\n\nconst LIVE_AGENT_STATUSES = new Set<AgentStatus>(['starting', 'running']);\n\nfunction normalizeIssueId(issueId: string): string {\n return issueId.trim().toUpperCase();\n}\n\nfunction normalizePositiveCost(value: number | null | undefined): number | null {\n return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : null;\n}\n\nexport function getLiveIssueCostForIssue(\n issueId: string,\n agents: ReadonlyArray<Pick<AgentSnapshot, 'issueId' | 'costSoFar' | 'status'>>,\n): number | null {\n const normalizedIssueId = normalizeIssueId(issueId);\n let maxLiveCost: number | null = null;\n\n for (const agent of agents) {\n if (normalizeIssueId(agent.issueId) !== normalizedIssueId) continue;\n if (!LIVE_AGENT_STATUSES.has(agent.status)) continue;\n const candidate = normalizePositiveCost(agent.costSoFar);\n if (candidate === null) continue;\n if (maxLiveCost === null || candidate > maxLiveCost) {\n maxLiveCost = candidate;\n }\n }\n\n return maxLiveCost;\n}\n\nexport function resolveIssueHeadlineCost({\n issueId,\n aggregateCost,\n agents,\n}: ResolveIssueHeadlineCostOptions): ResolvedIssueHeadlineCost {\n const normalizedAggregateCost = normalizePositiveCost(aggregateCost);\n const liveCost = getLiveIssueCostForIssue(issueId, agents);\n\n return {\n aggregateCost: normalizedAggregateCost,\n liveCost,\n resolvedTotalCost:\n normalizedAggregateCost === null\n ? liveCost\n : liveCost === null\n ? normalizedAggregateCost\n : Math.max(normalizedAggregateCost, liveCost),\n };\n}\n","import { Effect } from 'effect';\nimport { listRunningAgents, type AgentState } from '../../../lib/agents.js';\n\ntype RunningAgent = AgentState & { tmuxActive: boolean };\ntype RunningAgents = RunningAgent[];\ntype ListAgentsProgram = () => Effect.Effect<RunningAgents, unknown>;\n\nconst RUNNING_AGENTS_CACHE_TTL_MS = 3_000;\nconst GLOBAL_RUNNING_AGENTS_CACHE_KEY = '__all_agents__';\n\nconst runningAgentsCache = new Map<string, {\n timestamp: number;\n agents: RunningAgents;\n}>();\nlet inflightRunningAgents: Promise<RunningAgents> | null = null;\n\nfunction sweepExpired<T extends { timestamp: number }>(cache: Map<string, T>, ttlMs: number): void {\n const cutoff = Date.now() - ttlMs;\n for (const [key, entry] of cache) {\n if (entry.timestamp < cutoff) {\n cache.delete(key);\n }\n }\n}\n\nexport async function getCachedRunningAgents(\n listAgents: ListAgentsProgram = listRunningAgents,\n): Promise<RunningAgents> {\n sweepExpired(runningAgentsCache, RUNNING_AGENTS_CACHE_TTL_MS);\n const cached = runningAgentsCache.get(GLOBAL_RUNNING_AGENTS_CACHE_KEY);\n if (cached && cached.timestamp > Date.now() - RUNNING_AGENTS_CACHE_TTL_MS) {\n return cached.agents;\n }\n\n if (!inflightRunningAgents) {\n inflightRunningAgents = Effect.runPromise(listAgents()).then((agents) => {\n runningAgentsCache.set(GLOBAL_RUNNING_AGENTS_CACHE_KEY, {\n timestamp: Date.now(),\n agents,\n });\n return agents;\n }).finally(() => {\n inflightRunningAgents = null;\n });\n }\n\n return inflightRunningAgents;\n}\n\nexport function clearRunningAgentsCache(): void {\n runningAgentsCache.clear();\n inflightRunningAgents = null;\n}\n","/**\n * Pending feedback recovery for dashboard restarts (PAN-585).\n *\n * Feedback files and review-status rows are persistent, but tmux delivery is not.\n * When the dashboard dies after writing feedback and before messageAgent() completes,\n * startup replays the queued delivery so the work agent is notified.\n */\n\nimport { existsSync } from 'node:fs';\nimport { mkdir, readFile, unlink, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { Effect } from 'effect';\nimport { messageAgent, getAgentState, type AgentState } from '../../lib/agents.js';\nimport { getReviewStatusSync, loadReviewStatuses, type ReviewStatus } from '../../lib/review-status.js';\nimport { getOverdeckHome } from '../../lib/paths.js';\nimport { emitActivityEntrySync } from '../../lib/activity-logger.js';\n\nconst PENDING_FEEDBACK_FILE = join(getOverdeckHome(), 'pending-feedback-deliveries.json');\nconst STALE_THRESHOLD_MS = 7 * 24 * 60 * 60 * 1000;\n\ntype FeedbackKind = 'review-blocked' | 'review-failed' | 'test-failed';\n\nexport interface PendingFeedbackDelivery {\n issueId: string;\n agentId: string;\n kind: FeedbackKind;\n filePath: string;\n message: string;\n createdAt: string;\n}\n\ninterface PendingFeedbackStore {\n deliveries: PendingFeedbackDelivery[];\n}\n\nasync function readStore(filePath: string): Promise<PendingFeedbackStore> {\n if (!existsSync(filePath)) {\n return { deliveries: [] };\n }\n\n try {\n const raw = await readFile(filePath, 'utf-8');\n if (!raw.trim()) return { deliveries: [] };\n const parsed = JSON.parse(raw) as PendingFeedbackStore;\n return { deliveries: Array.isArray(parsed.deliveries) ? parsed.deliveries : [] };\n } catch {\n return { deliveries: [] };\n }\n}\n\nasync function writeStore(filePath: string, store: PendingFeedbackStore): Promise<void> {\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(filePath, JSON.stringify(store, null, 2), 'utf-8');\n}\n\nfunction isDeliveryStillRelevant(delivery: PendingFeedbackDelivery, status: ReviewStatus | null | undefined): boolean {\n switch (delivery.kind) {\n case 'review-blocked':\n return status?.reviewStatus === 'blocked';\n case 'review-failed':\n return status?.reviewStatus === 'failed';\n case 'test-failed':\n return status?.testStatus === 'failed';\n default:\n return false;\n }\n}\n\nexport async function enqueuePendingFeedbackDelivery(\n delivery: PendingFeedbackDelivery,\n options?: { filePath?: string }\n): Promise<void> {\n const filePath = options?.filePath ?? PENDING_FEEDBACK_FILE;\n const store = await readStore(filePath);\n store.deliveries = store.deliveries.filter(\n (existing) => !(existing.issueId === delivery.issueId && existing.kind === delivery.kind)\n );\n store.deliveries.push(delivery);\n await writeStore(filePath, store);\n}\n\nexport async function markPendingFeedbackDelivered(\n issueId: string,\n kind: FeedbackKind,\n options?: { filePath?: string }\n): Promise<void> {\n const filePath = options?.filePath ?? PENDING_FEEDBACK_FILE;\n const store = await readStore(filePath);\n const next = store.deliveries.filter(\n (delivery) => !(delivery.issueId === issueId && delivery.kind === kind)\n );\n\n if (next.length === store.deliveries.length) return;\n\n if (next.length === 0) {\n if (existsSync(filePath)) {\n await unlink(filePath).catch(() => {});\n }\n return;\n }\n\n await writeStore(filePath, { deliveries: next });\n}\n\nexport async function processPendingFeedbackDeliveries(options?: {\n filePath?: string;\n staleThresholdMs?: number;\n now?: number;\n _deliver?: (agentId: string, message: string) => Promise<void>;\n _getAgentState?: (agentId: string) => Promise<AgentState | null>;\n _loadStatuses?: typeof loadReviewStatuses;\n _getStatus?: typeof getReviewStatusSync;\n}): Promise<void> {\n const filePath = options?.filePath ?? PENDING_FEEDBACK_FILE;\n const staleThresholdMs = options?.staleThresholdMs ?? STALE_THRESHOLD_MS;\n const now = options?.now ?? Date.now();\n const deliver = options?._deliver ?? messageAgent;\n const getAgentState = options?._getAgentState ?? ((agentId: string) => Effect.runPromise(getAgentState(agentId)));\n const loadStatuses = options?._loadStatuses ?? loadReviewStatuses;\n const getStatus = options?._getStatus ?? getReviewStatusSync;\n\n if (!existsSync(filePath)) return;\n\n const store = await readStore(filePath);\n if (store.deliveries.length === 0) {\n await unlink(filePath).catch(() => {});\n return;\n }\n\n const statuses = loadStatuses();\n const remaining: PendingFeedbackDelivery[] = [];\n\n for (const delivery of store.deliveries) {\n const createdAt = Date.parse(delivery.createdAt);\n if (Number.isFinite(createdAt) && now - createdAt > staleThresholdMs) {\n continue;\n }\n\n const status = statuses[delivery.issueId] ?? getStatus(delivery.issueId);\n if (!isDeliveryStillRelevant(delivery, status)) {\n continue;\n }\n\n const agentState = await getAgentState(delivery.agentId);\n if (!agentState) {\n remaining.push(delivery);\n continue;\n }\n\n try {\n await deliver(delivery.agentId, delivery.message);\n emitActivityEntrySync({\n source: 'dashboard',\n level: 'warn',\n message: `${delivery.issueId} — replayed missed ${delivery.kind} feedback after restart`,\n issueId: delivery.issueId,\n details: delivery.filePath,\n });\n } catch {\n remaining.push(delivery);\n }\n }\n\n if (remaining.length === 0) {\n await unlink(filePath).catch(() => {});\n return;\n }\n\n await writeStore(filePath, { deliveries: remaining });\n}\n","import type { ProjectConfig, DatabaseConfig } from '../workspace-config.js';\n\nexport interface DatabaseProvisionerLogger {\n setText(message: string): void;\n info(message: string): void;\n warn(message: string): void;\n fail(message: string): void;\n succeed(message: string): void;\n log(message: string): void;\n}\n\nexport interface DatabaseProvisionerErrorOptions {\n status?: number;\n}\n\nexport class DatabaseProvisionerError extends Error {\n readonly status: number;\n\n constructor(message: string, options: DatabaseProvisionerErrorOptions = {}) {\n super(message);\n this.name = 'DatabaseProvisionerError';\n this.status = options.status ?? 500;\n }\n}\n\nexport interface RefreshDatabaseContext {\n issueId: string;\n projectConfig: ProjectConfig;\n workspacePath: string;\n pgContainer: string;\n apiContainer: string;\n databaseName: string;\n seedFile: string;\n seedVerifyQuery?: string;\n logger?: DatabaseProvisionerLogger;\n}\n\nexport interface RefreshDatabaseResult {\n seedVerifyResult?: string;\n}\n\nexport interface SeedDatabaseContext {\n workspaceOrIssue: string;\n projectConfig: ProjectConfig;\n workspacePath: string;\n pgContainer: string;\n databaseName: string;\n seedFile: string;\n force?: boolean;\n logger?: DatabaseProvisionerLogger;\n}\n\nexport interface RepairMigrationsContext {\n issueId: string;\n projectConfig: ProjectConfig;\n workspacePath: string;\n pgContainer: string;\n databaseName: string;\n logger?: DatabaseProvisionerLogger;\n log?: (message: string) => void;\n}\n\nexport interface RepairMigrationsResult {\n repaired: boolean;\n message: string;\n}\n\nexport interface SnapshotDatabaseContext {\n projectConfig: ProjectConfig;\n dbConfig: DatabaseConfig;\n output?: string;\n sanitize?: boolean;\n logger?: DatabaseProvisionerLogger;\n}\n\nexport interface SnapshotDatabaseResult {\n outputPath: string;\n sizeBytes: number;\n}\n\nexport interface CleanSnapshotContext {\n file: string;\n output?: string;\n dryRun?: boolean;\n logger?: DatabaseProvisionerLogger;\n}\n\nexport interface CleanSnapshotResult {\n outputPath?: string;\n originalLines: number;\n cleanedLines: number;\n removedLines: number;\n removedSample: string[];\n}\n\nexport interface StatusDatabaseContext {\n projectConfig: ProjectConfig;\n pgContainer: string;\n databaseName: string;\n logger?: DatabaseProvisionerLogger;\n}\n\nexport interface DatabaseProvisioner {\n readonly id: string;\n validateRefreshDatabase(ctx: Pick<RefreshDatabaseContext, 'seedFile'>): void;\n refreshDatabase(ctx: RefreshDatabaseContext): Promise<RefreshDatabaseResult>;\n seed(ctx: SeedDatabaseContext): Promise<void>;\n repairMigrations(ctx: RepairMigrationsContext): Promise<RepairMigrationsResult>;\n snapshot(ctx: SnapshotDatabaseContext): Promise<SnapshotDatabaseResult>;\n cleanSnapshot(ctx: CleanSnapshotContext): Promise<CleanSnapshotResult>;\n status(ctx: StatusDatabaseContext): Promise<void>;\n}\n","import { exec } from 'node:child_process';\nimport { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';\nimport { readdir, readFile, unlink, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { promisify } from 'node:util';\nimport { crc32 } from 'node:zlib';\n\nimport type { DatabaseConfig } from '../workspace-config.js';\nimport {\n DatabaseProvisionerError,\n type CleanSnapshotContext,\n type CleanSnapshotResult,\n type DatabaseProvisioner,\n type DatabaseProvisionerLogger,\n type RefreshDatabaseContext,\n type RefreshDatabaseResult,\n type RepairMigrationsContext,\n type RepairMigrationsResult,\n type SeedDatabaseContext,\n type SnapshotDatabaseContext,\n type SnapshotDatabaseResult,\n type StatusDatabaseContext,\n} from './types.js';\n\nconst execAsync = promisify(exec);\n\nfunction shellQuote(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\n\nfunction sqlIdentifier(value: string): string {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n}\n\nfunction sqlString(value: string): string {\n return value.replace(/'/g, \"''\");\n}\n\nfunction defaultLogger(prefix: string): DatabaseProvisionerLogger {\n return {\n setText(message) { console.log(`${prefix} ${message}`); },\n info(message) { console.log(`${prefix} ${message}`); },\n warn(message) { console.warn(`${prefix} ${message}`); },\n fail(message) { console.error(`${prefix} ${message}`); },\n succeed(message) { console.log(`${prefix} ${message}`); },\n log(message) { console.log(`${prefix} ${message}`); },\n };\n}\n\nfunction requireLogger(\n logger: DatabaseProvisionerLogger | undefined,\n prefix: string\n): DatabaseProvisionerLogger {\n return logger ?? defaultLogger(prefix);\n}\n\nfunction getFlywayBaselineFile(seedFile: string): string {\n return join(dirname(seedFile), 'zzz-flyway-workspace-baseline.sql');\n}\n\nfunction getSnapshotCommand(dbConfig: DatabaseConfig): string | null {\n if (dbConfig.snapshot_command) {\n return dbConfig.snapshot_command;\n }\n\n if (dbConfig.external_db) {\n const ext = dbConfig.external_db;\n const password = ext.password_env ? process.env[ext.password_env] : '';\n if (ext.password_env && !password) {\n throw new DatabaseProvisionerError(`Environment variable ${ext.password_env} not set`);\n }\n return `PGPASSWORD=\"${password}\" pg_dump -h ${ext.host} -p ${ext.port || 5432} -U ${ext.user || 'postgres'} ${ext.database}`;\n }\n\n return null;\n}\n\nfunction getSnapshotHelp(projectKey: string): string {\n return `\n ${projectKey}:\n workspace:\n database:\n name: myapp\n snapshot_command: \"kubectl exec -n prod pod/postgres -- pg_dump -U app mydb\"\n # or\n external_db:\n host: prod-db.example.com\n database: myapp\n user: readonly\n password_env: PROD_DB_PASSWORD\n`;\n}\n\nfunction kubectlNoisePatterns(): RegExp[] {\n return [\n /^Defaulted container/,\n /^Unable to use a TTY/,\n /^All commands and output from this session/,\n /^If you don't see a command prompt/,\n /^warning: couldn't attach to pod/,\n /^pod \".*\" deleted from .* namespace$/,\n /^\\[stderr\\]/,\n /^error: timed out waiting/,\n ];\n}\n\nasync function cleanFile(filePath: string): Promise<void> {\n const content = readFileSync(filePath, 'utf-8');\n const lines = content.split('\\n');\n\n let startIndex = 0;\n for (let i = 0; i < lines.length; i++) {\n if (lines[i].startsWith('--') && lines[i].includes('PostgreSQL database dump')) {\n startIndex = i;\n break;\n }\n }\n\n const patternsToRemove = kubectlNoisePatterns();\n const cleanedLines = lines\n .slice(startIndex)\n .filter((line) => !patternsToRemove.some((p) => p.test(line)));\n\n writeFileSync(filePath, cleanedLines.join('\\n'));\n}\n\nexport async function repairFlywayIfNeeded(\n issueId: string,\n pgContainer: string,\n dbName: string,\n projectConfig: RepairMigrationsContext['projectConfig'],\n workspacePath: string,\n log?: (msg: string) => void\n): Promise<RepairMigrationsResult> {\n void issueId;\n const emit = log || ((msg: string) => console.log(`[flyway-repair] ${msg}`));\n\n try {\n await execAsync(`docker exec \"${pgContainer}\" pg_isready -U postgres`, {\n encoding: 'utf-8',\n timeout: 5000,\n });\n } catch {\n return { repaired: false, message: 'Postgres container not ready, skipping Flyway check' };\n }\n\n let rowCount = 0;\n try {\n const { stdout } = await execAsync(\n `docker exec \"${pgContainer}\" psql -U postgres -d ${dbName} -t -A -c \"SELECT count(*) FROM flyway_schema_history;\"`,\n { encoding: 'utf-8', timeout: 10000 }\n );\n rowCount = parseInt(stdout.trim(), 10) || 0;\n } catch {\n rowCount = 0;\n }\n\n if (rowCount >= 10) {\n return { repaired: false, message: `Flyway schema_history has ${rowCount} entries, no repair needed` };\n }\n\n emit(`Flyway schema_history has only ${rowCount} entries — repairing`);\n\n const seedRelPath = projectConfig.workspace?.database?.seed_file;\n if (!seedRelPath) {\n return { repaired: false, message: 'No seed_file configured, cannot locate Flyway baseline' };\n }\n\n const seedFile = join(projectConfig.path, seedRelPath);\n const flywayFile = getFlywayBaselineFile(seedFile);\n if (!existsSync(flywayFile)) {\n return { repaired: false, message: `Flyway baseline not found: ${flywayFile}` };\n }\n\n emit(`Loading Flyway baseline from ${flywayFile}`);\n await execAsync(\n `docker exec -i \"${pgContainer}\" psql -U postgres -d ${dbName} < \"${flywayFile}\"`,\n { encoding: 'utf-8', timeout: 60000 }\n );\n\n const migrationsRelPath = projectConfig.workspace?.database?.migrations?.path;\n if (migrationsRelPath) {\n const migrationsDir = join(workspacePath, migrationsRelPath);\n if (existsSync(migrationsDir)) {\n emit(`Syncing Flyway checksums from workspace migrations`);\n const migrationFiles = (await readdir(migrationsDir)).filter(f => /^V\\d+__.*\\.sql$/.test(f));\n const updates: string[] = [];\n\n for (const file of migrationFiles) {\n const version = file.match(/^V(\\d+)__/)?.[1];\n if (!version) continue;\n let content = await readFile(join(migrationsDir, file));\n if (content[0] === 0xEF && content[1] === 0xBB && content[2] === 0xBF) {\n content = content.slice(3);\n }\n const lines = content.toString('utf-8').split(/\\r?\\n/);\n const checksum = crc32(Buffer.from(lines.join(''), 'utf-8')) | 0;\n updates.push(\n `UPDATE flyway_schema_history SET checksum = ${checksum} WHERE version = '${version}' AND checksum IS NOT NULL;`\n );\n }\n\n if (updates.length > 0) {\n const tmpSql = `/tmp/flyway-checksum-sync-${Date.now()}.sql`;\n await writeFile(tmpSql, updates.join('\\n'));\n try {\n const { stdout } = await execAsync(\n `docker exec -i \"${pgContainer}\" psql -U postgres -d ${dbName} < \"${tmpSql}\"`,\n { encoding: 'utf-8', timeout: 30000 }\n );\n const updatedCount = (stdout.match(/UPDATE \\d+/g) || [])\n .reduce((sum, m) => sum + parseInt(m.replace('UPDATE ', ''), 10), 0);\n emit(`Synced ${migrationFiles.length} migration checksums (${updatedCount} rows updated)`);\n } finally {\n try { await unlink(tmpSql); } catch {}\n }\n }\n }\n }\n\n try {\n const { stdout } = await execAsync(\n `docker exec \"${pgContainer}\" psql -U postgres -d ${dbName} -t -A -c \"SELECT count(*) FROM flyway_schema_history;\"`,\n { encoding: 'utf-8', timeout: 10000 }\n );\n const newCount = parseInt(stdout.trim(), 10) || 0;\n emit(`Repair complete: flyway_schema_history now has ${newCount} entries (was ${rowCount})`);\n return { repaired: true, message: `Repaired Flyway schema_history: ${rowCount} → ${newCount} entries` };\n } catch (err: unknown) {\n return {\n repaired: false,\n message: `Repair may have failed: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n}\n\nexport const flywayPostgresProvisioner: DatabaseProvisioner = {\n id: 'flyway-postgres',\n\n validateRefreshDatabase(ctx): void {\n const flywayFile = getFlywayBaselineFile(ctx.seedFile);\n if (!existsSync(flywayFile)) {\n throw new DatabaseProvisionerError(`Flyway baseline not found: ${flywayFile}`, {\n status: 400,\n });\n }\n },\n\n async refreshDatabase(ctx: RefreshDatabaseContext): Promise<RefreshDatabaseResult> {\n const logger = requireLogger(ctx.logger, '[refresh-db]');\n\n logger.log(`Starting DB refresh for ${ctx.issueId}`);\n\n try {\n await execAsync(`docker stop \"${ctx.apiContainer}\"`, { encoding: 'utf-8', timeout: 30000 });\n } catch {\n logger.log('API container not running or already stopped');\n }\n\n await execAsync(\n `docker exec ${shellQuote(ctx.pgContainer)} psql -U postgres -d postgres -c \"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '${sqlString(ctx.databaseName)}' AND pid <> pg_backend_pid();\"`,\n { encoding: 'utf-8', timeout: 10000 }\n );\n await execAsync(\n `docker exec ${shellQuote(ctx.pgContainer)} psql -U postgres -d postgres -c \"DROP DATABASE IF EXISTS ${sqlIdentifier(ctx.databaseName)};\"`,\n { encoding: 'utf-8', timeout: 10000 }\n );\n await execAsync(\n `docker exec ${shellQuote(ctx.pgContainer)} psql -U postgres -d postgres -c \"CREATE DATABASE ${sqlIdentifier(ctx.databaseName)} OWNER postgres;\"`,\n { encoding: 'utf-8', timeout: 10000 }\n );\n\n logger.log(`Loading seed file: ${ctx.seedFile}`);\n await execAsync(\n `docker exec -i ${shellQuote(ctx.pgContainer)} psql -U postgres -d ${shellQuote(ctx.databaseName)} < ${shellQuote(ctx.seedFile)}`,\n { encoding: 'utf-8', timeout: 600000 }\n );\n\n const repairResult = await repairFlywayIfNeeded(\n ctx.issueId,\n ctx.pgContainer,\n ctx.databaseName,\n ctx.projectConfig,\n ctx.workspacePath,\n (msg) => logger.log(msg)\n );\n logger.log(`Flyway setup: ${repairResult.message}`);\n\n try {\n await execAsync(`docker start \"${ctx.apiContainer}\"`, { encoding: 'utf-8', timeout: 30000 });\n } catch {\n logger.log('Could not start API container (may need manual start)');\n }\n\n let seedVerifyResult: string | undefined;\n if (ctx.seedVerifyQuery) {\n try {\n const { stdout } = await execAsync(\n `docker exec ${shellQuote(ctx.pgContainer)} psql -U postgres -d ${shellQuote(ctx.databaseName)} -t -A -c ${shellQuote(ctx.seedVerifyQuery)}`,\n { encoding: 'utf-8', timeout: 10000 }\n );\n seedVerifyResult = stdout.trim();\n } catch {}\n }\n\n logger.log(`DB refresh complete for ${ctx.issueId}`);\n return { seedVerifyResult };\n },\n\n async seed(ctx: SeedDatabaseContext): Promise<void> {\n const logger = requireLogger(ctx.logger, '[db-seed]');\n\n logger.setText(`Finding database container ${ctx.pgContainer}...`);\n\n try {\n const { stdout } = await execAsync(`docker ps --filter \"name=${ctx.pgContainer}\" --format \"{{.Names}}\"`);\n if (!stdout.trim()) {\n logger.fail(`Database container not running: ${ctx.pgContainer}`);\n logger.log('\\nStart the workspace containers first:');\n logger.log(` pan workspace create ${ctx.workspaceOrIssue} --docker`);\n return;\n }\n } catch {\n logger.fail('Failed to check Docker containers');\n return;\n }\n\n if (!ctx.force) {\n try {\n const { stdout } = await execAsync(\n `docker exec ${shellQuote(ctx.pgContainer)} psql -U postgres -d ${shellQuote(ctx.databaseName)} -c \"SELECT count(*) FROM flyway_schema_history\" -t 2>/dev/null`\n );\n const count = parseInt(stdout.trim(), 10);\n if (count > 0) {\n logger.info(`Database already seeded (${count} migrations). Use --force to reseed.`);\n return;\n }\n } catch {\n // Table doesn't exist, proceed with seeding\n }\n }\n\n if (ctx.force) {\n logger.setText('Dropping existing database...');\n try {\n await execAsync(\n `docker exec ${shellQuote(ctx.pgContainer)} psql -U postgres -c \"DROP DATABASE IF EXISTS ${sqlIdentifier(ctx.databaseName)}; CREATE DATABASE ${sqlIdentifier(ctx.databaseName)};\"`\n );\n } catch (error: unknown) {\n logger.warn(`Could not drop database: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n logger.setText('Copying seed file to container...');\n await execAsync(`docker cp ${shellQuote(ctx.seedFile)} ${shellQuote(`${ctx.pgContainer}:/tmp/seed.sql`)}`);\n\n logger.setText('Executing seed...');\n try {\n await execAsync(`docker exec ${shellQuote(ctx.pgContainer)} psql -U postgres -d ${shellQuote(ctx.databaseName)} -f /tmp/seed.sql`, {\n timeout: 600000,\n });\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n if (message.includes('ERROR')) {\n logger.fail(`Seed failed: ${message}`);\n return;\n }\n }\n\n await execAsync(`docker exec ${shellQuote(ctx.pgContainer)} rm /tmp/seed.sql`);\n\n logger.succeed('Database seeded successfully');\n\n try {\n const { stdout } = await execAsync(\n `docker exec ${shellQuote(ctx.pgContainer)} psql -U postgres -d ${shellQuote(ctx.databaseName)} -c \"SELECT version, description FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 3\" -t`\n );\n logger.log('\\nRecent migrations:');\n stdout\n .trim()\n .split('\\n')\n .forEach((line) => logger.log(` ${line.trim()}`));\n } catch {\n // Ignore\n }\n },\n\n repairMigrations(ctx: RepairMigrationsContext): Promise<RepairMigrationsResult> {\n return repairFlywayIfNeeded(\n ctx.issueId,\n ctx.pgContainer,\n ctx.databaseName,\n ctx.projectConfig,\n ctx.workspacePath,\n ctx.log\n );\n },\n\n async snapshot(ctx: SnapshotDatabaseContext): Promise<SnapshotDatabaseResult> {\n const logger = requireLogger(ctx.logger, '[db-snapshot]');\n\n if (!ctx.dbConfig.snapshot_command && !ctx.dbConfig.external_db) {\n throw new DatabaseProvisionerError(\n `No snapshot configuration for project ${'key' in ctx.projectConfig ? String(ctx.projectConfig.key) : ctx.projectConfig.name}`\n );\n }\n\n const outputPath =\n ctx.output ||\n ctx.dbConfig.seed_file ||\n join(ctx.projectConfig.path, 'infra', 'seed', 'seed.sql');\n\n const outputDir = dirname(outputPath);\n if (!existsSync(outputDir)) {\n mkdirSync(outputDir, { recursive: true });\n }\n\n logger.setText('Running snapshot command...');\n\n const snapshotCmd = getSnapshotCommand(ctx.dbConfig);\n if (!snapshotCmd) {\n throw new DatabaseProvisionerError('No snapshot configuration found');\n }\n\n const fullCmd = `${snapshotCmd} > \"${outputPath}\" 2>&1`;\n\n try {\n await execAsync(fullCmd, { timeout: 300000 });\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n if (existsSync(outputPath)) {\n const content = readFileSync(outputPath, 'utf-8');\n if (content.includes('PostgreSQL database dump')) {\n logger.warn('Snapshot completed with warnings (stderr captured)');\n logger.log(' Run `pan db clean` to remove stderr noise from the file');\n } else {\n throw new DatabaseProvisionerError(`Snapshot failed: ${message}`);\n }\n } else {\n throw new DatabaseProvisionerError(`Snapshot failed: ${message}`);\n }\n }\n\n const content = readFileSync(outputPath, 'utf-8');\n if (content.includes('Defaulted container') || content.includes('Unable to use a TTY')) {\n logger.setText('Cleaning kubectl output from snapshot...');\n await cleanFile(outputPath);\n }\n\n if (ctx.sanitize && ctx.dbConfig.seed_command) {\n logger.setText('Running sanitization...');\n try {\n await execAsync(ctx.dbConfig.seed_command, { cwd: ctx.projectConfig.path });\n } catch (error: unknown) {\n logger.warn(`Sanitization warning: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n const stats = statSync(outputPath);\n return { outputPath, sizeBytes: stats.size };\n },\n\n async cleanSnapshot(ctx: CleanSnapshotContext): Promise<CleanSnapshotResult> {\n if (!existsSync(ctx.file)) {\n throw new DatabaseProvisionerError(`File not found: ${ctx.file}`);\n }\n\n const content = readFileSync(ctx.file, 'utf-8');\n const lines = content.split('\\n');\n const patternsToRemove = kubectlNoisePatterns();\n\n let startIndex = 0;\n for (let i = 0; i < lines.length; i++) {\n if (lines[i].startsWith('--') && lines[i].includes('PostgreSQL')) {\n startIndex = i;\n break;\n }\n }\n void startIndex;\n\n const dumpStarts: number[] = [];\n const dumpEnds: number[] = [];\n\n for (let i = 0; i < lines.length; i++) {\n if (lines[i].includes('PostgreSQL database dump') && lines[i].startsWith('--')) {\n if (!lines[i].includes('complete')) {\n dumpStarts.push(i);\n }\n }\n if (lines[i].includes('PostgreSQL database dump complete')) {\n dumpEnds.push(i);\n }\n }\n\n let cleanedLines: string[];\n if (dumpStarts.length > 1 && dumpEnds.length > 0) {\n const lastEnd = dumpEnds[dumpEnds.length - 1];\n let useStart = dumpStarts[0];\n for (const start of dumpStarts) {\n if (start < lastEnd) {\n useStart = start;\n } else {\n break;\n }\n }\n cleanedLines = lines.slice(useStart, lastEnd + 1);\n ctx.logger?.setText(`Found ${dumpStarts.length} dump headers, using last complete dump`);\n } else {\n cleanedLines = lines.filter((line) => !patternsToRemove.some((p) => p.test(line)));\n }\n\n cleanedLines = cleanedLines.filter((line) => !patternsToRemove.some((p) => p.test(line)));\n\n const cleanedContent = cleanedLines.join('\\n');\n const removedLines = lines.length - cleanedLines.length;\n const removedSample = lines\n .filter((line) => patternsToRemove.some((p) => p.test(line)))\n .slice(0, 5);\n\n if (ctx.dryRun) {\n return {\n originalLines: lines.length,\n cleanedLines: cleanedLines.length,\n removedLines,\n removedSample,\n };\n }\n\n const outputPath = ctx.output || ctx.file;\n writeFileSync(outputPath, cleanedContent);\n\n return {\n outputPath,\n originalLines: lines.length,\n cleanedLines: cleanedLines.length,\n removedLines,\n removedSample,\n };\n },\n\n async status(ctx: StatusDatabaseContext): Promise<void> {\n const logger = requireLogger(ctx.logger, '[db-status]');\n\n logger.setText(`Checking container ${ctx.pgContainer}...`);\n\n const { stdout: containerStatus } = await execAsync(\n `docker ps --filter \"name=${ctx.pgContainer}\" --format \"{{.Status}}\"`\n );\n\n if (!containerStatus.trim()) {\n logger.fail(`Container not running: ${ctx.pgContainer}`);\n return;\n }\n\n logger.succeed(`Container: ${ctx.pgContainer}`);\n logger.log(` Status: ${containerStatus.trim()}`);\n\n try {\n const { stdout: version } = await execAsync(\n `docker exec ${shellQuote(ctx.pgContainer)} psql -U postgres -d ${shellQuote(ctx.databaseName)} -c \"SELECT version, description FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 1\" -t`\n );\n const [ver, desc] = version.trim().split('|').map((s) => s.trim());\n logger.log(` Flyway: V${ver} - ${desc}`);\n } catch {\n logger.warn(' Flyway: Not initialized');\n }\n\n try {\n const { stdout: tableCount } = await execAsync(\n `docker exec ${shellQuote(ctx.pgContainer)} psql -U postgres -d ${shellQuote(ctx.databaseName)} -c \"SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public'\" -t`\n );\n logger.log(` Tables: ${tableCount.trim()}`);\n } catch {\n // Ignore\n }\n\n try {\n const { stdout: dbSize } = await execAsync(\n `docker exec ${shellQuote(ctx.pgContainer)} psql -U postgres -d ${shellQuote(ctx.databaseName)} -c \"SELECT pg_size_pretty(pg_database_size('${sqlString(ctx.databaseName)}'))\" -t`\n );\n logger.log(` Size: ${dbSize.trim()}`);\n } catch {\n // Ignore\n }\n },\n};\n\nexport function getFlywayPostgresSnapshotHelp(projectKey: string): string {\n return getSnapshotHelp(projectKey);\n}\n","import type { DatabaseConfig } from '../workspace-config.js';\nimport { flywayPostgresProvisioner } from './flyway-postgres.js';\nimport type { DatabaseProvisioner } from './types.js';\n\nexport function getDatabaseProvisioner(\n dbConfig: DatabaseConfig | undefined\n): DatabaseProvisioner | null {\n if (!dbConfig) return null;\n if (dbConfig.provisioner === 'flyway-postgres') return flywayPostgresProvisioner;\n if (dbConfig.migrations?.type === 'flyway') return flywayPostgresProvisioner;\n return null;\n}\n\nexport function getSnapshotCleanerProvisioner(): DatabaseProvisioner {\n return flywayPostgresProvisioner;\n}\n\nexport type {\n DatabaseProvisioner,\n DatabaseProvisionerLogger,\n RefreshDatabaseContext,\n RefreshDatabaseResult,\n SeedDatabaseContext,\n RepairMigrationsContext,\n RepairMigrationsResult,\n SnapshotDatabaseContext,\n SnapshotDatabaseResult,\n CleanSnapshotContext,\n CleanSnapshotResult,\n StatusDatabaseContext,\n} from './types.js';\n\nexport { DatabaseProvisionerError } from './types.js';\n","/**\n * Container-ops route module — extracted from routes/workspaces.ts (B2).\n *\n * Containerization + Docker control + DB refresh endpoints:\n * POST /api/workspaces/:issueId/containerize\n * POST /api/workspaces/:issueId/containers/:containerName/:action\n * POST /api/workspaces/:issueId/memory-summary\n * POST /api/workspaces/:issueId/refresh-db\n *\n * Shared singletons (activity log, pending ops, project helpers)\n * stay owned by ../workspaces.js and are imported here.\n */\n\nimport { exec, spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { basename, join } from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { Effect, Layer } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\n\nimport { buildChildEnvWithoutTmuxSync } from '../../../../lib/child-env.js';\nimport { extractPrefixSync, parseIssueIdSync } from '../../../../lib/issue-id.js';\nimport { generateDailySummary } from '../../../../lib/memory/cli.js';\nimport {\n extractTeamPrefix,\n findProjectByTeamSync,\n listProjectsSync,\n} from '../../../../lib/projects.js';\nimport {\n DatabaseProvisionerError,\n getDatabaseProvisioner,\n} from '../../../../lib/db-provisioners/index.js';\nimport { DEVCONTAINER_DIRNAME } from '../../../../lib/workspace/devcontainer-renderer.js';\nimport type { DatabaseConfig } from '../../../../lib/workspace-config.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { httpHandler } from '../http-handler.js';\nimport {\n appendActivityOutput,\n getProjectPath,\n logActivity,\n requireTrustedMutationOrigin,\n updateActivity,\n} from '../workspaces.js';\n\nconst execAsync = promisify(exec);\n\nfunction requireDatabaseName(dbConfig: DatabaseConfig | undefined): string {\n if (!dbConfig) {\n throw new Error('No database configuration found in projects.yaml');\n }\n const name = dbConfig.name?.trim();\n if (!name) {\n throw new Error('Missing required database.name in projects.yaml database config');\n }\n return name;\n}\n\n// ─── Route: POST /api/workspaces/:issueId/containerize ───────────────────────\n\nconst postWorkspaceContainerizeRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces/:issueId/containerize',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const issueLower = issueId.toLowerCase();\n\n const newFeatureScript = join(projectPath, 'infra', 'new-feature');\n if (!existsSync(newFeatureScript)) {\n return jsonResponse(\n { error: 'Project does not support containerization (no infra/new-feature script)' },\n { status: 400 }\n );\n }\n\n const workspaceName = `feature-${issueLower}`;\n const workspacePath = join(projectPath, 'workspaces', workspaceName);\n if (existsSync(join(workspacePath, '.devcontainer'))) {\n return jsonResponse({ error: 'Workspace is already containerized' }, { status: 400 });\n }\n\n try {\n yield* Effect.promise(() => execAsync('docker info >/dev/null 2>&1', { encoding: 'utf-8' }));\n } catch {\n return jsonResponse(\n { error: 'Docker is not running. Start Docker Desktop first.' },\n { status: 400 }\n );\n }\n\n if (existsSync(workspacePath)) {\n return jsonResponse(\n { error: 'Workspace already exists. Use the workspace inspector to manage it, or remove it first with: pan workspace destroy ' + issueId },\n { status: 409 }\n );\n }\n\n const featureName = issueLower;\n const activityId = Date.now().toString();\n\n logActivity({\n id: activityId,\n timestamp: new Date().toISOString(),\n command: `./new-feature ${featureName}`,\n status: 'running',\n output: [],\n });\n\n const child = spawn('./new-feature', [featureName], {\n cwd: join(projectPath, 'infra'),\n detached: true,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n child.stdout?.on('data', (data) => {\n data.toString().split('\\n').filter(Boolean).forEach((line: string) => {\n appendActivityOutput(activityId, line);\n });\n });\n child.stderr?.on('data', (data) => {\n data.toString().split('\\n').filter(Boolean).forEach((line: string) => {\n appendActivityOutput(activityId, `[stderr] ${line}`);\n });\n });\n\n child.on('close', (code) => {\n appendActivityOutput(\n activityId,\n `[${new Date().toISOString()}] new-feature exited with code ${code}`\n );\n if (code === 0) {\n appendActivityOutput(activityId, '');\n appendActivityOutput(activityId, '=== Starting containers ===');\n\n const workspaceDir = join(projectPath, 'workspaces', `feature-${featureName}`);\n const uid = process.getuid?.() ?? 1000;\n const gid = process.getgid?.() ?? 1000;\n const devUp = spawn('./dev', ['all'], {\n cwd: workspaceDir,\n detached: true,\n stdio: ['ignore', 'pipe', 'pipe'],\n env: buildChildEnvWithoutTmuxSync(process.env, { UID: String(uid), GID: String(gid), DOCKER_USER: `${uid}:${gid}` }),\n });\n\n devUp.stdout?.on('data', (data) => {\n data.toString().split('\\n').filter(Boolean).forEach((line: string) => {\n appendActivityOutput(activityId, line);\n });\n });\n devUp.stderr?.on('data', (data) => {\n data.toString().split('\\n').filter(Boolean).forEach((line: string) => {\n appendActivityOutput(activityId, `[stderr] ${line}`);\n });\n });\n devUp.on('close', (devCode) => {\n appendActivityOutput(\n activityId,\n `[${new Date().toISOString()}] ./dev all exited with code ${devCode}`\n );\n updateActivity(activityId, { status: devCode === 0 ? 'completed' : 'failed' });\n });\n devUp.on('error', (err) => {\n appendActivityOutput(activityId, `[error] ${err.message}`);\n updateActivity(activityId, { status: 'failed' });\n });\n } else {\n updateActivity(activityId, { status: 'failed' });\n }\n });\n\n child.on('error', (err) => {\n appendActivityOutput(activityId, `[error] ${err.message}`);\n updateActivity(activityId, { status: 'failed' });\n });\n\n return jsonResponse({\n success: true,\n message: `Containerizing workspace for ${issueId}`,\n activityId,\n projectPath,\n });\n }))\n);\n\n// ─── Route: POST /api/workspaces/:issueId/containers/:containerName/:action ───\n\nconst postWorkspaceContainerActionRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces/:issueId/containers/:containerName/:action',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const containerName = params['containerName'] ?? '';\n const action = params['action'] ?? '';\n\n if (!['start', 'stop', 'restart'].includes(action)) {\n return jsonResponse(\n { error: 'Invalid action. Must be start, stop, or restart.' },\n { status: 400 }\n );\n }\n\n const teamPrefix = extractTeamPrefix(issueId);\n const containerProjectConfig = teamPrefix ? findProjectByTeamSync(teamPrefix) : null;\n const projectPaths = containerProjectConfig\n ? [\n join(\n containerProjectConfig.path,\n 'workspaces',\n `feature-${issueId.toLowerCase()}`\n ),\n ]\n : listProjectsSync().map(p =>\n join(p.config.path, 'workspaces', `feature-${issueId.toLowerCase()}`)\n );\n\n let workspacePath: string | null = null;\n let composeFile: string | null = null;\n\n for (const path of projectPaths) {\n if (existsSync(path)) {\n workspacePath = path;\n const composePaths = [\n join(path, '.devcontainer/docker-compose.devcontainer.yml'),\n join(path, 'docker-compose.yml'),\n join(path, 'docker-compose.yaml'),\n ];\n for (const cp of composePaths) {\n if (existsSync(cp)) {\n composeFile = cp;\n break;\n }\n }\n break;\n }\n }\n\n if (!workspacePath) {\n return jsonResponse(\n { error: `Workspace not found for ${issueId}` },\n { status: 404 }\n );\n }\n\n // Self-heal: if .devcontainer/ is missing for start/restart, re-render\n // from the project template so docker compose can operate on containers.\n if (!composeFile && ['start', 'restart'].includes(action)) {\n const { ensureDevcontainerSync } = yield* Effect.promise(() =>\n import('../../../../lib/workspace/ensure-devcontainer.js')\n );\n const ensure = ensureDevcontainerSync({ workspacePath, issueId });\n if (ensure.rendered) {\n console.log(`[container-control] Re-rendered ${DEVCONTAINER_DIRNAME}/ from project template`);\n }\n // Re-scan for compose file after re-render\n const composePaths = [\n join(workspacePath, '.devcontainer/docker-compose.devcontainer.yml'),\n join(workspacePath, 'docker-compose.yml'),\n join(workspacePath, 'docker-compose.yaml'),\n ];\n for (const cp of composePaths) {\n if (existsSync(cp)) {\n composeFile = cp;\n break;\n }\n }\n }\n\n if (!composeFile) {\n return jsonResponse(\n { error: `No docker-compose file found in workspace` },\n { status: 404 }\n );\n }\n\n try {\n yield* Effect.promise(() => execAsync('docker info >/dev/null 2>&1', { encoding: 'utf-8' }));\n } catch {\n return jsonResponse(\n { error: 'Docker is not running. Start Docker Desktop first.' },\n { status: 400 }\n );\n }\n\n const serviceMap: Record<string, string[]> = {\n frontend: ['fe', 'frontend'],\n api: ['api'],\n dev: ['dev'],\n postgres: ['postgres'],\n redis: ['redis'],\n fe: ['fe', 'frontend'],\n };\n\n const serviceNames = serviceMap[containerName.toLowerCase()];\n if (!serviceNames) {\n return jsonResponse(\n {\n error: `Unknown container: ${containerName}. Valid: ${Object.keys(serviceMap).join(', ')}`,\n },\n { status: 400 }\n );\n }\n\n const { stdout: projectNameOut } = yield* Effect.promise(() => execAsync(\n `docker compose -f \"${composeFile}\" config --format json 2>/dev/null | jq -r '.name // empty'`,\n { encoding: 'utf-8' }\n ));\n const projectName = projectNameOut.trim();\n\n // Pre-start migration repair for API containers\n if (\n containerName.toLowerCase() === 'api' &&\n ['start', 'restart'].includes(action)\n ) {\n const tPrefix = extractTeamPrefix(issueId);\n const pConfig = tPrefix ? findProjectByTeamSync(tPrefix) : null;\n const dbConfig = pConfig?.workspace?.database;\n const provisioner = getDatabaseProvisioner(dbConfig);\n if (pConfig && dbConfig && provisioner && projectName) {\n const databaseName = requireDatabaseName(pConfig.workspace.database);\n const pgContainer = `${projectName}-postgres-1`;\n try {\n const result = yield* Effect.promise(() => provisioner.repairMigrations({\n issueId,\n pgContainer,\n databaseName,\n projectConfig: pConfig,\n workspacePath: workspacePath!,\n }));\n if (result.repaired) {\n console.log(`[container-control] ${result.message}`);\n }\n } catch (repairErr: unknown) {\n console.warn(\n `[container-control] migration pre-check failed (non-fatal): ${repairErr instanceof Error ? repairErr.message : String(repairErr)}`\n );\n }\n }\n }\n\n let success = false;\n let lastError = '';\n\n for (const serviceName of serviceNames) {\n try {\n const cmd = `docker compose -f \"${composeFile}\" ${projectName ? `--project-name \"${projectName}\"` : ''} ${action} ${serviceName}`;\n console.log(`[container-control] Running: ${cmd}`);\n yield* Effect.promise(() => execAsync(cmd, { encoding: 'utf-8', timeout: 30000 }));\n success = true;\n console.log(\n `[container-control] Successfully ${action}ed ${serviceName} for ${issueId}`\n );\n break;\n } catch (err: unknown) {\n lastError = (err instanceof Error ? err.message : '') || String(err);\n }\n }\n\n if (success) {\n return jsonResponse({\n success: true,\n message: `Container ${containerName} ${action}ed successfully`,\n });\n } else {\n return jsonResponse(\n { error: `Failed to ${action} ${containerName}: ${lastError}` },\n { status: 500 }\n );\n }\n }))\n);\n\n// ─── Route: POST /api/workspaces/:issueId/memory-summary ─────────────────────\n\nconst postWorkspaceMemorySummaryRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces/:issueId/memory-summary',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const originError = requireTrustedMutationOrigin(request);\n if (originError) return originError;\n\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: 'Invalid issue ID' }, { status: 400 });\n }\n\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const result = yield* Effect.promise(() => generateDailySummary({\n projectId: basename(projectPath),\n issueId,\n }));\n return jsonResponse(result);\n }))\n);\n\n// ─── Route: POST /api/workspaces/:issueId/refresh-db ─────────────────────────\n\nconst postWorkspaceRefreshDbRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces/:issueId/refresh-db',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n\n const teamPrefix = extractTeamPrefix(issueId);\n const projectConfig = teamPrefix ? findProjectByTeamSync(teamPrefix) : null;\n\n if (!projectConfig) {\n return jsonResponse(\n { error: `No project found for issue prefix: ${issueId}` },\n { status: 404 }\n );\n }\n\n const dbConfig = projectConfig.workspace?.database;\n const provisioner = getDatabaseProvisioner(dbConfig);\n if (!provisioner) {\n return jsonResponse(\n { error: 'No database configuration found in projects.yaml' },\n { status: 400 }\n );\n }\n\n if (!dbConfig?.seed_file) {\n return jsonResponse(\n { error: 'No seed_file configured in projects.yaml database config' },\n { status: 400 }\n );\n }\n const databaseName = requireDatabaseName(dbConfig);\n\n const seedFile = join(projectConfig.path, dbConfig.seed_file);\n if (!existsSync(seedFile)) {\n return jsonResponse(\n { error: `Seed file not found: ${seedFile}` },\n { status: 400 }\n );\n }\n\n try {\n provisioner.validateRefreshDatabase({ seedFile });\n } catch (err: unknown) {\n if (err instanceof DatabaseProvisionerError) {\n return jsonResponse({ error: err.message }, { status: err.status });\n }\n throw err;\n }\n\n const issueLower = issueId.toLowerCase();\n const featureFolder = `feature-${issueLower}`;\n const workspacesDir = projectConfig.workspace?.workspaces_dir || 'workspaces';\n const workspacePath = join(projectConfig.path, workspacesDir, featureFolder);\n\n if (!existsSync(workspacePath)) {\n return jsonResponse(\n { error: `Workspace not found: ${featureFolder}` },\n { status: 404 }\n );\n }\n\n const composePaths = [\n join(workspacePath, '.devcontainer/docker-compose.devcontainer.yml'),\n join(workspacePath, 'docker-compose.yml'),\n join(workspacePath, 'docker-compose.yaml'),\n ];\n let composeFile: string | null = null;\n for (const cp of composePaths) {\n if (existsSync(cp)) { composeFile = cp; break; }\n }\n\n if (!composeFile) {\n return jsonResponse(\n { error: 'No docker-compose file found in workspace' },\n { status: 404 }\n );\n }\n\n const { stdout: projectNameOut } = yield* Effect.promise(() => execAsync(\n `docker compose -f \"${composeFile}\" config --format json 2>/dev/null | jq -r '.name // empty'`,\n { encoding: 'utf-8' }\n ));\n const projectName = projectNameOut.trim();\n\n if (!projectName) {\n return jsonResponse(\n { error: 'Could not determine docker compose project name' },\n { status: 500 }\n );\n }\n\n const pgContainer = `${projectName}-postgres-1`;\n const apiContainer = `${projectName}-api-1`;\n\n console.log(`[refresh-db] Starting DB refresh for ${issueId} (project: ${projectName})`);\n\n const { seedVerifyResult } = yield* Effect.promise(() => provisioner.refreshDatabase({\n issueId,\n projectConfig,\n workspacePath,\n pgContainer,\n apiContainer,\n databaseName,\n seedFile,\n seedVerifyQuery: dbConfig.seedVerifyQuery,\n logger: {\n setText: (msg) => console.log(`[refresh-db] ${msg}`),\n info: (msg) => console.log(`[refresh-db] ${msg}`),\n warn: (msg) => console.warn(`[refresh-db] ${msg}`),\n fail: (msg) => console.error(`[refresh-db] ${msg}`),\n succeed: (msg) => console.log(`[refresh-db] ${msg}`),\n log: (msg) => console.log(`[refresh-db] ${msg}`),\n },\n }));\n\n return jsonResponse({\n success: true,\n message: `Database refreshed successfully`,\n seedVerifyResult,\n });\n }))\n);\n\nexport const containerOpsRouteLayer = Layer.mergeAll(\n postWorkspaceContainerizeRoute,\n postWorkspaceContainerActionRoute,\n postWorkspaceMemorySummaryRoute,\n postWorkspaceRefreshDbRoute,\n);\n\nexport default containerOpsRouteLayer;\n","import { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { Effect } from 'effect';\nimport { GitError } from './errors.js';\n\nconst execAsync = promisify(exec);\n\n/**\n * Canonical stash kinds Overdeck creates.\n *\n * PAN-1531: collapsed from four kinds to one. The retired kinds (`pre-merge`,\n * `pre-spawn`, `review-temp`) are no longer created by Overdeck code, but\n * `parseCanonicalStashMessage` still recognizes them so existing stashes in\n * `refs/stash` parse correctly during cleanup. Only `salvageable` may be\n * created going forward — see /home/eltmon/.overdeck/context/global.md\n * \"Stash Discipline\" and docs/MERGE-WORKFLOW.md.\n */\nexport type CanonicalStashKind = 'salvageable';\nexport type LegacyStashKind = 'pre-merge' | 'pre-spawn' | 'review-temp';\nexport type AnyStashKind = CanonicalStashKind | LegacyStashKind;\n\nexport interface ParsedStashEntry {\n /** Stable stash object SHA for persisted references. */\n ref: string;\n /** Current stack position (e.g. stash@{0}) for display and index-based ordering. */\n stackRef?: string;\n message: string;\n createdAt?: Date;\n issueId?: string;\n /**\n * `'salvageable'` for canonical stashes Overdeck creates today.\n * `'pre-merge' | 'pre-spawn' | 'review-temp'` for stashes left behind by\n * the pre-PAN-1531 pipeline; recognized for cleanup but never created anew.\n * `'unknown'` for hand-typed or otherwise unrecognized stashes.\n */\n kind: AnyStashKind | 'unknown';\n shortDescription?: string;\n sequence?: number;\n}\n\nexport interface SalvageableStashEntry extends ParsedStashEntry {\n kind: 'salvageable';\n issueId: string;\n shortDescription: string;\n}\n\nconst ISO_STASH_TIMESTAMP_PATTERN = '(\\\\d{4}-\\\\d{2}-\\\\d{2}T\\\\d{2}:\\\\d{2}:\\\\d{2}(?:\\\\.\\\\d+)?(?:Z|[+-]\\\\d{2}:\\\\d{2}))';\nconst ISSUE_ID_PATTERN = '([A-Z]+(?:-[A-Z]+)*-\\\\d+)';\nconst ISSUE_ID_REGEX = new RegExp(`^${ISSUE_ID_PATTERN}$`);\n\nfunction isoForStash(date = new Date()): string {\n return date.toISOString().replace(/\\.\\d{3}Z$/, 'Z');\n}\n\nfunction normalizeStashTimestamp(value: string): string {\n return value\n .replace(/\\.\\d+(Z|[+-]\\d{2}:\\d{2})$/, '$1')\n .replace(/\\+00:00$/, 'Z');\n}\n\nfunction sanitizeShortDescription(value: string): string {\n return value\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 48) || 'recovery';\n}\n\nfunction validateIssueId(issueId: string): string {\n const normalizedIssueId = issueId.toUpperCase();\n if (!ISSUE_ID_REGEX.test(normalizedIssueId)) {\n throw new Error(`Invalid issue ID format: ${issueId}`);\n }\n return normalizedIssueId;\n}\n\n/**\n * Build a canonical stash message.\n *\n * PAN-1531: `salvageable` is the only canonical kind Overdeck creates.\n * Retired overloads for `pre-merge` / `pre-spawn` / `review-temp` were\n * removed when their callers were deleted. Any human who wants a stash\n * Overdeck will leave alone uses `salvageable:`.\n */\nexport function buildStashMessage(\n kind: CanonicalStashKind,\n issueId: string,\n date: Date = new Date(),\n shortDescription: string = 'recovery',\n): string {\n const normalizedIssueId = validateIssueId(issueId);\n if (kind === 'salvageable') {\n return `salvageable:${normalizedIssueId}:${isoForStash(date)}:${sanitizeShortDescription(shortDescription)}`;\n }\n // Unreachable: type system narrows `kind` to 'salvageable'. Defensive throw\n // so type widening at the call site is caught at runtime rather than\n // silently producing an invalid message.\n throw new Error(`buildStashMessage: unsupported kind ${kind as string}`);\n}\n\nexport function parseCanonicalStashMessage(message: string): ParsedStashEntry {\n const preTimedMatch = new RegExp(`^(pre-merge|pre-spawn):${ISSUE_ID_PATTERN}:${ISO_STASH_TIMESTAMP_PATTERN}$`).exec(message);\n if (preTimedMatch) {\n return {\n ref: '',\n message,\n kind: preTimedMatch[1] as CanonicalStashKind,\n issueId: preTimedMatch[2].toUpperCase(),\n createdAt: new Date(normalizeStashTimestamp(preTimedMatch[3])),\n };\n }\n\n const reviewTempMatch = new RegExp(`^review-temp:${ISSUE_ID_PATTERN}:(\\\\d+)$`).exec(message);\n if (reviewTempMatch) {\n return {\n ref: '',\n message,\n kind: 'review-temp',\n issueId: reviewTempMatch[1].toUpperCase(),\n sequence: parseInt(reviewTempMatch[2], 10),\n };\n }\n\n const salvageableMatch = new RegExp(`^salvageable:${ISSUE_ID_PATTERN}:${ISO_STASH_TIMESTAMP_PATTERN}:(.+)$`).exec(message);\n if (salvageableMatch) {\n return {\n ref: '',\n message,\n kind: 'salvageable',\n issueId: salvageableMatch[1].toUpperCase(),\n createdAt: new Date(normalizeStashTimestamp(salvageableMatch[2])),\n shortDescription: salvageableMatch[3],\n };\n }\n\n return { ref: '', message, kind: 'unknown' };\n}\n\nexport function parseStashListLine(line: string): ParsedStashEntry | null {\n const trimmed = line.trim();\n if (!trimmed) return null;\n\n const formattedMatch = /^(stash@\\{\\d+\\})\\t([0-9a-f]{40})\\t([^\\t]+)\\t(.*)$/.exec(trimmed);\n if (formattedMatch) {\n const [, stackRef, ref, createdAtRaw, decoratedMessage] = formattedMatch;\n const messageMatch = /^(?:On|WIP on)\\s+[^:]*:\\s*(.*)$/.exec(decoratedMessage.trim());\n const message = (messageMatch?.[1] ?? decoratedMessage).trim();\n const parsed = parseCanonicalStashMessage(message);\n const createdAt = new Date(normalizeStashTimestamp(createdAtRaw));\n return {\n ...parsed,\n ref,\n stackRef,\n message,\n createdAt: Number.isNaN(createdAt.getTime()) ? parsed.createdAt : createdAt,\n };\n }\n\n const legacyMatch = /^(stash@\\{\\d+\\}):\\s*(?:On|WIP on)\\s+[^:]*:\\s*(.*)$/.exec(trimmed);\n if (!legacyMatch) return null;\n const stackRef = legacyMatch[1];\n const message = legacyMatch[2].trim();\n const parsed = parseCanonicalStashMessage(message);\n return { ...parsed, ref: stackRef, stackRef, message };\n}async function listStashesPromise(repoPath: string): Promise<ParsedStashEntry[]> {\n const { stdout } = await execAsync('git stash list --format=\"%gd%x09%H%x09%cI%x09%gs\"', {\n cwd: repoPath,\n encoding: 'utf-8',\n });\n return stdout\n .split('\\n')\n .map((line) => parseStashListLine(line))\n .filter((entry): entry is ParsedStashEntry => entry !== null);\n}async function createNamedStashPromise(repoPath: string, message: string, includeUntracked = true): Promise<string | null> {\n const command = includeUntracked\n ? `git stash push -u -m ${JSON.stringify(message)}`\n : `git stash push -m ${JSON.stringify(message)}`;\n const { stdout } = await execAsync(command, { cwd: repoPath, encoding: 'utf-8' });\n if (/No local changes to save/i.test(stdout)) return null;\n\n const { stdout: stashRef } = await execAsync('git rev-parse --verify stash@{0}', {\n cwd: repoPath,\n encoding: 'utf-8',\n });\n const normalizedRef = stashRef.trim();\n return normalizedRef || null;\n}\n\nasync function resolveStashOperationRef(repoPath: string, ref: string, stackRef?: string): Promise<string> {\n // Per-workspace stash operations must not run concurrently; callers may\n // resolve a stack slot and immediately consume it, but another stash mutation in between\n // can still shift indices. The rev-parse checks below re-validate the resolved slot\n // against the expected SHA immediately before use.\n const candidateRef = /^stash@\\{\\d+\\}$/.test(ref) ? ref : stackRef;\n if (candidateRef) {\n const { stdout: resolvedSha } = await execAsync(`git rev-parse --verify ${JSON.stringify(candidateRef)}`, {\n cwd: repoPath,\n encoding: 'utf-8',\n });\n if (/^stash@\\{\\d+\\}$/.test(ref) || resolvedSha.trim() === ref) {\n return candidateRef;\n }\n throw new Error(`Stash ${ref} no longer matches ${candidateRef}`);\n }\n\n const stashes = await Effect.runPromise(listStashes(repoPath));\n const matchingEntry = stashes.find((entry) => entry.ref === ref);\n if (!matchingEntry?.stackRef) {\n throw new Error(`Stash ${ref} not found`);\n }\n\n const { stdout: resolvedSha } = await execAsync(`git rev-parse --verify ${JSON.stringify(matchingEntry.stackRef)}`, {\n cwd: repoPath,\n encoding: 'utf-8',\n });\n if (resolvedSha.trim() !== ref) {\n throw new Error(`Stash ${ref} no longer matches ${matchingEntry.stackRef}`);\n }\n\n return matchingEntry.stackRef;\n}async function popStashPromise(repoPath: string, ref: string, stackRef?: string): Promise<void> {\n const operationRef = await resolveStashOperationRef(repoPath, ref, stackRef);\n await execAsync(`git stash pop ${JSON.stringify(operationRef)}`, { cwd: repoPath, encoding: 'utf-8' });\n}async function dropStashPromise(repoPath: string, ref: string, stackRef?: string): Promise<void> {\n const operationRef = await resolveStashOperationRef(repoPath, ref, stackRef);\n await execAsync(`git stash drop ${JSON.stringify(operationRef)}`, { cwd: repoPath, encoding: 'utf-8' });\n}async function applyStashPromise(repoPath: string, ref: string, stackRef?: string): Promise<void> {\n const operationRef = await resolveStashOperationRef(repoPath, ref, stackRef);\n await execAsync(`git stash apply ${JSON.stringify(operationRef)}`, { cwd: repoPath, encoding: 'utf-8' });\n}async function createRecoveryBranchFromStashPromise(\n repoPath: string,\n stashRef: string,\n issueId: string,\n shortDescription: string,\n stackRef?: string,\n): Promise<string> {\n const operationRef = await resolveStashOperationRef(repoPath, stashRef, stackRef);\n const normalizedIssueId = validateIssueId(issueId);\n const branchName = `recovery/${normalizedIssueId}-${sanitizeShortDescription(shortDescription)}`;\n await execAsync(`git branch ${JSON.stringify(branchName)} ${JSON.stringify(operationRef)}`, {\n cwd: repoPath,\n encoding: 'utf-8',\n });\n return branchName;\n}\n\nexport function getNextReviewTempSequence(entries: ParsedStashEntry[], issueId: string): number {\n const normalizedIssueId = issueId.toUpperCase();\n const maxSequence = entries.reduce((max, entry) => {\n if (entry.kind !== 'review-temp' || entry.issueId !== normalizedIssueId || entry.sequence === undefined) {\n return max;\n }\n return Math.max(max, entry.sequence);\n }, 0);\n return maxSequence + 1;\n}\n\nexport function isOlderThanDays(entry: ParsedStashEntry, days: number, now = new Date()): boolean {\n if (!entry.createdAt) return false;\n return now.getTime() - entry.createdAt.getTime() > days * 24 * 60 * 60 * 1000;\n}\n\nexport function isSalvageableStash(entry: ParsedStashEntry): entry is SalvageableStashEntry {\n return entry.kind === 'salvageable' && !!entry.issueId && !!entry.shortDescription;\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\nconst toGitError = (op: string, cause: unknown): GitError =>\n new GitError({\n command: ['git', 'stash', op],\n stderr: cause instanceof Error ? cause.message : String(cause),\n exitCode: 1,\n cause,\n });\n\n/** List parsed stash entries (newest first per git's natural ordering). */\nexport const listStashes = (\n repoPath: string,\n): Effect.Effect<readonly ParsedStashEntry[], GitError> =>\n Effect.tryPromise({\n try: () => listStashesPromise(repoPath),\n catch: (cause) => toGitError('list', cause),\n });\n\n/** Create a named stash; returns null if there were no changes to save. */\nexport const createNamedStash = (\n repoPath: string,\n message: string,\n includeUntracked = true,\n): Effect.Effect<string | null, GitError> =>\n Effect.tryPromise({\n try: () => createNamedStashPromise(repoPath, message, includeUntracked),\n catch: (cause) => toGitError('push', cause),\n });\n\n/** Pop a stash by SHA (preferred) or stack ref. */\nexport const popStash = (\n repoPath: string,\n ref: string,\n stackRef?: string,\n): Effect.Effect<void, GitError> =>\n Effect.tryPromise({\n try: () => popStashPromise(repoPath, ref, stackRef),\n catch: (cause) => toGitError('pop', cause),\n });\n\n/** Drop a stash by SHA (preferred) or stack ref. */\nexport const dropStash = (\n repoPath: string,\n ref: string,\n stackRef?: string,\n): Effect.Effect<void, GitError> =>\n Effect.tryPromise({\n try: () => dropStashPromise(repoPath, ref, stackRef),\n catch: (cause) => toGitError('drop', cause),\n });\n\n/** Apply a stash without removing it. */\nexport const applyStash = (\n repoPath: string,\n ref: string,\n stackRef?: string,\n): Effect.Effect<void, GitError> =>\n Effect.tryPromise({\n try: () => applyStashPromise(repoPath, ref, stackRef),\n catch: (cause) => toGitError('apply', cause),\n });\n\n/** Materialise a recovery branch from a stash. Returns the new branch name. */\nexport const createRecoveryBranchFromStash = (\n repoPath: string,\n stashRef: string,\n issueId: string,\n shortDescription: string,\n stackRef?: string,\n): Effect.Effect<string, GitError> =>\n Effect.tryPromise({\n try: () =>\n createRecoveryBranchFromStashPromise(repoPath, stashRef, issueId, shortDescription, stackRef),\n catch: (cause) => toGitError('branch', cause),\n });\n","/**\n * Aggregation Cache Management for Cost Tracking\n *\n * Manages the by-issue.json cache that stores pre-computed cost aggregations.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { Effect } from 'effect';\nimport { CostEvent, readEventsSync, readEventsFromLineSync, getLastEventMetadataSync } from './events.js';\nimport { FsError } from '../errors.js';\n\n// ============== Types ==============\n\nexport interface ModelStats {\n cost: number;\n calls: number;\n tokens: number;\n}\n\nexport interface StageStats {\n cost: number;\n tokens: number;\n calls: number;\n}\n\nexport interface IssueStats {\n totalCost: number;\n budget?: number;\n budgetWarning: boolean;\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens: number;\n cacheWriteTokens: number;\n models: Record<string, ModelStats>;\n providers: Record<string, number>;\n stages: Record<string, StageStats>;\n lastUpdated: string;\n}\n\nexport interface CostCache {\n version: number;\n status: 'live' | 'migrating' | 'stale';\n lastEventTs: string | null;\n lastEventLine: number;\n retentionDays: number;\n issues: Record<string, IssueStats>;\n}\n\n// ============== Constants ==============\n\nconst CACHE_VERSION = 3;\nconst DEFAULT_RETENTION_DAYS = 90;\n\n// Use functions for paths to allow test mocking via process.env.HOME\nfunction getCostsDir(): string {\n return join(process.env.HOME || homedir(), '.overdeck', 'costs');\n}\n\nfunction getCacheFile(): string {\n return join(getCostsDir(), 'by-issue.json');\n}\n\n// ============== Cache Loading ==============\n\n/**\n * Load the cache from disk\n */\nexport function loadCacheSync(): CostCache {\n const cacheFile = getCacheFile();\n if (!existsSync(cacheFile)) {\n return createEmptyCache();\n }\n\n try {\n const content = readFileSync(cacheFile, 'utf-8');\n const cache = JSON.parse(content) as CostCache;\n\n // Validate version\n if (cache.version !== CACHE_VERSION) {\n console.warn(`Cache version mismatch: expected ${CACHE_VERSION}, got ${cache.version}. Rebuilding cache.`);\n return createEmptyCache();\n }\n\n return cache;\n } catch (err) {\n console.error('Error loading cache:', err);\n return createEmptyCache();\n }\n}\n\n/**\n * Create an empty cache structure\n */\nfunction createEmptyCache(): CostCache {\n return {\n version: CACHE_VERSION,\n status: 'live',\n lastEventTs: null,\n lastEventLine: 0,\n retentionDays: DEFAULT_RETENTION_DAYS,\n issues: {},\n };\n}\n\n// ============== Cache Saving ==============\n\n/**\n * Save the cache to disk atomically\n */\nexport function saveCacheSync(cache: CostCache): void {\n const costsDir = getCostsDir();\n const cacheFile = getCacheFile();\n mkdirSync(costsDir, { recursive: true });\n\n // Write to temp file first\n const tempFile = cacheFile + '.tmp';\n const content = JSON.stringify(cache, null, 2);\n writeFileSync(tempFile, content, 'utf-8');\n\n // Atomic rename\n renameSync(tempFile, cacheFile);\n}\n\n// ============== Cache Updates ==============\n\n/**\n * Update cache incrementally from new events\n * @param events Array of events to add\n * @param newLineNumber Optional new line number (for correct tracking with malformed lines)\n */\nexport function updateCacheFromEventsSync(events: CostEvent[], newLineNumber?: number): CostCache {\n const cache = loadCacheSync();\n\n for (const event of events) {\n addEventToCache(cache, event);\n }\n\n // Update metadata\n if (events.length > 0) {\n const lastEvent = events[events.length - 1];\n cache.lastEventTs = lastEvent.ts;\n\n // Use provided line number if available (handles malformed lines correctly)\n // Otherwise fall back to incrementing by event count (for backward compatibility)\n if (newLineNumber !== undefined) {\n cache.lastEventLine = newLineNumber;\n } else {\n cache.lastEventLine += events.length;\n }\n }\n\n cache.status = 'live';\n\n saveCacheSync(cache);\n return cache;\n}\n\n/**\n * Add a single event to the cache\n */\nfunction addEventToCache(cache: CostCache, event: CostEvent): void {\n const issueKey = event.issueId.toUpperCase();\n\n // Initialize issue stats if needed\n if (!cache.issues[issueKey]) {\n cache.issues[issueKey] = {\n totalCost: 0,\n budgetWarning: false,\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n models: {},\n providers: {},\n stages: {},\n lastUpdated: event.ts,\n };\n }\n\n const issue = cache.issues[issueKey];\n\n // Update totals\n issue.totalCost += event.cost;\n issue.inputTokens += event.input;\n issue.outputTokens += event.output;\n issue.cacheReadTokens += event.cacheRead;\n issue.cacheWriteTokens += event.cacheWrite;\n issue.lastUpdated = event.ts;\n\n // Update model stats\n if (!issue.models[event.model]) {\n issue.models[event.model] = {\n cost: 0,\n calls: 0,\n tokens: 0,\n };\n }\n\n const modelStats = issue.models[event.model];\n modelStats.cost += event.cost;\n modelStats.calls += 1;\n modelStats.tokens += event.input + event.output + event.cacheRead + event.cacheWrite;\n\n // Update provider stats\n if (!issue.providers[event.provider]) {\n issue.providers[event.provider] = 0;\n }\n issue.providers[event.provider] += event.cost;\n\n // Update stage stats (using sessionType as stage)\n const stage = event.sessionType || 'unknown';\n if (!issue.stages[stage]) {\n issue.stages[stage] = {\n cost: 0,\n tokens: 0,\n calls: 0,\n };\n }\n const stageStats = issue.stages[stage];\n stageStats.cost += event.cost;\n stageStats.calls += 1;\n stageStats.tokens += event.input + event.output + event.cacheRead + event.cacheWrite;\n\n // Check budget warning\n if (issue.budget) {\n issue.budgetWarning = issue.totalCost >= issue.budget * 0.8;\n }\n\n // Round costs to avoid floating point errors\n issue.totalCost = Math.round(issue.totalCost * 1000000) / 1000000;\n modelStats.cost = Math.round(modelStats.cost * 1000000) / 1000000;\n issue.providers[event.provider] = Math.round(issue.providers[event.provider] * 1000000) / 1000000;\n stageStats.cost = Math.round(stageStats.cost * 1000000) / 1000000;\n}\n\n/**\n * Rebuild the entire cache from all events\n */\nexport function rebuildCacheSync(): CostCache {\n console.log('Rebuilding cost cache from events...');\n\n const cache = createEmptyCache();\n cache.status = 'migrating';\n\n // Read all events\n const events = readEventsSync();\n console.log(`Processing ${events.length} events...`);\n\n for (const event of events) {\n addEventToCache(cache, event);\n }\n\n // Update metadata\n const metadata = getLastEventMetadataSync();\n cache.lastEventTs = metadata.lastEventTs;\n cache.lastEventLine = metadata.lastEventLine;\n cache.status = 'live';\n\n console.log(`Cache rebuilt: ${Object.keys(cache.issues).length} issues, ${events.length} events`);\n\n saveCacheSync(cache);\n return cache;\n}\n\n/**\n * Sync cache with latest events\n * Reads events since the last processed event and updates cache\n */\nexport function syncCacheSync(): CostCache {\n const cache = loadCacheSync();\n\n // Check if there are new events\n const metadata = getLastEventMetadataSync();\n\n if (metadata.lastEventLine === cache.lastEventLine) {\n // Already up to date\n return cache;\n }\n\n if (metadata.lastEventLine < cache.lastEventLine) {\n // Events file was truncated (retention cleanup) - rebuild\n console.log('Events file was truncated, rebuilding cache...');\n return rebuildCacheSync();\n }\n\n // Read new events (returns both events and new line position)\n const { events: newEvents, newLine } = readEventsFromLineSync(cache.lastEventLine);\n\n if (newEvents.length > 0) {\n console.log(`Syncing cache with ${newEvents.length} new events...`);\n return updateCacheFromEventsSync(newEvents, newLine);\n }\n\n // Even if no events, update line number in case file was appended with only malformed lines\n if (newLine !== cache.lastEventLine) {\n cache.lastEventLine = newLine;\n saveCacheSync(cache);\n }\n\n return cache;\n}\n\n// ============== Cache Queries ==============\n\n/**\n * Get costs for all issues\n */\nexport function getCostsByIssueSync(): Record<string, IssueStats> {\n const cache = syncCacheSync();\n return cache.issues;\n}\n\n/**\n * Get costs for a specific issue\n */\nexport function getCostsForIssueSync(issueId: string): IssueStats | null {\n const cache = syncCacheSync();\n const issueKey = issueId.toUpperCase();\n return cache.issues[issueKey] || null;\n}\n\n/**\n * Set budget for an issue\n */\nexport function setIssueBudgetSync(issueId: string, budget: number): void {\n const cache = loadCacheSync();\n const issueKey = issueId.toUpperCase();\n\n if (!cache.issues[issueKey]) {\n cache.issues[issueKey] = {\n totalCost: 0,\n budgetWarning: false,\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n models: {},\n providers: {},\n stages: {},\n lastUpdated: new Date().toISOString(),\n };\n }\n\n cache.issues[issueKey].budget = budget;\n cache.issues[issueKey].budgetWarning = cache.issues[issueKey].totalCost >= budget * 0.8;\n\n saveCacheSync(cache);\n}\n\n/**\n * Get cache status\n */\nexport function getCacheStatus(): {\n status: 'live' | 'migrating' | 'stale';\n lastEventTs: string | null;\n eventCount: number;\n issueCount: number;\n needsSync: boolean;\n} {\n const cache = loadCacheSync();\n const metadata = getLastEventMetadataSync();\n\n return {\n status: cache.status,\n lastEventTs: cache.lastEventTs,\n eventCount: cache.lastEventLine,\n issueCount: Object.keys(cache.issues).length,\n needsSync: metadata.lastEventLine !== cache.lastEventLine,\n };\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\n/** Effect variant of loadCache. Failures surface as FsError. */\nexport const loadCache = (): Effect.Effect<CostCache, FsError> =>\n Effect.try({\n try: () => loadCacheSync(),\n catch: (cause) => new FsError({ path: getCacheFile(), operation: 'loadCache', cause }),\n });\n\n/** Effect variant of saveCache. */\nexport const saveCache = (cache: CostCache): Effect.Effect<void, FsError> =>\n Effect.try({\n try: () => saveCacheSync(cache),\n catch: (cause) => new FsError({ path: getCacheFile(), operation: 'saveCache', cause }),\n });\n\n/** Effect variant of updateCacheFromEvents. */\nexport const updateCacheFromEvents = (\n events: CostEvent[],\n newLineNumber?: number,\n): Effect.Effect<CostCache, FsError> =>\n Effect.try({\n try: () => updateCacheFromEventsSync(events, newLineNumber),\n catch: (cause) => new FsError({ path: getCacheFile(), operation: 'updateCacheFromEvents', cause }),\n });\n\n/** Effect variant of rebuildCache. */\nexport const rebuildCache = (): Effect.Effect<CostCache, FsError> =>\n Effect.try({\n try: () => rebuildCacheSync(),\n catch: (cause) => new FsError({ path: getCacheFile(), operation: 'rebuildCache', cause }),\n });\n\n/** Effect variant of syncCache. */\nexport const syncCache = (): Effect.Effect<CostCache, FsError> =>\n Effect.try({\n try: () => syncCacheSync(),\n catch: (cause) => new FsError({ path: getCacheFile(), operation: 'syncCache', cause }),\n });\n\n/** Effect variant of getCostsByIssue. */\nexport const getCostsByIssue = (): Effect.Effect<Record<string, IssueStats>, FsError> =>\n Effect.try({\n try: () => getCostsByIssueSync(),\n catch: (cause) => new FsError({ path: getCacheFile(), operation: 'getCostsByIssue', cause }),\n });\n\n/** Effect variant of getCostsForIssue. */\nexport const getCostsForIssue = (\n issueId: string,\n): Effect.Effect<IssueStats | null, FsError> =>\n Effect.try({\n try: () => getCostsForIssueSync(issueId),\n catch: (cause) => new FsError({ path: getCacheFile(), operation: 'getCostsForIssue', cause }),\n });\n\n/** Effect variant of setIssueBudget. */\nexport const setIssueBudget = (\n issueId: string,\n budget: number,\n): Effect.Effect<void, FsError> =>\n Effect.try({\n try: () => setIssueBudgetSync(issueId, budget),\n catch: (cause) => new FsError({ path: getCacheFile(), operation: 'setIssueBudget', cause }),\n });\n","/**\n * Historical Data Migration for Cost Tracking\n *\n * Migrates historical session data to the event-sourced format.\n * Includes both main session files and subagent session files.\n */\n\nimport { existsSync, readdirSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { Effect } from 'effect';\nimport { encodeClaudeProjectDir } from '../paths.js';\nimport { appendCostEventSync, CostEvent, eventsFileExists, getLastEventMetadataSync } from './events.js';\nimport { getPricingSync, calculateCostSync, TokenUsage } from '../cost.js';\nimport { FsError } from '../errors.js';\n\n// ============== Types ==============\n\nexport interface MigrationStats {\n agentsProcessed: number;\n sessionFilesProcessed: number;\n subagentFilesProcessed: number;\n eventsCreated: number;\n errors: Array<{ file: string; error: string }>;\n warnings: Array<{ file: string; message: string }>;\n totalCost: number;\n totalTokens: number;\n}\n\ninterface SessionUsage {\n model: string;\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens: number;\n cacheWriteTokens: number;\n timestamp?: string;\n}\n\ninterface AgentContext {\n agentId: string;\n issueId: string;\n sessionType: string;\n workspace: string;\n}\n\n// ============== Path Helpers ==============\n// Use functions for paths to allow test mocking via process.env.HOME\nfunction getAgentsDir(): string {\n return join(process.env.HOME || homedir(), '.overdeck', 'agents');\n}\n\nfunction getClaudeProjectsDir(): string {\n return join(process.env.HOME || homedir(), '.claude', 'projects');\n}\n\nfunction getProjectsYamlPath(): string {\n return join(process.env.HOME || homedir(), '.overdeck', 'projects.yaml');\n}\n\n// ============== Workspace Resolution ==============\n\n/**\n * Infer issueId from agent directory name\n * e.g., 'agent-pan-105' -> 'PAN-105', 'agent-min-663' -> 'MIN-663'\n */\nfunction inferIssueId(agentDir: string): string | null {\n // Strip 'agent-' or 'planning-' prefix\n const stripped = agentDir.replace(/^(agent|planning)-/, '');\n // Match pattern like 'pan-105' or 'min-663'\n const match = stripped.match(/^([a-z]+)-(\\d+)$/i);\n if (match) {\n return `${match[1].toUpperCase()}-${match[2]}`;\n }\n return null;\n}\n\n/**\n * Load project paths from projects.yaml\n */\nfunction getProjectPaths(): string[] {\n const paths: string[] = [];\n try {\n const yamlPath = getProjectsYamlPath();\n if (existsSync(yamlPath)) {\n const content = readFileSync(yamlPath, 'utf-8');\n // Simple YAML parsing for path: values\n const pathMatches = content.match(/^\\s+path:\\s+(.+)$/gm);\n if (pathMatches) {\n for (const m of pathMatches) {\n const pathMatch = m.match(/path:\\s+(.+)/);\n if (pathMatch) {\n paths.push(pathMatch[1].trim());\n }\n }\n }\n }\n } catch {\n // Ignore errors\n }\n return paths;\n}\n\n/**\n * Try to find workspace directory for an issue\n * Searches known project paths for workspaces/feature-{issue-id}\n */\nfunction resolveWorkspace(issueId: string): string | null {\n const issueLower = issueId.toLowerCase();\n const projectPaths = getProjectPaths();\n\n for (const projectPath of projectPaths) {\n const wsPath = join(projectPath, 'workspaces', `feature-${issueLower}`);\n if (existsSync(wsPath)) {\n return wsPath;\n }\n }\n\n return null;\n}\n\n/**\n * Find Claude session directories by scanning the Claude projects dir\n * This is a fallback when workspace path is not known\n */\nfunction findSessionDirsByIssue(issueId: string): string[] {\n const dirs: string[] = [];\n const issueLower = issueId.toLowerCase();\n const claudeDir = getClaudeProjectsDir();\n\n try {\n const entries = readdirSync(claudeDir);\n for (const entry of entries) {\n // Match directories that contain the issue ID pattern\n if (entry.includes(`feature-${issueLower}`)) {\n const fullPath = join(claudeDir, entry);\n dirs.push(fullPath);\n }\n }\n } catch {\n // Ignore errors\n }\n\n return dirs;\n}\n\n// ============== Session Parsing ==============\n\n/**\n * Parse a single session JSONL file and extract usage data\n */\nfunction parseSessionFile(filePath: string): SessionUsage[] {\n const usages: SessionUsage[] = [];\n\n try {\n const content = readFileSync(filePath, 'utf-8');\n const lines = content.split('\\n').filter(l => l.trim());\n\n for (const line of lines) {\n try {\n const entry = JSON.parse(line);\n\n // Extract timestamp\n const timestamp = entry.timestamp || entry.ts || entry.created_at;\n\n // Extract model\n const model = entry.message?.model || entry.model;\n\n // Extract usage - can be at top level or in message\n const usage = entry.usage || entry.message?.usage;\n\n if (usage && model) {\n usages.push({\n model,\n inputTokens: usage.input_tokens || 0,\n outputTokens: usage.output_tokens || 0,\n cacheReadTokens: usage.cache_read_input_tokens || 0,\n cacheWriteTokens: usage.cache_creation_input_tokens || 0,\n timestamp,\n });\n }\n } catch {\n // Skip malformed lines\n }\n }\n } catch (err) {\n throw new Error(`Failed to read file: ${err}`);\n }\n\n return usages;\n}\n\n/**\n * Convert session usage to cost events\n */\nfunction usageToCostEvents(\n usages: SessionUsage[],\n context: AgentContext\n): CostEvent[] {\n const events: CostEvent[] = [];\n\n for (const usage of usages) {\n // Determine provider from model name\n let provider = 'anthropic';\n if (usage.model.includes('gpt')) {\n provider = 'openai';\n } else if (usage.model.includes('gemini')) {\n provider = 'google';\n } else if (usage.model.includes('kimi')) {\n provider = 'custom';\n }\n\n // Get pricing and calculate cost\n const pricing = getPricingSync(provider as any, usage.model);\n if (!pricing) {\n continue; // Skip if no pricing found\n }\n\n const tokenUsage: TokenUsage = {\n inputTokens: usage.inputTokens,\n outputTokens: usage.outputTokens,\n cacheReadTokens: usage.cacheReadTokens,\n cacheWriteTokens: usage.cacheWriteTokens,\n cacheTTL: '5m',\n };\n\n const cost = calculateCostSync(tokenUsage, pricing);\n\n events.push({\n ts: usage.timestamp || new Date().toISOString(),\n type: 'cost',\n agentId: context.agentId,\n issueId: context.issueId,\n sessionType: context.sessionType,\n provider,\n model: usage.model,\n input: usage.inputTokens,\n output: usage.outputTokens,\n cacheRead: usage.cacheReadTokens,\n cacheWrite: usage.cacheWriteTokens,\n cost,\n });\n }\n\n return events;\n}\n\n/**\n * Find session directory for a workspace\n */\nfunction getSessionDir(workspacePath: string): string | null {\n // Claude Code session directory name format: path with leading / removed and / replaced by -\n // e.g., /home/user/projects/foo -> -home-user-projects-foo\n const sessionDirName = encodeClaudeProjectDir(workspacePath);\n const sessionDir = join(getClaudeProjectsDir(), sessionDirName);\n\n if (existsSync(sessionDir)) {\n return sessionDir;\n }\n\n return null;\n}\n\n/**\n * Migrate a single agent's session data\n */\nfunction migrateAgent(agentDir: string, stats: MigrationStats): void {\n const stateFile = join(getAgentsDir(), agentDir, 'state.json');\n\n if (!existsSync(stateFile)) {\n stats.warnings.push({\n file: stateFile,\n message: 'No state.json found',\n });\n return;\n }\n\n // Read agent state\n let state: any;\n try {\n state = JSON.parse(readFileSync(stateFile, 'utf-8'));\n } catch (err) {\n stats.errors.push({\n file: stateFile,\n error: `Failed to parse state.json: ${err}`,\n });\n return;\n }\n\n // Extract context - infer issueId and workspace when not in state.json\n const inferredIssueId = inferIssueId(agentDir);\n const context: AgentContext = {\n agentId: agentDir,\n issueId: state.issueId || inferredIssueId || 'UNKNOWN',\n sessionType: state.sessionType || (agentDir.startsWith('planning-') ? 'planning' : 'implementation'),\n workspace: state.workspace,\n };\n\n // Try to resolve workspace if not in state\n if (!context.workspace && context.issueId !== 'UNKNOWN') {\n context.workspace = resolveWorkspace(context.issueId) || '';\n }\n\n // Find session directory - try workspace path first, then scan by issue\n let sessionDir: string | null = null;\n\n if (context.workspace) {\n // GUARD: Reject broad parent directories that contain multiple projects.\n // Planning agents sometimes have workspace set to a parent dir like /home/user/Projects\n // instead of a specific workspace. Using such a path would attribute ALL sessions from\n // every project to a single issue, causing massive cost inflation.\n const isSpecificWorkspace = context.workspace.includes('workspaces/feature-') ||\n context.workspace.includes(`/${context.issueId.toLowerCase()}`) ||\n context.workspace.includes(`/${context.issueId.toUpperCase()}`);\n\n if (isSpecificWorkspace) {\n sessionDir = getSessionDir(context.workspace);\n } else {\n stats.warnings.push({\n file: stateFile,\n message: `Skipped broad workspace path \"${context.workspace}\" for ${context.issueId} (would attribute unrelated sessions)`,\n });\n }\n }\n\n // Fallback: scan Claude projects directory for matching session dirs\n if (!sessionDir && context.issueId !== 'UNKNOWN') {\n const sessionDirs = findSessionDirsByIssue(context.issueId);\n if (sessionDirs.length > 0) {\n sessionDir = sessionDirs[0];\n }\n }\n\n if (!sessionDir) {\n stats.warnings.push({\n file: stateFile,\n message: `No session directory found for ${context.issueId} (workspace: ${context.workspace || 'unknown'})`,\n });\n return;\n }\n\n // Process main session files\n try {\n const files = readdirSync(sessionDir).filter(f => f.endsWith('.jsonl'));\n\n for (const file of files) {\n const filePath = join(sessionDir, file);\n\n try {\n const usages = parseSessionFile(filePath);\n const events = usageToCostEvents(usages, context);\n\n for (const event of events) {\n appendCostEventSync(event);\n stats.eventsCreated++;\n stats.totalCost += event.cost;\n stats.totalTokens += event.input + event.output + event.cacheRead + event.cacheWrite;\n }\n\n stats.sessionFilesProcessed++;\n } catch (err) {\n stats.errors.push({\n file: filePath,\n error: `${err}`,\n });\n }\n }\n } catch (err) {\n stats.errors.push({\n file: sessionDir,\n error: `Failed to read session directory: ${err}`,\n });\n }\n\n // Process subagent files (CRITICAL - this was missing in old code)\n const subagentsDir = join(sessionDir, 'subagents');\n if (existsSync(subagentsDir)) {\n try {\n const subagentFiles = readdirSync(subagentsDir).filter(f => f.endsWith('.jsonl'));\n\n for (const file of subagentFiles) {\n const filePath = join(subagentsDir, file);\n\n try {\n const usages = parseSessionFile(filePath);\n\n // Create subagent context\n const subagentContext: AgentContext = {\n ...context,\n agentId: `${context.agentId}-subagent-${file.replace('.jsonl', '')}`,\n };\n\n const events = usageToCostEvents(usages, subagentContext);\n\n for (const event of events) {\n appendCostEventSync(event);\n stats.eventsCreated++;\n stats.totalCost += event.cost;\n stats.totalTokens += event.input + event.output + event.cacheRead + event.cacheWrite;\n }\n\n stats.subagentFilesProcessed++;\n } catch (err) {\n stats.errors.push({\n file: filePath,\n error: `${err}`,\n });\n }\n }\n } catch (err) {\n stats.warnings.push({\n file: subagentsDir,\n message: `Failed to read subagents directory: ${err}`,\n });\n }\n }\n\n stats.agentsProcessed++;\n}\n\n// ============== Migration Entry Points ==============\n\n/**\n * Migrate all historical session data to events.jsonl\n */\nexport function migrateAllSessionsSync(): MigrationStats {\n const stats: MigrationStats = {\n agentsProcessed: 0,\n sessionFilesProcessed: 0,\n subagentFilesProcessed: 0,\n eventsCreated: 0,\n errors: [],\n warnings: [],\n totalCost: 0,\n totalTokens: 0,\n };\n\n console.log('Starting migration of historical session data...');\n\n const agentsDir = getAgentsDir();\n\n // Check if agents directory exists\n if (!existsSync(agentsDir)) {\n console.log('No agents directory found - nothing to migrate');\n return stats;\n }\n\n // Get all agent directories\n const agentDirs = readdirSync(agentsDir).filter(\n name => name.startsWith('agent-') || name.startsWith('planning-')\n );\n\n console.log(`Found ${agentDirs.length} agent directories to process`);\n\n // Process each agent\n for (const agentDir of agentDirs) {\n try {\n migrateAgent(agentDir, stats);\n } catch (err) {\n stats.errors.push({\n file: agentDir,\n error: `Failed to migrate agent: ${err}`,\n });\n }\n }\n\n // Log summary\n console.log('\\nMigration complete:');\n console.log(` Agents processed: ${stats.agentsProcessed}`);\n console.log(` Session files: ${stats.sessionFilesProcessed}`);\n console.log(` Subagent files: ${stats.subagentFilesProcessed}`);\n console.log(` Events created: ${stats.eventsCreated}`);\n console.log(` Total cost: $${stats.totalCost.toFixed(4)}`);\n console.log(` Total tokens: ${stats.totalTokens.toLocaleString()}`);\n console.log(` Errors: ${stats.errors.length}`);\n console.log(` Warnings: ${stats.warnings.length}`);\n\n return stats;\n}\n\n/**\n * Check if migration is needed\n */\nexport function needsMigrationSync(): boolean {\n // If events file doesn't exist, we need migration\n if (!eventsFileExists()) {\n return true;\n }\n\n // If events file is empty, we need migration\n const metadata = getLastEventMetadataSync();\n if (metadata.totalEvents === 0) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Migrate only if needed\n */\nexport function migrateIfNeededSync(): MigrationStats | null {\n if (!needsMigrationSync()) {\n console.log('Migration not needed - events file already exists with data');\n return null;\n }\n\n return migrateAllSessionsSync();\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\n/** Effect variant of migrateAllSessions. Failures surface as FsError. */\nexport const migrateAllSessions = (): Effect.Effect<MigrationStats, FsError> =>\n Effect.try({\n try: () => migrateAllSessionsSync(),\n catch: (cause) => new FsError({ path: '<all sessions>', operation: 'migrateAllSessions', cause }),\n });\n\n/** Effect variant of needsMigration. */\nexport const needsMigration = (): Effect.Effect<boolean, FsError> =>\n Effect.try({\n try: () => needsMigrationSync(),\n catch: (cause) => new FsError({ path: '<events>', operation: 'needsMigration', cause }),\n });\n\n/** Effect variant of migrateIfNeeded. */\nexport const migrateIfNeeded = (): Effect.Effect<MigrationStats | null, FsError> =>\n Effect.try({\n try: () => migrateIfNeededSync(),\n catch: (cause) => new FsError({ path: '<all sessions>', operation: 'migrateIfNeeded', cause }),\n });\n","import { findConversationForCostSessionSync } from '../overdeck/conversations.js';\nimport { getOverdeckDatabaseSync } from '../overdeck/infra.js';\n\nexport const NO_ISSUE_BUCKETS = {\n conversations: 'CONVERSATIONS',\n flywheel: 'FLYWHEEL',\n unattributed: 'UNATTRIBUTED',\n} as const;\n\nexport type NoIssueBucket = typeof NO_ISSUE_BUCKETS[keyof typeof NO_ISSUE_BUCKETS];\n\nexport type ConversationSessionLookup = (input: {\n sessionId?: string | null;\n agentId?: string | null;\n}) => { name: string } | null;\n\nexport function classifySessionBucket(\n input: { sessionId?: string | null; agentId?: string | null },\n lookup: ConversationSessionLookup,\n): NoIssueBucket {\n const conversation = lookup(input);\n if (!conversation) return NO_ISSUE_BUCKETS.unattributed;\n if (conversation.name === 'flywheel-orchestrator') return NO_ISSUE_BUCKETS.flywheel;\n return NO_ISSUE_BUCKETS.conversations;\n}\n\ninterface UnknownCostSessionRow {\n sessionId: string | null;\n agentId: string | null;\n}\n\nexport function reclassifyUnknownCostEventsSync(): { updated: number } {\n const db = getOverdeckDatabaseSync();\n const rows = db.prepare(\n `SELECT DISTINCT session_id AS sessionId, agent_id AS agentId\n FROM cost_events\n WHERE issue_id = 'UNKNOWN'`,\n ).all() as UnknownCostSessionRow[];\n let updated = 0;\n const update = db.prepare(\n `UPDATE cost_events\n SET issue_id = ?\n WHERE issue_id = 'UNKNOWN'\n AND session_id IS ?\n AND agent_id IS ?`,\n );\n for (const row of rows) {\n const bucket = classifySessionBucket(row, findConversationForCostSessionSync);\n const result = update.run(bucket, row.sessionId, row.agentId);\n updated += result.changes;\n }\n return { updated };\n}\n","/**\n * Cost Reconciler — Periodic catch-up sweep for cost tracking\n *\n * The live recording hook (heartbeat-hook → record-cost-event.js) captures costs\n * in real time, but can miss events due to hook failures, process crashes, or\n * system reboots. The reconciler ensures completeness by periodically sweeping\n * ALL Claude transcript files and importing any events not yet in SQLite.\n *\n * v2 Architecture: Scans ~/.claude/projects/ directly instead of going through\n * agent state.json files. This catches everything:\n * - Work agent sessions (from worktree workspaces)\n * - Planning agent sessions (from main project dirs)\n * - Specialist sessions (review, test, merge)\n * - Interactive/manual Claude sessions\n *\n * Key properties:\n * - Idempotent: dedup on request_id — safe to run any number of times\n * - Incremental: tracks byte offset per session — only reads new bytes\n * - Non-destructive: never deletes from SQLite, append-only\n * - Catches everything: scans all transcript files regardless of source\n */\n\nimport { readFileSync, existsSync, readdirSync, openSync, readSync, fstatSync, closeSync } from 'fs';\nimport { join, basename } from 'path';\nimport { homedir } from 'os';\nimport { Effect } from 'effect';\nimport { calculateCostSync, getPricingSync, type AIProvider, type TokenUsage } from '../cost.js';\nimport { FsError } from '../errors.js';\nimport { CostDoorLive, CostWriter, type CostEvent as OverdeckCostEvent } from '../overdeck/cost.js';\nimport { findConversationForCostSessionSync } from '../overdeck/conversations.js';\nimport type { IssueId } from '../overdeck/issues.js';\nimport { classifySessionBucket, type ConversationSessionLookup } from './attribution.js';\nimport type { CostEvent } from './events.js';\n\n// ============== Types ==============\n\nexport interface ReconcileResult {\n sessionsScanned: number;\n sessionsWithNewData: number;\n eventsImported: number;\n duplicatesSkipped: number;\n errors: Array<{ path: string; error: string }>;\n earliestEventTs: string | null;\n latestEventTs: string | null;\n}\n\ninterface SessionMapping {\n agentId: string;\n issueId: string | null;\n sessionType: string; // planning, implementation, review, test, merge\n}\n\ninterface TranscriptEntry {\n type?: string;\n requestId?: string;\n timestamp?: string;\n ts?: string;\n created_at?: string;\n message?: {\n model?: string;\n usage?: {\n input_tokens?: number;\n output_tokens?: number;\n cache_read_input_tokens?: number;\n cache_creation_input_tokens?: number;\n };\n };\n}\n\n/**\n * Pi/oh-my-pi harness transcript entry (PAN-1935).\n *\n * Pi writes session transcripts to ~/.overdeck/agents/<id>/*.jsonl with a\n * different shape than Claude Code: top-level `type: 'message'` rows whose\n * `message.usage` uses short camelCase keys (`input`, `output`, `cacheRead`,\n * `cacheWrite`) — NOT the Anthropic `input_tokens`/`cache_read_input_tokens`\n * shape the Claude path expects. `message.provider` and `message.model` carry\n * the real routed provider/model (e.g. zai/glm-5.2); `message.responseId` is\n * the provider's unique response id, used for dedup.\n */\ninterface PiTranscriptEntry {\n type?: string;\n id?: string;\n timestamp?: string;\n message?: {\n role?: string;\n model?: string;\n provider?: string;\n responseId?: string;\n usage?: {\n input?: number;\n output?: number;\n cacheRead?: number;\n cacheWrite?: number;\n totalTokens?: number;\n };\n };\n}\n\n/**\n * Map a pi-reported provider string to the AIProvider union used by the pricing\n * layer. zai/kimi/minimax/mimo and anything unknown collapse to 'custom' (which\n * is how these providers are keyed in DEFAULT_PRICING).\n */\nfunction piProviderToAiProvider(provider?: string): AIProvider {\n if (provider === 'anthropic') return 'anthropic';\n if (provider === 'openai') return 'openai';\n if (provider === 'google') return 'google';\n return 'custom';\n}\n\n// ============== Path Helpers ==============\n\nfunction getAgentsDir(): string {\n return join(process.env.HOME || homedir(), '.overdeck', 'agents');\n}\n\nfunction getClaudeProjectsDir(): string {\n return join(process.env.HOME || homedir(), '.claude', 'projects');\n}\n\n/**\n * Extract session UUID from a transcript filename.\n * e.g., \"678c8d8d-cefe-45ca-bebc-bafe602aff93.jsonl\" → \"678c8d8d-cefe-45ca-bebc-bafe602aff93\"\n */\nfunction extractSessionId(filename: string): string {\n return basename(filename, '.jsonl');\n}\n\nexport function resolveUnmappedSessionIssueId(\n input: { sessionId?: string | null; agentId?: string | null },\n lookup: ConversationSessionLookup = findConversationForCostSessionSync,\n): string {\n return classifySessionBucket(input, lookup);\n}\n\n/**\n * Decode a Claude projects directory name back to the original cwd path.\n * e.g., \"-home-eltmon-Projects-krux-workspaces-feature-krux-4\" → \"/home/eltmon/Projects/krux/workspaces/feature-krux-4\"\n */\nfunction decodeClaudeDirName(dirName: string): string {\n // Strip leading dash, replace dashes with slashes, add leading slash\n // This is imperfect (directory names with dashes get mangled) but works for inference\n return '/' + dirName.replace(/^-/, '').replace(/-/g, '/');\n}\n\n// ============== Session-to-Agent Mapping ==============\n\n/**\n * Build a reverse index: session UUID → agent context.\n * Sources:\n * 1. sessions.json files in agent directories (authoritative — written by heartbeat hook)\n * 2. Agent state.json for issue/workspace context\n */\nfunction buildSessionIndex(): Map<string, SessionMapping> {\n const index = new Map<string, SessionMapping>();\n const agentsDir = getAgentsDir();\n\n if (!existsSync(agentsDir)) return index;\n\n let entries: string[];\n try {\n entries = readdirSync(agentsDir);\n } catch {\n return index;\n }\n\n for (const agentDir of entries) {\n const agentPath = join(agentsDir, agentDir);\n\n // Read sessions.json for the session UUID list\n const sessionsFile = join(agentPath, 'sessions.json');\n let sessionIds: string[] = [];\n if (existsSync(sessionsFile)) {\n try {\n sessionIds = JSON.parse(readFileSync(sessionsFile, 'utf-8'));\n } catch { /* skip */ }\n }\n\n // Read state.json for issue/workspace context and role.\n const stateFile = join(agentPath, 'state.json');\n let issueId: string | null = inferIssueId(agentDir);\n let stateRole: string | undefined;\n if (existsSync(stateFile)) {\n try {\n const state = JSON.parse(readFileSync(stateFile, 'utf-8'));\n if (state.issueId) issueId = state.issueId;\n if (state.role) stateRole = state.role;\n } catch { /* use inferred */ }\n }\n\n // Determine session type: prefer state.json role, then infer from agent directory name\n let sessionType = stateRole || 'work';\n const reviewSubRole = agentDir.match(/-review-(security|correctness|performance|requirements)$/i)?.[1]?.toLowerCase();\n if (reviewSubRole) {\n sessionType = `review.${reviewSubRole}`;\n } else if (agentDir.startsWith('planning-')) {\n sessionType = 'planning';\n } else if (agentDir.includes('review')) {\n sessionType = 'review';\n } else if (agentDir.includes('test')) {\n sessionType = 'test';\n } else if (agentDir.includes('merge')) {\n sessionType = 'merge';\n }\n\n // Map each session UUID to this agent\n for (const sid of sessionIds) {\n index.set(sid, {\n agentId: agentDir,\n issueId: issueId ?? resolveUnmappedSessionIssueId({ sessionId: sid, agentId: agentDir }),\n sessionType,\n });\n }\n }\n\n return index;\n}\n\n/**\n * Infer issue ID from a decoded path.\n * Looks for patterns like \"feature-min-787\", \"feature/pan-208\", etc.\n */\nfunction inferIssueFromPath(decodedPath: string): string | null {\n const match = decodedPath.match(/(?:feature[-/])?(pan|min|aud|krux|cli)[-/](\\d+)/i);\n if (match) {\n return `${match[1].toUpperCase()}-${match[2]}`;\n }\n return null;\n}\n\n/**\n * Infer issue ID from agent directory name.\n * e.g., 'agent-pan-105' → 'PAN-105', 'planning-min-663' → 'MIN-663'\n */\nfunction inferIssueId(agentDir: string): string | null {\n const stripped = agentDir.replace(/^(agent|planning|specialist)-/, '');\n const match = stripped.match(/^(?:.*-)?(pan|min|aud|krux|cli)-(\\d+)$/i);\n if (match) {\n return `${match[1].toUpperCase()}-${match[2]}`;\n }\n return null;\n}\n\n// ============== Offset Tracking ==============\n\nfunction getSessionOffset(_sessionId: string): number {\n return 0;\n}\n\nfunction saveSessionOffset(\n sessionId: string,\n byteOffset: number,\n newEvents: number,\n agentId: string,\n issueId: string,\n transcriptPath: string,\n): void {\n void sessionId;\n void byteOffset;\n void newEvents;\n void agentId;\n void issueId;\n void transcriptPath;\n}\n\n// ============== Transcript Processing ==============\n\n/**\n * Read new bytes from a transcript file starting at the given offset.\n */\nfunction readNewBytes(filePath: string, fromOffset: number): { content: string; newSize: number } | null {\n let fd: number;\n try {\n fd = openSync(filePath, 'r');\n } catch {\n return null;\n }\n\n try {\n const stat = fstatSync(fd);\n if (stat.size <= fromOffset) {\n return { content: '', newSize: stat.size };\n }\n\n const bytesToRead = stat.size - fromOffset;\n const buffer = Buffer.alloc(bytesToRead);\n readSync(fd, buffer, 0, bytesToRead, fromOffset);\n return { content: buffer.toString('utf-8'), newSize: stat.size };\n } finally {\n closeSync(fd);\n }\n}\n\n/**\n * Parse transcript content and extract cost events.\n * Only processes assistant messages with usage data and a requestId.\n */\nexport function extractCostEvents(\n content: string,\n agentId: string,\n issueId: string,\n sessionType: string,\n sessionId: string,\n): CostEvent[] {\n const events: CostEvent[] = [];\n const lines = content.split('\\n');\n\n for (const line of lines) {\n if (!line.trim()) continue;\n\n try {\n const entry = JSON.parse(line) as TranscriptEntry;\n\n if (entry.type !== 'assistant' || !entry.message?.usage) continue;\n\n const requestId: string | undefined = entry.requestId ?? undefined;\n\n const usage = entry.message.usage;\n const model = entry.message.model || 'claude-sonnet-4';\n const inputTokens = usage.input_tokens || 0;\n const outputTokens = usage.output_tokens || 0;\n const cacheReadTokens = usage.cache_read_input_tokens || 0;\n const cacheWriteTokens = usage.cache_creation_input_tokens || 0;\n\n if (inputTokens === 0 && outputTokens === 0 && cacheReadTokens === 0 && cacheWriteTokens === 0) continue;\n\n let provider: AIProvider = 'anthropic';\n if (model.includes('gpt')) provider = 'openai';\n else if (model.includes('gemini')) provider = 'google';\n else if (model.includes('kimi')) provider = 'custom' as AIProvider;\n else if (model.toLowerCase().startsWith('minimax')) provider = 'custom' as AIProvider;\n\n // Strip claudish prefix for pricing lookup: \"oai@gpt-5.4\" → \"gpt-5.4\"\n const pricingModel = model.replace(/^(?:oai|cx|go)@/, '');\n const pricing = getPricingSync(provider, pricingModel);\n if (!pricing) continue;\n\n const tokenUsage: TokenUsage = { inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, cacheTTL: '5m' };\n const cost = calculateCostSync(tokenUsage, pricing);\n const timestamp = entry.timestamp || entry.ts || entry.created_at || new Date().toISOString();\n\n events.push({\n ts: timestamp,\n type: 'cost',\n agentId,\n issueId,\n sessionType,\n provider,\n model,\n input: inputTokens,\n output: outputTokens,\n cacheRead: cacheReadTokens,\n cacheWrite: cacheWriteTokens,\n cost,\n requestId,\n sessionId,\n });\n } catch {\n // Skip malformed lines\n }\n }\n\n return events;\n}\n\n/**\n * Parse Pi/oh-my-pi transcript content and extract cost events (PAN-1935).\n *\n * Pi's `message.usage` uses short camelCase keys (`input`, `output`,\n * `cacheRead`, `cacheWrite`) and the real model/provider live on `message`\n * (not inferred from a Claude-style `model` field). Cost is always computed\n * from tokens × pricing because pi reports `cost.total = 0` for non-Anthropic\n * providers. Exported for unit testing.\n */\nexport function extractPiCostEvents(\n content: string,\n agentId: string,\n issueId: string,\n sessionType: string,\n sessionId: string,\n): CostEvent[] {\n const events: CostEvent[] = [];\n for (const line of content.split('\\n')) {\n if (!line.trim()) continue;\n let entry: PiTranscriptEntry;\n try {\n entry = JSON.parse(line) as PiTranscriptEntry;\n } catch {\n continue;\n }\n if (entry.type !== 'message') continue;\n const usage = entry.message?.usage;\n if (!usage) continue;\n\n const inputTokens = usage.input ?? 0;\n const outputTokens = usage.output ?? 0;\n const cacheReadTokens = usage.cacheRead ?? 0;\n const cacheWriteTokens = usage.cacheWrite ?? 0;\n if (inputTokens === 0 && outputTokens === 0 && cacheReadTokens === 0 && cacheWriteTokens === 0) continue;\n\n const model = entry.message?.model || 'unknown';\n const provider = piProviderToAiProvider(entry.message?.provider);\n // Strip any routing prefix (oai@/cx@/go@) for pricing lookup.\n const pricingModel = model.replace(/^(?:oai|cx|go)@/, '');\n const pricing = getPricingSync(provider, pricingModel);\n if (!pricing) continue;\n\n const tokenUsage: TokenUsage = { inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, cacheTTL: '5m' };\n const cost = calculateCostSync(tokenUsage, pricing);\n // Prefer the provider response id for precise dedup; fall back to a\n // session-scoped synthetic id so re-runs are idempotent.\n const requestId = entry.message?.responseId ?? (entry.id ? `${sessionId}#${entry.id}` : undefined);\n const timestamp = entry.timestamp || new Date().toISOString();\n\n events.push({\n ts: timestamp,\n type: 'cost',\n agentId,\n issueId,\n sessionType,\n provider,\n model,\n input: inputTokens,\n output: outputTokens,\n cacheRead: cacheReadTokens,\n cacheWrite: cacheWriteTokens,\n cost,\n requestId,\n sessionId,\n });\n }\n return events;\n}\n\n/**\n * Pi session transcripts in an agent dir. Pi transcripts begin with a\n * `{\"type\":\"session\",\"version\":3,...}` header; we detect that and skip the\n * sibling non-transcripts (activity.jsonl, cost-events.jsonl, pending-events).\n */\nfunction findPiTranscriptFiles(agentDir: string): string[] {\n let files: string[];\n try {\n files = readdirSync(agentDir).filter((f) => f.endsWith('.jsonl'));\n } catch {\n return [];\n }\n const transcripts: string[] = [];\n for (const f of files) {\n if (f === 'activity.jsonl' || f === 'cost-events.jsonl' || f === 'pending-events.jsonl') continue;\n const full = join(agentDir, f);\n try {\n const fd = openSync(full, 'r');\n const buf = Buffer.alloc(128);\n const bytesRead = readSync(fd, buf, 0, 128, 0);\n closeSync(fd);\n const head = buf.toString('utf-8', 0, bytesRead);\n if (/\"type\"\\s*:\\s*\"session\"/.test(head)) transcripts.push(full);\n } catch {\n // skip unreadable\n }\n }\n return transcripts;\n}\n\n/**\n * Sweep pi session transcripts under the per-agent dirs (~/.overdeck/agents)\n * and import their cost events through the same Overdeck cost writer the Claude\n * path uses (PAN-1935).\n * Independent of the pi extension hook, so it captures cost even when the\n * extension emits null usage or the wrong model label.\n */\nexport async function reconcilePiTranscripts(): Promise<ReconcileResult> {\n const result: ReconcileResult = {\n sessionsScanned: 0,\n sessionsWithNewData: 0,\n eventsImported: 0,\n duplicatesSkipped: 0,\n errors: [],\n earliestEventTs: null,\n latestEventTs: null,\n };\n\n const agentsDir = getAgentsDir();\n if (!existsSync(agentsDir)) return result;\n\n let agentDirs: string[];\n try {\n agentDirs = readdirSync(agentsDir);\n } catch {\n return result;\n }\n\n for (const agentDirName of agentDirs) {\n const agentPath = join(agentsDir, agentDirName);\n\n // Resolve issueId + sessionType from state.json (authoritative), falling\n // back to inference from the directory name.\n let issueId: string | null = inferIssueId(agentDirName);\n let sessionType = 'work';\n const stateFile = join(agentPath, 'state.json');\n if (existsSync(stateFile)) {\n try {\n const st = JSON.parse(readFileSync(stateFile, 'utf-8')) as { issueId?: string; role?: string };\n if (st.issueId) issueId = st.issueId;\n if (st.role) sessionType = st.role;\n } catch {\n // use inferred\n }\n }\n if (agentDirName.startsWith('planning-')) sessionType = 'planning';\n\n const transcripts = findPiTranscriptFiles(agentPath);\n for (const transcriptPath of transcripts) {\n const sessionId = basename(transcriptPath, '.jsonl');\n const resolvedIssueId = issueId ?? resolveUnmappedSessionIssueId({ sessionId, agentId: agentDirName });\n result.sessionsScanned++;\n try {\n const content = readFileSync(transcriptPath, 'utf-8');\n const events = extractPiCostEvents(content, agentDirName, resolvedIssueId, sessionType, sessionId);\n if (events.length === 0) continue;\n result.sessionsWithNewData++;\n const { inserted, duplicates, earliestEventTs, latestEventTs } = await recordCostEventsThroughOverdeck(events, `reconciler:${transcriptPath}`);\n result.eventsImported += inserted;\n result.duplicatesSkipped += duplicates;\n mergeCoverage(result, { earliestEventTs, latestEventTs });\n } catch (err) {\n result.errors.push({\n path: transcriptPath,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n }\n\n return result;\n}\n\nfunction toOverdeckCostEvent(event: CostEvent, sourceFile: string): OverdeckCostEvent {\n return {\n ts: new Date(event.ts),\n issueId: event.issueId ? (event.issueId as IssueId) : null,\n agentId: event.agentId ?? null,\n sessionId: event.sessionId ?? null,\n sessionType: event.sessionType ?? null,\n provider: event.provider ?? null,\n model: event.model ?? null,\n input: event.input ?? 0,\n output: event.output ?? 0,\n cacheRead: event.cacheRead ?? 0,\n cacheWrite: event.cacheWrite ?? 0,\n cost: event.cost ?? 0,\n requestId: event.requestId ?? null,\n sourceFile,\n };\n}\n\nasync function recordCostEventsThroughOverdeck(\n events: CostEvent[],\n sourceFile: string,\n opts: { dryRun?: boolean } = {},\n): Promise<{ inserted: number; duplicates: number; earliestEventTs: string | null; latestEventTs: string | null }> {\n let inserted = 0;\n let duplicates = 0;\n let earliestEventTs: string | null = null;\n let latestEventTs: string | null = null;\n for (const event of events) {\n const didInsert = await Effect.runPromise(\n CostWriter.use((writer) => writer.record(toOverdeckCostEvent(event, sourceFile), { dryRun: opts.dryRun })).pipe(\n Effect.provide(CostDoorLive),\n ),\n );\n if (didInsert) {\n inserted++;\n const iso = new Date(event.ts).toISOString();\n if (earliestEventTs == null || iso < earliestEventTs) earliestEventTs = iso;\n if (latestEventTs == null || iso > latestEventTs) latestEventTs = iso;\n } else {\n duplicates++;\n }\n }\n return { inserted, duplicates, earliestEventTs, latestEventTs };\n}\n\nfunction mergeCoverage(\n result: Pick<ReconcileResult, 'earliestEventTs' | 'latestEventTs'>,\n coverage: Pick<ReconcileResult, 'earliestEventTs' | 'latestEventTs'>,\n): void {\n if (coverage.earliestEventTs != null && (result.earliestEventTs == null || coverage.earliestEventTs < result.earliestEventTs)) {\n result.earliestEventTs = coverage.earliestEventTs;\n }\n if (coverage.latestEventTs != null && (result.latestEventTs == null || coverage.latestEventTs > result.latestEventTs)) {\n result.latestEventTs = coverage.latestEventTs;\n }\n}\n\nasync function reconcilePromise(opts: { dryRun?: boolean; includePi?: boolean } = {}): Promise<ReconcileResult> {\n const result: ReconcileResult = {\n sessionsScanned: 0,\n sessionsWithNewData: 0,\n eventsImported: 0,\n duplicatesSkipped: 0,\n errors: [],\n earliestEventTs: null,\n latestEventTs: null,\n };\n\n const claudeProjectsDir = getClaudeProjectsDir();\n if (!existsSync(claudeProjectsDir)) return result;\n\n // Build reverse index: session UUID → (agentId, issueId, sessionType)\n const sessionIndex = buildSessionIndex();\n\n // Scan all Claude project directories\n let projectDirs: string[];\n try {\n projectDirs = readdirSync(claudeProjectsDir);\n } catch {\n return result;\n }\n\n for (const dirName of projectDirs) {\n const projectDir = join(claudeProjectsDir, dirName);\n\n // Skip non-directories\n try {\n const stat = fstatSync(openSync(projectDir, 'r'));\n // fstatSync on a dir fd doesn't work well, use readdirSync as canary\n } catch { /* skip */ }\n\n // Infer issue ID from the directory name (decoded path)\n const decodedPath = decodeClaudeDirName(dirName);\n const pathIssueId = inferIssueFromPath(decodedPath);\n\n // Find all transcript JSONL files (top-level)\n let files: string[];\n try {\n files = readdirSync(projectDir).filter(f => f.endsWith('.jsonl'));\n } catch {\n continue;\n }\n\n // Process each transcript file\n for (const file of files) {\n const sessionId = extractSessionId(file);\n const transcriptPath = join(projectDir, file);\n result.sessionsScanned++;\n\n try {\n // Look up agent mapping for this session\n const mapping = sessionIndex.get(sessionId);\n const agentId = mapping?.agentId || 'unattributed';\n const issueId = mapping?.issueId || pathIssueId || resolveUnmappedSessionIssueId({ sessionId, agentId });\n const sessionType = mapping?.sessionType || 'implementation';\n\n // Get last processed offset\n const lastOffset = getSessionOffset(sessionId);\n\n // Read new bytes\n const readResult = readNewBytes(transcriptPath, lastOffset);\n if (!readResult || !readResult.content) {\n if (!opts.dryRun && readResult && readResult.newSize > lastOffset) {\n saveSessionOffset(sessionId, readResult.newSize, 0, agentId, issueId, transcriptPath);\n }\n continue;\n }\n\n // Extract cost events\n const events = extractCostEvents(readResult.content, agentId, issueId, sessionType, sessionId);\n\n if (events.length === 0) {\n if (!opts.dryRun) {\n saveSessionOffset(sessionId, readResult.newSize, 0, agentId, issueId, transcriptPath);\n }\n continue;\n }\n\n result.sessionsWithNewData++;\n\n const { inserted, duplicates, earliestEventTs, latestEventTs } = await recordCostEventsThroughOverdeck(events, `reconciler:${transcriptPath}`, { dryRun: opts.dryRun });\n result.eventsImported += inserted;\n result.duplicatesSkipped += duplicates;\n mergeCoverage(result, { earliestEventTs, latestEventTs });\n\n if (!opts.dryRun) {\n saveSessionOffset(sessionId, readResult.newSize, inserted, agentId, issueId, transcriptPath);\n }\n } catch (err) {\n result.errors.push({\n path: transcriptPath,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n // Also check subagents directory\n const subagentsDir = join(projectDir, 'subagents');\n if (existsSync(subagentsDir)) {\n try {\n const subFiles = readdirSync(subagentsDir).filter(f => f.endsWith('.jsonl'));\n for (const file of subFiles) {\n const sessionId = `sub-${extractSessionId(file)}`;\n const transcriptPath = join(subagentsDir, file);\n result.sessionsScanned++;\n\n try {\n const mapping = sessionIndex.get(sessionId);\n const agentId = mapping?.agentId || 'unattributed-subagent';\n const issueId = mapping?.issueId || pathIssueId || resolveUnmappedSessionIssueId({ sessionId, agentId });\n const sessionType = mapping?.sessionType || 'implementation';\n\n const lastOffset = getSessionOffset(sessionId);\n const readResult = readNewBytes(transcriptPath, lastOffset);\n if (!readResult || !readResult.content) continue;\n\n const events = extractCostEvents(readResult.content, agentId, issueId, sessionType, sessionId);\n\n if (events.length === 0) {\n if (!opts.dryRun) {\n saveSessionOffset(sessionId, readResult.newSize, 0, agentId, issueId, transcriptPath);\n }\n continue;\n }\n\n result.sessionsWithNewData++;\n const { inserted, duplicates, earliestEventTs, latestEventTs } = await recordCostEventsThroughOverdeck(events, `reconciler:${transcriptPath}`, { dryRun: opts.dryRun });\n result.eventsImported += inserted;\n result.duplicatesSkipped += duplicates;\n mergeCoverage(result, { earliestEventTs, latestEventTs });\n if (!opts.dryRun) {\n saveSessionOffset(sessionId, readResult.newSize, inserted, agentId, issueId, transcriptPath);\n }\n } catch (err) {\n result.errors.push({\n path: transcriptPath,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n } catch {\n // Non-fatal\n }\n }\n }\n\n // PAN-1935: also sweep pi/oh-my-pi harness transcripts under\n // ~/.overdeck/agents/*/ (the Claude scan above only covers ~/.claude/projects).\n if (opts.includePi ?? true) {\n const piResult = await reconcilePiTranscripts();\n result.sessionsScanned += piResult.sessionsScanned;\n result.sessionsWithNewData += piResult.sessionsWithNewData;\n result.eventsImported += piResult.eventsImported;\n result.duplicatesSkipped += piResult.duplicatesSkipped;\n result.errors.push(...piResult.errors);\n mergeCoverage(result, piResult);\n }\n\n return result;\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\n/**\n * Effect variant of reconcile. Per-file errors are still surfaced via\n * `result.errors`; only catastrophic failures (e.g. SQLite open failure)\n * surface on the Effect error channel.\n */\nexport const reconcile = (opts: { dryRun?: boolean; includePi?: boolean } = {}): Effect.Effect<ReconcileResult, FsError> =>\n Effect.tryPromise({\n try: () => reconcilePromise(opts),\n catch: (cause) => new FsError({ path: '<reconciler>', operation: 'reconcile', cause }),\n });\n","/**\n * Event Retention Management for Cost Tracking\n *\n * Manages the rolling 90-day retention window for cost events.\n */\n\nimport { Effect } from 'effect';\nimport { readEventsSync, replaceEventsFileSync, getLastEventMetadataSync, CostEvent } from './events.js';\nimport { rebuildCacheSync } from './aggregator.js';\nimport { FsError } from '../errors.js';\n\n// ============== Types ==============\n\nexport interface RetentionStats {\n totalEvents: number;\n eventsRemoved: number;\n eventsRetained: number;\n oldestEventTs: string | null;\n newestEventTs: string | null;\n}\n\n// ============== Retention Logic ==============\n\n/**\n * Prune events older than the specified retention period\n * Returns stats about what was pruned\n */\nexport function pruneOldEventsSync(retentionDays: number = 90): RetentionStats {\n console.log(`Pruning events older than ${retentionDays} days...`);\n\n // Calculate cutoff date using milliseconds (not setDate, which is DST-sensitive)\n const cutoffTs = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000).toISOString();\n\n // Read all events\n const allEvents = readEventsSync();\n const totalEvents = allEvents.length;\n\n if (totalEvents === 0) {\n return {\n totalEvents: 0,\n eventsRemoved: 0,\n eventsRetained: 0,\n oldestEventTs: null,\n newestEventTs: null,\n };\n }\n\n // Filter events to keep only those within retention window\n const retainedEvents = allEvents.filter(event => event.ts >= cutoffTs);\n const eventsRemoved = totalEvents - retainedEvents.length;\n\n // Get timestamps\n const oldestEventTs = retainedEvents.length > 0 ? retainedEvents[0].ts : null;\n const newestEventTs = retainedEvents.length > 0 ? retainedEvents[retainedEvents.length - 1].ts : null;\n\n // If we removed any events, write the pruned file\n if (eventsRemoved > 0) {\n console.log(`Removing ${eventsRemoved} events older than ${cutoffTs}...`);\n replaceEventsFileSync(retainedEvents);\n\n // Rebuild cache after pruning\n console.log('Rebuilding cache after pruning...');\n rebuildCacheSync();\n\n console.log(`Pruning complete: removed ${eventsRemoved} events, retained ${retainedEvents.length} events`);\n } else {\n console.log('No events to prune - all events are within retention window');\n }\n\n return {\n totalEvents,\n eventsRemoved,\n eventsRetained: retainedEvents.length,\n oldestEventTs,\n newestEventTs,\n };\n}\n\n/**\n * Check if pruning is needed based on oldest event\n */\nexport function needsPruningSync(retentionDays: number = 90): boolean {\n const allEvents = readEventsSync();\n\n if (allEvents.length === 0) {\n return false;\n }\n\n // Check oldest event\n const oldestEvent = allEvents[0];\n const oldestDate = new Date(oldestEvent.ts);\n const cutoffDate = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000);\n\n return oldestDate < cutoffDate;\n}\n\n/**\n * Get retention status\n */\nexport function getRetentionStatusSync(retentionDays: number = 90): {\n totalEvents: number;\n oldestEventTs: string | null;\n oldestEventAge: number; // days\n needsPruning: boolean;\n eventsToRemove: number;\n} {\n const allEvents = readEventsSync();\n\n if (allEvents.length === 0) {\n return {\n totalEvents: 0,\n oldestEventTs: null,\n oldestEventAge: 0,\n needsPruning: false,\n eventsToRemove: 0,\n };\n }\n\n const oldestEvent = allEvents[0];\n const oldestDate = new Date(oldestEvent.ts);\n const now = new Date();\n const oldestEventAge = Math.floor((now.getTime() - oldestDate.getTime()) / (1000 * 60 * 60 * 24));\n\n const cutoffTs = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000).toISOString();\n\n const eventsToRemove = allEvents.filter(e => e.ts < cutoffTs).length;\n\n return {\n totalEvents: allEvents.length,\n oldestEventTs: oldestEvent.ts,\n oldestEventAge,\n needsPruning: oldestEventAge > retentionDays,\n eventsToRemove,\n };\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\n/** Effect variant of pruneOldEvents. */\nexport const pruneOldEvents = (\n retentionDays: number = 90,\n): Effect.Effect<RetentionStats, FsError> =>\n Effect.try({\n try: () => pruneOldEventsSync(retentionDays),\n catch: (cause) => new FsError({ path: '<events>', operation: 'pruneOldEvents', cause }),\n });\n\n/** Effect variant of needsPruning. */\nexport const needsPruning = (\n retentionDays: number = 90,\n): Effect.Effect<boolean, FsError> =>\n Effect.try({\n try: () => needsPruningSync(retentionDays),\n catch: (cause) => new FsError({ path: '<events>', operation: 'needsPruning', cause }),\n });\n\n/** Effect variant of getRetentionStatus. */\nexport const getRetentionStatus = (\n retentionDays: number = 90,\n): Effect.Effect<\n {\n totalEvents: number;\n oldestEventTs: string | null;\n oldestEventAge: number;\n needsPruning: boolean;\n eventsToRemove: number;\n },\n FsError\n> =>\n Effect.try({\n try: () => getRetentionStatusSync(retentionDays),\n catch: (cause) => new FsError({ path: '<events>', operation: 'getRetentionStatus', cause }),\n });\n","/**\n * WAL Import — scan project repos and import cost events into panopticon.db\n *\n * Called:\n * - On `pan up` (after startup) to pick up events written by other developers\n * - By `pan sync-costs` CLI command for on-demand sync\n */\n\nimport { existsSync } from 'fs';\nimport { readdir, readFile } from 'fs/promises';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport { listProjectsSync } from '../projects.js';\nimport { FsError } from '../errors.js';\nimport type { CostEvent } from './events.js';\nimport { CostDoorLive, CostWriter, type CostEvent as OverdeckCostEvent } from '../overdeck/cost.js';\nimport type { IssueId } from '../overdeck/issues.js';\n\nconst DEFAULT_EVENTS_SUBDIR = '.pan/events';\n\nexport interface SyncResult {\n /** Total events imported across all projects */\n imported: number;\n /** Total duplicates skipped (already in DB) */\n duplicates: number;\n /** Files processed */\n filesScanned: number;\n /** Per-project breakdown */\n byProject: Record<string, { imported: number; duplicates: number; files: number }>;\n /** Any errors encountered (non-fatal) */\n errors: string[];\n}async function syncWalFromAllProjectsPromise(): Promise<SyncResult> {\n const result: SyncResult = {\n imported: 0,\n duplicates: 0,\n filesScanned: 0,\n byProject: {},\n errors: [],\n };\n\n const projects = listProjectsSync();\n\n for (const { key, config } of projects) {\n const repoPath = config.events_repo ?? config.path;\n const eventsSubdir = config.events_path ?? DEFAULT_EVENTS_SUBDIR;\n const eventsDir = join(repoPath, eventsSubdir);\n\n if (!existsSync(eventsDir)) continue;\n\n const projectStats = { imported: 0, duplicates: 0, files: 0 };\n\n let files: string[];\n try {\n files = (await readdir(eventsDir)).filter(f => f.endsWith('.jsonl'));\n } catch (err) {\n result.errors.push(`${key}: failed to read events dir: ${err}`);\n continue;\n }\n\n for (const file of files) {\n const filePath = join(eventsDir, file);\n const events = await parseWalFile(filePath, result.errors);\n if (events.length === 0) continue;\n\n try {\n const { inserted, duplicates } = await recordCostEventsThroughOverdeck(events, filePath);\n projectStats.imported += inserted;\n projectStats.duplicates += duplicates;\n projectStats.files++;\n result.filesScanned++;\n } catch (err) {\n result.errors.push(`${key}/${file}: import failed: ${err}`);\n }\n }\n\n if (projectStats.files > 0 || projectStats.imported > 0) {\n result.byProject[key] = projectStats;\n result.imported += projectStats.imported;\n result.duplicates += projectStats.duplicates;\n }\n }\n\n return result;\n}async function syncWalFromDirPromise(eventsDir: string): Promise<{ imported: number; duplicates: number; files: number; errors: string[] }> {\n const stats = { imported: 0, duplicates: 0, files: 0, errors: [] as string[] };\n\n if (!existsSync(eventsDir)) return stats;\n\n let files: string[];\n try {\n files = (await readdir(eventsDir)).filter(f => f.endsWith('.jsonl'));\n } catch (err) {\n stats.errors.push(`Failed to read dir ${eventsDir}: ${err}`);\n return stats;\n }\n\n for (const file of files) {\n const filePath = join(eventsDir, file);\n const events = await parseWalFile(filePath, stats.errors);\n if (events.length === 0) continue;\n\n try {\n const { inserted, duplicates } = await recordCostEventsThroughOverdeck(events, filePath);\n stats.imported += inserted;\n stats.duplicates += duplicates;\n stats.files++;\n } catch (err) {\n stats.errors.push(`${file}: import failed: ${err}`);\n }\n }\n\n return stats;\n}\n\n// ============== Helpers ==============\n\nfunction toOverdeckCostEvent(event: CostEvent, sourceFile: string): OverdeckCostEvent {\n return {\n ts: new Date(event.ts),\n issueId: event.issueId ? (event.issueId as IssueId) : null,\n agentId: event.agentId ?? null,\n sessionId: event.sessionId ?? null,\n sessionType: event.sessionType ?? null,\n provider: event.provider ?? null,\n model: event.model ?? null,\n input: event.input ?? 0,\n output: event.output ?? 0,\n cacheRead: event.cacheRead ?? 0,\n cacheWrite: event.cacheWrite ?? 0,\n cost: event.cost ?? 0,\n requestId: event.requestId ?? null,\n sourceFile,\n };\n}\n\nasync function recordCostEventsThroughOverdeck(\n events: CostEvent[],\n sourceFile: string,\n): Promise<{ inserted: number; duplicates: number }> {\n let inserted = 0;\n let duplicates = 0;\n for (const event of events) {\n const didInsert = await Effect.runPromise(\n CostWriter.use((writer) => writer.record(toOverdeckCostEvent(event, sourceFile))).pipe(\n Effect.provide(CostDoorLive),\n ),\n );\n if (didInsert) inserted++;\n else duplicates++;\n }\n return { inserted, duplicates };\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\n/**\n * Effect variant of syncWalFromAllProjects. The underlying impl already\n * collects per-file errors in `result.errors` rather than throwing, so the\n * Effect channel only sees catastrophic failures (e.g. listProjects throwing).\n */\nexport const syncWalFromAllProjects = (): Effect.Effect<SyncResult, FsError> =>\n Effect.tryPromise({\n try: () => syncWalFromAllProjectsPromise(),\n catch: (cause) => new FsError({ path: '<all projects>', operation: 'syncWalFromAllProjects', cause }),\n });\n\n/** Effect variant of syncWalFromDir. */\nexport const syncWalFromDir = (\n eventsDir: string,\n): Effect.Effect<{ imported: number; duplicates: number; files: number; errors: string[] }, FsError> =>\n Effect.tryPromise({\n try: () => syncWalFromDirPromise(eventsDir),\n catch: (cause) => new FsError({ path: eventsDir, operation: 'syncWalFromDir', cause }),\n });\n\nasync function parseWalFile(filePath: string, errors: string[]): Promise<CostEvent[]> {\n let content: string;\n try {\n content = await readFile(filePath, 'utf-8');\n } catch (err) {\n errors.push(`Failed to read ${filePath}: ${err}`);\n return [];\n }\n\n const events: CostEvent[] = [];\n const lines = content.split('\\n').filter(l => l.trim());\n\n for (const line of lines) {\n try {\n const event = JSON.parse(line) as CostEvent;\n // Basic validation\n if (event.ts && event.agentId && event.issueId && event.model) {\n events.push(event);\n }\n } catch {\n // Skip malformed lines\n }\n }\n\n return events;\n}\n","/**\n * Cost Tracking Module - Event-Sourced Architecture\n *\n * Exports all public functions for cost tracking, aggregation, and migration.\n */\n\n// Event log management\nexport {\n appendCostEventSync,\n readEventsSync,\n tailEventsSync,\n readEventsFromLineSync,\n getLastEventMetadataSync,\n replaceEventsFileSync,\n deduplicateEventsSync,\n eventsFileExists,\n getEventsFilePath,\n // Effect variants (PAN-1249)\n appendCostEvent,\n readEvents,\n tailEvents,\n readEventsFromLine,\n getLastEventMetadata,\n replaceEventsFile,\n deduplicateEvents,\n type CostEvent,\n type EventMetadata,\n type ReadEventsOptions,\n} from './events.js';\n\n// Aggregation cache management\nexport {\n loadCacheSync,\n saveCacheSync,\n updateCacheFromEventsSync,\n rebuildCacheSync,\n syncCacheSync,\n getCostsByIssueSync,\n getCostsForIssueSync,\n setIssueBudgetSync,\n getCacheStatus,\n // Effect variants (PAN-1249)\n loadCache,\n saveCache,\n updateCacheFromEvents,\n rebuildCache,\n syncCache,\n getCostsByIssue,\n getCostsForIssue,\n setIssueBudget,\n type CostCache,\n type IssueStats,\n type ModelStats,\n type StageStats,\n} from './aggregator.js';\n\n// Historical data migration\nexport {\n migrateAllSessionsSync,\n needsMigrationSync,\n migrateIfNeededSync,\n // Effect variants (PAN-1249)\n migrateAllSessions,\n needsMigration,\n migrateIfNeeded,\n type MigrationStats,\n} from './migration.js';\n\n// Cost reconciler — periodic catch-up sweep\nexport {\n reconcile,\n type ReconcileResult,\n} from './reconciler.js';\n\n// Event retention\nexport {\n pruneOldEventsSync,\n needsPruningSync,\n getRetentionStatusSync,\n // Effect variants (PAN-1249)\n pruneOldEvents,\n needsPruning,\n getRetentionStatus,\n type RetentionStats,\n} from './retention.js';\n\n// WAL writers / sync (PAN-1249)\nexport {\n appendToWalSync,\n resolveWalDir,\n appendToWal,\n} from './wal.js';\nexport {\n syncWalFromAllProjects,\n syncWalFromDir,\n type SyncResult,\n} from './sync-wal.js';\n","import { getAllActiveQueues } from '../../../lib/overdeck/merge-sync.js';\n\nexport type MergeTriggerHandler = (issueId: string) => Promise<unknown>;\n\nlet mergeTriggerHandler: MergeTriggerHandler | null = null;\n\nexport function setMergeQueueTriggerHandler(handler: MergeTriggerHandler): void {\n mergeTriggerHandler = handler;\n}\n\nexport async function resumeQueuedMerges(): Promise<void> {\n if (!mergeTriggerHandler) {\n console.warn('[overdeck] Merge queue resume skipped: trigger handler not registered');\n return;\n }\n\n const queues = getAllActiveQueues();\n const resumableIssues = queues\n .filter(queue => !queue.current && queue.queue.length > 0)\n .map(queue => queue.queue[0]!)\n .filter((issueId): issueId is string => Boolean(issueId));\n\n for (const issueId of resumableIssues) {\n console.log(`[overdeck] Resuming queued merge for ${issueId}`);\n mergeTriggerHandler(issueId).catch((err: any) => {\n console.error(`[overdeck] Failed to resume queued merge for ${issueId}: ${err.message}`);\n });\n }\n}\n","export function buildLocalMainRecoveryError(\n projectPath: string,\n aheadCount: number,\n behindCount: number,\n): string {\n if (aheadCount > 0 && behindCount > 0) {\n return `Local main has diverged from origin/main: ${aheadCount} local commit(s) and ${behindCount} remote commit(s) are unique. `\n + `A previous approve attempt left unpushed work while origin/main also advanced. To recover, preserve it:\\n`\n + ` cd ${projectPath} && git fetch origin main && git merge origin/main\\n`\n + 'Resolve any conflicts, then push main. If the local commits should not land, revert them explicitly instead of resetting. '\n + 'Then unstick the workspace and retry.';\n }\n return `Local main is ${aheadCount} commit(s) ahead of origin/main — a previous approve attempt left unpushed work. `\n + `To recover, preserve it:\\n cd ${projectPath} && git push origin main\\n`\n + 'If those commits should not land, revert them explicitly instead of resetting. Then unstick the workspace and retry.';\n}\n","/**\n * Merge-ops route module — extracted from routes/workspaces.ts.\n *\n * Merge / approve / queue endpoints:\n * POST /api/issues/:issueId/sync-main\n * POST /api/issues/:issueId/merge\n * POST /api/issues/:issueId/forge-approve\n * POST /api/issues/:issueId/forge-merge\n * POST /api/issues/:issueId/approve\n * GET /api/merge-queue\n * POST /api/internal/pipeline/notify\n *\n * Shared singletons (pending operations, project path, workspace info, readJsonBody)\n * stay owned by ../workspaces.js and are imported here.\n */\nimport { exec, execFile } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { basename, dirname, join } from 'node:path';\nimport { promisify } from 'node:util';\nimport { Effect, Layer } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\nimport { messageAgent, getAgentState, spawnAgent } from '../../../../lib/agents.js';\nimport { queryBeadsForIssue, type BeadEntry } from '../../../../lib/beads-query.js';\nimport { syncMainIntoWorkspace } from '../../../../lib/cloister/merge-agent.js';\nimport { MainDivergedError, gitPush } from '../../../../lib/git/operations.js';\nimport { listGitOperationsSync } from '../../../../lib/git-activity.js';\nimport { extractNumberSync, extractPrefixSync, parseIssueIdSync } from '../../../../lib/issue-id.js';\nimport { enqueueMerge, getCurrentMerge, markMergeProcessing, dequeueMerge, getAllActiveQueues } from '../../../../lib/overdeck/merge.js';\nimport { findProjectByTeamSync } from '../../../../lib/projects.js';\nimport { getReviewStatusSync, markWorkspaceStuck, setReviewStatusSync as setReviewStatusBase, type ReviewStatus } from '../../../../lib/review-status.js';\nimport { isStatePlaneOnlyStatus } from '../../../../lib/state-plane.js';\nimport { getWorkAgentLifecycleStateSync } from '../../../../lib/work-agent-lifecycle.js';\nimport { findPlan } from '../../../../lib/vbrief/io.js';\nimport { isIntegrationPermissionError, verifyAppCanMerge } from '../../../../lib/github-app.js';\nimport { resolveGitHubIssueSync as resolveGitHubIssueShared } from '../../../../lib/tracker-utils.js';\nimport { sessionExists } from '../../../../lib/tmux.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { EventStoreService } from '../../services/domain-services.js';\nimport { setMergeQueueTriggerHandler } from '../../services/merge-queue-service.js';\nimport { httpHandler } from '../http-handler.js';\nimport { _serverManagedMerges } from '../specialists.js';\nimport { completePendingOperation, getPendingOperation, getProjectPath, getWorkspaceInfoForIssue, readJsonBody, setPendingOperation, setReviewStatus } from '../workspaces.js';\nimport { buildLocalMainRecoveryError } from './git-recovery-advice.js';\nconst execAsync = promisify(exec);\nconst execFileAsync = promisify(execFile);\nexport const shouldBlockApproveForDirtyStatus = (status: string): boolean =>\n status.trim() !== '' && !isStatePlaneOnlyStatus(status);\n\nasync function ensureWorkAgentReadyForMerge(\n issueId: string,\n workspacePath: string,\n rebaseMsg: string,\n): Promise<{ recovered: boolean; agentId: string; detail: string }> {\n const agentId = `agent-${issueId.toLowerCase()}`;\n const lifecycle = getWorkAgentLifecycleStateSync(agentId);\n\n if (lifecycle.hasLiveTmuxSession) {\n await messageAgent(agentId, rebaseMsg);\n return { recovered: true, agentId, detail: 'Work agent already running; sent merge preparation request.' };\n }\n\n const agentState = await Effect.runPromise(getAgentState(agentId));\n if (agentState) {\n try {\n await messageAgent(agentId, rebaseMsg);\n const updatedLifecycle = getWorkAgentLifecycleStateSync(agentId);\n return {\n recovered: true,\n agentId,\n detail: updatedLifecycle.canResumeSession\n ? 'Resumed work agent and sent merge preparation request.'\n : 'Restarted work agent and sent merge preparation request.',\n };\n } catch (err: any) {\n if (!lifecycle.canStartFresh) {\n throw err;\n }\n }\n }\n\n if (!lifecycle.canStartFresh) {\n throw new Error(lifecycle.reason || `Work agent ${agentId} cannot be resumed or started for merge preparation.`);\n }\n\n const state = await spawnAgent({\n issueId,\n workspace: workspacePath,\n role: 'work',\n prompt: rebaseMsg,\n });\n\n return {\n recovered: true,\n agentId,\n detail: `Started fresh work agent ${state.id} and sent merge preparation request.`,\n };\n}\n\n/**\n * Check whether origin/branchName already contains origin/targetBranch.\n * If true, no rebase is needed — the branch is already up to date with target.\n */\nexport async function isBranchAlreadyRebased(\n workspacePath: string,\n branchName: string,\n targetBranch: string,\n): Promise<{ alreadyRebased: boolean; currentHead?: string }> {\n try {\n await Promise.all([\n execFileAsync('git', ['fetch', 'origin', targetBranch], { cwd: workspacePath, encoding: 'utf-8', timeout: 15000 }),\n execFileAsync('git', ['fetch', 'origin', branchName], { cwd: workspacePath, encoding: 'utf-8', timeout: 15000 }),\n ]);\n await execFileAsync(\n 'git',\n ['merge-base', '--is-ancestor', `origin/${targetBranch}`, `origin/${branchName}`],\n { cwd: workspacePath, encoding: 'utf-8', timeout: 5000 }\n );\n const { stdout: currentHead } = await execFileAsync(\n 'git',\n ['rev-parse', `origin/${branchName}`],\n { cwd: workspacePath, encoding: 'utf-8', timeout: 5000 }\n );\n return { alreadyRebased: true, currentHead: currentHead.trim() };\n } catch {\n return { alreadyRebased: false };\n }\n}\n\n\nfunction parseGitHubPullRequestUrl(url?: string | null): { owner: string; repo: string; number: number } | null {\n if (!url) return null;\n const match = url.match(/github\\.com\\/([^/]+)\\/([^/]+)\\/pull\\/(\\d+)/);\n if (!match) return null;\n return {\n owner: match[1],\n repo: match[2],\n number: Number.parseInt(match[3], 10),\n };\n}\n\n// Exported for unit tests covering late-success merge reconciliation guards.\nexport async function reconcileGitHubMergeStatus(issueId: string, status: Pick<ReviewStatus, 'prUrl' | 'mergeStatus' | 'readyForMerge'> | null | undefined): Promise<boolean> {\n if (!status?.prUrl) return false;\n\n const prRef = parseGitHubPullRequestUrl(status.prUrl);\n if (!prRef) return false;\n\n try {\n const { getPullRequestState, isGitHubAppConfigured } = await import('../../../../lib/github-app.js');\n if (!isGitHubAppConfigured()) return false;\n\n const prState = await Effect.runPromise(getPullRequestState(prRef.owner, prRef.repo, prRef.number));\n console.log(`[merge] reconcileGitHubMergeStatus: ${issueId} PR #${prRef.number} merged=${prState.merged} state=${prState.state}`);\n if (!prState.merged) return false;\n\n setReviewStatus(issueId, {\n mergeStatus: 'merged',\n mergeNotes: undefined,\n readyForMerge: false,\n });\n completePendingOperation(issueId, null);\n return true;\n } catch (err: any) {\n console.warn(`[merge] Failed to reconcile PR state for ${issueId}: ${err.message}`);\n return false;\n }\n}\n\n\nasync function readBeadsFromJsonl(workspacePath: string, issueId: string): Promise<BeadEntry[]> {\n try {\n const jsonlPath = join(workspacePath, '.beads', 'issues.jsonl');\n if (!existsSync(jsonlPath)) return [];\n const raw = await readFile(jsonlPath, 'utf-8');\n const issueLower = issueId.toLowerCase();\n const beads: BeadEntry[] = [];\n for (const line of raw.split('\\n')) {\n if (!line.trim()) continue;\n try {\n const entry = JSON.parse(line);\n const labels = Array.isArray(entry.labels) ? entry.labels : [];\n if (labels.some((l: string) => l.toLowerCase() === issueLower)) {\n beads.push({\n id: String(entry.id ?? ''),\n title: String(entry.title ?? ''),\n status: String(entry.status ?? 'open'),\n labels: labels as string[],\n });\n }\n } catch { /* skip malformed lines */ }\n }\n return beads;\n } catch {\n return [];\n }\n}\n\n/**\n * Build a rich PR body with issue link, beads task summary, and AC checklist\n * from the vBRIEF plan. Exported for testing.\n */\nexport async function buildRichPRBody(issueId: string, workspacePath: string): Promise<string> {\n const lines: string[] = [];\n\n // Non-closing reference on purpose: a closing keyword (\"Closes #N\") hands\n // close authority to GitHub, which fires the moment the PR's head becomes\n // reachable from main and races the pipeline's verifying_on_main → close-out\n // lifecycle (the first UAT batch promote closed 2 of 3 member issues\n // mid-handoff, 2026-06-11). Overdeck's close-out owns issue closing.\n lines.push(`**Issue:** #${extractNumberSync(issueId) ?? issueId}`);\n lines.push('');\n\n // Acceptance criteria checklist from vBRIEF plan items\n try {\n const planPath = await Effect.runPromise(findPlan(workspacePath));\n if (planPath && existsSync(planPath)) {\n const raw = await readFile(planPath, 'utf-8');\n const doc = JSON.parse(raw);\n const items: Array<{ status: string; title: string }> = doc?.plan?.items ?? [];\n if (items.length > 0) {\n lines.push('## Acceptance Criteria');\n lines.push('');\n for (const item of items) {\n const checked = item.status === 'completed' ? 'x' : ' ';\n lines.push(`- [${checked}] ${item.title}`);\n }\n lines.push('');\n }\n }\n } catch {\n // No vBRIEF plan — omit checklist\n }\n\n // Beads task summary from live Dolt database via bd CLI\n try {\n // Use a short lock timeout so PR-body generation does not block behind CLI\n // processes that hold the cross-process bd lock. queryBeadsForIssue already\n // falls back to JSONL on failure, so the explicit fallback here is a safety net.\n const queryResult = await Effect.runPromise(\n queryBeadsForIssue(workspacePath, issueId, { acquisitionTimeoutMs: 500 }),\n );\n let beads = queryResult.beads;\n if (beads.length === 0) {\n // Fallback: read from .beads/issues.jsonl when bd CLI is unavailable\n beads = await readBeadsFromJsonl(workspacePath, issueId);\n }\n if (beads.length > 0) {\n lines.push('## Implementation Tasks');\n lines.push('');\n for (const bead of beads) {\n const checked = bead.status === 'closed' ? 'x' : ' ';\n lines.push(`- [${checked}] ${bead.title.replace(/^[^:]+:\\s*/, '')}`);\n }\n lines.push('');\n }\n } catch {\n // No beads — omit task list\n }\n\n return lines.join('\\n') || `Automated PR for ${issueId}`;\n}\n\nasync function ensurePRExists(\n issueId: string,\n options?: { cwd?: string; branchName?: string; targetBranch?: string }\n): Promise<{ created: boolean; prUrl?: string; error?: string }> {\n try {\n const issueLower = issueId.toLowerCase();\n const branchName = options?.branchName ?? `feature/${issueLower}`;\n const targetBranch = options?.targetBranch ?? 'main';\n const execOptions: Parameters<typeof execFileAsync>[2] = { encoding: 'utf-8' };\n if (options?.cwd) execOptions.cwd = options.cwd;\n\n // Check for existing PR\n let existingOut: string = '';\n try {\n const { stdout } = await execFileAsync('gh', ['pr', 'view', branchName, '--json', 'url', '--jq', '.url'], execOptions);\n existingOut = String(stdout);\n } catch { /* no existing PR */ }\n const existing = existingOut.trim();\n if (existing) return { created: false, prUrl: existing };\n\n // Build rich PR body if workspace path is available\n const prBody = options?.cwd ? await buildRichPRBody(issueId, options.cwd) : `Automated PR for ${issueId}`;\n\n // Write body to a temp file to avoid shell escaping issues\n const { tmpdir } = await import('os');\n const { join: pathJoin } = await import('path');\n const { writeFile: writeFileAsync, unlink: unlinkAsync } = await import('fs/promises');\n const bodyFile = pathJoin(tmpdir(), `pan-pr-body-${issueId}-${Date.now()}.md`);\n await writeFileAsync(bodyFile, prBody, 'utf-8');\n\n try {\n const { stdout: rawOut } = await execFileAsync('gh', ['pr', 'create', '--head', branchName, '--base', targetBranch, '--title', issueId, '--body-file', bodyFile], execOptions);\n const createOut = String(rawOut);\n // gh pr create prints the PR URL as the last line of stdout\n const prUrl = createOut.trim().split('\\n').pop()?.trim() || createOut.trim();\n return { created: true, prUrl };\n } finally {\n unlinkAsync(bodyFile).catch(() => {});\n }\n } catch (err: any) {\n return { created: false, error: err.message };\n }\n}\n\n\nexport async function pushApproveMain(\n issueId: string,\n projectPath: string,\n): Promise<ApprovePushResult> {\n try {\n await Effect.runPromise(gitPush(projectPath, 'origin', 'main', { issueId }));\n return { pushed: true };\n } catch (pushErr: unknown) {\n if (pushErr instanceof MainDivergedError) {\n // Mark the workspace stuck so Deacon skips it — no automatic retry.\n // Do NOT hard-reset local main here: that is a destructive operation that\n // must be explicit/user-confirmed, not a silent side-effect of a failed push.\n // The stuck flag prevents any further automatic approve attempts; when the\n // user manually unsticks and retries, the approve route's git pull --ff-only\n // step will detect the orphaned merge commit and surface a recoverable error\n // with instructions to preserve and reconcile the local commits.\n markWorkspaceStuck(issueId, 'main_diverged', {\n localSha: pushErr.localSha,\n remoteSha: pushErr.remoteSha,\n });\n const error = `Push aborted: origin/main has advanced past your local ancestor (remote: ${pushErr.remoteSha?.slice(0, 7)}, local: ${pushErr.localSha?.slice(0, 7)}). A hotfix may have landed. Workspace marked stuck — to recover, preserve local commits: cd ${projectPath} && git fetch origin main && git merge origin/main, resolve any conflicts, push main, then unstick and retry.`;\n return { pushed: false, httpStatus: 409, error };\n }\n const message = pushErr instanceof Error ? pushErr.message : String(pushErr);\n const error = `Merge succeeded but push failed! Your work is safe locally.\\nPlease push manually: cd ${projectPath} && git push origin main\\nError: ${message}`;\n return { pushed: false, httpStatus: 400, error };\n }\n}\n\n// ─── Route: POST /api/issues/:issueId/sync-main ──────────────────────────\n\nconst postWorkspaceSyncMainRoute = HttpRouter.add(\n 'POST',\n '/api/issues/:issueId/sync-main',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const issueLower = issueId.toLowerCase();\n\n const workspaceInfo = getWorkspaceInfoForIssue(issueId);\n if (workspaceInfo.isRemote) {\n return jsonResponse(\n {\n success: false,\n error: 'Sync with Main is not supported for remote workspaces',\n },\n { status: 400 }\n );\n }\n\n const workspacePath =\n workspaceInfo.localPath ||\n join(projectPath, 'workspaces', `feature-${issueLower}`);\n\n if (!existsSync(workspacePath)) {\n return jsonResponse(\n { success: false, error: 'Workspace does not exist' },\n { status: 400 }\n );\n }\n\n console.log(`[sync-main] Starting sync for ${issueId} at ${workspacePath}`);\n\n const result = yield* Effect.promise(() => syncMainIntoWorkspace(workspacePath, issueId));\n\n if (result.success) {\n if (result.alreadyUpToDate) {\n return jsonResponse({\n success: true,\n alreadyUpToDate: true,\n message: 'Already up to date with main',\n });\n }\n return jsonResponse({\n success: true,\n commitCount: result.commitCount || 0,\n changedFiles: result.changedFiles || [],\n message: `Synced ${result.commitCount || 0} commit(s) from main`,\n });\n } else {\n const status = result.reason?.includes('uncommitted') ? 400 : 500;\n return jsonResponse(\n {\n success: false,\n error: result.reason || 'Sync failed',\n conflictFiles: result.conflictFiles,\n },\n { status }\n );\n }\n }))\n);\n\n// ─── Shared triggerMerge logic ────────────────────────────────────────────────\n\nexport interface TriggerMergeResult {\n success: boolean;\n statusCode: number;\n error?: string;\n message?: string;\n reviewStatus?: string;\n testStatus?: string;\n mergeStatus?: string;\n prUrl?: string;\n remote?: boolean;\n repos?: Array<{ repo: string; success: boolean; message: string; testsStatus?: string }>;\n testsStatus?: string;\n note?: string;\n mergeResult?: unknown;\n}\n\n// Per-project merge queue backed by SQLite (PAN-632).\n// Replaces the in-memory _mergeQueues Map — survives server restarts.\n\n/** Dequeue the next merge after current completes (success or failure). */\nfunction dequeueNextMerge(projectKey: string, completedIssueId?: string): void {\n const nextIssueId = dequeueMerge(projectKey, completedIssueId);\n if (nextIssueId) {\n console.log(`[merge] Dequeuing next merge: ${nextIssueId}`);\n triggerMerge(nextIssueId).catch(err =>\n console.error(`[merge] Queue error for ${nextIssueId}: ${err}`)\n );\n }\n}\n\nexport async function triggerMerge(issueId: string): Promise<TriggerMergeResult> {\n const reviewStatus = getReviewStatusSync(issueId);\n if (!reviewStatus?.readyForMerge) {\n return {\n success: false,\n statusCode: 400,\n error: 'Cannot merge: review and tests have not passed yet',\n reviewStatus: reviewStatus?.reviewStatus || 'pending',\n testStatus: reviewStatus?.testStatus || 'pending',\n };\n }\n\n // NOTE: Commit status reporting moved to AFTER rebase — see below.\n // The rebase changes the HEAD SHA, so statuses must be reported on the new commit.\n if (false && reviewStatus?.prUrl) {\n try {\n const { isGitHubAppConfigured, reportCommitStatus } = await import('../../../../lib/github-app.js');\n if (isGitHubAppConfigured()) {\n const prMatch = reviewStatus!.prUrl!.match(/\\/pull\\/(\\d+)/);\n if (prMatch) {\n const { stdout } = await execAsync(\n `gh pr view ${prMatch![1]} --json headRefOid --jq .headRefOid`,\n { encoding: 'utf-8', timeout: 10000 }\n );\n const sha = stdout.trim();\n if (sha) {\n await reportCommitStatus('eltmon', 'overdeck', sha, 'success', 'overdeck/review', 'Review passed');\n await reportCommitStatus('eltmon', 'overdeck', sha, 'success', 'overdeck/test', 'Tests passed');\n console.log(`[merge] Reported commit statuses for ${issueId} (${sha.slice(0, 8)})`);\n }\n }\n }\n } catch (err: any) {\n console.warn(`[merge] Failed to report commit statuses: ${err.message}`);\n }\n }\n\n if (reviewStatus?.mergeStatus === 'merging') {\n const pendingOp = getPendingOperation(issueId);\n const activelyMerging = pendingOp?.type === 'merge' && pendingOp?.status === 'running';\n if (activelyMerging) {\n return {\n success: false,\n statusCode: 400,\n error: 'Merge already in progress',\n mergeStatus: 'merging',\n };\n }\n console.log(\n `[merge] Clearing stuck mergeStatus for ${issueId} (pending op: ${pendingOp?.status ?? 'absent'})`\n );\n setReviewStatus(issueId, { mergeStatus: undefined });\n }\n\n if (reviewStatus?.mergeStatus === 'merged') {\n return { success: false, statusCode: 400, error: 'Already merged', mergeStatus: 'merged' };\n }\n\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const issueLower = issueId.toLowerCase();\n\n // Serialize merges per project via persistent SQLite queue (PAN-632).\n // Survives server restarts — no more lost queues.\n const projectKey = issuePrefix.toLowerCase();\n const normalizedId = issueId.toUpperCase();\n const currentlyMerging = getCurrentMerge(projectKey);\n if (currentlyMerging && currentlyMerging !== normalizedId) {\n // Another merge is in progress — queue this one\n const position = enqueueMerge(projectKey, normalizedId);\n setReviewStatus(issueId, { mergeStatus: 'queued', mergeStep: 'queued' });\n console.log(`[merge] Queued ${issueId} (position ${position}, waiting for ${currentlyMerging})`);\n return {\n success: true,\n statusCode: 200,\n message: `Queued for merge (position ${position}, waiting for ${currentlyMerging})`,\n mergeStatus: 'queued',\n };\n }\n // Mark as processing IMMEDIATELY — before any async work — to prevent race conditions.\n // SQLite write is atomic — no window for concurrent calls to both pass the check.\n enqueueMerge(projectKey, normalizedId);\n markMergeProcessing(projectKey, normalizedId);\n\n const workspaceInfo = getWorkspaceInfoForIssue(issueId);\n\n // Use the actual resolved workspace path (handles legacy feature-484 naming)\n const workspacePath = (!workspaceInfo.isRemote && workspaceInfo.localPath)\n ? workspaceInfo.localPath\n : join(projectPath, 'workspaces', `feature-${issueLower}`);\n const workspaceDirName = basename(workspacePath);\n const branchName = workspaceDirName.startsWith('feature-')\n ? `feature/${workspaceDirName.slice('feature-'.length)}`\n : `feature/${issueLower}`;\n\n setReviewStatus(issueId, { mergeStatus: 'merging', mergeStep: 'validating-pr' });\n\n const normalizedMergeId = issueId.toUpperCase();\n _serverManagedMerges.add(normalizedMergeId);\n setPendingOperation(issueId, 'merge');\n let queueAdvanced = false;\n\n const advanceQueue = (): void => {\n if (queueAdvanced) return;\n queueAdvanced = true;\n _serverManagedMerges.delete(normalizedMergeId);\n dequeueNextMerge(projectKey, normalizedId);\n };\n\n try {\n if (workspaceInfo.isRemote && workspaceInfo.vmName) {\n console.log(\n `[merge] Remote workspace detected for ${issueId}, using review artifact merge...`\n );\n const { getMergeSetSync, ensureMergeSetForIssueSync } = await import('../../../../lib/merge-set.js');\n const { getForgeAdapter } = await import('../../../../lib/forge.js');\n const remoteMergeSet = getMergeSetSync(issueId) || ensureMergeSetForIssueSync(issueId);\n const remotePrimaryRepo = remoteMergeSet?.repos[0];\n const remoteTargetBranch = remotePrimaryRepo?.targetBranch || 'main';\n const remoteForge = remotePrimaryRepo?.forge || 'github';\n\n const prResult = await ensurePRExists(issueId, { targetBranch: remoteTargetBranch });\n if (!prResult.prUrl) {\n const error = `Failed to create PR: ${prResult.error || 'Unknown error'}`;\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: error });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 400, error };\n }\n const artifactUrl = remotePrimaryRepo?.artifactUrl || prResult.prUrl;\n const artifactId = remotePrimaryRepo?.artifactId;\n\n try {\n console.log(`[merge] Merging ${remoteForge} review artifact for ${issueId}...`);\n await getForgeAdapter(remoteForge).mergeReviewArtifact({\n forge: remoteForge,\n url: artifactUrl,\n id: artifactId,\n method: 'squash',\n });\n\n setReviewStatus(issueId, { mergeStatus: 'merged', mergeNotes: undefined, readyForMerge: false });\n completePendingOperation(issueId, null);\n\n const { postMergeLifecycle } = await import('../../../../lib/cloister/merge-agent.js');\n await postMergeLifecycle(issueId, projectPath);\n\n const remotePrNumber = prResult.prUrl.match(/\\/pull\\/(\\d+)/)?.[1] ?? '?';\n return {\n success: true,\n statusCode: 200,\n message: `Successfully merged PR #${remotePrNumber} for ${issueId}`,\n mergeStatus: 'merged',\n prUrl: prResult.prUrl,\n remote: true,\n };\n } catch (remoteErr: any) {\n const mergeErrorMessage = `Remote merge failed: ${remoteErr.message}`;\n console.error(`[merge] Remote merge failed for ${issueId}:`, remoteErr);\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: mergeErrorMessage });\n completePendingOperation(issueId, remoteErr.message);\n return {\n success: false,\n statusCode: 500,\n error: mergeErrorMessage,\n };\n }\n }\n\n if (!existsSync(workspacePath)) {\n completePendingOperation(issueId, 'Workspace does not exist');\n return { success: false, statusCode: 400, error: 'Workspace does not exist' };\n }\n\n const projectConfig = findProjectByTeamSync(issuePrefix);\n const isPolyrepo = projectConfig?.workspace?.type === 'polyrepo';\n\n if (isPolyrepo && projectConfig?.workspace?.repos) {\n console.log(`[merge] Polyrepo detected for ${issueId}, coordinating merge set...`);\n const { getMergeSetSync, ensureMergeSetForIssueSync, upsertMergeSetSync, withRepoStateSync } = await import('../../../../lib/merge-set.js');\n const { runQualityGates } = await import('../../../../lib/cloister/validation.js');\n const { getForgeAdapter } = await import('../../../../lib/forge.js');\n const { messageAgent } = await import('../../../../lib/agents.js');\n let mergeSet = getMergeSetSync(issueId) || ensureMergeSetForIssueSync(issueId);\n if (!mergeSet) {\n const error = `No merge set found for ${issueId}`;\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: error });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 400, error };\n }\n\n const activeRepos = mergeSet.repos\n .filter(repo => repo.mergeStatus !== 'skipped' && !!repo.artifactUrl)\n .sort((a, b) => a.mergeOrder - b.mergeOrder);\n\n if (activeRepos.length === 0) {\n const error = `No changed repos are marked ready for coordinated merge in ${issueId}`;\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: error });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 400, error };\n }\n\n const agentId = `agent-${issueId.toLowerCase()}`;\n\n mergeSet = {\n ...mergeSet,\n status: 'merging',\n updatedAt: new Date().toISOString(),\n };\n upsertMergeSetSync(mergeSet);\n\n const mergeResults: Array<{\n repo: string;\n success: boolean;\n message: string;\n testsStatus?: string;\n }> = [];\n const repoHeadsBefore = new Map<string, string>();\n // PAN-2461: repos whose source branch already contains the target branch need no\n // rebase round-trip — a retry after the work agent already rebased+pushed must not\n // wait 30 minutes for a push that will never come (mirrors the monorepo\n // \"already contains origin/main\" skip).\n const alreadyRebased = new Set<string>();\n\n for (const repo of activeRepos) {\n const repoWorkspacePath = join(workspacePath, repo.repoKey);\n if (!existsSync(repoWorkspacePath) || !existsSync(join(repoWorkspacePath, '.git'))) {\n const error = `Workspace repo ${repo.repoKey} is missing`;\n mergeResults.push({ repo: repo.repoKey, success: false, message: error });\n continue;\n }\n\n const { stdout: headBefore } = await execAsync(\n `git rev-parse origin/${repo.sourceBranch} 2>/dev/null || echo NONE`,\n { cwd: repoWorkspacePath, encoding: 'utf-8', timeout: 10000 }\n );\n repoHeadsBefore.set(repo.repoKey, headBefore.trim());\n try {\n await execAsync(`git fetch origin ${repo.targetBranch} ${repo.sourceBranch}`, {\n cwd: repoWorkspacePath, encoding: 'utf-8', timeout: 30000,\n });\n await execAsync(\n `git merge-base --is-ancestor origin/${repo.targetBranch} origin/${repo.sourceBranch}`,\n { cwd: repoWorkspacePath, encoding: 'utf-8', timeout: 10000 }\n );\n alreadyRebased.add(repo.repoKey);\n mergeSet = withRepoStateSync(mergeSet, repo.repoKey, { rebaseStatus: 'passed' });\n console.log(`[merge] ${repo.repoKey} feature branch already contains origin/${repo.targetBranch} — skipping rebase request for ${issueId}`);\n continue;\n } catch {\n // Not an ancestor (or fetch failed) — a rebase is genuinely required.\n }\n mergeSet = withRepoStateSync(mergeSet, repo.repoKey, { rebaseStatus: 'requested' });\n }\n upsertMergeSetSync(mergeSet);\n\n if (mergeResults.some(result => !result.success)) {\n const failedDetails = mergeResults.filter(r => !r.success).map(r => `${r.repo}: ${r.message}`).join('; ');\n const error = `Polyrepo merge prerequisites failed for ${issueId}: ${failedDetails}`;\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: error });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 400, error, repos: mergeResults };\n }\n\n const reposNeedingRebase = activeRepos.filter(repo => !alreadyRebased.has(repo.repoKey));\n\n if (reposNeedingRebase.length > 0 && !await Effect.runPromise(sessionExists(agentId))) {\n const error = `Work agent ${agentId} is not running. Polyrepo merge requires the work agent to rebase every affected repo and push.`;\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: error });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 400, error };\n }\n\n if (reposNeedingRebase.length > 0) {\n const rebaseInstructions = reposNeedingRebase.map((repo, index) => (\n `${index + 1}. cd ${repo.repoKey}\\n git fetch origin ${repo.targetBranch}\\n git rebase origin/${repo.targetBranch}\\n git push --force-with-lease`\n )).join('\\n');\n const rebaseMsg = `MERGE REQUESTED: The human has clicked MERGE for ${issueId}. Rebase and push every affected repo in this merge set:\\n\\n${rebaseInstructions}\\n\\nResolve any conflicts in the workspaces above, complete every rebase, and push all affected branches. Do NOT merge PRs/MRs yourself.`;\n await messageAgent(agentId, rebaseMsg);\n }\n\n const REBASE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes — complex polyrepo rebases need time for conflict resolution\n const POLL_INTERVAL_MS = 5000;\n const pushedRepos = new Set<string>(alreadyRebased);\n const rebaseStart = Date.now();\n\n while (Date.now() - rebaseStart < REBASE_TIMEOUT_MS && pushedRepos.size < activeRepos.length) {\n await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL_MS));\n\n for (const repo of activeRepos) {\n if (pushedRepos.has(repo.repoKey)) continue;\n\n const repoWorkspacePath = join(workspacePath, repo.repoKey);\n try {\n await execAsync('git fetch origin', { cwd: repoWorkspacePath, encoding: 'utf-8', timeout: 15000 });\n const { stdout: headNow } = await execAsync(\n `git rev-parse origin/${repo.sourceBranch}`,\n { cwd: repoWorkspacePath, encoding: 'utf-8', timeout: 5000 }\n );\n if (headNow.trim() !== repoHeadsBefore.get(repo.repoKey)) {\n pushedRepos.add(repo.repoKey);\n mergeSet = withRepoStateSync(mergeSet, repo.repoKey, { rebaseStatus: 'passed' });\n upsertMergeSetSync(mergeSet);\n }\n } catch {\n // Retry until timeout or agent exit.\n }\n }\n\n if (!await Effect.runPromise(sessionExists(agentId))) break;\n }\n\n if (pushedRepos.size !== activeRepos.length) {\n const remaining = activeRepos\n .filter(repo => !pushedRepos.has(repo.repoKey))\n .map(repo => repo.repoKey);\n const agentRunning = await Effect.runPromise(sessionExists(agentId));\n const error = !agentRunning\n ? `Work agent ${agentId} stopped before completing polyrepo rebases for ${remaining.join(', ')}`\n : `Work agent did not push rebased branches for ${remaining.join(', ')} within ${REBASE_TIMEOUT_MS / 60000} minutes`;\n for (const repoKey of remaining) {\n mergeSet = withRepoStateSync(mergeSet, repoKey, { rebaseStatus: 'failed' });\n }\n upsertMergeSetSync(mergeSet);\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: error });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 500, error };\n }\n\n setReviewStatus(issueId, { mergeStatus: 'verifying', mergeNotes: undefined });\n // PAN-2461: gates must run with rendered placeholders and the workspace ROOT.\n // The previous call passed the repo subdir (doubling gate.path → <ws>/fe/fe)\n // and no placeholders (docker exec hit the literal \"myn-{{FEATURE_FOLDER}}-fe-1\").\n const featureFolder = basename(workspacePath);\n const gatePlaceholders = {\n FEATURE_NAME: featureFolder.replace(/^feature-/, ''),\n FEATURE_FOLDER: featureFolder,\n BRANCH_NAME: `feature/${featureFolder.replace(/^feature-/, '')}`,\n COMPOSE_PROJECT: `${basename(projectPath)}-${featureFolder}`,\n DOMAIN: projectConfig?.workspace?.dns?.domain || 'localhost',\n PROJECT_NAME: basename(projectPath),\n PROJECT_PATH: projectPath,\n PROJECTS_DIR: dirname(projectPath),\n WORKSPACE_PATH: workspacePath,\n };\n\n for (const repo of activeRepos) {\n const repoConfig = projectConfig.workspace.repos.find(configRepo => configRepo.name === repo.repoKey);\n const gateIdentifiers = new Set<string>([\n repo.repoKey,\n repoConfig?.path || '',\n ].filter(Boolean));\n const gates = Object.fromEntries(\n Object.entries(projectConfig.quality_gates || {}).filter(\n ([, gate]) => gate.path && gateIdentifiers.has(gate.path)\n )\n );\n\n mergeSet = withRepoStateSync(mergeSet, repo.repoKey, { verificationStatus: 'running' });\n upsertMergeSetSync(mergeSet);\n\n if (Object.keys(gates).length === 0) {\n mergeSet = withRepoStateSync(mergeSet, repo.repoKey, { verificationStatus: 'skipped' });\n upsertMergeSetSync(mergeSet);\n continue;\n }\n\n const gateResults = await Effect.runPromise(runQualityGates(gates, workspacePath, 'pre_push', {\n placeholders: { ...gatePlaceholders, CHANGED_BASE: `origin/${repo.targetBranch}` },\n }));\n const failedGate = gateResults.find(result => !result.passed && result.required !== false);\n if (failedGate) {\n const error = `Polyrepo post-rebase verification failed for ${repo.repoKey} at ${failedGate.name}`;\n mergeSet = withRepoStateSync(mergeSet, repo.repoKey, { verificationStatus: 'failed' });\n upsertMergeSetSync(mergeSet);\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: error });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 500, error };\n }\n\n mergeSet = withRepoStateSync(mergeSet, repo.repoKey, { verificationStatus: 'passed' });\n upsertMergeSetSync(mergeSet);\n }\n\n setReviewStatus(issueId, { mergeStatus: 'merging' });\n for (const repo of activeRepos) {\n const repoWorkspacePath = join(workspacePath, repo.repoKey);\n try {\n mergeSet = withRepoStateSync(mergeSet, repo.repoKey, { mergeStatus: 'merging' });\n upsertMergeSetSync(mergeSet);\n await getForgeAdapter(repo.forge).mergeReviewArtifact({\n forge: repo.forge,\n url: repo.artifactUrl,\n id: repo.artifactId,\n cwd: repoWorkspacePath,\n method: 'squash',\n });\n mergeSet = withRepoStateSync(mergeSet, repo.repoKey, { mergeStatus: 'merged' });\n upsertMergeSetSync(mergeSet);\n mergeResults.push({\n repo: repo.repoKey,\n success: true,\n message: `Merged via ${repo.forge}`,\n });\n } catch (mergeErr: any) {\n const error = mergeErr.message || 'Artifact merge failed';\n mergeSet = withRepoStateSync(mergeSet, repo.repoKey, { mergeStatus: 'failed' });\n upsertMergeSetSync(mergeSet);\n mergeResults.push({ repo: repo.repoKey, success: false, message: error });\n break;\n }\n }\n\n const failedRepos = mergeResults.filter(r => !r.success);\n\n if (failedRepos.length > 0) {\n const error = `Polyrepo merge failed for: ${failedRepos\n .map(r => `${r.repo} (${r.message})`)\n .join(', ')}`;\n mergeSet = {\n ...mergeSet,\n status: 'failed',\n updatedAt: new Date().toISOString(),\n };\n upsertMergeSetSync(mergeSet);\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: error });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 500, error, repos: mergeResults };\n }\n\n mergeSet = {\n ...mergeSet,\n status: 'merged',\n updatedAt: new Date().toISOString(),\n };\n upsertMergeSetSync(mergeSet);\n setReviewStatus(issueId, { mergeStatus: 'merged', mergeNotes: undefined, readyForMerge: false });\n completePendingOperation(issueId, null);\n\n const { postMergeLifecycle } = await import('../../../../lib/cloister/merge-agent.js');\n advanceQueue();\n await postMergeLifecycle(issueId, projectPath);\n\n return {\n success: true,\n statusCode: 200,\n message: `Polyrepo merge complete for ${issueId}`,\n mergeStatus: 'merged',\n repos: mergeResults,\n };\n }\n\n // Monorepo / single-repo merge: PR-based flow\n const { getMergeSetSync, ensureMergeSetForIssueSync } = await import('../../../../lib/merge-set.js');\n const { getForgeAdapter } = await import('../../../../lib/forge.js');\n const monorepoMergeSet = getMergeSetSync(issueId) || ensureMergeSetForIssueSync(issueId);\n const primaryRepo = monorepoMergeSet?.repos[0];\n const targetBranch = primaryRepo?.targetBranch || 'main';\n const primaryForge = primaryRepo?.forge || 'github';\n\n // Step 1: Ensure PR exists (creates if needed)\n const prResult = await ensurePRExists(issueId, { cwd: workspacePath, branchName, targetBranch });\n if (!prResult.prUrl) {\n const error = `Failed to create PR: ${prResult.error || 'Unknown error'}`;\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: error });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 400, error };\n }\n\n const artifactUrl = primaryRepo?.artifactUrl || prResult.prUrl;\n const artifactId = primaryRepo?.artifactId;\n const githubPrRef = primaryForge === 'github' ? parseGitHubPullRequestUrl(artifactUrl) : null;\n const prNumber = githubPrRef ? String(githubPrRef.number) : undefined;\n if (primaryForge === 'github' && !prNumber) {\n const error = `Could not parse PR number from URL: ${artifactUrl}`;\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: error });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 400, error };\n }\n\n // Step 1b: Validate that the PR is still OPEN before rebasing/merging.\n // A cancel-flow or manual `gh pr close` can leave stale `prUrl` pointing at a\n // CLOSED PR while Overdeck state still shows readyForMerge=true. Without this\n // check, the rebase + merge pipeline runs against a dead PR and dies silently\n // inside `gh pr merge` (see PAN-509 cancel-flow divergence).\n if (githubPrRef) {\n try {\n const { getPullRequestState, isGitHubAppConfigured } = await import('../../../../lib/github-app.js');\n if (isGitHubAppConfigured()) {\n const prState = await Effect.runPromise(getPullRequestState(githubPrRef.owner, githubPrRef.repo, githubPrRef.number));\n if (prState.state !== 'OPEN' && !prState.merged) {\n const error = `PR #${githubPrRef.number} is ${prState.state} (not OPEN). Overdeck state is out of sync — likely a cancel-flow left a stale prUrl. Re-open the work agent to create a fresh PR, or reset review state.`;\n console.error(`[merge] ${error}`);\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: error });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 409, error };\n }\n // Defense-in-depth: refuse to merge when required CI checks are failing on the\n // PR's current HEAD. Without this gate, we attempt a rebase and `gh pr merge`\n // against a branch whose CI is red; branch protection blocks the merge and we\n // get a generic error. Surface the real blocker (failing CI) up-front so the\n // work-agent can fix it instead of us churning the queue. See PAN-611/PAN-544\n // (Run 7): feature branches had gitignored source + stale bun.lock; local\n // verification passed but CI failed — the divergence was invisible until merge.\n if (prState.checksFailed && !prState.merged) {\n const error = `GitHub PR #${githubPrRef.number} has failing required checks on HEAD ${prState.headSha.slice(0, 8)}. Fix CI before merging — see ${prState.url || 'the PR page'} for details.`;\n console.error(`[merge] ${error}`);\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: error });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 409, error };\n }\n // Defense-in-depth: refuse to merge when the PR is CONFLICTING with its base.\n // Without this, a clean-CI-but-conflicting PR sails into the rebase/retry loop in\n // forge.ts and churns for minutes before a generic timeout. The mergeable/\n // mergeableState fields are already on prState from getPullRequestState. Writing a\n // merge_conflict blocker back into review status drops the row out of the\n // \"Awaiting Merge\" queue and into \"Blocked from Merge\" with a clear reason even if\n // the GitHub webhook that normally populates blockerReasons was delayed or dropped\n // (the reactive-only gap that let PAN-1574 show a live MERGE button). PAN-1619-followup.\n if ((prState.mergeable === false || prState.mergeableState === 'dirty') && !prState.merged) {\n const error = `GitHub PR #${githubPrRef.number} is CONFLICTING with ${prState.baseBranch}. Resolve conflicts before merging — see ${prState.url || 'the PR page'}.`;\n console.error(`[merge] ${error}`);\n setReviewStatus(issueId, {\n mergeStatus: 'failed',\n readyForMerge: false,\n mergeNotes: error,\n blockerReasons: [{\n type: 'merge_conflict',\n summary: `Conflicts with ${prState.baseBranch}`,\n detectedAt: new Date().toISOString(),\n }],\n });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 409, error };\n }\n if (prState.merged) {\n console.log(`[merge] PR #${githubPrRef.number} for ${issueId} is already merged — running post-merge lifecycle`);\n setReviewStatus(issueId, { mergeStatus: 'merged', mergeNotes: undefined, readyForMerge: false });\n completePendingOperation(issueId, null);\n const { postMergeLifecycle } = await import('../../../../lib/cloister/merge-agent.js');\n await postMergeLifecycle(issueId, projectPath, branchName);\n return {\n success: true,\n statusCode: 200,\n message: `PR #${githubPrRef.number} for ${issueId} was already merged`,\n mergeStatus: 'merged',\n prUrl: prResult.prUrl,\n };\n }\n }\n } catch (prStateErr: any) {\n console.warn(`[merge] Pre-merge PR state check failed for ${issueId}: ${prStateErr.message} — proceeding (check is best-effort)`);\n }\n }\n\n // Step 2: Tell the WORK AGENT to rebase onto the target branch and push.\n // The server coordinates; the work agent owns all code-changing git operations.\n const { postMergeLifecycle } = await import(\n '../../../../lib/cloister/merge-agent.js'\n );\n const agentId = `agent-${issueId.toLowerCase()}`;\n const rebaseMsg = `MERGE REQUESTED: The human has clicked MERGE for ${issueId}. Please rebase onto ${targetBranch} and push:\\n\\n1. git fetch origin ${targetBranch}\\n2. git rebase origin/${targetBranch}\\n3. If conflicts: resolve them, git add, git rebase --continue\\n4. git push --force-with-lease\\n\\nAfter pushing, the server will handle verification and merge automatically. Do NOT run gh pr merge yourself.`;\n\n setReviewStatus(issueId, { mergeStep: 'rebasing' });\n console.log(`[merge] Rebasing ${branchName} onto ${targetBranch} for ${issueId} (agent=${await Effect.runPromise(sessionExists(agentId)) ? 'running' : 'stopped'})...`);\n {\n const { beginShipLog, appendShipLog } = await import('../../../../lib/cloister/ship-log.js');\n beginShipLog(issueId);\n appendShipLog(issueId, `Ship started: rebasing ${branchName} onto ${targetBranch}…`, 'rebasing');\n }\n\n let rebaseResult: { success: boolean; reason?: string; conflictFiles?: string[]; newHead?: string };\n\n // Pre-check: if origin/<branch> already contains origin/<target>, the branch\n // is already rebased — no rebase or push is needed.\n const { alreadyRebased, currentHead } = await isBranchAlreadyRebased(workspacePath, branchName, targetBranch);\n\n if (alreadyRebased && currentHead) {\n console.log(`[merge] ${branchName} already contains origin/${targetBranch} — skipping rebase request for ${issueId}`);\n rebaseResult = { success: true, newHead: currentHead };\n } else {\n try {\n const recovery = await ensureWorkAgentReadyForMerge(issueId, workspacePath, rebaseMsg);\n console.log(`[merge] ${recovery.detail}`);\n\n // Poll for the push: check if remote HEAD changed\n const { stdout: headBefore } = await execAsync(\n `git rev-parse origin/${branchName} 2>/dev/null || echo NONE`,\n { cwd: workspacePath, encoding: 'utf-8', timeout: 10000 }\n );\n\n const REBASE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes — complex rebases with conflicts need time\n const POLL_INTERVAL_MS = 5000;\n const startTime = Date.now();\n let newHead: string | null = null;\n\n while (Date.now() - startTime < REBASE_TIMEOUT_MS) {\n await new Promise(r => setTimeout(r, POLL_INTERVAL_MS));\n\n try {\n await execAsync('git fetch origin', { cwd: workspacePath, encoding: 'utf-8', timeout: 15000 });\n const { stdout: headNow } = await execAsync(\n `git rev-parse origin/${branchName}`,\n { cwd: workspacePath, encoding: 'utf-8', timeout: 5000 }\n );\n if (headNow.trim() !== headBefore.trim()) {\n newHead = headNow.trim();\n console.log(`[merge] Work agent pushed rebased branch for ${issueId} (new HEAD: ${newHead.slice(0, 8)})`);\n break;\n }\n } catch { /* fetch failed, retry */ }\n\n if (!await Effect.runPromise(sessionExists(agentId))) {\n console.log(`[merge] Work agent ${agentId} stopped during rebase`);\n break;\n }\n }\n\n if (newHead) {\n rebaseResult = { success: true, newHead };\n } else if (!await Effect.runPromise(sessionExists(agentId))) {\n rebaseResult = {\n success: false,\n reason: `Work agent ${agentId} stopped before completing the rebase onto ${targetBranch}`,\n };\n } else {\n rebaseResult = { success: false, reason: `Work agent did not push the rebased branch within ${REBASE_TIMEOUT_MS / 60000} minutes` };\n }\n } catch (recoveryErr: any) {\n rebaseResult = {\n success: false,\n reason: recoveryErr.message || `Work agent ${agentId} could not be prepared for merge`,\n };\n }\n }\n\n if (!rebaseResult.success) {\n const error = rebaseResult.reason || 'Rebase failed';\n setReviewStatus(issueId, { mergeStatus: 'failed', mergeNotes: error, readyForMerge: false });\n completePendingOperation(issueId, error);\n\n // Post PR comment about failure\n try {\n if (artifactUrl) {\n const body = rebaseResult.conflictFiles?.length\n ? `## Merge Failed — Rebase Conflicts\\n\\nConflicts in: ${rebaseResult.conflictFiles.join(', ')}\\n\\nThe work agent has been notified to resolve conflicts.`\n : `## Merge Failed\\n\\n${error}`;\n await getForgeAdapter(primaryForge).commentOnArtifact({\n forge: primaryForge,\n url: artifactUrl,\n id: artifactId,\n cwd: workspacePath,\n body,\n });\n }\n } catch { /* non-fatal */ }\n\n return { success: false, statusCode: 500, error };\n }\n\n setReviewStatus(issueId, { mergeStep: 'stripping-planning' });\n // Strip .planning/ artifacts before merge — these are workspace-local\n // scratch files (STATE.md, feedback/) that must never land on main (#888).\n try {\n const { stdout: hasPlanning } = await execAsync(\n 'git ls-files -- .planning/ 2>/dev/null || true',\n { cwd: workspacePath, encoding: 'utf-8', timeout: 10000 }\n );\n if (hasPlanning.trim()) {\n console.log(`[merge] Stripping .planning/ artifacts from ${branchName} before merge...`);\n await execAsync('git rm -r --cached .planning/', { cwd: workspacePath, encoding: 'utf-8', timeout: 10000 });\n await execAsync(\n `git commit -m \"chore: strip .planning/ before merge\"`,\n { cwd: workspacePath, encoding: 'utf-8', timeout: 10000 }\n );\n await execAsync(\n `git push --force-with-lease origin HEAD:${branchName}`,\n { cwd: workspacePath, encoding: 'utf-8', timeout: 30000 }\n );\n console.log(`[merge] Stripped .planning/ from ${branchName}`);\n }\n } catch (stripErr: any) {\n console.warn(`[merge] Failed to strip .planning/ from ${branchName}: ${stripErr.message}`);\n // Non-fatal: proceed to verification. The no-planning-on-main guardrail\n // will catch any .planning/ files that slip through.\n }\n\n // Step 3: Post-rebase verification gate (typecheck, lint, test)\n // Ensures the rebase didn't introduce issues before merging.\n setReviewStatus(issueId, { mergeStatus: 'verifying', mergeStep: 'verifying', mergeNotes: undefined });\n\n // PAN-2487: when the tip SHA being merged already has GREEN CI on GitHub,\n // the local gate suite re-proves what CI already proved on the identical\n // tree — 10-20 min of redundant wall-clock per merge. Skip local gates in\n // that case; any rebase above produced a NEW SHA, whose CI can't be green\n // yet, so post-rebase combinations still verify locally.\n let skipLocalVerification = false;\n if (primaryForge === 'github' && artifactUrl) {\n try {\n const { parsePullRequestRef, getCiCheckRunsStatePromise, isGitHubAppConfigured } = await import('../../../../lib/github-app.js');\n if (isGitHubAppConfigured()) {\n const ref = parsePullRequestRef({ url: artifactUrl });\n if (ref) {\n const { stdout: tipShaRaw } = await execAsync('git rev-parse HEAD', { cwd: workspacePath, encoding: 'utf-8', timeout: 10000 });\n const tipSha = tipShaRaw.trim();\n const ci = await getCiCheckRunsStatePromise(ref.owner, ref.repo, tipSha);\n if (ci.green && ci.total > 0) {\n skipLocalVerification = true;\n console.log(`[merge] CI is green on ${tipSha.slice(0, 8)} (${ci.successCount}/${ci.total} checks) — skipping redundant local verification for ${issueId} (PAN-2487)`);\n setReviewStatus(issueId, { mergeNotes: `Local verification skipped: CI green on ${tipSha.slice(0, 8)} (${ci.successCount}/${ci.total} checks)` });\n } else {\n console.log(`[merge] CI on tip not green (verdict=${ci.verdict}, total=${ci.total}) — running local verification for ${issueId}`);\n }\n }\n }\n } catch (ciErr: any) {\n console.warn(`[merge] CI-state check failed (${ciErr.message?.slice(0, 120)}) — falling back to local verification for ${issueId}`);\n }\n }\n\n const { appendShipLog } = await import('../../../../lib/cloister/ship-log.js');\n if (skipLocalVerification) {\n appendShipLog(issueId, '✓ Local verification skipped — CI already green on this exact commit', 'verifying');\n }\n const verifyResult = skipLocalVerification\n ? { outcome: 'passed' as const }\n : await (async () => {\n console.log(`[merge] Running post-rebase verification for ${issueId}...`);\n appendShipLog(issueId, 'Running post-rebase verification (full quality-gate suite)…', 'verifying');\n const { runVerificationForIssue } = await import(\n '../../../../lib/cloister/verification-runner.js'\n );\n return Effect.runPromise(runVerificationForIssue(\n issueId,\n workspacePath,\n { isRemote: false },\n 'merge-verify',\n { syncTargetBranch: false, onGateLog: (line) => appendShipLog(issueId, line, 'verifying') },\n ));\n })();\n\n if (verifyResult.outcome === 'failed') {\n const error = `Post-rebase verification failed at ${verifyResult.failedCheck}`;\n console.log(`[merge] ${error}`);\n setReviewStatus(issueId, { mergeStatus: 'failed', mergeNotes: error, readyForMerge: false });\n completePendingOperation(issueId, error);\n\n // Post comment on PR so failure is visible\n try {\n if (artifactUrl) {\n await getForgeAdapter(primaryForge).commentOnArtifact({\n forge: primaryForge,\n url: artifactUrl,\n id: artifactId,\n cwd: workspacePath,\n body: `## Merge Blocked — Post-Rebase Verification Failed\\n\\nFailed check: ${verifyResult.failedCheck}\\n\\nThe branch was rebased successfully but verification failed. The work agent needs to fix the errors and resubmit.`,\n });\n }\n } catch { /* non-fatal */ }\n\n return { success: false, statusCode: 500, error };\n }\n console.log(`[merge] Post-rebase verification ${verifyResult.outcome} for ${issueId}`);\n\n // Step 4a: Report commit statuses on post-rebase HEAD (branch protection requires them).\n // Must happen AFTER rebase because rebase changes the HEAD SHA.\n setReviewStatus(issueId, { mergeStep: 'reporting-statuses' });\n try {\n const { getPullRequestState, isGitHubAppConfigured, reportCommitStatus } = await import('../../../../lib/github-app.js');\n if (githubPrRef && isGitHubAppConfigured()) {\n const prState = await Effect.runPromise(getPullRequestState(githubPrRef.owner, githubPrRef.repo, githubPrRef.number));\n const sha = prState.headSha.trim();\n if (sha) {\n await reportCommitStatus(githubPrRef.owner, githubPrRef.repo, sha, 'success', 'overdeck/review', 'Review passed');\n await reportCommitStatus(githubPrRef.owner, githubPrRef.repo, sha, 'success', 'overdeck/test', 'Tests passed');\n console.log(`[merge] Reported commit statuses on post-rebase HEAD for ${issueId} (${sha.slice(0, 8)})`);\n }\n }\n } catch (statusErr: any) {\n console.warn(`[merge] Failed to report commit statuses: ${statusErr.message}`);\n }\n\n // Step 4b: Merge the review artifact via the configured forge.\n setReviewStatus(issueId, { mergeStep: 'squash-merging' });\n appendShipLog(issueId, `Verification passed — squash-merging the ${primaryForge} PR…`, 'squash-merging');\n let artifactMerged = false;\n try {\n console.log(`[merge] Merging ${primaryForge} review artifact for ${issueId}...`);\n await getForgeAdapter(primaryForge).mergeReviewArtifact({\n forge: primaryForge,\n url: artifactUrl,\n id: artifactId,\n cwd: workspacePath,\n method: 'squash',\n });\n artifactMerged = true;\n } catch (prMergeErr: any) {\n console.error(`[merge] Review artifact merge threw for ${issueId}:`, prMergeErr);\n try {\n const { getPullRequestState, isGitHubAppConfigured } = await import('../../../../lib/github-app.js');\n if (githubPrRef && isGitHubAppConfigured()) {\n const prState = await Effect.runPromise(getPullRequestState(githubPrRef.owner, githubPrRef.repo, githubPrRef.number));\n artifactMerged = prState.merged;\n if (artifactMerged) {\n console.log(`[merge] Race-detected: PR #${githubPrRef.number} for ${issueId} was already merged despite thrown error; proceeding`);\n }\n }\n } catch (stateCheckErr: any) {\n console.warn(`[merge] Post-error PR state check failed for ${issueId}: ${stateCheckErr.message}`);\n }\n\n if (!artifactMerged) {\n const error = `${primaryForge} merge failed: ${prMergeErr.message}`;\n console.error(`[merge] ${error}`);\n\n if (isIntegrationPermissionError(prMergeErr.message)) {\n let scopes = 'pull_requests:write, contents:write';\n try {\n const cap = await verifyAppCanMerge();\n if (cap.missing && cap.missing.length > 0) {\n scopes = cap.missing.join(', ');\n }\n } catch (permCheckErr: any) {\n console.warn(`[merge] verifyAppCanMerge failed for ${issueId}: ${permCheckErr.message}`);\n }\n const note = `GitHub App cannot merge — installation is missing ${scopes}. Grant these scopes to the overdeck-agent installation, then re-merge.`;\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: note });\n completePendingOperation(issueId, note);\n return { success: false, statusCode: 403, error: note };\n }\n\n const isTransient =\n prMergeErr.message?.includes('Timed out waiting for GitHub PR') ||\n prMergeErr.message?.includes('ECONNRESET') ||\n prMergeErr.message?.includes('ETIMEDOUT') ||\n prMergeErr.message?.includes('ECONNREFUSED');\n if (isTransient) {\n const reconciled = await reconcileGitHubMergeStatus(issueId, getReviewStatusSync(issueId));\n if (reconciled) {\n artifactMerged = true;\n console.log(`[merge] Reconciliation confirmed PR merged for ${issueId} after transient error; proceeding to success path`);\n } else {\n setReviewStatus(issueId, { mergeStatus: 'verifying', mergeNotes: error });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 500, error };\n }\n // readyForMerge stays true while reconciliation catches up or the operator retries.\n } else {\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: error });\n completePendingOperation(issueId, error);\n return { success: false, statusCode: 500, error };\n }\n }\n }\n\n // Step 5: Mark merged and dequeue next BEFORE post-merge lifecycle.\n // postMergeLifecycle spawns a deploy script that may kill this server process,\n // so queue processing must happen before that point.\n appendShipLog(issueId, `✓ MERGED — running post-merge cleanup (labels, docker teardown, verify-on-main)`, 'post-merge-cleanup');\n setReviewStatus(issueId, { mergeStatus: 'merged', mergeStep: 'post-merge-cleanup', mergeNotes: undefined, readyForMerge: false });\n completePendingOperation(issueId, null);\n\n // Dequeue next merge before lifecycle (which may kill the process)\n advanceQueue();\n\n // Post-merge lifecycle runs last — may spawn deploy script that kills this server\n await postMergeLifecycle(issueId, projectPath, branchName);\n\n return {\n success: true,\n statusCode: 200,\n message: `Successfully merged ${primaryForge} review artifact for ${issueId}`,\n mergeStatus: 'merged',\n prUrl: prResult.prUrl,\n };\n } catch (error: any) {\n const mergeErrorMessage = `Merge pipeline error: ${error.message}`;\n console.error(`[merge] Error for ${issueId}:`, error);\n setReviewStatus(issueId, { mergeStatus: 'failed', readyForMerge: false, mergeNotes: mergeErrorMessage });\n completePendingOperation(issueId, error.message);\n return { success: false, statusCode: 500, error: error.message };\n } finally {\n advanceQueue();\n }\n\n}\n\nsetMergeQueueTriggerHandler(triggerMerge);\n\n// ─── Route: POST /api/issues/:issueId/merge ───────────────────────────────\n\nconst postWorkspaceMergeRoute = HttpRouter.add(\n 'POST',\n '/api/issues/:issueId/merge',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n if (!/^[A-Z]+-\\d+$/i.test(issueId)) {\n return jsonResponse({ error: 'Invalid issue ID format' }, { status: 400 });\n }\n const eventStore = yield* EventStoreService;\n\n const result = yield* Effect.promise(() => triggerMerge(issueId));\n if (result.success) {\n yield* Effect.promise(() => Effect.runPromise(eventStore.append({\n type: 'merge.ready',\n timestamp: new Date().toISOString(),\n payload: { issueId },\n })));\n }\n const { statusCode, ...body } = result;\n return jsonResponse(body, { status: statusCode });\n }))\n);\n\n// ─── Route: POST /api/issues/:issueId/forge-approve ──────────────────────\n// Approves the PR/MR on GitHub/GitLab (submits an approving review).\n// This is distinct from the Overdeck /approve endpoint which runs the\n// full merge flow. This just clicks \"Approve\" on the forge.\n\nconst postForgeApproveRoute = HttpRouter.add(\n 'POST',\n '/api/issues/:issueId/forge-approve',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n if (!/^[A-Z]+-\\d+$/i.test(issueId)) {\n return jsonResponse({ error: 'Invalid issue ID format' }, { status: 400 });\n }\n\n return yield* Effect.promise(async () => {\n const { getMergeSetSync, upsertMergeSetSync, withRepoArtifactUrlSync, withRepoStateSync } = await import('../../../../lib/merge-set.js');\n const { getForgeAdapter } = await import('../../../../lib/forge.js');\n\n let mergeSet = getMergeSetSync(issueId);\n if (!mergeSet) {\n return jsonResponse({ error: `No merge set found for ${issueId}` }, { status: 404 });\n }\n\n const results: Array<{ repoKey: string; approved: boolean; error?: string }> = [];\n for (const repo of mergeSet.repos) {\n if (repo.mergeStatus === 'merged' || repo.mergeStatus === 'skipped') {\n results.push({ repoKey: repo.repoKey, approved: true });\n continue;\n }\n\n const adapter = getForgeAdapter(repo.forge);\n const workspacePath = mergeSet.workspaceType === 'polyrepo'\n ? join(mergeSet.projectPath, 'workspaces', `feature-${issueId.toLowerCase()}`, repo.repoKey)\n : join(mergeSet.projectPath, 'workspaces', `feature-${issueId.toLowerCase()}`);\n\n let artifactUrl = repo.artifactUrl;\n let artifactId = repo.artifactId;\n\n if (!artifactUrl && !artifactId) {\n try {\n const discovered = await adapter.discoverArtifact({\n sourceBranch: repo.sourceBranch,\n cwd: existsSync(workspacePath) ? workspacePath : repo.repoPath,\n });\n if (discovered?.url || discovered?.id) {\n artifactUrl = discovered.url;\n artifactId = discovered.id;\n mergeSet = withRepoArtifactUrlSync(mergeSet, repo.repoKey, artifactUrl ?? '', artifactId);\n upsertMergeSetSync(mergeSet);\n console.log(`[forge-approve] Discovered artifact for ${issueId}/${repo.repoKey}: ${artifactUrl}`);\n } else {\n results.push({ repoKey: repo.repoKey, approved: true });\n continue;\n }\n } catch {\n results.push({ repoKey: repo.repoKey, approved: true });\n continue;\n }\n }\n\n try {\n await adapter.approveReviewArtifact({\n forge: repo.forge,\n url: artifactUrl,\n id: artifactId,\n cwd: existsSync(workspacePath) ? workspacePath : repo.repoPath,\n });\n results.push({ repoKey: repo.repoKey, approved: true });\n } catch (err: any) {\n results.push({ repoKey: repo.repoKey, approved: false, error: err.message });\n }\n }\n\n const approvedCount = results.filter(r => r.approved).length;\n if (approvedCount > 0) {\n const { emitActivityEntrySync, emitActivityTtsSync } = await import('../../../../lib/activity-logger.js');\n emitActivityEntrySync({\n source: 'dashboard',\n level: 'success',\n message: `Merge approved for ${issueId}`,\n issueId,\n });\n emitActivityTtsSync({\n utterance: `Merge approved for ${issueId}`,\n priority: 1,\n issueId,\n source: 'dashboard',\n eventType: 'merge.approved',\n });\n }\n\n const allApproved = results.every(r => r.approved);\n return jsonResponse(\n { success: allApproved, results },\n { status: allApproved ? 200 : 207 }\n );\n });\n }))\n);\n\n// ─── Route: POST /api/issues/:issueId/forge-merge ────────────────────────\n// Merges the PR/MR directly on GitHub/GitLab via the forge adapter.\n// This is a lightweight forge-level merge — it does NOT run Overdeck's\n// full post-merge lifecycle (label cleanup, workspace teardown, etc.).\n\nconst postForgeMergeRoute = HttpRouter.add(\n 'POST',\n '/api/issues/:issueId/forge-merge',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n if (!/^[A-Z]+-\\d+$/i.test(issueId)) {\n return jsonResponse({ error: 'Invalid issue ID format' }, { status: 400 });\n }\n\n return yield* Effect.promise(async () => {\n const { getMergeSetSync, upsertMergeSetSync, withRepoArtifactUrlSync, withRepoStateSync } = await import('../../../../lib/merge-set.js');\n const { getForgeAdapter } = await import('../../../../lib/forge.js');\n\n let mergeSet = getMergeSetSync(issueId);\n if (!mergeSet) {\n return jsonResponse({ error: `No merge set found for ${issueId}` }, { status: 404 });\n }\n\n const results: Array<{ repoKey: string; merged: boolean; error?: string }> = [];\n for (const repo of mergeSet.repos) {\n if (repo.mergeStatus === 'merged' || repo.mergeStatus === 'skipped') {\n results.push({ repoKey: repo.repoKey, merged: true });\n continue;\n }\n\n const adapter = getForgeAdapter(repo.forge);\n const workspacePath = mergeSet.workspaceType === 'polyrepo'\n ? join(mergeSet.projectPath, 'workspaces', `feature-${issueId.toLowerCase()}`, repo.repoKey)\n : join(mergeSet.projectPath, 'workspaces', `feature-${issueId.toLowerCase()}`);\n\n let artifactUrl = repo.artifactUrl;\n let artifactId = repo.artifactId;\n\n if (!artifactUrl && !artifactId) {\n try {\n const discovered = await adapter.discoverArtifact({\n sourceBranch: repo.sourceBranch,\n cwd: existsSync(workspacePath) ? workspacePath : repo.repoPath,\n });\n if (discovered?.url || discovered?.id) {\n artifactUrl = discovered.url;\n artifactId = discovered.id;\n mergeSet = withRepoArtifactUrlSync(mergeSet, repo.repoKey, artifactUrl ?? '', artifactId);\n upsertMergeSetSync(mergeSet);\n console.log(`[forge-merge] Discovered artifact for ${issueId}/${repo.repoKey}: ${artifactUrl}`);\n } else {\n mergeSet = withRepoStateSync(mergeSet, repo.repoKey, { mergeStatus: 'skipped' });\n upsertMergeSetSync(mergeSet);\n results.push({ repoKey: repo.repoKey, merged: true });\n continue;\n }\n } catch {\n mergeSet = withRepoStateSync(mergeSet, repo.repoKey, { mergeStatus: 'skipped' });\n upsertMergeSetSync(mergeSet);\n results.push({ repoKey: repo.repoKey, merged: true });\n continue;\n }\n }\n\n try {\n await adapter.mergeReviewArtifact({\n forge: repo.forge,\n url: artifactUrl,\n id: artifactId,\n method: 'squash',\n cwd: existsSync(workspacePath) ? workspacePath : repo.repoPath,\n });\n mergeSet = withRepoStateSync(mergeSet, repo.repoKey, { mergeStatus: 'merged' });\n upsertMergeSetSync(mergeSet);\n results.push({ repoKey: repo.repoKey, merged: true });\n } catch (err: any) {\n results.push({ repoKey: repo.repoKey, merged: false, error: err.message });\n }\n }\n\n const mergedCount = results.filter(r => r.merged).length;\n if (mergedCount > 0) {\n const { emitActivityEntrySync, emitActivityTtsSync } = await import('../../../../lib/activity-logger.js');\n emitActivityEntrySync({\n source: 'dashboard',\n level: 'success',\n message: `Merged ${issueId} on ${mergeSet.repos[0]?.forge ?? 'forge'}`,\n issueId,\n });\n emitActivityTtsSync({\n utterance: `${issueId} has been merged`,\n priority: 1,\n issueId,\n source: 'dashboard',\n eventType: 'forgeMerge.merged',\n });\n }\n\n const allMerged = results.every(r => r.merged);\n return jsonResponse(\n { success: allMerged, results },\n { status: allMerged ? 200 : 207 }\n );\n });\n }))\n);\n\n// ─── Route: POST /api/issues/:issueId/approve ────────────────────────────\n\nconst postWorkspaceApproveRoute = HttpRouter.add(\n 'POST',\n '/api/issues/:issueId/approve',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n\n const existingStatus = getReviewStatusSync(issueId);\n if (\n existingStatus?.readyForMerge &&\n existingStatus.reviewStatus === 'passed' &&\n existingStatus.testStatus === 'passed'\n ) {\n console.log(\n `[approve] Review+test already passed for ${issueId}, forwarding to merge endpoint...`\n );\n const apiPort = process.env.API_PORT || process.env.PORT || '3011';\n try {\n const mergeRes = yield* Effect.promise(() => fetch(\n `http://localhost:${apiPort}/api/issues/${issueId}/merge`,\n { method: 'POST', headers: { 'Content-Type': 'application/json' } }\n ));\n const mergeData = (yield* Effect.promise(() => mergeRes.json())) as any;\n return jsonResponse(mergeData, { status: mergeRes.status });\n } catch (err: any) {\n return jsonResponse(\n { error: `Failed to forward to merge: ${err.message}` },\n { status: 500 }\n );\n }\n }\n\n return yield* Effect.promise(async () => {\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const issueLower = issueId.toLowerCase();\n const workspacePath = join(projectPath, 'workspaces', `feature-${issueLower}`);\n const branchName = `feature/${issueLower}`;\n\n setPendingOperation(issueId, 'approve');\n\n if (!existsSync(workspacePath)) {\n completePendingOperation(issueId, 'Workspace does not exist');\n return jsonResponse({ error: 'Workspace does not exist' }, { status: 400 });\n }\n\n try {\n await execAsync(`git rev-parse --verify ${branchName}`, {\n cwd: projectPath,\n encoding: 'utf-8',\n });\n } catch {\n completePendingOperation(issueId, `Branch ${branchName} does not exist`);\n return jsonResponse(\n { error: `Branch ${branchName} does not exist` },\n { status: 400 }\n );\n }\n\n try {\n const { stdout: status } = await execAsync(\n 'git status --porcelain -uno',\n { cwd: workspacePath, encoding: 'utf-8' }\n );\n // STATE-PLANE-COMMIT-POLICY rules 3/6: state-plane-only dirt is not agent work.\n if (shouldBlockApproveForDirtyStatus(status)) {\n const error = `Workspace has uncommitted changes. Please commit the changes, explicitly discard them, or surface them to the operator first:\\ncd ${workspacePath}\\ngit status`;\n completePendingOperation(issueId, error);\n return jsonResponse({ error }, { status: 400 });\n }\n } catch {}\n\n try {\n await execAsync(`git push origin ${branchName}`, {\n cwd: workspacePath,\n encoding: 'utf-8',\n });\n } catch (pushErr: any) {\n console.log(`Feature branch push note: ${pushErr.message}`);\n }\n\n // Concurrent-merge detection: warn if another push to main succeeded in the last 30s.\n // recentPushWarning is included in the success response body below (line ~4146) so\n // the caller can surface it to the operator without a separate lookup.\n const recentCutoff = new Date(Date.now() - 30_000).toISOString();\n const recentMainPushes = listGitOperationsSync({ operation: 'push', since: recentCutoff })\n .filter((op) => op.status === 'success' && op.branch === 'main' && op.issueId !== issueId);\n const recentPushWarning = recentMainPushes.length > 0\n ? `Another workspace pushed to main ${Math.round((Date.now() - new Date(recentMainPushes[0].ts).getTime()) / 1000)}s ago — divergence possible`\n : undefined;\n if (recentPushWarning) {\n console.warn(`[approve] ${recentPushWarning} (${issueId})`);\n }\n\n try {\n await execAsync('git checkout main', { cwd: projectPath, encoding: 'utf-8' });\n await execAsync('git fetch origin main', { cwd: projectPath, encoding: 'utf-8' });\n // Detect local-only main commits without silently hard-resetting.\n const { stdout: divergenceRaw } = await execAsync(\n 'git rev-list --left-right --count HEAD...origin/main',\n { cwd: projectPath, encoding: 'utf-8' }\n );\n const [aheadRaw = '0', behindRaw = '0'] = divergenceRaw.trim().split(/\\s+/);\n const aheadCount = parseInt(aheadRaw, 10) || 0;\n const behindCount = parseInt(behindRaw, 10) || 0;\n if (aheadCount > 0) {\n const error = buildLocalMainRecoveryError(projectPath, aheadCount, behindCount);\n completePendingOperation(issueId, error);\n return jsonResponse({ error }, { status: 409 });\n }\n await execAsync('git pull origin main --ff-only', {\n cwd: projectPath,\n encoding: 'utf-8',\n });\n } catch (checkoutErr: any) {\n const error = `Failed to checkout/update main branch: ${checkoutErr.message}`;\n completePendingOperation(issueId, error);\n return jsonResponse({ error }, { status: 400 });\n }\n\n // Divergence preview: count how many commits main has advanced past the feature branch\n let mainAdvancedBy = 0;\n try {\n const { stdout: aheadRaw } = await execAsync(\n `git rev-list ${branchName}..main --count`,\n { cwd: projectPath, encoding: 'utf-8' }\n );\n mainAdvancedBy = parseInt(aheadRaw.trim(), 10) || 0;\n if (mainAdvancedBy > 0) {\n console.log(`[approve] main has advanced ${mainAdvancedBy} commit(s) past ${branchName}`);\n }\n } catch {}\n\n console.log(`[approve] Starting role pipeline for ${issueId}...`);\n\n // PAN-1048 R3: route through the same wrapper every other approve path\n // uses (idempotency + feedback archive + status flip\n // + pipeline event). The role agent loads roles/review.md, fans out the\n // four code-review-* convoy reviewers via Agent tool, synthesizes, and\n // posts the verdict via /api/review/:id/status. Test dispatch is NOT\n // part of the review prompt — reactive Cloister picks up the\n // review.approved lifecycle event and spawns the test role.\n let reviewResult: { success: boolean; message: string; error?: string; gated?: boolean };\n try {\n const { spawnReviewRoleForIssue } = await import('../../../../lib/cloister/review-agent.js');\n reviewResult = await Effect.runPromise(spawnReviewRoleForIssue({\n issueId,\n workspace: workspacePath,\n branch: branchName,\n prUrl: getReviewStatusSync(issueId)?.prUrl,\n }));\n } catch (err: any) {\n reviewResult = {\n success: false,\n message: err?.message ?? 'Failed to start review role',\n error: err?.message,\n };\n }\n\n if (!reviewResult.success) {\n if (reviewResult.gated) {\n console.log(`[approve] review dispatch deferred for ${issueId}: ${reviewResult.message}`);\n completePendingOperation(issueId, reviewResult.message);\n setReviewStatusBase(issueId, {\n reviewStatus: 'pending',\n reviewNotes: reviewResult.message,\n });\n return jsonResponse({\n success: false,\n gated: true,\n message: reviewResult.message,\n pipeline: 'deferred',\n ...(recentPushWarning && { recentPushWarning }),\n ...(mainAdvancedBy > 0 && { mainAdvancedBy }),\n }, { status: 409 });\n }\n\n console.warn(`[approve] review role failed to start: ${reviewResult.message}`);\n console.log(`[approve] Falling back to direct merge...`);\n } else {\n console.log(\n `[approve] Pipeline started - review role will synthesize convoy findings`\n );\n completePendingOperation(issueId, null);\n return jsonResponse({\n success: true,\n message: `Approval pipeline started for ${issueId}. Role: review`,\n pipeline: 'running',\n note: 'Watch the role run for progress. Click Merge when review+test pass.',\n ...(recentPushWarning && { recentPushWarning }),\n ...(mainAdvancedBy > 0 && { mainAdvancedBy }),\n });\n }\n\n // Fallback (PAN-1531): direct server-side rebase via rebaseFeatureBranch.\n // The ship-role LLM agent was retired — rebase is deterministic mechanical\n // work and runs in-process. On success the workspace branch is pushed to\n // origin with --force-with-lease and the dashboard flips readyForMerge so\n // the human Merge button renders. On conflict the operator resolves\n // manually in the workspace and re-requests review.\n console.log(`[approve] Step 3/3: Running server-side rebase for ${issueId}...`);\n\n try {\n const { rebaseFeatureBranch } = await import(\n '../../../../lib/cloister/merge-rebase.js'\n );\n const workspacePathForRebase = join(projectPath, 'workspaces', `feature-${issueId.toLowerCase()}`);\n const rebaseResult = await Effect.runPromise(\n rebaseFeatureBranch(workspacePathForRebase, branchName, 'main', issueId),\n );\n\n if (!rebaseResult.success) {\n const conflictDetail = rebaseResult.conflictFiles?.length\n ? `\\nConflict files: ${rebaseResult.conflictFiles.join(', ')}`\n : '';\n const error = `Rebase blocked for ${issueId}.\\nReason: ${rebaseResult.reason ?? 'Unknown'}${conflictDetail}\\n\\nResume in workspace:\\n cd ${workspacePathForRebase}\\n git rebase origin/main\\n # resolve conflicts, then\\n git push --force-with-lease`;\n completePendingOperation(issueId, error);\n return jsonResponse({ error }, { status: 400 });\n }\n\n console.log(`[approve] Rebase complete for ${issueId} (${rebaseResult.skipped ? 'no-op' : 'rebased'}); ready for human Merge button`);\n } catch (rebaseError: any) {\n const error = `Server-side rebase failed: ${rebaseError.message}\\n\\nResolve manually:\\n cd <workspace>\\n git rebase origin/main\\n git push --force-with-lease`;\n completePendingOperation(issueId, error);\n return jsonResponse({ error }, { status: 400 });\n }\n\n // Push merged main (with divergence guard — pushApproveMain catches MainDivergedError\n // and marks workspace stuck if origin/main advanced past our local ancestor)\n const pushResult = await pushApproveMain(issueId, projectPath);\n if (!pushResult.pushed) {\n completePendingOperation(issueId, pushResult.error);\n return jsonResponse({ error: pushResult.error }, { status: pushResult.httpStatus });\n }\n\n // Post-merge lifecycle\n const { approve: lifecycleApprove } = await import('../../../../lib/lifecycle/index.js');\n const ghResolved = resolveGitHubIssueShared(issueId);\n const isGitHubIssueFlag = ghResolved.isGitHub;\n const lifecycleCtx = {\n issueId,\n projectPath,\n ...(ghResolved.isGitHub\n ? {\n github: {\n owner: ghResolved.owner,\n repo: ghResolved.repo,\n number: ghResolved.number,\n },\n }\n : {}),\n };\n\n const lifecycleResult = await Effect.runPromise(lifecycleApprove(lifecycleCtx));\n console.log(\n `[approve] Lifecycle completed for ${issueId}: ${lifecycleResult.steps\n .filter((s: any) => s.success && !s.skipped)\n .map((s: any) => s.step)\n .join(', ')}`\n );\n\n if (isGitHubIssueFlag) {\n try {\n await execAsync('pan sync', { encoding: 'utf-8', timeout: 30000 });\n } catch (syncError: any) {\n console.error('pan sync failed (non-fatal):', syncError.message);\n }\n }\n\n completePendingOperation(issueId);\n\n return jsonResponse({\n success: true,\n message: `Approved ${issueId}: ${lifecycleResult.steps\n .filter((s: any) => s.success && !s.skipped)\n .map((s: any) => s.step)\n .join(', ')}${isGitHubIssueFlag ? ', skills synced' : ''}`,\n });\n });\n }))\n);\n\n\n// ─── Route: GET /api/merge-queue ─────────────────────────────────────────────\n\nconst getMergeQueueRoute = HttpRouter.add(\n 'GET',\n '/api/merge-queue',\n httpHandler(Effect.gen(function* () {\n const queues = getAllActiveQueues();\n return jsonResponse({ queues });\n })),\n);\n\n// ─── Route: POST /api/internal/pipeline/notify ────────────────────────────────\n//\n// Cross-process bridge for `notifyPipeline()` (PAN-891, expanded in PAN-915).\n//\n// `notifyPipeline` is an in-process handler registry; only the dashboard server\n// registers a handler. CLI processes (e.g. `pan review run`) write to shared\n// state and call `notifyPipeline()`, which is a no-op in their own process.\n// This endpoint lets them poke the dashboard so it re-emits the corresponding\n// domain event into the live event stream.\n//\n// Accepted bodies (PAN-915):\n// { type: 'status_changed', issueId }\n// — Server re-reads ReviewStatus from SQLite (avoids stale snapshots) and\n// dispatches via in-process handler.\n// { type: 'review.approved', issueId }\n// { type: 'test.passed', issueId }\n// { type: 'task_queued', specialist, issueId }\n// { type: 'reviewer_started', issueId, role, sessionName }\n// { type: 'reviewer_completed', issueId, role }\n// { type: 'reviewer_timed_out', issueId, role, sessionName, attempt, maxRetries, willRetry }\n// { type: 'coordinator_started', issueId, sessionName }\n// { type: 'coordinator_died', issueId, sessionName, reason }\n// — Forwarded verbatim to the in-process handler.\n\nconst postInternalPipelineNotifyRoute = HttpRouter.add(\n 'POST',\n '/api/internal/pipeline/notify',\n httpHandler(Effect.gen(function* () {\n // Shared-secret check (PAN-891 review feedback). The dashboard binds 0.0.0.0\n // by default, so this stateful endpoint must be unreachable without the\n // server-issued token. Same token is read by CLI senders via getInternalToken().\n const request = yield* HttpServerRequest.HttpServerRequest;\n const { INTERNAL_TOKEN_HEADER, getInternalTokenSync } = yield* Effect.promise(() =>\n import('../../../../lib/internal-token.js'),\n );\n const expected = getInternalTokenSync();\n if (!expected) {\n return jsonResponse({ ok: false, error: 'internal token not configured' }, 503);\n }\n const headers = request.headers as Record<string, string | string[] | undefined>;\n const raw = headers[INTERNAL_TOKEN_HEADER];\n const provided = Array.isArray(raw) ? raw[0] : raw;\n if (!provided || provided !== expected) {\n return jsonResponse({ ok: false, error: 'forbidden' }, 403);\n }\n\n const body = yield* readJsonBody;\n const event = body as Record<string, unknown>;\n const type = event.type as string | undefined;\n\n const { notifyPipelineSync } = yield* Effect.promise(() =>\n import('../../../../lib/pipeline-notifier.js'),\n );\n\n switch (type) {\n case 'status_changed': {\n const issueId = event.issueId as string | undefined;\n if (!issueId) {\n return jsonResponse({ ok: false, error: 'status_changed requires issueId' }, 400);\n }\n const status = getReviewStatusSync(issueId);\n if (!status) {\n return jsonResponse({ ok: false, error: `no review status found for ${issueId}` }, 404);\n }\n notifyPipelineSync({ type: 'status_changed', issueId, status });\n return jsonResponse({ ok: true });\n }\n case 'review.approved':\n case 'test.passed': {\n const issueId = event.issueId as string | undefined;\n if (!issueId) {\n return jsonResponse({ ok: false, error: `${type} requires issueId` }, 400);\n }\n // PAN-1988: this MUST be notifyPipelineSync (the imported function). The bare\n // `notifyPipeline` (the Effect variant) is not imported here, so it threw\n // \"notifyPipeline is not defined\" and silently dropped EVERY forwarded review.approved /\n // test.passed event — breaking the reactive review→test and test→ship handoffs for any\n // CLI-originated verdict. The in-process dashboard handler routes these to reactive Cloister.\n notifyPipelineSync({ type, issueId });\n return jsonResponse({ ok: true });\n }\n case 'task_queued': {\n const issueId = event.issueId as string | undefined;\n const specialist = event.specialist as string | undefined;\n if (!issueId || !specialist) {\n return jsonResponse({ ok: false, error: 'task_queued requires issueId and specialist' }, 400);\n }\n notifyPipelineSync({ type: 'task_queued', specialist, issueId });\n return jsonResponse({ ok: true });\n }\n case 'reviewer_started': {\n const issueId = event.issueId as string | undefined;\n const role = event.role as string | undefined;\n const sessionName = event.sessionName as string | undefined;\n if (!issueId || !role || !sessionName) {\n return jsonResponse({ ok: false, error: 'reviewer_started requires issueId, role, sessionName' }, 400);\n }\n notifyPipelineSync({ type: 'reviewer_started', issueId, role, sessionName });\n return jsonResponse({ ok: true });\n }\n case 'reviewer_completed': {\n const issueId = event.issueId as string | undefined;\n const role = event.role as string | undefined;\n if (!issueId || !role) {\n return jsonResponse({ ok: false, error: 'reviewer_completed requires issueId, role' }, 400);\n }\n notifyPipelineSync({ type: 'reviewer_completed', issueId, role });\n return jsonResponse({ ok: true });\n }\n case 'reviewer_timed_out': {\n const issueId = event.issueId as string | undefined;\n const role = event.role as string | undefined;\n const sessionName = event.sessionName as string | undefined;\n const attempt = typeof event.attempt === 'number' ? event.attempt : undefined;\n const maxRetries = typeof event.maxRetries === 'number' ? event.maxRetries : undefined;\n const willRetry = typeof event.willRetry === 'boolean' ? event.willRetry : undefined;\n if (!issueId || !role || !sessionName || attempt === undefined || maxRetries === undefined || willRetry === undefined) {\n return jsonResponse({ ok: false, error: 'reviewer_timed_out requires issueId, role, sessionName, attempt, maxRetries, willRetry' }, 400);\n }\n notifyPipelineSync({ type: 'reviewer_timed_out', issueId, role, sessionName, attempt, maxRetries, willRetry });\n return jsonResponse({ ok: true });\n }\n case 'coordinator_started': {\n const issueId = event.issueId as string | undefined;\n const sessionName = event.sessionName as string | undefined;\n if (!issueId || !sessionName) {\n return jsonResponse({ ok: false, error: 'coordinator_started requires issueId, sessionName' }, 400);\n }\n notifyPipelineSync({ type: 'coordinator_started', issueId, sessionName });\n return jsonResponse({ ok: true });\n }\n case 'coordinator_died': {\n const issueId = event.issueId as string | undefined;\n const sessionName = event.sessionName as string | undefined;\n const reason = event.reason as string | undefined;\n if (!issueId || !sessionName || !reason) {\n return jsonResponse({ ok: false, error: 'coordinator_died requires issueId, sessionName, reason' }, 400);\n }\n notifyPipelineSync({ type: 'coordinator_died', issueId, sessionName, reason });\n return jsonResponse({ ok: true });\n }\n default:\n return jsonResponse({ ok: false, error: `unknown pipeline event type: ${type}` }, 400);\n }\n })),\n);\n\n\nexport const mergeOpsRouteLayer = Layer.mergeAll(\n postWorkspaceSyncMainRoute,\n postWorkspaceMergeRoute,\n postForgeApproveRoute,\n postForgeMergeRoute,\n postWorkspaceApproveRoute,\n getMergeQueueRoute,\n postInternalPipelineNotifyRoute,\n);\n\nexport default mergeOpsRouteLayer;\n","/**\n * Workspace-data route module — extracted from routes/workspaces.ts (B / wave 2, seam 1).\n *\n * Read-only workspace query + plan/UAT + TLDR endpoints:\n * GET /api/workspace-stack-health\n * GET /api/workspaces/:issueId\n * POST /api/workspaces\n * GET /api/workspaces/:issueId/plan\n * GET /api/workspaces/:issueId/uat-context\n * PATCH /api/workspaces/:issueId/plan/inspection-policy\n * GET /api/workspaces/:issueId/tldr\n *\n * Shared singletons (project path, workspace info, container status, pending ops,\n * review reconciliation, readJsonBody, spawnPanCommand, requireTrustedMutationOrigin)\n * stay owned by ../workspaces.js and are imported here.\n */\n\nimport { exec, execFile } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { access, readFile, stat, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { Effect, Layer } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\n\nimport { parseIssueIdSync, extractPrefixSync } from '../../../../lib/issue-id.js';\nimport {\n resolveProjectFromIssueSync,\n getProjectSync,\n findProjectByTeamSync,\n} from '../../../../lib/projects.js';\nimport { loadWorkspaceMetadataSync } from '../../../../lib/remote/workspace-metadata.js';\nimport {\n collectDockerContainerLifecycleSnapshot,\n getWorkspaceStackHealth,\n} from '../../../../lib/workspace/stack-health.js';\nimport { listSessionNames, capturePane } from '../../../../lib/tmux.js';\nimport { getActiveSessionModelSync } from '../../../../lib/cost-parsers/jsonl-parser.js';\nimport { getReviewStatusSync } from '../../../../lib/review-status.js';\nimport { listStashes, isSalvageableStash } from '../../../../lib/stashes.js';\nimport { findPlan, isPlanningComplete, mergeRecordStatusOverrides, readPlan } from '../../../../lib/vbrief/io.js';\nimport { getCostsForIssueSync } from '../../../../lib/costs/index.js';\nimport { resolveIssueHeadlineCost } from '../../services/issue-cost-resolver.js';\nimport { getCachedRunningAgents } from '../../services/running-agents-cache.js';\nimport { PAN_CONTINUE_FILENAME, PAN_DIRNAME } from '../../../../lib/pan-dir/types.js';\nimport { getWorkspacePathForIssue } from '../../workspace-paths.js';\nimport { criticalPath, actionableDoc } from '../../../../lib/vbrief/dag.js';\nimport { findVBriefByIssue, readVBriefDocument } from '../../../../lib/vbrief/vbrief-index.js';\nimport { VBRIEF_INSPECTION_POLICIES } from '../../../../lib/vbrief/types.js';\nimport type { VBriefDocument, VBriefInspectionPolicy } from '../../../../lib/vbrief/types.js';\nimport { getChangedFiles, getDiffBase, getDiffStat } from '../../../../lib/cloister/review-context.js';\nimport type { ChangedFile } from '../../../../lib/cloister/review-context.js';\nimport { getTldrDaemonServiceSync } from '../../../../lib/tldr-daemon.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { httpHandler } from '../http-handler.js';\nimport {\n getProjectPath,\n getWorkspaceInfoForIssue,\n getContainerStatusAsync,\n getPendingOperation,\n readJsonBody,\n spawnPanCommand,\n requireTrustedMutationOrigin,\n} from '../workspaces.js';\nimport { reconcileGitHubMergeStatus } from './merge-ops.js';\n\nconst execAsync = promisify(exec);\nconst execFileAsync = promisify(execFile);\n\nfunction getWorkspaceLocation(issueId: string): 'local' | 'remote' | undefined {\n try {\n const meta = loadWorkspaceMetadataSync(issueId);\n if (meta?.location) return meta.location as 'local' | 'remote';\n } catch { /* non-fatal */ }\n return undefined;\n}\nasync function getGitStatusAsync(workspacePath: string): Promise<{\n branch: string;\n uncommittedFiles: number;\n latestCommit: string;\n} | null> {\n try {\n const [branchResult, statusResult, logResult] = await Promise.all([\n execAsync('git rev-parse --abbrev-ref HEAD', { cwd: workspacePath, encoding: 'utf-8' }),\n execAsync('git status --porcelain', { cwd: workspacePath, encoding: 'utf-8' }),\n execAsync('git log -1 --format=\"%s\"', { cwd: workspacePath, encoding: 'utf-8' }),\n ]);\n return {\n branch: branchResult.stdout.trim(),\n uncommittedFiles: statusResult.stdout.trim() ? statusResult.stdout.trim().split('\\n').length : 0,\n latestCommit: logResult.stdout.trim(),\n };\n } catch {\n return null;\n }\n}\nasync function getRepoGitStatusAsync(workspacePath: string): Promise<{\n ahead: number;\n behind: number;\n hasOrigin: boolean;\n} | null> {\n try {\n const { stdout: remoteOut } = await execAsync('git remote -v', { cwd: workspacePath, encoding: 'utf-8' });\n if (!remoteOut.includes('origin')) return { ahead: 0, behind: 0, hasOrigin: false };\n const { stdout: revlistOut } = await execAsync(\n 'git rev-list --left-right --count HEAD...origin/HEAD 2>/dev/null || echo \"0\\t0\"',\n { cwd: workspacePath, encoding: 'utf-8' }\n );\n const parts = revlistOut.trim().split('\\t');\n return {\n ahead: parseInt(parts[0] || '0', 10),\n behind: parseInt(parts[1] || '0', 10),\n hasOrigin: true,\n };\n } catch {\n return null;\n }\n}\nasync function getMrUrlAsync(issueId: string, workspacePath: string): Promise<string | null> {\n try {\n const issueLower = issueId.toLowerCase();\n const branchName = `feature/${issueLower}`;\n const { stdout } = await execFileAsync('gh', ['pr', 'view', branchName, '--json', 'url', '--jq', '.url'], { cwd: workspacePath, encoding: 'utf-8' });\n const url = stdout.trim();\n return url || null;\n } catch {\n return null;\n }\n}\nasync function getIndexStats(workspacePath: string): Promise<{\n fileCount?: number;\n indexAge?: string;\n edgeCount?: number;\n}> {\n const tldrPath = join(workspacePath, '.tldr');\n const tldrExists = await access(tldrPath).then(() => true, () => false);\n if (!tldrExists) return {};\n try {\n let indexAge: string | undefined;\n const langPath = join(tldrPath, 'languages.json');\n const langContent = await readFile(langPath, 'utf-8').catch(() => null);\n if (langContent) {\n const langData = JSON.parse(langContent);\n if (langData.timestamp) {\n const ageMs = Date.now() - langData.timestamp * 1000;\n const ageHours = Math.floor(ageMs / (1000 * 60 * 60));\n indexAge =\n ageHours === 0 ? 'now' : ageHours < 24 ? `${ageHours}h ago` : `${Math.floor(ageHours / 24)}d ago`;\n }\n }\n if (!indexAge) {\n const stats = await stat(tldrPath);\n const ageMs = Date.now() - stats.mtimeMs;\n const ageHours = Math.floor(ageMs / (1000 * 60 * 60));\n indexAge =\n ageHours === 0 ? 'now' : ageHours < 24 ? `${ageHours}h ago` : `${Math.floor(ageHours / 24)}d ago`;\n }\n let fileCount: number | undefined;\n let edgeCount: number | undefined;\n const cgPath = join(tldrPath, 'cache', 'call_graph.json');\n const cgContent = await readFile(cgPath, 'utf-8').catch(() => null);\n if (cgContent) {\n const cg = JSON.parse(cgContent);\n edgeCount = Array.isArray(cg.edges) ? cg.edges.length : undefined;\n if (Array.isArray(cg.edges)) {\n const files = new Set<string>();\n for (const e of cg.edges) {\n if (e.from_file) files.add(e.from_file);\n if (e.to_file) files.add(e.to_file);\n }\n fileCount = files.size;\n }\n }\n return { fileCount, indexAge, edgeCount };\n } catch (err) {\n console.error(`[getIndexStats] Error for ${workspacePath}:`, err);\n return {};\n }\n}\nfunction resolvePlanLocation(projectPath: string, issueId: string): Effect.Effect<{ path: string; lifecycleDir: string; doc: VBriefDocument } | null, unknown> {\n return Effect.gen(function* () {\n // PAN-2401: every doc this route returns gets the per-issue record's\n // statusOverrides applied — merged beads must read 'completed', not the\n // spec's immutable 'pending'.\n const issueLower = issueId.toLowerCase();\n const workspacePath = join(projectPath, 'workspaces', `feature-${issueLower}`);\n\n const found = yield* findVBriefByIssue(projectPath, issueId);\n if (found) {\n const doc = yield* readVBriefDocument(found.path);\n return {\n path: found.path,\n lifecycleDir: found.lifecycleDir,\n doc: mergeRecordStatusOverrides(doc, workspacePath),\n };\n }\n\n const planPath = yield* findPlan(workspacePath);\n if (!planPath) return null;\n const doc = yield* readPlan(planPath);\n return {\n path: planPath,\n lifecycleDir: 'workspace',\n doc: mergeRecordStatusOverrides(doc, workspacePath),\n };\n });\n}\n\nexport interface UatContextAcceptanceCriterion {\n id: string;\n title: string;\n status: string;\n itemId: string;\n itemTitle: string;\n}\n\nexport interface UatContextDeliverable {\n id: string;\n title: string;\n status: string;\n action?: string;\n}\n\nexport interface UatContextChangedFile {\n path: string;\n status: 'M' | 'A' | 'D' | 'R' | 'C' | 'U';\n additions: number;\n deletions: number;\n}\n\ninterface UatContextPlanFields {\n acceptanceCriteria: UatContextAcceptanceCriterion[];\n deliverables: UatContextDeliverable[];\n proposal: string | null;\n}\n\ninterface UatContextGitFields {\n changedFiles: UatContextChangedFile[];\n changedFilesTotal: number;\n changedFilesOmitted: number;\n diffStat: { stat: string; truncated: boolean } | null;\n source: { files: 'git' | 'none' };\n}\n\nconst MAX_UAT_CONTEXT_CHANGED_FILES = 12;\nconst MAX_UAT_CONTEXT_DIFF_STAT_LENGTH = 4_000;\n\nexport function assembleUatContextPlanFields(doc: VBriefDocument | null): UatContextPlanFields {\n if (!doc) {\n return { acceptanceCriteria: [], deliverables: [], proposal: null };\n }\n\n const acceptanceCriteria: UatContextAcceptanceCriterion[] = [];\n const deliverables: UatContextDeliverable[] = [];\n\n for (const item of doc.plan.items ?? []) {\n deliverables.push({\n id: item.id,\n title: item.title,\n status: item.status,\n ...(item.narrative?.Action ? { action: item.narrative.Action } : {}),\n });\n\n const subItems = item.subItems ?? [];\n if (subItems.length === 0) continue;\n\n const taggedAcceptanceCriteria = subItems.filter(\n (subItem) => subItem.metadata?.kind === 'acceptance_criterion',\n );\n const selectedSubItems = taggedAcceptanceCriteria.length > 0\n ? taggedAcceptanceCriteria\n : subItems;\n\n for (const subItem of selectedSubItems) {\n acceptanceCriteria.push({\n id: subItem.id,\n title: subItem.title,\n status: subItem.status,\n itemId: item.id,\n itemTitle: item.title,\n });\n }\n }\n\n return {\n acceptanceCriteria,\n deliverables,\n proposal: doc.plan.narratives?.Proposal ?? null,\n };\n}\n\nexport function emptyUatContextGitFields(): UatContextGitFields {\n return {\n changedFiles: [],\n changedFilesTotal: 0,\n changedFilesOmitted: 0,\n diffStat: null,\n source: { files: 'none' },\n };\n}\n\nexport function assembleUatContextGitFields(\n changedFiles: ChangedFile[],\n diffStat: { stat: string; truncated: boolean },\n): UatContextGitFields {\n if (diffStat.stat === 'Unable to compute diff stat') {\n return emptyUatContextGitFields();\n }\n\n const limitedChangedFiles = changedFiles\n .slice(0, MAX_UAT_CONTEXT_CHANGED_FILES)\n .map((file) => ({\n path: file.path,\n status: file.status,\n additions: file.additions,\n deletions: file.deletions,\n }));\n\n const isDiffStatTruncated = diffStat.truncated || diffStat.stat.length > MAX_UAT_CONTEXT_DIFF_STAT_LENGTH;\n const limitedDiffStat = {\n stat: diffStat.stat.slice(0, MAX_UAT_CONTEXT_DIFF_STAT_LENGTH),\n truncated: isDiffStatTruncated,\n };\n\n return {\n changedFiles: limitedChangedFiles,\n changedFilesTotal: changedFiles.length,\n changedFilesOmitted: Math.max(0, changedFiles.length - limitedChangedFiles.length),\n diffStat: limitedDiffStat,\n source: { files: 'git' },\n };\n}\n\nasync function readWorkspaceUatChangedFiles(workspacePath: string): Promise<UatContextGitFields> {\n if (!existsSync(workspacePath)) return emptyUatContextGitFields();\n\n try {\n const base = await getDiffBase(workspacePath);\n const [changedFiles, diffStat] = await Promise.all([\n getChangedFiles(workspacePath, base),\n getDiffStat(workspacePath, base),\n ]);\n\n return assembleUatContextGitFields(changedFiles, diffStat);\n } catch {\n return emptyUatContextGitFields();\n }\n}\n// ─── Route: GET /api/workspace-stack-health ──────────────────────────────────\n\nconst getWorkspaceStackHealthBatchRoute = HttpRouter.add(\n 'GET',\n '/api/workspace-stack-health',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const url = new URL(request.url, 'http://localhost');\n const issueIds = Array.from(new Set((url.searchParams.get('issueIds') ?? '')\n .split(',')\n .map((id) => id.trim())\n .filter(Boolean)))\n .slice(0, 100);\n\n const parsedIds = issueIds.map((issueId) => ({ issueId, parsed: parseIssueIdSync(issueId) }));\n const invalid = parsedIds.find(({ parsed }) => !parsed);\n if (invalid) {\n return jsonResponse({ error: `Invalid issue ID: ${invalid.issueId}` }, { status: 400 });\n }\n\n const workspaceRequests = parsedIds.map(({ parsed }) => {\n const normalizedIssueId = parsed!.raw.toUpperCase();\n const workspaceMetadata = (() => {\n try {\n return loadWorkspaceMetadataSync(normalizedIssueId);\n } catch {\n return null;\n }\n })();\n if (workspaceMetadata?.location === 'remote') {\n return {\n kind: 'response' as const,\n normalizedIssueId,\n response: { exists: true, issueId: normalizedIssueId, location: 'remote', isRemote: true },\n };\n }\n\n const resolved = resolveProjectFromIssueSync(normalizedIssueId);\n if (!resolved) {\n return {\n kind: 'response' as const,\n normalizedIssueId,\n response: { exists: false, issueId: normalizedIssueId },\n };\n }\n\n const projectConfig = getProjectSync(resolved.projectKey);\n if (!projectConfig) {\n return {\n kind: 'response' as const,\n normalizedIssueId,\n response: { exists: false, issueId: normalizedIssueId },\n };\n }\n const workspacePath = join(\n resolved.projectPath,\n projectConfig.workspace?.workspaces_dir ?? 'workspaces',\n `feature-${parsed!.normalized}`,\n );\n if (!existsSync(workspacePath)) {\n return {\n kind: 'response' as const,\n normalizedIssueId,\n response: { exists: false, issueId: normalizedIssueId },\n };\n }\n\n return {\n kind: 'local' as const,\n normalizedIssueId,\n projectConfig,\n projectPath: resolved.projectPath,\n workspacePath,\n };\n });\n\n const entries = yield* Effect.promise(async () => {\n const containers = workspaceRequests.some((request) =>\n request.kind === 'local' && Boolean(request.projectConfig.workspace?.docker?.compose_template)\n )\n ? await Effect.runPromise(collectDockerContainerLifecycleSnapshot())\n : undefined;\n\n return Promise.all(workspaceRequests.map(async (request) => {\n if (request.kind === 'response') {\n return [request.normalizedIssueId, request.response] as const;\n }\n\n const stackHealth = await Effect.runPromise(getWorkspaceStackHealth(request.normalizedIssueId, {\n projectConfig: { ...request.projectConfig, path: request.projectPath },\n workspacePath: request.workspacePath,\n containers,\n }));\n\n return [request.normalizedIssueId, {\n exists: true,\n issueId: request.normalizedIssueId,\n path: request.workspacePath,\n stackHealth,\n hasDocker: Boolean(request.projectConfig.workspace?.docker?.compose_template),\n location: 'local',\n }] as const;\n }));\n });\n\n return jsonResponse({ workspaces: Object.fromEntries(entries) });\n })),\n);\n// ─── Route: GET /api/workspaces/:issueId ─────────────────────────────────────\n\nconst getWorkspaceRoute = HttpRouter.add(\n 'GET',\n '/api/workspaces/:issueId',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const issueLower = issueId.toLowerCase();\n\n const workspaceInfo = getWorkspaceInfoForIssue(issueId);\n\n if (workspaceInfo.isRemote && workspaceInfo.vmName) {\n return jsonResponse({\n exists: true,\n issueId,\n isRemote: true,\n vmName: workspaceInfo.vmName,\n remotePath: workspaceInfo.remotePath,\n agentId: workspaceInfo.agentId,\n path: `${workspaceInfo.vmName}:${workspaceInfo.remotePath}`,\n location: 'remote',\n message: `Workspace is on remote Fly machine: ${workspaceInfo.vmName}`,\n });\n }\n\n const workspaceName = `feature-${issueLower}`;\n const workspacePath = join(projectPath, 'workspaces', workspaceName);\n\n if (!existsSync(workspacePath)) {\n return jsonResponse({ exists: false, issueId });\n }\n\n const gitFile = join(workspacePath, '.git');\n const apiGit = join(workspacePath, 'api', '.git');\n const feGit = join(workspacePath, 'fe', '.git');\n const srcGit = join(workspacePath, 'src', '.git');\n const devcontainer = join(workspacePath, '.devcontainer');\n const claudeMd = join(workspacePath, 'CLAUDE.md');\n\n const hasValidStructure =\n existsSync(gitFile) ||\n existsSync(apiGit) ||\n existsSync(feGit) ||\n existsSync(srcGit) ||\n existsSync(devcontainer) ||\n existsSync(claudeMd);\n\n if (!hasValidStructure) {\n const location = getWorkspaceLocation(issueId);\n return jsonResponse({\n exists: true,\n corrupted: true,\n issueId,\n path: workspacePath,\n message: 'Workspace exists but is not a valid git worktree or containerized workspace',\n location,\n });\n }\n\n const projectConfig = findProjectByTeamSync(issuePrefix);\n const dnsDomain = projectConfig?.workspace?.dns?.domain || 'localhost';\n const featureFolder = `feature-${issueLower}`;\n\n let frontendUrl = `https://${featureFolder}.${dnsDomain}`;\n let apiUrl = `https://api-${featureFolder}.${dnsDomain}`;\n\n if (projectConfig?.workspace?.dns?.entries) {\n const entries = projectConfig.workspace.dns.entries;\n if (entries[0]) {\n frontendUrl = `https://${entries[0]\n .replace('{{FEATURE_FOLDER}}', featureFolder)\n .replace('{{DOMAIN}}', dnsDomain)}`;\n }\n if (entries[1]) {\n apiUrl = `https://${entries[1]\n .replace('{{FEATURE_FOLDER}}', featureFolder)\n .replace('{{DOMAIN}}', dnsDomain)}`;\n }\n }\n\n let services: { name: string; url?: string }[] = [];\n const panContinueFile = join(workspacePath, PAN_DIRNAME, PAN_CONTINUE_FILENAME);\n const workspaceMd = join(workspacePath, 'WORKSPACE.md');\n const dockerCompose = join(workspacePath, 'docker-compose.yml');\n\n const urlSourceFile = existsSync(panContinueFile)\n ? panContinueFile\n : existsSync(workspaceMd)\n ? workspaceMd\n : null;\n\n if (urlSourceFile) {\n try {\n const content = yield* Effect.promise(() => readFile(urlSourceFile, 'utf-8'));\n const urlMatches = content.matchAll(/(\\w+):\\s*(https?:\\/\\/[^\\s\\n]+)/gi);\n for (const match of urlMatches) {\n services.push({ name: match[1], url: match[2] });\n }\n } catch {}\n }\n\n if (services.length === 0) {\n services = [\n { name: 'Frontend', url: frontendUrl },\n { name: 'API', url: apiUrl },\n ];\n }\n\n const devcontainerPath = join(workspacePath, '.devcontainer');\n let hasDocker =\n existsSync(dockerCompose) ||\n existsSync(join(workspacePath, 'compose.yaml')) ||\n existsSync(join(devcontainerPath, 'docker-compose.yml')) ||\n existsSync(join(devcontainerPath, 'docker-compose.devcontainer.yml')) ||\n existsSync(join(devcontainerPath, 'compose.yaml')) ||\n existsSync(join(devcontainerPath, 'compose.infra.yml')) ||\n existsSync(devcontainerPath);\n\n // For polyrepo workspaces, also check compose files inside sub-repos\n if (!hasDocker && projectConfig?.workspace?.repos) {\n for (const repo of projectConfig.workspace.repos) {\n const repoPath = join(workspacePath, repo.path);\n if (\n existsSync(join(repoPath, 'docker-compose.yml')) ||\n existsSync(join(repoPath, 'docker-compose.yaml')) ||\n existsSync(join(repoPath, 'compose.yaml'))\n ) {\n hasDocker = true;\n break;\n }\n }\n }\n\n const canContainerize = false;\n\n const agentSession = `agent-${issueLower}`;\n const [git, repoGit, containers, stackHealth, mrUrl] = yield* Effect.promise(() => Promise.all([\n getGitStatusAsync(workspacePath),\n getRepoGitStatusAsync(workspacePath),\n hasDocker ? getContainerStatusAsync(issueId, projectPath) : Promise.resolve(null),\n Effect.runPromise(getWorkspaceStackHealth(issueId, { projectConfig, emitTransitionActivity: true })),\n getMrUrlAsync(issueId, workspacePath),\n ]));\n const sessionNames = yield* listSessionNames();\n const paneOutput = yield* capturePane(agentSession, 50).pipe(Effect.orElseSucceed(() => ''));\n\n let hasAgent = false;\n let agentSessionId: string | null = null;\n let agentModel: string | undefined;\n let agentModelFull: string | undefined;\n\n if (sessionNames.includes(agentSession)) {\n hasAgent = true;\n agentSessionId = agentSession;\n\n // Match Anthropic models: [Opus], [Sonnet 4.6], [Haiku 4.5]\n // Also match OpenAI models: [gpt-5.4], [oai@gpt-5.4], [o3], [cx@o3], [o4-mini]\n const modelMatch = paneOutput.match(\n /\\[((?:oai|cx|go)?@?(?:gpt-[0-9.]+(?:-mini|-nano|-pro)?|o[1-4](?:-mini)?(?:-high)?|gemini-[0-9.]+(?:-pro|-flash|-lite)?))[^\\]]*\\]/i\n ) || paneOutput.match(/\\[(Opus|Sonnet|Haiku)[^\\]]*\\]/i);\n agentModel = modelMatch ? modelMatch[1] : undefined;\n\n const fullModel = getActiveSessionModelSync(workspacePath);\n if (fullModel) agentModelFull = fullModel;\n }\n\n const pendingOperation = getPendingOperation(issueId);\n const location = getWorkspaceLocation(issueId);\n const reviewStatus = getReviewStatusSync(issueId);\n\n if (\n pendingOperation?.type === 'merge' &&\n pendingOperation.status === 'failed' &&\n reviewStatus?.mergeStatus !== 'merged'\n ) {\n yield* Effect.promise(() => reconcileGitHubMergeStatus(issueId, reviewStatus));\n }\n\n const stashes = yield* listStashes(workspacePath);\n const salvageableStashes = stashes\n .filter(isSalvageableStash)\n .filter((entry) => entry.issueId === issueId.toUpperCase());\n\n const planPath = yield* findPlan(workspacePath);\n const hasPlan = planPath !== null;\n const planningComplete = hasPlan ? yield* isPlanningComplete(workspacePath) : false;\n const hasBeads = planningComplete;\n\n const issueData = getCostsForIssueSync(issueId);\n const agents = yield* Effect.promise(() => getCachedRunningAgents());\n const resolvedCost = resolveIssueHeadlineCost({\n issueId: issueId,\n aggregateCost: issueData?.totalCost,\n agents,\n });\n\n return jsonResponse({\n exists: true,\n issueId,\n path: workspacePath,\n frontendUrl,\n apiUrl,\n mrUrl,\n hasAgent,\n agentSessionId,\n agentModel,\n agentModelFull,\n git,\n repoGit,\n services,\n containers,\n stackHealth,\n hasDocker,\n canContainerize,\n pendingOperation,\n location,\n salvageableStashes,\n planningState: {\n hasPlan,\n hasBeads,\n beadsCount: 0,\n planningComplete,\n workspacePath,\n },\n costs: issueData\n ? {\n issueId: issueId.toUpperCase(),\n totalCost: issueData.totalCost,\n resolvedTotalCost: resolvedCost.resolvedTotalCost,\n aggregateCost: resolvedCost.aggregateCost,\n liveCost: resolvedCost.liveCost,\n totalTokens: issueData.inputTokens + issueData.outputTokens + issueData.cacheReadTokens + issueData.cacheWriteTokens,\n inputTokens: issueData.inputTokens,\n outputTokens: issueData.outputTokens,\n cacheReadTokens: issueData.cacheReadTokens,\n cacheWriteTokens: issueData.cacheWriteTokens,\n models: issueData.models,\n providers: issueData.providers,\n byModel: Object.fromEntries(\n Object.entries(issueData.models).map(([model, stats]: [string, { cost: number; tokens: number }]) => [\n model,\n { cost: stats.cost, tokens: stats.tokens },\n ])\n ),\n sessions: (issueData as unknown as { sessions?: unknown[] }).sessions ?? [],\n byStage: Object.fromEntries(\n Object.entries(issueData.stages || {}).map(([stage, stats]: [string, { cost: number; tokens: number }]) => [\n stage,\n { cost: stats.cost, tokens: stats.tokens },\n ])\n ),\n budget: issueData.budget,\n budgetWarning: issueData.budgetWarning,\n lastUpdated: issueData.lastUpdated,\n }\n : {\n issueId: issueId.toUpperCase(),\n totalCost: 0,\n resolvedTotalCost: resolvedCost.resolvedTotalCost,\n aggregateCost: resolvedCost.aggregateCost,\n liveCost: resolvedCost.liveCost,\n totalTokens: 0,\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n models: {},\n providers: {},\n byModel: {},\n sessions: [],\n byStage: {},\n budget: undefined,\n budgetWarning: false,\n },\n });\n }))\n);\n// ─── Route: POST /api/workspaces ─────────────────────────────────────────────\n\nconst postWorkspacesRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces',\n httpHandler(Effect.gen(function* () {\n const body = yield* readJsonBody;\n const { issueId, projectId } = body as { issueId?: string; projectId?: string };\n\n if (!issueId) {\n return jsonResponse({ error: 'issueId required' }, { status: 400 });\n }\n\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(projectId, issuePrefix);\n const activityId = spawnPanCommand(\n ['workspace', 'create', issueId],\n `Create workspace for ${issueId}`,\n projectPath\n );\n return jsonResponse({\n success: true,\n message: `Creating workspace for ${issueId}`,\n activityId,\n projectPath,\n });\n }))\n);\nconst getWorkspacePlanRoute = HttpRouter.add(\n 'GET',\n '/api/workspaces/:issueId/plan',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n\n const location = yield* resolvePlanLocation(projectPath, issueId);\n if (!location) {\n return jsonResponse(\n { error: 'No vBRIEF plan found for this workspace' },\n { status: 404 }\n );\n }\n\n const cp = criticalPath(actionableDoc(location.doc));\n return jsonResponse({ ...location.doc, criticalPath: cp, lifecycleDir: location.lifecycleDir });\n }))\n);\nconst getWorkspaceUatContextRoute = HttpRouter.add(\n 'GET',\n '/api/workspaces/:issueId/uat-context',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n const parsed = parseIssueIdSync(issueId);\n if (!parsed) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n\n const issuePrefix = parsed.prefix ?? extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const { parsedIssueId, workspacePath } = getWorkspacePathForIssue(projectPath, issueId);\n\n const location = yield* Effect.promise(() =>\n Effect.runPromise(resolvePlanLocation(projectPath, parsedIssueId)).catch(() => null)\n );\n const planFields = assembleUatContextPlanFields(location?.doc ?? null);\n const gitFields = yield* Effect.promise(() => readWorkspaceUatChangedFiles(workspacePath));\n\n return jsonResponse({\n issueId: parsedIssueId,\n ...planFields,\n changedFiles: gitFields.changedFiles,\n changedFilesTotal: gitFields.changedFilesTotal,\n changedFilesOmitted: gitFields.changedFilesOmitted,\n diffStat: gitFields.diffStat,\n source: {\n plan: location ? 'vbrief' : 'none',\n files: gitFields.source.files,\n },\n });\n }))\n);\nconst patchWorkspacePlanInspectionPolicyRoute = HttpRouter.add(\n 'PATCH',\n '/api/workspaces/:issueId/plan/inspection-policy',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const originError = requireTrustedMutationOrigin(request);\n if (originError) return originError;\n\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n\n const body = yield* readJsonBody;\n const policy = (body as { inspectionPolicy?: unknown }).inspectionPolicy;\n if (!VBRIEF_INSPECTION_POLICIES.includes(policy as VBriefInspectionPolicy)) {\n return jsonResponse({ error: 'Invalid inspection policy' }, { status: 400 });\n }\n\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const location = yield* resolvePlanLocation(projectPath, issueId);\n if (!location) {\n return jsonResponse(\n { error: 'No vBRIEF plan found for this workspace' },\n { status: 404 }\n );\n }\n\n const now = new Date().toISOString();\n const updated: VBriefDocument = {\n ...location.doc,\n vBRIEFInfo: {\n ...location.doc.vBRIEFInfo,\n inspectionPolicy: policy as VBriefInspectionPolicy,\n updated: now,\n },\n plan: {\n ...location.doc.plan,\n updated: now,\n },\n };\n\n yield* Effect.promise(() => writeFile(location.path, JSON.stringify(updated, null, 2) + '\\n', 'utf-8'));\n const cp = criticalPath(updated);\n return jsonResponse({ ...updated, criticalPath: cp, lifecycleDir: location.lifecycleDir });\n }))\n);\n// ─── Route: GET /api/workspaces/:issueId/tldr ─────────────────────────────────\n\nconst getWorkspaceTldrRoute = HttpRouter.add(\n 'GET',\n '/api/workspaces/:issueId/tldr',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n\n return yield* Effect.promise(async () => {\n const projectRoot = process.cwd();\n const workspacePath = join(projectRoot, 'workspaces', `feature-${issueId.toLowerCase()}`);\n const venvPath = join(workspacePath, '.venv');\n\n if (!existsSync(workspacePath)) {\n return jsonResponse({ error: 'Workspace not found' }, { status: 404 });\n }\n\n if (!existsSync(venvPath)) {\n return jsonResponse({\n available: false,\n reason: 'No .venv found in workspace',\n });\n }\n\n const service = getTldrDaemonServiceSync(workspacePath, venvPath);\n const status = await service.getStatus();\n const { fileCount, indexAge, edgeCount } = await getIndexStats(workspacePath);\n\n return jsonResponse({\n available: true,\n running: status.running,\n pid: status.pid,\n healthy: status.healthy,\n workspacePath,\n fileCount,\n indexAge,\n edgeCount,\n });\n })\n }))\n);\n\n// ─── Route: PATCH /api/workspaces/:issueId/tiered-execution ──────────────────\n\nconst patchWorkspaceTieredExecutionRoute = HttpRouter.add(\n 'PATCH',\n '/api/workspaces/:issueId/tiered-execution',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const originError = requireTrustedMutationOrigin(request);\n if (originError) return originError;\n\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: 'Invalid issue ID' }, { status: 400 });\n }\n\n const body = yield* readJsonBody;\n const override = (body as { override?: unknown }).override;\n if (override !== 'on' && override !== 'off' && override !== null && override !== undefined) {\n return jsonResponse({ error: 'Invalid tiered-execution override' }, { status: 400 });\n }\n\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const project = getProjectSync(issuePrefix);\n if (!project) {\n return jsonResponse({ error: 'Project not found' }, { status: 404 });\n }\n\n // Persist via the record write door\n yield* Effect.promise(() =>\n import('../../../../lib/pan-dir/record.js').then(m =>\n m.writeRecordTieredExecutionOverride(project, issueId, override as 'on' | 'off' | null)\n )\n );\n\n // Return the updated computed tieredExecution block (same shape as read door)\n const projectPath = project.path;\n const location = yield* resolvePlanLocation(projectPath, issueId);\n if (!location) {\n return jsonResponse(\n { error: 'No vBRIEF plan found for this workspace' },\n { status: 404 }\n );\n }\n\n // Compute tieredExecution block using the same logic as the read door\n const config = loadConfigSync().config;\n const tieredExecutionConfig = config.tieredExecution;\n const globalEnabled = tieredExecutionConfig?.enabled ?? false;\n const planMetadata = location.doc.plan?.metadata;\n\n // Determine source and effective state with record override precedence\n let source: 'issue-override' | 'plan-metadata' | 'global';\n let effective: boolean;\n\n if (override !== null && override !== undefined) {\n source = 'issue-override';\n effective = override === 'on';\n } else if (planMetadata?.tiered_execution === 'on') {\n source = 'plan-metadata';\n effective = true;\n } else if (planMetadata?.tiered_execution === 'off') {\n source = 'plan-metadata';\n effective = false;\n } else {\n source = 'global';\n effective = globalEnabled;\n }\n\n const tieredExecution = {\n effective,\n source,\n override: (override as 'on' | 'off' | null) ?? null,\n };\n\n return jsonResponse({ ...location.doc, tieredExecution, lifecycleDir: location.lifecycleDir });\n }))\n);\n\nexport const workspaceDataRouteLayer = Layer.mergeAll(\n getWorkspaceStackHealthBatchRoute,\n getWorkspaceRoute,\n postWorkspacesRoute,\n getWorkspacePlanRoute,\n getWorkspaceUatContextRoute,\n patchWorkspacePlanInspectionPolicyRoute,\n patchWorkspaceTieredExecutionRoute,\n getWorkspaceTldrRoute,\n);\n\nexport default workspaceDataRouteLayer;\n","/**\n * Stash-clean route module — extracted from routes/workspaces.ts (B / wave 2, seam 2).\n *\n * Git-stash recovery + corrupted-workspace clean endpoints:\n * GET /api/workspaces/:issueId/stashes\n * POST /api/workspaces/:issueId/stashes/:stashRef/recover\n * DELETE /api/workspaces/:issueId/stashes/:stashRef\n * GET /api/workspaces/:issueId/clean/preview\n * POST /api/workspaces/:issueId/clean\n *\n * Shared singletons (project path, trusted-origin guard, readJsonBody,\n * spawnPanCommand) stay owned by ../workspaces.js and are imported here.\n */\n\nimport { exec } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { Effect, Layer } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\n\nimport { parseIssueIdSync, extractPrefixSync } from '../../../../lib/issue-id.js';\nimport {\n listStashes,\n isSalvageableStash,\n createRecoveryBranchFromStash,\n dropStash,\n} from '../../../../lib/stashes.js';\nimport { getContainersReferencingWorkspacePath } from '../../../../lib/workspace-manager.js';\nimport { DEVCONTAINER_DIRNAME } from '../../../../lib/workspace/devcontainer-renderer.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { httpHandler } from '../http-handler.js';\nimport {\n getProjectPath,\n requireTrustedMutationOrigin,\n readJsonBody,\n spawnPanCommand,\n getWorkspaceInfoForIssue,\n} from '../workspaces.js';\n\nconst execAsync = promisify(exec);\n\nfunction resolveWorkspacePath(issueId: string): string | null {\n const info = getWorkspaceInfoForIssue(issueId);\n if (info.isRemote || !info.localPath) return null;\n return info.localPath;\n}\nconst getWorkspaceStashesRoute = HttpRouter.add(\n 'GET',\n '/api/workspaces/:issueId/stashes',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const workspacePath = resolveWorkspacePath(issueId);\n\n if (!workspacePath || !existsSync(workspacePath)) {\n return jsonResponse({ error: 'Workspace not found' }, { status: 404 });\n }\n\n const stashes = yield* listStashes(workspacePath);\n const salvageableStashes = stashes\n .filter(isSalvageableStash)\n .filter((entry) => entry.issueId === issueId.toUpperCase())\n .map((entry) => ({\n ref: entry.ref,\n stackRef: entry.stackRef,\n issueId: entry.issueId,\n message: entry.message,\n shortDescription: entry.shortDescription,\n createdAt: entry.createdAt?.toISOString(),\n }));\n\n return jsonResponse({ salvageableStashes });\n }))\n);\nconst postWorkspaceRecoverStashRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces/:issueId/stashes/:stashRef/recover',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const originError = requireTrustedMutationOrigin(request);\n if (originError) return originError;\n\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const stashRef = decodeURIComponent(params['stashRef'] ?? '');\n const workspacePath = resolveWorkspacePath(issueId);\n\n if (!workspacePath || !existsSync(workspacePath)) {\n return jsonResponse({ error: 'Workspace not found' }, { status: 404 });\n }\n\n const stashes = yield* listStashes(workspacePath);\n const stash = stashes.find((entry) => entry.ref === stashRef);\n if (!stash || !isSalvageableStash(stash) || stash.issueId !== issueId.toUpperCase()) {\n return jsonResponse({ error: 'Salvageable stash not found for this workspace' }, { status: 404 });\n }\n\n const branchName = yield* createRecoveryBranchFromStash(\n workspacePath,\n stash.ref,\n stash.issueId,\n stash.shortDescription,\n );\n\n return jsonResponse({ success: true, branchName });\n }))\n);\nconst deleteWorkspaceStashRoute = HttpRouter.add(\n 'DELETE',\n '/api/workspaces/:issueId/stashes/:stashRef',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const originError = requireTrustedMutationOrigin(request);\n if (originError) return originError;\n\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const stashRef = decodeURIComponent(params['stashRef'] ?? '');\n const workspacePath = resolveWorkspacePath(issueId);\n\n if (!workspacePath || !existsSync(workspacePath)) {\n return jsonResponse({ error: 'Workspace not found' }, { status: 404 });\n }\n\n const stashes = yield* listStashes(workspacePath);\n const stash = stashes.find((entry) => entry.ref === stashRef);\n if (!stash || !isSalvageableStash(stash) || stash.issueId !== issueId.toUpperCase()) {\n return jsonResponse({ error: 'Salvageable stash not found for this workspace' }, { status: 404 });\n }\n\n yield* dropStash(workspacePath, stash.ref);\n return jsonResponse({ success: true });\n }))\n);\n// ─── Route: GET /api/workspaces/:issueId/clean/preview ───────────────────────\n\nconst getWorkspaceCleanPreviewRoute = HttpRouter.add(\n 'GET',\n '/api/workspaces/:issueId/clean/preview',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const issueLower = issueId.toLowerCase();\n const workspaceName = `feature-${issueLower}`;\n const workspacePath = join(projectPath, 'workspaces', workspaceName);\n\n if (!existsSync(workspacePath)) {\n return jsonResponse({ error: 'Workspace does not exist' }, { status: 404 });\n }\n\n return yield* Effect.promise(async () => {\n const excludeDirs = [\n 'node_modules', 'target', 'dist', 'build', '.git', '__pycache__', '.cache', '.next', 'coverage',\n ];\n const excludePattern = excludeDirs.map(d => `-name \"${d}\" -prune`).join(' -o ');\n const findCmd = `find \"${workspacePath}\" \\\\( ${excludePattern} \\\\) -o -type f -print 2>/dev/null | head -500`;\n const { stdout: filesOutput } = await execAsync(findCmd, {\n encoding: 'utf-8',\n maxBuffer: 10 * 1024 * 1024,\n });\n const files = filesOutput.trim()\n ? filesOutput.trim().split('\\n').map(f => f.replace(workspacePath + '/', ''))\n : [];\n\n let totalSize = '0';\n try {\n const duCmd = `du -sh \"${workspacePath}\" --exclude=node_modules --exclude=target --exclude=dist --exclude=.git 2>/dev/null | cut -f1`;\n const { stdout: sizeOutput } = await execAsync(duCmd, {\n encoding: 'utf-8',\n maxBuffer: 10 * 1024 * 1024,\n });\n totalSize = sizeOutput.trim() || '0';\n } catch {\n totalSize = 'unknown';\n }\n\n const codeFiles = files.filter(f =>\n /\\.(ts|tsx|js|jsx|java|py|rs|go|rb|php|cs|swift|kt)$/.test(f)\n );\n const configFiles = files.filter(\n f => /\\.(json|yaml|yml|toml|xml|env|md)$/.test(f) || f.includes('config')\n );\n const otherFiles = files.filter(f => !codeFiles.includes(f) && !configFiles.includes(f));\n\n let diffAnalysis: {\n modifiedFiles: string[];\n newFiles: string[];\n unchangedFiles: string[];\n comparedAgainst: string;\n error?: string;\n } = { modifiedFiles: [], newFiles: [], unchangedFiles: [], comparedAgainst: 'main' };\n\n try {\n const subrepos: { prefix: string; gitRoot: string }[] = [];\n const possibleSubrepos = ['fe', 'api', 'frontend', 'backend', 'web', 'server'];\n for (const subdir of possibleSubrepos) {\n const subdirPath = join(workspacePath, subdir);\n if (existsSync(join(subdirPath, '.git'))) {\n subrepos.push({ prefix: subdir + '/', gitRoot: subdirPath });\n }\n }\n\n let mainGitRoot: string | null = null;\n const possibleRoots = [projectPath, join(projectPath, '..'), workspacePath];\n for (const root of possibleRoots) {\n if (existsSync(join(root, '.git'))) {\n mainGitRoot = root;\n break;\n }\n }\n\n const filesToCheck = codeFiles.slice(0, 100);\n const reposUsed: string[] = [];\n\n for (const file of filesToCheck) {\n const workspaceFilePath = join(workspacePath, file);\n let gitRoot: string | null = null;\n let relativePath = file;\n\n for (const { prefix, gitRoot: subGitRoot } of subrepos) {\n if (file.startsWith(prefix)) {\n gitRoot = subGitRoot;\n relativePath = file.slice(prefix.length);\n if (!reposUsed.includes(prefix)) reposUsed.push(prefix);\n break;\n }\n }\n\n if (!gitRoot && mainGitRoot) {\n gitRoot = mainGitRoot;\n if (!reposUsed.includes('main')) reposUsed.push('main');\n }\n\n if (!gitRoot) {\n diffAnalysis.newFiles.push(file);\n continue;\n }\n\n try {\n const branchName = `feature/${issueLower}`;\n let compareRef = 'main';\n try {\n await execAsync(`git rev-parse --verify ${branchName} 2>/dev/null`, {\n cwd: gitRoot,\n encoding: 'utf-8',\n maxBuffer: 10 * 1024 * 1024,\n });\n compareRef = branchName;\n } catch {\n try {\n await execAsync(`git rev-parse --verify main 2>/dev/null`, {\n cwd: gitRoot,\n encoding: 'utf-8',\n maxBuffer: 10 * 1024 * 1024,\n });\n } catch {\n compareRef = 'master';\n }\n }\n\n const { stdout: gitContent } = await execAsync(\n `git show ${compareRef}:${relativePath} 2>/dev/null`,\n { cwd: gitRoot, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 }\n );\n const workspaceContent = await readFile(workspaceFilePath, 'utf-8');\n if (gitContent === workspaceContent) {\n diffAnalysis.unchangedFiles.push(file);\n } else {\n diffAnalysis.modifiedFiles.push(file);\n }\n } catch {\n diffAnalysis.newFiles.push(file);\n }\n }\n\n diffAnalysis.comparedAgainst =\n reposUsed.length > 0 ? `${reposUsed.join(', ')} repos (main branch)` : 'main';\n\n if (subrepos.length === 0 && !mainGitRoot) {\n diffAnalysis.error = 'Could not find git repository to compare against';\n }\n } catch (diffError: unknown) {\n diffAnalysis.error = `Diff analysis failed: ${diffError instanceof Error ? diffError.message : String(diffError)}`;\n }\n\n return jsonResponse({\n workspacePath,\n totalSize,\n fileCount: files.length,\n codeFiles: codeFiles.slice(0, 50),\n configFiles: configFiles.slice(0, 30),\n otherFiles: otherFiles.slice(0, 20),\n hasMore: files.length > 100,\n backupPath: join(\n projectPath,\n 'workspaces',\n `.backup-${workspaceName}-${Date.now()}`\n ),\n diffAnalysis,\n });\n });\n }))\n);\n// ─── Route: POST /api/workspaces/:issueId/clean ───────────────────────────────\n\nconst postWorkspaceCleanRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces/:issueId/clean',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const body = yield* readJsonBody;\n const { createBackup } = body as { createBackup?: boolean };\n\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const issueLower = issueId.toLowerCase();\n const workspaceName = `feature-${issueLower}`;\n const workspacePath = join(projectPath, 'workspaces', workspaceName);\n\n if (!existsSync(workspacePath)) {\n return jsonResponse({ error: 'Workspace does not exist' }, { status: 404 });\n }\n\n let backupPath: string | null = null;\n\n if (createBackup) {\n backupPath = join(\n projectPath,\n 'workspaces',\n `.backup-${workspaceName}-${Date.now()}`\n );\n console.log(`Creating backup: ${workspacePath} -> ${backupPath}`);\n yield* Effect.promise(() => execAsync(\n `rsync -a --quiet --exclude=node_modules --exclude=target --exclude=dist --exclude=.git --exclude=__pycache__ --exclude=.cache --exclude=.next --exclude=coverage \"${workspacePath}/\" \"${backupPath}/\"`,\n { encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024 }\n ));\n }\n\n console.log(`Removing corrupted workspace: ${workspacePath}`);\n\n // Guard: never delete workspace while containers still reference its compose path\n const orphanedContainers = yield* Effect.promise(() =>\n getContainersReferencingWorkspacePath(workspacePath)\n );\n if (orphanedContainers.length > 0) {\n return jsonResponse(\n {\n error: `Cannot remove workspace: ${orphanedContainers.length} Docker container(s) still reference compose paths in ${DEVCONTAINER_DIRNAME}/. Stop the containers first.`,\n },\n { status: 409 },\n );\n }\n\n try {\n yield* Effect.promise(() => execAsync(`rm -rf \"${workspacePath}\"`, {\n encoding: 'utf-8',\n maxBuffer: 50 * 1024 * 1024,\n }));\n } catch {\n console.log('Regular rm failed, using Docker to clean up root-owned files...');\n yield* Effect.promise(() => execAsync(\n `docker run --rm -v \"${workspacePath}:/cleanup\" alpine sh -c \"rm -rf /cleanup/* /cleanup/.[!.]* /cleanup/..?* 2>/dev/null || true\"`,\n { encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024 }\n ));\n yield* Effect.promise(() => execAsync(`rmdir \"${workspacePath}\"`, { encoding: 'utf-8' }));\n }\n\n const activityId = spawnPanCommand(\n ['workspace', 'create', issueId],\n `Recreate workspace for ${issueId}`,\n projectPath\n );\n\n return jsonResponse({\n success: true,\n message: createBackup\n ? `Backed up to ${backupPath} and recreating workspace for ${issueId}`\n : `Cleaned corrupted workspace and recreating for ${issueId}`,\n activityId,\n projectPath,\n backupPath,\n });\n }))\n);\n\nexport const stashCleanRouteLayer = Layer.mergeAll(\n getWorkspaceStashesRoute,\n postWorkspaceRecoverStashRoute,\n deleteWorkspaceStashRoute,\n getWorkspaceCleanPreviewRoute,\n postWorkspaceCleanRoute,\n);\n\nexport default stashCleanRouteLayer;\n","/**\n * Review-pipeline route module — extracted from routes/workspaces.ts (B / wave 2, seam 3a).\n *\n * Review dispatch endpoints (start / re-run the review+test pipeline):\n * POST /api/review/:issueId/trigger\n * POST /api/review/:issueId/request\n *\n * The cancel/control routes (reset, purge, abort, pending, unstick,\n * deacon-ignore, auto-merge) live in review-control.ts. Shared singletons\n * (review-status wrapper, pending-ops cluster, project path, readJsonBody,\n * workspace info, flyExecCmd) stay owned by ../workspaces.js.\n */\n\nimport { exec } from 'node:child_process';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { Effect, Layer, Option } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\n\nimport { parseIssueIdSync, extractPrefixSync } from '../../../../lib/issue-id.js';\nimport { resolveProjectFromIssueSync } from '../../../../lib/projects.js';\nimport { isStatePlaneOnlyStatus } from '../../../../lib/state-plane.js';\nimport { EventStoreService } from '../../services/domain-services.js';\nimport { getReviewStatusSync, type ReviewStatus } from '../../../../lib/review-status.js';\nimport { getCachedConflictGateMergeability } from '../../../../lib/cloister/conflict-gate.js';\nimport { restoreTrackedBeadsExport } from '../../../../lib/beads-restore.js';\nimport { transitionIssueToInReview, spawnRun } from '../../../../lib/agents.js';\nimport { runVerificationForIssue } from '../../../../lib/cloister/verification-runner.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { httpHandler } from '../http-handler.js';\nimport {\n getProjectPath,\n readJsonBody,\n getWorkspaceInfoForIssue,\n setReviewStatus,\n setPendingOperation,\n completePendingOperation,\n clearPendingOperation,\n flyExecCmd,\n type WorkspaceInfo,\n} from '../workspaces.js';\n\nconst execAsync = promisify(exec);\nconst MAX_AUTO_REQUEUE = 25;\n\n/** Safe `.message` read for caught values of unknown shape. */\nconst errorMessage = (e: unknown): string | undefined => e instanceof Error ? e.message : undefined;\n\nfunction shouldTreatAsRerun(status: Pick<ReviewStatus, 'readyForMerge' | 'reviewStatus' | 'testStatus' | 'mergeStatus'> | null | undefined): boolean {\n if (!status) return false;\n return status.readyForMerge === true\n || status.reviewStatus === 'passed'\n || status.testStatus === 'passed'\n || status.mergeStatus === 'failed';\n}\n\nexport function getDirtyWorkspaceErrorForReviewRequestStatus(\n status: string,\n workspacePath: string,\n): string | null {\n // STATE-PLANE-COMMIT-POLICY rules 3/6: pipeline-owned state-plane dirt is\n // not agent work, and the path list must come from src/lib/state-plane.ts.\n if (!status.trim() || isStatePlaneOnlyStatus(status)) {\n return null;\n }\n\n return `Workspace has uncommitted changes. Commit the changes, explicitly discard them, or surface them to the operator before requesting review:\\ncd ${workspacePath}\\ngit status`;\n}\n\nasync function getDirtyWorkspaceErrorForReviewRequest(\n workspacePath: string,\n workspaceInfo: WorkspaceInfo,\n): Promise<string | null> {\n try {\n if (!workspaceInfo.isRemote) {\n await Effect.runPromise(restoreTrackedBeadsExport(workspacePath));\n }\n\n const statusCmd = 'git status --porcelain -uno';\n const status = workspaceInfo.isRemote && workspaceInfo.vmName\n ? (await execAsync(\n flyExecCmd(workspaceInfo.vmName, `cd ${workspacePath} && ${statusCmd}`),\n { encoding: 'utf-8', timeout: 30000 },\n )).stdout\n : (await execAsync(statusCmd, { cwd: workspacePath, encoding: 'utf-8' })).stdout;\n\n return getDirtyWorkspaceErrorForReviewRequestStatus(status, workspacePath);\n } catch {\n return null;\n }\n}\n// ─── Route: POST /api/review/:issueId/trigger ─────────────────────────────\n\nconst postWorkspaceReviewRoute = HttpRouter.add(\n 'POST',\n '/api/review/:issueId/trigger',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const request = yield* HttpServerRequest.HttpServerRequest;\n const body = yield* readJsonBody;\n const eventStore = yield* EventStoreService;\n\n const urlOpt = HttpServerRequest.toURL(request);\n const forceReview =\n (Option.isSome(urlOpt) && urlOpt.value.searchParams.get('force') === 'true') ||\n (body as { force?: unknown })?.force === true;\n\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const issueLower = issueId.toLowerCase();\n const numericSuffix = issueLower.replace(/^[a-z]+-/, '');\n // Use numeric-suffix form (feature/1034) as canonical branch name\n const branchName = `feature/${numericSuffix}`;\n\n const workspaceInfo = getWorkspaceInfoForIssue(issueId);\n // Review runs against the local worktree only. Remote (fly.io) workspaces\n // exist for the work phase; the reap flow retires them and materializes\n // the local worktree before review (PAN-1676).\n if (workspaceInfo.isRemote) {\n return jsonResponse({\n error: `${issueId} is executing remotely on ${workspaceInfo.vmName ?? 'a fly machine'} — run 'pan admin remote reap --issue ${issueId}' after the agent finishes, then review.`,\n }, { status: 409 });\n }\n const workspacePath = workspaceInfo.localPath || join(projectPath, 'workspaces', `feature-${numericSuffix}`);\n\n const existingStatus = getReviewStatusSync(issueId);\n\n if (existingStatus?.reviewNotes && ['blocked', 'failed'].includes(existingStatus.reviewStatus || '')) {\n const infraFailurePatterns = [\n 'Failed to send task',\n \"can't find pane\",\n 'Command failed: tmux',\n 'Operation timed out',\n 'specialist.*not running',\n 'specialist.*busy',\n 'legacy specialist wake',\n ];\n const isInfraFailure = infraFailurePatterns.some(pattern =>\n new RegExp(pattern, 'i').test(existingStatus.reviewNotes || '')\n );\n\n if (!isInfraFailure && !forceReview) {\n return jsonResponse({\n success: false,\n alreadyReviewed: true,\n message: `Review already completed with status: ${existingStatus.reviewStatus}`,\n reviewNotes: existingStatus.reviewNotes,\n hint: 'Address the review feedback before requesting another review, or use force=true to override',\n });\n }\n\n console.log(\n `[review] Re-triggering review for ${issueId} (${isInfraFailure ? 'infrastructure failure' : 'forced'})`\n );\n }\n\n if (existingStatus?.reviewStatus === 'passed' && !forceReview) {\n console.log(`[review] Skipping ${issueId}: already passed review`);\n return jsonResponse({\n success: false,\n alreadyReviewed: true,\n message: `Review already passed for ${issueId}`,\n hint: 'Issue already passed review — proceed to testing or merge',\n });\n }\n\n if (existingStatus?.mergeStatus === 'merged') {\n console.log(`[review] Skipping ${issueId}: already merged`);\n return jsonResponse({\n success: false,\n alreadyMerged: true,\n message: `${issueId} is already merged`,\n });\n }\n\n if (!workspaceInfo.exists) {\n return jsonResponse({ error: 'Workspace does not exist' }, { status: 400 });\n }\n\n // Reset review status — keep 'pending' until dispatch succeeds (PAN-511 atomicity fix).\n // reviewStatus is set to 'reviewing' only after the specialist is successfully dispatched\n // or queued, not before. This prevents stuck 'reviewing' state if Cloister crashes mid-dispatch.\n setPendingOperation(issueId, 'review');\n const reviewReset: Record<string, unknown> = {\n reviewStatus: 'pending',\n testStatus: 'pending',\n autoRequeueCount: 0,\n verificationCycleCount: 0,\n verificationStatus: 'pending',\n verificationNotes: undefined,\n };\n if (forceReview) {\n reviewReset.readyForMerge = false;\n reviewReset.mergeStatus = 'pending';\n reviewReset.reviewNotes = undefined;\n reviewReset.testNotes = undefined;\n }\n setReviewStatus(issueId, reviewReset);\n\n // PAN-1765: short-circuit conflict-gated dispatches before responding so the\n // HTTP client gets a 409 with the deferral message instead of a false 200.\n // Use only the synchronous probe cache here: if a fresh cached result says\n // the branch is not mergeable, return 409 immediately. When the cache is\n // absent/stale, fall through to the background block below, which runs the\n // async probe inside spawnReviewRoleForIssue without holding the HTTP response.\n const cachedMergeability = getCachedConflictGateMergeability(issueId);\n if (cachedMergeability === 'conflicts' || cachedMergeability === 'unknown') {\n const message = cachedMergeability === 'conflicts'\n ? `Review deferred: merge conflict with main must be resolved before review dispatch`\n : `Review deferred: mergeability against main could not be verified; deferring review conservatively`;\n setReviewStatus(issueId, { reviewStatus: 'pending', reviewNotes: message });\n completePendingOperation(issueId, message);\n return jsonResponse({\n success: false,\n gated: true,\n message,\n pipeline: 'deferred',\n }, { status: 409 });\n }\n\n // Respond immediately\n // Run pipeline in background\n (async () => {\n try {\n transitionIssueToInReview(issueId, workspacePath).catch((err: unknown) => {\n console.warn(`[review] Could not transition ${issueId} to in_review: ${errorMessage(err)}`);\n });\n\n try {\n await execAsync(`git push origin ${branchName}`, {\n cwd: workspacePath,\n encoding: 'utf-8',\n });\n } catch (pushErr: unknown) {\n console.log(`Feature branch push note: ${errorMessage(pushErr)}`);\n }\n\n\t // Ensure review artifacts exist so review/test agents have stable URLs.\n\t let reviewTargetBranch: string | undefined;\n\t try {\n\t const { createReviewArtifactsForIssue } = await import('../../../../lib/review-artifacts.js');\n\t const artifactResult = await Effect.runPromise(createReviewArtifactsForIssue(issueId, workspacePath));\n\t const primaryArtifact = artifactResult.mergeSet?.repos.find(repo => !!repo.artifactUrl);\n\t reviewTargetBranch = artifactResult.mergeSet?.repos.find(repo => repo.mergeStatus !== 'skipped')?.targetBranch;\n\t if (primaryArtifact?.artifactUrl) {\n\t setReviewStatus(issueId, { prUrl: primaryArtifact.artifactUrl });\n\t console.log(`[review] Review artifact ready for ${issueId}: ${primaryArtifact.artifactUrl}`);\n\t } else {\n\t console.warn(`[review] No review artifact URL available for ${issueId}`);\n\t }\n\t } catch (artifactErr: unknown) {\n\t console.warn(`[review] Review artifact creation failed for ${issueId}: ${errorMessage(artifactErr)}`);\n\t }\n\n try {\n (await Effect.runPromise(eventStore.append({\n type: 'pipeline.verification-started',\n timestamp: new Date().toISOString(),\n payload: { issueId },\n })));\n } catch { /* non-fatal */ }\n\n const verifyOutcome = await Effect.runPromise(runVerificationForIssue(\n issueId,\n workspacePath,\n workspaceInfo,\n 'review'\n ));\n if (verifyOutcome.outcome === 'failed') {\n completePendingOperation(\n issueId,\n `Verification failed at ${verifyOutcome.failedCheck}`\n );\n setReviewStatus(issueId, {\n reviewStatus: 'failed',\n reviewNotes: `Verification failed at ${verifyOutcome.failedCheck}`,\n });\n try {\n (await Effect.runPromise(eventStore.append({\n type: 'pipeline.verification-failed',\n timestamp: new Date().toISOString(),\n payload: { issueId, failedCheck: verifyOutcome.failedCheck },\n })));\n } catch { /* non-fatal */ }\n return;\n }\n if (verifyOutcome.outcome === 'error') {\n completePendingOperation(\n issueId,\n `Verification infrastructure error: ${verifyOutcome.message}`\n );\n setReviewStatus(issueId, {\n reviewStatus: 'failed',\n reviewNotes: `Verification error: ${verifyOutcome.message}`,\n });\n try {\n (await Effect.runPromise(eventStore.append({\n type: 'pipeline.verification-failed',\n timestamp: new Date().toISOString(),\n payload: { issueId, message: verifyOutcome.message },\n })));\n } catch { /* non-fatal */ }\n return;\n }\n\n // PAN-1048 C1/R3: review now runs as the role primitive via spawnRun\n // (loads roles/review.md → Agent tool fans out to code-review-* sub-agents).\n // The wrapper preserves dispatchParallelReview's orchestration concerns\n // (idempotency, feedback archive, status flip,\n // pipeline event) but the review itself is no longer a detached\n // `pan review run` coordinator process.\n const { spawnReviewRoleForIssue } = await import('../../../../lib/cloister/review-agent.js');\n const prUrl = getReviewStatusSync(issueId)?.prUrl;\n const reviewResult = await Effect.runPromise(spawnReviewRoleForIssue({\n issueId,\n branch: branchName,\n workspace: workspacePath,\n prUrl,\n force: forceReview,\n }));\n\n if (!reviewResult.success) {\n if (reviewResult.gated) {\n console.log(`[review] review dispatch deferred for ${issueId}: ${reviewResult.message}`);\n completePendingOperation(issueId, reviewResult.message);\n setReviewStatus(issueId, {\n reviewStatus: 'pending',\n reviewNotes: reviewResult.message,\n });\n return;\n }\n\n console.warn(\n `[review] review dispatch failed: ${reviewResult.message}`\n );\n completePendingOperation(issueId, `Failed to start review: ${reviewResult.message}`);\n setReviewStatus(issueId, {\n reviewStatus: 'pending',\n reviewNotes: reviewResult.message,\n });\n return;\n }\n\n console.log(`[review] Parallel review dispatched for ${issueId}`);\n // PAN-511's \"set 'reviewing' only after dispatch succeeds\" invariant now lives\n // inside spawnReviewRoleForIssue (it writes reviewing + reviewSpawnedAt right\n // before spawning). PAN-2578: do NOT repeat a bare 'reviewing' write here — a\n // fast review agent may have already recorded its verdict by the time this line\n // runs, and the redundant write clobbered PAN-399's BLOCKED verdict.\n completePendingOperation(issueId, null);\n try {\n (await Effect.runPromise(eventStore.append({\n type: 'pipeline.review-started',\n timestamp: new Date().toISOString(),\n payload: { issueId },\n })));\n } catch { /* non-fatal */ }\n } catch (error: unknown) {\n console.error(`[review] Error starting review:`, error);\n completePendingOperation(issueId, errorMessage(error));\n setReviewStatus(issueId, { reviewStatus: 'pending', reviewNotes: errorMessage(error) });\n }\n })();\n\n return jsonResponse({\n success: true,\n message: `Review pipeline starting for ${issueId}`,\n pipeline: 'verification → review → test',\n note: 'Watch the status panel for progress.',\n });\n }))\n);\n// ─── Route: POST /api/review/:issueId/request ─────────────────────\n\nconst postWorkspaceRequestReviewRoute = HttpRouter.add(\n 'POST',\n '/api/review/:issueId/request',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n const parsedIssueId = parseIssueIdSync(issueId);\n if (!parsedIssueId) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const canonicalIssueId = issueId.toUpperCase();\n const request = yield* HttpServerRequest.HttpServerRequest;\n const body = yield* readJsonBody;\n const { message } = body as { message?: string };\n const eventStore = yield* EventStoreService;\n\n const urlOpt = HttpServerRequest.toURL(request);\n const forceReview =\n (Option.isSome(urlOpt) && urlOpt.value.searchParams.get('force') === 'true') ||\n (body as { force?: unknown })?.force === true;\n const nudgeReview =\n (Option.isSome(urlOpt) && urlOpt.value.searchParams.get('nudge') === 'true') ||\n (body as { nudge?: unknown })?.nudge === true;\n\n const existingStatus = getReviewStatusSync(issueId);\n\n if (existingStatus?.mergeStatus === 'merged') {\n console.log(`[request-review] Rejecting ${issueId}: already merged`);\n return jsonResponse({\n success: false,\n alreadyMerged: true,\n message: `${issueId} is already merged. Use Reopen or Reset Reviews first.`,\n });\n }\n\n if (existingStatus?.reviewStatus === 'passed') {\n if (forceReview) {\n console.log(`[request-review] FORCE: full reset requested by operator for ${canonicalIssueId}`);\n } else if (nudgeReview) {\n if (existingStatus.testStatus !== 'passed') {\n return jsonResponse(\n {\n success: false,\n error: 'Cannot nudge — tests have not passed',\n hint: 'Use ?force=true for a full re-review or wait for tests to complete',\n },\n { status: 400 },\n );\n }\n\n console.log(`[request-review] NUDGE: re-emitting test.passed for ${canonicalIssueId} without state reset`);\n yield* Effect.promise(() => Effect.runPromise(eventStore.append({\n type: 'test.passed',\n timestamp: new Date().toISOString(),\n payload: { issueId: canonicalIssueId },\n })));\n return jsonResponse({\n success: true,\n nudged: true,\n message: `Re-emitted test.passed for ${canonicalIssueId}`,\n });\n }\n\n if (forceReview && shouldTreatAsRerun(existingStatus)) {\n const issueLowerRerun = canonicalIssueId.toLowerCase();\n const issuePrefixRerun = extractPrefixSync(canonicalIssueId) ?? canonicalIssueId.split('-')[0];\n const projectPathRerun = getProjectPath(undefined, issuePrefixRerun);\n const wsInfoRerun = getWorkspaceInfoForIssue(canonicalIssueId);\n // Review runs against the local worktree only (PAN-1676) — see the\n // matching guard in /api/review/:issueId/trigger.\n if (wsInfoRerun.isRemote) {\n return jsonResponse({\n success: false,\n message: `${canonicalIssueId} is executing remotely on ${wsInfoRerun.vmName ?? 'a fly machine'} — run 'pan admin remote reap --issue ${canonicalIssueId}' after the agent finishes, then review.`,\n }, { status: 409 });\n }\n const workspacePathRerun = wsInfoRerun.localPath || join(projectPathRerun, 'workspaces', `feature-${issueLowerRerun}`);\n\n if (!wsInfoRerun.isRemote) {\n yield* restoreTrackedBeadsExport(workspacePathRerun);\n }\n const dirtyError = yield* Effect.promise(() => getDirtyWorkspaceErrorForReviewRequest(workspacePathRerun, wsInfoRerun));\n if (dirtyError) {\n console.log(`[request-review] Rejecting ${issueId}: dirty workspace on rerun path`);\n return jsonResponse({ success: false, error: dirtyError }, { status: 400 });\n }\n\n console.log(`[request-review] ${issueId}: forcing full review/test rerun from passed state`);\n setPendingOperation(issueId, 'review');\n setReviewStatus(issueId, {\n reviewStatus: 'pending',\n testStatus: 'pending',\n mergeStatus: 'pending',\n readyForMerge: false,\n autoRequeueCount: 0,\n verificationCycleCount: 0,\n verificationStatus: 'pending',\n verificationNotes: undefined,\n reviewNotes: undefined,\n testNotes: undefined,\n mergeNotes: undefined,\n });\n\n (async () => {\n try {\n // Resolve workspace info locally — outer scope vars (workspacePath, branchName)\n // are declared after the early return below and must not be relied on here.\n const branchNameRerun = `feature/${issueLowerRerun}`;\n\n transitionIssueToInReview(issueId, workspacePathRerun).catch((err: unknown) => {\n console.warn(`[request-review] Could not transition ${issueId} to in_review: ${errorMessage(err)}`);\n });\n\n try {\n await execAsync(`git push origin ${branchNameRerun}`, {\n cwd: workspacePathRerun,\n encoding: 'utf-8',\n });\n } catch (pushErr: unknown) {\n console.log(`[request-review] Feature branch push note: ${errorMessage(pushErr)}`);\n }\n\n const prUrl = getReviewStatusSync(issueId)?.prUrl;\n const { spawnReviewRoleForIssue } = await import('../../../../lib/cloister/review-agent.js');\n const result = await Effect.runPromise(spawnReviewRoleForIssue({\n issueId,\n workspace: workspacePathRerun,\n branch: branchNameRerun,\n prUrl,\n force: true,\n }));\n\n if (result.success) {\n // reviewStatus transitions ('reviewing' → passed/blocked/failed) are\n // managed by the review role itself via /api/review/:id/status.\n console.log(`[request-review] Review role spawned for ${issueId}`);\n } else if (result.gated) {\n console.log(`[request-review] Review deferred for ${issueId}: ${result.message}`);\n setReviewStatus(issueId, { reviewStatus: 'pending', reviewNotes: result.message });\n } else {\n const errorMsg = result.error || result.message || 'Failed to dispatch review';\n console.error(`[request-review] Dispatch failed for ${issueId}: ${errorMsg}`);\n setReviewStatus(issueId, { reviewStatus: 'pending', reviewNotes: errorMsg });\n }\n } catch (error: unknown) {\n console.error(`[request-review] Error:`, error);\n setReviewStatus(issueId, {\n reviewStatus: 'pending',\n reviewNotes: errorMessage(error) || 'Unknown error',\n });\n }\n })();\n\n return jsonResponse({\n success: true,\n rerun: true,\n message: `Re-running review & test pipeline for ${issueId}`,\n });\n }\n\n if (existingStatus.testStatus === 'failed' || existingStatus.testStatus === 'pending' || existingStatus.testStatus === 'dispatch_failed') {\n console.log(\n `[request-review] ${issueId}: review passed but tests ${existingStatus.testStatus} — dispatching test role`\n );\n setReviewStatus(issueId, { testStatus: 'pending' });\n\n try {\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) {\n console.error(\n `[request-review] No project configured for ${issueId} — cannot spawn test role`\n );\n setReviewStatus(issueId, {\n testStatus: 'dispatch_failed',\n testNotes: 'No project configured',\n });\n } else {\n const workspacePath = join(\n resolved.projectPath,\n 'workspaces',\n `feature-${issueId.toLowerCase()}`\n );\n // PAN-1048 R1: spawn the test role via the role primitive instead\n // of the legacy spawnEphemeralSpecialist machinery. Reactive\n // Cloister normally drives this on lifecycle transitions; this\n // path is a manual re-dispatch for already-approved reviews.\n // PAN-2579: set 'testing' only AFTER the spawn succeeds — a pre-set\n // non-terminal status would make spawnRun's warm-idle reaper treat a\n // leftover warm test session as active and refuse the dispatch.\n const { spawnRun } = yield* Effect.promise(() => import('../../../../lib/agents.js'));\n try {\n const testRun = yield* Effect.promise(() => spawnRun(issueId, 'test', {\n workspace: workspacePath,\n }));\n setReviewStatus(issueId, { testStatus: 'testing' });\n console.log(\n `[request-review] Test role spawned for ${issueId} as ${testRun.id}`\n );\n } catch (testErr) {\n const msg = testErr instanceof Error ? testErr.message : String(testErr);\n console.error(\n `[request-review] Test role spawn failed for ${issueId}: ${msg}`\n );\n setReviewStatus(issueId, {\n testStatus: 'dispatch_failed',\n testNotes: `Test dispatch failed: ${msg}`,\n });\n }\n }\n } catch (err: unknown) {\n console.warn(\n `[request-review] Failed to queue test role for ${issueId}: ${errorMessage(err)}`\n );\n }\n return jsonResponse({\n success: true,\n requeued: true,\n message: `Tests re-queued for ${issueId} (review already passed)`,\n });\n }\n console.log(\n `[request-review] ${issueId}: review already passed — returning success no-op`\n );\n return jsonResponse({\n success: true,\n alreadyPassed: true,\n message: `Review already passed for ${issueId}`,\n });\n }\n\n const currentCount = existingStatus?.autoRequeueCount || 0;\n\n if (currentCount >= MAX_AUTO_REQUEUE) {\n console.log(\n `[request-review] Circuit breaker: ${issueId} exceeded max auto-requeues (${currentCount}/${MAX_AUTO_REQUEUE})`\n );\n return jsonResponse(\n {\n success: false,\n error: 'Circuit breaker triggered',\n message: `Maximum automatic re-review requests (${MAX_AUTO_REQUEUE}) exceeded. Human intervention required.`,\n autoRequeueCount: currentCount,\n hint: 'A human must click the Review button to continue.',\n },\n { status: 429 }\n );\n }\n\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const issueLower = issueId.toLowerCase();\n const branchName = `feature/${issueLower}`;\n\n const workspaceInfo = getWorkspaceInfoForIssue(issueId);\n const workspacePath = workspaceInfo.isRemote\n ? workspaceInfo.remotePath!\n : workspaceInfo.localPath || join(projectPath, 'workspaces', `feature-${issueLower}`);\n\n if (!workspaceInfo.exists) {\n return jsonResponse(\n { success: false, error: 'Workspace does not exist' },\n { status: 400 }\n );\n }\n\n if (!workspaceInfo.isRemote) {\n yield* restoreTrackedBeadsExport(workspacePath);\n }\n\n const dirtyWorkspaceError = yield* Effect.promise(() => getDirtyWorkspaceErrorForReviewRequest(workspacePath, workspaceInfo));\n if (dirtyWorkspaceError) {\n return jsonResponse(\n { success: false, error: dirtyWorkspaceError },\n { status: 400 }\n );\n }\n\n transitionIssueToInReview(issueId, workspacePath).catch((err: unknown) => {\n console.warn(\n `[request-review] Could not transition ${issueId} to in_review: ${errorMessage(err)}`\n );\n });\n\n const newCount = currentCount + 1;\n const reviewNotes = message\n ? `Agent re-review request (${newCount}/${MAX_AUTO_REQUEUE}): ${message}`\n : undefined;\n\n const reqVerifyOutcome = yield* runVerificationForIssue(\n issueId,\n workspacePath,\n workspaceInfo,\n 'request-review'\n );\n if (reqVerifyOutcome.outcome === 'failed') {\n return jsonResponse({\n success: false,\n verificationFailed: true,\n failedCheck: reqVerifyOutcome.failedCheck,\n message: `Verification failed at ${reqVerifyOutcome.failedCheck} — fix and resubmit`,\n cycleCount: reqVerifyOutcome.cycleCount,\n maxCycles: reqVerifyOutcome.maxCycles,\n });\n }\n if (reqVerifyOutcome.outcome === 'error') {\n return jsonResponse(\n {\n success: false,\n error: `Verification infrastructure error: ${reqVerifyOutcome.message}`,\n autoRequeueCount: currentCount,\n },\n { status: 500 }\n );\n }\n\n // PAN-511: set metadata fields but keep reviewStatus='pending' until dispatch succeeds.\n // reviewStatus is set to 'reviewing' only after specialist is dispatched or queued.\n setReviewStatus(issueId, {\n reviewStatus: 'pending',\n testStatus: 'pending',\n autoRequeueCount: newCount,\n reviewNotes,\n });\n\n console.log(\n `[request-review] Agent requested re-review for ${issueId} (${newCount}/${MAX_AUTO_REQUEUE})${workspaceInfo.isRemote ? ` (remote: ${workspaceInfo.vmName})` : ''}`\n );\n\n try {\n const resolved = resolveProjectFromIssueSync(issueId);\n\n if (!resolved) {\n return jsonResponse(\n {\n success: false,\n error: `No project configured for ${issueId}. Add it to projects.yaml.`,\n autoRequeueCount: newCount,\n },\n { status: 500 }\n );\n }\n\n const result = yield* Effect.promise(async () => {\n const { spawnReviewRoleForIssue } = await import('../../../../lib/cloister/review-agent.js');\n return (await Effect.runPromise(spawnReviewRoleForIssue({\n issueId,\n workspace: workspacePath,\n branch: branchName,\n force: true,\n })));\n });\n\n if (result.success) {\n console.log(`[request-review] Review role spawned for ${issueId}`);\n // spawnReviewRoleForIssue already flips reviewStatus to 'reviewing' (with\n // reviewSpawnedAt) internally. PAN-2578: do not repeat a bare 'reviewing' write —\n // it can clobber a fast agent's terminal verdict. Only record the requeue count.\n setReviewStatus(issueId, { autoRequeueCount: newCount });\n yield* Effect.promise(() => Effect.runPromise(eventStore.append({\n type: 'pipeline.review-started',\n timestamp: new Date().toISOString(),\n payload: { issueId },\n })));\n return jsonResponse({\n success: true,\n queued: false,\n message: `Review started (${newCount}/${MAX_AUTO_REQUEUE} auto-requeues used)`,\n autoRequeueCount: newCount,\n remainingRequeues: MAX_AUTO_REQUEUE - newCount,\n });\n } else if (result.gated) {\n console.log(`[request-review] Review deferred for ${issueId}: ${result.message}`);\n setReviewStatus(issueId, {\n reviewStatus: 'pending',\n reviewNotes: result.message,\n autoRequeueCount: currentCount,\n });\n return jsonResponse(\n {\n success: false,\n gated: true,\n message: result.message,\n autoRequeueCount: currentCount,\n remainingRequeues: MAX_AUTO_REQUEUE - currentCount,\n },\n { status: 409 }\n );\n } else {\n console.warn(\n `[request-review] Dispatch failed for ${issueId}: ${result.error}`\n );\n setReviewStatus(issueId, {\n reviewStatus: 'pending',\n reviewNotes: `Dispatch failed: ${result.error || result.message}`,\n autoRequeueCount: currentCount,\n });\n return jsonResponse(\n {\n success: false,\n error: result.error || 'Failed to dispatch review',\n autoRequeueCount: currentCount,\n remainingRequeues: MAX_AUTO_REQUEUE - currentCount,\n },\n { status: 500 }\n );\n }\n } catch (error: unknown) {\n console.error(`[request-review] Error:`, error);\n setReviewStatus(issueId, {\n reviewStatus: 'pending',\n reviewNotes: `Dispatch error: ${errorMessage(error)}`,\n });\n return jsonResponse(\n { success: false, error: errorMessage(error), autoRequeueCount: newCount },\n { status: 500 }\n );\n }\n }))\n);\n\nexport const reviewPipelineRouteLayer = Layer.mergeAll(\n postWorkspaceReviewRoute,\n postWorkspaceRequestReviewRoute,\n);\n\nexport default reviewPipelineRouteLayer;\n","/**\n * Review-control route module — extracted from routes/workspaces.ts (B / wave 2, seam 3b).\n *\n * Operator control endpoints over the review/worker lifecycle:\n * POST /api/review/:issueId/reset\n * POST /api/review/:issueId/purge\n * POST /api/review/:issueId/abort\n * DELETE /api/review/:issueId/pending\n * POST /api/workspaces/:issueId/unstick\n * POST /api/workspaces/:issueId/deacon-ignore\n * POST /api/workspaces/:issueId/auto-merge\n *\n * The dispatch routes (trigger, request) live in review-pipeline.ts. Shared\n * singletons (review-status wrapper, project path, readJsonBody, workspace info)\n * stay owned by ../workspaces.js.\n */\n\nimport { exec } from 'node:child_process';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { Effect, Layer } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\n\nimport { parseIssueIdSync, extractPrefixSync } from '../../../../lib/issue-id.js';\nimport { findWorkspacePath } from '../../../../lib/lifecycle/archive-planning.js';\nimport { resolveProjectFromIssueSync } from '../../../../lib/projects.js';\nimport {\n getReviewStatusSync,\n setReviewStatusSync as setReviewStatusBase,\n markWorkspaceStuck,\n setDeaconIgnored,\n setAutoMerge,\n} from '../../../../lib/review-status.js';\nimport { getAgentRuntimeStateSync } from '../../../../lib/agents.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { httpHandler } from '../http-handler.js';\nimport {\n getProjectPath,\n readJsonBody,\n getWorkspaceInfoForIssue,\n setReviewStatus,\n requireTrustedMutationOrigin,\n} from '../workspaces.js';\n\nconst execAsync = promisify(exec);\n\nexport type ResetReviewResult =\n | { httpStatus: 400; body: { success: false; error: string } }\n | {\n httpStatus: 200;\n body: {\n success: true;\n /** Work-agent runtime state observed before the reset — informational, logged for debugging. */\n preservedResolution?: { agentId: string; resolution?: string; resolutionCount?: number };\n };\n };\n\n/**\n * Core logic for POST /api/review/:issueId/reset (synchronous, testable).\n *\n * Resets the specialist pipeline state (review / test / merge / verification) for\n * a workspace. Writes ONE setReviewStatus() call, reads the work-agent's runtime\n * state purely for logging, and returns a structured result that the route\n * handler maps to an HTTP response.\n *\n * CRITICAL — resolution preservation:\n * This function deliberately does NOT mutate the work-agent's runtime state\n * (resolution / resolutionCount / activity). `resolution` tracks the WORK\n * agent's own lifecycle (working/done/unclear/stuck/needs_input) and is written\n * exclusively by `work-agent-stop-hook` based on the agent's tail. The pipeline\n * reset is about specialist state — wiping resolution here previously erased\n * legitimate unclear/stuck counts when `pan done`'s self-heal path triggered a\n * reset, which prevented the deacon from noticing genuinely confused agents.\n * (Root cause of PAN-805 never escalating to stuck.)\n *\n * Regression test: tests/unit/dashboard/server/routes/reset-review-route.test.ts\n *\n * Exported so a unit test can assert the sync mutation set is exactly\n * {reset review status} — nothing else.\n */\nexport function processResetReviewPipeline(\n issueId: string,\n workspaceExists: boolean,\n): ResetReviewResult {\n if (!workspaceExists) {\n return {\n httpStatus: 400,\n body: { success: false, error: 'Workspace does not exist' },\n };\n }\n\n const agentId = `agent-${issueId.toLowerCase()}`;\n const priorRuntime = getAgentRuntimeStateSync(agentId);\n\n console.log(\n `[reset-review] Human-initiated pipeline reset for ${issueId} ` +\n `(work-agent ${agentId} resolution=${priorRuntime?.resolution ?? 'none'}/` +\n `${priorRuntime?.resolutionCount ?? 0} — preserved, not reset)`\n );\n\n setReviewStatus(issueId, {\n reviewStatus: 'pending',\n testStatus: 'pending',\n mergeStatus: 'pending',\n reviewNotes: undefined,\n testNotes: undefined,\n mergeNotes: undefined,\n readyForMerge: false,\n autoRequeueCount: 0,\n verificationStatus: 'pending',\n verificationNotes: undefined,\n verificationCycleCount: 0,\n // A human-initiated reset is an explicit circuit-breaker override: clear\n // the stuck marker and the review/test retry counters too. Without this,\n // a workspace stuck on review_infrastructure_failure (or with exhausted\n // retry budgets) is reset to `pending` but immediately re-skipped by the\n // deacon's stuck guard / retry-budget checks — the \"override\" is a no-op.\n stuck: false,\n stuckReason: undefined,\n stuckAt: undefined,\n stuckDetails: undefined,\n reviewRetryCount: 0,\n testRetryCount: 0,\n mergeRetryCount: 0,\n recoveryStartedAt: undefined,\n });\n\n return {\n httpStatus: 200,\n body: {\n success: true,\n preservedResolution: priorRuntime\n ? {\n agentId,\n resolution: priorRuntime.resolution,\n resolutionCount: priorRuntime.resolutionCount,\n }\n : undefined,\n },\n };\n}\n\n/**\n * Resolve whether a workspace exists for the reset endpoint, including\n * strike workspaces (feature-<id>-strike) that getWorkspaceInfoForIssue does\n * not yet know about. PAN-2270 regression test hook.\n */\nexport function resolveResetWorkspace(\n issueId: string,\n workspaceInfo: { exists: boolean; localPath?: string },\n resolved: { projectPath: string } | null,\n): { exists: boolean; localPath: string | null } {\n const issueLower = issueId.toLowerCase();\n const workspacePath = resolved ? findWorkspacePath(resolved.projectPath, issueLower) : null;\n return {\n exists: workspaceInfo.exists || workspacePath !== null,\n localPath: workspaceInfo.localPath || workspacePath,\n };\n}\n\n/**\n * Build the workspace/branch pair for a review re-dispatch, handling\n * strike workspaces (feature-<id>-strike -> strike/<id>) and preserving the\n * existing feature/<numeric> convention for non-strike workspaces.\n * PAN-2270 regression test hook.\n */\nexport function buildReviewRedispatchArgs(\n issueId: string,\n resetWorkspace: { localPath: string | null },\n workspaceInfo: { localPath?: string },\n resolved: { projectPath: string } | null,\n): { workspace: string; branch: string } | null {\n if (!resolved) return null;\n const issueLower = issueId.toLowerCase();\n const numericSuffix = issueLower.replace(/^[a-z]+-/, '');\n const wsPath =\n resetWorkspace.localPath ||\n workspaceInfo.localPath ||\n findWorkspacePath(resolved.projectPath, issueLower) ||\n join(resolved.projectPath, 'workspaces', `feature-${numericSuffix}`);\n const branchName = wsPath.endsWith('-strike')\n ? `strike/${issueLower}`\n : `feature/${numericSuffix}`;\n return { workspace: wsPath, branch: branchName };\n}\nexport type UnstickResult =\n | { httpStatus: 404; body: { success: false; error: string } }\n | { httpStatus: 400; body: { success: false; error: string } }\n | { httpStatus: 409; body: { success: false; error: string } }\n | { httpStatus: 200; body: { success: true; issueId: string; previousReason?: string } };\n\nexport type UnstickGitRepairState =\n | { safe: true }\n | { safe: false; advice: string };\n\n/**\n * Core logic for POST /api/workspaces/:issueId/unstick.\n *\n * Validates preconditions (workspace exists, workspace is stuck, git state is\n * repaired), clears the persistent stuck marker, and invalidates stale\n * review/test results by resetting the lifecycle to pending.\n *\n * The recovery path changes the project's main-branch state by committing,\n * pushing, fast-forwarding, or otherwise reconciling it. Keeping\n * reviewStatus=passed after that would let the UI present a stale approval.\n * One atomic setReviewStatus() call clears stuck state and resets the\n * lifecycle in a single DB write and a single notifyPipeline event.\n *\n * gitRepairState must be pre-verified by the caller (async git check). An\n * unsafe result returns 409 with recovery instructions before any DB mutation.\n *\n * Exported for unit testing — the route handler calls this and maps the result\n * directly to an HTTP response.\n */\nexport function processUnstickRequest(\n issueId: string,\n workspaceExists: boolean,\n currentStatus: ReturnType<typeof getReviewStatusSync>,\n gitRepairState: UnstickGitRepairState,\n): UnstickResult {\n if (!workspaceExists) {\n return { httpStatus: 404, body: { success: false, error: 'Workspace does not exist' } };\n }\n if (!currentStatus?.stuck) {\n return { httpStatus: 400, body: { success: false, error: `Workspace ${issueId} is not stuck` } };\n }\n // Enforce that the operator has actually repaired the git state before we\n // clear the stuck flag. If project main still has local-only commits, remote\n // commits, or uncommitted changes, Deacon would immediately re-enter the same\n // broken approve/merge path.\n if (!gitRepairState.safe) {\n return {\n httpStatus: 409,\n body: {\n success: false,\n error: `Workspace git state is not yet repaired. ${gitRepairState.advice} Then retry Unstick.`,\n },\n };\n }\n // Single atomic write: clear stuck fields and reset lifecycle to pending.\n setReviewStatusBase(issueId, {\n reviewStatus: 'pending',\n testStatus: 'pending',\n mergeStatus: 'pending',\n readyForMerge: false,\n stuck: undefined,\n stuckReason: undefined,\n stuckAt: undefined,\n stuckDetails: undefined,\n reviewedAtCommit: undefined,\n // PAN-794: unstick opens a fresh recovery cycle — arm the breaker budget\n // again so legitimate transient failures don't inherit prior cycle counts.\n reviewRetryCount: 0,\n recoveryStartedAt: undefined,\n });\n console.log(`[unstick] Cleared stuck flag and reset lifecycle for ${issueId} (was: ${currentStatus.stuckReason ?? 'unknown'})`);\n return { httpStatus: 200, body: { success: true, issueId, previousReason: currentStatus.stuckReason } };\n}\n\nfunction buildUnstickRepairAdvice(aheadCount: number, behindCount: number, dirty: boolean): string | null {\n const steps: string[] = [];\n if (dirty) {\n steps.push('commit the project repo changes that should be preserved, or explicitly discard only changes known to be disposable');\n }\n if (aheadCount > 0 && behindCount > 0) {\n steps.push(`reconcile local main with origin/main while preserving the ${aheadCount} local commit(s), then push the reconciled main`);\n } else if (aheadCount > 0) {\n steps.push(`push the ${aheadCount} local main commit(s) to origin/main`);\n } else if (behindCount > 0) {\n steps.push(`fast-forward local main from origin/main (${behindCount} commit(s) behind)`);\n }\n return steps.length > 0 ? `In the project repo, ${steps.join('; ')}.` : null;\n}\n\n/**\n * Check whether the project repo's local main branch matches origin/main and\n * has no uncommitted changes. Unsafe states return recoverable instructions:\n * push local-only commits, commit dirty work, fast-forward behind branches, or\n * reconcile a true divergence. Never prescribe destructive reset.\n *\n * Returns safe for any git error so a transient failure doesn't permanently\n * block unstick.\n */\nasync function checkProjectGitRepairState(projectPath: string): Promise<UnstickGitRepairState> {\n try {\n const [divergence, status] = await Promise.all([\n execAsync(\n 'git rev-list --left-right --count main...origin/main',\n { cwd: projectPath, encoding: 'utf-8', timeout: 5000 }\n ),\n execAsync(\n 'git status --porcelain',\n { cwd: projectPath, encoding: 'utf-8', timeout: 5000 }\n ),\n ]);\n const [aheadRaw = '0', behindRaw = '0'] = divergence.stdout.trim().split(/\\s+/);\n const aheadCount = parseInt(aheadRaw, 10) || 0;\n const behindCount = parseInt(behindRaw, 10) || 0;\n const dirty = status.stdout.trim().length > 0;\n const advice = buildUnstickRepairAdvice(aheadCount, behindCount, dirty);\n return advice ? { safe: false, advice } : { safe: true };\n } catch {\n // If we can't check (no git repo, no origin/main), don't block the operator.\n return { safe: true };\n }\n}\n\nexport const __testInternals = {\n buildUnstickRepairAdvice,\n};\nconst postWorkspaceResetReviewRoute = HttpRouter.add(\n 'POST',\n '/api/review/:issueId/reset',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const body = yield* readJsonBody;\n\n const workspaceInfo = getWorkspaceInfoForIssue(issueId);\n const resolved = resolveProjectFromIssueSync(issueId);\n const resetWorkspace = resolveResetWorkspace(issueId, workspaceInfo, resolved);\n const result = processResetReviewPipeline(issueId, resetWorkspace.exists);\n if (result.httpStatus !== 200) {\n return jsonResponse(result.body, { status: result.httpStatus });\n }\n\n try {\n const { resetPostMergeState } = yield* Effect.promise(() => import(\n '../../../../lib/cloister/merge-agent.js'\n ));\n resetPostMergeState(issueId);\n } catch (err) {\n console.warn(`[reset-review] resetPostMergeState best-effort failed for ${issueId}:`, err);\n }\n\n console.log(\n `[reset-review] Pipeline state reset for ${issueId} — awaiting agent to request review`\n );\n\n const rerun = (body as { rerun?: unknown })?.rerun === true;\n if (rerun) {\n try {\n yield* Effect.promise(async () => {\n const { spawnReviewRoleForIssue } = await import('../../../../lib/cloister/review-agent.js');\n const dispatchArgs = buildReviewRedispatchArgs(issueId, resetWorkspace, workspaceInfo, resolved);\n if (dispatchArgs) {\n const result = await Effect.runPromise(spawnReviewRoleForIssue({\n issueId,\n workspace: dispatchArgs.workspace,\n branch: dispatchArgs.branch,\n prUrl: getReviewStatusSync(issueId)?.prUrl,\n }));\n\n if (result.success) {\n // spawnReviewRoleForIssue already set 'reviewing' + reviewSpawnedAt.\n // PAN-2578: no bare 'reviewing' repeat — it can clobber a fast verdict.\n console.log(`[reset-review] Re-dispatched review for ${issueId}`);\n } else {\n console.warn(\n `[reset-review] Re-dispatch failed for ${issueId}: ${result.message || result.error}`\n );\n setReviewStatus(issueId, { reviewStatus: 'pending' });\n }\n } else {\n console.warn(\n `[reset-review] Could not resolve project for ${issueId}, skipping re-dispatch`\n );\n }\n });\n } catch (rerunErr) {\n console.warn(`[reset-review] Re-dispatch error for ${issueId}: ${rerunErr}`);\n setReviewStatus(issueId, { reviewStatus: 'pending' });\n }\n }\n\n return jsonResponse({\n success: true,\n message: rerun\n ? `Pipeline reset and review re-dispatched for ${issueId}.`\n : `Review cycles reset for ${issueId}. Agent can now request review when ready.`,\n rerun,\n });\n }))\n);\n// ─── Route: POST /api/review/:issueId/purge ────────────────────────────────\n//\n// COMPLETE review reset. Tears down the issue's entire review fleet — the\n// agent-<id>-review parent PLUS any leftover extended-review (convoy) sub-reviewers\n// (-correctness/-security/-performance/-requirements) — by killing their tmux sessions\n// and removing each agent via removeAgentSync (overdeck.db row + state dir, never the\n// JSONL transcript), then resets review_status (the pipeline verdict block re-derives\n// from it). Use this to clear stale review ghosts left by a prior cycle so a fresh\n// review runs clean. Destructive to review-agent state only; confirmed via a dialog.\n\nconst postWorkspaceReviewPurgeRoute = HttpRouter.add(\n 'POST',\n '/api/review/:issueId/purge',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: 'Invalid issue ID' }, { status: 400 });\n }\n\n const { purgeReviewAgentsForIssue } = yield* Effect.promise(\n () => import('../../../../lib/cloister/review-agent.js'),\n );\n const projectKey = resolveProjectFromIssueSync(issueId)?.projectKey;\n const purge = yield* Effect.promise(() => purgeReviewAgentsForIssue(projectKey, issueId));\n\n // Reset review_status (pipeline verdict block re-derives from it). Only meaningful\n // when the workspace still exists; ghost removal above runs regardless.\n const workspaceInfo = getWorkspaceInfoForIssue(issueId);\n let reviewStatusReset = false;\n if (workspaceInfo.exists) {\n processResetReviewPipeline(issueId, true);\n reviewStatusReset = true;\n }\n\n console.log(\n `[review-purge] ${issueId}: removed=[${purge.removed.join(', ')}] ` +\n `killed=[${purge.killed.join(', ')}] reviewStatusReset=${reviewStatusReset}`,\n );\n\n return jsonResponse({\n success: true,\n issueId,\n removed: purge.removed,\n killed: purge.killed,\n reviewStatusReset,\n message: `Purged ${purge.removed.length} review agent(s) for ${issueId}.`,\n });\n })),\n);\n// ─── Route: POST /api/review/:issueId/abort ────────────────────────────────\n//\n// Kill all running reviewer tmux sessions for an issue and reset reviewStatus\n// to 'pending'. Does NOT message the work agent — leaves the worker idle.\n// Use this to stop a runaway or stuck review without triggering a resubmit.\n\nconst postWorkspaceAbortReviewRoute = HttpRouter.add(\n 'POST',\n '/api/review/:issueId/abort',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = (params['issueId'] ?? '').toUpperCase();\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n if (!issueId) {\n return jsonResponse({ success: false, error: 'Missing issueId' }, { status: 400 });\n }\n\n const workspaceInfo = getWorkspaceInfoForIssue(issueId);\n if (!workspaceInfo.exists) {\n return jsonResponse({ success: false, error: 'Workspace does not exist' }, { status: 400 });\n }\n\n const { resolveProjectFromIssueSync } = yield* Effect.promise(() =>\n import('../../../../lib/projects.js'),\n );\n const { killAllReviewerSessions } = yield* Effect.promise(() =>\n import('../../../../lib/cloister/review-agent.js'),\n );\n const resolved = resolveProjectFromIssueSync(issueId);\n const { killed, failed } = yield* killAllReviewerSessions(resolved?.projectKey, issueId);\n\n // Reset only reviewStatus — leave test/merge/verification untouched\n setReviewStatus(issueId, {\n reviewStatus: 'pending',\n reviewNotes: undefined,\n });\n\n console.log(\n `[abort-review] Aborted ${killed.length} reviewer session(s) for ${issueId}` +\n (failed.length ? ` (${failed.length} kill failed)` : '')\n );\n\n return jsonResponse({\n success: true,\n message: `Aborted ${killed.length} reviewer session(s) for ${issueId}. Worker left idle.`,\n killed,\n failed,\n });\n }))\n);\nconst postWorkspaceUnstickRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces/:issueId/unstick',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n\n const workspaceInfo = getWorkspaceInfoForIssue(issueId);\n const current = getReviewStatusSync(issueId);\n\n // Pre-verify git state before mutating stuck flag.\n // For main_diverged: check that local main is not ahead of origin/main.\n // PAN-794: review_infrastructure_failure is unrelated to git divergence —\n // skip the git safe-state check so operators can unstick review-infra\n // workspaces without touching the project's main branch.\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const skipGitCheck = current?.stuckReason === 'review_infrastructure_failure';\n const gitRepairState = skipGitCheck\n ? { safe: true } as const\n : yield* Effect.promise(() => checkProjectGitRepairState(projectPath));\n\n const result = processUnstickRequest(issueId, workspaceInfo.exists, current, gitRepairState);\n return jsonResponse(result.body, result.httpStatus !== 200 ? { status: result.httpStatus } : undefined);\n }))\n);\nconst postWorkspaceDeaconIgnoreRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces/:issueId/deacon-ignore',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = (params['issueId'] ?? '').toUpperCase();\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n if (!issueId) {\n return jsonResponse({ success: false, error: 'Missing issueId' }, { status: 400 });\n }\n\n const body = (yield* readJsonBody) as { ignored?: unknown; reason?: unknown };\n if (typeof body.ignored !== 'boolean') {\n return jsonResponse(\n { success: false, error: 'Body must include { ignored: boolean }' },\n { status: 400 },\n );\n }\n const reason = typeof body.reason === 'string' && body.reason.trim().length > 0\n ? body.reason.trim()\n : undefined;\n\n setDeaconIgnored(issueId, body.ignored, reason);\n const updated = getReviewStatusSync(issueId);\n return jsonResponse({\n success: true,\n issueId,\n deaconIgnored: updated?.deaconIgnored ?? body.ignored,\n deaconIgnoredAt: updated?.deaconIgnoredAt,\n deaconIgnoredReason: updated?.deaconIgnoredReason,\n });\n }))\n);\nconst postWorkspaceAutoMergeRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces/:issueId/auto-merge',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = (params['issueId'] ?? '').toUpperCase();\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n\n const body = (yield* readJsonBody) as { autoMerge?: unknown };\n if (typeof body.autoMerge !== 'boolean' && body.autoMerge !== null) {\n return jsonResponse(\n { success: false, error: 'Body must include { autoMerge: boolean | null }' },\n { status: 400 },\n );\n }\n\n setAutoMerge(issueId, body.autoMerge);\n const updated = getReviewStatusSync(issueId);\n return jsonResponse({\n success: true,\n issueId,\n autoMerge: updated?.autoMerge ?? null,\n });\n }))\n);\n// ─── Route: DELETE /api/review/:issueId/pending ──────────────────────────\n\nconst deleteWorkspacePendingRoute = HttpRouter.add(\n 'DELETE',\n '/api/review/:issueId/pending',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n clearPendingOperation(issueId);\n return jsonResponse({ success: true });\n }))\n);\n\n\n// ─── Route: GET/POST /api/review/:issueId/config (PAN-1874) ─────────────────\n//\n// Per-issue review configuration override: reviewMode (quick|full|none) and\n// reReviewScope (all|changed|blockers), persisted on the per-issue record —\n// the same override `pan review mode` / `pan review scope` write. Resolution\n// order at review-spawn time: per-issue record → per-project roles.review →\n// global config (resolveReviewMode / resolveReReviewScope). Passing null for\n// a field clears the override back to config resolution.\nconst getReviewConfigRoute = HttpRouter.add(\n 'GET',\n '/api/review/:issueId/config',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = (params['issueId'] ?? '').toUpperCase();\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const result = yield* Effect.promise(async () => {\n const { readIssueRecordSync, resolveProjectForIssue } = await import('../../../../lib/pan-dir/record.js');\n const { resolveReviewMode, resolveReReviewScope } = await import('../../../../lib/cloister/review-agent.js');\n const project = resolveProjectForIssue(issueId);\n const record = project ? readIssueRecordSync(project, issueId) : null;\n return {\n issueId,\n override: { reviewMode: record?.reviewMode ?? null, reReviewScope: record?.reReviewScope ?? null },\n resolved: { reviewMode: resolveReviewMode(issueId), reReviewScope: resolveReReviewScope(issueId) },\n };\n });\n return jsonResponse(result);\n }))\n);\n\nconst postReviewConfigRoute = HttpRouter.add(\n 'POST',\n '/api/review/:issueId/config',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = (params['issueId'] ?? '').toUpperCase();\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const body = (yield* readJsonBody) as { reviewMode?: string | null; reReviewScope?: string | null };\n const hasMode = Object.prototype.hasOwnProperty.call(body ?? {}, 'reviewMode');\n const hasScope = Object.prototype.hasOwnProperty.call(body ?? {}, 'reReviewScope');\n if (!hasMode && !hasScope) {\n return jsonResponse({ error: 'Provide reviewMode and/or reReviewScope (null clears the override)' }, { status: 400 });\n }\n if (hasMode && body.reviewMode !== null && body.reviewMode !== 'quick' && body.reviewMode !== 'full' && body.reviewMode !== 'none') {\n return jsonResponse({ error: \"reviewMode must be quick, full, none, or null\" }, { status: 400 });\n }\n if (hasScope && body.reReviewScope !== null && body.reReviewScope !== 'all' && body.reReviewScope !== 'changed' && body.reReviewScope !== 'blockers') {\n return jsonResponse({ error: \"reReviewScope must be all, changed, blockers, or null\" }, { status: 400 });\n }\n const result = yield* Effect.promise(async () => {\n const { ensureIssueRecordSync, readIssueRecordSync, resolveProjectForIssue, writeIssueRecordSync } = await import('../../../../lib/pan-dir/record.js');\n const { resolveReviewMode, resolveReReviewScope } = await import('../../../../lib/cloister/review-agent.js');\n const project = resolveProjectForIssue(issueId);\n const record = readIssueRecordSync(project, issueId) ?? ensureIssueRecordSync(project, issueId);\n if (hasMode) {\n if (body.reviewMode === null) delete record.reviewMode;\n else record.reviewMode = body.reviewMode as 'quick' | 'full' | 'none';\n }\n if (hasScope) {\n if (body.reReviewScope === null) delete record.reReviewScope;\n else record.reReviewScope = body.reReviewScope as 'all' | 'changed' | 'blockers';\n }\n writeIssueRecordSync(project, issueId, record);\n return {\n success: true,\n issueId,\n override: { reviewMode: record.reviewMode ?? null, reReviewScope: record.reReviewScope ?? null },\n resolved: { reviewMode: resolveReviewMode(issueId), reReviewScope: resolveReReviewScope(issueId) },\n };\n });\n return jsonResponse(result);\n }))\n);\n\nexport const reviewControlRouteLayer = Layer.mergeAll(\n postWorkspaceResetReviewRoute,\n postWorkspaceReviewPurgeRoute,\n postWorkspaceAbortReviewRoute,\n postWorkspaceUnstickRoute,\n postWorkspaceDeaconIgnoreRoute,\n postWorkspaceAutoMergeRoute,\n deleteWorkspacePendingRoute,\n getReviewConfigRoute,\n postReviewConfigRoute,\n);\n\nexport default reviewControlRouteLayer;\n","import { execFile, spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { Effect, Layer } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\n\nimport { emitActivityEntrySync } from '../../../../lib/activity-logger.js';\nimport { teardownWorkspace } from '../../../../lib/lifecycle/teardown-workspace.js';\nimport { extractPrefixSync, parseIssueIdSync } from '../../../../lib/issue-id.js';\nimport { getProjectSync, resolveProjectFromIssueSync } from '../../../../lib/projects.js';\nimport { DEVCONTAINER_DIRNAME } from '../../../../lib/workspace/devcontainer-renderer.js';\nimport { composeProjectNameForWorkspace } from '../../../../lib/workspace/rebuild-stack.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { rejectUnauthorizedDashboardRequest, rejectUnsafeDashboardMutationRequest } from '../dashboard-auth.js';\nimport { httpHandler } from '../http-handler.js';\nimport {\n appendActivityOutput,\n completePendingOperation,\n getProjectPath,\n logActivity,\n setPendingOperation,\n updateActivity,\n} from '../workspaces.js';\n\nconst execFileAsync = promisify(execFile);\n\ntype UatStackPendingOperation = 'start-stack' | 'stop-stack' | 'restart-stack' | 'reap-workspace';\n\nconst WORKSPACE_COMPOSE_FILES = [\n 'docker-compose.devcontainer.yml',\n 'docker-compose.yml',\n 'docker-compose.yaml',\n 'compose.yml',\n 'compose.yaml',\n];\n\ninterface WorkspaceStackContext {\n issueId: string;\n projectPath: string;\n workspacePath: string;\n composeFile: string;\n composeProjectName: string;\n projectName?: string;\n}\n\nfunction findWorkspaceComposeFile(workspacePath: string): string | null {\n const devcontainerDir = join(workspacePath, DEVCONTAINER_DIRNAME);\n for (const file of WORKSPACE_COMPOSE_FILES) {\n const fullPath = join(devcontainerDir, file);\n if (existsSync(fullPath)) return fullPath;\n }\n return null;\n}\n\nfunction resolveWorkspaceStackContext(issueId: string): WorkspaceStackContext | { error: string; status: number } {\n const parsed = parseIssueIdSync(issueId);\n if (!parsed) return { error: 'Invalid issue ID', status: 400 };\n\n const normalizedIssueId = parsed.raw.toUpperCase();\n const issueLower = normalizedIssueId.toLowerCase();\n const resolvedProject = resolveProjectFromIssueSync(normalizedIssueId);\n const projectConfig = resolvedProject ? getProjectSync(resolvedProject.projectKey) : null;\n const issuePrefix = extractPrefixSync(normalizedIssueId) ?? normalizedIssueId.split('-')[0];\n const projectPath = resolvedProject?.projectPath ?? getProjectPath(undefined, issuePrefix);\n const workspacePath = join(\n projectPath,\n projectConfig?.workspace?.workspaces_dir ?? 'workspaces',\n `feature-${issueLower}`,\n );\n\n if (!existsSync(workspacePath)) {\n return { error: `Workspace not found for ${normalizedIssueId}`, status: 404 };\n }\n\n const composeFile = findWorkspaceComposeFile(workspacePath);\n if (!composeFile) {\n return { error: `No workspace compose file found for ${normalizedIssueId}`, status: 404 };\n }\n\n try {\n return {\n issueId: normalizedIssueId,\n projectPath,\n workspacePath,\n composeFile,\n composeProjectName: composeProjectNameForWorkspace(workspacePath, normalizedIssueId),\n projectName: projectConfig?.name,\n };\n } catch (err: unknown) {\n return {\n error: err instanceof Error ? err.message : String(err),\n status: 409,\n };\n }\n}\n\nfunction spawnCommandActivity(\n command: string,\n args: string[],\n description: string,\n cwd: string,\n options: { issueId: string; pendingOperation: UatStackPendingOperation },\n): string {\n const activityId = Date.now().toString();\n setPendingOperation(options.issueId, options.pendingOperation);\n emitActivityEntrySync({\n source: 'dashboard',\n level: 'info',\n issueId: options.issueId.toUpperCase(),\n message: `${description} started`,\n });\n logActivity({\n id: activityId,\n timestamp: new Date().toISOString(),\n command: [command, ...args].join(' '),\n status: 'running',\n output: [],\n });\n\n const child = spawn(command, args, {\n cwd,\n detached: true,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n child.stdout?.on('data', (data) => {\n data.toString().split('\\n').filter(Boolean).forEach((line: string) => {\n appendActivityOutput(activityId, line);\n });\n });\n child.stderr?.on('data', (data) => {\n data.toString().split('\\n').filter(Boolean).forEach((line: string) => {\n appendActivityOutput(activityId, `[stderr] ${line}`);\n });\n });\n child.on('close', (code) => {\n const ok = code === 0;\n updateActivity(activityId, { status: ok ? 'completed' : 'failed' });\n completePendingOperation(options.issueId, ok ? null : `${command} exited ${code ?? 'unknown'}`);\n emitActivityEntrySync({\n source: 'dashboard',\n level: ok ? 'success' : 'error',\n issueId: options.issueId.toUpperCase(),\n message: `${description} ${ok ? 'completed' : 'failed'}`,\n });\n });\n\n return activityId;\n}\n\nfunction spawnEffectActivity(\n description: string,\n issueId: string,\n pendingOperation: UatStackPendingOperation,\n run: (activityId: string) => Promise<void>,\n): string {\n const activityId = Date.now().toString();\n setPendingOperation(issueId, pendingOperation);\n emitActivityEntrySync({\n source: 'dashboard',\n level: 'info',\n issueId: issueId.toUpperCase(),\n message: `${description} started`,\n });\n logActivity({\n id: activityId,\n timestamp: new Date().toISOString(),\n command: description,\n status: 'running',\n output: [],\n });\n\n void run(activityId)\n .then(() => {\n updateActivity(activityId, { status: 'completed' });\n completePendingOperation(issueId, null);\n emitActivityEntrySync({\n source: 'dashboard',\n level: 'success',\n issueId: issueId.toUpperCase(),\n message: `${description} completed`,\n });\n })\n .catch((err: unknown) => {\n const message = err instanceof Error ? err.message : String(err);\n appendActivityOutput(activityId, message);\n updateActivity(activityId, { status: 'failed' });\n completePendingOperation(issueId, message);\n emitActivityEntrySync({\n source: 'dashboard',\n level: 'error',\n issueId: issueId.toUpperCase(),\n message: `${description} failed`,\n });\n });\n\n return activityId;\n}\n\nconst postWorkspaceStackActionRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces/:issueId/stack/:action',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const authError = rejectUnsafeDashboardMutationRequest(request);\n if (authError) return authError;\n\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n const action = params['action'] ?? '';\n if (action !== 'start' && action !== 'stop' && action !== 'restart') {\n return jsonResponse({ error: 'Invalid stack action' }, { status: 400 });\n }\n\n const context = resolveWorkspaceStackContext(issueId);\n if ('error' in context) {\n return jsonResponse({ error: context.error }, { status: context.status });\n }\n\n const pendingOperation = action === 'start'\n ? 'start-stack'\n : action === 'stop'\n ? 'stop-stack'\n : 'restart-stack';\n const description = `${action[0].toUpperCase()}${action.slice(1)} stack for ${context.issueId}`;\n const activityId = spawnCommandActivity(\n 'docker',\n ['compose', '-f', context.composeFile, '-p', context.composeProjectName, action],\n description,\n dirname(context.composeFile),\n { issueId: context.issueId, pendingOperation },\n );\n\n return jsonResponse({\n success: true,\n message: `${description} started`,\n activityId,\n });\n })),\n);\n\nconst getWorkspaceStackLogsRoute = HttpRouter.add(\n 'GET',\n '/api/workspaces/:issueId/stack-logs',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const authError = rejectUnauthorizedDashboardRequest(request);\n if (authError) return authError;\n\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n const context = resolveWorkspaceStackContext(issueId);\n if ('error' in context) {\n return jsonResponse({ error: context.error }, { status: context.status });\n }\n\n const { stdout } = yield* Effect.promise(() => execFileAsync('docker', [\n 'ps',\n '-a',\n '--filter',\n `label=com.docker.compose.project=${context.composeProjectName}`,\n '--format',\n '{{.Names}}',\n ], { encoding: 'utf-8', timeout: 10_000 }));\n const names = stdout.split('\\n').map(line => line.trim()).filter(Boolean);\n const chunks: string[] = [];\n for (const name of names) {\n try {\n const { stdout: logs, stderr } = yield* Effect.promise(() => execFileAsync('docker', [\n 'logs',\n '--tail',\n '200',\n '--timestamps',\n name,\n ], { encoding: 'utf-8', timeout: 10_000, maxBuffer: 2 * 1024 * 1024 }));\n chunks.push(`--- ${name} ---\\n${logs}${stderr ? `\\n${stderr}` : ''}`.trimEnd());\n } catch (err: unknown) {\n chunks.push(`--- ${name} ---\\n${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n return jsonResponse({\n success: true,\n issueId: context.issueId,\n stackName: context.composeProjectName,\n logs: chunks.join('\\n\\n'),\n });\n })),\n);\n\nconst getWorkspaceStateDirRoute = HttpRouter.add(\n 'GET',\n '/api/workspaces/:issueId/state-dir',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const authError = rejectUnauthorizedDashboardRequest(request);\n if (authError) return authError;\n\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n const context = resolveWorkspaceStackContext(issueId);\n if ('error' in context) {\n return jsonResponse({ error: context.error }, { status: context.status });\n }\n return jsonResponse({\n success: true,\n issueId: context.issueId,\n path: context.workspacePath,\n stackName: context.composeProjectName,\n });\n })),\n);\n\nconst postWorkspaceReapRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces/:issueId/reap',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const authError = rejectUnsafeDashboardMutationRequest(request);\n if (authError) return authError;\n\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n const context = resolveWorkspaceStackContext(issueId);\n if ('error' in context) {\n return jsonResponse({ error: context.error }, { status: context.status });\n }\n\n const activityId = spawnEffectActivity(\n `Reap workspace for ${context.issueId}`,\n context.issueId,\n 'reap-workspace',\n async (id) => {\n const steps = await Effect.runPromise(teardownWorkspace({\n issueId: context.issueId,\n projectPath: context.projectPath,\n projectName: context.projectName,\n }, {\n deleteBranches: false,\n clearBeads: false,\n }));\n for (const step of steps) {\n const detail = step.details?.length ? `: ${step.details.join('; ')}` : '';\n appendActivityOutput(id, `${step.success ? '✓' : step.skipped ? '-' : '✗'} ${step.step}${detail}${step.error ? ` (${step.error})` : ''}`);\n }\n const failed = steps.find(step => !step.success && !step.skipped);\n if (failed) {\n throw new Error(failed.error ?? `${failed.step} failed`);\n }\n },\n );\n\n return jsonResponse({\n success: true,\n message: `Reaping workspace for ${context.issueId}`,\n activityId,\n });\n })),\n);\n\nexport const uatStackActionRouteLayer = Layer.mergeAll(\n postWorkspaceStackActionRoute,\n getWorkspaceStackLogsRoute,\n getWorkspaceStateDirRoute,\n postWorkspaceReapRoute,\n);\n","import { jsonResponse } from \"../http-helpers.js\";\nimport { httpHandler } from './http-handler.js';\nimport { buildChildEnvWithoutTmuxSync } from '../../../lib/child-env.js';\n/**\n * Workspaces route module — Effect HttpRouter.Layer (PAN-428 B8)\n *\n * Workspaces + lifecycle + review HTTP routes.\n *\n * Workspace data endpoints (/api/workspaces/):\n * GET /api/workspace-stack-health\n * GET /api/workspaces/:issueId\n * POST /api/workspaces\n * GET /api/workspaces/:issueId/plan\n * GET /api/workspaces/:issueId/uat-context\n * PATCH /api/workspaces/:issueId/plan/inspection-policy\n * GET /api/workspaces/:issueId/clean/preview\n * POST /api/workspaces/:issueId/clean\n * POST /api/workspaces/:issueId/containerize\n * POST /api/workspaces/:issueId/containers/:containerName/:action\n * POST /api/workspaces/:issueId/memory-summary\n * POST /api/workspaces/:issueId/refresh-db\n * GET /api/workspaces/:issueId/stashes\n * POST /api/workspaces/:issueId/stashes/:stashRef/recover\n * DELETE /api/workspaces/:issueId/stashes/:stashRef\n * GET /api/workspaces/:issueId/tldr\n *\n * Lifecycle endpoints (/api/issues/):\n * POST /api/issues/:issueId/start\n * POST /api/issues/:issueId/sync-main\n * POST /api/issues/:issueId/approve\n * POST /api/issues/:issueId/merge\n *\n * Review endpoints (/api/review/):\n * GET /api/review/:issueId/status\n * POST /api/review/:issueId/status\n * POST /api/review/:issueId/trigger\n * POST /api/review/:issueId/request\n * POST /api/review/:issueId/reset\n * DELETE /api/review/:issueId/pending\n *\n * Stuck-state endpoints (/api/workspaces/):\n * POST /api/workspaces/:issueId/unstick\n */\n\nimport { exec, execFile, spawn } from 'node:child_process';\nimport { createConnection } from 'node:net';\nimport { existsSync } from 'node:fs';\nimport { access, chmod, mkdir, readdir, readFile, stat, symlink, unlink, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { basename, dirname, join } from 'node:path';\nimport { promisify } from 'node:util';\nimport { crc32 } from 'node:zlib';\n\nimport { Effect, Layer, Option } from 'effect';\nimport { HttpRouter, HttpServerRequest, HttpServerResponse } from 'effect/unstable/http';\n\nimport {\n resolveProjectFromIssueSync,\n getProjectSync,\n listProjectsSync,\n findProjectByTeamSync,\n extractTeamPrefix,\n} from '../../../lib/projects.js';\nimport { resolveGitHubIssueSync as resolveGitHubIssueShared } from '../../../lib/tracker-utils.js';\nimport { getGitHubConfig } from '../services/tracker-config.js';\nimport { EventStoreService } from '../services/domain-services.js';\nimport {\n enqueuePendingFeedbackDelivery,\n markPendingFeedbackDelivered,\n} from '../pending-feedback.js';\nimport {\n getReviewStatusSync,\n setReviewStatusSync as setReviewStatusBase,\n markWorkspaceStuck,\n setDeaconIgnored,\n setAutoMerge,\n type ReviewStatus,\n} from '../../../lib/review-status.js';\nimport {\n getCachedConflictGateMergeability,\n} from '../../../lib/cloister/conflict-gate.js';\nimport { restoreTrackedBeadsExport } from '../../../lib/beads-restore.js';\nimport {\n computeQueuePositionFromStatusSync,\n findPositionInQueueSync,\n} from '../../../lib/queue-position.js';\nimport {\n messageAgent,\n saveAgentRuntimeState,\n getAgentRuntimeStateSync,\n transitionIssueToInReview,\n getAgentStateSync,\n spawnRun,\n} from '../../../lib/agents.js';\nimport { getActiveSessionModelSync } from '../../../lib/cost-parsers/jsonl-parser.js';\nimport { getCostsForIssueSync } from '../../../lib/costs/index.js';\nimport { resolveIssueHeadlineCost } from '../services/issue-cost-resolver.js';\nimport { getCachedRunningAgents } from '../services/running-agents-cache.js';\nimport { readPlan, isPlanningComplete } from '../../../lib/vbrief/io.js';\nimport { VBRIEF_INSPECTION_POLICIES } from '../../../lib/vbrief/types.js';\nimport type { VBriefDocument, VBriefInspectionPolicy } from '../../../lib/vbrief/types.js';\nimport { findVBriefByIssue, readVBriefDocument } from '../../../lib/vbrief/vbrief-index.js';\nimport { criticalPath, actionableDoc } from '../../../lib/vbrief/dag.js';\nimport { getChangedFiles, getDiffBase, getDiffStat, type ChangedFile } from '../../../lib/cloister/review-context.js';\nimport { capturePane, listSessionNames, sessionExists } from '../../../lib/tmux.js';\nimport { syncBeadStatusToVBrief } from '../../../lib/vbrief/beads.js';\nimport { getUnblockedItemsSync } from '../../../lib/cloister/task-readiness.js';\nimport { runVerificationForIssue } from '../../../lib/cloister/verification-runner.js';\nimport { getTldrDaemonServiceSync } from '../../../lib/tldr-daemon.js';\nimport { loadWorkspaceMetadataSync, listWorkspaceMetadataSync } from '../../../lib/remote/workspace-metadata.js';\nimport { loadConfigSync } from '../../../lib/config.js';\nimport { extractPrefixSync, parseIssueIdSync } from '../../../lib/issue-id.js';\nimport { getContainersReferencingWorkspacePath } from '../../../lib/workspace-manager.js';\nimport { collectDockerContainerLifecycleSnapshot, getWorkspaceStackHealth } from '../../../lib/workspace/stack-health.js';\nimport { emitActivityEntrySync } from '../../../lib/activity-logger.js';\nimport { enrichReviewStatusFromSessions } from '../../../lib/review-status-enrichment.js';\nimport { createRecoveryBranchFromStash, dropStash, isSalvageableStash, listStashes } from '../../../lib/stashes.js';\nimport { PAN_CONTINUE_FILENAME, PAN_DIRNAME } from '../../../lib/pan-dir/types.js';\nimport { getWorkspacePathForIssue } from '../workspace-paths.js';\nimport { containerOpsRouteLayer } from './workspaces/container-ops.js';\nimport { workspaceDataRouteLayer } from './workspaces/workspace-data.js';\nimport { stashCleanRouteLayer } from './workspaces/stash-clean.js';\nimport { reviewPipelineRouteLayer } from './workspaces/review-pipeline.js';\nimport { reviewControlRouteLayer } from './workspaces/review-control.js';\nimport { mergeOpsRouteLayer } from './workspaces/merge-ops.js';\nimport { uatStackActionRouteLayer } from './workspaces/uat-stack-actions.js';\n\nconst execAsync = promisify(exec);\nconst execFileAsync = promisify(execFile);\nconst MAX_PROBED_PORTS = 5;\nconst MAX_PROBED_CONTAINERS = 10;\nconst PROBE_CACHE_TTL_MS = 30_000;\nconst PROBE_CACHE_MAX_ENTRIES = MAX_PROBED_CONTAINERS * MAX_PROBED_PORTS * 20;\n\nfunction safeToISOString(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined;\n const d = new Date(value);\n if (Number.isNaN(d.getTime())) return undefined;\n return d.toISOString();\n}\n\ninterface ProbeCacheEntry {\n result: { healthy: boolean; reason?: string };\n cachedAt: number;\n}\n\nconst probeCache = new Map<string, ProbeCacheEntry>();\n\nfunction pruneProbeCache(\n now = Date.now(),\n runningNames?: Set<string>,\n shouldPruneContainer?: (containerName: string) => boolean,\n): void {\n for (const [key, entry] of probeCache) {\n const containerName = key.split('::', 1)[0] ?? '';\n const expired = now - entry.cachedAt > PROBE_CACHE_TTL_MS;\n const absent = runningNames && shouldPruneContainer?.(containerName) === true && !runningNames.has(containerName);\n if (expired || absent) {\n probeCache.delete(key);\n }\n }\n\n while (probeCache.size > PROBE_CACHE_MAX_ENTRIES) {\n const oldestKey = probeCache.keys().next().value;\n if (typeof oldestKey !== 'string') break;\n probeCache.delete(oldestKey);\n }\n}\n\nfunction getCachedProbe(key: string): { healthy: boolean; reason?: string } | undefined {\n const entry = probeCache.get(key);\n if (!entry) return undefined;\n const now = Date.now();\n if (now - entry.cachedAt > PROBE_CACHE_TTL_MS) {\n probeCache.delete(key);\n return undefined;\n }\n probeCache.delete(key);\n probeCache.set(key, entry);\n return entry.result;\n}\n\nfunction setCachedProbe(key: string, result: { healthy: boolean; reason?: string }): void {\n const now = Date.now();\n pruneProbeCache(now);\n probeCache.set(key, { result, cachedAt: now });\n pruneProbeCache(now);\n}\n\nasync function readWorkspacePlanningMarkdown(\n issueId: string,\n fileName: 'INFERENCE.md',\n): Promise<{ issueId: string; body: string }> {\n const parsed = parseIssueIdSync(issueId);\n const issuePrefix = parsed?.prefix ?? extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const { parsedIssueId, workspacePath } = getWorkspacePathForIssue(projectPath, issueId);\n\n const content = await readFile(join(workspacePath, PAN_DIRNAME, fileName), 'utf-8');\n return {\n issueId: parsedIssueId,\n body: content,\n };\n}\n\n/**\n * Read per-issue record continue view and return it as normalized JSON text,\n * or null when the record does not exist.\n */\nasync function readWorkspaceContinueFile(\n _projectPath: string,\n workspacePath: string,\n issueId: string,\n): Promise<string | null> {\n try {\n const { readRecordContinueViewSync, getProjectConfigFromWorkspacePath, resolveProjectForIssue } =\n await import('../../../lib/pan-dir/record.js');\n const project = resolveProjectForIssue(issueId) ?? getProjectConfigFromWorkspacePath(workspacePath);\n const recordView = readRecordContinueViewSync(project, issueId);\n return recordView ? JSON.stringify(recordView, null, 2) : null;\n } catch {\n return null;\n }\n}\n\nasync function deliverQueuedFeedback(\n issueId: string,\n kind: 'review-blocked' | 'review-failed' | 'test-failed',\n filePath: string,\n message: string,\n): Promise<void> {\n const agentId = `agent-${issueId.toLowerCase()}`;\n await enqueuePendingFeedbackDelivery({\n issueId,\n agentId,\n kind,\n filePath,\n message,\n createdAt: new Date().toISOString(),\n });\n await messageAgent(agentId, message);\n await markPendingFeedbackDelivered(issueId, kind);\n}\n\n// ─── Constants ────────────────────────────────────────────────────────────────\n\nconst PORT = parseInt(process.env.API_PORT || process.env.PORT || '3011', 10);\n\n// ─── Activity log (in-memory, shared with server startup) ─────────────────────\n\ninterface ActivityEntry {\n id: string;\n timestamp: string;\n command: string;\n status: 'running' | 'completed' | 'failed';\n output: string[];\n}\n\nconst activityLog: ActivityEntry[] = [];\n\nexport function logActivity(entry: ActivityEntry): void {\n activityLog.unshift(entry);\n if (activityLog.length > 100) activityLog.pop();\n}\n\nexport function updateActivity(id: string, updates: Partial<ActivityEntry>): void {\n const entry = activityLog.find(e => e.id === id);\n if (entry) Object.assign(entry, updates);\n}\n\nexport function appendActivityOutput(id: string, line: string): void {\n const entry = activityLog.find(e => e.id === id);\n if (entry) {\n entry.output.push(line);\n if (entry.output.length > 500) entry.output.shift();\n }\n}\n\n// ─── Pending operations (in-memory) ──────────────────────────────────────────\n\ninterface PendingOperation {\n type:\n | 'review'\n | 'merge'\n | 'approve'\n | 'start'\n | 'clean'\n | 'containerize'\n | 'refresh-db'\n | 'rebuild-stack'\n | 'start-stack'\n | 'stop-stack'\n | 'restart-stack'\n | 'reap-workspace';\n status: 'running' | 'completed' | 'failed';\n startedAt: string;\n error?: string;\n}\n\nconst pendingOperations = new Map<string, PendingOperation>();\n\nexport function setPendingOperation(issueId: string, type: PendingOperation['type']): void {\n pendingOperations.set(issueId.toLowerCase(), {\n type,\n status: 'running',\n startedAt: new Date().toISOString(),\n });\n}\n\nexport function completePendingOperation(issueId: string, error?: string | null): void {\n const op = pendingOperations.get(issueId.toLowerCase());\n if (op) {\n op.status = error ? 'failed' : 'completed';\n if (error) op.error = error;\n }\n}\n\nexport function getPendingOperation(issueId: string): PendingOperation | null {\n return pendingOperations.get(issueId.toLowerCase()) ?? null;\n}\n\nexport function clearPendingOperation(issueId: string): void {\n pendingOperations.delete(issueId.toLowerCase());\n}\n\n// ─── Local helpers ────────────────────────────────────────────────────────────\n\nexport function getProjectPath(linearProjectId?: string, issuePrefix?: string): string {\n if (issuePrefix) {\n const issueId = `${issuePrefix}-1`;\n const resolved = resolveProjectFromIssueSync(issueId);\n if (resolved) return resolved.projectPath;\n\n const config = getGitHubConfig();\n if (config) {\n for (const { owner, repo, prefix } of config.repos) {\n const repoPrefix = prefix || repo.toUpperCase().replace(/-CLI$/, '').replace(/-/g, '');\n if (repoPrefix.toUpperCase() === issuePrefix.toUpperCase()) {\n const possiblePaths = [\n join(homedir(), 'Projects', repo),\n join(homedir(), 'Projects', repo.replace(/-cli$/, '')),\n join(homedir(), 'Projects', owner, repo),\n ];\n for (const path of possiblePaths) {\n if (existsSync(path)) return path;\n }\n }\n }\n }\n }\n return join(homedir(), 'Projects');\n}\n\nexport function requireTrustedMutationOrigin(request: HttpServerRequest.HttpServerRequest): HttpServerResponse.HttpServerResponse | null {\n const origin = (() => {\n const value = (request.headers as Record<string, string | string[] | undefined>)['origin'];\n return Array.isArray(value) ? value[0] : value;\n })();\n const referer = (() => {\n const value = (request.headers as Record<string, string | string[] | undefined>)['referer'];\n return Array.isArray(value) ? value[0] : value;\n })();\n\n const port = parseInt(process.env['API_PORT'] ?? process.env['PORT'] ?? '3011', 10);\n const dashboardUrl = process.env['DASHBOARD_URL'] ?? `http://localhost:${port}`;\n const trustedOrigins = new Set<string>([dashboardUrl]);\n // Always trust direct localhost access — the dashboard may be reached via\n // reverse proxy (e.g. https://overdeck.localhost) OR directly (http://localhost:3011).\n trustedOrigins.add(`http://localhost:${port}`);\n trustedOrigins.add(`http://127.0.0.1:${port}`);\n if (process.env['NODE_ENV'] === 'development') {\n trustedOrigins.add('http://localhost:3000');\n trustedOrigins.add('http://127.0.0.1:3000');\n }\n\n const normalize = (value?: string): string | null => {\n if (!value) return null;\n try {\n const url = new URL(value);\n return `${url.protocol}//${url.host}`;\n } catch {\n return null;\n }\n };\n\n const normalizedOrigin = normalize(origin);\n if (normalizedOrigin) {\n return trustedOrigins.has(normalizedOrigin)\n ? null\n : jsonResponse({ error: 'Invalid origin' }, { status: 403 });\n }\n\n const normalizedReferer = normalize(referer);\n if (normalizedReferer) {\n return trustedOrigins.has(normalizedReferer)\n ? null\n : jsonResponse({ error: 'Invalid referer' }, { status: 403 });\n }\n\n return jsonResponse({ error: 'Missing origin' }, { status: 403 });\n}\n\nexport interface WorkspaceInfo {\n exists: boolean;\n isRemote: boolean;\n vmName?: string;\n remotePath?: string;\n localPath?: string;\n agentId?: string;\n}\n\nexport function getWorkspaceInfoForIssue(issueId: string): WorkspaceInfo {\n try {\n const meta = loadWorkspaceMetadataSync(issueId);\n if (meta?.location === 'remote' && meta.vmName) {\n const metaRecord = meta as unknown as Record<string, unknown>;\n return {\n exists: true,\n isRemote: true,\n vmName: meta.vmName,\n remotePath: typeof metaRecord['remotePath'] === 'string' ? metaRecord['remotePath'] : undefined,\n agentId: typeof metaRecord['agentId'] === 'string' ? metaRecord['agentId'] : undefined,\n };\n }\n } catch { /* non-fatal */ }\n\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const issueLower = issueId.toLowerCase();\n const numericSuffix = issueLower.replace(/^[a-z]+-/, '');\n\n for (const { config } of listProjectsSync()) {\n if (!config.path) continue;\n for (const candidate of [`feature-${issueLower}`, `feature-${numericSuffix}`]) {\n const p = join(config.path, 'workspaces', candidate);\n if (existsSync(p)) return { exists: true, isRemote: false, localPath: p };\n }\n }\n\n const projectPath = getProjectPath(undefined, issuePrefix);\n const workspacePath = join(projectPath, 'workspaces', `feature-${issueLower}`);\n if (existsSync(workspacePath)) return { exists: true, isRemote: false, localPath: workspacePath };\n\n return { exists: false, isRemote: false };\n}\n\nfunction isGitHubIssue(issueId: string): {\n isGitHub: boolean;\n owner?: string;\n repo?: string;\n number?: number;\n} {\n const resolved = resolveGitHubIssueShared(issueId);\n if (resolved.isGitHub) {\n return { isGitHub: true, owner: resolved.owner, repo: resolved.repo, number: resolved.number };\n }\n return { isGitHub: false };\n}\n\nfunction attachPanOutputStreams(\n child: { stdout?: NodeJS.ReadableStream | null; stderr?: NodeJS.ReadableStream | null },\n activityId: string,\n): void {\n child.stdout?.on('data', (data) => {\n data.toString().split('\\n').filter(Boolean).forEach((line: string) => {\n appendActivityOutput(activityId, line);\n });\n });\n child.stderr?.on('data', (data) => {\n data.toString().split('\\n').filter(Boolean).forEach((line: string) => {\n appendActivityOutput(activityId, `[stderr] ${line}`);\n });\n });\n}\n\ninterface ChainPanOnSuccess {\n args: string[];\n /** Activity message emitted when the first command succeeds and the chained\n * command begins (e.g. \"Stack rebuilt — starting agent\"). */\n phaseLabel: string;\n}\n\nexport function spawnPanCommand(\n args: string[],\n description: string,\n cwd?: string,\n options?: {\n issueId?: string;\n pendingOperation?: PendingOperation['type'];\n /** When set, this second `pan` invocation runs only if the first exits 0,\n * streaming into the same activity. Used by the rebuild-and-start route to\n * chain `pan start` after a successful `pan workspace rebuild`. */\n chainOnSuccess?: ChainPanOnSuccess;\n },\n): string {\n const activityId = Date.now().toString();\n const issueId = options?.issueId;\n const pendingOp = options?.pendingOperation;\n const chain = options?.chainOnSuccess;\n const commandLine = chain ? `pan ${args.join(' ')} && pan ${chain.args.join(' ')}` : `pan ${args.join(' ')}`;\n\n if (issueId && pendingOp) {\n setPendingOperation(issueId, pendingOp);\n emitActivityEntrySync({\n source: 'dashboard',\n level: 'info',\n issueId: issueId.toUpperCase(),\n message: `${description} started`,\n });\n }\n logActivity({\n id: activityId,\n timestamp: new Date().toISOString(),\n command: commandLine,\n status: 'running',\n output: [],\n });\n\n const finalize = (code: number | null, failedCommand: string) => {\n updateActivity(activityId, { status: code === 0 ? 'completed' : 'failed' });\n if (issueId && pendingOp) {\n completePendingOperation(issueId, code === 0 ? null : `${failedCommand} exited ${code ?? 'unknown'}`);\n emitActivityEntrySync({\n source: 'dashboard',\n level: code === 0 ? 'success' : 'error',\n issueId: issueId.toUpperCase(),\n message: `${description} ${code === 0 ? 'completed' : 'failed'}`,\n });\n }\n };\n\n const child = spawn('pan', args, {\n cwd: cwd || process.cwd(),\n detached: true,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n attachPanOutputStreams(child, activityId);\n\n child.on('close', (code) => {\n if (code === 0 && chain) {\n if (issueId) {\n emitActivityEntrySync({\n source: 'dashboard',\n level: 'info',\n issueId: issueId.toUpperCase(),\n message: chain.phaseLabel,\n });\n }\n appendActivityOutput(activityId, `--- ${chain.phaseLabel} ---`);\n const next = spawn('pan', chain.args, {\n cwd: cwd || process.cwd(),\n detached: true,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n attachPanOutputStreams(next, activityId);\n next.on('close', (nextCode) => finalize(nextCode, `pan ${chain.args.join(' ')}`));\n } else {\n finalize(code, `pan ${args.join(' ')}`);\n }\n });\n\n return activityId;\n}\n\nexport interface WorkspaceContainerStatus {\n running: boolean;\n uptime: string | null;\n status?: string;\n health?: 'healthy' | 'unhealthy' | 'starting' | 'unknown';\n ports?: number[];\n lastProbeAt?: string;\n lastFailureReason?: string;\n}\n\nexport async function getContainerStatusAsync(\n issueId: string,\n projectPath?: string\n): Promise<Record<string, WorkspaceContainerStatus>> {\n const result: Record<string, WorkspaceContainerStatus> = {};\n try {\n const { stdout } = await execFileAsync('docker', ['ps', '-a', '--format', '{{.Names}}\\t{{.Status}}'], { encoding: 'utf-8' });\n const search = issueId.toLowerCase();\n const lines = stdout.trim().split('\\n').filter(Boolean);\n for (const line of lines) {\n if (!line.toLowerCase().includes(search)) continue;\n const [name, ...statusParts] = line.split('\\t');\n const statusStr = statusParts.join('\\t');\n const running = statusStr.toLowerCase().startsWith('up');\n const uptimeMatch = statusStr.match(/Up (.+)/);\n result[name] = {\n running,\n uptime: running ? (uptimeMatch ? uptimeMatch[1] : null) : null,\n status: statusStr,\n };\n }\n\n // Batch docker inspect for all running containers to avoid serial O(n) latency\n const runningNames = Object.entries(result)\n .filter(([, info]) => info.running)\n .map(([name]) => name);\n pruneProbeCache(\n Date.now(),\n new Set(runningNames),\n (containerName) => containerName.toLowerCase().includes(search),\n );\n\n if (runningNames.length > 0) {\n let inspectByName = new Map<string, any>();\n try {\n const { stdout: inspectStdout } = await execFileAsync(\n 'docker',\n ['inspect', ...runningNames],\n { encoding: 'utf-8', timeout: 10000 }\n );\n const inspects: Array<{\n Name?: string;\n Config?: { Labels?: Record<string, string>; ExposedPorts?: Record<string, unknown> };\n NetworkSettings?: { Ports?: Record<string, unknown> };\n State?: { Health?: { Status?: string; LastExecution?: { End?: string; ExitCode?: number }; FailingStreak?: number; ExitCode?: number } };\n }> = JSON.parse(inspectStdout);\n inspectByName = new Map(\n inspects\n .map((i): [string | undefined, typeof i] => [i.Name?.replace(/^\\//, ''), i])\n .filter((entry): entry is [string, (typeof inspects)[number]] => typeof entry[0] === 'string')\n );\n } catch (err: any) {\n // Docker inspect may return partial JSON on stderr even when one container is missing.\n // Try to salvage valid results from stdout so one missing container doesn't drop all health data.\n const partial = err?.stdout;\n if (typeof partial === 'string' && partial.trim().startsWith('[')) {\n try {\n const inspects = JSON.parse(partial);\n if (Array.isArray(inspects)) {\n inspectByName = new Map(inspects.map((i: any) => [i.Name?.replace(/^\\//, ''), i]));\n }\n } catch {\n // Partial JSON unreadable — fall through to empty inspectByName\n }\n }\n }\n\n // Cap total probed containers to prevent unbounded fan-out per request\n const namesToProbe = runningNames.slice(0, MAX_PROBED_CONTAINERS);\n\n await Promise.all(\n namesToProbe.map(async (name) => {\n const info = result[name];\n const inspect = inspectByName.get(name);\n const serviceHealth = extractContainerServiceHealth(inspect);\n let health: WorkspaceContainerStatus['health'] = serviceHealth.health;\n let lastFailureReason = serviceHealth.lastFailureReason;\n let lastProbeAt = serviceHealth.lastProbeAt;\n\n // If docker healthcheck is unknown but we have host-mapped ports, probe them\n if (health === 'unknown' && serviceHealth.bindings.length > 0) {\n // Deduplicate and cap probe targets\n const dedupedBindings = Array.from(\n new Map(\n serviceHealth.bindings.map((b) => [`${b.hostIp}:${b.hostPort}`, b])\n ).values()\n ).slice(0, MAX_PROBED_PORTS);\n\n const probeResults = await Promise.all(\n dedupedBindings.map(async (b) => {\n const cacheKey = `${name}::${b.hostIp}:${b.hostPort}`;\n const cached = getCachedProbe(cacheKey);\n if (cached) return cached;\n const probeResult = await probeContainerPortAsync(b.hostPort, b.hostIp);\n setCachedProbe(cacheKey, probeResult);\n return probeResult;\n })\n );\n const anyHealthy = probeResults.some((r) => r.healthy);\n health = anyHealthy ? 'healthy' : 'unhealthy';\n lastProbeAt = new Date().toISOString();\n if (!anyHealthy) {\n const firstFailure = probeResults.find((r) => !r.healthy);\n lastFailureReason = firstFailure?.reason ?? 'probe failed';\n }\n }\n\n result[name] = {\n ...info,\n health,\n ports: serviceHealth.ports,\n lastProbeAt,\n lastFailureReason,\n };\n })\n );\n }\n } catch { /* non-fatal */ }\n return result;\n}\n\ninterface ContainerPortBinding {\n containerPort: number;\n hostIp: string;\n hostPort: number;\n}\n\ninterface ContainerServiceHealth {\n health: 'healthy' | 'unhealthy' | 'starting' | 'unknown';\n ports: number[];\n bindings: ContainerPortBinding[];\n lastProbeAt?: string;\n lastFailureReason?: string;\n}\n\nfunction extractContainerServiceHealth(\n inspect?: {\n Config?: { Labels?: Record<string, string>; ExposedPorts?: Record<string, unknown> };\n NetworkSettings?: { Ports?: Record<string, unknown> };\n State?: { Health?: { Status?: string; LastExecution?: { End?: string; ExitCode?: number }; FailingStreak?: number; ExitCode?: number } };\n }\n): ContainerServiceHealth {\n if (!inspect) return { health: 'unknown', ports: [], bindings: [] };\n const labels = inspect?.Config?.Labels ?? {};\n const healthState = inspect?.State?.Health;\n const exposedPorts = inspect?.Config?.ExposedPorts ?? {};\n const portBindings = inspect?.NetworkSettings?.Ports ?? {};\n\n // Parse exposed ports\n const ports = Object.keys(exposedPorts)\n .map((p) => parseInt(p.split('/')[0], 10))\n .filter((n) => !Number.isNaN(n));\n\n // Parse Traefik loadbalancer port labels\n const traefikPorts: number[] = [];\n for (const [key, value] of Object.entries(labels)) {\n if (key.endsWith('.loadbalancer.server.port') && value) {\n const port = parseInt(value, 10);\n if (!Number.isNaN(port)) traefikPorts.push(port);\n }\n }\n\n const allPorts = traefikPorts.length > 0 ? traefikPorts : ports;\n\n // Extract host-mapped ports from NetworkSettings.Ports\n const bindings: ContainerPortBinding[] = [];\n for (const [containerPortProto, hostBindings] of Object.entries(portBindings)) {\n if (!Array.isArray(hostBindings)) continue;\n const containerPort = parseInt(containerPortProto.split('/')[0], 10);\n if (Number.isNaN(containerPort)) continue;\n for (const hb of hostBindings) {\n if (!hb || typeof hb !== 'object') continue;\n const hostPort = parseInt((hb as any).HostPort, 10);\n if (Number.isNaN(hostPort) || hostPort < 1 || hostPort > 65535) continue;\n bindings.push({\n containerPort,\n hostIp: String((hb as any).HostIp || '127.0.0.1'),\n hostPort,\n });\n }\n }\n\n // If container has a Docker healthcheck, use its state\n if (healthState?.Status) {\n const status = String(healthState.Status).toLowerCase();\n const health: ContainerServiceHealth['health'] =\n status === 'healthy' ? 'healthy' :\n status === 'unhealthy' ? 'unhealthy' :\n status === 'starting' ? 'starting' : 'unknown';\n const lastProbeAt = safeToISOString(healthState?.LastExecution?.End);\n const exitCode = healthState?.LastExecution?.ExitCode;\n const failingStreak = healthState?.FailingStreak;\n const lastFailureReason = failingStreak && failingStreak > 0\n ? (typeof exitCode === 'number' && exitCode !== 0 ? `exit code ${exitCode}` : 'healthcheck failed')\n : undefined;\n return {\n health,\n ports: allPorts,\n bindings,\n lastProbeAt,\n lastFailureReason,\n };\n }\n\n return {\n health: 'unknown',\n ports: allPorts,\n bindings,\n };\n}\n\nasync function probeContainerPortAsync(hostPort: number, hostIp = '127.0.0.1'): Promise<{ healthy: boolean; reason?: string }> {\n return new Promise((resolve) => {\n const socket = createConnection(hostPort, hostIp);\n socket.setTimeout(5000);\n\n socket.on('connect', () => {\n socket.end();\n resolve({ healthy: true });\n });\n\n socket.on('error', (err) => {\n resolve({ healthy: false, reason: err.message.slice(0, 200) });\n });\n\n socket.on('timeout', () => {\n socket.destroy();\n resolve({ healthy: false, reason: 'connection timeout' });\n });\n });\n}\n\nfunction getFlyAppName(vmName: string): string {\n // Resolve via workspace metadata — deriving from the vmName prefix is wrong\n // ('pan-pan-1712-ws' → 'pan-pan'). Fall back to the configured app.\n try {\n const meta = listWorkspaceMetadataSync().find((m) => m.vmName === vmName);\n if (meta?.appName) return meta.appName;\n } catch { /* fall through */ }\n try {\n return loadConfigSync().remote?.fly?.app ?? 'pan-workspaces';\n } catch {\n return 'pan-workspaces';\n }\n}\n\nfunction shellQuote(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\n\nexport function flyExecCmd(vmName: string, command: string): string {\n const appName = getFlyAppName(vmName);\n return `fly ssh console -a ${appName} -C \"${command.replace(/\"/g, '\\\\\"')}\"`;\n}\n\nexport async function repairFlywayIfNeeded(\n issueId: string,\n pgContainer: string,\n dbName: string,\n projectConfig: any,\n workspacePath: string,\n log?: (msg: string) => void\n): Promise<{ repaired: boolean; message: string }> {\n const emit = log || ((msg: string) => console.log(`[flyway-repair] ${msg}`));\n\n try {\n await execAsync(`docker exec \"${pgContainer}\" pg_isready -U postgres`, {\n encoding: 'utf-8',\n timeout: 5000,\n });\n } catch {\n return { repaired: false, message: 'Postgres container not ready, skipping Flyway check' };\n }\n\n let rowCount = 0;\n try {\n const { stdout } = await execAsync(\n `docker exec \"${pgContainer}\" psql -U postgres -d ${dbName} -t -A -c \"SELECT count(*) FROM flyway_schema_history;\"`,\n { encoding: 'utf-8', timeout: 10000 }\n );\n rowCount = parseInt(stdout.trim(), 10) || 0;\n } catch {\n rowCount = 0;\n }\n\n if (rowCount >= 10) {\n return { repaired: false, message: `Flyway schema_history has ${rowCount} entries, no repair needed` };\n }\n\n emit(`Flyway schema_history has only ${rowCount} entries — repairing`);\n\n const seedRelPath = projectConfig.workspace?.database?.seed_file;\n if (!seedRelPath) {\n return { repaired: false, message: 'No seed_file configured, cannot locate Flyway baseline' };\n }\n\n const seedFile = join(projectConfig.path, seedRelPath);\n const flywayFile = join(dirname(seedFile), 'zzz-flyway-workspace-baseline.sql');\n if (!existsSync(flywayFile)) {\n return { repaired: false, message: `Flyway baseline not found: ${flywayFile}` };\n }\n\n emit(`Loading Flyway baseline from ${flywayFile}`);\n await execAsync(\n `docker exec -i \"${pgContainer}\" psql -U postgres -d ${dbName} < \"${flywayFile}\"`,\n { encoding: 'utf-8', timeout: 60000 }\n );\n\n const migrationsRelPath = projectConfig.workspace?.database?.migrations?.path;\n if (migrationsRelPath) {\n const migrationsDir = join(workspacePath, migrationsRelPath);\n if (existsSync(migrationsDir)) {\n emit(`Syncing Flyway checksums from workspace migrations`);\n const migrationFiles = (await readdir(migrationsDir)).filter(f => /^V\\d+__.*\\.sql$/.test(f));\n const updates: string[] = [];\n\n for (const file of migrationFiles) {\n const version = file.match(/^V(\\d+)__/)?.[1];\n if (!version) continue;\n let content = await readFile(join(migrationsDir, file));\n if (content[0] === 0xEF && content[1] === 0xBB && content[2] === 0xBF) {\n content = content.slice(3);\n }\n const lines = content.toString('utf-8').split(/\\r?\\n/);\n const checksum = crc32(Buffer.from(lines.join(''), 'utf-8')) | 0;\n updates.push(\n `UPDATE flyway_schema_history SET checksum = ${checksum} WHERE version = '${version}' AND checksum IS NOT NULL;`\n );\n }\n\n if (updates.length > 0) {\n const tmpSql = `/tmp/flyway-checksum-sync-${Date.now()}.sql`;\n await writeFile(tmpSql, updates.join('\\n'));\n try {\n const { stdout } = await execAsync(\n `docker exec -i \"${pgContainer}\" psql -U postgres -d ${dbName} < \"${tmpSql}\"`,\n { encoding: 'utf-8', timeout: 30000 }\n );\n const updatedCount = (stdout.match(/UPDATE \\d+/g) || [])\n .reduce((sum, m) => sum + parseInt(m.replace('UPDATE ', ''), 10), 0);\n emit(`Synced ${migrationFiles.length} migration checksums (${updatedCount} rows updated)`);\n } finally {\n try { await unlink(tmpSql); } catch {}\n }\n }\n }\n }\n\n try {\n const { stdout } = await execAsync(\n `docker exec \"${pgContainer}\" psql -U postgres -d ${dbName} -t -A -c \"SELECT count(*) FROM flyway_schema_history;\"`,\n { encoding: 'utf-8', timeout: 10000 }\n );\n const newCount = parseInt(stdout.trim(), 10) || 0;\n emit(`Repair complete: flyway_schema_history now has ${newCount} entries (was ${rowCount})`);\n return { repaired: true, message: `Repaired Flyway schema_history: ${rowCount} → ${newCount} entries` };\n } catch (err: any) {\n return { repaired: false, message: `Repair may have failed: ${err.message}` };\n }\n}\n\n// setReviewStatus wrapper (mirrors the index.ts version; side-effects are\n// intentionally omitted here — the server-side side-effects (auto-PR, auto-merge)\n// live in the Express server until full migration is complete).\nexport function setReviewStatus(issueId: string, update: Partial<ReviewStatus>): ReviewStatus {\n return setReviewStatusBase(issueId, update);\n}\n\n// ─── Read JSON body helper ────────────────────────────────────────────────────\n\nexport const readJsonBody = Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const text = yield* request.text;\n try {\n return text ? JSON.parse(text) : {};\n } catch {\n return {};\n }\n});\n\n// ─── Route: POST /api/workspaces/:issueId/rebuild-stack ───────────────────────\n// Bring a workspace's Docker stack back up (e.g. so the \"UAT\" action on the\n// Awaiting-Merge page works when the stack is down). Fire-and-forget like the\n// create route: spawns `pan workspace rebuild`, returns an activityId so the\n// dashboard streams progress.\nconst postWorkspaceRebuildRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces/:issueId/rebuild-stack',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: 'Invalid issue ID' }, { status: 400 });\n }\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const activityId = spawnPanCommand(\n ['workspace', 'rebuild', issueId],\n `Rebuild stack for ${issueId}`,\n projectPath,\n { issueId, pendingOperation: 'rebuild-stack' },\n );\n return jsonResponse({\n success: true,\n message: `Rebuilding stack for ${issueId}`,\n activityId,\n });\n }))\n);\n\n// ─── Route: POST /api/workspaces/:issueId/rebuild-and-start ──────────────────\n// Recovery action for the `stack-unhealthy` work-agent spawn block: rebuild the\n// workspace's Docker stack, then spawn the work agent once the stack is healthy.\n// Fire-and-forget like the rebuild-stack route, but chains `pan start` after a\n// successful `pan workspace rebuild` under a single activityId so the dashboard\n// streams both phases as one operation.\nconst postWorkspaceRebuildAndStartRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces/:issueId/rebuild-and-start',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: 'Invalid issue ID' }, { status: 400 });\n }\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const activityId = spawnPanCommand(\n ['workspace', 'rebuild', issueId],\n `Rebuild & start for ${issueId}`,\n projectPath,\n {\n issueId,\n pendingOperation: 'rebuild-stack',\n chainOnSuccess: {\n args: ['start', issueId],\n phaseLabel: `Stack rebuilt — starting agent for ${issueId.toUpperCase()}`,\n },\n },\n );\n return jsonResponse({\n success: true,\n message: `Rebuilding stack and starting agent for ${issueId}`,\n activityId,\n });\n }))\n);\n\n// ─── Route: GET /api/workspaces/:issueId/plan ─────────────────────────────────\n\nconst getWorkspaceStateMdRoute = HttpRouter.add(\n 'GET',\n '/api/workspaces/:issueId/state-md',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n\n const parsed = parseIssueIdSync(issueId);\n const issuePrefix = parsed?.prefix ?? extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const { parsedIssueId, workspacePath } = getWorkspacePathForIssue(projectPath, issueId);\n\n const continueBody = yield* Effect.promise(() => readWorkspaceContinueFile(projectPath, workspacePath, issueId));\n if (continueBody) {\n return jsonResponse({ issueId: parsedIssueId, body: continueBody });\n }\n\n return jsonResponse({ error: 'Planning state not found for this workspace' }, { status: 404 });\n }))\n);\n\nconst getWorkspaceInferenceMdRoute = HttpRouter.add(\n 'GET',\n '/api/workspaces/:issueId/inference-md',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n\n return yield* Effect.promise(() =>\n readWorkspacePlanningMarkdown(issueId, 'INFERENCE.md')\n .then((result) => jsonResponse(result))\n .catch((err: unknown) => {\n if (\n typeof err === 'object'\n && err !== null\n && ('code' in err || 'message' in err)\n && ((err as { code?: unknown }).code === 'ENOENT'\n || String((err as { message?: unknown }).message ?? '').includes('Invalid issue ID'))\n ) {\n return jsonResponse({ error: 'INFERENCE.md not found for this workspace' }, { status: 404 });\n }\n console.error('[workspaces] Failed to read INFERENCE.md:', err);\n return jsonResponse({ error: 'Internal server error' }, { status: 500 });\n })\n );\n }))\n);\n\n// ─── Route: POST /api/issues/:issueId/start ───────────────────────────────\n\nconst postWorkspaceStartRoute = HttpRouter.add(\n 'POST',\n '/api/issues/:issueId/start',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const issueLower = issueId.toLowerCase();\n const workspacePath = join(projectPath, 'workspaces', `feature-${issueLower}`);\n\n if (!existsSync(workspacePath)) {\n return jsonResponse({ error: 'Workspace does not exist' }, { status: 400 });\n }\n\n const workspaceBeadsDir = join(workspacePath, '.beads');\n if (!existsSync(workspaceBeadsDir)) {\n const projectRootBeadsDir = join(projectPath, '.beads');\n if (existsSync(projectRootBeadsDir)) {\n try {\n yield* Effect.promise(() => execAsync(`cp -r \"${projectRootBeadsDir}\" \"${workspaceBeadsDir}\"`, {\n encoding: 'utf-8',\n }));\n console.log(\n `[workspace/start] Copied beads from project root to workspace for ${issueId}`\n );\n } catch (e) {\n console.warn(`[workspace/start] Could not copy beads: ${e}`);\n }\n }\n }\n\n // Check for ./dev script\n const devScript = join(workspacePath, 'dev');\n const devScriptInContainer = join(workspacePath, '.devcontainer', 'dev');\n\n if (!existsSync(devScript)) {\n if (existsSync(devScriptInContainer)) {\n try {\n yield* Effect.promise(() => symlink('.devcontainer/dev', devScript));\n yield* Effect.promise(() => chmod(devScriptInContainer, 0o755));\n console.log(`[workspace/start] Repaired: created ./dev symlink for ${issueId}`);\n } catch (repairErr) {\n return jsonResponse(\n {\n error: `Workspace has no ./dev script and repair failed: ${repairErr}`,\n },\n { status: 400 }\n );\n }\n } else {\n return jsonResponse(\n { error: 'Workspace has no ./dev script (checked root and .devcontainer/)' },\n { status: 400 }\n );\n }\n }\n\n // Repair .env if needed\n const envFilePath = join(workspacePath, '.env');\n const teamPrefix = extractTeamPrefix(issueId);\n const projectConfig = teamPrefix ? findProjectByTeamSync(teamPrefix) : null;\n\n if (projectConfig?.workspace?.ports && projectConfig?.workspace?.env?.template) {\n const featureFolder = `feature-${issueLower}`;\n let needsRepair = !existsSync(envFilePath);\n\n if (!needsRepair && existsSync(envFilePath)) {\n const existingEnv = yield* Effect.promise(() => readFile(envFilePath, 'utf-8'));\n for (const portName of Object.keys(projectConfig.workspace.ports)) {\n const portVar = `${portName.toUpperCase()}_PORT`;\n if (!existingEnv.includes(portVar)) {\n needsRepair = true;\n break;\n }\n }\n }\n\n if (needsRepair) {\n try {\n const placeholders: Record<string, string> = { FEATURE_FOLDER: featureFolder };\n for (const [portName, portConfig] of Object.entries(\n projectConfig.workspace.ports\n )) {\n const portFile = join(projectPath, `.${portName}-ports`);\n const range = (portConfig as any).range as [number, number];\n let content = '';\n if (existsSync(portFile)) content = yield* Effect.promise(() => readFile(portFile, 'utf-8'));\n const lines = content.split('\\n').filter(Boolean);\n let port: number | null = null;\n for (const line of lines) {\n const [folder, p] = line.split(':');\n if (folder === featureFolder) {\n port = parseInt(p, 10);\n break;\n }\n }\n if (!port) {\n const usedPorts = new Set(lines.map(l => parseInt(l.split(':')[1], 10)));\n for (let p = range[0]; p <= range[1]; p++) {\n if (!usedPorts.has(p)) {\n port = p;\n yield* Effect.promise(() => writeFile(\n portFile,\n content +\n (content.endsWith('\\n') || !content ? '' : '\\n') +\n `${featureFolder}:${port}\\n`\n ));\n break;\n }\n }\n }\n if (port) placeholders[`${portName.toUpperCase()}_PORT`] = String(port);\n }\n let envContent = projectConfig.workspace.env.template;\n for (const [key, value] of Object.entries(placeholders)) {\n envContent = envContent.replace(\n new RegExp(`\\\\{\\\\{${key}\\\\}\\\\}`, 'g'),\n value\n );\n }\n yield* Effect.promise(() => writeFile(envFilePath, envContent));\n console.log(\n `[workspace/start] Repaired: created .env with port assignments for ${issueId}`\n );\n } catch (envErr) {\n console.warn(\n `[workspace/start] Could not repair .env for ${issueId}: ${envErr}`\n );\n }\n }\n }\n\n // Check Docker is running\n try {\n yield* Effect.promise(() => execAsync('docker info >/dev/null 2>&1', { encoding: 'utf-8' }));\n } catch {\n return jsonResponse(\n { error: 'Docker is not running. Start Docker Desktop first.' },\n { status: 400 }\n );\n }\n\n // Pre-start Flyway repair if applicable\n if (projectConfig?.workspace?.database?.migrations?.type === 'flyway') {\n try {\n const composePaths = [\n join(workspacePath, '.devcontainer/docker-compose.devcontainer.yml'),\n join(workspacePath, 'docker-compose.yml'),\n ];\n let compFile: string | undefined;\n for (const cp of composePaths) {\n if (existsSync(cp)) { compFile = cp; break; }\n }\n if (compFile) {\n const { stdout: pnOut } = yield* Effect.promise(() => execAsync(\n `docker compose -f \"${compFile}\" config --format json 2>/dev/null | jq -r '.name // empty'`,\n { encoding: 'utf-8' }\n ));\n const composeName = pnOut.trim();\n if (composeName) {\n const pgContainer = `${composeName}-postgres-1`;\n const result = yield* Effect.promise(() => repairFlywayIfNeeded(\n issueId,\n pgContainer,\n 'myn',\n projectConfig,\n workspacePath\n ));\n if (result.repaired) {\n console.log(`[workspace/start] Pre-start Flyway repair: ${result.message}`);\n }\n }\n }\n } catch (preCheckErr: any) {\n console.log(\n `[workspace/start] Pre-start Flyway check skipped: ${preCheckErr.message}`\n );\n }\n }\n\n const activityId = Date.now().toString();\n logActivity({\n id: activityId,\n timestamp: new Date().toISOString(),\n command: `./dev all (${issueId})`,\n status: 'running',\n output: [],\n });\n\n const uid = process.getuid?.() ?? 1000;\n const gid = process.getgid?.() ?? 1000;\n\n const child = spawn('./dev', ['all'], {\n cwd: workspacePath,\n detached: true,\n stdio: ['ignore', 'pipe', 'pipe'],\n env: buildChildEnvWithoutTmuxSync(process.env, { UID: String(uid), GID: String(gid), DOCKER_USER: `${uid}:${gid}` }),\n });\n\n child.stdout?.on('data', (data) => {\n data.toString().split('\\n').filter(Boolean).forEach((line: string) => {\n appendActivityOutput(activityId, line);\n });\n });\n child.stderr?.on('data', (data) => {\n data.toString().split('\\n').filter(Boolean).forEach((line: string) => {\n appendActivityOutput(activityId, `[stderr] ${line}`);\n });\n });\n\n child.on('close', (code) => {\n appendActivityOutput(\n activityId,\n `[${new Date().toISOString()}] ./dev all exited with code ${code}`\n );\n\n if (code !== 0 && projectConfig?.workspace?.database?.migrations?.type === 'flyway') {\n (async () => {\n try {\n const composePaths = [\n join(workspacePath, '.devcontainer/docker-compose.devcontainer.yml'),\n join(workspacePath, 'docker-compose.yml'),\n ];\n let composeFile: string | undefined;\n for (const cp of composePaths) {\n if (existsSync(cp)) { composeFile = cp; break; }\n }\n if (!composeFile) return;\n\n const { stdout: pnOut } = await execAsync(\n `docker compose -f \"${composeFile}\" config --format json 2>/dev/null | jq -r '.name // empty'`,\n { encoding: 'utf-8' }\n );\n const composeName = pnOut.trim();\n if (!composeName) return;\n\n const apiContainer = `${composeName}-api-1`;\n const pgContainer = `${composeName}-postgres-1`;\n\n const { stdout: apiStatus } = await execAsync(\n `docker ps -a --filter \"name=^${apiContainer}$\" --format \"{{.Status}}\" 2>/dev/null`,\n { encoding: 'utf-8' }\n );\n if (!apiStatus.trim().startsWith('Exited')) return;\n\n const { stdout: logs } = await execAsync(\n `docker logs --tail 50 \"${apiContainer}\" 2>&1 || true`,\n { encoding: 'utf-8', timeout: 10000 }\n );\n if (!logs.toLowerCase().includes('flyway')) return;\n\n appendActivityOutput(activityId, '');\n appendActivityOutput(\n activityId,\n '=== Detected Flyway failure — attempting auto-repair ==='\n );\n\n const result = await repairFlywayIfNeeded(\n issueId,\n pgContainer,\n 'myn',\n projectConfig,\n workspacePath,\n (msg) => appendActivityOutput(activityId, `[flyway-repair] ${msg}`)\n );\n\n if (result.repaired) {\n appendActivityOutput(activityId, `[flyway-repair] Restarting API container...`);\n await execAsync(`docker start \"${apiContainer}\"`, {\n encoding: 'utf-8',\n timeout: 30000,\n });\n appendActivityOutput(\n activityId,\n `[flyway-repair] API container restarted successfully`\n );\n updateActivity(activityId, { status: 'completed' });\n return;\n }\n } catch (repairErr: any) {\n appendActivityOutput(\n activityId,\n `[flyway-repair] Auto-repair failed: ${repairErr.message}`\n );\n }\n updateActivity(activityId, { status: 'failed' });\n })();\n } else {\n updateActivity(activityId, { status: code === 0 ? 'completed' : 'failed' });\n }\n });\n\n child.on('error', (err) => {\n appendActivityOutput(activityId, `[error] ${err.message}`);\n updateActivity(activityId, { status: 'failed' });\n });\n\n return jsonResponse({\n success: true,\n message: `Starting containers for ${issueId}`,\n activityId,\n });\n }))\n);\n\n// ─── Route: GET /api/review/:issueId/status ───────────────────────\n\nconst getWorkspaceReviewStatusRoute = HttpRouter.add(\n 'GET',\n '/api/review/:issueId/status',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n\n const status = getReviewStatusSync(issueId);\n const base: ReviewStatus = status || {\n issueId,\n reviewStatus: 'pending',\n testStatus: 'pending',\n mergeStatus: 'pending',\n readyForMerge: false,\n updatedAt: new Date().toISOString(),\n };\n\n let { queuePosition, activeSpecialist } = computeQueuePositionFromStatusSync(status);\n\n // Discover active parallel review sessions for this issue\n let reviewCoordinatorSessionName: string | undefined;\n let reviewSessionNames: string[] | undefined;\n let reviewSubStatuses: Record<string, 'running' | 'done'> | undefined;\n try {\n const allSessions = yield* listSessionNames();\n const enriched = enrichReviewStatusFromSessions(issueId, base, allSessions);\n reviewCoordinatorSessionName = enriched.reviewCoordinatorSessionName;\n reviewSessionNames = enriched.reviewSessionNames;\n reviewSubStatuses = enriched.reviewSubStatuses;\n } catch { /* non-fatal: tmux may not be available */ }\n\n // Only the merge queue is persistent — check it when no active phase is detected\n if (queuePosition === null) {\n try {\n const resolved = resolveProjectFromIssueSync(issueId);\n if (resolved) {\n const { getQueueForProject } = yield* Effect.promise(() =>\n import('../../../lib/overdeck/merge.js')\n );\n const mergeQueue = getQueueForProject(resolved.projectKey);\n const mergePos = findPositionInQueueSync(issueId, mergeQueue.map(e => ({\n id: String(e.id),\n type: 'task' as const,\n priority: 'normal' as const,\n source: 'merge-queue',\n payload: { issueId: e.issueId },\n createdAt: e.queuedAt,\n })));\n if (mergePos > 0) {\n queuePosition = mergePos;\n activeSpecialist = 'merge';\n }\n }\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[review-status] Merge queue lookup failed for ${issueId} (non-fatal): ${msg}`);\n }\n }\n\n return jsonResponse({ ...base, queuePosition, activeSpecialist, reviewCoordinatorSessionName, reviewSessionNames, reviewSubStatuses });\n }))\n);\n\n// ─── Route: POST /api/review/:issueId/status ──────────────────────\n\nconst postWorkspaceReviewStatusRoute = HttpRouter.add(\n 'POST',\n '/api/review/:issueId/status',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const body = yield* readJsonBody;\n const eventStore = yield* EventStoreService;\n const { reviewStatus, testStatus, mergeStatus, reviewNotes, testNotes, verificationStatus, readyForMerge } = body as {\n reviewStatus?: string;\n testStatus?: string;\n mergeStatus?: string;\n reviewNotes?: string;\n testNotes?: string;\n verificationStatus?: string;\n readyForMerge?: boolean;\n };\n\n // Snapshot reviewedAtCommit BEFORE the first setReviewStatus call so canSkipTests\n // fires correctly in that same call — setting it afterward is too late (the\n // async test-agent dispatch is already scheduled).\n const update: Partial<ReviewStatus> = {};\n if (reviewStatus === 'passed') {\n const workspaceInfo = getWorkspaceInfoForIssue(issueId);\n if (workspaceInfo.exists && workspaceInfo.localPath) {\n const localPath = workspaceInfo.localPath;\n const { getWorkspaceGitInfo } = yield* Effect.promise(() => import('../../../lib/git-utils.js'));\n try {\n const gitInfo = yield* getWorkspaceGitInfo(localPath);\n if (gitInfo.HEAD) {\n update.reviewedAtCommit = gitInfo.HEAD;\n }\n } catch { /* non-fatal */ }\n }\n }\n if (reviewStatus) update.reviewStatus = reviewStatus as any;\n if (testStatus) update.testStatus = testStatus as any;\n if (mergeStatus) update.mergeStatus = mergeStatus as any;\n if (reviewNotes) update.reviewNotes = reviewNotes;\n if (testNotes) update.testNotes = testNotes;\n if (verificationStatus) update.verificationStatus = verificationStatus as any;\n if (readyForMerge !== undefined) update.readyForMerge = readyForMerge;\n\n const status = setReviewStatus(issueId, update);\n\n const { getTmuxSessionName } =\n yield* Effect.promise(() => import('../../../lib/cloister/specialists.js'));\n\n const resolvedProject = resolveProjectFromIssueSync(issueId);\n const projectKey = resolvedProject?.projectKey;\n\n if (reviewStatus && ['passed', 'blocked', 'failed'].includes(reviewStatus)) {\n const tmuxSession = getTmuxSessionName('review-agent', projectKey);\n saveAgentRuntimeState(tmuxSession, {\n state: 'idle',\n currentIssue: undefined,\n lastActivity: new Date().toISOString(),\n });\n console.log(`[review-status] Set review-agent (${tmuxSession}) to idle`);\n\n if (['blocked', 'failed'].includes(reviewStatus) && reviewNotes) {\n const agentId = `agent-${issueId.toLowerCase()}`;\n const feedbackBody = `CODE REVIEW ${reviewStatus.toUpperCase()} for ${issueId}:\\n\\n${reviewNotes}\\n\\n## REQUIRED: Fix ALL issues above, then invoke the /rebase-and-submit skill\\n\\n1. Read each blocking issue carefully\\n2. Fix the code for EVERY issue listed\\n3. Run tests locally to verify your fixes\\n4. Commit every change\\n5. Invoke the /rebase-and-submit skill for ${issueId} — this is an atomic task that runs pan done (which handles rebase + push + re-submit internally)\\n\\nDo NOT stop between steps. Do NOT run git push manually — the skill handles it. Do NOT stop until pan done has completed successfully.`;\n try {\n const { writeFeedbackFile } = yield* Effect.promise(() => import(\n '../../../lib/cloister/feedback-writer.js'\n ));\n const wsInfo = getWorkspaceInfoForIssue(issueId);\n const fileResult = yield* Effect.promise(() => writeFeedbackFile({\n issueId,\n workspacePath: wsInfo.localPath,\n specialist: 'review-agent',\n outcome: reviewStatus === 'blocked' ? 'changes-requested' : 'failed',\n summary: `Review ${reviewStatus.toUpperCase()}: ${(reviewNotes || '').slice(0, 80)}`,\n markdownBody: feedbackBody,\n }));\n if (!fileResult.success) {\n console.error(\n `[review-status] Failed to write feedback file for ${issueId}: ${fileResult.error}`\n );\n } else {\n const msg = `SPECIALIST FEEDBACK: review-agent reported ${reviewStatus.toUpperCase()} for ${issueId}.\\n\\nMUST READ: ${fileResult.filePath}\\n\\nUse your Read tool to open this file, read every line, then fix ALL issues. Do NOT stop at the prompt — keep working until every blocking issue is resolved and you have invoked /rebase-and-submit.`;\n const deliveryKind = reviewStatus === 'blocked' ? 'review-blocked' : 'review-failed';\n yield* Effect.promise(() => deliverQueuedFeedback(issueId, deliveryKind, fileResult.filePath!, msg));\n console.log(\n `[review-status] Auto-sent feedback to ${agentId} (file: ${fileResult.relativePath})`\n );\n }\n } catch (err) {\n console.error(`[review-status] Failed to send feedback to ${agentId}:`, err);\n }\n }\n\n if (reviewStatus === 'passed') {\n yield* Effect.promise(() => Effect.runPromise(eventStore.append({\n type: 'pipeline.review-completed',\n timestamp: new Date().toISOString(),\n payload: { issueId, passed: true },\n })));\n console.log(`[review-status] ${issueId} review approved; reactive Cloister will dispatch the test role`);\n } else if (['blocked', 'failed'].includes(reviewStatus)) {\n yield* Effect.promise(() => Effect.runPromise(eventStore.append({\n type: 'pipeline.review-completed',\n timestamp: new Date().toISOString(),\n payload: { issueId, passed: false },\n })));\n }\n }\n\n if (testStatus && ['passed', 'failed', 'skipped'].includes(testStatus)) {\n const tmuxSession = getTmuxSessionName('test-agent', projectKey);\n saveAgentRuntimeState(tmuxSession, {\n state: 'idle',\n currentIssue: undefined,\n lastActivity: new Date().toISOString(),\n });\n console.log(`[review-status] Set test-agent (${tmuxSession}) to idle`);\n\n if (testStatus === 'failed') {\n yield* Effect.promise(() => Effect.runPromise(eventStore.append({\n type: 'pipeline.test-completed',\n timestamp: new Date().toISOString(),\n payload: { issueId, passed: false },\n })));\n }\n\n if (testStatus === 'failed' && testNotes) {\n const agentId = `agent-${issueId.toLowerCase()}`;\n const feedbackBody = `TESTS FAILED for ${issueId}:\\n\\n${testNotes}\\n\\n## REQUIRED: Fix ALL test failures, then invoke the /rebase-and-submit skill\\n\\n1. Read each test failure carefully\\n2. Fix the code causing EVERY failure\\n3. Run the test suite locally to verify your fixes pass\\n4. Commit every change\\n5. Invoke the /rebase-and-submit skill for ${issueId} — this is an atomic task that runs pan done (which handles rebase + push + re-submit internally)\\n\\nDo NOT stop between steps. Do NOT run git push manually — the skill handles it. Do NOT stop until pan done has completed successfully.`;\n try {\n const { writeFeedbackFile } = yield* Effect.promise(() => import(\n '../../../lib/cloister/feedback-writer.js'\n ));\n const wsInfo = getWorkspaceInfoForIssue(issueId);\n const fileResult = yield* Effect.promise(() => writeFeedbackFile({\n issueId,\n workspacePath: wsInfo.localPath,\n specialist: 'test-agent',\n outcome: 'failed',\n summary: `Tests FAILED: ${(testNotes || '').slice(0, 80)}`,\n markdownBody: feedbackBody,\n }));\n if (!fileResult.success) {\n console.error(\n `[review-status] Failed to write test feedback file for ${issueId}: ${fileResult.error}`\n );\n } else {\n const msg = `SPECIALIST FEEDBACK: test-agent reported FAILED for ${issueId}.\\n\\nMUST READ: ${fileResult.filePath}\\n\\nUse your Read tool to open this file, read every line, then fix the failing tests and re-submit. Do NOT stop at the prompt — keep working until all tests pass and you have invoked /rebase-and-submit.`;\n yield* Effect.promise(() => deliverQueuedFeedback(issueId, 'test-failed', fileResult.filePath!, msg));\n console.log(\n `[review-status] Auto-sent test failure to ${agentId} (file: ${fileResult.relativePath})`\n );\n }\n } catch (err) {\n console.error(`[review-status] Failed to send test feedback to ${agentId}:`, err);\n }\n }\n\n if (testStatus === 'passed') {\n // Mark ready for merge when tests pass. Post-rebase verification in\n // triggerMerge() is the real quality gate — don't block on stale pre-merge verification.\n setReviewStatus(issueId, { readyForMerge: true });\n console.log(`[review-status] ${issueId} marked ready for merge after test=passed`);\n\n // Post overdeck/tests=success so the CI test job self-skips on this\n // commit. Mirrors what verification-runner does at the pre-review gate.\n yield* Effect.promise(async () => {\n try {\n const { resolveProjectFromIssueSync, getProjectSync } = await import('../../../lib/projects.js');\n const project = resolveProjectFromIssueSync(issueId);\n const projectCfg = project ? getProjectSync(project.projectKey) : null;\n const repo = projectCfg?.github_repo;\n if (!repo || !repo.includes('/')) return;\n const [owner, name] = repo.split('/');\n const wsInfo = getWorkspaceInfoForIssue(issueId);\n if (!wsInfo?.localPath) return;\n const { postOverdeckTestsStatus } = await import('../../../lib/github-app.js');\n await postOverdeckTestsStatus(wsInfo.localPath, owner!, name!, 'success', 'Test specialist passed');\n } catch (err: any) {\n console.warn(`[review-status] Failed to post overdeck/tests for ${issueId}: ${err.message}`);\n }\n });\n\n yield* Effect.promise(() => Effect.runPromise(eventStore.append({\n type: 'pipeline.test-completed',\n timestamp: new Date().toISOString(),\n payload: { issueId, passed: true },\n })));\n // Notify the whole-issue work agent when one exists. This MUST be an\n // Effect-level catch: a try/catch around `yield*` does not intercept a\n // failed Effect — the failure propagated and 500'd the entire verdict\n // POST after readyForMerge was only partially applied. Swarm issues\n // have no `agent-<issue>` at all, so this path failed on every\n // swarm test-pass (observed live on PAN-1791).\n const notifyAgentId = `agent-${issueId.toLowerCase()}`;\n yield* Effect.tryPromise(() => messageAgent(\n notifyAgentId,\n `ALL CHECKS PASSED for ${issueId}. Review: passed. Tests: passed. Your work is complete — ready for merge. You may stop working on this issue.`\n )).pipe(\n Effect.tap(() => Effect.sync(() => console.log(`[review-status] Notified ${notifyAgentId} that all checks passed`))),\n Effect.catch((err) => Effect.sync(() => console.log(\n `[review-status] Could not notify work agent for ${issueId} (may not be running): ${err instanceof Error ? err.message : String(err)}`\n ))),\n );\n }\n }\n\n return jsonResponse(status);\n }))\n);\n\n// ─── Route: POST /api/review/:issueId/reset ───────────────────────\n\n/** HTTP-contract result from the reset-review endpoint. Exported for unit testing. */\n\n// ─── Route: POST /api/workspaces/:issueId/unstick ────────────────────────\n//\n// Clears the persistent stuck flag set by markWorkspaceStuck() so Deacon\n// resumes normal patrol for this workspace. Does NOT restart the agent —\n// the user should do that separately via the start-agent UI once they have\n// resolved the divergence (e.g. by syncing main and re-approving).\n\n/** HTTP-contract result from the unstick endpoint. Exported for unit testing. */\n\n// ─── Route: POST /api/workspaces/:issueId/deacon-ignore ──────────────────\n\n/**\n * Operator toggle: tell Deacon to stop patrolling this issue. Body:\n * { ignored: boolean, reason?: string }\n *\n * Idempotent — calling with ignored=true repeatedly refreshes the timestamp\n * but otherwise no-ops. Separate from stuck/unstick: stuck is a system-set\n * failure marker, deaconIgnored is an explicit human \"hands off\".\n */\n\n// ─── Route: POST /api/workspaces/:issueId/auto-merge ─────────────────────\n\n/**\n * PAN-1691: operator toggle for the per-issue auto-merge routing key. Body:\n * { autoMerge: boolean | null }\n * `true` = auto-merge (fast lane), `false` = hold for UAT (manual lane),\n * `null` = clear back to the project default. Emits status_changed via the\n * setAutoMerge wrapper so open dashboards reflect the toggle live.\n */\n\n// ─── Route: POST /api/workspaces/:issueId/refresh-token ───────────────────────\n\nconst postWorkspaceRefreshTokenRoute = HttpRouter.add(\n 'POST',\n '/api/workspaces/:issueId/refresh-token',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const issueId = params['issueId'] ?? '';\n if (!parseIssueIdSync(issueId)) {\n return jsonResponse({ error: \"Invalid issue ID\" }, { status: 400 });\n }\n const issueLower = issueId.toLowerCase();\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPath(undefined, issuePrefix);\n const workspacePath = join(projectPath, 'workspaces', `feature-${issueLower}`);\n\n const { refreshWorkspaceToken, isGitHubAppConfigured } = yield* Effect.promise(() => import('../../../lib/github-app.js'));\n if (!isGitHubAppConfigured()) {\n return jsonResponse({ success: false, error: 'GitHub App not configured' }, { status: 400 });\n }\n\n yield* Effect.promise(() => refreshWorkspaceToken(workspacePath));\n return jsonResponse({ success: true, message: `Token refreshed for ${issueId}` });\n })),\n);\n\n// ─── Compose all routes into a single Layer ───────────────────────────────────\n\nexport const workspacesRouteLayer = Layer.mergeAll(\n workspaceDataRouteLayer,\n postWorkspaceRebuildRoute,\n postWorkspaceRebuildAndStartRoute,\n uatStackActionRouteLayer,\n getWorkspaceStateMdRoute,\n getWorkspaceInferenceMdRoute,\n stashCleanRouteLayer,\n containerOpsRouteLayer,\n postWorkspaceStartRoute,\n getWorkspaceReviewStatusRoute,\n postWorkspaceReviewStatusRoute,\n reviewPipelineRouteLayer,\n reviewControlRouteLayer,\n mergeOpsRouteLayer,\n postWorkspaceRefreshTokenRoute,\n);\n\nexport default workspacesRouteLayer;\n"],"mappings":"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"}