@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":"review-context-CCq56O_Y.js","names":["execAsync","promisify","exec","execAsync","promisify","exec"],"sources":["../../src/lib/cloister/stub-ui-patterns.ts","../../src/lib/cloister/lint-stub-ui.ts","../../src/lib/cloister/coderabbit-ingestion.ts","../../src/lib/cloister/review-context.ts"],"sourcesContent":["/**\n * Stub UI pattern catalog (PAN-1500 / PAN-1454 pattern 4).\n *\n * This module defines a deterministic, regex-based catalog of code shapes that\n * strongly suggest a PR is merging a UI stub: a new tab, mode, or routed view\n * whose data layer is faked, whose handlers no-op, or whose copy apologizes for\n * missing functionality. The catalog is best-effort, not exhaustive — extend it\n * when new stub shapes escape post-merge audits.\n *\n * The globs and patterns are consumed by `scanStubUi()` in\n * `src/lib/cloister/lint-stub-ui.ts`, which restricts inspection to added (+)\n * lines in frontend UI affordance files. Reviewers see the emitted findings on\n * the review context manifest and must block unless the diff also shows one of\n * the three valid mitigations: a feature flag guarding the affordance off,\n * removal of the affordance from the user-facing surface, or a non-stub\n * implementation calling real data.\n */\n\nexport type StubUiSeverity = 'block' | 'advisory';\n\nexport interface StubUiFinding {\n patternId: StubUiPatternId;\n patternLabel: string;\n filePath: string;\n lineNumber: number;\n addedLine: string;\n severity: StubUiSeverity;\n}\n\nexport interface StubUiPattern {\n id: StubUiPatternId;\n label: string;\n regex: RegExp;\n severity: StubUiSeverity;\n}\n\n/**\n * Micromatch-compatible globs naming the file types that can introduce\n * user-facing UI affordances. The scanner applies additional status logic:\n * - Added (A) files under `src/dashboard/frontend/src/components/` are in scope.\n * - Modified (M) files are in scope only when they match the `*Tab.tsx`,\n * `*Mode.tsx`, or `*View.tsx` suffix globs, because modifications to generic\n * components are unlikely to introduce brand-new affordances.\n */\nexport const STUB_UI_FILE_GLOBS = [\n 'src/dashboard/frontend/src/components/**/*Tab.tsx',\n 'src/dashboard/frontend/src/components/**/*Mode.tsx',\n 'src/dashboard/frontend/src/components/**/*View.tsx',\n 'src/dashboard/frontend/src/components/**/*.tsx',\n] as const;\n\n/**\n * Stable pattern identifiers. Downstream beads (scanner, prompt) can reference\n * these by name via `typeof STUB_UI_PATTERNS[number]['id']`.\n */\nexport type StubUiPatternId =\n | 'coming-soon-copy'\n | 'empty-array-return'\n | 'null-return'\n | 'use-state-false'\n | 'new-tab-or-route'\n | 'segmented-control-noop'\n | 'noop-handler';\n\n/**\n * Regex catalog of stub shapes. Each pattern is applied to individual added\n * lines, so patterns must not rely on multi-line context.\n */\nexport const STUB_UI_PATTERNS: StubUiPattern[] = [\n {\n id: 'coming-soon-copy',\n label: 'Copy suggests the feature is not implemented (\"coming soon\" / \"not yet implemented\")',\n // Ignore lines that begin with a // comment so TODO/notes don't drown out\n // real user-facing copy. JSX text and quoted strings are still caught.\n regex: /^(?!\\s*\\/\\/).*?\\b(?:[Cc]oming\\s+soon|[Nn]ot\\s+yet\\s+implemented)\\b/,\n severity: 'block',\n },\n {\n id: 'empty-array-return',\n label: 'Hook/function returns an empty array',\n regex: /^\\s*return\\s*\\[\\s*\\]\\s*;/,\n severity: 'block',\n },\n {\n id: 'null-return',\n label: 'Hook/function returns null',\n regex: /^\\s*return\\s+null\\s*;/,\n severity: 'block',\n },\n {\n id: 'use-state-false',\n label: 'Tab/mode state initialized to false with no other writer suspected',\n regex: /\\buseState\\s*\\(\\s*false\\s*\\)/,\n severity: 'advisory',\n },\n {\n id: 'new-tab-or-route',\n label: 'New Tab, TabPanel, or Route JSX entry added',\n regex: /<(?:Tab|TabPanel|Route)\\b/,\n severity: 'advisory',\n },\n {\n id: 'segmented-control-noop',\n label: 'Segmented-control or mode onChange handler is a no-op arrow function',\n // Matches both JSX prop form `onChange={() => {}}` and object form\n // `onChange: () => {},`. The outer braces are optional.\n regex: /onChange\\s*[:=]\\s*\\{?\\s*\\(\\s*\\)\\s*=>\\s*(?:\\{\\s*\\}|null|undefined)\\s*\\}?/,\n severity: 'block',\n },\n {\n id: 'noop-handler',\n label: 'Explicit onClick/onSelect/onSubmit handler is a no-op arrow function',\n // Matches JSX prop form `onClick={() => {}}` and object form\n // `onClick: () => {},`. Outer braces are optional.\n regex: /on(?:Click|Select|Submit)\\s*[:=]\\s*\\{?\\s*\\(\\s*\\)\\s*=>\\s*(?:\\{\\s*\\}|null|undefined)\\s*\\}?/,\n severity: 'block',\n },\n];\n\n/**\n * Convert a micromatch-style glob (supporting `*` and `**`) to a RegExp.\n * This is intentionally minimal: it covers the globs exported above and avoids\n * adding a dependency on `micromatch` for a small, stable set of patterns.\n */\nfunction globToRegex(glob: string): RegExp {\n let pattern = '';\n let i = 0;\n while (i < glob.length) {\n const c = glob[i];\n if (c === '*' && glob[i + 1] === '*') {\n if (glob[i + 2] === '/') {\n pattern += '(?:.*/)?';\n i += 3;\n continue;\n }\n pattern += '.*';\n i += 2;\n continue;\n }\n if (c === '*') {\n pattern += '[^/]*';\n i += 1;\n continue;\n }\n if (/[.+^${}()|[\\]\\\\]/.test(c)) {\n pattern += `\\\\${c}`;\n i += 1;\n continue;\n }\n pattern += c;\n i += 1;\n }\n return new RegExp(`^${pattern}$`);\n}\n\n/**\n * Test whether a repository-relative file path matches any of the stub-UI\n * file globs. This is the public entry point used by the scanner.\n */\nexport function matchesStubUiFileGlob(filePath: string): boolean {\n for (const glob of STUB_UI_FILE_GLOBS) {\n if (globToRegex(glob).test(filePath)) return true;\n }\n return false;\n}\n\n/**\n * Status-aware scope check. Added component files are in scope because they may\n * introduce brand-new affordances; modified files are inspected only when their\n * basename suggests they are UI affordance files (*Tab/*Mode/*View).\n */\nexport function isStubUiFileInScope(filePath: string, status: 'A' | 'M' | string): boolean {\n if (status === 'A') {\n return matchesStubUiFileGlob(filePath);\n }\n // Modified files: only the explicit affordance suffixes.\n const affordanceGlobs = STUB_UI_FILE_GLOBS.slice(0, 3);\n for (const glob of affordanceGlobs) {\n if (globToRegex(glob).test(filePath)) return true;\n }\n return false;\n}\n","/**\n * Stub-UI scanner (PAN-1500).\n *\n * Reads the PR diff for a workspace and emits structured findings for added\n * (+) lines that match the STUB_UI_PATTERNS catalog. The scanner restricts\n * inspection to frontend UI affordance files and degrades gracefully: any\n * internal failure returns an empty array and logs a non-fatal warning.\n */\n\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport {\n STUB_UI_PATTERNS,\n isStubUiFileInScope,\n type StubUiFinding,\n} from './stub-ui-patterns.js';\n\nexport type { StubUiFinding };\n\nconst execAsync = promisify(exec);\n\ninterface ChangedFileEntry {\n path: string;\n status: string;\n}\n\nasync function listChangedFiles(workspace: string, diffBase: string): Promise<ChangedFileEntry[]> {\n const { stdout } = await execAsync(\n `git diff --name-status \"${diffBase}\"...HEAD`,\n { cwd: workspace, encoding: 'utf-8', maxBuffer: 4 * 1024 * 1024 },\n );\n\n const entries: ChangedFileEntry[] = [];\n for (const line of stdout.split('\\n')) {\n if (!line.trim()) continue;\n const parts = line.split('\\t');\n const status = parts[0]?.[0] ?? 'M';\n const path = parts[parts.length - 1] ?? '';\n if (!path) continue;\n entries.push({ path, status });\n }\n return entries;\n}\n\n/**\n * Parse a unified-diff hunk header and return the destination starting line.\n * Headers look like `@@ -3,5 +8,9 @@` or `@@ -1 +1 @@`.\n */\nfunction parseHunkHeader(line: string): number | null {\n const match = line.match(/@@\\s+-\\d+(?:,\\d+)?\\s+\\+(\\d+)(?:,\\d+)?\\s+@@/);\n if (!match) return null;\n return parseInt(match[1], 10);\n}\n\n/**\n * Read the diff for a single file and yield every added line with its\n * destination line number. Uses `-U0` so there are no context lines to skip.\n */\nasync function* addedLinesForFile(\n workspace: string,\n diffBase: string,\n filePath: string,\n): AsyncGenerator<{ lineNumber: number; content: string }> {\n const { stdout } = await execAsync(\n `git diff -U0 \"${diffBase}\"...HEAD -- \"${filePath}\"`,\n { cwd: workspace, encoding: 'utf-8', maxBuffer: 4 * 1024 * 1024 },\n );\n\n let currentLine = 0;\n\n for (const rawLine of stdout.split('\\n')) {\n if (rawLine.startsWith('@@')) {\n const parsed = parseHunkHeader(rawLine);\n if (parsed !== null) {\n currentLine = parsed;\n }\n continue;\n }\n\n if (rawLine.startsWith('+++')) continue;\n if (rawLine.startsWith('---')) continue;\n\n if (rawLine.startsWith('+')) {\n const content = rawLine.slice(1);\n yield { lineNumber: currentLine, content };\n currentLine += 1;\n continue;\n }\n\n if (rawLine.startsWith(' ')) {\n currentLine += 1;\n continue;\n }\n\n // Deleted lines do not advance the destination line counter.\n }\n}\n\n/**\n * Scan the PR diff in `workspace` against `diffBase` for stub-UI patterns.\n *\n * Returns a list of findings; never throws. Internal failures (git errors,\n * malformed hunks, missing workspace) log a warning and return [].\n */\nexport async function scanStubUi(workspace: string, diffBase: string): Promise<StubUiFinding[]> {\n try {\n const changedFiles = await listChangedFiles(workspace, diffBase);\n const findings: StubUiFinding[] = [];\n\n for (const { path, status } of changedFiles) {\n if (!isStubUiFileInScope(path, status)) continue;\n\n for await (const { lineNumber, content } of addedLinesForFile(workspace, diffBase, path)) {\n for (const pattern of STUB_UI_PATTERNS) {\n if (pattern.regex.test(content)) {\n findings.push({\n patternId: pattern.id,\n patternLabel: pattern.label,\n filePath: path,\n lineNumber,\n addedLine: content.trimEnd(),\n severity: pattern.severity,\n });\n }\n }\n }\n }\n\n return findings;\n } catch (err) {\n console.warn(\n `[scanStubUi] Failed to scan workspace ${workspace} against ${diffBase}: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n return [];\n }\n}\n","/**\n * CodeRabbit review ingestion (PAN-2374).\n *\n * Quota invariant: CodeRabbit reviews PRs only sometimes. An empty finding\n * array means any of: no findings, quota miss, CodeRabbit absent, or fetch\n * error. Callers MUST treat all four identically — this module is advisory\n * and must never gate anything.\n */\n\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { lookupPullRequestNumberForBranch } from '../github-pr-lookup.js';\nimport { postPrComment } from './review-verdict-feedback.js';\n\nconst execAsync = promisify(exec);\n\nconst CODERABBIT_BOT_LOGIN = 'coderabbitai[bot]';\n\nexport interface CodeRabbitFinding {\n path?: string;\n line?: number;\n body: string;\n url?: string;\n}\n\n/**\n * Canonical reply template when a CodeRabbit finding has been addressed.\n *\n * Calling convention: `postCodeRabbitReply(prUrl, 'addressed', finding.body)`\n */\nexport const CODERABBIT_REPLY_ADDRESSED = (finding: string): string =>\n `Thanks for the finding — addressed: ${finding}`;\n\n/**\n * Canonical reply template when a CodeRabbit finding is rejected.\n *\n * Calling convention: `postCodeRabbitReply(prUrl, 'rejected', finding.body, rationale)`\n */\nexport const CODERABBIT_REPLY_REJECTED = (finding: string, rationale: string): string =>\n `Thanks for the finding — after review we're not applying this: ${finding}. Rationale: ${rationale}`;\n\n/**\n * Reply to a CodeRabbit finding on the PR thread.\n *\n * @param prUrl - The GitHub PR URL.\n * @param disposition - Whether the finding was addressed or rejected.\n * @param finding - The finding text (usually `finding.body`).\n * @param rationale - Required when disposition is 'rejected'; explains why.\n * @returns true if the comment was posted, false on any error (never throws).\n *\n * This is a best-effort reply path and MUST NOT read or write review status.\n */\nexport async function postCodeRabbitReply(\n prUrl: string,\n disposition: 'addressed' | 'rejected',\n finding: string,\n rationale?: string,\n): Promise<boolean> {\n try {\n const body =\n disposition === 'addressed'\n ? CODERABBIT_REPLY_ADDRESSED(finding)\n : CODERABBIT_REPLY_REJECTED(finding, rationale ?? 'no rationale provided');\n return await postPrComment(prUrl, body);\n } catch {\n return false;\n }\n}\n\nasync function resolveOwnerRepo(workspace: string): Promise<{ owner: string; repo: string } | null> {\n try {\n const { stdout } = await execAsync('gh repo view --json owner,name', {\n cwd: workspace,\n encoding: 'utf-8',\n timeout: 15000,\n });\n const parsed = JSON.parse(stdout) as { owner?: { login?: string }; name?: string };\n const owner = parsed.owner?.login;\n const repo = parsed.name;\n if (!owner || !repo) return null;\n return { owner, repo };\n } catch {\n return null;\n }\n}\n\nasync function fetchFindingsFromReviews(\n owner: string,\n repo: string,\n prNumber: number,\n): Promise<CodeRabbitFinding[]> {\n const { stdout } = await execAsync(\n `gh api \"repos/${owner}/${repo}/pulls/${prNumber}/reviews?per_page=100\"`,\n { encoding: 'utf-8', timeout: 15000, maxBuffer: 8 * 1024 * 1024 },\n );\n const arr = JSON.parse(stdout) as Array<{\n user?: { login?: string } | null;\n body?: string | null;\n state?: string;\n html_url?: string;\n }>;\n return arr\n .filter(r => r.user?.login === CODERABBIT_BOT_LOGIN && r.state !== 'DISMISSED')\n .map(r => ({\n body: r.body ?? '',\n url: r.html_url,\n }));\n}\n\nasync function fetchFindingsFromComments(\n owner: string,\n repo: string,\n prNumber: number,\n): Promise<CodeRabbitFinding[]> {\n const { stdout } = await execAsync(\n `gh api \"repos/${owner}/${repo}/pulls/${prNumber}/comments?per_page=100\"`,\n { encoding: 'utf-8', timeout: 15000, maxBuffer: 8 * 1024 * 1024 },\n );\n const arr = JSON.parse(stdout) as Array<{\n user?: { login?: string } | null;\n body?: string | null;\n html_url?: string;\n path?: string;\n line?: number | null;\n }>;\n return arr\n .filter(c => c.user?.login === CODERABBIT_BOT_LOGIN)\n .map(c => ({\n path: c.path,\n line: typeof c.line === 'number' ? c.line : undefined,\n body: c.body ?? '',\n url: c.html_url,\n }));\n}\n\nexport async function fetchCodeRabbitFindings(opts: {\n workspace: string;\n branch: string;\n}): Promise<CodeRabbitFinding[]> {\n try {\n const ownerRepo = await resolveOwnerRepo(opts.workspace);\n if (!ownerRepo) return [];\n const { owner, repo } = ownerRepo;\n\n const prNumber = await lookupPullRequestNumberForBranch(owner, repo, opts.branch);\n if (!prNumber) return [];\n\n const [reviewFindings, commentFindings] = await Promise.all([\n fetchFindingsFromReviews(owner, repo, prNumber).catch(() => [] as CodeRabbitFinding[]),\n fetchFindingsFromComments(owner, repo, prNumber).catch(() => [] as CodeRabbitFinding[]),\n ]);\n\n return [...reviewFindings, ...commentFindings];\n } catch (err) {\n console.warn(\n `[coderabbit-ingestion] fetch failed — skipping: ${err instanceof Error ? err.message : String(err)}`,\n );\n return [];\n }\n}\n","/**\n * Review Context Manifest Builder (PAN-1059)\n *\n * Builds a shared `.pan/review/<runId>/context.json` before spawning\n * any sub-reviewers. All four convoy agents read this file instead of\n * independently running `git diff` and reading every changed file,\n * eliminating ~4× redundant I/O and the token cost that goes with it.\n */\n\nimport { exec } from 'child_process';\nimport { existsSync } from 'fs';\nimport { mkdir, readFile, writeFile } from 'fs/promises';\nimport { join } from 'path';\nimport { promisify } from 'util';\nimport { Effect } from 'effect';\nimport { PAN_DIRNAME } from '../pan-dir/types.js';\nimport { findPlanSync, readPlan } from '../vbrief/io.js';\nimport { scanStubUi, type StubUiFinding } from './lint-stub-ui.js';\nimport { fetchCodeRabbitFindings, type CodeRabbitFinding } from './coderabbit-ingestion.js';\nimport { findVBriefByIssueSync } from '../vbrief/lifecycle-io.js';\nimport { getDevrootPathSync } from '../config.js';\nimport { FsError } from '../errors.js';\n\nconst execAsync = promisify(exec);\n\nexport const REVIEW_LARGE_CHANGESET_FILES = 25;\nexport const REVIEW_LARGE_CHANGESET_LINES = 1500;\n\n// The manifest no longer embeds raw diff text (PAN-1125).\n// Reviewers receive a concise inline summary in their spawn prompt and read\n// individual files on demand. The manifest carries metadata only: stat,\n// changedFiles, acceptanceCriteria, and policyNotes.\n\nconst RISK_HIGH = 5;\nconst RISK_MED = 3;\nconst RISK_LOW = 1;\n\n// Patterns that raise a file's risk score\nconst HIGH_RISK_PATTERNS = [\n /auth/i, /password/i, /token/i, /secret/i, /crypt/i,\n /permission/i, /privilege/i, /admin/i, /acl/i, /rbac/i,\n /payment/i, /billing/i, /stripe/i,\n /sql/i, /query/i, /inject/i,\n /exec\\b/i, /spawn/i, /shell/i, /eval\\b/i,\n];\n\nconst MED_RISK_PATTERNS = [\n /config/i, /env/i, /setting/i, /migration/i,\n /middleware/i, /route/i, /api/i,\n];\n\nconst LOW_RISK_PATTERNS = [\n /test/i, /spec/i, /mock/i, /fixture/i, /stub/i,\n /\\.md$/, /\\.txt$/, /\\.json$/, /README/i,\n];\n\nexport interface ChangedFile {\n path: string;\n status: 'M' | 'A' | 'D' | 'R' | 'C' | 'U';\n additions: number;\n deletions: number;\n riskScore: number;\n}\n\nexport interface ReviewItemTrace {\n itemId: string;\n title: string;\n traces: string[];\n}\n\nexport interface ReviewContextManifest {\n runId: string;\n issueId: string;\n generatedAt: string;\n branch: string;\n headSha: string;\n diff: {\n stat: string;\n truncated: boolean;\n };\n changedFiles: ChangedFile[];\n largeChangeset: {\n fileCount: number;\n changedLines: number;\n isLarge: boolean;\n };\n acceptanceCriteria: string[];\n nonGoals: string[];\n traces: ReviewItemTrace[];\n policyNotes: string[];\n stubUiFindings: StubUiFinding[];\n codeRabbitFindings: CodeRabbitFinding[];\n manifestPath: string;\n}\n\nfunction riskScore(filePath: string): number {\n if (LOW_RISK_PATTERNS.some(p => p.test(filePath))) return RISK_LOW;\n if (HIGH_RISK_PATTERNS.some(p => p.test(filePath))) return RISK_HIGH;\n if (MED_RISK_PATTERNS.some(p => p.test(filePath))) return RISK_MED;\n return 2; // default: between low and medium\n}\n\nasync function getHeadSha(cwd: string): Promise<string> {\n try {\n const { stdout } = await execAsync('git rev-parse HEAD', { cwd, encoding: 'utf-8' });\n return stdout.trim();\n } catch {\n return 'unknown';\n }\n}\n\nasync function getCurrentBranch(cwd: string): Promise<string> {\n try {\n const { stdout } = await execAsync('git branch --show-current', { cwd, encoding: 'utf-8' });\n return stdout.trim() || 'unknown';\n } catch {\n return 'unknown';\n }\n}\n\nexport async function getDiffBase(cwd: string): Promise<string> {\n try {\n const { stdout } = await execAsync('git merge-base origin/main HEAD', { cwd, encoding: 'utf-8' });\n return stdout.trim();\n } catch {\n try {\n const { stdout } = await execAsync('git merge-base main HEAD', { cwd, encoding: 'utf-8' });\n return stdout.trim();\n } catch {\n return 'main';\n }\n }\n}\n\nexport async function getChangedFiles(cwd: string, base: string): Promise<ChangedFile[]> {\n // --name-status gives us the status letter + path\n let nameStatus = '';\n try {\n const { stdout } = await execAsync(\n `git diff --name-status \"${base}\"...HEAD`,\n { cwd, encoding: 'utf-8', maxBuffer: 4 * 1024 * 1024 },\n );\n nameStatus = stdout;\n } catch {\n return [];\n }\n\n // --numstat gives us additions + deletions per file\n const numstatMap = new Map<string, { additions: number; deletions: number }>();\n try {\n const { stdout } = await execAsync(\n `git diff --numstat \"${base}\"...HEAD`,\n { cwd, encoding: 'utf-8', maxBuffer: 4 * 1024 * 1024 },\n );\n for (const line of stdout.split('\\n')) {\n const parts = line.split('\\t');\n if (parts.length >= 3) {\n const additions = parseInt(parts[0], 10) || 0;\n const deletions = parseInt(parts[1], 10) || 0;\n // Binary files show '-'; treat as 0\n numstatMap.set(parts[2], { additions, deletions });\n }\n }\n } catch {\n // numstat failure is non-fatal; additions/deletions default to 0\n }\n\n const files: ChangedFile[] = [];\n for (const line of nameStatus.split('\\n')) {\n if (!line.trim()) continue;\n const [statusChar, ...pathParts] = line.split('\\t');\n const path = pathParts[pathParts.length - 1] ?? '';\n if (!path) continue;\n\n const statusLetter = (statusChar?.[0] ?? 'M') as ChangedFile['status'];\n const counts = numstatMap.get(path) ?? { additions: 0, deletions: 0 };\n\n files.push({\n path,\n status: statusLetter,\n additions: counts.additions,\n deletions: counts.deletions,\n riskScore: riskScore(path),\n });\n }\n\n // Sort descending by risk score so reviewers see hotspots first\n return files.sort((a, b) => b.riskScore - a.riskScore);\n}\n\nexport async function getDiffStat(cwd: string, base: string): Promise<{ stat: string; truncated: boolean }> {\n let stat = '';\n\n try {\n const { stdout } = await execAsync(\n `git diff --stat \"${base}\"...HEAD`,\n { cwd, encoding: 'utf-8', maxBuffer: 4 * 1024 * 1024 },\n );\n stat = stdout.trim() || 'No changes';\n } catch {\n stat = 'Unable to compute diff stat';\n }\n\n // We no longer embed raw diff text in the manifest (PAN-1125).\n // Reviewers read individual files via Read/Grep as needed.\n return { stat, truncated: true };\n}\n\ninterface PlanReviewRequirements {\n acceptanceCriteria: string[];\n nonGoals: string[];\n traces: ReviewItemTrace[];\n}\n\nasync function extractPlanReviewRequirements(workspace: string, issueId: string): Promise<PlanReviewRequirements> {\n // Try workspace-local spec first\n const planPath = findPlanSync(workspace);\n if (planPath) {\n try {\n const doc = await Effect.runPromise(readPlan(planPath));\n return {\n acceptanceCriteria: flattenAC(doc),\n nonGoals: flattenNonGoals(doc),\n traces: flattenTraces(doc),\n };\n } catch {\n // Fall through to lifecycle lookup\n }\n }\n\n // Try project-root lifecycle directories\n try {\n const projectRoot = getDevrootPathSync();\n if (!projectRoot) return { acceptanceCriteria: [], nonGoals: [], traces: [] };\n const found = findVBriefByIssueSync(projectRoot, issueId);\n if (found) {\n return {\n acceptanceCriteria: flattenAC(found.document),\n nonGoals: flattenNonGoals(found.document),\n traces: flattenTraces(found.document),\n };\n }\n } catch {\n // Non-fatal\n }\n\n return { acceptanceCriteria: [], nonGoals: [], traces: [] };\n}\n\ninterface PanItem {\n id?: string;\n title?: string;\n acceptanceCriteria?: string[];\n subItems?: Array<{ title?: string; description?: string }>;\n metadata?: Record<string, unknown>;\n}\n\nfunction flattenAC(doc: { plan?: { items?: PanItem[] } }): string[] {\n const acs: string[] = [];\n for (const item of doc?.plan?.items ?? []) {\n // Planning agent writes acceptanceCriteria directly on items\n if (Array.isArray(item.acceptanceCriteria)) {\n acs.push(...item.acceptanceCriteria);\n }\n // Standard vBRIEF v0.5 sub-items\n for (const sub of item.subItems ?? []) {\n const text = sub.title ?? sub.description ?? '';\n if (text) acs.push(text);\n }\n }\n return acs;\n}\n\nfunction flattenNonGoals(doc: { plan?: { narratives?: Record<string, string | undefined> } }): string[] {\n const raw = doc.plan?.narratives?.NonGoals?.trim();\n if (!raw || raw.toLowerCase() === 'none') return [];\n return raw\n .split('\\n')\n .map(line => line.trim().replace(/^[-*]\\s*/, '').trim())\n .filter(Boolean);\n}\n\nfunction flattenTraces(doc: { plan?: { items?: PanItem[] } }): ReviewItemTrace[] {\n const traces: ReviewItemTrace[] = [];\n for (const item of doc.plan?.items ?? []) {\n const raw = item.metadata?.traces;\n if (!Array.isArray(raw)) continue;\n const itemTraces = raw.filter((trace): trace is string => typeof trace === 'string' && trace.trim().length > 0);\n if (itemTraces.length === 0) continue;\n traces.push({\n itemId: item.id ?? '<unknown>',\n title: item.title ?? '',\n traces: itemTraces,\n });\n }\n return traces;\n}\n\nasync function readPolicyNotes(workspace: string): Promise<string[]> {\n const notes: string[] = [];\n\n // Pull CRITICAL rules from CLAUDE.md\n const claudeMdPath = join(workspace, 'CLAUDE.md');\n if (existsSync(claudeMdPath)) {\n try {\n const content = await readFile(claudeMdPath, 'utf-8');\n const criticalLines = content\n .split('\\n')\n .filter(l => l.startsWith('## CRITICAL') || l.startsWith('**NEVER') || l.startsWith('**CRITICAL'))\n .map(l => l.replace(/^#+\\s*/, '').replace(/^\\*+/, '').trim())\n .filter(Boolean);\n notes.push(...criticalLines.slice(0, 10));\n } catch {\n // Non-fatal\n }\n }\n\n return notes;\n}\n\nexport interface BuildReviewContextOpts {\n runId: string;\n issueId: string;\n workspace: string;\n branch?: string;\n}\n\n/**\n * Build and persist the review context manifest for a review run.\n *\n * Returns the manifest object and its path on disk.\n * Throws if the workspace directory does not exist.\n */\n/**\n * Format a concise Tier-1 inline summary from manifest fields.\n *\n * This summary is embedded directly into each convoy reviewer's spawn prompt\n * so they receive scope, priority, and constraints without reading a large\n * manifest file first (PAN-1125).\n */\nexport function formatTier1Summary(\n manifest: Pick<\n ReviewContextManifest,\n | 'issueId' | 'branch' | 'headSha' | 'changedFiles' | 'acceptanceCriteria' | 'nonGoals' | 'traces' | 'policyNotes' | 'stubUiFindings' | 'codeRabbitFindings' | 'diff' | 'largeChangeset'\n >,\n): string {\n const lines: string[] = [];\n\n lines.push(`Issue: ${manifest.issueId}`);\n lines.push(`Branch: ${manifest.branch}`);\n lines.push(`Head: ${manifest.headSha}`);\n\n const highRisk = manifest.changedFiles.filter((f) => f.riskScore >= 5);\n const medRisk = manifest.changedFiles.filter((f) => f.riskScore >= 3 && f.riskScore < 5);\n const lowRisk = manifest.changedFiles.filter((f) => f.riskScore < 3);\n lines.push(\n `Files changed: ${manifest.changedFiles.length} (${highRisk.length} high-risk, ${medRisk.length} medium, ${lowRisk.length} low)`,\n );\n\n if (manifest.changedFiles.length > 0) {\n lines.push('');\n lines.push('Changed files (risk-ranked):');\n for (const f of manifest.changedFiles.slice(0, 15)) {\n const riskLabel = f.riskScore >= 5 ? 'HIGH' : f.riskScore >= 3 ? 'MED' : 'LOW';\n lines.push(` ${f.path.padEnd(40)} (+${f.additions}/-${f.deletions}) risk: ${riskLabel}`);\n }\n if (manifest.changedFiles.length > 15) {\n lines.push(` ... and ${manifest.changedFiles.length - 15} more (see manifest)`);\n }\n }\n\n if (manifest.largeChangeset?.isLarge) {\n lines.push('');\n lines.push(`LARGE CHANGESET: ${manifest.largeChangeset.fileCount} files, ${manifest.largeChangeset.changedLines} changed lines.`);\n lines.push('Read the highest-risk files first, then medium-risk. Read only changed regions, not whole files.');\n lines.push('If you approach the context limit, write a report on what you reviewed and flag any uncovered high-risk files as a BLOCKING coverage gap.');\n lines.push('Do not read everything and overflow; do not approve changes you have not reviewed.');\n }\n\n if (manifest.acceptanceCriteria.length > 0) {\n lines.push('');\n lines.push('Acceptance criteria:');\n for (const [i, ac] of manifest.acceptanceCriteria.slice(0, 7).entries()) {\n lines.push(` ${i + 1}. ${ac}`);\n }\n if (manifest.acceptanceCriteria.length > 7) {\n lines.push(` ... and ${manifest.acceptanceCriteria.length - 7} more (see manifest)`);\n }\n }\n\n if (manifest.nonGoals.length > 0) {\n lines.push('');\n lines.push('NonGoals / must-not constraints:');\n for (const [i, nonGoal] of manifest.nonGoals.slice(0, 7).entries()) {\n lines.push(` ${i + 1}. ${nonGoal}`);\n }\n if (manifest.nonGoals.length > 7) {\n lines.push(` ... and ${manifest.nonGoals.length - 7} more (see manifest)`);\n }\n }\n\n if (manifest.traces.length > 0) {\n lines.push('');\n lines.push('Requirement traces:');\n for (const trace of manifest.traces.slice(0, 7)) {\n lines.push(` ${trace.itemId}: ${trace.traces.join(', ')}`);\n }\n if (manifest.traces.length > 7) {\n lines.push(` ... and ${manifest.traces.length - 7} more (see manifest)`);\n }\n }\n\n if (manifest.policyNotes.length > 0) {\n lines.push('');\n lines.push('Policy notes:');\n for (const note of manifest.policyNotes.slice(0, 5)) {\n lines.push(` - ${note}`);\n }\n if (manifest.policyNotes.length > 5) {\n lines.push(` ... and ${manifest.policyNotes.length - 5} more (see manifest)`);\n }\n }\n\n if (manifest.stubUiFindings.length > 0) {\n lines.push('');\n lines.push('Stub UI findings (BLOCKING if unmitigated):');\n for (const finding of manifest.stubUiFindings.slice(0, 10)) {\n lines.push(` - ${finding.patternLabel} @ ${finding.filePath}:${finding.lineNumber}`);\n }\n if (manifest.stubUiFindings.length > 10) {\n lines.push(` ... and ${manifest.stubUiFindings.length - 10} more (see manifest)`);\n }\n }\n\n if (manifest.codeRabbitFindings.length > 0) {\n lines.push('');\n lines.push('CodeRabbit findings (ADVISORY — non-gating; context, not acceptance criteria):');\n for (const finding of manifest.codeRabbitFindings.slice(0, 10)) {\n const location = finding.path ? `${finding.path}${typeof finding.line === 'number' ? `:${finding.line}` : ''}` : 'general';\n const bodyPreview = finding.body.slice(0, 200);\n lines.push(` - ${location} ${bodyPreview}${finding.body.length > 200 ? '...' : ''}`);\n }\n if (manifest.codeRabbitFindings.length > 10) {\n lines.push(` ... and ${manifest.codeRabbitFindings.length - 10} more (see manifest)`);\n }\n }\n\n lines.push('');\n lines.push(`Diff stat: ${manifest.diff.stat}`);\n\n return lines.join('\\n');\n}export async function buildReviewContextPromise(opts: BuildReviewContextOpts): Promise<ReviewContextManifest> {\n const { runId, issueId, workspace } = opts;\n\n if (!existsSync(workspace)) {\n throw new Error(`Workspace directory does not exist: ${workspace}`);\n }\n\n const [headSha, currentBranch, diffBase] = await Promise.all([\n getHeadSha(workspace),\n opts.branch ? Promise.resolve(opts.branch) : getCurrentBranch(workspace),\n getDiffBase(workspace),\n ]);\n\n const [changedFiles, diff] = await Promise.all([\n getChangedFiles(workspace, diffBase),\n getDiffStat(workspace, diffBase),\n ]);\n\n const [planRequirements, policyNotes, stubUiFindings, codeRabbitFindings] = await Promise.all([\n extractPlanReviewRequirements(workspace, issueId),\n readPolicyNotes(workspace),\n scanStubUi(workspace, diffBase).catch((err) => {\n console.warn(`[buildReviewContext] scanStubUi failed: ${err instanceof Error ? err.message : String(err)}`);\n return [] as StubUiFinding[];\n }),\n fetchCodeRabbitFindings({ workspace, branch: currentBranch }).catch((err) => {\n console.warn(`[buildReviewContext] CodeRabbit ingestion failed: ${err instanceof Error ? err.message : String(err)}`);\n return [] as CodeRabbitFinding[];\n }),\n ]);\n\n const fileCount = changedFiles.length;\n const changedLines = changedFiles.reduce((sum, f) => sum + f.additions + f.deletions, 0);\n const largeChangeset = {\n fileCount,\n changedLines,\n isLarge: fileCount > REVIEW_LARGE_CHANGESET_FILES || changedLines > REVIEW_LARGE_CHANGESET_LINES,\n };\n\n const manifestDir = join(workspace, PAN_DIRNAME, 'review', runId);\n await mkdir(manifestDir, { recursive: true });\n const manifestPath = join(manifestDir, 'context.json');\n\n const manifest: ReviewContextManifest = {\n runId,\n issueId,\n generatedAt: new Date().toISOString(),\n branch: currentBranch,\n headSha,\n diff,\n changedFiles,\n largeChangeset,\n acceptanceCriteria: planRequirements.acceptanceCriteria,\n nonGoals: planRequirements.nonGoals,\n traces: planRequirements.traces,\n policyNotes,\n stubUiFindings,\n codeRabbitFindings,\n manifestPath,\n };\n\n await writeFile(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');\n\n return manifest;\n}\n\n// ─── Effect variant (PAN-1249) ───────────────────────────────────────────────\n\n/**\n * Effect variant of {@link buildReviewContext}. Wraps the Promise-based\n * implementation in `Effect.tryPromise` so callers in Effect pipelines can\n * compose it with typed error channels. The git probes inside\n * {@link buildReviewContext} are best-effort — they fall back to sentinel\n * strings instead of failing — so the only error this Effect can surface is\n * the workspace-not-found {@link FsError}.\n */\nexport const buildReviewContext = (\n opts: BuildReviewContextOpts,\n): Effect.Effect<ReviewContextManifest, FsError> =>\n Effect.tryPromise({\n try: () => buildReviewContextPromise(opts),\n catch: (cause) =>\n new FsError({\n path: opts.workspace,\n operation: 'buildReviewContext',\n cause,\n }),\n });\n"],"mappings":"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"}
1
+ {"version":3,"file":"review-context-Dyo0tsBa.js","names":["execAsync","promisify","exec","execAsync","promisify","exec"],"sources":["../../src/lib/cloister/stub-ui-patterns.ts","../../src/lib/cloister/lint-stub-ui.ts","../../src/lib/cloister/coderabbit-ingestion.ts","../../src/lib/cloister/review-context.ts"],"sourcesContent":["/**\n * Stub UI pattern catalog (PAN-1500 / PAN-1454 pattern 4).\n *\n * This module defines a deterministic, regex-based catalog of code shapes that\n * strongly suggest a PR is merging a UI stub: a new tab, mode, or routed view\n * whose data layer is faked, whose handlers no-op, or whose copy apologizes for\n * missing functionality. The catalog is best-effort, not exhaustive — extend it\n * when new stub shapes escape post-merge audits.\n *\n * The globs and patterns are consumed by `scanStubUi()` in\n * `src/lib/cloister/lint-stub-ui.ts`, which restricts inspection to added (+)\n * lines in frontend UI affordance files. Reviewers see the emitted findings on\n * the review context manifest and must block unless the diff also shows one of\n * the three valid mitigations: a feature flag guarding the affordance off,\n * removal of the affordance from the user-facing surface, or a non-stub\n * implementation calling real data.\n */\n\nexport type StubUiSeverity = 'block' | 'advisory';\n\nexport interface StubUiFinding {\n patternId: StubUiPatternId;\n patternLabel: string;\n filePath: string;\n lineNumber: number;\n addedLine: string;\n severity: StubUiSeverity;\n}\n\nexport interface StubUiPattern {\n id: StubUiPatternId;\n label: string;\n regex: RegExp;\n severity: StubUiSeverity;\n}\n\n/**\n * Micromatch-compatible globs naming the file types that can introduce\n * user-facing UI affordances. The scanner applies additional status logic:\n * - Added (A) files under `src/dashboard/frontend/src/components/` are in scope.\n * - Modified (M) files are in scope only when they match the `*Tab.tsx`,\n * `*Mode.tsx`, or `*View.tsx` suffix globs, because modifications to generic\n * components are unlikely to introduce brand-new affordances.\n */\nexport const STUB_UI_FILE_GLOBS = [\n 'src/dashboard/frontend/src/components/**/*Tab.tsx',\n 'src/dashboard/frontend/src/components/**/*Mode.tsx',\n 'src/dashboard/frontend/src/components/**/*View.tsx',\n 'src/dashboard/frontend/src/components/**/*.tsx',\n] as const;\n\n/**\n * Stable pattern identifiers. Downstream beads (scanner, prompt) can reference\n * these by name via `typeof STUB_UI_PATTERNS[number]['id']`.\n */\nexport type StubUiPatternId =\n | 'coming-soon-copy'\n | 'empty-array-return'\n | 'null-return'\n | 'use-state-false'\n | 'new-tab-or-route'\n | 'segmented-control-noop'\n | 'noop-handler';\n\n/**\n * Regex catalog of stub shapes. Each pattern is applied to individual added\n * lines, so patterns must not rely on multi-line context.\n */\nexport const STUB_UI_PATTERNS: StubUiPattern[] = [\n {\n id: 'coming-soon-copy',\n label: 'Copy suggests the feature is not implemented (\"coming soon\" / \"not yet implemented\")',\n // Ignore lines that begin with a // comment so TODO/notes don't drown out\n // real user-facing copy. JSX text and quoted strings are still caught.\n regex: /^(?!\\s*\\/\\/).*?\\b(?:[Cc]oming\\s+soon|[Nn]ot\\s+yet\\s+implemented)\\b/,\n severity: 'block',\n },\n {\n id: 'empty-array-return',\n label: 'Hook/function returns an empty array',\n regex: /^\\s*return\\s*\\[\\s*\\]\\s*;/,\n severity: 'block',\n },\n {\n id: 'null-return',\n label: 'Hook/function returns null',\n regex: /^\\s*return\\s+null\\s*;/,\n severity: 'block',\n },\n {\n id: 'use-state-false',\n label: 'Tab/mode state initialized to false with no other writer suspected',\n regex: /\\buseState\\s*\\(\\s*false\\s*\\)/,\n severity: 'advisory',\n },\n {\n id: 'new-tab-or-route',\n label: 'New Tab, TabPanel, or Route JSX entry added',\n regex: /<(?:Tab|TabPanel|Route)\\b/,\n severity: 'advisory',\n },\n {\n id: 'segmented-control-noop',\n label: 'Segmented-control or mode onChange handler is a no-op arrow function',\n // Matches both JSX prop form `onChange={() => {}}` and object form\n // `onChange: () => {},`. The outer braces are optional.\n regex: /onChange\\s*[:=]\\s*\\{?\\s*\\(\\s*\\)\\s*=>\\s*(?:\\{\\s*\\}|null|undefined)\\s*\\}?/,\n severity: 'block',\n },\n {\n id: 'noop-handler',\n label: 'Explicit onClick/onSelect/onSubmit handler is a no-op arrow function',\n // Matches JSX prop form `onClick={() => {}}` and object form\n // `onClick: () => {},`. Outer braces are optional.\n regex: /on(?:Click|Select|Submit)\\s*[:=]\\s*\\{?\\s*\\(\\s*\\)\\s*=>\\s*(?:\\{\\s*\\}|null|undefined)\\s*\\}?/,\n severity: 'block',\n },\n];\n\n/**\n * Convert a micromatch-style glob (supporting `*` and `**`) to a RegExp.\n * This is intentionally minimal: it covers the globs exported above and avoids\n * adding a dependency on `micromatch` for a small, stable set of patterns.\n */\nfunction globToRegex(glob: string): RegExp {\n let pattern = '';\n let i = 0;\n while (i < glob.length) {\n const c = glob[i];\n if (c === '*' && glob[i + 1] === '*') {\n if (glob[i + 2] === '/') {\n pattern += '(?:.*/)?';\n i += 3;\n continue;\n }\n pattern += '.*';\n i += 2;\n continue;\n }\n if (c === '*') {\n pattern += '[^/]*';\n i += 1;\n continue;\n }\n if (/[.+^${}()|[\\]\\\\]/.test(c)) {\n pattern += `\\\\${c}`;\n i += 1;\n continue;\n }\n pattern += c;\n i += 1;\n }\n return new RegExp(`^${pattern}$`);\n}\n\n/**\n * Test whether a repository-relative file path matches any of the stub-UI\n * file globs. This is the public entry point used by the scanner.\n */\nexport function matchesStubUiFileGlob(filePath: string): boolean {\n for (const glob of STUB_UI_FILE_GLOBS) {\n if (globToRegex(glob).test(filePath)) return true;\n }\n return false;\n}\n\n/**\n * Status-aware scope check. Added component files are in scope because they may\n * introduce brand-new affordances; modified files are inspected only when their\n * basename suggests they are UI affordance files (*Tab/*Mode/*View).\n */\nexport function isStubUiFileInScope(filePath: string, status: 'A' | 'M' | string): boolean {\n if (status === 'A') {\n return matchesStubUiFileGlob(filePath);\n }\n // Modified files: only the explicit affordance suffixes.\n const affordanceGlobs = STUB_UI_FILE_GLOBS.slice(0, 3);\n for (const glob of affordanceGlobs) {\n if (globToRegex(glob).test(filePath)) return true;\n }\n return false;\n}\n","/**\n * Stub-UI scanner (PAN-1500).\n *\n * Reads the PR diff for a workspace and emits structured findings for added\n * (+) lines that match the STUB_UI_PATTERNS catalog. The scanner restricts\n * inspection to frontend UI affordance files and degrades gracefully: any\n * internal failure returns an empty array and logs a non-fatal warning.\n */\n\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport {\n STUB_UI_PATTERNS,\n isStubUiFileInScope,\n type StubUiFinding,\n} from './stub-ui-patterns.js';\n\nexport type { StubUiFinding };\n\nconst execAsync = promisify(exec);\n\ninterface ChangedFileEntry {\n path: string;\n status: string;\n}\n\nasync function listChangedFiles(workspace: string, diffBase: string): Promise<ChangedFileEntry[]> {\n const { stdout } = await execAsync(\n `git diff --name-status \"${diffBase}\"...HEAD`,\n { cwd: workspace, encoding: 'utf-8', maxBuffer: 4 * 1024 * 1024 },\n );\n\n const entries: ChangedFileEntry[] = [];\n for (const line of stdout.split('\\n')) {\n if (!line.trim()) continue;\n const parts = line.split('\\t');\n const status = parts[0]?.[0] ?? 'M';\n const path = parts[parts.length - 1] ?? '';\n if (!path) continue;\n entries.push({ path, status });\n }\n return entries;\n}\n\n/**\n * Parse a unified-diff hunk header and return the destination starting line.\n * Headers look like `@@ -3,5 +8,9 @@` or `@@ -1 +1 @@`.\n */\nfunction parseHunkHeader(line: string): number | null {\n const match = line.match(/@@\\s+-\\d+(?:,\\d+)?\\s+\\+(\\d+)(?:,\\d+)?\\s+@@/);\n if (!match) return null;\n return parseInt(match[1], 10);\n}\n\n/**\n * Read the diff for a single file and yield every added line with its\n * destination line number. Uses `-U0` so there are no context lines to skip.\n */\nasync function* addedLinesForFile(\n workspace: string,\n diffBase: string,\n filePath: string,\n): AsyncGenerator<{ lineNumber: number; content: string }> {\n const { stdout } = await execAsync(\n `git diff -U0 \"${diffBase}\"...HEAD -- \"${filePath}\"`,\n { cwd: workspace, encoding: 'utf-8', maxBuffer: 4 * 1024 * 1024 },\n );\n\n let currentLine = 0;\n\n for (const rawLine of stdout.split('\\n')) {\n if (rawLine.startsWith('@@')) {\n const parsed = parseHunkHeader(rawLine);\n if (parsed !== null) {\n currentLine = parsed;\n }\n continue;\n }\n\n if (rawLine.startsWith('+++')) continue;\n if (rawLine.startsWith('---')) continue;\n\n if (rawLine.startsWith('+')) {\n const content = rawLine.slice(1);\n yield { lineNumber: currentLine, content };\n currentLine += 1;\n continue;\n }\n\n if (rawLine.startsWith(' ')) {\n currentLine += 1;\n continue;\n }\n\n // Deleted lines do not advance the destination line counter.\n }\n}\n\n/**\n * Scan the PR diff in `workspace` against `diffBase` for stub-UI patterns.\n *\n * Returns a list of findings; never throws. Internal failures (git errors,\n * malformed hunks, missing workspace) log a warning and return [].\n */\nexport async function scanStubUi(workspace: string, diffBase: string): Promise<StubUiFinding[]> {\n try {\n const changedFiles = await listChangedFiles(workspace, diffBase);\n const findings: StubUiFinding[] = [];\n\n for (const { path, status } of changedFiles) {\n if (!isStubUiFileInScope(path, status)) continue;\n\n for await (const { lineNumber, content } of addedLinesForFile(workspace, diffBase, path)) {\n for (const pattern of STUB_UI_PATTERNS) {\n if (pattern.regex.test(content)) {\n findings.push({\n patternId: pattern.id,\n patternLabel: pattern.label,\n filePath: path,\n lineNumber,\n addedLine: content.trimEnd(),\n severity: pattern.severity,\n });\n }\n }\n }\n }\n\n return findings;\n } catch (err) {\n console.warn(\n `[scanStubUi] Failed to scan workspace ${workspace} against ${diffBase}: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n return [];\n }\n}\n","/**\n * CodeRabbit review ingestion (PAN-2374).\n *\n * Quota invariant: CodeRabbit reviews PRs only sometimes. An empty finding\n * array means any of: no findings, quota miss, CodeRabbit absent, or fetch\n * error. Callers MUST treat all four identically — this module is advisory\n * and must never gate anything.\n */\n\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { lookupPullRequestNumberForBranch } from '../github-pr-lookup.js';\nimport { postPrComment } from './review-verdict-feedback.js';\n\nconst execAsync = promisify(exec);\n\nconst CODERABBIT_BOT_LOGIN = 'coderabbitai[bot]';\n\nexport interface CodeRabbitFinding {\n path?: string;\n line?: number;\n body: string;\n url?: string;\n}\n\n/**\n * Canonical reply template when a CodeRabbit finding has been addressed.\n *\n * Calling convention: `postCodeRabbitReply(prUrl, 'addressed', finding.body)`\n */\nexport const CODERABBIT_REPLY_ADDRESSED = (finding: string): string =>\n `Thanks for the finding — addressed: ${finding}`;\n\n/**\n * Canonical reply template when a CodeRabbit finding is rejected.\n *\n * Calling convention: `postCodeRabbitReply(prUrl, 'rejected', finding.body, rationale)`\n */\nexport const CODERABBIT_REPLY_REJECTED = (finding: string, rationale: string): string =>\n `Thanks for the finding — after review we're not applying this: ${finding}. Rationale: ${rationale}`;\n\n/**\n * Reply to a CodeRabbit finding on the PR thread.\n *\n * @param prUrl - The GitHub PR URL.\n * @param disposition - Whether the finding was addressed or rejected.\n * @param finding - The finding text (usually `finding.body`).\n * @param rationale - Required when disposition is 'rejected'; explains why.\n * @returns true if the comment was posted, false on any error (never throws).\n *\n * This is a best-effort reply path and MUST NOT read or write review status.\n */\nexport async function postCodeRabbitReply(\n prUrl: string,\n disposition: 'addressed' | 'rejected',\n finding: string,\n rationale?: string,\n): Promise<boolean> {\n try {\n const body =\n disposition === 'addressed'\n ? CODERABBIT_REPLY_ADDRESSED(finding)\n : CODERABBIT_REPLY_REJECTED(finding, rationale ?? 'no rationale provided');\n return await postPrComment(prUrl, body);\n } catch {\n return false;\n }\n}\n\nasync function resolveOwnerRepo(workspace: string): Promise<{ owner: string; repo: string } | null> {\n try {\n const { stdout } = await execAsync('gh repo view --json owner,name', {\n cwd: workspace,\n encoding: 'utf-8',\n timeout: 15000,\n });\n const parsed = JSON.parse(stdout) as { owner?: { login?: string }; name?: string };\n const owner = parsed.owner?.login;\n const repo = parsed.name;\n if (!owner || !repo) return null;\n return { owner, repo };\n } catch {\n return null;\n }\n}\n\nasync function fetchFindingsFromReviews(\n owner: string,\n repo: string,\n prNumber: number,\n): Promise<CodeRabbitFinding[]> {\n const { stdout } = await execAsync(\n `gh api \"repos/${owner}/${repo}/pulls/${prNumber}/reviews?per_page=100\"`,\n { encoding: 'utf-8', timeout: 15000, maxBuffer: 8 * 1024 * 1024 },\n );\n const arr = JSON.parse(stdout) as Array<{\n user?: { login?: string } | null;\n body?: string | null;\n state?: string;\n html_url?: string;\n }>;\n return arr\n .filter(r => r.user?.login === CODERABBIT_BOT_LOGIN && r.state !== 'DISMISSED')\n .map(r => ({\n body: r.body ?? '',\n url: r.html_url,\n }));\n}\n\nasync function fetchFindingsFromComments(\n owner: string,\n repo: string,\n prNumber: number,\n): Promise<CodeRabbitFinding[]> {\n const { stdout } = await execAsync(\n `gh api \"repos/${owner}/${repo}/pulls/${prNumber}/comments?per_page=100\"`,\n { encoding: 'utf-8', timeout: 15000, maxBuffer: 8 * 1024 * 1024 },\n );\n const arr = JSON.parse(stdout) as Array<{\n user?: { login?: string } | null;\n body?: string | null;\n html_url?: string;\n path?: string;\n line?: number | null;\n }>;\n return arr\n .filter(c => c.user?.login === CODERABBIT_BOT_LOGIN)\n .map(c => ({\n path: c.path,\n line: typeof c.line === 'number' ? c.line : undefined,\n body: c.body ?? '',\n url: c.html_url,\n }));\n}\n\nexport async function fetchCodeRabbitFindings(opts: {\n workspace: string;\n branch: string;\n}): Promise<CodeRabbitFinding[]> {\n try {\n const ownerRepo = await resolveOwnerRepo(opts.workspace);\n if (!ownerRepo) return [];\n const { owner, repo } = ownerRepo;\n\n const prNumber = await lookupPullRequestNumberForBranch(owner, repo, opts.branch);\n if (!prNumber) return [];\n\n const [reviewFindings, commentFindings] = await Promise.all([\n fetchFindingsFromReviews(owner, repo, prNumber).catch(() => [] as CodeRabbitFinding[]),\n fetchFindingsFromComments(owner, repo, prNumber).catch(() => [] as CodeRabbitFinding[]),\n ]);\n\n return [...reviewFindings, ...commentFindings];\n } catch (err) {\n console.warn(\n `[coderabbit-ingestion] fetch failed — skipping: ${err instanceof Error ? err.message : String(err)}`,\n );\n return [];\n }\n}\n","/**\n * Review Context Manifest Builder (PAN-1059)\n *\n * Builds a shared `.pan/review/<runId>/context.json` before spawning\n * any sub-reviewers. All four convoy agents read this file instead of\n * independently running `git diff` and reading every changed file,\n * eliminating ~4× redundant I/O and the token cost that goes with it.\n */\n\nimport { exec } from 'child_process';\nimport { existsSync } from 'fs';\nimport { mkdir, readFile, writeFile } from 'fs/promises';\nimport { join } from 'path';\nimport { promisify } from 'util';\nimport { Effect } from 'effect';\nimport { PAN_DIRNAME } from '../pan-dir/types.js';\nimport { findPlanSync, readPlan } from '../vbrief/io.js';\nimport { scanStubUi, type StubUiFinding } from './lint-stub-ui.js';\nimport { fetchCodeRabbitFindings, type CodeRabbitFinding } from './coderabbit-ingestion.js';\nimport { findVBriefByIssueSync } from '../vbrief/lifecycle-io.js';\nimport { getDevrootPathSync } from '../config.js';\nimport { FsError } from '../errors.js';\n\nconst execAsync = promisify(exec);\n\nexport const REVIEW_LARGE_CHANGESET_FILES = 25;\nexport const REVIEW_LARGE_CHANGESET_LINES = 1500;\n\n// The manifest no longer embeds raw diff text (PAN-1125).\n// Reviewers receive a concise inline summary in their spawn prompt and read\n// individual files on demand. The manifest carries metadata only: stat,\n// changedFiles, acceptanceCriteria, and policyNotes.\n\nconst RISK_HIGH = 5;\nconst RISK_MED = 3;\nconst RISK_LOW = 1;\n\n// Patterns that raise a file's risk score\nconst HIGH_RISK_PATTERNS = [\n /auth/i, /password/i, /token/i, /secret/i, /crypt/i,\n /permission/i, /privilege/i, /admin/i, /acl/i, /rbac/i,\n /payment/i, /billing/i, /stripe/i,\n /sql/i, /query/i, /inject/i,\n /exec\\b/i, /spawn/i, /shell/i, /eval\\b/i,\n];\n\nconst MED_RISK_PATTERNS = [\n /config/i, /env/i, /setting/i, /migration/i,\n /middleware/i, /route/i, /api/i,\n];\n\nconst LOW_RISK_PATTERNS = [\n /test/i, /spec/i, /mock/i, /fixture/i, /stub/i,\n /\\.md$/, /\\.txt$/, /\\.json$/, /README/i,\n];\n\nexport interface ChangedFile {\n path: string;\n status: 'M' | 'A' | 'D' | 'R' | 'C' | 'U';\n additions: number;\n deletions: number;\n riskScore: number;\n}\n\nexport interface ReviewItemTrace {\n itemId: string;\n title: string;\n traces: string[];\n}\n\nexport interface ReviewContextManifest {\n runId: string;\n issueId: string;\n generatedAt: string;\n branch: string;\n headSha: string;\n diff: {\n stat: string;\n truncated: boolean;\n };\n changedFiles: ChangedFile[];\n largeChangeset: {\n fileCount: number;\n changedLines: number;\n isLarge: boolean;\n };\n acceptanceCriteria: string[];\n nonGoals: string[];\n traces: ReviewItemTrace[];\n policyNotes: string[];\n stubUiFindings: StubUiFinding[];\n codeRabbitFindings: CodeRabbitFinding[];\n manifestPath: string;\n}\n\nfunction riskScore(filePath: string): number {\n if (LOW_RISK_PATTERNS.some(p => p.test(filePath))) return RISK_LOW;\n if (HIGH_RISK_PATTERNS.some(p => p.test(filePath))) return RISK_HIGH;\n if (MED_RISK_PATTERNS.some(p => p.test(filePath))) return RISK_MED;\n return 2; // default: between low and medium\n}\n\nasync function getHeadSha(cwd: string): Promise<string> {\n try {\n const { stdout } = await execAsync('git rev-parse HEAD', { cwd, encoding: 'utf-8' });\n return stdout.trim();\n } catch {\n return 'unknown';\n }\n}\n\nasync function getCurrentBranch(cwd: string): Promise<string> {\n try {\n const { stdout } = await execAsync('git branch --show-current', { cwd, encoding: 'utf-8' });\n return stdout.trim() || 'unknown';\n } catch {\n return 'unknown';\n }\n}\n\nexport async function getDiffBase(cwd: string): Promise<string> {\n try {\n const { stdout } = await execAsync('git merge-base origin/main HEAD', { cwd, encoding: 'utf-8' });\n return stdout.trim();\n } catch {\n try {\n const { stdout } = await execAsync('git merge-base main HEAD', { cwd, encoding: 'utf-8' });\n return stdout.trim();\n } catch {\n return 'main';\n }\n }\n}\n\nexport async function getChangedFiles(cwd: string, base: string): Promise<ChangedFile[]> {\n // --name-status gives us the status letter + path\n let nameStatus = '';\n try {\n const { stdout } = await execAsync(\n `git diff --name-status \"${base}\"...HEAD`,\n { cwd, encoding: 'utf-8', maxBuffer: 4 * 1024 * 1024 },\n );\n nameStatus = stdout;\n } catch {\n return [];\n }\n\n // --numstat gives us additions + deletions per file\n const numstatMap = new Map<string, { additions: number; deletions: number }>();\n try {\n const { stdout } = await execAsync(\n `git diff --numstat \"${base}\"...HEAD`,\n { cwd, encoding: 'utf-8', maxBuffer: 4 * 1024 * 1024 },\n );\n for (const line of stdout.split('\\n')) {\n const parts = line.split('\\t');\n if (parts.length >= 3) {\n const additions = parseInt(parts[0], 10) || 0;\n const deletions = parseInt(parts[1], 10) || 0;\n // Binary files show '-'; treat as 0\n numstatMap.set(parts[2], { additions, deletions });\n }\n }\n } catch {\n // numstat failure is non-fatal; additions/deletions default to 0\n }\n\n const files: ChangedFile[] = [];\n for (const line of nameStatus.split('\\n')) {\n if (!line.trim()) continue;\n const [statusChar, ...pathParts] = line.split('\\t');\n const path = pathParts[pathParts.length - 1] ?? '';\n if (!path) continue;\n\n const statusLetter = (statusChar?.[0] ?? 'M') as ChangedFile['status'];\n const counts = numstatMap.get(path) ?? { additions: 0, deletions: 0 };\n\n files.push({\n path,\n status: statusLetter,\n additions: counts.additions,\n deletions: counts.deletions,\n riskScore: riskScore(path),\n });\n }\n\n // Sort descending by risk score so reviewers see hotspots first\n return files.sort((a, b) => b.riskScore - a.riskScore);\n}\n\nexport async function getDiffStat(cwd: string, base: string): Promise<{ stat: string; truncated: boolean }> {\n let stat = '';\n\n try {\n const { stdout } = await execAsync(\n `git diff --stat \"${base}\"...HEAD`,\n { cwd, encoding: 'utf-8', maxBuffer: 4 * 1024 * 1024 },\n );\n stat = stdout.trim() || 'No changes';\n } catch {\n stat = 'Unable to compute diff stat';\n }\n\n // We no longer embed raw diff text in the manifest (PAN-1125).\n // Reviewers read individual files via Read/Grep as needed.\n return { stat, truncated: true };\n}\n\ninterface PlanReviewRequirements {\n acceptanceCriteria: string[];\n nonGoals: string[];\n traces: ReviewItemTrace[];\n}\n\nasync function extractPlanReviewRequirements(workspace: string, issueId: string): Promise<PlanReviewRequirements> {\n // Try workspace-local spec first\n const planPath = findPlanSync(workspace);\n if (planPath) {\n try {\n const doc = await Effect.runPromise(readPlan(planPath));\n return {\n acceptanceCriteria: flattenAC(doc),\n nonGoals: flattenNonGoals(doc),\n traces: flattenTraces(doc),\n };\n } catch {\n // Fall through to lifecycle lookup\n }\n }\n\n // Try project-root lifecycle directories\n try {\n const projectRoot = getDevrootPathSync();\n if (!projectRoot) return { acceptanceCriteria: [], nonGoals: [], traces: [] };\n const found = findVBriefByIssueSync(projectRoot, issueId);\n if (found) {\n return {\n acceptanceCriteria: flattenAC(found.document),\n nonGoals: flattenNonGoals(found.document),\n traces: flattenTraces(found.document),\n };\n }\n } catch {\n // Non-fatal\n }\n\n return { acceptanceCriteria: [], nonGoals: [], traces: [] };\n}\n\ninterface PanItem {\n id?: string;\n title?: string;\n acceptanceCriteria?: string[];\n subItems?: Array<{ title?: string; description?: string }>;\n metadata?: Record<string, unknown>;\n}\n\nfunction flattenAC(doc: { plan?: { items?: PanItem[] } }): string[] {\n const acs: string[] = [];\n for (const item of doc?.plan?.items ?? []) {\n // Planning agent writes acceptanceCriteria directly on items\n if (Array.isArray(item.acceptanceCriteria)) {\n acs.push(...item.acceptanceCriteria);\n }\n // Standard vBRIEF v0.5 sub-items\n for (const sub of item.subItems ?? []) {\n const text = sub.title ?? sub.description ?? '';\n if (text) acs.push(text);\n }\n }\n return acs;\n}\n\nfunction flattenNonGoals(doc: { plan?: { narratives?: Record<string, string | undefined> } }): string[] {\n const raw = doc.plan?.narratives?.NonGoals?.trim();\n if (!raw || raw.toLowerCase() === 'none') return [];\n return raw\n .split('\\n')\n .map(line => line.trim().replace(/^[-*]\\s*/, '').trim())\n .filter(Boolean);\n}\n\nfunction flattenTraces(doc: { plan?: { items?: PanItem[] } }): ReviewItemTrace[] {\n const traces: ReviewItemTrace[] = [];\n for (const item of doc.plan?.items ?? []) {\n const raw = item.metadata?.traces;\n if (!Array.isArray(raw)) continue;\n const itemTraces = raw.filter((trace): trace is string => typeof trace === 'string' && trace.trim().length > 0);\n if (itemTraces.length === 0) continue;\n traces.push({\n itemId: item.id ?? '<unknown>',\n title: item.title ?? '',\n traces: itemTraces,\n });\n }\n return traces;\n}\n\nasync function readPolicyNotes(workspace: string): Promise<string[]> {\n const notes: string[] = [];\n\n // Pull CRITICAL rules from CLAUDE.md\n const claudeMdPath = join(workspace, 'CLAUDE.md');\n if (existsSync(claudeMdPath)) {\n try {\n const content = await readFile(claudeMdPath, 'utf-8');\n const criticalLines = content\n .split('\\n')\n .filter(l => l.startsWith('## CRITICAL') || l.startsWith('**NEVER') || l.startsWith('**CRITICAL'))\n .map(l => l.replace(/^#+\\s*/, '').replace(/^\\*+/, '').trim())\n .filter(Boolean);\n notes.push(...criticalLines.slice(0, 10));\n } catch {\n // Non-fatal\n }\n }\n\n return notes;\n}\n\nexport interface BuildReviewContextOpts {\n runId: string;\n issueId: string;\n workspace: string;\n branch?: string;\n}\n\n/**\n * Build and persist the review context manifest for a review run.\n *\n * Returns the manifest object and its path on disk.\n * Throws if the workspace directory does not exist.\n */\n/**\n * Format a concise Tier-1 inline summary from manifest fields.\n *\n * This summary is embedded directly into each convoy reviewer's spawn prompt\n * so they receive scope, priority, and constraints without reading a large\n * manifest file first (PAN-1125).\n */\nexport function formatTier1Summary(\n manifest: Pick<\n ReviewContextManifest,\n | 'issueId' | 'branch' | 'headSha' | 'changedFiles' | 'acceptanceCriteria' | 'nonGoals' | 'traces' | 'policyNotes' | 'stubUiFindings' | 'codeRabbitFindings' | 'diff' | 'largeChangeset'\n >,\n): string {\n const lines: string[] = [];\n\n lines.push(`Issue: ${manifest.issueId}`);\n lines.push(`Branch: ${manifest.branch}`);\n lines.push(`Head: ${manifest.headSha}`);\n\n const highRisk = manifest.changedFiles.filter((f) => f.riskScore >= 5);\n const medRisk = manifest.changedFiles.filter((f) => f.riskScore >= 3 && f.riskScore < 5);\n const lowRisk = manifest.changedFiles.filter((f) => f.riskScore < 3);\n lines.push(\n `Files changed: ${manifest.changedFiles.length} (${highRisk.length} high-risk, ${medRisk.length} medium, ${lowRisk.length} low)`,\n );\n\n if (manifest.changedFiles.length > 0) {\n lines.push('');\n lines.push('Changed files (risk-ranked):');\n for (const f of manifest.changedFiles.slice(0, 15)) {\n const riskLabel = f.riskScore >= 5 ? 'HIGH' : f.riskScore >= 3 ? 'MED' : 'LOW';\n lines.push(` ${f.path.padEnd(40)} (+${f.additions}/-${f.deletions}) risk: ${riskLabel}`);\n }\n if (manifest.changedFiles.length > 15) {\n lines.push(` ... and ${manifest.changedFiles.length - 15} more (see manifest)`);\n }\n }\n\n if (manifest.largeChangeset?.isLarge) {\n lines.push('');\n lines.push(`LARGE CHANGESET: ${manifest.largeChangeset.fileCount} files, ${manifest.largeChangeset.changedLines} changed lines.`);\n lines.push('Read the highest-risk files first, then medium-risk. Read only changed regions, not whole files.');\n lines.push('If you approach the context limit, write a report on what you reviewed and flag any uncovered high-risk files as a BLOCKING coverage gap.');\n lines.push('Do not read everything and overflow; do not approve changes you have not reviewed.');\n }\n\n if (manifest.acceptanceCriteria.length > 0) {\n lines.push('');\n lines.push('Acceptance criteria:');\n for (const [i, ac] of manifest.acceptanceCriteria.slice(0, 7).entries()) {\n lines.push(` ${i + 1}. ${ac}`);\n }\n if (manifest.acceptanceCriteria.length > 7) {\n lines.push(` ... and ${manifest.acceptanceCriteria.length - 7} more (see manifest)`);\n }\n }\n\n if (manifest.nonGoals.length > 0) {\n lines.push('');\n lines.push('NonGoals / must-not constraints:');\n for (const [i, nonGoal] of manifest.nonGoals.slice(0, 7).entries()) {\n lines.push(` ${i + 1}. ${nonGoal}`);\n }\n if (manifest.nonGoals.length > 7) {\n lines.push(` ... and ${manifest.nonGoals.length - 7} more (see manifest)`);\n }\n }\n\n if (manifest.traces.length > 0) {\n lines.push('');\n lines.push('Requirement traces:');\n for (const trace of manifest.traces.slice(0, 7)) {\n lines.push(` ${trace.itemId}: ${trace.traces.join(', ')}`);\n }\n if (manifest.traces.length > 7) {\n lines.push(` ... and ${manifest.traces.length - 7} more (see manifest)`);\n }\n }\n\n if (manifest.policyNotes.length > 0) {\n lines.push('');\n lines.push('Policy notes:');\n for (const note of manifest.policyNotes.slice(0, 5)) {\n lines.push(` - ${note}`);\n }\n if (manifest.policyNotes.length > 5) {\n lines.push(` ... and ${manifest.policyNotes.length - 5} more (see manifest)`);\n }\n }\n\n if (manifest.stubUiFindings.length > 0) {\n lines.push('');\n lines.push('Stub UI findings (BLOCKING if unmitigated):');\n for (const finding of manifest.stubUiFindings.slice(0, 10)) {\n lines.push(` - ${finding.patternLabel} @ ${finding.filePath}:${finding.lineNumber}`);\n }\n if (manifest.stubUiFindings.length > 10) {\n lines.push(` ... and ${manifest.stubUiFindings.length - 10} more (see manifest)`);\n }\n }\n\n if (manifest.codeRabbitFindings.length > 0) {\n lines.push('');\n lines.push('CodeRabbit findings (ADVISORY — non-gating; context, not acceptance criteria):');\n for (const finding of manifest.codeRabbitFindings.slice(0, 10)) {\n const location = finding.path ? `${finding.path}${typeof finding.line === 'number' ? `:${finding.line}` : ''}` : 'general';\n const bodyPreview = finding.body.slice(0, 200);\n lines.push(` - ${location} ${bodyPreview}${finding.body.length > 200 ? '...' : ''}`);\n }\n if (manifest.codeRabbitFindings.length > 10) {\n lines.push(` ... and ${manifest.codeRabbitFindings.length - 10} more (see manifest)`);\n }\n }\n\n lines.push('');\n lines.push(`Diff stat: ${manifest.diff.stat}`);\n\n return lines.join('\\n');\n}export async function buildReviewContextPromise(opts: BuildReviewContextOpts): Promise<ReviewContextManifest> {\n const { runId, issueId, workspace } = opts;\n\n if (!existsSync(workspace)) {\n throw new Error(`Workspace directory does not exist: ${workspace}`);\n }\n\n const [headSha, currentBranch, diffBase] = await Promise.all([\n getHeadSha(workspace),\n opts.branch ? Promise.resolve(opts.branch) : getCurrentBranch(workspace),\n getDiffBase(workspace),\n ]);\n\n const [changedFiles, diff] = await Promise.all([\n getChangedFiles(workspace, diffBase),\n getDiffStat(workspace, diffBase),\n ]);\n\n const [planRequirements, policyNotes, stubUiFindings, codeRabbitFindings] = await Promise.all([\n extractPlanReviewRequirements(workspace, issueId),\n readPolicyNotes(workspace),\n scanStubUi(workspace, diffBase).catch((err) => {\n console.warn(`[buildReviewContext] scanStubUi failed: ${err instanceof Error ? err.message : String(err)}`);\n return [] as StubUiFinding[];\n }),\n fetchCodeRabbitFindings({ workspace, branch: currentBranch }).catch((err) => {\n console.warn(`[buildReviewContext] CodeRabbit ingestion failed: ${err instanceof Error ? err.message : String(err)}`);\n return [] as CodeRabbitFinding[];\n }),\n ]);\n\n const fileCount = changedFiles.length;\n const changedLines = changedFiles.reduce((sum, f) => sum + f.additions + f.deletions, 0);\n const largeChangeset = {\n fileCount,\n changedLines,\n isLarge: fileCount > REVIEW_LARGE_CHANGESET_FILES || changedLines > REVIEW_LARGE_CHANGESET_LINES,\n };\n\n const manifestDir = join(workspace, PAN_DIRNAME, 'review', runId);\n await mkdir(manifestDir, { recursive: true });\n const manifestPath = join(manifestDir, 'context.json');\n\n const manifest: ReviewContextManifest = {\n runId,\n issueId,\n generatedAt: new Date().toISOString(),\n branch: currentBranch,\n headSha,\n diff,\n changedFiles,\n largeChangeset,\n acceptanceCriteria: planRequirements.acceptanceCriteria,\n nonGoals: planRequirements.nonGoals,\n traces: planRequirements.traces,\n policyNotes,\n stubUiFindings,\n codeRabbitFindings,\n manifestPath,\n };\n\n await writeFile(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');\n\n return manifest;\n}\n\n// ─── Effect variant (PAN-1249) ───────────────────────────────────────────────\n\n/**\n * Effect variant of {@link buildReviewContext}. Wraps the Promise-based\n * implementation in `Effect.tryPromise` so callers in Effect pipelines can\n * compose it with typed error channels. The git probes inside\n * {@link buildReviewContext} are best-effort — they fall back to sentinel\n * strings instead of failing — so the only error this Effect can surface is\n * the workspace-not-found {@link FsError}.\n */\nexport const buildReviewContext = (\n opts: BuildReviewContextOpts,\n): Effect.Effect<ReviewContextManifest, FsError> =>\n Effect.tryPromise({\n try: () => buildReviewContextPromise(opts),\n catch: (cause) =>\n new FsError({\n path: opts.workspace,\n operation: 'buildReviewContext',\n cause,\n }),\n });\n"],"mappings":"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"}
@@ -377,6 +377,18 @@ async function buildIssueRecord(project, issueId, opts = {}) {
377
377
  };
378
378
  }
379
379
  /**
380
+ * PAN-2587: never regress the pipeline block during a whole-record rebuild. A
381
+ * concurrent writer (e.g. a reviewer's verdict landing between the rebuild's
382
+ * `existing` snapshot and its write) may have put a NEWER pipeline on disk than
383
+ * the status the rebuild projects — observed clobbering PAN-399's passed
384
+ * verdict back to a pre-verdict snapshot. Same newer-wins rule (ISO `updatedAt`
385
+ * comparison) the journal readers use.
386
+ */
387
+ function pickNewerPipeline(rebuilt, fresh) {
388
+ if (!fresh?.updatedAt || !rebuilt?.updatedAt) return rebuilt;
389
+ return rebuilt.updatedAt < fresh.updatedAt ? fresh : rebuilt;
390
+ }
391
+ /**
380
392
  * PAN-1908 / PAN-1919: rebuild and queue the per-issue permanent record for a
381
393
  * given issue. Fire-and-forget: failures are logged, never thrown, so
382
394
  * review-status writes stay synchronous and fast.
@@ -400,6 +412,7 @@ async function updateIssueRecordForIssue(issueId, reviewStatus) {
400
412
  ...record.statusOverrides,
401
413
  ...fresh.statusOverrides
402
414
  };
415
+ record.pipeline = pickNewerPipeline(record.pipeline, fresh?.pipeline);
403
416
  queueIssueRecordCommit(project, issueId, writeIssueRecordSync(project, issueId, record));
404
417
  });
405
418
  return true;
@@ -1056,7 +1069,7 @@ function resetPipelineVerdictsForWorkStartSync(issueId) {
1056
1069
  */
1057
1070
  async function deliverReviewVerdictFeedbackHostSide(issueId, status) {
1058
1071
  try {
1059
- const { deliverReviewVerdictFeedback } = await import("./review-verdict-feedback-cZh9Wi9G.js");
1072
+ const { deliverReviewVerdictFeedback } = await import("./review-verdict-feedback-CorgH49f.js");
1060
1073
  if ((await Effect.runPromise(deliverReviewVerdictFeedback({
1061
1074
  issueId,
1062
1075
  verdict: status.reviewStatus === "failed" ? "failed" : "blocked",
@@ -1105,7 +1118,7 @@ async function dispatchReviewHostSide(issueId, prUrl) {
1105
1118
  });
1106
1119
  branch = stdout.trim() || branch;
1107
1120
  } catch {}
1108
- const { spawnReviewRoleForIssue } = await import("./review-agent-DgySDb-G.js");
1121
+ const { spawnReviewRoleForIssue } = await import("./review-agent-DTINEffB.js");
1109
1122
  if ((await Effect.runPromise(spawnReviewRoleForIssue({
1110
1123
  issueId,
1111
1124
  workspace,
@@ -1165,10 +1178,10 @@ async function deliverTestFailureToWorkAgentHostSide(issueId, status) {
1165
1178
  if (r.success) feedbackPath = r.filePath;
1166
1179
  } catch {}
1167
1180
  const message = `SPECIALIST FEEDBACK: test-agent reported FAILED for ${issueId}.\n\n${feedbackPath ? `MUST READ: ${feedbackPath}\n\n` : ""}${notes ? `${notes.slice(0, 400)}\n\n` : ""}Fix the failing tests, commit and push, then re-run pan done ${issueId}. Do NOT stop at the prompt.`;
1168
- const { resolveIssueFeedbackTarget, surfaceIssueFeedbackNeedsYou } = await import("./feedback-target-C4DaDSZ8.js");
1181
+ const { resolveIssueFeedbackTarget, surfaceIssueFeedbackNeedsYou } = await import("./feedback-target-nQSM6ygL.js");
1169
1182
  const target = await resolveIssueFeedbackTarget(issueId);
1170
1183
  if ("agentId" in target) {
1171
- const { messageAgent } = await import("./agents-BIggLvi_.js");
1184
+ const { messageAgent } = await import("./agents-C-vqmKDi.js");
1172
1185
  await messageAgent(target.agentId, message);
1173
1186
  console.log(`[review-status] delivered test failure to ${target.agentId} for ${issueId} (host-side)`);
1174
1187
  } else await surfaceIssueFeedbackNeedsYou(issueId, target.reason, {
@@ -1401,4 +1414,4 @@ var init_review_status = __esmMin((() => {
1401
1414
  //#endregion
1402
1415
  export { listOverdeckAgentStatesSync as A, init_record_lock as C, AGENT_COLUMNS_FOR_DB as D, readReviewStatusMap as E, stateToOverdeckParamsForDb as M, getOverdeckAgentStateSync as O, needsReviewDispatch as S, init_review_status_source as T, setDeaconIgnored as _, fixStuckReadyForMerge as a, verificationSatisfied as b, init_review_status as c, loadReviewStatusesForIssues as d, markWorkspaceStuck as f, setAutoMerge as g, reviewGatesPassedSync as h, fixStuckCommentedReviews as i, saveOverdeckAgentStateSync as j, init_agent_state_sync as k, loadReadyForMergeFlags as l, resetPipelineVerdictsForWorkStartSync as m, clearReviewStatus as n, getReviewStatus as o, mergeGateEligibility as p, clearStuckMergeStatuses as r, getReviewStatusSync as s, ReviewStatusError as t, loadReviewStatuses as u, setReviewStatus as v, withIssueRecordLock as w, init_review_dispatch_decision as x, setReviewStatusSync as y };
1403
1416
 
1404
- //# sourceMappingURL=review-status-BQjqQXZG.js.map
1417
+ //# sourceMappingURL=review-status-B5ZASOo4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review-status-B5ZASOo4.js","names":["join","fsp","join","dirname"],"sources":["../../src/lib/overdeck/agent-state-sync.ts","../../src/lib/cloister/review-status-source.ts","../../src/lib/pan-dir/record-lock.ts","../../src/lib/pan-dir/records.ts","../../src/lib/overdeck/review-status-record-sync.ts","../../src/lib/review-dispatch-decision.ts","../../src/lib/review-status.ts"],"sourcesContent":["import type { AgentState, Role } from '../agents.js';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport { getOverdeckDatabaseSync } from './infra.js';\n\ntype OverdeckAgentRow = {\n id: string;\n issue_id: string;\n role: string;\n status: string;\n workspace: string;\n session_id: string | null;\n harness: string | null;\n model: string | null;\n host_override: string | null;\n delivery_method: string | null;\n started_at: number | null;\n last_resume_at: number | null;\n stopped_by_user: number | null;\n stopped_by_pause: number | null;\n kickoff_delivered: number | null;\n paused: number | null;\n paused_reason: string | null;\n yielded_by_scheduler: number | null;\n yielded_at: number | null;\n last_yield_resume_at: number | null;\n troubled: number | null;\n channels_enabled: number | null;\n consecutive_failures: number | null;\n first_failure_in_run_at: number | null;\n last_failure_next_retry_at: number | null;\n stopped_at: number | null;\n paused_at: number | null;\n troubled_at: number | null;\n last_activity: number | null;\n last_failure_reason: string | null;\n phase: string | null;\n role_run_head: string | null;\n flywheel_run_id: string | null;\n cost_so_far: number | null;\n review_sub_role: string | null;\n review_run_id: string | null;\n review_synthesis_agent_id: string | null;\n review_output_path: string | null;\n review_deadline_at: number | null;\n review_monitor_signaled: string | null;\n review_retry_attempt: number | null;\n updated_at: number;\n};\n\n/** Exported for agent-projection.ts transactional writes. */\nexport const AGENT_COLUMNS_FOR_DB = [\n 'id',\n 'issue_id',\n 'role',\n 'status',\n 'workspace',\n 'session_id',\n 'harness',\n 'model',\n 'host_override',\n 'delivery_method',\n 'started_at',\n 'last_resume_at',\n 'stopped_by_user',\n 'stopped_by_pause',\n 'kickoff_delivered',\n 'paused',\n 'paused_reason',\n 'yielded_by_scheduler',\n 'yielded_at',\n 'last_yield_resume_at',\n 'troubled',\n 'channels_enabled',\n 'consecutive_failures',\n 'first_failure_in_run_at',\n 'last_failure_next_retry_at',\n 'stopped_at',\n 'paused_at',\n 'troubled_at',\n 'last_activity',\n 'last_failure_reason',\n 'phase',\n 'role_run_head',\n 'flywheel_run_id',\n 'cost_so_far',\n 'review_sub_role',\n 'review_run_id',\n 'review_synthesis_agent_id',\n 'review_output_path',\n 'review_deadline_at',\n 'review_monitor_signaled',\n 'review_retry_attempt',\n 'updated_at',\n] as const;\n\nconst SELECT_AGENT_SQL = `SELECT ${AGENT_COLUMNS_FOR_DB.join(', ')} FROM agents`;\n\nfunction isoFromMillis(value: number | null | undefined): string | undefined {\n return value == null ? undefined : new Date(value).toISOString();\n}\n\nfunction millisFromIso(value: string | null | undefined): number | null {\n if (!value) return null;\n const millis = Date.parse(value);\n return Number.isFinite(millis) ? millis : null;\n}\n\nfunction boolFromInteger(value: number | null | undefined): boolean | undefined {\n return value == null ? undefined : Boolean(value);\n}\n\nfunction hostOverrideFromRow(value: string | null | undefined): boolean | undefined {\n if (value == null) return undefined;\n return value === '1' || value === 'true' || value === 'yes';\n}\n\nfunction hostOverrideToRow(value: boolean | undefined): string | null {\n return value === true ? 'true' : null;\n}\n\nfunction overdeckRowToAgentState(row: OverdeckAgentRow): AgentState {\n const deliveryMethod = row.delivery_method as AgentState['deliveryMethod'] | null;\n return {\n id: row.id,\n issueId: row.issue_id,\n workspace: row.workspace,\n role: row.role as Role,\n model: row.model ?? '',\n status: row.status as AgentState['status'],\n startedAt: isoFromMillis(row.started_at) ?? isoFromMillis(row.updated_at) ?? new Date().toISOString(),\n harness: row.harness ? (row.harness as RuntimeName) : undefined,\n lastResumeAt: isoFromMillis(row.last_resume_at),\n stoppedByUser: boolFromInteger(row.stopped_by_user),\n stoppedByPause: boolFromInteger(row.stopped_by_pause),\n kickoffDelivered: boolFromInteger(row.kickoff_delivered),\n paused: boolFromInteger(row.paused),\n pausedReason: row.paused_reason ?? undefined,\n yieldedByScheduler: boolFromInteger(row.yielded_by_scheduler),\n yieldedAt: isoFromMillis(row.yielded_at),\n lastYieldResumeAt: isoFromMillis(row.last_yield_resume_at),\n troubled: boolFromInteger(row.troubled),\n consecutiveFailures: row.consecutive_failures ?? undefined,\n firstFailureInRunAt: isoFromMillis(row.first_failure_in_run_at),\n lastFailureNextRetryAt: isoFromMillis(row.last_failure_next_retry_at),\n sessionId: row.session_id ?? undefined,\n channelsEnabled: boolFromInteger(row.channels_enabled),\n supervisorEnabled: deliveryMethod === 'supervisor' ? true : undefined,\n deliveryMethod: deliveryMethod ?? undefined,\n hostOverride: hostOverrideFromRow(row.host_override),\n stoppedAt: isoFromMillis(row.stopped_at),\n pausedAt: isoFromMillis(row.paused_at),\n troubledAt: isoFromMillis(row.troubled_at),\n lastActivity: isoFromMillis(row.last_activity),\n lastFailureReason: row.last_failure_reason ?? undefined,\n phase: row.phase == null ? undefined : (row.phase as AgentState['phase']),\n roleRunHead: row.role_run_head ?? undefined,\n flywheelRunId: row.flywheel_run_id ?? undefined,\n costSoFar: row.cost_so_far ?? undefined,\n reviewSubRole: row.review_sub_role ?? undefined,\n reviewRunId: row.review_run_id ?? undefined,\n reviewSynthesisAgentId: row.review_synthesis_agent_id ?? undefined,\n reviewOutputPath: row.review_output_path ?? undefined,\n reviewDeadlineAt: isoFromMillis(row.review_deadline_at),\n reviewMonitorSignaled: (row.review_monitor_signaled ?? undefined) as AgentState['reviewMonitorSignaled'],\n reviewRetryAttempt: row.review_retry_attempt ?? undefined,\n };\n}\n\n/** Exported for agent-projection.ts transactional writes. */\nexport function stateToOverdeckParamsForDb(state: AgentState, updatedAt: number): unknown[] {\n const deliveryMethod = state.deliveryMethod ?? (state.supervisorEnabled === true ? 'supervisor' : null);\n return [\n state.id,\n state.issueId,\n state.role,\n state.status,\n state.workspace,\n state.sessionId ?? null,\n state.harness ?? '',\n state.model ?? '',\n hostOverrideToRow(state.hostOverride),\n deliveryMethod,\n millisFromIso(state.startedAt),\n millisFromIso(state.lastResumeAt),\n state.stoppedByUser == null ? null : (state.stoppedByUser ? 1 : 0),\n state.stoppedByPause == null ? null : (state.stoppedByPause ? 1 : 0),\n state.kickoffDelivered == null ? null : (state.kickoffDelivered ? 1 : 0),\n state.paused == null ? null : (state.paused ? 1 : 0),\n state.pausedReason ?? null,\n state.yieldedByScheduler == null ? null : (state.yieldedByScheduler ? 1 : 0),\n millisFromIso(state.yieldedAt),\n millisFromIso(state.lastYieldResumeAt),\n state.troubled == null ? null : (state.troubled ? 1 : 0),\n state.channelsEnabled == null ? null : (state.channelsEnabled ? 1 : 0),\n state.consecutiveFailures ?? null,\n millisFromIso(state.firstFailureInRunAt),\n millisFromIso(state.lastFailureNextRetryAt),\n millisFromIso(state.stoppedAt),\n millisFromIso(state.pausedAt),\n millisFromIso(state.troubledAt),\n millisFromIso(state.lastActivity),\n state.lastFailureReason ?? null,\n state.phase ?? null,\n state.roleRunHead ?? null,\n state.flywheelRunId ?? null,\n state.costSoFar ?? null,\n state.reviewSubRole ?? null,\n state.reviewRunId ?? null,\n state.reviewSynthesisAgentId ?? null,\n state.reviewOutputPath ?? null,\n millisFromIso(state.reviewDeadlineAt),\n state.reviewMonitorSignaled ?? null,\n state.reviewRetryAttempt ?? null,\n updatedAt,\n ];\n}\n\nexport function getOverdeckAgentStateSync(agentId: string): AgentState | null {\n const row = getOverdeckDatabaseSync()\n .prepare(`${SELECT_AGENT_SQL} WHERE id = ?`)\n .get(agentId) as OverdeckAgentRow | undefined;\n return row ? overdeckRowToAgentState(row) : null;\n}\n\nexport function listOverdeckAgentStatesSync(): AgentState[] {\n const rows = getOverdeckDatabaseSync()\n .prepare(SELECT_AGENT_SQL)\n .all() as OverdeckAgentRow[];\n return rows.map(overdeckRowToAgentState);\n}\n\nexport function saveOverdeckAgentStateSync(state: AgentState): void {\n const db = getOverdeckDatabaseSync();\n const updatedAt = Date.now();\n db.prepare(\n `INSERT OR IGNORE INTO issues (id, stage, updated_at) VALUES (?, 'working', ?)`,\n ).run(state.issueId, updatedAt);\n db.prepare(\n `INSERT OR REPLACE INTO agents (${AGENT_COLUMNS_FOR_DB.join(', ')}) VALUES (${AGENT_COLUMNS_FOR_DB.map(() => '?').join(', ')})`,\n ).run(...stateToOverdeckParamsForDb(state, updatedAt));\n}\n","/**\n * PAN-2579: synchronous access to the review-status map for warm-idle advancing\n * classification in concurrency.ts, WITHOUT importing review-status-sync.ts —\n * that module transitively reaches agents/spawn.ts -> concurrency.ts\n * (review-status-sync -> review-status-normalize -> review-status -> agents),\n * so a direct concurrency.ts import closes a real cycle (caught by\n * scripts/lint-circular-deps.sh). Same shape as memory-verdict-cache.ts: this\n * module has no imports of its own, so it cannot be part of any cycle.\n *\n * review-status.ts registers the reader at module load. Before registration\n * (or if the read throws) callers get null and treat NOTHING as warm-idle —\n * the ceiling stays conservative.\n */\n\n/** Minimal status shape needed for warm-idle classification (mirrors ReapableStatus). */\nexport interface WarmIdleStatusShape {\n reviewStatus?: string;\n testStatus?: string;\n readyForMerge?: boolean;\n mergeStatus?: string;\n}\n\ntype StatusMapReader = () => Record<string, WarmIdleStatusShape>;\n\nlet reader: StatusMapReader | null = null;\n\nexport function registerReviewStatusMapReader(fn: StatusMapReader): void {\n reader = fn;\n}\n\nexport function readReviewStatusMap(): Record<string, WarmIdleStatusShape> | null {\n if (!reader) return null;\n try {\n return reader();\n } catch {\n return null;\n }\n}\n","const issueRecordLocks = new Map<string, Promise<void>>();\n\n/**\n * Serialize async read-modify-write rebuilds for a single issue record.\n *\n * Synchronous record writers remain synchronous; async flows that read, await,\n * then write the whole record must use this lock so same-issue rebuilds cannot\n * overwrite each other's final re-read.\n */\nexport async function withIssueRecordLock<T>(\n issueId: string,\n fn: () => T | Promise<T>,\n): Promise<T> {\n const key = issueId.toUpperCase();\n const previous = issueRecordLocks.get(key) ?? Promise.resolve();\n let release: () => void = () => {};\n const current = new Promise<void>((resolve) => {\n release = resolve;\n });\n const chain = previous.catch(() => undefined).then(() => current);\n issueRecordLocks.set(key, chain);\n\n await previous.catch(() => undefined);\n\n try {\n return await fn();\n } finally {\n release();\n if (issueRecordLocks.get(key) === chain) {\n issueRecordLocks.delete(key);\n }\n }\n}\n","/**\n * PAN-1908 / PAN-1919: per-issue permanent-record builder.\n *\n * Builds a single JSON record per issue from SQLite (pipeline verdicts, usage,\n * merges) plus the legacy project-side continue file. The actual read/write/path\n * helpers live in `record.ts` (PAN-1919 single-writer module) and write the\n * record onto the feature branch at `<workspace>/.pan/records/<issue>.json`.\n */\n\nimport { hostname } from 'node:os';\nimport { join } from 'node:path';\nimport { existsSync, promises as fsp } from 'node:fs';\nimport { PAN_CONTINUES_DIRNAME, PAN_DIRNAME } from './types.js';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\nimport {\n getCostBreakdownByStageAndModelSync,\n getCostForIssueSync,\n} from '../overdeck/cost-sync.js';\nimport { getMergeSetSync } from '../merge-set.js';\nimport {\n getProjectSync,\n resolveProjectFromIssueSync,\n type ProjectConfig,\n} from '../projects.js';\nimport type { ReviewStatus } from '../review-status.js';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport type {\n ContinueBeadsMapping,\n ContinueDecision,\n ContinueFeedbackEntry,\n ContinueHazard,\n ContinueResumePoint,\n ContinueSessionEntry,\n ScopeDriftRecord,\n} from '../vbrief/continue-state.js';\nimport { listOverdeckAgentStatesSync } from '../overdeck/agent-state-sync.js';\nimport {\n getIssueWorkspacePath,\n queueIssueRecordCommit,\n RECORD_SCHEMA_VERSION,\n readIssueRecord,\n readIssueRecordSync,\n writeIssueRecordSync,\n type PanIssueRecord,\n type PanIssuePipelineRecord,\n type PanIssueUsageRecord,\n} from './record.js';\nimport { withIssueRecordLock } from './record-lock.js';\n\nexport type {\n PanIssueRecord,\n PanIssueCloseOutRecord,\n PanIssuePipelineRecord,\n PanIssueUsageRecord,\n} from './record.js';\n\n// ─── Continue projection ──────────────────────────────────────────────────────\n\ninterface ContinueFile {\n issueId?: string;\n gitState?: { branch?: string };\n decisions?: ContinueDecision[];\n hazards?: ContinueHazard[];\n resumePoint?: ContinueResumePoint | null;\n beadsMapping?: ContinueBeadsMapping;\n sessionHistory?: ContinueSessionEntry[];\n feedback?: ContinueFeedbackEntry[];\n agentModel?: string;\n scopeDrift?: ScopeDriftRecord;\n}\n\nfunction projectContinue(raw: ContinueFile | null): Pick<PanIssueRecord, 'decisions' | 'hazards' | 'resumePoint' | 'beadsMapping' | 'sessionHistory' | 'feedback' | 'scopeDrift'> {\n return {\n decisions: raw?.decisions,\n hazards: raw?.hazards,\n resumePoint: raw?.resumePoint,\n beadsMapping: raw?.beadsMapping,\n sessionHistory: raw?.sessionHistory,\n feedback: raw?.feedback ?? [],\n scopeDrift: raw?.scopeDrift,\n };\n}\n\n// ─── Pipeline projection ──────────────────────────────────────────────────────\n\nfunction projectPipeline(\n issueId: string,\n status: ReviewStatus | null,\n existing?: PanIssuePipelineRecord,\n): PanIssuePipelineRecord {\n const base: PanIssuePipelineRecord = {\n issueId,\n reviewStatus: status?.reviewStatus ?? 'pending',\n testStatus: status?.testStatus ?? 'pending',\n verificationStatus: status?.verificationStatus,\n inspectStatus: status?.inspectStatus,\n mergeStatus: status?.mergeStatus,\n readyForMerge: status?.readyForMerge ?? false,\n closedOut: existing?.closedOut,\n closedOutAt: existing?.closedOutAt,\n updatedAt: status?.updatedAt ?? new Date().toISOString(),\n };\n\n if (!status) return base;\n\n return {\n ...base,\n reviewNotes: status.reviewNotes,\n testNotes: status.testNotes,\n verificationNotes: status.verificationNotes,\n inspectNotes: status.inspectNotes,\n mergeNotes: status.mergeNotes,\n blockerReasons: status.blockerReasons,\n prUrl: status.prUrl,\n prNumber: status.prNumber,\n prHeadSha: status.prHeadSha,\n reviewedAtCommit: status.reviewedAtCommit,\n lastVerifiedCommit: status.lastVerifiedCommit,\n reviewRequestedAt: status.reviewRequestedAt,\n scopeDrift: status.scopeDrift,\n autoMerge: status.autoMerge,\n deaconIgnored: status.deaconIgnored,\n deaconIgnoredAt: status.deaconIgnoredAt,\n deaconIgnoredReason: status.deaconIgnoredReason,\n reviewerVerdicts: (status as { reviewerVerdicts?: unknown }).reviewerVerdicts,\n };\n}\n\n// ─── Usage projection ─────────────────────────────────────────────────────────\n\nfunction projectUsage(issueId: string): PanIssueUsageRecord {\n const { byStage, totals } = getCostBreakdownByStageAndModelSync(issueId);\n const aggregate = getCostForIssueSync(issueId);\n\n return {\n byStage,\n totals,\n costAtCloseOut: {\n usd: aggregate?.totalCost ?? 0,\n pricingAsOf: new Date().toISOString(),\n },\n };\n}\n\n// ─── Merge projection ─────────────────────────────────────────────────────────\n\nfunction projectMerges(issueId: string): string[] {\n const mergeSet = getMergeSetSync(issueId);\n if (!mergeSet) return [];\n return mergeSet.repos\n .map(r => r.artifactUrl)\n .filter((url): url is string => typeof url === 'string' && url.length > 0);\n}\n\n// ─── Record builder ───────────────────────────────────────────────────────────\n\nasync function readLegacyContinueText(projectRoot: string, issueId: string): Promise<string | null> {\n const path = join(projectRoot, PAN_DIRNAME, PAN_CONTINUES_DIRNAME, `${issueId.toLowerCase()}.vbrief.json`);\n try {\n return await fsp.readFile(path, 'utf-8');\n } catch {\n return null;\n }\n}\n\n/** Read workspace continue.json and return its statusOverrides (if any). */\nasync function readWorkspaceContinueStatusOverrides(issueId: string): Promise<Record<string, string> | undefined> {\n const workspacePath = getIssueWorkspacePath(issueId);\n if (!workspacePath || !existsSync(workspacePath)) return undefined;\n const continuePath = join(workspacePath, '.pan', 'continue.json');\n try {\n const text = await fsp.readFile(continuePath, 'utf-8');\n const parsed = JSON.parse(text) as { statusOverrides?: Record<string, string> };\n return parsed.statusOverrides ?? undefined;\n } catch {\n return undefined;\n }\n}\n\n/** Read harness/model for an issue from the overdeck agents table (most recent work agent). */\nfunction projectAgentHarnessModel(issueId: string): { harness?: RuntimeName; model?: string } {\n try {\n const agents = listOverdeckAgentStatesSync().filter(\n (a) => a.issueId?.toUpperCase() === issueId.toUpperCase(),\n );\n const workAgent =\n agents\n .filter((a) => a.role === 'work')\n .sort((a, b) => (b.startedAt ?? '').localeCompare(a.startedAt ?? ''))[0]\n ?? agents[agents.length - 1];\n return {\n harness: workAgent?.harness,\n model: workAgent?.model ?? undefined,\n };\n } catch {\n return {};\n }\n}\n\nexport interface BuildIssueRecordOptions {\n closedAt?: string;\n owner?: string;\n reviewStatus?: ReviewStatus | null;\n}\n\nexport async function buildIssueRecord(\n project: ProjectConfig,\n issueId: string,\n opts: BuildIssueRecordOptions = {},\n): Promise<PanIssueRecord> {\n const existing = await readIssueRecord(project, issueId);\n const legacyContinueText = await readLegacyContinueText(project.path, issueId);\n const continueSubset = projectContinue(legacyContinueText ? (JSON.parse(legacyContinueText) as ContinueFile) : null);\n const pipelineRecord = projectPipeline(issueId, opts.reviewStatus ?? null, existing?.pipeline);\n const usageRecord = projectUsage(issueId);\n const merges = projectMerges(issueId);\n\n // PAN-1919: fold workspace continue statusOverrides; fall back to existing.\n const workspaceStatusOverrides = await readWorkspaceContinueStatusOverrides(issueId);\n const mergedStatusOverrides = workspaceStatusOverrides\n ? { ...(existing?.statusOverrides ?? {}), ...workspaceStatusOverrides }\n : existing?.statusOverrides;\n\n // PAN-1919: harness/model from existing record, then agents table as fallback.\n const agentHM = existing?.harness || existing?.model\n ? { harness: existing.harness, model: existing.model }\n : projectAgentHarnessModel(issueId);\n\n return {\n issueId: issueId.toUpperCase(),\n schemaVersion: RECORD_SCHEMA_VERSION,\n ...continueSubset,\n ...existing,\n harness: agentHM.harness,\n model: agentHM.model,\n statusOverrides: mergedStatusOverrides,\n pipeline: pipelineRecord,\n closeOut: {\n usage: usageRecord,\n merges,\n ranOn: hostname(),\n closedAt: opts.closedAt,\n },\n owner: opts.owner ?? existing?.owner,\n };\n}\n\n// Re-export core record I/O from the PAN-1919 single-writer module.\nexport {\n getIssueRecordPath,\n markRecordPipelineClosedOutSync,\n writeIssueRecordSync,\n readIssueRecord,\n queueIssueRecordCommit,\n} from './record.js';\n\n// ─── Owner-URI lease (PAN-1908, CP-3) ─────────────────────────────────────────\n\nexport {\n buildOwnUri,\n claimIssueOwner,\n clearIssueOwner,\n type ClaimResult,\n} from './record.js';\n\n/**\n * PAN-2587: never regress the pipeline block during a whole-record rebuild. A\n * concurrent writer (e.g. a reviewer's verdict landing between the rebuild's\n * `existing` snapshot and its write) may have put a NEWER pipeline on disk than\n * the status the rebuild projects — observed clobbering PAN-399's passed\n * verdict back to a pre-verdict snapshot. Same newer-wins rule (ISO `updatedAt`\n * comparison) the journal readers use.\n */\nexport function pickNewerPipeline(\n rebuilt: PanIssuePipelineRecord,\n fresh: PanIssuePipelineRecord | undefined,\n): PanIssuePipelineRecord {\n if (!fresh?.updatedAt || !rebuilt?.updatedAt) return rebuilt;\n return rebuilt.updatedAt < fresh.updatedAt ? fresh : rebuilt;\n}\n\n/**\n * PAN-1908 / PAN-1919: rebuild and queue the per-issue permanent record for a\n * given issue. Fire-and-forget: failures are logged, never thrown, so\n * review-status writes stay synchronous and fast.\n *\n * PAN-2583: returns whether the durable write actually landed, so the caller\n * can fall back to a sandbox-writable verdict drop when it did not (a sandboxed\n * reviewer cannot write ${OVERDECK_HOME}/state, and swallowing that here was\n * how blocked review verdicts silently vanished).\n */\nexport async function updateIssueRecordForIssue(\n issueId: string,\n reviewStatus?: ReviewStatus | null,\n): Promise<boolean> {\n try {\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) return false;\n const project = getProjectSync(resolved.projectKey);\n if (!project) return false;\n\n await withIssueRecordLock(issueId, async () => {\n const record = await buildIssueRecord(project, issueId, { reviewStatus });\n // buildIssueRecord's `existing` snapshot is read at the top of several awaits;\n // anything written to the record during that window would be erased by this\n // whole-record write (lost update). Observed twice on PAN-1791: wiped swarm\n // slot assignments during the runaway, then wiped item statusOverrides right\n // after `pan done` — which made every completed item dispatchable again and\n // re-spawned slots for finished work. Re-read both blocks synchronously\n // immediately before writing so the freshest values survive the rebuild.\n const fresh = readIssueRecordSync(project, issueId);\n if (fresh?.swarm) record.swarm = fresh.swarm;\n if (fresh?.statusOverrides && Object.keys(fresh.statusOverrides).length > 0) {\n record.statusOverrides = { ...record.statusOverrides, ...fresh.statusOverrides };\n }\n record.pipeline = pickNewerPipeline(record.pipeline, fresh?.pipeline);\n const recordPath = writeIssueRecordSync(project, issueId, record);\n queueIssueRecordCommit(project, issueId, recordPath);\n });\n return true;\n } catch (err) {\n console.warn(`[pan-dir/records] Failed to update record for ${issueId}: ${(err as Error).message}`);\n return false;\n }\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport type { ReviewStatus } from '../review-status.js';\nimport { updateIssueRecordForIssue, type PanIssuePipelineRecord } from '../pan-dir/records.js';\nimport { readIssueRecordSync } from '../pan-dir/record.js';\nimport { resolveProjectFromIssueSync, getProjectSync } from '../projects.js';\n\nexport function updateIssueRecordForReviewStatusSync(issueId: string, status: ReviewStatus): void {\n // PAN-2583: the state-dir journal is the durable home, but a sandboxed reviewer\n // (codex workspace-write) cannot write ${OVERDECK_HOME}/state — and swallowing that\n // failure here is how blocked review verdicts silently vanished for hours. When the\n // journal write does not land, drop the durable verdict into the workspace runtime\n // dir (the one place a sandboxed agent can always write); readJournalStatusSync\n // overlays it on the next host-side read.\n void Promise.resolve(updateIssueRecordForIssue(issueId, status))\n .then((landed) => {\n // Only an explicit `false` means the durable write failed — undefined\n // (e.g. a test double) is \"unknown\", not \"failed\".\n if (landed === false) writeWorkspaceVerdictFallbackSync(issueId, status);\n })\n .catch(() => writeWorkspaceVerdictFallbackSync(issueId, status));\n}\n\n/** Resolve and read the per-issue journal record's pipeline block, or null. Best-effort. */\nfunction readPipelineSync(issueId: string): PanIssuePipelineRecord | null {\n try {\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) return null;\n const project = getProjectSync(resolved.projectKey);\n if (!project) return null;\n const record = readIssueRecordSync(project, issueId);\n return record?.pipeline ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * PAN-1988: feedback TEXT (review / test / merge / inspect / verification notes) is durable\n * JOURNAL state, not DB-cache state. The SQLite row holds only the queryable status flags;\n * the human-readable notes live in the per-issue git record (`<workspace>/.pan/records/<issue>.json`,\n * PAN-1908). This overlays those notes onto a DB-sourced status so every reader (deacon\n * CI-failure detection, dashboard panels) stays transparent while the DB stops storing them.\n */\nexport function enrichReviewNotesFromRecordSync(issueId: string, status: ReviewStatus): ReviewStatus {\n const pipeline = readPipelineSync(issueId);\n if (!pipeline) return status;\n return {\n ...status,\n reviewNotes: pipeline.reviewNotes ?? status.reviewNotes,\n testNotes: pipeline.testNotes ?? status.testNotes,\n mergeNotes: pipeline.mergeNotes ?? status.mergeNotes,\n inspectNotes: pipeline.inspectNotes ?? status.inspectNotes,\n verificationNotes: pipeline.verificationNotes ?? status.verificationNotes,\n scopeDrift: pipeline.scopeDrift ?? status.scopeDrift,\n // PAN-1988 auto-heal: the durable review-request intent is journal-only — overlay it on every\n // read so the merge base preserves it through partial updates and the dispatch reconcile sees it.\n reviewRequestedAt: pipeline.reviewRequestedAt ?? status.reviewRequestedAt,\n };\n}\n\ntype DurableStatusFields = Partial<ReviewStatus> & { closedOut?: boolean; closedOutAt?: string };\n\n/**\n * The durable field subset shared by the journal record's pipeline block and the\n * workspace verdict fallback (PAN-2583). Derived/live columns (readyForMerge,\n * blockerReasons) are intentionally omitted — the reader recomputes them.\n */\nfunction durableSubset(p: PanIssuePipelineRecord): DurableStatusFields {\n return {\n reviewStatus: p.reviewStatus as ReviewStatus['reviewStatus'],\n testStatus: p.testStatus as ReviewStatus['testStatus'],\n mergeStatus: (p.mergeStatus as ReviewStatus['mergeStatus']) ?? undefined,\n inspectStatus: (p.inspectStatus as ReviewStatus['inspectStatus']) ?? undefined,\n verificationStatus: (p.verificationStatus as ReviewStatus['verificationStatus']) ?? undefined,\n reviewNotes: p.reviewNotes,\n testNotes: p.testNotes,\n mergeNotes: p.mergeNotes,\n inspectNotes: p.inspectNotes,\n verificationNotes: p.verificationNotes,\n scopeDrift: p.scopeDrift,\n prUrl: p.prUrl,\n prNumber: p.prNumber,\n prHeadSha: p.prHeadSha,\n reviewedAtCommit: p.reviewedAtCommit,\n lastVerifiedCommit: p.lastVerifiedCommit,\n reviewRequestedAt: p.reviewRequestedAt,\n reviewerVerdicts: p.reviewerVerdicts as ReviewStatus['reviewerVerdicts'],\n autoMerge: p.autoMerge,\n deaconIgnored: p.deaconIgnored,\n deaconIgnoredAt: p.deaconIgnoredAt,\n deaconIgnoredReason: p.deaconIgnoredReason,\n closedOut: p.closedOut,\n closedOutAt: p.closedOutAt,\n };\n}\n\n/**\n * PAN-2583: sandbox-writable emergency drop for a verdict whose state-dir journal\n * write failed. Lives in the workspace runtime dir (gitignored), swept by\n * readJournalStatusSync on the next host-side read.\n */\ninterface WorkspaceVerdictFallback {\n issueId: string;\n updatedAt: string;\n pipeline: DurableStatusFields;\n}\n\nexport function workspaceVerdictFallbackPath(issueId: string): string | null {\n try {\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) return null;\n return join(\n resolved.projectPath,\n 'workspaces',\n `feature-${issueId.toLowerCase()}`,\n '.overdeck',\n 'pipeline-verdict.json',\n );\n } catch {\n return null;\n }\n}\n\nfunction writeWorkspaceVerdictFallbackSync(issueId: string, status: ReviewStatus): void {\n try {\n const path = workspaceVerdictFallbackPath(issueId);\n if (!path) {\n console.error(\n `[review-status] PAN-2583: journal write failed for ${issueId} and no workspace could be resolved — the verdict is NOT durable`,\n );\n return;\n }\n mkdirSync(dirname(path), { recursive: true });\n const payload: WorkspaceVerdictFallback = {\n issueId: issueId.toUpperCase(),\n updatedAt: status.updatedAt ?? new Date().toISOString(),\n pipeline: durableSubset(status as unknown as PanIssuePipelineRecord),\n };\n writeFileSync(path, JSON.stringify(payload, null, 2));\n console.warn(\n `[review-status] journal write failed for ${issueId} — durable verdict written to workspace fallback ${path}; the host sweeps it on the next status read (PAN-2583)`,\n );\n } catch (err) {\n console.error(\n `[review-status] PAN-2583: could not persist verdict for ${issueId} anywhere durable: ${(err as Error).message}`,\n );\n }\n}\n\nfunction readWorkspaceVerdictFallbackSync(issueId: string): WorkspaceVerdictFallback | null {\n try {\n const path = workspaceVerdictFallbackPath(issueId);\n if (!path || !existsSync(path)) return null;\n const parsed = JSON.parse(readFileSync(path, 'utf-8')) as WorkspaceVerdictFallback;\n if (!parsed?.updatedAt || !parsed.pipeline) return null;\n return parsed;\n } catch {\n return null;\n }\n}\n\n/**\n * PAN-1988: read the journal record's durable verdict for an issue — the SOURCE OF TRUTH.\n * Returns the record `updatedAt` (used to decide whether the journal is newer than the DB\n * cache) plus the durable status fields (flags + feedback notes). Derived/live columns\n * (readyForMerge, blockerReasons) are intentionally omitted — the reader recomputes them.\n *\n * This is what makes verdict writes host-owned: a sandboxed agent can always write the journal\n * (workspace-local) even when it cannot write `~/.overdeck/overdeck.db`. The host reconciles\n * the cache from this on read, so no agent has to escalate out of its sandbox to record a verdict.\n *\n * PAN-2583: since the records migration (PAN-2541) the journal itself lives in\n * ${OVERDECK_HOME}/state, which a sandboxed reviewer cannot write either. When such a\n * writer dropped its verdict into the workspace fallback instead, overlay it here —\n * a strictly newer fallback wins over the record, and the host's own next journal\n * write folds it back into the canonical record.\n */\nexport function readJournalStatusSync(\n issueId: string,\n): { updatedAt: string; durable: DurableStatusFields } | null {\n const p = readPipelineSync(issueId);\n const fallback = readWorkspaceVerdictFallbackSync(issueId);\n const fallbackNewer = !!fallback && (!p || (p.updatedAt ?? '') < fallback.updatedAt);\n if (fallback && fallbackNewer) {\n const base = p ? durableSubset(p) : {};\n const overlay = Object.fromEntries(\n Object.entries(fallback.pipeline).filter(([, value]) => value !== undefined),\n ) as DurableStatusFields;\n return { updatedAt: fallback.updatedAt, durable: { ...base, ...overlay } };\n }\n if (!p) return null;\n return { updatedAt: p.updatedAt, durable: durableSubset(p) };\n}\n","/**\n * PAN-1988 auto-heal — decide whether a review needs to be (re-)dispatched from the durable\n * journal intent, as a PURE function so it is locked by tests.\n *\n * The work agent's `pan done` writes a durable `reviewRequestedAt` into the journal/record (always\n * writable, even sandboxed) BEFORE it tries to reach the dashboard. If that reactive trigger is\n * dropped — the dashboard was mid-reload, the network blipped, the deacon is frozen — the intent\n * survives in the journal. The host reconciles on read: when a request is newer than the last\n * dispatch and nothing is currently reviewing, it dispatches review. This heals with NO deacon and\n * NO live dashboard event, on the next status read.\n *\n * Returns true when a review dispatch is owed:\n * - a request exists (`reviewRequestedAt` set), AND\n * - it is newer than the last spawn (or there was never a spawn) — i.e. a genuinely un-serviced\n * request, not the request that produced the current review, AND\n * - a review is not already in progress (`reviewing`), AND\n * - the issue is not already merged.\n */\nexport function needsReviewDispatch(params: {\n reviewRequestedAt?: string;\n reviewSpawnedAt?: string;\n reviewStatus?: string;\n mergeStatus?: string;\n}): boolean {\n if (!params.reviewRequestedAt) return false;\n if (params.reviewStatus === 'reviewing') return false;\n if (params.mergeStatus === 'merged') return false;\n return !params.reviewSpawnedAt || params.reviewRequestedAt > params.reviewSpawnedAt;\n}\n","import { join } from 'path';\nimport { homedir } from 'os';\nimport { Data, Effect } from 'effect';\nimport { notifyPipelineSync } from './pipeline-notifier.js';\nimport { emitActivityEntrySync, emitActivityTtsSync } from './activity-logger.js';\nimport {\n upsertReviewStatusSync as dbUpsert,\n deleteReviewStatus as dbDelete,\n getReviewStatusFromDbSync,\n getAllReviewStatusesFromDb,\n getReviewStatusesFromDb,\n markWorkspaceStuck as dbMarkStuck,\n clearWorkspaceStuck as dbClearStuck,\n} from './overdeck/review-status-sync.js';\nimport { registerReviewStatusMapReader } from './cloister/review-status-source.js';\nimport { normalizeReviewStatusSync } from './review-status-normalize.js';\nimport { updateIssueRecordForReviewStatusSync, enrichReviewNotesFromRecordSync, readJournalStatusSync } from './overdeck/review-status-record-sync.js';\nimport { needsReviewDispatch } from './review-dispatch-decision.js';\nimport type { ScopeDriftRecord } from './vbrief/continue-state.js';\n\nfunction emitReactiveLifecycleEvent(type: 'review.approved' | 'test.passed', issueId: string): void {\n try {\n notifyPipelineSync({ type, issueId });\n } catch (error) {\n console.warn(`[review-status] Failed to emit ${type} for ${issueId}:`, error);\n }\n}\n\nexport interface StatusHistoryEntry {\n type: 'review' | 'test' | 'merge' | 'inspect' | 'uat';\n status: string;\n timestamp: string;\n notes?: string;\n}\n\nexport interface BlockerReason {\n type: 'failing_checks' | 'merge_conflict' | 'unresolved_conversations' | 'changes_requested' | 'draft_pr' | 'not_mergeable';\n summary: string;\n details?: string;\n detectedAt: string;\n}\n\nexport interface ReviewStatus {\n issueId: string;\n reviewStatus: 'pending' | 'reviewing' | 'passed' | 'failed' | 'blocked' | 'skipped';\n testStatus: 'pending' | 'testing' | 'passed' | 'failed' | 'skipped' | 'dispatch_failed';\n mergeStatus?: 'pending' | 'queued' | 'merging' | 'verifying' | 'merged' | 'failed';\n inspectStatus?: 'pending' | 'inspecting' | 'passed' | 'failed' | 'error';\n inspectNotes?: string;\n inspectStartedAt?: string;\n inspectBeadId?: string;\n uatStatus?: 'pending' | 'testing' | 'passed' | 'failed';\n uatNotes?: string;\n verificationStatus?: 'pending' | 'running' | 'passed' | 'failed' | 'skipped';\n verificationNotes?: string;\n verificationCycleCount?: number;\n verificationMaxCycles?: number;\n reviewNotes?: string;\n testNotes?: string;\n mergeNotes?: string;\n updatedAt: string;\n readyForMerge: boolean;\n /**\n * PAN-1691: per-issue merge-train routing key.\n * `undefined` = follow the project default; `true` = auto-merge (fast lane,\n * rides the train and ships when green); `false` = hold for UAT (manual lane,\n * waits for human batch review). The merge-train engine reads this to decide\n * whether a ready issue auto-advances or is held for the UAT candidate.\n */\n autoMerge?: boolean;\n autoRequeueCount?: number;\n mergeRetryCount?: number;\n prUrl?: string;\n /** PAN-905: HEAD commit SHA of the tracked PR for webhook identity validation */\n prHeadSha?: string;\n /** PAN-905: GitHub PR number of the tracked PR for webhook identity validation */\n prNumber?: number;\n history?: StatusHistoryEntry[];\n /** PAN-905: GitHub-native merge blocker reasons */\n blockerReasons?: BlockerReason[];\n /** HEAD commit SHA at the time review passed — used to detect new commits after review */\n reviewedAtCommit?: string;\n /** HEAD commit SHA at the time the pre-review verification gate passed — used to skip redundant test-agent */\n lastVerifiedCommit?: string;\n /** Current merge pipeline step for granular merge progress tracking */\n mergeStep?: string;\n /** PAN-653: workspace is stuck (e.g. main diverged mid-approve) — Deacon skips it */\n stuck?: boolean;\n /** PAN-653: reason workspace is stuck (e.g. 'main_diverged') */\n stuckReason?: string;\n /** PAN-653: ISO timestamp when workspace was marked stuck */\n stuckAt?: string;\n /** PAN-653: JSON details about the stuck event (e.g. {localSha, remoteSha}) */\n stuckDetails?: string;\n /** PAN-699: timestamp when review agents were dispatched (deacon timeout detection) */\n reviewSpawnedAt?: string;\n /**\n * PAN-1988 auto-heal: durable JOURNAL intent set by `pan done` BEFORE it reaches the dashboard.\n * \"The work agent finished and wants review.\" When this is newer than {@link reviewSpawnedAt}\n * and nothing is reviewing, the host reconciles on read and dispatches review — surviving a\n * dashboard reload, a dropped event, or a frozen deacon. Journal-only (not a DB column).\n */\n reviewRequestedAt?: string;\n /** PAN-1765: timestamp when a conflict-resolution work agent was dispatched. */\n conflictResolutionDispatchedAt?: string;\n /** PAN-699: number of test-agent dispatch retries (circuit breaker) */\n testRetryCount?: number;\n /** PAN-794: number of consecutive parallel-review re-dispatch attempts within the current cycle */\n reviewRetryCount?: number;\n /** PAN-794: ISO timestamp when deacon began the current recovery cycle — acts as the history cutoff for the breaker */\n recoveryStartedAt?: string;\n /** Human-requested ignore flag: when true, Deacon patrol skips this issue entirely (distinct from `stuck`, which is a system-set failure marker). */\n deaconIgnored?: boolean;\n /** ISO timestamp when the ignore flag was set. */\n deaconIgnoredAt?: string;\n /** Optional free-form reason shown alongside the ignore toggle. */\n deaconIgnoredReason?: string;\n /** PAN-1762: advisory files_scope drift recorded at pan done and surfaced to review. */\n scopeDrift?: ScopeDriftRecord;\n /**\n * PAN-1862 (FR-6): per-convoy-reviewer verdicts from the latest full-review cycle,\n * keyed by sub-role (security/correctness/performance/requirements). Journal-durable\n * (not a DB column — overlaid on read like reviewRequestedAt). `atCommit` anchors\n * drift invalidation for selective re-review (reviewersToRerun).\n */\n reviewerVerdicts?: Partial<Record<string, { status: 'passed' | 'blocked'; atCommit?: string; findingsPath?: string }>>;\n // PAN-1531: reviewTempStashRef / reviewTempStashMessage / reviewTempStashSequence\n // removed. The review pipeline no longer stashes uncommitted work — the\n // dirty-worktree gate refuses pan done / pan review request before review\n // is dispatched.\n}\n\nexport function verificationSatisfied(status: Pick<ReviewStatus, 'verificationStatus'>): boolean {\n // Only block readyForMerge if verification explicitly FAILED.\n // 'pending' means \"scheduled but not yet run this cycle\" — not a failure signal.\n // request-review resets verificationStatus to 'pending' as part of its cycle reset,\n // but subsequent review+test passing should still yield readyForMerge=true.\n // The post-rebase gate in triggerMerge() is the authoritative quality gate (PAN-XXX).\n return status.verificationStatus !== 'failed';\n}\n\n/**\n * PAN-1988: the merge-gate predicate (review passed, test passed/skipped, verification not\n * failed, UAT ok, merge not started). Extracted so both setReviewStatusSync and the\n * journal→DB reconcile in getReviewStatusSync derive readyForMerge identically.\n */\nexport function reviewGatesPassedSync(\n s: Pick<ReviewStatus, 'reviewStatus' | 'testStatus' | 'verificationStatus' | 'uatStatus' | 'mergeStatus'>,\n): boolean {\n return (\n (s.reviewStatus === 'passed' || s.reviewStatus === 'skipped') &&\n (s.testStatus === 'passed' || s.testStatus === 'skipped') &&\n verificationSatisfied(s) &&\n (s.uatStatus === undefined || s.uatStatus === 'passed') &&\n (s.mergeStatus === 'pending' ||\n s.mergeStatus === 'queued' ||\n s.mergeStatus === undefined ||\n s.mergeStatus === null)\n );\n}\n\nexport interface MergeGateEligibility {\n eligible: boolean;\n reason?: string;\n}\n\n/**\n * Authoritative \"allowed to merge\" predicate (PAN-1759). The flywheel\n * orchestrator's pipeline verb says it INTENDS to merge an issue; this record\n * says the pipeline ALLOWS it. Both must hold before an issue enters the merge\n * queue or a UAT batch — RUN-20 tagged a mid-review issue with a merge verb\n * and it rode into a promotable batch. Same criteria as the\n * fixStuckReadyForMerge repair sweep: review passed, test passed/skipped,\n * verification not failed, not already merged.\n */\nexport function mergeGateEligibility(\n status: Pick<ReviewStatus, 'reviewStatus' | 'testStatus' | 'verificationStatus' | 'mergeStatus'> | null,\n): MergeGateEligibility {\n if (!status) return { eligible: false, reason: 'no review record' };\n if (status.reviewStatus !== 'passed' && status.reviewStatus !== 'skipped') {\n return { eligible: false, reason: `review is ${status.reviewStatus}` };\n }\n if (status.testStatus !== 'passed' && status.testStatus !== 'skipped') {\n return { eligible: false, reason: `test is ${status.testStatus}` };\n }\n if (!verificationSatisfied(status)) return { eligible: false, reason: 'verification failed' };\n if (status.mergeStatus === 'merged') return { eligible: false, reason: 'already merged' };\n return { eligible: true };\n}\n// PAN-2579: register the cycle-free status-map reader used by concurrency.ts for\n// warm-idle advancing classification (see cloister/review-status-source.ts).\nregisterReviewStatusMapReader(() => getAllReviewStatusesFromDb());\n\nconst DEFAULT_STATUS_FILE = join(homedir(), '.overdeck', 'review-status.json');\n\nexport function loadReviewStatuses(filePath = DEFAULT_STATUS_FILE): Record<string, ReviewStatus> {\n // SQLite is the authoritative store for the default (server) path.\n // Non-default JSON paths have been moved to review-status-json.ts so that\n // dashboard-reachable code never imports sync FS operations.\n if (filePath !== DEFAULT_STATUS_FILE) {\n throw new Error(\n `Non-default review-status paths are not supported in review-status.ts. ` +\n `Import from review-status-json.ts for JSON file operations.`\n );\n }\n return getAllReviewStatusesFromDb();\n}\n\nexport function loadReviewStatusesForIssues(issueIds: string[]): Record<string, ReviewStatus> {\n return getReviewStatusesFromDb(issueIds);\n}\n\nexport function loadReadyForMergeFlags(issueIds: string[]): Map<string, boolean> {\n const normalizedIds = [...new Set(issueIds.map((id) => id.toUpperCase()).filter(Boolean))];\n const dbStatuses = loadReviewStatusesForIssues(normalizedIds);\n const flags = new Map<string, boolean>();\n\n for (const issueId of normalizedIds) {\n const dbStatus = dbStatuses[issueId] ?? null;\n const journal = readJournalStatusSync(issueId);\n let status = dbStatus;\n\n if (journal) {\n if ((journal.durable as { closedOut?: boolean }).closedOut === true) {\n flags.set(issueId, false);\n continue;\n }\n\n if (!dbStatus || (dbStatus.updatedAt ?? '') < journal.updatedAt) {\n const merged: ReviewStatus = {\n ...(dbStatus ?? {\n issueId,\n reviewStatus: 'pending' as const,\n testStatus: 'pending' as const,\n updatedAt: journal.updatedAt,\n readyForMerge: false,\n }),\n };\n for (const [key, value] of Object.entries(journal.durable)) {\n if (value !== undefined) (merged as unknown as Record<string, unknown>)[key] = value;\n }\n merged.issueId = issueId;\n merged.updatedAt = journal.updatedAt;\n const hasBlockers = (merged.blockerReasons?.length ?? 0) > 0;\n merged.readyForMerge = hasBlockers ? false : reviewGatesPassedSync(merged);\n status = normalizeReviewStatusSync(merged);\n }\n }\n\n flags.set(issueId, status?.readyForMerge ?? false);\n }\n\n return flags;\n}\n\nexport function saveReviewStatuses(statuses: Record<string, ReviewStatus>, filePath = DEFAULT_STATUS_FILE): void {\n // SQLite is the authoritative store for the default (server) path.\n // Mirrors the old JSON overwrite semantics: upsert every entry in the map and\n // delete any SQLite rows whose keys are absent from the map (replace-all).\n if (filePath !== DEFAULT_STATUS_FILE) {\n throw new Error(\n `Non-default review-status paths are not supported in review-status.ts. ` +\n `Import from review-status-json.ts for JSON file operations.`\n );\n }\n const incoming = new Set(Object.keys(statuses));\n const existing = getAllReviewStatusesFromDb();\n for (const id of Object.keys(existing)) {\n if (!incoming.has(id)) {\n dbDelete(id);\n }\n }\n for (const status of Object.values(statuses)) {\n dbUpsert(status);\n }\n}\n\nexport function setReviewStatusSync(\n issueId: string,\n update: Partial<ReviewStatus>,\n existing?: ReviewStatus,\n): ReviewStatus {\n // Guard: bare numeric IDs (no alphabetic prefix) must never reach the DB.\n // They would create orphaned rows that pollute pending lists and metrics.\n if (/^\\d+$/.test(issueId)) {\n console.warn(\n `[review-status] Rejecting setReviewStatus for bare numeric ID \"${issueId}\" — ` +\n `issue IDs must include a project prefix (e.g. PAN-${issueId}).`\n );\n return {\n issueId,\n reviewStatus: 'pending' as const,\n testStatus: 'pending' as const,\n updatedAt: new Date().toISOString(),\n readyForMerge: false,\n };\n }\n\n issueId = issueId.toUpperCase();\n\n // Read only the single row we're updating (avoids TOCTOU: bulk read-modify-write\n // races when two concurrent calls for different issue IDs run concurrently).\n // If `existing` is provided (e.g. from mutateBlockers), skip the read to\n // avoid double-read on the webhook ingestion path (PAN-905).\n // PAN-1988: the merge base goes through getReviewStatusSync (journal-reconciled +\n // notes-enriched), NOT the raw DB read. Otherwise a partial update (e.g. a testStatus\n // change carrying no review notes) would merge against a DB row whose notes/flags lag\n // the journal and silently erase the journal's feedback. The reconcile inside\n // getReviewStatusSync never re-enters setReviewStatusSync, so there is no recursion.\n const status: ReviewStatus = existing ?? getReviewStatusSync(issueId) ?? {\n issueId,\n reviewStatus: 'pending' as const,\n testStatus: 'pending' as const,\n updatedAt: new Date().toISOString(),\n readyForMerge: false,\n };\n\n // Guard: reject reviewStatus regression from 'passed' to 'reviewing' unless the caller\n // is explicitly resetting the merge lifecycle (update includes mergeStatus).\n // This is belt-and-suspenders — endpoint-level guards should catch this first.\n if (update.reviewStatus === 'reviewing' && status.reviewStatus === 'passed' && update.mergeStatus === undefined) {\n console.warn(`[review-status] Rejecting reviewStatus regression from 'passed' to 'reviewing' for ${issueId} (mergeStatus not being reset)`);\n notifyPipelineSync({ type: 'status_changed', issueId, status: status as ReviewStatus });\n return status as ReviewStatus;\n }\n\n // PAN-2578: a bare 'reviewing' write must never clobber a terminal blocked/failed verdict.\n // A review agent can finish FASTER than the dispatch path that spawned it (PAN-399: the\n // verification gate completed 2 minutes after the agent recorded BLOCKED, and the dispatch\n // route's redundant `{ reviewStatus: 'reviewing' }` write destroyed the verdict in both the\n // DB and the journal). Blocked/failed → reviewing is legal ONLY when the caller explicitly\n // starts a NEW review cycle by carrying reviewSpawnedAt (spawnReviewRoleForIssue does).\n if (\n update.reviewStatus === 'reviewing' &&\n (status.reviewStatus === 'blocked' || status.reviewStatus === 'failed') &&\n update.reviewSpawnedAt === undefined\n ) {\n console.warn(\n `[review-status] Rejecting stale 'reviewing' write for ${issueId} — a terminal ` +\n `'${status.reviewStatus}' verdict is already recorded for this review cycle. Only a ` +\n `new dispatch (carrying reviewSpawnedAt) may re-enter 'reviewing'.`\n );\n notifyPipelineSync({ type: 'status_changed', issueId, status: status as ReviewStatus });\n return status as ReviewStatus;\n }\n\n // PAN-424: Reject testStatus regression from 'passed' to 'dispatch_failed' or 'failed'.\n // Once tests pass, duplicate dispatch failures must not overwrite the result.\n if (\n (update.testStatus === 'dispatch_failed' || update.testStatus === 'failed') &&\n status.testStatus === 'passed'\n ) {\n console.warn(`[review-status] Rejecting testStatus regression from 'passed' to '${update.testStatus}' for ${issueId}`);\n delete update.testStatus;\n delete update.testNotes;\n }\n\n const merged = { ...status, ...update };\n\n // PAN-1862 (FR-6): reviewerVerdicts is a per-sub-role MAP — a partial update\n // (synthesis reporting only the reviewers that re-ran this cycle) must merge\n // with, not replace, the carried-forward entries from prior cycles.\n if (update.reviewerVerdicts && status.reviewerVerdicts) {\n merged.reviewerVerdicts = { ...status.reviewerVerdicts, ...update.reviewerVerdicts };\n }\n\n // Track status transitions in history (last 10 entries)\n const history = [...(status.history || [])];\n const now = new Date().toISOString();\n if (update.reviewStatus && update.reviewStatus !== status.reviewStatus) {\n history.push({ type: 'review', status: update.reviewStatus, timestamp: now, notes: update.reviewNotes });\n }\n if (update.testStatus && update.testStatus !== status.testStatus) {\n history.push({ type: 'test', status: update.testStatus, timestamp: now, notes: update.testNotes });\n }\n if (update.uatStatus && update.uatStatus !== status.uatStatus) {\n history.push({ type: 'uat', status: update.uatStatus, timestamp: now, notes: update.uatNotes });\n }\n if (update.mergeStatus && update.mergeStatus !== status.mergeStatus) {\n history.push({ type: 'merge', status: update.mergeStatus, timestamp: now });\n }\n while (history.length > 10) history.shift();\n\n // PAN-1650: readyForMerge is EVENT-DRIVEN — derived from the gate state on every\n // write, so it flips the instant review+test+verification pass instead of waiting\n // for a deacon patrol or a startup `fixStuckReadyForMerge` reconcile (those become\n // redundant safety nets). This supersedes the PAN-1048 explicit-only model.\n //\n // Deriving from gates here does NOT bypass the \"rebased onto main + verified\"\n // guarantee: `triggerMerge()` performs the authoritative post-rebase quality gate\n // before it actually merges (see verificationSatisfied's note). The gate predicate\n // mirrors `fixStuckReadyForMerge` so behaviour is identical — just immediate.\n //\n // Explicit caller intent still wins (the merge flow sets readyForMerge=false when a\n // merge starts; mergeStatus then leaves pending/queued so the derive agrees).\n // PAN-905: GitHub-native blockers always override readyForMerge to false.\n const hasBlockers = (merged.blockerReasons?.length ?? 0) > 0;\n const readyForMerge = hasBlockers\n ? false\n : (update.readyForMerge !== undefined\n ? update.readyForMerge\n : reviewGatesPassedSync(merged));\n\n const updated: ReviewStatus = normalizeReviewStatusSync({\n ...merged,\n issueId,\n updatedAt: now,\n readyForMerge,\n history,\n });\n\n // Report commit statuses to GitHub when readyForMerge transitions to true (PAN-536)\n if (readyForMerge && !status.readyForMerge && updated.prUrl) {\n (async () => {\n try {\n const { isGitHubAppConfigured, reportCommitStatus } = await import('./github-app.js');\n if (!isGitHubAppConfigured()) return;\n const prMatch = updated.prUrl!.match(/github\\.com\\/([^/]+)\\/([^/]+)\\/pull/);\n if (!prMatch) return;\n const [, owner, repo] = prMatch;\n // Get HEAD SHA of the PR branch\n const { exec } = await import('child_process');\n const { promisify } = await import('util');\n const execAsync = promisify(exec);\n const { stdout } = await execAsync(\n `gh pr view ${updated.prUrl!.match(/\\/pull\\/(\\d+)/)?.[1]} --json headRefOid --jq .headRefOid`,\n { encoding: 'utf-8', timeout: 10000 }\n );\n const sha = stdout.trim();\n if (sha) {\n // overdeck/review is honest here — review just transitioned to readyForMerge.\n // The overdeck/tests status is posted separately at the actual test-completion\n // site (verification-runner success, test-agent POST in workspaces routes) so it\n // accurately reflects which commit was tested.\n await reportCommitStatus(owner, repo, sha, 'success', 'overdeck/review', 'Review passed');\n console.log(`[review-status] Reported overdeck/review for ${issueId} (${sha.slice(0, 8)})`);\n }\n } catch (err: any) {\n console.warn(`[review-status] Failed to report commit status: ${err.message}`);\n }\n })();\n }\n\n // PAN-1908 + PAN-1988: the journal record is the SOURCE OF TRUTH for the verdict; the\n // SQLite row is a rebuildable cache. Write the journal FIRST — it is workspace-local\n // (<workspace>/.pan/records/<issue>.json), so it succeeds even for a sandboxed agent\n // (codex workspace-write) that cannot reach ~/.overdeck. Fire-and-forget; queueAutoCommit\n // debounces bursts into one commit. Do NOT mirror into canonical vBRIEF specs (PAN-1124).\n updateIssueRecordForReviewStatusSync(issueId, updated);\n\n // The DB cache write is best-effort. A sandboxed agent's write throws SQLITE_READONLY, but\n // the verdict is already durable in the journal above, and the host reconciles the cache on\n // read (getReviewStatusSync's journal→DB reconcile). Tolerating this is what removes the\n // sandbox-escalation \"smoke and mirrors\" — the agent never has to break out of its jail to\n // record a verdict.\n try {\n dbUpsert(updated);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[review-status] DB cache write skipped for ${issueId} (${msg}); journal holds the verdict, host will reconcile on read.`);\n }\n\n notifyPipelineSync({ type: 'status_changed', issueId, status: updated });\n\n // Emit activity log entries for meaningful pipeline state transitions.\n // Each transition produces one entry so the ActivityPanel shows live pipeline progress.\n if (update.verificationStatus && update.verificationStatus !== status.verificationStatus) {\n const vMap: Record<string, { level: 'info' | 'warn' | 'error' | 'success'; msg: string; tts?: string; ttsPriority?: number }> = {\n running: { level: 'info', msg: `${issueId} — verification running`, tts: `${issueId} verification running` },\n passed: { level: 'success', msg: `${issueId} — verification passed`, tts: `${issueId} verification passed` },\n failed: { level: 'error', msg: `${issueId} — verification failed`, tts: `${issueId} verification failed` },\n skipped: { level: 'info', msg: `${issueId} — verification skipped`, tts: `${issueId} verification skipped`, ttsPriority: 2 },\n };\n const entry = vMap[update.verificationStatus];\n if (entry) emitActivityEntrySync({ source: 'cloister', level: entry.level, message: entry.msg, details: update.verificationNotes, issueId });\n if (entry?.tts) emitActivityTtsSync({\n utterance: entry.tts,\n priority: entry.ttsPriority ?? (entry.level === 'error' ? 0 : 1),\n issueId,\n source: 'cloister',\n eventType: `verificationStatus.${update.verificationStatus}`,\n });\n }\n if (update.reviewStatus && update.reviewStatus !== status.reviewStatus) {\n let reviewMsg = `${issueId} — review started`;\n if (update.reviewStatus === 'reviewing') {\n const retryCount = updated.reviewRetryCount ?? 0;\n reviewMsg = retryCount > 0\n ? `${issueId} — review re-dispatched (retry ${retryCount})`\n : `${issueId} — review started`;\n }\n const rMap: Record<string, { level: 'info' | 'warn' | 'error' | 'success'; msg: string; tts?: string }> = {\n reviewing: { level: 'info', msg: reviewMsg, tts: `${issueId} review started` },\n passed: { level: 'success', msg: `${issueId} — review passed`, tts: `${issueId} review passed` },\n failed: { level: 'error', msg: `${issueId} — review failed`, tts: `${issueId} review failed` },\n blocked: { level: 'warn', msg: `${issueId} — review blocked (changes requested)`, tts: `${issueId} review blocked` },\n };\n const entry = rMap[update.reviewStatus];\n if (entry) emitActivityEntrySync({ source: 'review', level: entry.level, message: entry.msg, details: update.reviewNotes, issueId });\n if (entry?.tts) emitActivityTtsSync({\n utterance: entry.tts,\n priority: entry.level === 'error' ? 0 : 1,\n issueId,\n source: 'review-specialist',\n eventType: `reviewStatus.${update.reviewStatus}`,\n });\n }\n if (update.testStatus && update.testStatus !== status.testStatus) {\n const tMap: Record<string, { level: 'info' | 'warn' | 'error' | 'success'; msg: string; tts?: string; ttsPriority?: number }> = {\n testing: { level: 'info', msg: `${issueId} — tests running`, tts: `${issueId} tests running`, ttsPriority: 2 },\n passed: { level: 'success', msg: `${issueId} — tests passed`, tts: `${issueId} tests passed` },\n failed: { level: 'error', msg: `${issueId} — tests failed`, tts: `${issueId} tests failed` },\n skipped: { level: 'info', msg: `${issueId} — tests skipped`, tts: `${issueId} tests skipped`, ttsPriority: 2 },\n dispatch_failed: { level: 'warn', msg: `${issueId} — test dispatch failed`, tts: `${issueId} test dispatch failed`, ttsPriority: 1 },\n };\n const entry = tMap[update.testStatus];\n if (entry) emitActivityEntrySync({ source: 'test', level: entry.level, message: entry.msg, details: update.testNotes, issueId });\n if (entry?.tts) emitActivityTtsSync({\n utterance: entry.tts,\n priority: entry.ttsPriority ?? (entry.level === 'error' ? 0 : 1),\n issueId,\n source: 'test-specialist',\n eventType: `testStatus.${update.testStatus}`,\n });\n }\n if (update.mergeStatus && update.mergeStatus !== status.mergeStatus) {\n const mMap: Record<string, { level: 'info' | 'warn' | 'error' | 'success'; msg: string; tts?: string; ttsPriority?: number }> = {\n queued: { level: 'info', msg: `${issueId} — queued for merge`, tts: `${issueId} queued for merge`, ttsPriority: 2 },\n merging: { level: 'info', msg: `${issueId} — merge in progress`, tts: `${issueId} merge in progress`, ttsPriority: 2 },\n verifying: { level: 'info', msg: `${issueId} — post-merge verification`, tts: `${issueId} post-merge verification running`, ttsPriority: 2 },\n merged: { level: 'success', msg: `${issueId} — merged`, tts: `${issueId} merged to main` },\n failed: { level: 'error', msg: `${issueId} — merge failed`, tts: `${issueId} merge failed` },\n };\n const entry = mMap[update.mergeStatus];\n if (entry) emitActivityEntrySync({ source: 'ship', level: entry.level, message: entry.msg, details: update.mergeNotes, issueId });\n if (entry?.tts) emitActivityTtsSync({\n utterance: entry.tts,\n priority: entry.ttsPriority ?? (entry.level === 'error' ? 0 : 1),\n issueId,\n source: 'merge-agent',\n eventType: `mergeStatus.${update.mergeStatus}`,\n });\n }\n if (update.readyForMerge === true && !status.readyForMerge) {\n emitActivityEntrySync({ source: 'cloister', level: 'success', message: `${issueId} — ready for merge`, issueId });\n emitActivityTtsSync({\n utterance: `${issueId} ready for merge`,\n priority: 1,\n issueId,\n source: 'cloister',\n eventType: 'readyForMerge',\n });\n }\n\n // Reactive Cloister owns review→test and test→ship scheduling. setReviewStatus\n // emits the lifecycle event here so API and direct-import callers share one path.\n // PAN-1862 (FR-14): reviewStatus 'skipped' (review mode none) advances the\n // lifecycle exactly like an approved review — same event, same downstream.\n if (\n (update.reviewStatus === 'passed' || update.reviewStatus === 'skipped') &&\n status.reviewStatus !== 'passed' && status.reviewStatus !== 'skipped' &&\n updated.testStatus === 'pending'\n ) {\n const canSkipTests =\n updated.reviewedAtCommit &&\n updated.lastVerifiedCommit &&\n updated.reviewedAtCommit === updated.lastVerifiedCommit;\n\n if (canSkipTests) {\n console.log(`[review-status] Skipping test role for ${issueId} — no code drift since verification (HEAD=${updated.reviewedAtCommit!.slice(0, 8)})`);\n emitActivityEntrySync({ source: 'cloister', level: 'info', message: `${issueId} — tests skipped (no code change since verification gate)`, issueId });\n setReviewStatusSync(issueId, {\n testStatus: 'passed',\n testNotes: 'Skipped: no code changed since pre-review verification gate',\n verificationStatus: 'passed',\n verificationNotes: 'Pre-review verification already covered the reviewed commit',\n });\n void emitReactiveLifecycleEvent('test.passed', issueId);\n } else {\n void emitReactiveLifecycleEvent('review.approved', issueId);\n }\n }\n\n if (update.testStatus === 'passed' && status.testStatus !== 'passed') {\n void emitReactiveLifecycleEvent('test.passed', issueId);\n }\n\n // PAN-1988: test FAILED → hand it straight back to the work agent (host-side). The dead-end\n // requeue that does this is deacon-only, so without this a failure strands until the (possibly\n // frozen) deacon nudges. Fires once per transition into failed — both the agent's direct POST and\n // the host's .pan/test/result.json recovery flow through this same transition.\n if (\n (update.testStatus === 'failed' || update.testStatus === 'dispatch_failed') &&\n status.testStatus !== 'failed' && status.testStatus !== 'dispatch_failed'\n ) {\n void deliverTestFailureToWorkAgentHostSide(issueId, updated);\n }\n\n return updated;\n}\n\nexport function resetPipelineVerdictsForWorkStartSync(issueId: string): ReviewStatus | null {\n const status = getReviewStatusSync(issueId);\n if (!status) return null;\n\n const isPending =\n status.reviewStatus === 'pending' &&\n status.testStatus === 'pending' &&\n (status.mergeStatus === undefined || status.mergeStatus === 'pending') &&\n (status.verificationStatus === undefined || status.verificationStatus === 'pending') &&\n !status.readyForMerge &&\n status.autoRequeueCount === 0 &&\n status.verificationCycleCount === 0 &&\n status.reviewRetryCount === 0 &&\n status.testRetryCount === 0 &&\n status.mergeRetryCount === 0 &&\n status.recoveryStartedAt === undefined &&\n status.reviewedAtCommit === undefined &&\n status.lastVerifiedCommit === undefined;\n\n if (isPending) return null;\n\n return setReviewStatusSync(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 stuck: false,\n stuckReason: undefined,\n stuckAt: undefined,\n stuckDetails: undefined,\n reviewRetryCount: 0,\n testRetryCount: 0,\n mergeRetryCount: 0,\n recoveryStartedAt: undefined,\n reviewedAtCommit: undefined,\n lastVerifiedCommit: undefined,\n });\n}\n\n/**\n * PAN-1988 — deliver review feedback to the work agent from the HOST when a blocked/failed\n * verdict is reconciled from the journal. Dynamic import to avoid a static import cycle\n * (review-verdict-feedback → review-status). Fully best-effort: in a sandboxed agent process the\n * delivery fails (host paths blocked) and is swallowed; the host process performs the real\n * notification. Never throws into the read path.\n */\nasync function deliverReviewVerdictFeedbackHostSide(issueId: string, status: ReviewStatus): Promise<void> {\n try {\n const { deliverReviewVerdictFeedback } = await import('./cloister/review-verdict-feedback.js');\n const result = await Effect.runPromise(deliverReviewVerdictFeedback({\n issueId,\n verdict: status.reviewStatus === 'failed' ? 'failed' : 'blocked',\n notes: status.reviewNotes,\n prUrl: status.prUrl,\n }));\n if (result.agentMessageSent) {\n console.log(`[review-status] delivered review feedback to the work agent for ${issueId} (host-side)`);\n }\n } catch (err) {\n console.warn(`[review-status] host-side review feedback delivery for ${issueId} did not complete (non-fatal): ${err instanceof Error ? err.message : String(err)}`);\n }\n}\n\n// PAN-1988 auto-heal — throttle so the reconcile-on-read does not spam dispatch attempts for a\n// genuinely-gated issue (one whose dispatch keeps deferring on a merge conflict). Date.now() is\n// available in normal lib code (only workflow scripts forbid it).\nconst reviewDispatchAttemptAt = new Map<string, number>();\nconst REVIEW_AUTO_DISPATCH_THROTTLE_MS = 30_000;\n\n/**\n * PAN-1988 auto-heal — the host re-dispatches review from the durable journal intent. When the\n * work agent's `pan done` recorded a `reviewRequestedAt` but the reactive dashboard trigger never\n * landed (dashboard reloading, dropped event, frozen deacon), the next status read notices the\n * un-serviced request and dispatches review here. {@link needsReviewDispatch} gates it,\n * spawnReviewRoleForIssue is idempotent (it skips a live review), and a 30s throttle bounds retries\n * for a gated issue. Fire-and-forget; in a sandboxed reader the dispatch is a best-effort no-op.\n */\nfunction maybeAutoDispatchReviewHostSide(issueId: string, status: ReviewStatus): void {\n if (!needsReviewDispatch({\n reviewRequestedAt: status.reviewRequestedAt,\n reviewSpawnedAt: status.reviewSpawnedAt,\n reviewStatus: status.reviewStatus,\n mergeStatus: status.mergeStatus,\n })) return;\n const last = reviewDispatchAttemptAt.get(issueId) ?? 0;\n if (Date.now() - last < REVIEW_AUTO_DISPATCH_THROTTLE_MS) return;\n reviewDispatchAttemptAt.set(issueId, Date.now());\n void dispatchReviewHostSide(issueId, status.prUrl);\n}\n\nasync function dispatchReviewHostSide(issueId: string, prUrl?: string): Promise<void> {\n try {\n const { resolveProjectFromIssueSync } = await import('./projects.js');\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) return;\n const { existsSync } = await import('fs');\n const workspace = join(resolved.projectPath, 'workspaces', `feature-${issueId.toLowerCase()}`);\n if (!existsSync(workspace)) return;\n let branch = `feature/${issueId.toLowerCase()}`;\n try {\n const { promisify } = await import('util');\n const { exec } = await import('child_process');\n const execAsync = promisify(exec);\n const { stdout } = await execAsync('git branch --show-current', { cwd: workspace, encoding: 'utf-8' });\n branch = stdout.trim() || branch;\n } catch { /* non-fatal — fall back to the conventional branch name */ }\n const { spawnReviewRoleForIssue } = await import('./cloister/review-agent.js');\n const result = await Effect.runPromise(spawnReviewRoleForIssue({ issueId, workspace, branch, ...(prUrl ? { prUrl } : {}) }));\n if (result.success) {\n console.log(`[review-status] auto-dispatched review for ${issueId} from durable journal intent (host-side)`);\n }\n } catch (err) {\n console.warn(`[review-status] host-side review auto-dispatch for ${issueId} did not complete (non-fatal): ${err instanceof Error ? err.message : String(err)}`);\n }\n}\n\n// ── PAN-1988 host-owned TEST-stage recovery ───────────────────────────────────\n// The test stage had NO reactive path. Recovering an unsignaled verdict (the agent — often Haiku —\n// writes .pan/test/result.json then idles without POSTing) AND the test=failed → work-agent requeue\n// both lived ONLY in deacon patrols (checkCompletedButUnsignaledTests + the dead-end requeue). With\n// the deacon frozen, a finished test agent stranded the issue at test=testing forever. The host now\n// (a) recovers the written verdict on read and (b) hands a failure back to the work agent — symmetric\n// with the review stage. Recovery only acts on a WRITTEN artifact; it never guesses pass/fail.\nconst testVerdictRecoveryAt = new Map<string, number>();\nconst TEST_VERDICT_RECOVERY_THROTTLE_MS = 60_000;\n\nfunction maybeRecoverTestVerdictHostSide(issueId: string, status: ReviewStatus): void {\n if (status.reviewStatus !== 'passed') return; // tests only run after review approves\n if (status.mergeStatus === 'merged' || status.readyForMerge) return;\n // Only act while the verdict is UNSIGNALED — once test resolves (passed/failed) the write path\n // owns the transition (test.passed → ship; test failed → deliverTestFailureToWorkAgentHostSide).\n if (status.testStatus !== 'testing' && status.testStatus !== 'pending' && status.testStatus !== 'dispatch_failed') return;\n const last = testVerdictRecoveryAt.get(issueId) ?? 0;\n if (Date.now() - last < TEST_VERDICT_RECOVERY_THROTTLE_MS) return;\n testVerdictRecoveryAt.set(issueId, Date.now());\n void recoverTestVerdictHostSide(issueId);\n}\n\nasync function recoverTestVerdictHostSide(issueId: string): Promise<void> {\n try {\n const { resolveProjectFromIssueSync } = await import('./projects.js');\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) return;\n const { existsSync } = await import('fs');\n const workspace = join(resolved.projectPath, 'workspaces', `feature-${issueId.toLowerCase()}`);\n if (!existsSync(workspace)) return;\n const { readTestVerdictArtifact } = await import('./cloister/test-verdict.js');\n const artifact = readTestVerdictArtifact(workspace);\n if (!artifact) return; // no WRITTEN verdict — never guess; the agent/deacon owns the write-nudge\n // Record the recovered verdict. setReviewStatusSync emits test.passed (→ ship) for a pass and\n // fires the work-agent handoff (below) on a fail, via its test-transition logic.\n setReviewStatusSync(issueId, {\n testStatus: artifact.status,\n testNotes: artifact.notes ?? `Recovered from .pan/test/result.json (${artifact.status}) — the test agent wrote the verdict but never signaled`,\n });\n console.log(`[review-status] recovered unsignaled test verdict for ${issueId}: ${artifact.status} (host-side, from .pan/test/result.json)`);\n } catch (err) {\n console.warn(`[review-status] host-side test verdict recovery for ${issueId} did not complete (non-fatal): ${err instanceof Error ? err.message : String(err)}`);\n }\n}\n\nasync function deliverTestFailureToWorkAgentHostSide(issueId: string, status: ReviewStatus): Promise<void> {\n try {\n const { resolveProjectFromIssueSync } = await import('./projects.js');\n const resolved = resolveProjectFromIssueSync(issueId);\n const workspace = resolved ? join(resolved.projectPath, 'workspaces', `feature-${issueId.toLowerCase()}`) : undefined;\n const notes = status.testNotes; let feedbackPath: string | undefined;\n try {\n const { writeFeedbackFile } = await import('./cloister/feedback-writer.js');\n const r = await Effect.runPromise(writeFeedbackFile({\n issueId, workspacePath: workspace, specialist: 'test-agent', outcome: 'failed', summary: `Tests FAILED for ${issueId}`,\n markdownBody: `# Test failure\\n\\n${notes ?? 'The test gate reported failures. See .pan/test/result.json and re-run the project test suite.'}\\n\\n## Required\\nFix the failing tests, commit and push, then re-run \\`pan done ${issueId}\\`.`,\n }));\n if (r.success) feedbackPath = r.filePath;\n } catch { /* non-fatal — the message below still carries the summary */ }\n\n const message = `SPECIALIST FEEDBACK: test-agent reported FAILED for ${issueId}.\\n\\n${feedbackPath ? `MUST READ: ${feedbackPath}\\n\\n` : ''}${notes ? `${notes.slice(0, 400)}\\n\\n` : ''}Fix the failing tests, commit and push, then re-run pan done ${issueId}. Do NOT stop at the prompt.`;\n const { resolveIssueFeedbackTarget, surfaceIssueFeedbackNeedsYou } = await import('./cloister/feedback-target.js');\n const target = await resolveIssueFeedbackTarget(issueId);\n if ('agentId' in target) {\n const { messageAgent } = await import('./agents.js');\n await messageAgent(target.agentId, message);\n console.log(`[review-status] delivered test failure to ${target.agentId} for ${issueId} (host-side)`);\n } else {\n await surfaceIssueFeedbackNeedsYou(issueId, target.reason, { specialist: 'test-agent', feedbackPath });\n }\n } catch (err) {\n console.warn(`[review-status] host-side test-failure delivery for ${issueId} did not complete (non-fatal): ${err instanceof Error ? err.message : String(err)}`);\n }\n}\n\nexport function getReviewStatusSync(issueId: string): ReviewStatus | null {\n const dbStatus = getReviewStatusFromDbSync(issueId);\n const journal = readJournalStatusSync(issueId);\n\n // No journal record → the DB is all we have. (Pre-PAN-1908 issues, or issues whose\n // workspace/record hasn't been created yet.)\n if (!journal) return dbStatus ?? null;\n\n if ((journal.durable as { closedOut?: boolean }).closedOut === true) {\n // PAN-2054: closed-out journal records are terminal; stale active DB rows are cache residue.\n try {\n dbDelete(issueId);\n } catch {\n // Read-only DB (a sandboxed reader) — the host clears residue when it reads. Non-fatal.\n }\n return null;\n }\n\n // PAN-1988 — journal is the source of truth; DB is a rebuildable cache. If the journal is\n // NEWER than the DB row, an agent recorded its verdict to the journal but the DB write\n // lagged or was blocked (a sandboxed agent that can't write ~/.overdeck). Reconcile the\n // cache from the journal. Whatever process reads next performs this; the host (non-sandboxed)\n // write succeeds, a sandboxed reader's reconcile is a best-effort no-op.\n const journalNewer = !dbStatus || (dbStatus.updatedAt ?? '') < journal.updatedAt;\n if (journalNewer) {\n const merged: ReviewStatus = {\n ...(dbStatus ?? {\n issueId,\n reviewStatus: 'pending' as const,\n testStatus: 'pending' as const,\n updatedAt: journal.updatedAt,\n readyForMerge: false,\n }),\n };\n // Apply ONLY the journal fields that are actually present. The journal carries the durable\n // verdict; DB-only/ephemeral fields it does not store (stuck/stuckReason, transient counters)\n // must NOT be clobbered with `undefined`. This keeps every reconcile a strict overlay.\n for (const [key, value] of Object.entries(journal.durable)) {\n if (value !== undefined) (merged as unknown as Record<string, unknown>)[key] = value;\n }\n merged.issueId = issueId;\n merged.updatedAt = journal.updatedAt;\n const hasBlockers = (merged.blockerReasons?.length ?? 0) > 0;\n merged.readyForMerge = hasBlockers ? false : reviewGatesPassedSync(merged);\n const reconciled = normalizeReviewStatusSync(merged);\n try {\n dbUpsert(reconciled);\n } catch {\n // Read-only DB (a sandboxed reader) — the host reconciles when it reads. Non-fatal.\n }\n\n // PAN-1988 — host-owned review FEEDBACK delivery. A sandboxed review agent records its\n // verdict to the journal but cannot notify the work agent (the work agent's tmux/mail and\n // the network are outside its jail — \"side effects failed due restricted network / readonly\n // host paths\"). When the HOST reconciles a NEW blocked/failed review verdict, it delivers the\n // feedback here. Fires exactly once per verdict: the reconcile only runs while the journal is\n // newer than the DB, and the dbUpsert above makes the DB catch up, so the next read does not\n // re-fire. In a sandboxed agent process the delivery is a best-effort no-op (host paths\n // blocked); the host's read performs the real delivery. Fire-and-forget.\n const wasBlocked = dbStatus?.reviewStatus === 'blocked' || dbStatus?.reviewStatus === 'failed';\n const nowBlocked = reconciled.reviewStatus === 'blocked' || reconciled.reviewStatus === 'failed';\n if (nowBlocked && !wasBlocked) {\n void deliverReviewVerdictFeedbackHostSide(issueId, reconciled);\n }\n\n // PAN-1988 — host-owned review→test and test→ship HANDOFF. setReviewStatusSync emits these\n // lifecycle events on the write path, but a sandboxed agent (codex/pi) records its verdict to\n // the JOURNAL only and the host picks it up HERE via reconcile — which bypasses\n // setReviewStatusSync. Without re-emitting, a passed verdict strands at review=passed/test=pending\n // (and a passed test strands before ship) until the deacon patrol nudges it — and the deacon may\n // be frozen. Re-emit the same transitions the write path would, so reactive Cloister advances.\n const wasReviewPassed = dbStatus?.reviewStatus === 'passed';\n const nowReviewPassed = reconciled.reviewStatus === 'passed';\n if (nowReviewPassed && !wasReviewPassed && reconciled.testStatus === 'pending') {\n console.log(`[review-status] reconcile: review.approved for ${issueId} (host-owned handoff — sandboxed agent verdict from journal)`);\n void emitReactiveLifecycleEvent('review.approved', issueId);\n }\n const wasTestPassed = dbStatus?.testStatus === 'passed';\n const nowTestPassed = reconciled.testStatus === 'passed';\n if (nowTestPassed && !wasTestPassed) {\n console.log(`[review-status] reconcile: test.passed for ${issueId} (host-owned handoff — sandboxed agent verdict from journal)`);\n void emitReactiveLifecycleEvent('test.passed', issueId);\n }\n\n maybeAutoDispatchReviewHostSide(issueId, reconciled);\n maybeRecoverTestVerdictHostSide(issueId, reconciled);\n return reconciled;\n }\n\n // DB is current → overlay the feedback TEXT from the journal (it is no longer stored in the\n // DB; the row holds only the queryable status flags).\n const enriched = enrichReviewNotesFromRecordSync(issueId, dbStatus!);\n maybeAutoDispatchReviewHostSide(issueId, enriched);\n maybeRecoverTestVerdictHostSide(issueId, enriched);\n return enriched;\n}\n\n/**\n * On server startup, clear any mergeStatus stuck at 'merging'.\n * Pending merge operations are in-memory only — they don't survive a restart.\n * Any 'merging' status after boot is definitionally stuck (PAN-490).\n */\nexport function clearStuckMergeStatuses(): void {\n const statuses = loadReviewStatuses();\n // Don't clear 'queued' — the SQLite merge queue handles that (PAN-632).\n // Only clear truly stuck transient states.\n const stuck = Object.values(statuses).filter(s =>\n s.mergeStatus === 'merging' || s.mergeStatus === 'verifying'\n );\n if (stuck.length === 0) return;\n console.log(`[review-status] Clearing ${stuck.length} stuck merge status(es) on startup (merging/verifying)`);\n for (const s of stuck) {\n // Reset to pending so MERGE button reappears — the in-memory queue was lost on restart.\n // Preserve readyForMerge if review+test both passed — the merge just needs to be retried.\n const shouldBeReady =\n s.reviewStatus === 'passed' &&\n (s.testStatus === 'passed' || s.testStatus === 'skipped') &&\n verificationSatisfied(s) &&\n (s.uatStatus === undefined || s.uatStatus === 'passed');\n setReviewStatusSync(s.issueId, {\n mergeStatus: 'pending',\n ...(shouldBeReady ? { readyForMerge: true } : {}),\n });\n }\n}\n\n/**\n * On server startup, fix any issues where review+test both passed and\n * verificationStatus is not 'failed', but readyForMerge is stuck at false.\n *\n * This happens when:\n * 1. A merge attempt was made (merging → verifying)\n * 2. The server restarted while verifying — clearStuckMergeStatuses reset mergeStatus to 'pending'\n * 3. At restart time, verificationStatus was 'pending' (reset by the request-review cycle)\n * 4. The old verificationSatisfied check blocked readyForMerge because of 'pending' status\n *\n * With verificationSatisfied now only blocking on 'failed', these issues should be\n * re-evaluated and readyForMerge restored so they reappear on the Awaiting Merge page.\n */\nexport function fixStuckReadyForMerge(): void {\n const statuses = loadReviewStatuses();\n const stuck = Object.values(statuses).filter(s =>\n s.readyForMerge === false &&\n s.reviewStatus === 'passed' &&\n (s.testStatus === 'passed' || s.testStatus === 'skipped') &&\n verificationSatisfied(s) &&\n // Only fix 'pending'/'queued' merge states — not 'failed' ones.\n // 'failed' means the merge actually attempted and broke; those need human review,\n // not automatic restoration. 'merged' is done. Only pending/queued are stuck-but-valid.\n (s.mergeStatus === 'pending' || s.mergeStatus === 'queued' || s.mergeStatus === undefined || s.mergeStatus === null) &&\n (s.uatStatus === undefined || s.uatStatus === 'passed')\n );\n if (stuck.length === 0) return;\n console.log(`[review-status] Restoring readyForMerge for ${stuck.length} issue(s) with passed review+test`);\n for (const s of stuck) {\n console.log(`[review-status] Restoring readyForMerge=true for ${s.issueId} (verif=${s.verificationStatus}, merge=${s.mergeStatus})`);\n setReviewStatusSync(s.issueId, { readyForMerge: true });\n }\n}\n\n/**\n * PAN-869: On server startup, fix any issues where reviewStatus was incorrectly\n * set to 'failed' due to the old COMMENTED → 'failed' mapping bug.\n *\n * This identifies records where:\n * - reviewStatus = 'failed'\n * - testStatus = 'passed' (CI green, so review wasn't genuinely bad)\n * - mergeStatus is not terminal ('merged', 'failed')\n * - readyForMerge = false\n * - The last review history entry is type='review', status='failed'\n *\n * The old reviewResultToReviewStatus() mapped COMMENTED (regardless of success)\n * to 'failed', so the history stores 'failed' for old COMMENTED reviews.\n * We use testStatus='passed' as the signal that this was a successful review\n * (CI was green), distinguishing it from genuinely failed reviews.\n */\nexport function fixStuckCommentedReviews(): void {\n const statuses = loadReviewStatuses();\n const candidates = Object.values(statuses).filter(s =>\n s.reviewStatus === 'failed' &&\n (s.testStatus === 'passed' || s.testStatus === 'skipped') &&\n s.mergeStatus !== 'merged' &&\n s.mergeStatus !== 'failed' &&\n s.readyForMerge === false &&\n s.verificationStatus !== 'failed'\n );\n\n if (candidates.length === 0) return;\n\n const toFix: string[] = [];\n for (const s of candidates) {\n // Check if the last review history entry is 'failed' (the old COMMENTED mapping\n // stored 'failed' in history). testStatus='passed' signals CI was green,\n // so this was a successful review incorrectly stored as 'failed'.\n const lastReviewEntry = [...(s.history || [])]\n .reverse()\n .find(h => h.type === 'review');\n if (lastReviewEntry?.status === 'failed') {\n toFix.push(s.issueId);\n }\n }\n\n if (toFix.length === 0) return;\n console.log(`[review-status] Restoring reviewStatus='passed' for ${toFix.length} issue(s) with COMMENTED reviews (PAN-869 backfill)`);\n for (const issueId of toFix) {\n console.log(`[review-status] Restoring reviewStatus='passed' for ${issueId}`);\n // reviewStatus='passed' will trigger readyForMerge recomputation in setReviewStatus\n setReviewStatusSync(issueId, { reviewStatus: 'passed' });\n }\n}\n\nexport function clearReviewStatus(issueId: string): void {\n try {\n dbDelete(issueId);\n } catch (err) {\n console.error('[review-status] SQLite delete failed:', err);\n }\n}\n\n// ============== Stuck state helpers (PAN-653) ==============\n\n/**\n * Mark a workspace as stuck with a reason and optional JSON details.\n * Persists across dashboard restarts. Deacon will skip stuck workspaces.\n *\n * @param issueId - Issue ID (e.g. \"PAN-653\")\n * @param reason - Short reason code (e.g. \"main_diverged\")\n * @param details - Optional structured details (e.g. {localSha, remoteSha})\n */\n// PAN-1988: `stuck` is EPHEMERAL runtime state (set when patrol detects a wedged workspace),\n// NOT a durable verdict — projectPipeline deliberately does not journal it, and it is rebuilt\n// from runtime on a cache rebuild. So markWorkspaceStuck/clearWorkspaceStuck write the DB cache\n// directly and do not go through the journal door. The getReviewStatusSync journal→DB reconcile\n// preserves it (a strict overlay that never clobbers DB-only fields the journal doesn't carry).\nexport function markWorkspaceStuck(\n issueId: string,\n reason: string,\n details?: Record<string, unknown>,\n): void {\n try {\n dbMarkStuck(issueId, reason, details);\n console.log(`[review-status] Marked ${issueId} as stuck: ${reason}`);\n const updated = getReviewStatusSync(issueId);\n if (updated) notifyPipelineSync({ type: 'status_changed', issueId, status: updated });\n } catch (err) {\n console.error(`[review-status] Failed to mark ${issueId} as stuck:`, err);\n }\n}\n\n/**\n * Clear the stuck flag for a workspace.\n * Called when the human clicks \"Unstick\" in the dashboard.\n * Re-enables Deacon patrol for this workspace.\n */\nexport function clearWorkspaceStuck(issueId: string): void {\n try {\n dbClearStuck(issueId);\n console.log(`[review-status] Cleared stuck state for ${issueId}`);\n const updated = getReviewStatusSync(issueId);\n if (updated) notifyPipelineSync({ type: 'status_changed', issueId, status: updated });\n } catch (err) {\n console.error(`[review-status] Failed to clear stuck state for ${issueId}:`, err);\n }\n}\n\n/**\n * Set or clear the operator-requested deacon-ignore flag. When set, Deacon\n * patrol skips the issue entirely on every cycle. Distinct from `stuck`, which\n * is a system-set failure marker that also suppresses patrol.\n */\nexport function setDeaconIgnored(\n issueId: string,\n ignored: boolean,\n reason?: string,\n): void {\n try {\n // PAN-1988: route through the single write door. deaconIgnored is a DURABLE field that\n // projectPipeline journals — a DB-only write (the old dbSetDeaconIgnored) lands in the\n // cache but not the journal, so it vanishes on a cache rebuild. setReviewStatusSync writes\n // both (and emits the status_changed event), keeping DB and journal in sync.\n setReviewStatusSync(issueId, {\n deaconIgnored: ignored,\n deaconIgnoredAt: ignored ? new Date().toISOString() : undefined,\n deaconIgnoredReason: ignored ? reason : undefined,\n });\n console.log(`[review-status] deaconIgnored=${ignored} for ${issueId}${reason ? ` (${reason})` : ''}`);\n } catch (err) {\n console.error(`[review-status] Failed to set deaconIgnored for ${issueId}:`, err);\n }\n}\n\n/**\n * PAN-1691: set the per-issue auto-merge routing key and broadcast the change.\n * `autoMerge === null` clears it back to the project default. Emits a\n * status_changed pipeline event so open dashboards reflect the toggle live.\n */\nexport function setAutoMerge(issueId: string, autoMerge: boolean | null): void {\n try {\n // PAN-1988: route through the single write door. autoMerge is a DURABLE field journaled by\n // projectPipeline; a DB-only write would be lost on cache rebuild. `null` clears it back to\n // the project default (stored as undefined). setReviewStatusSync writes DB + journal and\n // emits the status_changed event.\n setReviewStatusSync(issueId, { autoMerge: autoMerge === null ? undefined : autoMerge });\n console.log(`[review-status] autoMerge=${autoMerge === null ? 'default' : autoMerge} for ${issueId}`);\n } catch (err) {\n console.error(`[review-status] Failed to set autoMerge for ${issueId}:`, err);\n }\n}\n\n/** Tagged error for review-status Effect variants. */\nexport class ReviewStatusError extends Data.TaggedError('ReviewStatusError')<{\n readonly issueId: string;\n readonly operation: string;\n readonly message: string;\n readonly cause?: unknown;\n}> {}\n\nexport const setReviewStatus = (\n issueId: string,\n update: Partial<ReviewStatus>,\n existing?: ReviewStatus,\n): Effect.Effect<ReviewStatus, ReviewStatusError> =>\n Effect.tryPromise({\n try: () => new Promise<ReviewStatus>((resolve, reject) => {\n setImmediate(() => {\n try {\n resolve(setReviewStatusSync(issueId, update, existing));\n } catch (err) {\n reject(err);\n }\n });\n }),\n catch: (cause) =>\n new ReviewStatusError({\n issueId,\n operation: 'setReviewStatus',\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n }),\n });\n\nexport const getReviewStatus = (\n issueId: string,\n): Effect.Effect<ReviewStatus | null, ReviewStatusError> =>\n Effect.try({\n try: () => getReviewStatusFromDbSync(issueId),\n catch: (cause) =>\n new ReviewStatusError({\n issueId,\n operation: 'getReviewStatus',\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n }),\n });\n"],"mappings":"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"}