@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":"cli-Cewlu3yU.js","names":["isEnoent"],"sources":["../src/lib/memory/pending.ts","../src/lib/memory/rollup.ts","../src/lib/memory/cli.ts"],"sourcesContent":["import { randomUUID } from 'crypto';\nimport { readdir, readFile, rename, writeFile } from 'fs/promises';\nimport type { MemoryIdentity, PendingTurn } from '@overdeck/contracts';\nimport { ensureDir, resolvePendingDir } from './paths.js';\nimport { getMemoryRollupPendingThreshold } from './settings.js';\nimport { updateMemoryHealth } from './health.js';\n\nexport interface WritePendingTurnResult {\n path: string;\n fileName: string;\n}\n\nexport interface StatusRollupJob {\n identity: Pick<MemoryIdentity, 'projectId' | 'workspaceId' | 'issueId'>;\n pendingTurns: PendingTurn[];\n threshold: number;\n}\n\nexport type EnqueueStatusRollup = (job: StatusRollupJob) => Promise<void>;\nexport type StatusRollupProcessor = (job: StatusRollupJob) => Promise<void>;\n\nexport interface StatusRollupTriggerOptions {\n enqueueStatusRollup?: EnqueueStatusRollup;\n loadThreshold?: () => number | Promise<number>;\n triggerRollup?: boolean;\n}\n\nexport type StatusRollupTriggerResult =\n | { status: 'below-threshold'; pendingCount: number; threshold: number }\n | { status: 'triggered'; pendingCount: number; threshold: number }\n | { status: 'collapsed'; pendingCount: number; threshold: number };\n\nlet configuredStatusRollupEnqueuer: EnqueueStatusRollup | undefined;\nlet configuredStatusRollupProcessor: StatusRollupProcessor | undefined;\nconst inFlightRollups = new Set<string>();\n\nexport function setStatusRollupEnqueuer(enqueue: EnqueueStatusRollup | undefined): () => void {\n const previous = configuredStatusRollupEnqueuer;\n configuredStatusRollupEnqueuer = enqueue;\n return () => {\n configuredStatusRollupEnqueuer = previous;\n };\n}\n\nexport function setStatusRollupProcessor(processor: StatusRollupProcessor | undefined): () => void {\n const previous = configuredStatusRollupProcessor;\n configuredStatusRollupProcessor = processor;\n return () => {\n configuredStatusRollupProcessor = previous;\n };\n}\n\nexport async function writePendingTurn(turn: PendingTurn, options: StatusRollupTriggerOptions = {}): Promise<WritePendingTurnResult> {\n const dir = resolvePendingDir(turn.identity.projectId, turn.identity.issueId);\n await ensureDir(dir);\n\n const existing = await findExistingPendingTurn(dir, turn);\n if (existing) return existing;\n\n const fileName = pendingTurnFileName(turn);\n const path = `${dir}/${fileName}`;\n const tempPath = `${dir}/.${fileName}.${process.pid}.${randomUUID()}.tmp`;\n await writeFile(tempPath, `${JSON.stringify(turn, null, 2)}\\n`, 'utf8');\n await rename(tempPath, path);\n if (options.triggerRollup !== false) await maybeTriggerStatusRollup(turn.identity, options);\n\n return { path, fileName };\n}\n\nexport async function maybeTriggerStatusRollup(\n identity: MemoryIdentity,\n options: StatusRollupTriggerOptions = {},\n): Promise<StatusRollupTriggerResult> {\n const threshold = await loadThreshold(options);\n const pendingTurns = await readPendingTurns(identity.projectId, identity.issueId);\n const pendingCount = pendingTurns.length;\n\n if (pendingCount < threshold) return { status: 'below-threshold', pendingCount, threshold };\n\n const workspaceKey = `${identity.projectId}:${identity.workspaceId}:${identity.issueId}`;\n if (inFlightRollups.has(workspaceKey)) return { status: 'collapsed', pendingCount, threshold };\n\n const enqueue = options.enqueueStatusRollup ?? configuredStatusRollupEnqueuer ?? enqueueStatusRollupEvent;\n\n inFlightRollups.add(workspaceKey);\n try {\n await enqueue({\n identity: {\n projectId: identity.projectId,\n workspaceId: identity.workspaceId,\n issueId: identity.issueId,\n },\n pendingTurns,\n threshold,\n });\n return { status: 'triggered', pendingCount, threshold };\n } finally {\n inFlightRollups.delete(workspaceKey);\n }\n}\n\nexport async function readPendingTurns(projectId: string, issueId: string): Promise<PendingTurn[]> {\n const dir = resolvePendingDir(projectId, issueId);\n const files = (await readdir(dir).catch((error: unknown) => {\n if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') return [] as string[];\n throw error;\n }))\n .filter((file) => file.endsWith('.json') && !file.startsWith('.'))\n .sort();\n\n const turns: PendingTurn[] = [];\n for (const file of files) {\n turns.push(JSON.parse(await readFile(`${dir}/${file}`, 'utf8')) as PendingTurn);\n }\n return turns.sort((a, b) => {\n const timeDiff = new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();\n if (timeDiff !== 0) return timeDiff;\n const sessionDiff = a.identity.sessionId.localeCompare(b.identity.sessionId);\n if (sessionDiff !== 0) return sessionDiff;\n return a.fromOffset - b.fromOffset || a.toOffset - b.toOffset;\n });\n}\n\nexport function pendingTurnFileName(turn: Pick<PendingTurn, 'createdAt' | 'identity' | 'fromOffset' | 'toOffset'>): string {\n const millis = new Date(turn.createdAt).getTime();\n return `${millis}_${pendingTurnRangeKey(turn)}.json`;\n}\n\nasync function findExistingPendingTurn(dir: string, turn: PendingTurn): Promise<WritePendingTurnResult | null> {\n const rangeSuffix = `_${pendingTurnRangeKey(turn)}.json`;\n const files = await readdir(dir).catch((error: unknown) => {\n if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') return [] as string[];\n throw error;\n });\n const fileName = files.find((file) => file.endsWith(rangeSuffix));\n return fileName ? { path: `${dir}/${fileName}`, fileName } : null;\n}\n\nfunction pendingTurnRangeKey(turn: Pick<PendingTurn, 'identity' | 'fromOffset' | 'toOffset'>): string {\n return `${safeFileSegment(turn.identity.sessionId)}_${turn.fromOffset}_${turn.toOffset}`;\n}\n\nasync function enqueueStatusRollupEvent(job: StatusRollupJob): Promise<void> {\n if (configuredStatusRollupProcessor) {\n await configuredStatusRollupProcessor(job);\n return;\n }\n\n const { initEventStore } = await import('../../dashboard/server/event-store.js');\n const { synthesizeStatusRollup, commitStatusRollup } = await import('./rollup.js');\n const store = await initEventStore();\n await store.appendAsync({\n type: 'memory.rollup_triggered',\n timestamp: new Date().toISOString(),\n payload: {\n projectId: job.identity.projectId,\n workspaceId: job.identity.workspaceId,\n issueId: job.identity.issueId,\n pendingCount: job.pendingTurns.length,\n turnIds: job.pendingTurns.map((turn) => turn.id),\n threshold: job.threshold,\n },\n });\n\n const identity = job.pendingTurns[0]?.identity;\n const result = await synthesizeStatusRollup({\n projectId: job.identity.projectId,\n issueId: job.identity.issueId,\n pendingTurns: job.pendingTurns,\n identity,\n });\n if (result.status !== 'synthesized') {\n if (identity) {\n await updateMemoryHealth(identity, {\n status: result.status === 'skipped' ? 'degraded' : 'failing',\n reason: result.reason,\n success: false,\n });\n }\n return;\n }\n\n await commitStatusRollup({\n identity: job.identity,\n status: result.memoryStatus,\n pendingTurns: job.pendingTurns,\n });\n}\n\nasync function loadThreshold(options: StatusRollupTriggerOptions): Promise<number> {\n const threshold = options.loadThreshold ? await options.loadThreshold() : await getMemoryRollupPendingThreshold();\n return Number.isInteger(threshold) && threshold > 0 ? threshold : 4;\n}\n\nfunction safeFileSegment(value: string): string {\n return value.replace(/[^a-zA-Z0-9._-]/g, '_');\n}\n","import { randomUUID } from 'crypto';\nimport { readdir, readFile, rename, unlink, writeFile } from 'fs/promises';\nimport { dirname } from 'path';\nimport { Result, Schema } from 'effect';\nimport {\n MemoryStatus,\n type MemoryIdentity,\n type MemoryObservation,\n type PendingTurn,\n} from '@overdeck/contracts';\nimport {\n extractWithProviderPolicy,\n type MemoryExtractionPolicyResult,\n type MemoryProviderSettings,\n} from './providers/index.js';\nimport { pendingTurnFileName } from './pending.js';\nimport { ensureDir, ensureParentDir, resolveArchiveDir, resolveObservationsFile, resolvePendingDir, resolveStatusFile } from './paths.js';\n\nconst STATUS_RESPONSE_JSON_SCHEMA = {\n type: 'object',\n additionalProperties: false,\n required: [\n 'name',\n 'headline',\n 'summary',\n 'goal',\n 'phase',\n 'accomplished',\n 'decided',\n 'open',\n 'nextSteps',\n 'confidence',\n 'workingSet',\n 'tags',\n ],\n properties: {\n name: { type: 'string' },\n headline: { type: 'string' },\n summary: { type: 'string' },\n goal: { type: ['string', 'null'] },\n phase: { type: 'string', enum: ['exploring', 'planning', 'building', 'verifying', 'cleaning', 'shipping'] },\n accomplished: { type: 'array', items: { type: 'string' } },\n decided: { type: 'array', items: { type: 'string' } },\n open: { type: 'array', items: { type: 'string' } },\n nextSteps: { type: 'array', items: { type: 'string' } },\n confidence: { type: 'number' },\n workingSet: { type: 'array', items: { type: 'string' } },\n tags: { type: 'array', items: { type: 'string' } },\n },\n};\n\nexport interface SynthesizeStatusRollupInput {\n projectId: string;\n issueId: string;\n pendingTurns: PendingTurn[];\n identity?: MemoryIdentity;\n observations?: MemoryObservation[];\n archivedStatuses?: MemoryStatus[];\n settings?: MemoryProviderSettings | null;\n perDayCostCapUsd?: number;\n extract?: StatusRollupExtractCall;\n}\n\nexport type StatusRollupExtractCall = (\n prompt: string,\n jsonSchema: unknown,\n) => Promise<MemoryExtractionPolicyResult<unknown>>;\n\nexport type SynthesizeStatusRollupResult =\n | { status: 'synthesized'; memoryStatus: MemoryStatus }\n | { status: 'skipped'; reason: 'cost-cap' }\n | { status: 'dropped'; reason: 'extraction-failed' | 'malformed-response' | 'missing-identity' };\n\nexport interface CommitStatusRollupInput {\n identity: Pick<MemoryIdentity, 'projectId' | 'workspaceId' | 'issueId'>;\n status: MemoryStatus;\n pendingTurns: PendingTurn[];\n now?: Date;\n emitStatusUpdated?: EmitStatusUpdated;\n failAfterArchive?: boolean;\n failAfterStatusWrite?: boolean;\n}\n\nexport interface CommitStatusRollupResult {\n previousStatus?: MemoryStatus;\n archivedPath?: string;\n statusPath: string;\n clearedPending: string[];\n}\n\nexport type EmitStatusUpdated = (event: {\n identity: Pick<MemoryIdentity, 'projectId' | 'workspaceId' | 'issueId'>;\n status: MemoryStatus;\n previousStatus?: MemoryStatus;\n}) => void | Promise<void>;\n\nexport async function synthesizeStatusRollup(input: SynthesizeStatusRollupInput): Promise<SynthesizeStatusRollupResult> {\n const identity = input.identity ?? input.pendingTurns[0]?.identity;\n if (!identity) return { status: 'dropped', reason: 'missing-identity' };\n\n const observations = input.observations ?? await readRecentObservations(input.projectId, input.issueId, 20);\n const archivedStatuses = input.archivedStatuses ?? await readArchivedStatuses(input.projectId, input.issueId, 3);\n const prompt = buildStatusRollupPrompt({\n pendingTurns: input.pendingTurns,\n observations,\n archivedStatuses,\n });\n const extract = input.extract ?? ((candidatePrompt, jsonSchema) => extractWithProviderPolicy(candidatePrompt, jsonSchema, {\n identity,\n settings: input.settings,\n perDayCostCapUsd: input.perDayCostCapUsd,\n }));\n\n for (let attempt = 0; attempt < 2; attempt++) {\n const result = await extract(prompt, STATUS_RESPONSE_JSON_SCHEMA);\n if (result.status === 'skipped') return { status: 'skipped', reason: result.reason };\n if (result.status === 'dropped') return { status: 'dropped', reason: result.reason };\n\n const statusResult = Schema.decodeUnknownResult(MemoryStatus)(result.result.data);\n if (statusResult._tag === 'Success') {\n return { status: 'synthesized', memoryStatus: Result.getOrThrow(statusResult) };\n }\n }\n\n return { status: 'dropped', reason: 'malformed-response' };\n}\n\nexport async function commitStatusRollup(input: CommitStatusRollupInput): Promise<CommitStatusRollupResult> {\n const { projectId, workspaceId, issueId } = input.identity;\n const statusPath = resolveStatusFile(projectId, issueId);\n const previousStatus = await readCurrentStatus(projectId, issueId);\n let archivedPath: string | undefined;\n\n if (previousStatus) {\n archivedPath = await archiveStatus(projectId, issueId, previousStatus, input.now ?? new Date());\n await pruneArchivedStatuses(projectId, issueId, 3);\n }\n\n if (input.failAfterArchive) throw new Error('Injected rollup archive failure');\n\n await writeJsonAtomically(statusPath, input.status);\n\n if (input.failAfterStatusWrite) throw new Error('Injected rollup status write failure');\n\n await (input.emitStatusUpdated ?? emitStatusUpdatedEvent)({\n identity: { projectId, workspaceId, issueId },\n status: input.status,\n previousStatus,\n });\n\n const clearedPending = await clearPendingTurns(projectId, issueId, input.pendingTurns);\n return { previousStatus, archivedPath, statusPath, clearedPending };\n}\n\nexport async function readCurrentStatus(projectId: string, issueId: string): Promise<MemoryStatus | undefined> {\n try {\n return JSON.parse(await readFile(resolveStatusFile(projectId, issueId), 'utf8')) as MemoryStatus;\n } catch (error) {\n if (isEnoent(error)) return undefined;\n throw error;\n }\n}\n\nexport function buildStatusRollupPrompt(input: {\n pendingTurns: PendingTurn[];\n observations: MemoryObservation[];\n archivedStatuses: MemoryStatus[];\n}): string {\n return [\n 'Synthesize the current Overdeck workspace memory status from recent evidence.',\n 'Return the full status object. This status is a fresh replacement, not a cumulative append-only summary.',\n 'You may add, update, delete, or leave fields unchanged relative to previous statuses based only on current evidence.',\n 'Refresh workingSet every cycle from files currently relevant in the evidence; do not carry forward stale files.',\n 'phase must be exactly one of: exploring, planning, building, verifying, cleaning, shipping.',\n `Last 3 archived statuses:\\n${renderArchivedStatuses(input.archivedStatuses)}`,\n `Last 20 observations:\\n${renderObservations(input.observations)}`,\n `New pending turns:\\n${renderPendingTurns(input.pendingTurns)}`,\n ].join('\\n\\n');\n}\n\nexport async function readRecentObservations(projectId: string, issueId: string, limit = 20): Promise<MemoryObservation[]> {\n const observationsDir = dirname(resolveObservationsFile(projectId, issueId, new Date()));\n const files = (await readdir(observationsDir).catch((error: unknown) => {\n if (isEnoent(error)) return [] as string[];\n throw error;\n }))\n .filter((file) => file.endsWith('.jsonl'))\n .sort()\n .reverse();\n\n const observations: MemoryObservation[] = [];\n for (const file of files) {\n const raw = await readFile(`${observationsDir}/${file}`, 'utf8');\n const lines = raw.trimEnd().split('\\n');\n for (let index = lines.length - 1; index >= 0 && observations.length < limit; index -= 1) {\n const line = lines[index];\n if (!line || line.trim().length === 0) continue;\n observations.push(JSON.parse(line) as MemoryObservation);\n }\n if (observations.length >= limit) break;\n }\n\n return observations\n .sort((a, b) => a.timestamp.localeCompare(b.timestamp));\n}\n\nexport async function readArchivedStatuses(projectId: string, issueId: string, limit = 3): Promise<MemoryStatus[]> {\n const archiveDir = resolveArchiveDir(projectId, issueId);\n const files = (await readdir(archiveDir).catch((error: unknown) => {\n if (isEnoent(error)) return [] as string[];\n throw error;\n }))\n .filter((file) => file.endsWith('.json'))\n .sort()\n .slice(-limit);\n\n const statuses: MemoryStatus[] = [];\n for (const file of files) {\n statuses.push(JSON.parse(await readFile(`${archiveDir}/${file}`, 'utf8')) as MemoryStatus);\n }\n return statuses;\n}\n\nasync function archiveStatus(projectId: string, issueId: string, status: MemoryStatus, now: Date): Promise<string> {\n const archiveDir = resolveArchiveDir(projectId, issueId);\n await ensureDir(archiveDir);\n const path = `${archiveDir}/${now.toISOString().replace(/[:.]/g, '-')}_${slugify(status.name)}_${randomUUID()}.json`;\n await writeJsonAtomically(path, status);\n return path;\n}\n\nasync function pruneArchivedStatuses(projectId: string, issueId: string, keep: number): Promise<void> {\n const archiveDir = resolveArchiveDir(projectId, issueId);\n const files = (await readdir(archiveDir).catch((error: unknown) => {\n if (isEnoent(error)) return [] as string[];\n throw error;\n }))\n .filter((file) => file.endsWith('.json'))\n .sort();\n\n await Promise.all(files.slice(0, Math.max(0, files.length - keep)).map((file) => unlink(`${archiveDir}/${file}`)));\n}\n\nasync function clearPendingTurns(projectId: string, issueId: string, pendingTurns: PendingTurn[]): Promise<string[]> {\n const pendingDir = resolvePendingDir(projectId, issueId);\n const cleared: string[] = [];\n for (const turn of pendingTurns) {\n const path = `${pendingDir}/${pendingTurnFileName(turn)}`;\n try {\n await unlink(path);\n cleared.push(path);\n } catch (error) {\n if (!isEnoent(error)) throw error;\n }\n }\n return cleared;\n}\n\nasync function emitStatusUpdatedEvent(event: {\n identity: Pick<MemoryIdentity, 'projectId' | 'workspaceId' | 'issueId'>;\n status: MemoryStatus;\n previousStatus?: MemoryStatus;\n}): Promise<void> {\n const { initEventStore } = await import('../../dashboard/server/event-store.js');\n const store = await initEventStore();\n await store.appendAsync({\n type: 'memory.status_updated',\n timestamp: new Date().toISOString(),\n payload: event,\n });\n}\n\nasync function writeJsonAtomically(path: string, value: unknown): Promise<void> {\n await ensureParentDir(path);\n const tempPath = `${dirname(path)}/.${randomUUID()}.tmp`;\n await writeFile(tempPath, `${JSON.stringify(value, null, 2)}\\n`, 'utf8');\n await rename(tempPath, path);\n}\n\nfunction slugify(value: string): string {\n const slug = value.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');\n return slug.length > 0 ? slug : 'status';\n}\n\nfunction renderArchivedStatuses(statuses: MemoryStatus[]): string {\n if (statuses.length === 0) return 'none';\n return statuses.map((status, index) => [\n `Archived status ${index + 1}:`,\n `- Name: ${status.name}`,\n `- Headline: ${status.headline}`,\n `- Summary: ${status.summary}`,\n `- Goal: ${status.goal ?? 'none'}`,\n `- Phase: ${status.phase}`,\n `- Accomplished: ${status.accomplished.join('; ') || 'none'}`,\n `- Decided: ${status.decided.join('; ') || 'none'}`,\n `- Open: ${status.open.join('; ') || 'none'}`,\n `- Next steps: ${status.nextSteps.join('; ') || 'none'}`,\n `- Working set: ${status.workingSet.join(', ') || 'none'}`,\n `- Tags: ${status.tags.join(', ') || 'none'}`,\n ].join('\\n')).join('\\n\\n');\n}\n\nfunction renderObservations(observations: MemoryObservation[]): string {\n if (observations.length === 0) return 'none';\n return observations.map((observation, index) => [\n `Observation ${index + 1}:`,\n `- Timestamp: ${observation.timestamp}`,\n `- Action status: ${observation.actionStatus ?? 'none'}`,\n `- Summary: ${observation.summary}`,\n `- Narrative: ${observation.narrative}`,\n `- Files: ${observation.files.join(', ') || 'none'}`,\n `- Tags: ${observation.tags.join(', ') || 'none'}`,\n ].join('\\n')).join('\\n\\n');\n}\n\nconst PENDING_TURN_PROMPT_BUDGET_CHARS = 12_000;\nconst PENDING_TURN_TEXT_BUDGET_CHARS = 2_000;\n\nfunction renderPendingTurns(pendingTurns: PendingTurn[]): string {\n if (pendingTurns.length === 0) return 'none';\n\n const rendered: string[] = [];\n let used = 0;\n let omitted = 0;\n for (const [index, turn] of pendingTurns.entries()) {\n const compressedText = truncateText(turn.compressedText, PENDING_TURN_TEXT_BUDGET_CHARS);\n const block = [\n `Pending turn ${index + 1}:`,\n `- Created: ${turn.createdAt}`,\n `- Session: ${turn.identity.sessionId}`,\n `- Trigger: ${turn.trigger}`,\n `- Offset: ${turn.fromOffset}-${turn.toOffset}`,\n `- Events consumed: ${turn.eventsConsumed}`,\n `- Compressed text:\\n${compressedText}`,\n ].join('\\n');\n if (used + block.length > PENDING_TURN_PROMPT_BUDGET_CHARS) {\n omitted = pendingTurns.length - index;\n break;\n }\n rendered.push(block);\n used += block.length;\n }\n\n if (omitted > 0) rendered.push(`Omitted ${omitted} pending turn(s) after the ${PENDING_TURN_PROMPT_BUDGET_CHARS} character budget.`);\n return rendered.join('\\n\\n');\n}\n\nfunction truncateText(value: string, maxChars: number): string {\n if (value.length <= maxChars) return value;\n return `${value.slice(0, maxChars)}\\n[truncated ${value.length - maxChars} chars]`;\n}\n\nfunction isEnoent(error: unknown): boolean {\n return typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT';\n}\n","import { randomUUID } from 'crypto';\nimport { readdir, readFile, rename, writeFile } from 'fs/promises';\nimport { dirname, join } from 'path';\nimport type { MemoryObservation, MemoryStatus, ResetMarker } from '@overdeck/contracts';\nimport { getOverdeckHome } from '../paths.js';\nimport { runMemoryFtsTransaction, withMemoryFtsDatabase } from './fts-db.js';\nimport { resolveExtractionProviderSelection } from './providers/index.js';\nimport { getMemoryRollupPendingThreshold, loadMemorySettings } from './settings.js';\nimport {\n ensureParentDir,\n resolveMemoryRoot,\n resolveObservationsFile,\n resolvePendingDir,\n resolveSummariesDir,\n} from './paths.js';\nimport { getMemoryHealthPath, type MemoryHealthSnapshot } from './health.js';\nimport { readCurrentStatus } from './rollup.js';\nimport { getAgentStateSync } from '../agents.js';\n\nconst DEFAULT_PROJECT_ID = 'overdeck';\nconst MIN_DAILY_SUMMARY_OBSERVATIONS = 3;\nconst DAILY_SUMMARY_REGENERATION_OBSERVATIONS = 20;\n\nexport interface MemorySearchOptions {\n project?: string;\n workspace?: string;\n issue?: string;\n tag?: string;\n sibling?: boolean;\n limit?: number;\n includeArchived?: boolean;\n}\n\nexport interface MemorySearchResult {\n observation: MemoryObservation;\n score: number;\n}\n\nexport type DailySummaryStatus = 'generated' | 'insufficient-data' | 'up-to-date';\n\nexport interface DailySummaryResult {\n status: DailySummaryStatus;\n path: string;\n markdown: string;\n observationCount: number;\n previousObservationCount: number | null;\n}\n\nexport interface MemoryDoctorOptions {\n project?: string;\n now?: Date;\n}\n\nexport interface MemoryDoctorResult {\n exitCode: number;\n provider: Awaited<ReturnType<typeof resolveExtractionProviderSelection>>;\n rollupPendingThreshold: number;\n issues: Array<{\n projectId: string;\n issueId: string;\n health: MemoryHealthSnapshot;\n pendingCount: number;\n lastObservation: string | null;\n }>;\n staleActiveAgents: Array<{ agentId: string; issueId: string; lastSuccess: string | null }>;\n}\n\nexport async function searchMemory(query: string, options: MemorySearchOptions = {}): Promise<MemorySearchResult[]> {\n const projectId = options.project ?? DEFAULT_PROJECT_ID;\n const observations = await readObservationScope(projectId, options.issue, options.sibling ?? false);\n const resetMarkers = options.includeArchived ? [] : await readResetMarkers(projectId);\n const terms = query.toLowerCase().split(/\\s+/).filter(Boolean);\n const results = observations\n .filter((observation) => !options.workspace || observation.workspaceId === options.workspace)\n .filter((observation) => !options.issue || options.sibling || observation.issueId === options.issue)\n .filter((observation) => options.includeArchived || isAfterLatestResetMarker(observation, resetMarkers))\n .filter((observation) => !options.tag || observation.tags.includes(options.tag))\n .map((observation) => ({ observation, score: scoreObservation(observation, terms) }))\n .filter((result) => terms.length === 0 || result.score > 0)\n .sort((a, b) => b.score - a.score || b.observation.timestamp.localeCompare(a.observation.timestamp));\n\n return results.slice(0, options.limit ?? 20);\n}\n\nexport async function getMemoryStatus(projectId: string, issueId: string): Promise<MemoryStatus | undefined> {\n return readCurrentStatus(projectId, issueId);\n}\n\nexport async function createResetMarker(input: {\n projectId?: string;\n scope: ResetMarker['scope'];\n scopeId: string;\n reason: string;\n fromTimestamp?: string;\n id?: string;\n createdAt?: string;\n emitResetMarkerCreated?: (marker: ResetMarker, timestamp: string) => void | Promise<void>;\n}): Promise<ResetMarker> {\n const projectId = input.projectId ?? DEFAULT_PROJECT_ID;\n const marker: ResetMarker = {\n id: input.id ?? randomUUID(),\n scope: input.scope,\n scopeId: input.scopeId,\n fromTimestamp: input.fromTimestamp ?? new Date().toISOString(),\n reason: input.reason,\n createdAt: input.createdAt ?? new Date().toISOString(),\n };\n const path = join(resolveMemoryRoot(projectId), 'reset-markers.json');\n const markers = await readJsonFile<ResetMarker[]>(path, []);\n await writeJsonAtomically(path, [...markers, marker]);\n await writeResetMarkerToFtsDb(projectId, marker);\n await (input.emitResetMarkerCreated ?? emitResetMarkerCreated)(marker, marker.createdAt);\n return marker;\n}\n\nexport async function generateDailySummary(input: {\n projectId?: string;\n issueId: string;\n date?: string;\n}): Promise<DailySummaryResult> {\n const projectId = input.projectId ?? DEFAULT_PROJECT_ID;\n const date = input.date ?? new Date().toISOString().slice(0, 10);\n const path = join(resolveSummariesDir(projectId, input.issueId), `${date}.md`);\n const observations = await readObservationsFile(resolveObservationsFile(projectId, input.issueId, date));\n const existingMarkdown = await readTextFile(path);\n const previousObservationCount = existingMarkdown ? parseSummaryObservationCount(existingMarkdown) : null;\n\n if (observations.length < MIN_DAILY_SUMMARY_OBSERVATIONS) {\n return { status: 'insufficient-data', path, markdown: existingMarkdown ?? '', observationCount: observations.length, previousObservationCount };\n }\n\n if (existingMarkdown && previousObservationCount !== null && observations.length - previousObservationCount < DAILY_SUMMARY_REGENERATION_OBSERVATIONS) {\n return { status: 'up-to-date', path, markdown: existingMarkdown, observationCount: observations.length, previousObservationCount };\n }\n\n const markdown = buildDailySummaryMarkdown(input.issueId, date, observations);\n await ensureParentDir(path);\n await writeFile(path, markdown, 'utf8');\n await indexDailySummary(projectId, input.issueId, date, observations, markdown);\n return { status: 'generated', path, markdown, observationCount: observations.length, previousObservationCount };\n}\n\nexport async function runMemoryDoctor(options: MemoryDoctorOptions = {}): Promise<MemoryDoctorResult> {\n const projectId = options.project ?? DEFAULT_PROJECT_ID;\n const now = options.now ?? new Date();\n const provider = await resolveExtractionProviderSelection();\n const rollupPendingThreshold = await getMemoryRollupPendingThreshold();\n const issues = await readIssueDoctorSnapshots(projectId);\n const activeAgents = await readActiveAgents();\n const staleActiveAgents = activeAgents\n .map((agent) => {\n const issue = issues.find((candidate) => candidate.issueId === agent.issueId);\n return { agentId: agent.id, issueId: agent.issueId, lastSuccess: issue?.health.last_success ?? null };\n })\n .filter((agent) => isStaleExtraction(agent.lastSuccess, now));\n\n return {\n exitCode: staleActiveAgents.length > 0 ? 1 : 0,\n provider,\n rollupPendingThreshold,\n issues,\n staleActiveAgents,\n };\n}\n\nexport async function readMemorySettingsSummary(): Promise<{\n rollupPendingThreshold: number;\n provider: Awaited<ReturnType<typeof resolveExtractionProviderSelection>>;\n}> {\n return {\n rollupPendingThreshold: (await loadMemorySettings()).rollupPendingThreshold,\n provider: await resolveExtractionProviderSelection(),\n };\n}\n\nasync function readObservationScope(projectId: string, issueId: string | undefined, sibling: boolean): Promise<MemoryObservation[]> {\n if (issueId && !sibling) return readIssueObservations(projectId, issueId);\n const issueIds = await listIssueIds(projectId);\n const selectedIssueIds = issueId && sibling ? issueIds.filter((candidate) => candidate !== issueId) : issueIds;\n const nested = await Promise.all(selectedIssueIds.map((candidate) => readIssueObservations(projectId, candidate)));\n return nested.flat();\n}\n\nasync function readIssueObservations(projectId: string, issueId: string): Promise<MemoryObservation[]> {\n const observationsDir = dirname(resolveObservationsFile(projectId, issueId, new Date()));\n const files = (await readdir(observationsDir).catch((error: unknown) => {\n if (isEnoent(error)) return [] as string[];\n throw error;\n })).filter((file) => file.endsWith('.jsonl')).sort();\n const nested = await Promise.all(files.map((file) => readObservationsFile(join(observationsDir, file))));\n return nested.flat();\n}\n\nasync function readResetMarkers(projectId: string): Promise<ResetMarker[]> {\n const jsonMarkers = await readJsonFile<ResetMarker[]>(join(resolveMemoryRoot(projectId), 'reset-markers.json'), []);\n const dbMarkers = await withMemoryFtsDatabase(projectId, (db) => db.prepare(`\n SELECT id, scope, scope_id, from_timestamp, reason, created_at\n FROM reset_markers\n `).all<{\n id: number;\n scope: ResetMarker['scope'];\n scope_id: string;\n from_timestamp: string;\n reason: string | null;\n created_at: string;\n }>()).catch((error: unknown) => {\n if (isNoSuchTable(error)) return [];\n throw error;\n });\n return dedupeResetMarkers([\n ...jsonMarkers,\n ...dbMarkers.map((marker) => ({\n id: `db-${marker.id}`,\n scope: marker.scope,\n scopeId: marker.scope_id,\n fromTimestamp: marker.from_timestamp,\n reason: marker.reason ?? '',\n createdAt: marker.created_at,\n })),\n ]);\n}\n\nfunction dedupeResetMarkers(markers: ResetMarker[]): ResetMarker[] {\n const seen = new Set<string>();\n const deduped: ResetMarker[] = [];\n for (const marker of markers) {\n const key = [marker.scope, marker.scopeId, marker.fromTimestamp, marker.reason, marker.createdAt].join('|');\n if (seen.has(key)) continue;\n seen.add(key);\n deduped.push(marker);\n }\n return deduped;\n}\n\nfunction isAfterLatestResetMarker(observation: MemoryObservation, markers: ResetMarker[]): boolean {\n const latest = markers\n .filter((marker) => appliesToObservation(marker, observation))\n .map((marker) => marker.fromTimestamp)\n .sort()\n .at(-1);\n return !latest || observation.timestamp > latest;\n}\n\nfunction appliesToObservation(marker: ResetMarker, observation: MemoryObservation): boolean {\n switch (marker.scope) {\n case 'project':\n return marker.scopeId === observation.projectId;\n case 'workspace':\n return marker.scopeId === observation.workspaceId;\n case 'issue':\n return marker.scopeId === observation.issueId;\n case 'session':\n return marker.scopeId === observation.sessionId;\n }\n}\n\nasync function readObservationsFile(path: string): Promise<MemoryObservation[]> {\n let raw: string;\n try {\n raw = await readFile(path, 'utf8');\n } catch (error) {\n if (isEnoent(error)) return [];\n throw error;\n }\n return raw.split('\\n')\n .filter((line) => line.trim().length > 0)\n .map((line) => JSON.parse(line) as MemoryObservation);\n}\n\nasync function readTextFile(path: string): Promise<string | null> {\n try {\n return await readFile(path, 'utf8');\n } catch (error) {\n if (isEnoent(error)) return null;\n throw error;\n }\n}\n\nfunction buildDailySummaryMarkdown(issueId: string, date: string, observations: MemoryObservation[]): string {\n const actions = observations.filter((observation) => observation.actionStatus !== null);\n return [\n `# ${issueId} memory summary — ${date}`,\n '',\n `Observations: ${observations.length}`,\n `Action updates: ${actions.length}`,\n '',\n ...observations.map((observation) => [\n `## ${observation.timestamp}`,\n '',\n observation.actionStatus ? `**Action:** ${observation.actionStatus}` : '**Action:** none',\n '',\n observation.summary,\n '',\n observation.files.length > 0 ? `Files: ${observation.files.join(', ')}` : 'Files: none',\n observation.tags.length > 0 ? `Tags: ${observation.tags.join(', ')}` : 'Tags: none',\n '',\n ].join('\\n')),\n ].join('\\n');\n}\n\nfunction parseSummaryObservationCount(markdown: string): number | null {\n const match = markdown.match(/^Observations: (\\d+)$/m);\n return match ? Number(match[1]) : null;\n}\n\nasync function indexDailySummary(projectId: string, issueId: string, date: string, observations: MemoryObservation[], markdown: string): Promise<void> {\n const latest = observations.at(-1);\n const files = [...new Set(observations.flatMap((observation) => observation.files))].join(',');\n const tags = [...new Set(['memory', 'summary', ...observations.flatMap((observation) => observation.tags)])].join(',');\n const entryTime = latest?.timestamp.slice(11) ?? '00:00:00.000Z';\n await runMemoryFtsTransaction(projectId, [\n {\n method: 'run',\n sql: `\n DELETE FROM memory_fts\n WHERE project_id = ?\n AND issue_id = ?\n AND doc_type = 'summary'\n AND entry_date = ?\n `,\n params: [projectId, issueId, date],\n },\n {\n method: 'run',\n sql: `\n INSERT INTO memory_fts (\n content,\n display_content,\n source,\n branch,\n entry_date,\n entry_time,\n entry_type,\n files,\n tags,\n doc_type,\n scope,\n project_id,\n workspace_id,\n issue_id,\n run_id,\n session_id,\n agent_role,\n agent_harness\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `,\n params: [\n markdown,\n markdown,\n 'summary',\n latest?.gitBranch ?? '',\n date,\n entryTime,\n 'memory-summary',\n files,\n tags,\n 'summary',\n 'issue',\n projectId,\n latest?.workspaceId ?? '',\n issueId,\n latest?.runId ?? '',\n latest?.sessionId ?? '',\n latest?.agentRole ?? '',\n latest?.agentHarness ?? '',\n ],\n },\n ]);\n}\n\nasync function listIssueIds(projectId: string): Promise<string[]> {\n const root = resolveMemoryRoot(projectId);\n const entries = await readdir(root, { withFileTypes: true }).catch((error: unknown) => {\n if (isEnoent(error)) return [];\n throw error;\n });\n return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();\n}\n\nfunction scoreObservation(observation: MemoryObservation, terms: string[]): number {\n const haystack = [\n observation.actionStatus ?? '',\n observation.summary,\n observation.narrative,\n observation.files.join(' '),\n observation.tags.join(' '),\n ].join(' ').toLowerCase();\n return terms.reduce((score, term) => score + occurrences(haystack, term), 0);\n}\n\nfunction occurrences(value: string, term: string): number {\n let count = 0;\n let index = value.indexOf(term);\n while (index !== -1) {\n count += 1;\n index = value.indexOf(term, index + term.length);\n }\n return count;\n}\n\nasync function readIssueDoctorSnapshots(projectId: string): Promise<MemoryDoctorResult['issues']> {\n const issueIds = await listIssueIds(projectId);\n return Promise.all(issueIds.map(async (issueId) => {\n const health = await readJsonFile<MemoryHealthSnapshot>(getMemoryHealthPath({ projectId, issueId }), emptyHealth());\n const pendingCount = await countJsonFiles(resolvePendingDir(projectId, issueId));\n const observations = await readIssueObservations(projectId, issueId);\n return {\n projectId,\n issueId,\n health,\n pendingCount,\n lastObservation: observations.at(-1)?.timestamp ?? null,\n };\n }));\n}\n\nasync function readActiveAgents(): Promise<Array<{ id: string; issueId: string }>> {\n const agentsDir = join(getOverdeckHome(), 'agents');\n const entries = await readdir(agentsDir, { withFileTypes: true }).catch((error: unknown) => {\n if (isEnoent(error)) return [];\n throw error;\n });\n const agents: Array<{ id: string; issueId: string }> = [];\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const state = getAgentStateSync(entry.name);\n if ((state?.status === 'running' || state?.status === 'starting') && state?.issueId) {\n agents.push({ id: entry.name, issueId: state.issueId });\n }\n }\n return agents;\n}\n\nasync function countJsonFiles(path: string): Promise<number> {\n const entries = await readdir(path).catch((error: unknown) => {\n if (isEnoent(error)) return [] as string[];\n throw error;\n });\n return entries.filter((entry) => entry.endsWith('.json') && !entry.startsWith('.')).length;\n}\n\nasync function readJsonFile<T>(path: string, fallback: T): Promise<T> {\n try {\n return JSON.parse(await readFile(path, 'utf8')) as T;\n } catch (error) {\n if (isEnoent(error)) return fallback;\n throw error;\n }\n}\n\nasync function writeJsonAtomically(path: string, value: unknown): Promise<void> {\n await ensureParentDir(path);\n const tempPath = `${dirname(path)}/.${randomUUID()}.tmp`;\n await writeFile(tempPath, `${JSON.stringify(value, null, 2)}\\n`, 'utf8');\n await rename(tempPath, path);\n}\n\nasync function writeResetMarkerToFtsDb(projectId: string, marker: ResetMarker): Promise<void> {\n await withMemoryFtsDatabase(projectId, async (db) => {\n await db.prepare(`\n INSERT INTO reset_markers (scope, scope_id, from_timestamp, reason, created_at)\n VALUES (?, ?, ?, ?, ?)\n `).run(marker.scope, marker.scopeId, marker.fromTimestamp, marker.reason, marker.createdAt);\n });\n}\n\nasync function emitResetMarkerCreated(marker: ResetMarker, timestamp: string): Promise<void> {\n const { initEventStore } = await import('../../dashboard/server/event-store.js');\n const store = await initEventStore();\n await store.appendAsync({\n type: 'memory.reset_marker_created',\n timestamp,\n payload: { marker },\n });\n}\n\nfunction isStaleExtraction(lastSuccess: string | null, now: Date): boolean {\n if (!lastSuccess) return true;\n const timestamp = Date.parse(lastSuccess);\n return Number.isNaN(timestamp) || now.getTime() - timestamp > 60 * 60 * 1000;\n}\n\nfunction emptyHealth(): MemoryHealthSnapshot {\n return {\n status: 'healthy',\n last_success: null,\n last_failure: null,\n extractions_attempted: 0,\n extractions_succeeded: 0,\n failed_by_reason: {},\n };\n}\n\nfunction isEnoent(error: unknown): boolean {\n return typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT';\n}\n\nfunction isNoSuchTable(error: unknown): boolean {\n return error instanceof Error && error.message.includes('no such table: reset_markers');\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli-DGeSW85B.js","names":["isEnoent"],"sources":["../src/lib/memory/pending.ts","../src/lib/memory/rollup.ts","../src/lib/memory/cli.ts"],"sourcesContent":["import { randomUUID } from 'crypto';\nimport { readdir, readFile, rename, writeFile } from 'fs/promises';\nimport type { MemoryIdentity, PendingTurn } from '@overdeck/contracts';\nimport { ensureDir, resolvePendingDir } from './paths.js';\nimport { getMemoryRollupPendingThreshold } from './settings.js';\nimport { updateMemoryHealth } from './health.js';\n\nexport interface WritePendingTurnResult {\n path: string;\n fileName: string;\n}\n\nexport interface StatusRollupJob {\n identity: Pick<MemoryIdentity, 'projectId' | 'workspaceId' | 'issueId'>;\n pendingTurns: PendingTurn[];\n threshold: number;\n}\n\nexport type EnqueueStatusRollup = (job: StatusRollupJob) => Promise<void>;\nexport type StatusRollupProcessor = (job: StatusRollupJob) => Promise<void>;\n\nexport interface StatusRollupTriggerOptions {\n enqueueStatusRollup?: EnqueueStatusRollup;\n loadThreshold?: () => number | Promise<number>;\n triggerRollup?: boolean;\n}\n\nexport type StatusRollupTriggerResult =\n | { status: 'below-threshold'; pendingCount: number; threshold: number }\n | { status: 'triggered'; pendingCount: number; threshold: number }\n | { status: 'collapsed'; pendingCount: number; threshold: number };\n\nlet configuredStatusRollupEnqueuer: EnqueueStatusRollup | undefined;\nlet configuredStatusRollupProcessor: StatusRollupProcessor | undefined;\nconst inFlightRollups = new Set<string>();\n\nexport function setStatusRollupEnqueuer(enqueue: EnqueueStatusRollup | undefined): () => void {\n const previous = configuredStatusRollupEnqueuer;\n configuredStatusRollupEnqueuer = enqueue;\n return () => {\n configuredStatusRollupEnqueuer = previous;\n };\n}\n\nexport function setStatusRollupProcessor(processor: StatusRollupProcessor | undefined): () => void {\n const previous = configuredStatusRollupProcessor;\n configuredStatusRollupProcessor = processor;\n return () => {\n configuredStatusRollupProcessor = previous;\n };\n}\n\nexport async function writePendingTurn(turn: PendingTurn, options: StatusRollupTriggerOptions = {}): Promise<WritePendingTurnResult> {\n const dir = resolvePendingDir(turn.identity.projectId, turn.identity.issueId);\n await ensureDir(dir);\n\n const existing = await findExistingPendingTurn(dir, turn);\n if (existing) return existing;\n\n const fileName = pendingTurnFileName(turn);\n const path = `${dir}/${fileName}`;\n const tempPath = `${dir}/.${fileName}.${process.pid}.${randomUUID()}.tmp`;\n await writeFile(tempPath, `${JSON.stringify(turn, null, 2)}\\n`, 'utf8');\n await rename(tempPath, path);\n if (options.triggerRollup !== false) await maybeTriggerStatusRollup(turn.identity, options);\n\n return { path, fileName };\n}\n\nexport async function maybeTriggerStatusRollup(\n identity: MemoryIdentity,\n options: StatusRollupTriggerOptions = {},\n): Promise<StatusRollupTriggerResult> {\n const threshold = await loadThreshold(options);\n const pendingTurns = await readPendingTurns(identity.projectId, identity.issueId);\n const pendingCount = pendingTurns.length;\n\n if (pendingCount < threshold) return { status: 'below-threshold', pendingCount, threshold };\n\n const workspaceKey = `${identity.projectId}:${identity.workspaceId}:${identity.issueId}`;\n if (inFlightRollups.has(workspaceKey)) return { status: 'collapsed', pendingCount, threshold };\n\n const enqueue = options.enqueueStatusRollup ?? configuredStatusRollupEnqueuer ?? enqueueStatusRollupEvent;\n\n inFlightRollups.add(workspaceKey);\n try {\n await enqueue({\n identity: {\n projectId: identity.projectId,\n workspaceId: identity.workspaceId,\n issueId: identity.issueId,\n },\n pendingTurns,\n threshold,\n });\n return { status: 'triggered', pendingCount, threshold };\n } finally {\n inFlightRollups.delete(workspaceKey);\n }\n}\n\nexport async function readPendingTurns(projectId: string, issueId: string): Promise<PendingTurn[]> {\n const dir = resolvePendingDir(projectId, issueId);\n const files = (await readdir(dir).catch((error: unknown) => {\n if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') return [] as string[];\n throw error;\n }))\n .filter((file) => file.endsWith('.json') && !file.startsWith('.'))\n .sort();\n\n const turns: PendingTurn[] = [];\n for (const file of files) {\n turns.push(JSON.parse(await readFile(`${dir}/${file}`, 'utf8')) as PendingTurn);\n }\n return turns.sort((a, b) => {\n const timeDiff = new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();\n if (timeDiff !== 0) return timeDiff;\n const sessionDiff = a.identity.sessionId.localeCompare(b.identity.sessionId);\n if (sessionDiff !== 0) return sessionDiff;\n return a.fromOffset - b.fromOffset || a.toOffset - b.toOffset;\n });\n}\n\nexport function pendingTurnFileName(turn: Pick<PendingTurn, 'createdAt' | 'identity' | 'fromOffset' | 'toOffset'>): string {\n const millis = new Date(turn.createdAt).getTime();\n return `${millis}_${pendingTurnRangeKey(turn)}.json`;\n}\n\nasync function findExistingPendingTurn(dir: string, turn: PendingTurn): Promise<WritePendingTurnResult | null> {\n const rangeSuffix = `_${pendingTurnRangeKey(turn)}.json`;\n const files = await readdir(dir).catch((error: unknown) => {\n if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') return [] as string[];\n throw error;\n });\n const fileName = files.find((file) => file.endsWith(rangeSuffix));\n return fileName ? { path: `${dir}/${fileName}`, fileName } : null;\n}\n\nfunction pendingTurnRangeKey(turn: Pick<PendingTurn, 'identity' | 'fromOffset' | 'toOffset'>): string {\n return `${safeFileSegment(turn.identity.sessionId)}_${turn.fromOffset}_${turn.toOffset}`;\n}\n\nasync function enqueueStatusRollupEvent(job: StatusRollupJob): Promise<void> {\n if (configuredStatusRollupProcessor) {\n await configuredStatusRollupProcessor(job);\n return;\n }\n\n const { initEventStore } = await import('../../dashboard/server/event-store.js');\n const { synthesizeStatusRollup, commitStatusRollup } = await import('./rollup.js');\n const store = await initEventStore();\n await store.appendAsync({\n type: 'memory.rollup_triggered',\n timestamp: new Date().toISOString(),\n payload: {\n projectId: job.identity.projectId,\n workspaceId: job.identity.workspaceId,\n issueId: job.identity.issueId,\n pendingCount: job.pendingTurns.length,\n turnIds: job.pendingTurns.map((turn) => turn.id),\n threshold: job.threshold,\n },\n });\n\n const identity = job.pendingTurns[0]?.identity;\n const result = await synthesizeStatusRollup({\n projectId: job.identity.projectId,\n issueId: job.identity.issueId,\n pendingTurns: job.pendingTurns,\n identity,\n });\n if (result.status !== 'synthesized') {\n if (identity) {\n await updateMemoryHealth(identity, {\n status: result.status === 'skipped' ? 'degraded' : 'failing',\n reason: result.reason,\n success: false,\n });\n }\n return;\n }\n\n await commitStatusRollup({\n identity: job.identity,\n status: result.memoryStatus,\n pendingTurns: job.pendingTurns,\n });\n}\n\nasync function loadThreshold(options: StatusRollupTriggerOptions): Promise<number> {\n const threshold = options.loadThreshold ? await options.loadThreshold() : await getMemoryRollupPendingThreshold();\n return Number.isInteger(threshold) && threshold > 0 ? threshold : 4;\n}\n\nfunction safeFileSegment(value: string): string {\n return value.replace(/[^a-zA-Z0-9._-]/g, '_');\n}\n","import { randomUUID } from 'crypto';\nimport { readdir, readFile, rename, unlink, writeFile } from 'fs/promises';\nimport { dirname } from 'path';\nimport { Result, Schema } from 'effect';\nimport {\n MemoryStatus,\n type MemoryIdentity,\n type MemoryObservation,\n type PendingTurn,\n} from '@overdeck/contracts';\nimport {\n extractWithProviderPolicy,\n type MemoryExtractionPolicyResult,\n type MemoryProviderSettings,\n} from './providers/index.js';\nimport { pendingTurnFileName } from './pending.js';\nimport { ensureDir, ensureParentDir, resolveArchiveDir, resolveObservationsFile, resolvePendingDir, resolveStatusFile } from './paths.js';\n\nconst STATUS_RESPONSE_JSON_SCHEMA = {\n type: 'object',\n additionalProperties: false,\n required: [\n 'name',\n 'headline',\n 'summary',\n 'goal',\n 'phase',\n 'accomplished',\n 'decided',\n 'open',\n 'nextSteps',\n 'confidence',\n 'workingSet',\n 'tags',\n ],\n properties: {\n name: { type: 'string' },\n headline: { type: 'string' },\n summary: { type: 'string' },\n goal: { type: ['string', 'null'] },\n phase: { type: 'string', enum: ['exploring', 'planning', 'building', 'verifying', 'cleaning', 'shipping'] },\n accomplished: { type: 'array', items: { type: 'string' } },\n decided: { type: 'array', items: { type: 'string' } },\n open: { type: 'array', items: { type: 'string' } },\n nextSteps: { type: 'array', items: { type: 'string' } },\n confidence: { type: 'number' },\n workingSet: { type: 'array', items: { type: 'string' } },\n tags: { type: 'array', items: { type: 'string' } },\n },\n};\n\nexport interface SynthesizeStatusRollupInput {\n projectId: string;\n issueId: string;\n pendingTurns: PendingTurn[];\n identity?: MemoryIdentity;\n observations?: MemoryObservation[];\n archivedStatuses?: MemoryStatus[];\n settings?: MemoryProviderSettings | null;\n perDayCostCapUsd?: number;\n extract?: StatusRollupExtractCall;\n}\n\nexport type StatusRollupExtractCall = (\n prompt: string,\n jsonSchema: unknown,\n) => Promise<MemoryExtractionPolicyResult<unknown>>;\n\nexport type SynthesizeStatusRollupResult =\n | { status: 'synthesized'; memoryStatus: MemoryStatus }\n | { status: 'skipped'; reason: 'cost-cap' }\n | { status: 'dropped'; reason: 'extraction-failed' | 'malformed-response' | 'missing-identity' };\n\nexport interface CommitStatusRollupInput {\n identity: Pick<MemoryIdentity, 'projectId' | 'workspaceId' | 'issueId'>;\n status: MemoryStatus;\n pendingTurns: PendingTurn[];\n now?: Date;\n emitStatusUpdated?: EmitStatusUpdated;\n failAfterArchive?: boolean;\n failAfterStatusWrite?: boolean;\n}\n\nexport interface CommitStatusRollupResult {\n previousStatus?: MemoryStatus;\n archivedPath?: string;\n statusPath: string;\n clearedPending: string[];\n}\n\nexport type EmitStatusUpdated = (event: {\n identity: Pick<MemoryIdentity, 'projectId' | 'workspaceId' | 'issueId'>;\n status: MemoryStatus;\n previousStatus?: MemoryStatus;\n}) => void | Promise<void>;\n\nexport async function synthesizeStatusRollup(input: SynthesizeStatusRollupInput): Promise<SynthesizeStatusRollupResult> {\n const identity = input.identity ?? input.pendingTurns[0]?.identity;\n if (!identity) return { status: 'dropped', reason: 'missing-identity' };\n\n const observations = input.observations ?? await readRecentObservations(input.projectId, input.issueId, 20);\n const archivedStatuses = input.archivedStatuses ?? await readArchivedStatuses(input.projectId, input.issueId, 3);\n const prompt = buildStatusRollupPrompt({\n pendingTurns: input.pendingTurns,\n observations,\n archivedStatuses,\n });\n const extract = input.extract ?? ((candidatePrompt, jsonSchema) => extractWithProviderPolicy(candidatePrompt, jsonSchema, {\n identity,\n settings: input.settings,\n perDayCostCapUsd: input.perDayCostCapUsd,\n }));\n\n for (let attempt = 0; attempt < 2; attempt++) {\n const result = await extract(prompt, STATUS_RESPONSE_JSON_SCHEMA);\n if (result.status === 'skipped') return { status: 'skipped', reason: result.reason };\n if (result.status === 'dropped') return { status: 'dropped', reason: result.reason };\n\n const statusResult = Schema.decodeUnknownResult(MemoryStatus)(result.result.data);\n if (statusResult._tag === 'Success') {\n return { status: 'synthesized', memoryStatus: Result.getOrThrow(statusResult) };\n }\n }\n\n return { status: 'dropped', reason: 'malformed-response' };\n}\n\nexport async function commitStatusRollup(input: CommitStatusRollupInput): Promise<CommitStatusRollupResult> {\n const { projectId, workspaceId, issueId } = input.identity;\n const statusPath = resolveStatusFile(projectId, issueId);\n const previousStatus = await readCurrentStatus(projectId, issueId);\n let archivedPath: string | undefined;\n\n if (previousStatus) {\n archivedPath = await archiveStatus(projectId, issueId, previousStatus, input.now ?? new Date());\n await pruneArchivedStatuses(projectId, issueId, 3);\n }\n\n if (input.failAfterArchive) throw new Error('Injected rollup archive failure');\n\n await writeJsonAtomically(statusPath, input.status);\n\n if (input.failAfterStatusWrite) throw new Error('Injected rollup status write failure');\n\n await (input.emitStatusUpdated ?? emitStatusUpdatedEvent)({\n identity: { projectId, workspaceId, issueId },\n status: input.status,\n previousStatus,\n });\n\n const clearedPending = await clearPendingTurns(projectId, issueId, input.pendingTurns);\n return { previousStatus, archivedPath, statusPath, clearedPending };\n}\n\nexport async function readCurrentStatus(projectId: string, issueId: string): Promise<MemoryStatus | undefined> {\n try {\n return JSON.parse(await readFile(resolveStatusFile(projectId, issueId), 'utf8')) as MemoryStatus;\n } catch (error) {\n if (isEnoent(error)) return undefined;\n throw error;\n }\n}\n\nexport function buildStatusRollupPrompt(input: {\n pendingTurns: PendingTurn[];\n observations: MemoryObservation[];\n archivedStatuses: MemoryStatus[];\n}): string {\n return [\n 'Synthesize the current Overdeck workspace memory status from recent evidence.',\n 'Return the full status object. This status is a fresh replacement, not a cumulative append-only summary.',\n 'You may add, update, delete, or leave fields unchanged relative to previous statuses based only on current evidence.',\n 'Refresh workingSet every cycle from files currently relevant in the evidence; do not carry forward stale files.',\n 'phase must be exactly one of: exploring, planning, building, verifying, cleaning, shipping.',\n `Last 3 archived statuses:\\n${renderArchivedStatuses(input.archivedStatuses)}`,\n `Last 20 observations:\\n${renderObservations(input.observations)}`,\n `New pending turns:\\n${renderPendingTurns(input.pendingTurns)}`,\n ].join('\\n\\n');\n}\n\nexport async function readRecentObservations(projectId: string, issueId: string, limit = 20): Promise<MemoryObservation[]> {\n const observationsDir = dirname(resolveObservationsFile(projectId, issueId, new Date()));\n const files = (await readdir(observationsDir).catch((error: unknown) => {\n if (isEnoent(error)) return [] as string[];\n throw error;\n }))\n .filter((file) => file.endsWith('.jsonl'))\n .sort()\n .reverse();\n\n const observations: MemoryObservation[] = [];\n for (const file of files) {\n const raw = await readFile(`${observationsDir}/${file}`, 'utf8');\n const lines = raw.trimEnd().split('\\n');\n for (let index = lines.length - 1; index >= 0 && observations.length < limit; index -= 1) {\n const line = lines[index];\n if (!line || line.trim().length === 0) continue;\n observations.push(JSON.parse(line) as MemoryObservation);\n }\n if (observations.length >= limit) break;\n }\n\n return observations\n .sort((a, b) => a.timestamp.localeCompare(b.timestamp));\n}\n\nexport async function readArchivedStatuses(projectId: string, issueId: string, limit = 3): Promise<MemoryStatus[]> {\n const archiveDir = resolveArchiveDir(projectId, issueId);\n const files = (await readdir(archiveDir).catch((error: unknown) => {\n if (isEnoent(error)) return [] as string[];\n throw error;\n }))\n .filter((file) => file.endsWith('.json'))\n .sort()\n .slice(-limit);\n\n const statuses: MemoryStatus[] = [];\n for (const file of files) {\n statuses.push(JSON.parse(await readFile(`${archiveDir}/${file}`, 'utf8')) as MemoryStatus);\n }\n return statuses;\n}\n\nasync function archiveStatus(projectId: string, issueId: string, status: MemoryStatus, now: Date): Promise<string> {\n const archiveDir = resolveArchiveDir(projectId, issueId);\n await ensureDir(archiveDir);\n const path = `${archiveDir}/${now.toISOString().replace(/[:.]/g, '-')}_${slugify(status.name)}_${randomUUID()}.json`;\n await writeJsonAtomically(path, status);\n return path;\n}\n\nasync function pruneArchivedStatuses(projectId: string, issueId: string, keep: number): Promise<void> {\n const archiveDir = resolveArchiveDir(projectId, issueId);\n const files = (await readdir(archiveDir).catch((error: unknown) => {\n if (isEnoent(error)) return [] as string[];\n throw error;\n }))\n .filter((file) => file.endsWith('.json'))\n .sort();\n\n await Promise.all(files.slice(0, Math.max(0, files.length - keep)).map((file) => unlink(`${archiveDir}/${file}`)));\n}\n\nasync function clearPendingTurns(projectId: string, issueId: string, pendingTurns: PendingTurn[]): Promise<string[]> {\n const pendingDir = resolvePendingDir(projectId, issueId);\n const cleared: string[] = [];\n for (const turn of pendingTurns) {\n const path = `${pendingDir}/${pendingTurnFileName(turn)}`;\n try {\n await unlink(path);\n cleared.push(path);\n } catch (error) {\n if (!isEnoent(error)) throw error;\n }\n }\n return cleared;\n}\n\nasync function emitStatusUpdatedEvent(event: {\n identity: Pick<MemoryIdentity, 'projectId' | 'workspaceId' | 'issueId'>;\n status: MemoryStatus;\n previousStatus?: MemoryStatus;\n}): Promise<void> {\n const { initEventStore } = await import('../../dashboard/server/event-store.js');\n const store = await initEventStore();\n await store.appendAsync({\n type: 'memory.status_updated',\n timestamp: new Date().toISOString(),\n payload: event,\n });\n}\n\nasync function writeJsonAtomically(path: string, value: unknown): Promise<void> {\n await ensureParentDir(path);\n const tempPath = `${dirname(path)}/.${randomUUID()}.tmp`;\n await writeFile(tempPath, `${JSON.stringify(value, null, 2)}\\n`, 'utf8');\n await rename(tempPath, path);\n}\n\nfunction slugify(value: string): string {\n const slug = value.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');\n return slug.length > 0 ? slug : 'status';\n}\n\nfunction renderArchivedStatuses(statuses: MemoryStatus[]): string {\n if (statuses.length === 0) return 'none';\n return statuses.map((status, index) => [\n `Archived status ${index + 1}:`,\n `- Name: ${status.name}`,\n `- Headline: ${status.headline}`,\n `- Summary: ${status.summary}`,\n `- Goal: ${status.goal ?? 'none'}`,\n `- Phase: ${status.phase}`,\n `- Accomplished: ${status.accomplished.join('; ') || 'none'}`,\n `- Decided: ${status.decided.join('; ') || 'none'}`,\n `- Open: ${status.open.join('; ') || 'none'}`,\n `- Next steps: ${status.nextSteps.join('; ') || 'none'}`,\n `- Working set: ${status.workingSet.join(', ') || 'none'}`,\n `- Tags: ${status.tags.join(', ') || 'none'}`,\n ].join('\\n')).join('\\n\\n');\n}\n\nfunction renderObservations(observations: MemoryObservation[]): string {\n if (observations.length === 0) return 'none';\n return observations.map((observation, index) => [\n `Observation ${index + 1}:`,\n `- Timestamp: ${observation.timestamp}`,\n `- Action status: ${observation.actionStatus ?? 'none'}`,\n `- Summary: ${observation.summary}`,\n `- Narrative: ${observation.narrative}`,\n `- Files: ${observation.files.join(', ') || 'none'}`,\n `- Tags: ${observation.tags.join(', ') || 'none'}`,\n ].join('\\n')).join('\\n\\n');\n}\n\nconst PENDING_TURN_PROMPT_BUDGET_CHARS = 12_000;\nconst PENDING_TURN_TEXT_BUDGET_CHARS = 2_000;\n\nfunction renderPendingTurns(pendingTurns: PendingTurn[]): string {\n if (pendingTurns.length === 0) return 'none';\n\n const rendered: string[] = [];\n let used = 0;\n let omitted = 0;\n for (const [index, turn] of pendingTurns.entries()) {\n const compressedText = truncateText(turn.compressedText, PENDING_TURN_TEXT_BUDGET_CHARS);\n const block = [\n `Pending turn ${index + 1}:`,\n `- Created: ${turn.createdAt}`,\n `- Session: ${turn.identity.sessionId}`,\n `- Trigger: ${turn.trigger}`,\n `- Offset: ${turn.fromOffset}-${turn.toOffset}`,\n `- Events consumed: ${turn.eventsConsumed}`,\n `- Compressed text:\\n${compressedText}`,\n ].join('\\n');\n if (used + block.length > PENDING_TURN_PROMPT_BUDGET_CHARS) {\n omitted = pendingTurns.length - index;\n break;\n }\n rendered.push(block);\n used += block.length;\n }\n\n if (omitted > 0) rendered.push(`Omitted ${omitted} pending turn(s) after the ${PENDING_TURN_PROMPT_BUDGET_CHARS} character budget.`);\n return rendered.join('\\n\\n');\n}\n\nfunction truncateText(value: string, maxChars: number): string {\n if (value.length <= maxChars) return value;\n return `${value.slice(0, maxChars)}\\n[truncated ${value.length - maxChars} chars]`;\n}\n\nfunction isEnoent(error: unknown): boolean {\n return typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT';\n}\n","import { randomUUID } from 'crypto';\nimport { readdir, readFile, rename, writeFile } from 'fs/promises';\nimport { dirname, join } from 'path';\nimport type { MemoryObservation, MemoryStatus, ResetMarker } from '@overdeck/contracts';\nimport { getOverdeckHome } from '../paths.js';\nimport { runMemoryFtsTransaction, withMemoryFtsDatabase } from './fts-db.js';\nimport { resolveExtractionProviderSelection } from './providers/index.js';\nimport { getMemoryRollupPendingThreshold, loadMemorySettings } from './settings.js';\nimport {\n ensureParentDir,\n resolveMemoryRoot,\n resolveObservationsFile,\n resolvePendingDir,\n resolveSummariesDir,\n} from './paths.js';\nimport { getMemoryHealthPath, type MemoryHealthSnapshot } from './health.js';\nimport { readCurrentStatus } from './rollup.js';\nimport { getAgentStateSync } from '../agents.js';\n\nconst DEFAULT_PROJECT_ID = 'overdeck';\nconst MIN_DAILY_SUMMARY_OBSERVATIONS = 3;\nconst DAILY_SUMMARY_REGENERATION_OBSERVATIONS = 20;\n\nexport interface MemorySearchOptions {\n project?: string;\n workspace?: string;\n issue?: string;\n tag?: string;\n sibling?: boolean;\n limit?: number;\n includeArchived?: boolean;\n}\n\nexport interface MemorySearchResult {\n observation: MemoryObservation;\n score: number;\n}\n\nexport type DailySummaryStatus = 'generated' | 'insufficient-data' | 'up-to-date';\n\nexport interface DailySummaryResult {\n status: DailySummaryStatus;\n path: string;\n markdown: string;\n observationCount: number;\n previousObservationCount: number | null;\n}\n\nexport interface MemoryDoctorOptions {\n project?: string;\n now?: Date;\n}\n\nexport interface MemoryDoctorResult {\n exitCode: number;\n provider: Awaited<ReturnType<typeof resolveExtractionProviderSelection>>;\n rollupPendingThreshold: number;\n issues: Array<{\n projectId: string;\n issueId: string;\n health: MemoryHealthSnapshot;\n pendingCount: number;\n lastObservation: string | null;\n }>;\n staleActiveAgents: Array<{ agentId: string; issueId: string; lastSuccess: string | null }>;\n}\n\nexport async function searchMemory(query: string, options: MemorySearchOptions = {}): Promise<MemorySearchResult[]> {\n const projectId = options.project ?? DEFAULT_PROJECT_ID;\n const observations = await readObservationScope(projectId, options.issue, options.sibling ?? false);\n const resetMarkers = options.includeArchived ? [] : await readResetMarkers(projectId);\n const terms = query.toLowerCase().split(/\\s+/).filter(Boolean);\n const results = observations\n .filter((observation) => !options.workspace || observation.workspaceId === options.workspace)\n .filter((observation) => !options.issue || options.sibling || observation.issueId === options.issue)\n .filter((observation) => options.includeArchived || isAfterLatestResetMarker(observation, resetMarkers))\n .filter((observation) => !options.tag || observation.tags.includes(options.tag))\n .map((observation) => ({ observation, score: scoreObservation(observation, terms) }))\n .filter((result) => terms.length === 0 || result.score > 0)\n .sort((a, b) => b.score - a.score || b.observation.timestamp.localeCompare(a.observation.timestamp));\n\n return results.slice(0, options.limit ?? 20);\n}\n\nexport async function getMemoryStatus(projectId: string, issueId: string): Promise<MemoryStatus | undefined> {\n return readCurrentStatus(projectId, issueId);\n}\n\nexport async function createResetMarker(input: {\n projectId?: string;\n scope: ResetMarker['scope'];\n scopeId: string;\n reason: string;\n fromTimestamp?: string;\n id?: string;\n createdAt?: string;\n emitResetMarkerCreated?: (marker: ResetMarker, timestamp: string) => void | Promise<void>;\n}): Promise<ResetMarker> {\n const projectId = input.projectId ?? DEFAULT_PROJECT_ID;\n const marker: ResetMarker = {\n id: input.id ?? randomUUID(),\n scope: input.scope,\n scopeId: input.scopeId,\n fromTimestamp: input.fromTimestamp ?? new Date().toISOString(),\n reason: input.reason,\n createdAt: input.createdAt ?? new Date().toISOString(),\n };\n const path = join(resolveMemoryRoot(projectId), 'reset-markers.json');\n const markers = await readJsonFile<ResetMarker[]>(path, []);\n await writeJsonAtomically(path, [...markers, marker]);\n await writeResetMarkerToFtsDb(projectId, marker);\n await (input.emitResetMarkerCreated ?? emitResetMarkerCreated)(marker, marker.createdAt);\n return marker;\n}\n\nexport async function generateDailySummary(input: {\n projectId?: string;\n issueId: string;\n date?: string;\n}): Promise<DailySummaryResult> {\n const projectId = input.projectId ?? DEFAULT_PROJECT_ID;\n const date = input.date ?? new Date().toISOString().slice(0, 10);\n const path = join(resolveSummariesDir(projectId, input.issueId), `${date}.md`);\n const observations = await readObservationsFile(resolveObservationsFile(projectId, input.issueId, date));\n const existingMarkdown = await readTextFile(path);\n const previousObservationCount = existingMarkdown ? parseSummaryObservationCount(existingMarkdown) : null;\n\n if (observations.length < MIN_DAILY_SUMMARY_OBSERVATIONS) {\n return { status: 'insufficient-data', path, markdown: existingMarkdown ?? '', observationCount: observations.length, previousObservationCount };\n }\n\n if (existingMarkdown && previousObservationCount !== null && observations.length - previousObservationCount < DAILY_SUMMARY_REGENERATION_OBSERVATIONS) {\n return { status: 'up-to-date', path, markdown: existingMarkdown, observationCount: observations.length, previousObservationCount };\n }\n\n const markdown = buildDailySummaryMarkdown(input.issueId, date, observations);\n await ensureParentDir(path);\n await writeFile(path, markdown, 'utf8');\n await indexDailySummary(projectId, input.issueId, date, observations, markdown);\n return { status: 'generated', path, markdown, observationCount: observations.length, previousObservationCount };\n}\n\nexport async function runMemoryDoctor(options: MemoryDoctorOptions = {}): Promise<MemoryDoctorResult> {\n const projectId = options.project ?? DEFAULT_PROJECT_ID;\n const now = options.now ?? new Date();\n const provider = await resolveExtractionProviderSelection();\n const rollupPendingThreshold = await getMemoryRollupPendingThreshold();\n const issues = await readIssueDoctorSnapshots(projectId);\n const activeAgents = await readActiveAgents();\n const staleActiveAgents = activeAgents\n .map((agent) => {\n const issue = issues.find((candidate) => candidate.issueId === agent.issueId);\n return { agentId: agent.id, issueId: agent.issueId, lastSuccess: issue?.health.last_success ?? null };\n })\n .filter((agent) => isStaleExtraction(agent.lastSuccess, now));\n\n return {\n exitCode: staleActiveAgents.length > 0 ? 1 : 0,\n provider,\n rollupPendingThreshold,\n issues,\n staleActiveAgents,\n };\n}\n\nexport async function readMemorySettingsSummary(): Promise<{\n rollupPendingThreshold: number;\n provider: Awaited<ReturnType<typeof resolveExtractionProviderSelection>>;\n}> {\n return {\n rollupPendingThreshold: (await loadMemorySettings()).rollupPendingThreshold,\n provider: await resolveExtractionProviderSelection(),\n };\n}\n\nasync function readObservationScope(projectId: string, issueId: string | undefined, sibling: boolean): Promise<MemoryObservation[]> {\n if (issueId && !sibling) return readIssueObservations(projectId, issueId);\n const issueIds = await listIssueIds(projectId);\n const selectedIssueIds = issueId && sibling ? issueIds.filter((candidate) => candidate !== issueId) : issueIds;\n const nested = await Promise.all(selectedIssueIds.map((candidate) => readIssueObservations(projectId, candidate)));\n return nested.flat();\n}\n\nasync function readIssueObservations(projectId: string, issueId: string): Promise<MemoryObservation[]> {\n const observationsDir = dirname(resolveObservationsFile(projectId, issueId, new Date()));\n const files = (await readdir(observationsDir).catch((error: unknown) => {\n if (isEnoent(error)) return [] as string[];\n throw error;\n })).filter((file) => file.endsWith('.jsonl')).sort();\n const nested = await Promise.all(files.map((file) => readObservationsFile(join(observationsDir, file))));\n return nested.flat();\n}\n\nasync function readResetMarkers(projectId: string): Promise<ResetMarker[]> {\n const jsonMarkers = await readJsonFile<ResetMarker[]>(join(resolveMemoryRoot(projectId), 'reset-markers.json'), []);\n const dbMarkers = await withMemoryFtsDatabase(projectId, (db) => db.prepare(`\n SELECT id, scope, scope_id, from_timestamp, reason, created_at\n FROM reset_markers\n `).all<{\n id: number;\n scope: ResetMarker['scope'];\n scope_id: string;\n from_timestamp: string;\n reason: string | null;\n created_at: string;\n }>()).catch((error: unknown) => {\n if (isNoSuchTable(error)) return [];\n throw error;\n });\n return dedupeResetMarkers([\n ...jsonMarkers,\n ...dbMarkers.map((marker) => ({\n id: `db-${marker.id}`,\n scope: marker.scope,\n scopeId: marker.scope_id,\n fromTimestamp: marker.from_timestamp,\n reason: marker.reason ?? '',\n createdAt: marker.created_at,\n })),\n ]);\n}\n\nfunction dedupeResetMarkers(markers: ResetMarker[]): ResetMarker[] {\n const seen = new Set<string>();\n const deduped: ResetMarker[] = [];\n for (const marker of markers) {\n const key = [marker.scope, marker.scopeId, marker.fromTimestamp, marker.reason, marker.createdAt].join('|');\n if (seen.has(key)) continue;\n seen.add(key);\n deduped.push(marker);\n }\n return deduped;\n}\n\nfunction isAfterLatestResetMarker(observation: MemoryObservation, markers: ResetMarker[]): boolean {\n const latest = markers\n .filter((marker) => appliesToObservation(marker, observation))\n .map((marker) => marker.fromTimestamp)\n .sort()\n .at(-1);\n return !latest || observation.timestamp > latest;\n}\n\nfunction appliesToObservation(marker: ResetMarker, observation: MemoryObservation): boolean {\n switch (marker.scope) {\n case 'project':\n return marker.scopeId === observation.projectId;\n case 'workspace':\n return marker.scopeId === observation.workspaceId;\n case 'issue':\n return marker.scopeId === observation.issueId;\n case 'session':\n return marker.scopeId === observation.sessionId;\n }\n}\n\nasync function readObservationsFile(path: string): Promise<MemoryObservation[]> {\n let raw: string;\n try {\n raw = await readFile(path, 'utf8');\n } catch (error) {\n if (isEnoent(error)) return [];\n throw error;\n }\n return raw.split('\\n')\n .filter((line) => line.trim().length > 0)\n .map((line) => JSON.parse(line) as MemoryObservation);\n}\n\nasync function readTextFile(path: string): Promise<string | null> {\n try {\n return await readFile(path, 'utf8');\n } catch (error) {\n if (isEnoent(error)) return null;\n throw error;\n }\n}\n\nfunction buildDailySummaryMarkdown(issueId: string, date: string, observations: MemoryObservation[]): string {\n const actions = observations.filter((observation) => observation.actionStatus !== null);\n return [\n `# ${issueId} memory summary — ${date}`,\n '',\n `Observations: ${observations.length}`,\n `Action updates: ${actions.length}`,\n '',\n ...observations.map((observation) => [\n `## ${observation.timestamp}`,\n '',\n observation.actionStatus ? `**Action:** ${observation.actionStatus}` : '**Action:** none',\n '',\n observation.summary,\n '',\n observation.files.length > 0 ? `Files: ${observation.files.join(', ')}` : 'Files: none',\n observation.tags.length > 0 ? `Tags: ${observation.tags.join(', ')}` : 'Tags: none',\n '',\n ].join('\\n')),\n ].join('\\n');\n}\n\nfunction parseSummaryObservationCount(markdown: string): number | null {\n const match = markdown.match(/^Observations: (\\d+)$/m);\n return match ? Number(match[1]) : null;\n}\n\nasync function indexDailySummary(projectId: string, issueId: string, date: string, observations: MemoryObservation[], markdown: string): Promise<void> {\n const latest = observations.at(-1);\n const files = [...new Set(observations.flatMap((observation) => observation.files))].join(',');\n const tags = [...new Set(['memory', 'summary', ...observations.flatMap((observation) => observation.tags)])].join(',');\n const entryTime = latest?.timestamp.slice(11) ?? '00:00:00.000Z';\n await runMemoryFtsTransaction(projectId, [\n {\n method: 'run',\n sql: `\n DELETE FROM memory_fts\n WHERE project_id = ?\n AND issue_id = ?\n AND doc_type = 'summary'\n AND entry_date = ?\n `,\n params: [projectId, issueId, date],\n },\n {\n method: 'run',\n sql: `\n INSERT INTO memory_fts (\n content,\n display_content,\n source,\n branch,\n entry_date,\n entry_time,\n entry_type,\n files,\n tags,\n doc_type,\n scope,\n project_id,\n workspace_id,\n issue_id,\n run_id,\n session_id,\n agent_role,\n agent_harness\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `,\n params: [\n markdown,\n markdown,\n 'summary',\n latest?.gitBranch ?? '',\n date,\n entryTime,\n 'memory-summary',\n files,\n tags,\n 'summary',\n 'issue',\n projectId,\n latest?.workspaceId ?? '',\n issueId,\n latest?.runId ?? '',\n latest?.sessionId ?? '',\n latest?.agentRole ?? '',\n latest?.agentHarness ?? '',\n ],\n },\n ]);\n}\n\nasync function listIssueIds(projectId: string): Promise<string[]> {\n const root = resolveMemoryRoot(projectId);\n const entries = await readdir(root, { withFileTypes: true }).catch((error: unknown) => {\n if (isEnoent(error)) return [];\n throw error;\n });\n return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();\n}\n\nfunction scoreObservation(observation: MemoryObservation, terms: string[]): number {\n const haystack = [\n observation.actionStatus ?? '',\n observation.summary,\n observation.narrative,\n observation.files.join(' '),\n observation.tags.join(' '),\n ].join(' ').toLowerCase();\n return terms.reduce((score, term) => score + occurrences(haystack, term), 0);\n}\n\nfunction occurrences(value: string, term: string): number {\n let count = 0;\n let index = value.indexOf(term);\n while (index !== -1) {\n count += 1;\n index = value.indexOf(term, index + term.length);\n }\n return count;\n}\n\nasync function readIssueDoctorSnapshots(projectId: string): Promise<MemoryDoctorResult['issues']> {\n const issueIds = await listIssueIds(projectId);\n return Promise.all(issueIds.map(async (issueId) => {\n const health = await readJsonFile<MemoryHealthSnapshot>(getMemoryHealthPath({ projectId, issueId }), emptyHealth());\n const pendingCount = await countJsonFiles(resolvePendingDir(projectId, issueId));\n const observations = await readIssueObservations(projectId, issueId);\n return {\n projectId,\n issueId,\n health,\n pendingCount,\n lastObservation: observations.at(-1)?.timestamp ?? null,\n };\n }));\n}\n\nasync function readActiveAgents(): Promise<Array<{ id: string; issueId: string }>> {\n const agentsDir = join(getOverdeckHome(), 'agents');\n const entries = await readdir(agentsDir, { withFileTypes: true }).catch((error: unknown) => {\n if (isEnoent(error)) return [];\n throw error;\n });\n const agents: Array<{ id: string; issueId: string }> = [];\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const state = getAgentStateSync(entry.name);\n if ((state?.status === 'running' || state?.status === 'starting') && state?.issueId) {\n agents.push({ id: entry.name, issueId: state.issueId });\n }\n }\n return agents;\n}\n\nasync function countJsonFiles(path: string): Promise<number> {\n const entries = await readdir(path).catch((error: unknown) => {\n if (isEnoent(error)) return [] as string[];\n throw error;\n });\n return entries.filter((entry) => entry.endsWith('.json') && !entry.startsWith('.')).length;\n}\n\nasync function readJsonFile<T>(path: string, fallback: T): Promise<T> {\n try {\n return JSON.parse(await readFile(path, 'utf8')) as T;\n } catch (error) {\n if (isEnoent(error)) return fallback;\n throw error;\n }\n}\n\nasync function writeJsonAtomically(path: string, value: unknown): Promise<void> {\n await ensureParentDir(path);\n const tempPath = `${dirname(path)}/.${randomUUID()}.tmp`;\n await writeFile(tempPath, `${JSON.stringify(value, null, 2)}\\n`, 'utf8');\n await rename(tempPath, path);\n}\n\nasync function writeResetMarkerToFtsDb(projectId: string, marker: ResetMarker): Promise<void> {\n await withMemoryFtsDatabase(projectId, async (db) => {\n await db.prepare(`\n INSERT INTO reset_markers (scope, scope_id, from_timestamp, reason, created_at)\n VALUES (?, ?, ?, ?, ?)\n `).run(marker.scope, marker.scopeId, marker.fromTimestamp, marker.reason, marker.createdAt);\n });\n}\n\nasync function emitResetMarkerCreated(marker: ResetMarker, timestamp: string): Promise<void> {\n const { initEventStore } = await import('../../dashboard/server/event-store.js');\n const store = await initEventStore();\n await store.appendAsync({\n type: 'memory.reset_marker_created',\n timestamp,\n payload: { marker },\n });\n}\n\nfunction isStaleExtraction(lastSuccess: string | null, now: Date): boolean {\n if (!lastSuccess) return true;\n const timestamp = Date.parse(lastSuccess);\n return Number.isNaN(timestamp) || now.getTime() - timestamp > 60 * 60 * 1000;\n}\n\nfunction emptyHealth(): MemoryHealthSnapshot {\n return {\n status: 'healthy',\n last_success: null,\n last_failure: null,\n extractions_attempted: 0,\n extractions_succeeded: 0,\n failed_by_reason: {},\n };\n}\n\nfunction isEnoent(error: unknown): boolean {\n return typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT';\n}\n\nfunction isNoSuchTable(error: unknown): boolean {\n return error instanceof Error && error.message.includes('no such table: reset_markers');\n}\n"],"mappings":";;;;;;;;;;;;;;eAG0D;gBACM;cACf;;;;ACqJjD,eAAsB,kBAAkB,WAAmB,SAAoD;AAC7G,KAAI;AACF,SAAO,KAAK,MAAM,MAAM,SAAS,kBAAkB,WAAW,QAAQ,EAAE,OAAO,CAAC;UACzE,OAAO;AACd,MAAIA,WAAS,MAAM,CAAE,QAAO,KAAA;AAC5B,QAAM;;;AAiMV,SAASA,WAAS,OAAyB;AACzC,QAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,UAAU,SAAS,MAAM,SAAS;;;WAxV7D;iBAKC;eACqB;eACuF;;;;ACmD1I,eAAsB,aAAa,OAAe,UAA+B,EAAE,EAAiC;CAClH,MAAM,YAAY,QAAQ,WAAW;CACrC,MAAM,eAAe,MAAM,qBAAqB,WAAW,QAAQ,OAAO,QAAQ,WAAW,MAAM;CACnG,MAAM,eAAe,QAAQ,kBAAkB,EAAE,GAAG,MAAM,iBAAiB,UAAU;CACrF,MAAM,QAAQ,MAAM,aAAa,CAAC,MAAM,MAAM,CAAC,OAAO,QAAQ;AAU9D,QATgB,aACb,QAAQ,gBAAgB,CAAC,QAAQ,aAAa,YAAY,gBAAgB,QAAQ,UAAU,CAC5F,QAAQ,gBAAgB,CAAC,QAAQ,SAAS,QAAQ,WAAW,YAAY,YAAY,QAAQ,MAAM,CACnG,QAAQ,gBAAgB,QAAQ,mBAAmB,yBAAyB,aAAa,aAAa,CAAC,CACvG,QAAQ,gBAAgB,CAAC,QAAQ,OAAO,YAAY,KAAK,SAAS,QAAQ,IAAI,CAAC,CAC/E,KAAK,iBAAiB;EAAE;EAAa,OAAO,iBAAiB,aAAa,MAAM;EAAE,EAAE,CACpF,QAAQ,WAAW,MAAM,WAAW,KAAK,OAAO,QAAQ,EAAE,CAC1D,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,UAAU,cAAc,EAAE,YAAY,UAAU,CAEvF,CAAC,MAAM,GAAG,QAAQ,SAAS,GAAG;;AAG9C,eAAsB,gBAAgB,WAAmB,SAAoD;AAC3G,QAAO,kBAAkB,WAAW,QAAQ;;AAG9C,eAAsB,kBAAkB,OASf;CACvB,MAAM,YAAY,MAAM,aAAa;CACrC,MAAM,SAAsB;EAC1B,IAAI,MAAM,MAAM,YAAY;EAC5B,OAAO,MAAM;EACb,SAAS,MAAM;EACf,eAAe,MAAM,kCAAiB,IAAI,MAAM,EAAC,aAAa;EAC9D,QAAQ,MAAM;EACd,WAAW,MAAM,8BAAa,IAAI,MAAM,EAAC,aAAa;EACvD;CACD,MAAM,OAAO,KAAK,kBAAkB,UAAU,EAAE,qBAAqB;AAErE,OAAM,oBAAoB,MAAM,CAAC,GAAG,MADd,aAA4B,MAAM,EAAE,CAAC,EACd,OAAO,CAAC;AACrD,OAAM,wBAAwB,WAAW,OAAO;AAChD,QAAO,MAAM,0BAA0B,wBAAwB,QAAQ,OAAO,UAAU;AACxF,QAAO;;AAGT,eAAsB,qBAAqB,OAIX;CAC9B,MAAM,YAAY,MAAM,aAAa;CACrC,MAAM,OAAO,MAAM,yBAAQ,IAAI,MAAM,EAAC,aAAa,CAAC,MAAM,GAAG,GAAG;CAChE,MAAM,OAAO,KAAK,oBAAoB,WAAW,MAAM,QAAQ,EAAE,GAAG,KAAK,KAAK;CAC9E,MAAM,eAAe,MAAM,qBAAqB,wBAAwB,WAAW,MAAM,SAAS,KAAK,CAAC;CACxG,MAAM,mBAAmB,MAAM,aAAa,KAAK;CACjD,MAAM,2BAA2B,mBAAmB,6BAA6B,iBAAiB,GAAG;AAErG,KAAI,aAAa,SAAS,+BACxB,QAAO;EAAE,QAAQ;EAAqB;EAAM,UAAU,oBAAoB;EAAI,kBAAkB,aAAa;EAAQ;EAA0B;AAGjJ,KAAI,oBAAoB,6BAA6B,QAAQ,aAAa,SAAS,2BAA2B,wCAC5G,QAAO;EAAE,QAAQ;EAAc;EAAM,UAAU;EAAkB,kBAAkB,aAAa;EAAQ;EAA0B;CAGpI,MAAM,WAAW,0BAA0B,MAAM,SAAS,MAAM,aAAa;AAC7E,OAAM,gBAAgB,KAAK;AAC3B,OAAM,UAAU,MAAM,UAAU,OAAO;AACvC,OAAM,kBAAkB,WAAW,MAAM,SAAS,MAAM,cAAc,SAAS;AAC/E,QAAO;EAAE,QAAQ;EAAa;EAAM;EAAU,kBAAkB,aAAa;EAAQ;EAA0B;;AAGjH,eAAsB,gBAAgB,UAA+B,EAAE,EAA+B;CACpG,MAAM,YAAY,QAAQ,WAAW;CACrC,MAAM,MAAM,QAAQ,uBAAO,IAAI,MAAM;CACrC,MAAM,WAAW,MAAM,oCAAoC;CAC3D,MAAM,yBAAyB,MAAM,iCAAiC;CACtE,MAAM,SAAS,MAAM,yBAAyB,UAAU;CAExD,MAAM,qBAAoB,MADC,kBAAkB,EAE1C,KAAK,UAAU;EACd,MAAM,QAAQ,OAAO,MAAM,cAAc,UAAU,YAAY,MAAM,QAAQ;AAC7E,SAAO;GAAE,SAAS,MAAM;GAAI,SAAS,MAAM;GAAS,aAAa,OAAO,OAAO,gBAAgB;GAAM;GACrG,CACD,QAAQ,UAAU,kBAAkB,MAAM,aAAa,IAAI,CAAC;AAE/D,QAAO;EACL,UAAU,kBAAkB,SAAS,IAAI,IAAI;EAC7C;EACA;EACA;EACA;EACD;;AAGH,eAAsB,4BAGnB;AACD,QAAO;EACL,yBAAyB,MAAM,oBAAoB,EAAE;EACrD,UAAU,MAAM,oCAAoC;EACrD;;AAGH,eAAe,qBAAqB,WAAmB,SAA6B,SAAgD;AAClI,KAAI,WAAW,CAAC,QAAS,QAAO,sBAAsB,WAAW,QAAQ;CACzE,MAAM,WAAW,MAAM,aAAa,UAAU;CAC9C,MAAM,mBAAmB,WAAW,UAAU,SAAS,QAAQ,cAAc,cAAc,QAAQ,GAAG;AAEtG,SAAO,MADc,QAAQ,IAAI,iBAAiB,KAAK,cAAc,sBAAsB,WAAW,UAAU,CAAC,CAAC,EACpG,MAAM;;AAGtB,eAAe,sBAAsB,WAAmB,SAA+C;CACrG,MAAM,kBAAkB,QAAQ,wBAAwB,WAAW,yBAAS,IAAI,MAAM,CAAC,CAAC;CACxF,MAAM,SAAS,MAAM,QAAQ,gBAAgB,CAAC,OAAO,UAAmB;AACtE,MAAI,SAAS,MAAM,CAAE,QAAO,EAAE;AAC9B,QAAM;GACN,EAAE,QAAQ,SAAS,KAAK,SAAS,SAAS,CAAC,CAAC,MAAM;AAEpD,SAAO,MADc,QAAQ,IAAI,MAAM,KAAK,SAAS,qBAAqB,KAAK,iBAAiB,KAAK,CAAC,CAAC,CAAC,EAC1F,MAAM;;AAGtB,eAAe,iBAAiB,WAA2C;CACzE,MAAM,cAAc,MAAM,aAA4B,KAAK,kBAAkB,UAAU,EAAE,qBAAqB,EAAE,EAAE,CAAC;CACnH,MAAM,YAAY,MAAM,sBAAsB,YAAY,OAAO,GAAG,QAAQ;;;IAG1E,CAAC,KAOC,CAAC,CAAC,OAAO,UAAmB;AAC9B,MAAI,cAAc,MAAM,CAAE,QAAO,EAAE;AACnC,QAAM;GACN;AACF,QAAO,mBAAmB,CACxB,GAAG,aACH,GAAG,UAAU,KAAK,YAAY;EAC5B,IAAI,MAAM,OAAO;EACjB,OAAO,OAAO;EACd,SAAS,OAAO;EAChB,eAAe,OAAO;EACtB,QAAQ,OAAO,UAAU;EACzB,WAAW,OAAO;EACnB,EAAE,CACJ,CAAC;;AAGJ,SAAS,mBAAmB,SAAuC;CACjE,MAAM,uBAAO,IAAI,KAAa;CAC9B,MAAM,UAAyB,EAAE;AACjC,MAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,MAAM;GAAC,OAAO;GAAO,OAAO;GAAS,OAAO;GAAe,OAAO;GAAQ,OAAO;GAAU,CAAC,KAAK,IAAI;AAC3G,MAAI,KAAK,IAAI,IAAI,CAAE;AACnB,OAAK,IAAI,IAAI;AACb,UAAQ,KAAK,OAAO;;AAEtB,QAAO;;AAGT,SAAS,yBAAyB,aAAgC,SAAiC;CACjG,MAAM,SAAS,QACZ,QAAQ,WAAW,qBAAqB,QAAQ,YAAY,CAAC,CAC7D,KAAK,WAAW,OAAO,cAAc,CACrC,MAAM,CACN,GAAG,GAAG;AACT,QAAO,CAAC,UAAU,YAAY,YAAY;;AAG5C,SAAS,qBAAqB,QAAqB,aAAyC;AAC1F,SAAQ,OAAO,OAAf;EACE,KAAK,UACH,QAAO,OAAO,YAAY,YAAY;EACxC,KAAK,YACH,QAAO,OAAO,YAAY,YAAY;EACxC,KAAK,QACH,QAAO,OAAO,YAAY,YAAY;EACxC,KAAK,UACH,QAAO,OAAO,YAAY,YAAY;;;AAI5C,eAAe,qBAAqB,MAA4C;CAC9E,IAAI;AACJ,KAAI;AACF,QAAM,MAAM,SAAS,MAAM,OAAO;UAC3B,OAAO;AACd,MAAI,SAAS,MAAM,CAAE,QAAO,EAAE;AAC9B,QAAM;;AAER,QAAO,IAAI,MAAM,KAAK,CACnB,QAAQ,SAAS,KAAK,MAAM,CAAC,SAAS,EAAE,CACxC,KAAK,SAAS,KAAK,MAAM,KAAK,CAAsB;;AAGzD,eAAe,aAAa,MAAsC;AAChE,KAAI;AACF,SAAO,MAAM,SAAS,MAAM,OAAO;UAC5B,OAAO;AACd,MAAI,SAAS,MAAM,CAAE,QAAO;AAC5B,QAAM;;;AAIV,SAAS,0BAA0B,SAAiB,MAAc,cAA2C;CAC3G,MAAM,UAAU,aAAa,QAAQ,gBAAgB,YAAY,iBAAiB,KAAK;AACvF,QAAO;EACL,KAAK,QAAQ,oBAAoB;EACjC;EACA,iBAAiB,aAAa;EAC9B,mBAAmB,QAAQ;EAC3B;EACA,GAAG,aAAa,KAAK,gBAAgB;GACnC,MAAM,YAAY;GAClB;GACA,YAAY,eAAe,eAAe,YAAY,iBAAiB;GACvE;GACA,YAAY;GACZ;GACA,YAAY,MAAM,SAAS,IAAI,UAAU,YAAY,MAAM,KAAK,KAAK,KAAK;GAC1E,YAAY,KAAK,SAAS,IAAI,SAAS,YAAY,KAAK,KAAK,KAAK,KAAK;GACvE;GACD,CAAC,KAAK,KAAK,CAAC;EACd,CAAC,KAAK,KAAK;;AAGd,SAAS,6BAA6B,UAAiC;CACrE,MAAM,QAAQ,SAAS,MAAM,yBAAyB;AACtD,QAAO,QAAQ,OAAO,MAAM,GAAG,GAAG;;AAGpC,eAAe,kBAAkB,WAAmB,SAAiB,MAAc,cAAmC,UAAiC;CACrJ,MAAM,SAAS,aAAa,GAAG,GAAG;CAClC,MAAM,QAAQ,CAAC,GAAG,IAAI,IAAI,aAAa,SAAS,gBAAgB,YAAY,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI;CAC9F,MAAM,OAAO,CAAC,GAAG,IAAI,IAAI;EAAC;EAAU;EAAW,GAAG,aAAa,SAAS,gBAAgB,YAAY,KAAK;EAAC,CAAC,CAAC,CAAC,KAAK,IAAI;CACtH,MAAM,YAAY,QAAQ,UAAU,MAAM,GAAG,IAAI;AACjD,OAAM,wBAAwB,WAAW,CACvC;EACE,QAAQ;EACR,KAAK;;;;;;;EAOL,QAAQ;GAAC;GAAW;GAAS;GAAK;EACnC,EACD;EACE,QAAQ;EACR,KAAK;;;;;;;;;;;;;;;;;;;;;;EAsBL,QAAQ;GACN;GACA;GACA;GACA,QAAQ,aAAa;GACrB;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,QAAQ,eAAe;GACvB;GACA,QAAQ,SAAS;GACjB,QAAQ,aAAa;GACrB,QAAQ,aAAa;GACrB,QAAQ,gBAAgB;GACzB;EACF,CACF,CAAC;;AAGJ,eAAe,aAAa,WAAsC;AAMhE,SAAO,MAJe,QADT,kBAAkB,UACG,EAAE,EAAE,eAAe,MAAM,CAAC,CAAC,OAAO,UAAmB;AACrF,MAAI,SAAS,MAAM,CAAE,QAAO,EAAE;AAC9B,QAAM;GACN,EACa,QAAQ,UAAU,MAAM,aAAa,CAAC,CAAC,KAAK,UAAU,MAAM,KAAK,CAAC,MAAM;;AAGzF,SAAS,iBAAiB,aAAgC,OAAyB;CACjF,MAAM,WAAW;EACf,YAAY,gBAAgB;EAC5B,YAAY;EACZ,YAAY;EACZ,YAAY,MAAM,KAAK,IAAI;EAC3B,YAAY,KAAK,KAAK,IAAI;EAC3B,CAAC,KAAK,IAAI,CAAC,aAAa;AACzB,QAAO,MAAM,QAAQ,OAAO,SAAS,QAAQ,YAAY,UAAU,KAAK,EAAE,EAAE;;AAG9E,SAAS,YAAY,OAAe,MAAsB;CACxD,IAAI,QAAQ;CACZ,IAAI,QAAQ,MAAM,QAAQ,KAAK;AAC/B,QAAO,UAAU,IAAI;AACnB,WAAS;AACT,UAAQ,MAAM,QAAQ,MAAM,QAAQ,KAAK,OAAO;;AAElD,QAAO;;AAGT,eAAe,yBAAyB,WAA0D;CAChG,MAAM,WAAW,MAAM,aAAa,UAAU;AAC9C,QAAO,QAAQ,IAAI,SAAS,IAAI,OAAO,YAAY;AAIjD,SAAO;GACL;GACA;GACA,QAAA,MANmB,aAAmC,oBAAoB;IAAE;IAAW;IAAS,CAAC,EAAE,aAAa,CAAC;GAOjH,cAAA,MANyB,eAAe,kBAAkB,WAAW,QAAQ,CAAC;GAO9E,kBAAiB,MANQ,sBAAsB,WAAW,QAAQ,EAMpC,GAAG,GAAG,EAAE,aAAa;GACpD;GACD,CAAC;;AAGL,eAAe,mBAAoE;CAEjF,MAAM,UAAU,MAAM,QADJ,KAAK,iBAAiB,EAAE,SACH,EAAE,EAAE,eAAe,MAAM,CAAC,CAAC,OAAO,UAAmB;AAC1F,MAAI,SAAS,MAAM,CAAE,QAAO,EAAE;AAC9B,QAAM;GACN;CACF,MAAM,SAAiD,EAAE;AACzD,MAAK,MAAM,SAAS,SAAS;AAC3B,MAAI,CAAC,MAAM,aAAa,CAAE;EAC1B,MAAM,QAAQ,kBAAkB,MAAM,KAAK;AAC3C,OAAK,OAAO,WAAW,aAAa,OAAO,WAAW,eAAe,OAAO,QAC1E,QAAO,KAAK;GAAE,IAAI,MAAM;GAAM,SAAS,MAAM;GAAS,CAAC;;AAG3D,QAAO;;AAGT,eAAe,eAAe,MAA+B;AAK3D,SAAO,MAJe,QAAQ,KAAK,CAAC,OAAO,UAAmB;AAC5D,MAAI,SAAS,MAAM,CAAE,QAAO,EAAE;AAC9B,QAAM;GACN,EACa,QAAQ,UAAU,MAAM,SAAS,QAAQ,IAAI,CAAC,MAAM,WAAW,IAAI,CAAC,CAAC;;AAGtF,eAAe,aAAgB,MAAc,UAAyB;AACpE,KAAI;AACF,SAAO,KAAK,MAAM,MAAM,SAAS,MAAM,OAAO,CAAC;UACxC,OAAO;AACd,MAAI,SAAS,MAAM,CAAE,QAAO;AAC5B,QAAM;;;AAIV,eAAe,oBAAoB,MAAc,OAA+B;AAC9E,OAAM,gBAAgB,KAAK;CAC3B,MAAM,WAAW,GAAG,QAAQ,KAAK,CAAC,IAAI,YAAY,CAAC;AACnD,OAAM,UAAU,UAAU,GAAG,KAAK,UAAU,OAAO,MAAM,EAAE,CAAC,KAAK,OAAO;AACxE,OAAM,OAAO,UAAU,KAAK;;AAG9B,eAAe,wBAAwB,WAAmB,QAAoC;AAC5F,OAAM,sBAAsB,WAAW,OAAO,OAAO;AACnD,QAAM,GAAG,QAAQ;;;MAGf,CAAC,IAAI,OAAO,OAAO,OAAO,SAAS,OAAO,eAAe,OAAO,QAAQ,OAAO,UAAU;GAC3F;;AAGJ,eAAe,uBAAuB,QAAqB,WAAkC;CAC3F,MAAM,EAAE,mBAAmB,MAAM,OAAO;AAExC,QAAM,MADc,gBAAgB,EACxB,YAAY;EACtB,MAAM;EACN;EACA,SAAS,EAAE,QAAQ;EACpB,CAAC;;AAGJ,SAAS,kBAAkB,aAA4B,KAAoB;AACzE,KAAI,CAAC,YAAa,QAAO;CACzB,MAAM,YAAY,KAAK,MAAM,YAAY;AACzC,QAAO,OAAO,MAAM,UAAU,IAAI,IAAI,SAAS,GAAG,YAAY,OAAU;;AAG1E,SAAS,cAAoC;AAC3C,QAAO;EACL,QAAQ;EACR,cAAc;EACd,cAAc;EACd,uBAAuB;EACvB,uBAAuB;EACvB,kBAAkB,EAAE;EACrB;;AAGH,SAAS,SAAS,OAAyB;AACzC,QAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,UAAU,SAAS,MAAM,SAAS;;AAG1F,SAAS,cAAc,OAAyB;AAC9C,QAAO,iBAAiB,SAAS,MAAM,QAAQ,SAAS,+BAA+B;;;;aA9e3C;cAC+B;iBACH;gBACU;eAOhE;cACyD;cAC7B;cACC;AAE3C,sBAAqB;AACrB,kCAAiC;AACjC,2CAA0C"}
@@ -0,0 +1,3 @@
1
+ import { i as init_cli, t as createResetMarker } from "./cli-DGeSW85B.js";
2
+ init_cli();
3
+ export { createResetMarker };
@@ -1,5 +1,5 @@
1
1
  import { i as normalizeIssueIdSync, n as extractPrefixSync, r as init_issue_id, t as extractNumberSync } from "./issue-id-DfZZfSZC.js";
2
- import { Qt as markAgentStoppedState, qt as getAgentState, t as init_agents, tn as saveAgentState } from "./agents-CQHK0CSA.js";
2
+ import { Qt as markAgentStoppedState, qt as getAgentState, t as init_agents, tn as saveAgentState } from "./agents-CgVr77Qq.js";
3
3
  import { a as stepSkipped, i as stepOk, n as init_types, r as stepFailed, t as getLinearApiKey } from "./types-D112L62U.js";
4
4
  import { Effect } from "effect";
5
5
  import { exec } from "child_process";
@@ -327,4 +327,4 @@ async function applyLabelLinearImpl(ctx, apiKey) {
327
327
  //#endregion
328
328
  export { closeIssue as n, WORKFLOW_LABELS as t };
329
329
 
330
- //# sourceMappingURL=close-issue-BJGX9_z7.js.map
330
+ //# sourceMappingURL=close-issue-Djajb6Sv.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"close-issue-BJGX9_z7.js","names":[],"sources":["../src/lib/lifecycle/close-issue.ts"],"sourcesContent":["/**\n * close-issue — Transition issue to closed/done state + label management.\n *\n * Uses the IssueTracker abstraction when available, with fallback to\n * direct API calls for contexts where the tracker isn't set up (e.g.,\n * standalone CLI).\n *\n * Operations:\n * 1. Transition issue to closed state\n * 2. Add 'closed-out' label\n * 3. Remove all current-phase workflow labels (WORKFLOW_LABELS)\n * 4. Add completion comment\n */\n\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { Effect } from 'effect';\nimport type { IssueTracker } from '../tracker/interface.js';\nimport type { LifecycleContext, StepResult } from './types.js';\nimport { stepOk, stepSkipped, stepFailed, getLinearApiKey } from './types.js';\nimport { extractNumberSync, extractPrefixSync, normalizeIssueIdSync } from '../issue-id.js';\nimport { getAgentState, markAgentStoppedState, saveAgentState } from '../agents.js';\n\nconst execAsync = promisify(exec);\n\nconst CLOSED_OUT_LABEL = 'closed-out';\nconst CLOSED_OUT_COLOR = '1d4ed8';\n/**\n * Canonical set of current-phase pipeline labels. A closed/done issue is in no\n * phase, so close paths strip ALL of these. Single source of truth — close-out\n * imports this rather than keeping its own (previously divergent, missing\n * `planned`) list, which had left ~82 closed issues falsely labelled in-review/\n * in-progress/planned.\n */\nexport const WORKFLOW_LABELS = [\n 'in-progress', 'in-review', 'planned', 'in-planning',\n 'needs-close-out', 'verifying-on-main', 'ready-for-merge',\n];\n\nexport const POST_MERGE_RESIDUE_LABELS = ['merged', 'ready'];\nconst CLOSE_OUT_LABELS_TO_REMOVE = [...WORKFLOW_LABELS, ...POST_MERGE_RESIDUE_LABELS];\n\n/** Options for close-issue */\nexport interface CloseIssueOptions {\n /** IssueTracker instance (preferred — uses abstraction layer) */\n tracker?: IssueTracker;\n /** Reason for closing */\n reason?: string;\n /** Comment to add when closing */\n comment?: string;\n /** Apply the closed-out label. Default: true */\n applyLabel?: boolean;\n /** Only apply label (skip state transition). Default: false */\n labelOnly?: boolean;\n}\n\n/**\n * Close an issue and manage labels.\n *\n * If a tracker is provided, uses the abstraction layer.\n * Otherwise, falls back to direct gh CLI (GitHub) or Linear SDK calls.\n */\nasync function markWorkAgentStoppedForIssue(issueId: string): Promise<void> {\n const agentId = `agent-${normalizeIssueIdSync(issueId)}`;\n const state = await Effect.runPromise(getAgentState(agentId));\n if (!state) return;\n markAgentStoppedState(state);\n await Effect.runPromise(saveAgentState(state));\n}\n\nexport function closeIssue(\n ctx: LifecycleContext,\n opts: CloseIssueOptions = {},\n): Effect.Effect<StepResult[]> {\n return Effect.gen(function* () {\n const results: StepResult[] = [];\n const { applyLabel = true, labelOnly = false, comment } = opts;\n\n // Step 1: Transition to closed (unless labelOnly)\n if (!labelOnly) {\n const closeResult = opts.tracker\n ? yield* closeViaTracker(ctx, opts.tracker, comment)\n : yield* closeViaDirect(ctx, comment);\n results.push(closeResult);\n\n // If close failed, don't bother with labels\n if (!closeResult.success && !closeResult.skipped) {\n return results;\n }\n }\n\n // Step 2: Close any open PR for the feature branch (GitHub only)\n if (ctx.github) {\n const prResult = yield* closeGitHubPr(ctx);\n results.push(prResult);\n }\n\n // Step 3: Apply closed-out label + remove workflow labels\n if (applyLabel) {\n const labelResult = yield* applyClosedOutLabel(ctx, opts.tracker);\n results.push(labelResult);\n }\n\n return results;\n });\n}\n\n/**\n * Close via IssueTracker abstraction.\n */\nfunction closeViaTracker(\n ctx: LifecycleContext,\n tracker: IssueTracker,\n comment?: string,\n): Effect.Effect<StepResult> {\n const step = 'close-issue:transition';\n return Effect.gen(function* () {\n yield* tracker.transitionIssue(ctx.issueId, 'closed');\n yield* Effect.promise(() => markWorkAgentStoppedForIssue(ctx.issueId));\n if (comment) {\n // Best-effort comment — swallow errors\n yield* tracker.addComment(ctx.issueId, comment).pipe(\n Effect.catch(() => Effect.void),\n );\n }\n return stepOk(step, [`Closed ${ctx.issueId} via ${tracker.name} tracker`]);\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed(step, `Failed to close via tracker: ${(err as Error).message ?? String(err)}`)),\n ),\n );\n}\n\n/**\n * Close via direct API calls (fallback when no tracker configured).\n * Determines issue type from context and uses appropriate method.\n */\nfunction closeViaDirect(\n ctx: LifecycleContext,\n comment?: string,\n): Effect.Effect<StepResult> {\n const step = 'close-issue:transition';\n\n if (ctx.github) {\n return closeGitHubDirect(ctx, comment);\n }\n\n // Rally issue\n if (ctx.rally) {\n return closeRallyDirect(ctx);\n }\n\n // Try Linear\n return Effect.gen(function* () {\n const linearApiKey = yield* Effect.promise(() => getLinearApiKey());\n if (linearApiKey) {\n return yield* closeLinearDirect(ctx, linearApiKey);\n }\n return stepFailed(step, 'No tracker available and cannot determine issue type');\n });\n}\n\n/**\n * Close a GitHub issue via gh CLI.\n */\nfunction closeGitHubDirect(ctx: LifecycleContext, comment?: string): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => closeGitHubDirectImpl(ctx, comment),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('close-issue:transition', `gh issue close failed: ${(err as Error).message}`)),\n ),\n );\n}\n\nasync function closeGitHubDirectImpl(ctx: LifecycleContext, comment?: string): Promise<StepResult> {\n const step = 'close-issue:transition';\n if (!ctx.github) {\n return stepFailed(step, 'GitHub config not provided');\n }\n const { owner, repo, number } = ctx.github;\n\n // Refuse to close a GitHub issue when its feature branch still has an open,\n // unmerged PR. PAN-1030 closed the issue while PR #1046 was open with 1152\n // lines of real changes — the close-out path trusted the operator without\n // verifying the merge. Fail loudly so the operator notices and either\n // merges the PR first or tells us why the issue should still close.\n const branchName = `feature/${ctx.issueId.toLowerCase()}`;\n try {\n const { stdout: prRaw } = await execAsync(\n `gh pr list --repo ${owner}/${repo} --head \"${branchName}\" --state open --json number,mergedAt,mergeCommit --jq '.[0]'`,\n { encoding: 'utf-8' },\n );\n const trimmed = prRaw.trim();\n if (trimmed) {\n try {\n const pr = JSON.parse(trimmed) as { number?: number; mergedAt?: string | null; mergeCommit?: unknown };\n if (pr.number && !pr.mergedAt && !pr.mergeCommit) {\n return stepFailed(\n step,\n `Refusing to close issue #${number}: open PR #${pr.number} on ${branchName} has not been merged. Merge or close the PR first, or rename the branch if it is unrelated.`,\n );\n }\n } catch {\n // PR JSON unparseable — continue with the close (don't block on parser error).\n }\n }\n } catch {\n // gh query failed — don't block close-out on a flaky network call.\n }\n\n try {\n const commentArg = comment ? ` --comment \"${comment.replace(/\"/g, '\\\\\"')}\"` : '';\n await execAsync(\n `gh issue close ${number} --repo ${owner}/${repo}${commentArg}`,\n { encoding: 'utf-8' },\n );\n await markWorkAgentStoppedForIssue(ctx.issueId);\n return stepOk(step, [`Closed GitHub issue #${number} on ${owner}/${repo}`]);\n } catch (err) {\n return stepFailed(step, `gh issue close failed: ${(err as Error).message}`);\n }\n}\n\n/**\n * Close any open GitHub PR for the feature branch.\n */\nfunction closeGitHubPr(ctx: LifecycleContext): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => closeGitHubPrImpl(ctx),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepSkipped('close-issue:close-pr', [`PR close failed (non-fatal): ${(err as Error).message}`])),\n ),\n );\n}\n\nasync function closeGitHubPrImpl(ctx: LifecycleContext): Promise<StepResult> {\n const step = 'close-issue:close-pr';\n if (!ctx.github) {\n return stepSkipped(step, ['Not a GitHub issue']);\n }\n const { owner, repo } = ctx.github;\n const issueLower = ctx.issueId.toLowerCase();\n const branchName = `feature/${issueLower}`;\n\n try {\n // Pull number + merge status. Closing without checking can land us in the\n // PAN-1030 state: issue closed + PR left OPEN with real unmerged code,\n // or worse — close a not-yet-merged PR with a \"Merged via Overdeck\n // lifecycle\" comment that lies about what happened.\n const { stdout: prListRaw } = await execAsync(\n `gh pr list --repo ${owner}/${repo} --head \"${branchName}\" --state open --json number,mergedAt,mergeCommit --jq '.[0]'`,\n { encoding: 'utf-8' },\n );\n const prRaw = prListRaw.trim();\n if (!prRaw) {\n return stepSkipped(step, ['No open PR found for branch']);\n }\n let pr: { number?: number; mergedAt?: string | null; mergeCommit?: unknown };\n try {\n pr = JSON.parse(prRaw);\n } catch {\n return stepSkipped(step, [`PR JSON parse failed: ${prRaw.slice(0, 80)}`]);\n }\n if (!pr.number) {\n return stepSkipped(step, ['Open PR query returned no number']);\n }\n if (!pr.mergedAt && !pr.mergeCommit) {\n // PR is open but NOT merged — refuse to close. The close-out flow must\n // not pretend an unmerged PR was merged. Fail loudly so the operator\n // notices the inconsistency rather than the PR silently disappearing.\n return stepFailed(\n step,\n `Refusing to close PR #${pr.number} on ${owner}/${repo}: PR is not merged. Investigate why close-out fired without a merge.`,\n );\n }\n await execAsync(\n `gh pr close ${pr.number} --repo ${owner}/${repo} --comment \"Merged via Overdeck lifecycle\"`,\n { encoding: 'utf-8' },\n );\n return stepOk(step, [`Closed PR #${pr.number} on ${owner}/${repo}`]);\n } catch (err) {\n return stepSkipped(step, [`PR close failed (non-fatal): ${(err as Error).message}`]);\n }\n}\n\n/**\n * Rate limit circuit breaker for Linear API.\n * After hitting a rate limit, stop all Linear API calls for COOLDOWN_MS.\n * This prevents the 24,626-call storm that exhausted Linear's 5000 req/hr limit (PAN-328).\n */\nlet _linearRateLimitUntil = 0;\nconst LINEAR_RATE_LIMIT_COOLDOWN_MS = 60 * 60 * 1000; // 1 hour (matches Linear's 5000/hr window)\n\n/**\n * Close a Linear issue via SDK (find by identifier, transition to Done).\n */\nfunction closeLinearDirect(ctx: LifecycleContext, apiKey: string): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => closeLinearDirectImpl(ctx, apiKey),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('close-issue:transition', `Linear close failed: ${(err as Error).message}`)),\n ),\n );\n}\n\nasync function closeLinearDirectImpl(ctx: LifecycleContext, apiKey: string): Promise<StepResult> {\n const step = 'close-issue:transition';\n\n // Circuit breaker: if we recently hit a rate limit, fail fast without making API calls\n if (Date.now() < _linearRateLimitUntil) {\n const remainingMin = Math.ceil((_linearRateLimitUntil - Date.now()) / 60000);\n return stepFailed(step, `Linear rate limit cooldown active (${remainingMin}min remaining). Issue will be closed during close-out ceremony.`);\n }\n\n try {\n const { LinearClient } = await import('@linear/sdk');\n const client = new LinearClient({ apiKey });\n\n const issueNumber = extractNumberSync(ctx.issueId);\n const issuePrefix = extractPrefixSync(ctx.issueId);\n if (issueNumber === null || issuePrefix === null) {\n return stepFailed(step, `Could not parse issue ID: ${ctx.issueId}`);\n }\n const results = await client.issues({\n filter: {\n number: { eq: issueNumber },\n team: { key: { eq: issuePrefix } },\n },\n first: 1,\n });\n\n if (results.nodes.length === 0) {\n return stepFailed(step, `Issue ${ctx.issueId} not found in Linear`);\n }\n\n const issue = results.nodes[0];\n const team = await issue.team;\n if (team) {\n const states = await team.states();\n const doneState = states.nodes.find(s => s.name === 'Done') ||\n states.nodes.find(s => s.type === 'completed');\n if (doneState) {\n await issue.update({ stateId: doneState.id });\n }\n }\n\n await markWorkAgentStoppedForIssue(ctx.issueId);\n return stepOk(step, [`Moved Linear issue ${ctx.issueId} to Done`]);\n } catch (err) {\n const message = (err as Error).message;\n\n // Detect rate limit errors and activate circuit breaker\n if (message.includes('Rate limit') || message.includes('rate limit') || message.includes('429')) {\n _linearRateLimitUntil = Date.now() + LINEAR_RATE_LIMIT_COOLDOWN_MS;\n console.warn(`[close-issue] Linear rate limit hit — circuit breaker activated for 1 hour`);\n return stepFailed(step, `Linear rate limit exceeded. Circuit breaker activated — no Linear API calls for 1 hour. Issue will be closed during close-out ceremony.`);\n }\n\n return stepFailed(step, `Linear close failed: ${message}`);\n }\n}\n\n/**\n * Close a Rally issue via RallyTracker.\n */\nfunction closeRallyDirect(ctx: LifecycleContext): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => closeRallyDirectImpl(ctx),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('close-issue:transition', `Rally close failed: ${(err as Error).message ?? String(err)}`)),\n ),\n );\n}\n\nasync function closeRallyDirectImpl(ctx: LifecycleContext): Promise<StepResult> {\n const step = 'close-issue:transition';\n if (!ctx.rally) {\n return stepFailed(step, 'Rally config not provided');\n }\n const { RallyTracker } = await import('../tracker/rally.js');\n const tracker = new RallyTracker({\n apiKey: ctx.rally.apiKey,\n server: ctx.rally.server,\n workspace: ctx.rally.workspace,\n project: ctx.rally.project,\n });\n // RallyTracker.transitionIssue returns Effect (migrated in PAN-1249).\n await Effect.runPromise(tracker.transitionIssue(ctx.issueId, 'closed'));\n await markWorkAgentStoppedForIssue(ctx.issueId);\n return stepOk(step, [`Closed Rally issue ${ctx.issueId}`]);\n}\n\n/**\n * Apply 'closed-out' label and remove workflow labels.\n * Uses tracker if available, falls back to direct calls.\n */\nfunction applyClosedOutLabel(\n ctx: LifecycleContext,\n tracker?: IssueTracker,\n): Effect.Effect<StepResult> {\n const step = 'close-issue:label';\n\n if (tracker) {\n return applyLabelViaTracker(ctx, tracker);\n }\n\n if (ctx.github) {\n return applyLabelGitHub(ctx);\n }\n\n return Effect.gen(function* () {\n const linearApiKey = yield* Effect.promise(() => getLinearApiKey());\n if (linearApiKey) {\n return yield* applyLabelLinear(ctx, linearApiKey);\n }\n return stepSkipped(step, ['No tracker available for label management']);\n });\n}\n\nfunction applyLabelViaTracker(\n ctx: LifecycleContext,\n tracker: IssueTracker,\n): Effect.Effect<StepResult> {\n const step = 'close-issue:label';\n return Effect.gen(function* () {\n const issue = yield* tracker.getIssue(ctx.issueId);\n const newLabels = issue.labels.filter((l: string) => !CLOSE_OUT_LABELS_TO_REMOVE.includes(l));\n if (!newLabels.includes(CLOSED_OUT_LABEL)) {\n newLabels.push(CLOSED_OUT_LABEL);\n }\n yield* tracker.updateIssue(ctx.issueId, { labels: newLabels });\n return stepOk(step, [`Applied '${CLOSED_OUT_LABEL}' label via ${tracker.name} tracker`]);\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepSkipped(step, [`Label management failed (non-fatal): ${(err as Error).message ?? String(err)}`])),\n ),\n );\n}\n\nfunction applyLabelGitHub(ctx: LifecycleContext): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => applyLabelGitHubImpl(ctx),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepSkipped('close-issue:label', [`Label management failed (non-fatal): ${(err as Error).message}`])),\n ),\n );\n}\n\nasync function applyLabelGitHubImpl(ctx: LifecycleContext): Promise<StepResult> {\n const step = 'close-issue:label';\n if (!ctx.github) return stepSkipped(step);\n const { owner, repo, number } = ctx.github;\n\n try {\n // Ensure label exists\n await execAsync(\n `gh label create \"${CLOSED_OUT_LABEL}\" --repo ${owner}/${repo} --color \"${CLOSED_OUT_COLOR}\" --description \"Verified and closed out\" --force 2>/dev/null || true`,\n { encoding: 'utf-8' },\n );\n // Only remove labels actually on the issue — `gh issue edit` fails the\n // whole edit if any --remove-label names a label missing from the repo.\n const { stdout: labelsJson } = await execAsync(\n `gh issue view ${number} --repo ${owner}/${repo} --json labels --jq '[.labels[].name]'`,\n { encoding: 'utf-8' },\n );\n const currentLabels: string[] = JSON.parse(labelsJson.trim() || '[]');\n const removeLabelArgs = CLOSE_OUT_LABELS_TO_REMOVE\n .filter(label => currentLabels.includes(label))\n .map(label => `--remove-label \"${label}\"`)\n .join(' ');\n await execAsync(\n `gh issue edit ${number} --repo ${owner}/${repo} --add-label \"${CLOSED_OUT_LABEL}\" ${removeLabelArgs}`.trim(),\n { encoding: 'utf-8' },\n );\n return stepOk(step, [`Applied '${CLOSED_OUT_LABEL}' label on GitHub`]);\n } catch (err) {\n return stepSkipped(step, [`Label management failed (non-fatal): ${(err as Error).message}`]);\n }\n}\n\nfunction applyLabelLinear(ctx: LifecycleContext, apiKey: string): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => applyLabelLinearImpl(ctx, apiKey),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepSkipped('close-issue:label', [`Linear label management failed (non-fatal): ${(err as Error).message}`])),\n ),\n );\n}\n\nasync function applyLabelLinearImpl(ctx: LifecycleContext, apiKey: string): Promise<StepResult> {\n const step = 'close-issue:label';\n try {\n const { LinearClient } = await import('@linear/sdk');\n const client = new LinearClient({ apiKey });\n\n const issueNum = extractNumberSync(ctx.issueId);\n const teamKey = extractPrefixSync(ctx.issueId);\n if (issueNum === null || teamKey === null) {\n return stepFailed(step, `Could not parse issue ID: ${ctx.issueId}`);\n }\n const results = await client.issues({\n filter: {\n number: { eq: issueNum },\n team: { key: { eq: teamKey } },\n },\n first: 1,\n });\n if (results.nodes.length === 0) {\n return stepSkipped(step, ['Issue not found for label management']);\n }\n\n const issue = results.nodes[0];\n\n // Find or create closed-out label\n const labels = await client.issueLabels({ filter: { name: { eq: CLOSED_OUT_LABEL } } });\n let labelId: string;\n if (labels.nodes.length > 0) {\n labelId = labels.nodes[0].id;\n } else {\n const created = await client.createIssueLabel({ name: CLOSED_OUT_LABEL, color: `#${CLOSED_OUT_COLOR}` });\n const createdLabel = await created.issueLabel;\n labelId = createdLabel ? createdLabel.id : '';\n }\n\n if (labelId) {\n const existingLabels = await issue.labels();\n const labelIds = existingLabels.nodes\n .filter(l => !CLOSE_OUT_LABELS_TO_REMOVE.includes(l.name))\n .map(l => l.id);\n if (!labelIds.includes(labelId)) {\n labelIds.push(labelId);\n }\n await issue.update({ labelIds });\n }\n\n return stepOk(step, [`Applied '${CLOSED_OUT_LABEL}' label on Linear`]);\n } catch (err) {\n return stepSkipped(step, [`Linear label management failed (non-fatal): ${(err as Error).message}`]);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"close-issue-Djajb6Sv.js","names":[],"sources":["../src/lib/lifecycle/close-issue.ts"],"sourcesContent":["/**\n * close-issue — Transition issue to closed/done state + label management.\n *\n * Uses the IssueTracker abstraction when available, with fallback to\n * direct API calls for contexts where the tracker isn't set up (e.g.,\n * standalone CLI).\n *\n * Operations:\n * 1. Transition issue to closed state\n * 2. Add 'closed-out' label\n * 3. Remove all current-phase workflow labels (WORKFLOW_LABELS)\n * 4. Add completion comment\n */\n\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { Effect } from 'effect';\nimport type { IssueTracker } from '../tracker/interface.js';\nimport type { LifecycleContext, StepResult } from './types.js';\nimport { stepOk, stepSkipped, stepFailed, getLinearApiKey } from './types.js';\nimport { extractNumberSync, extractPrefixSync, normalizeIssueIdSync } from '../issue-id.js';\nimport { getAgentState, markAgentStoppedState, saveAgentState } from '../agents.js';\n\nconst execAsync = promisify(exec);\n\nconst CLOSED_OUT_LABEL = 'closed-out';\nconst CLOSED_OUT_COLOR = '1d4ed8';\n/**\n * Canonical set of current-phase pipeline labels. A closed/done issue is in no\n * phase, so close paths strip ALL of these. Single source of truth — close-out\n * imports this rather than keeping its own (previously divergent, missing\n * `planned`) list, which had left ~82 closed issues falsely labelled in-review/\n * in-progress/planned.\n */\nexport const WORKFLOW_LABELS = [\n 'in-progress', 'in-review', 'planned', 'in-planning',\n 'needs-close-out', 'verifying-on-main', 'ready-for-merge',\n];\n\nexport const POST_MERGE_RESIDUE_LABELS = ['merged', 'ready'];\nconst CLOSE_OUT_LABELS_TO_REMOVE = [...WORKFLOW_LABELS, ...POST_MERGE_RESIDUE_LABELS];\n\n/** Options for close-issue */\nexport interface CloseIssueOptions {\n /** IssueTracker instance (preferred — uses abstraction layer) */\n tracker?: IssueTracker;\n /** Reason for closing */\n reason?: string;\n /** Comment to add when closing */\n comment?: string;\n /** Apply the closed-out label. Default: true */\n applyLabel?: boolean;\n /** Only apply label (skip state transition). Default: false */\n labelOnly?: boolean;\n}\n\n/**\n * Close an issue and manage labels.\n *\n * If a tracker is provided, uses the abstraction layer.\n * Otherwise, falls back to direct gh CLI (GitHub) or Linear SDK calls.\n */\nasync function markWorkAgentStoppedForIssue(issueId: string): Promise<void> {\n const agentId = `agent-${normalizeIssueIdSync(issueId)}`;\n const state = await Effect.runPromise(getAgentState(agentId));\n if (!state) return;\n markAgentStoppedState(state);\n await Effect.runPromise(saveAgentState(state));\n}\n\nexport function closeIssue(\n ctx: LifecycleContext,\n opts: CloseIssueOptions = {},\n): Effect.Effect<StepResult[]> {\n return Effect.gen(function* () {\n const results: StepResult[] = [];\n const { applyLabel = true, labelOnly = false, comment } = opts;\n\n // Step 1: Transition to closed (unless labelOnly)\n if (!labelOnly) {\n const closeResult = opts.tracker\n ? yield* closeViaTracker(ctx, opts.tracker, comment)\n : yield* closeViaDirect(ctx, comment);\n results.push(closeResult);\n\n // If close failed, don't bother with labels\n if (!closeResult.success && !closeResult.skipped) {\n return results;\n }\n }\n\n // Step 2: Close any open PR for the feature branch (GitHub only)\n if (ctx.github) {\n const prResult = yield* closeGitHubPr(ctx);\n results.push(prResult);\n }\n\n // Step 3: Apply closed-out label + remove workflow labels\n if (applyLabel) {\n const labelResult = yield* applyClosedOutLabel(ctx, opts.tracker);\n results.push(labelResult);\n }\n\n return results;\n });\n}\n\n/**\n * Close via IssueTracker abstraction.\n */\nfunction closeViaTracker(\n ctx: LifecycleContext,\n tracker: IssueTracker,\n comment?: string,\n): Effect.Effect<StepResult> {\n const step = 'close-issue:transition';\n return Effect.gen(function* () {\n yield* tracker.transitionIssue(ctx.issueId, 'closed');\n yield* Effect.promise(() => markWorkAgentStoppedForIssue(ctx.issueId));\n if (comment) {\n // Best-effort comment — swallow errors\n yield* tracker.addComment(ctx.issueId, comment).pipe(\n Effect.catch(() => Effect.void),\n );\n }\n return stepOk(step, [`Closed ${ctx.issueId} via ${tracker.name} tracker`]);\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed(step, `Failed to close via tracker: ${(err as Error).message ?? String(err)}`)),\n ),\n );\n}\n\n/**\n * Close via direct API calls (fallback when no tracker configured).\n * Determines issue type from context and uses appropriate method.\n */\nfunction closeViaDirect(\n ctx: LifecycleContext,\n comment?: string,\n): Effect.Effect<StepResult> {\n const step = 'close-issue:transition';\n\n if (ctx.github) {\n return closeGitHubDirect(ctx, comment);\n }\n\n // Rally issue\n if (ctx.rally) {\n return closeRallyDirect(ctx);\n }\n\n // Try Linear\n return Effect.gen(function* () {\n const linearApiKey = yield* Effect.promise(() => getLinearApiKey());\n if (linearApiKey) {\n return yield* closeLinearDirect(ctx, linearApiKey);\n }\n return stepFailed(step, 'No tracker available and cannot determine issue type');\n });\n}\n\n/**\n * Close a GitHub issue via gh CLI.\n */\nfunction closeGitHubDirect(ctx: LifecycleContext, comment?: string): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => closeGitHubDirectImpl(ctx, comment),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('close-issue:transition', `gh issue close failed: ${(err as Error).message}`)),\n ),\n );\n}\n\nasync function closeGitHubDirectImpl(ctx: LifecycleContext, comment?: string): Promise<StepResult> {\n const step = 'close-issue:transition';\n if (!ctx.github) {\n return stepFailed(step, 'GitHub config not provided');\n }\n const { owner, repo, number } = ctx.github;\n\n // Refuse to close a GitHub issue when its feature branch still has an open,\n // unmerged PR. PAN-1030 closed the issue while PR #1046 was open with 1152\n // lines of real changes — the close-out path trusted the operator without\n // verifying the merge. Fail loudly so the operator notices and either\n // merges the PR first or tells us why the issue should still close.\n const branchName = `feature/${ctx.issueId.toLowerCase()}`;\n try {\n const { stdout: prRaw } = await execAsync(\n `gh pr list --repo ${owner}/${repo} --head \"${branchName}\" --state open --json number,mergedAt,mergeCommit --jq '.[0]'`,\n { encoding: 'utf-8' },\n );\n const trimmed = prRaw.trim();\n if (trimmed) {\n try {\n const pr = JSON.parse(trimmed) as { number?: number; mergedAt?: string | null; mergeCommit?: unknown };\n if (pr.number && !pr.mergedAt && !pr.mergeCommit) {\n return stepFailed(\n step,\n `Refusing to close issue #${number}: open PR #${pr.number} on ${branchName} has not been merged. Merge or close the PR first, or rename the branch if it is unrelated.`,\n );\n }\n } catch {\n // PR JSON unparseable — continue with the close (don't block on parser error).\n }\n }\n } catch {\n // gh query failed — don't block close-out on a flaky network call.\n }\n\n try {\n const commentArg = comment ? ` --comment \"${comment.replace(/\"/g, '\\\\\"')}\"` : '';\n await execAsync(\n `gh issue close ${number} --repo ${owner}/${repo}${commentArg}`,\n { encoding: 'utf-8' },\n );\n await markWorkAgentStoppedForIssue(ctx.issueId);\n return stepOk(step, [`Closed GitHub issue #${number} on ${owner}/${repo}`]);\n } catch (err) {\n return stepFailed(step, `gh issue close failed: ${(err as Error).message}`);\n }\n}\n\n/**\n * Close any open GitHub PR for the feature branch.\n */\nfunction closeGitHubPr(ctx: LifecycleContext): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => closeGitHubPrImpl(ctx),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepSkipped('close-issue:close-pr', [`PR close failed (non-fatal): ${(err as Error).message}`])),\n ),\n );\n}\n\nasync function closeGitHubPrImpl(ctx: LifecycleContext): Promise<StepResult> {\n const step = 'close-issue:close-pr';\n if (!ctx.github) {\n return stepSkipped(step, ['Not a GitHub issue']);\n }\n const { owner, repo } = ctx.github;\n const issueLower = ctx.issueId.toLowerCase();\n const branchName = `feature/${issueLower}`;\n\n try {\n // Pull number + merge status. Closing without checking can land us in the\n // PAN-1030 state: issue closed + PR left OPEN with real unmerged code,\n // or worse — close a not-yet-merged PR with a \"Merged via Overdeck\n // lifecycle\" comment that lies about what happened.\n const { stdout: prListRaw } = await execAsync(\n `gh pr list --repo ${owner}/${repo} --head \"${branchName}\" --state open --json number,mergedAt,mergeCommit --jq '.[0]'`,\n { encoding: 'utf-8' },\n );\n const prRaw = prListRaw.trim();\n if (!prRaw) {\n return stepSkipped(step, ['No open PR found for branch']);\n }\n let pr: { number?: number; mergedAt?: string | null; mergeCommit?: unknown };\n try {\n pr = JSON.parse(prRaw);\n } catch {\n return stepSkipped(step, [`PR JSON parse failed: ${prRaw.slice(0, 80)}`]);\n }\n if (!pr.number) {\n return stepSkipped(step, ['Open PR query returned no number']);\n }\n if (!pr.mergedAt && !pr.mergeCommit) {\n // PR is open but NOT merged — refuse to close. The close-out flow must\n // not pretend an unmerged PR was merged. Fail loudly so the operator\n // notices the inconsistency rather than the PR silently disappearing.\n return stepFailed(\n step,\n `Refusing to close PR #${pr.number} on ${owner}/${repo}: PR is not merged. Investigate why close-out fired without a merge.`,\n );\n }\n await execAsync(\n `gh pr close ${pr.number} --repo ${owner}/${repo} --comment \"Merged via Overdeck lifecycle\"`,\n { encoding: 'utf-8' },\n );\n return stepOk(step, [`Closed PR #${pr.number} on ${owner}/${repo}`]);\n } catch (err) {\n return stepSkipped(step, [`PR close failed (non-fatal): ${(err as Error).message}`]);\n }\n}\n\n/**\n * Rate limit circuit breaker for Linear API.\n * After hitting a rate limit, stop all Linear API calls for COOLDOWN_MS.\n * This prevents the 24,626-call storm that exhausted Linear's 5000 req/hr limit (PAN-328).\n */\nlet _linearRateLimitUntil = 0;\nconst LINEAR_RATE_LIMIT_COOLDOWN_MS = 60 * 60 * 1000; // 1 hour (matches Linear's 5000/hr window)\n\n/**\n * Close a Linear issue via SDK (find by identifier, transition to Done).\n */\nfunction closeLinearDirect(ctx: LifecycleContext, apiKey: string): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => closeLinearDirectImpl(ctx, apiKey),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('close-issue:transition', `Linear close failed: ${(err as Error).message}`)),\n ),\n );\n}\n\nasync function closeLinearDirectImpl(ctx: LifecycleContext, apiKey: string): Promise<StepResult> {\n const step = 'close-issue:transition';\n\n // Circuit breaker: if we recently hit a rate limit, fail fast without making API calls\n if (Date.now() < _linearRateLimitUntil) {\n const remainingMin = Math.ceil((_linearRateLimitUntil - Date.now()) / 60000);\n return stepFailed(step, `Linear rate limit cooldown active (${remainingMin}min remaining). Issue will be closed during close-out ceremony.`);\n }\n\n try {\n const { LinearClient } = await import('@linear/sdk');\n const client = new LinearClient({ apiKey });\n\n const issueNumber = extractNumberSync(ctx.issueId);\n const issuePrefix = extractPrefixSync(ctx.issueId);\n if (issueNumber === null || issuePrefix === null) {\n return stepFailed(step, `Could not parse issue ID: ${ctx.issueId}`);\n }\n const results = await client.issues({\n filter: {\n number: { eq: issueNumber },\n team: { key: { eq: issuePrefix } },\n },\n first: 1,\n });\n\n if (results.nodes.length === 0) {\n return stepFailed(step, `Issue ${ctx.issueId} not found in Linear`);\n }\n\n const issue = results.nodes[0];\n const team = await issue.team;\n if (team) {\n const states = await team.states();\n const doneState = states.nodes.find(s => s.name === 'Done') ||\n states.nodes.find(s => s.type === 'completed');\n if (doneState) {\n await issue.update({ stateId: doneState.id });\n }\n }\n\n await markWorkAgentStoppedForIssue(ctx.issueId);\n return stepOk(step, [`Moved Linear issue ${ctx.issueId} to Done`]);\n } catch (err) {\n const message = (err as Error).message;\n\n // Detect rate limit errors and activate circuit breaker\n if (message.includes('Rate limit') || message.includes('rate limit') || message.includes('429')) {\n _linearRateLimitUntil = Date.now() + LINEAR_RATE_LIMIT_COOLDOWN_MS;\n console.warn(`[close-issue] Linear rate limit hit — circuit breaker activated for 1 hour`);\n return stepFailed(step, `Linear rate limit exceeded. Circuit breaker activated — no Linear API calls for 1 hour. Issue will be closed during close-out ceremony.`);\n }\n\n return stepFailed(step, `Linear close failed: ${message}`);\n }\n}\n\n/**\n * Close a Rally issue via RallyTracker.\n */\nfunction closeRallyDirect(ctx: LifecycleContext): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => closeRallyDirectImpl(ctx),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('close-issue:transition', `Rally close failed: ${(err as Error).message ?? String(err)}`)),\n ),\n );\n}\n\nasync function closeRallyDirectImpl(ctx: LifecycleContext): Promise<StepResult> {\n const step = 'close-issue:transition';\n if (!ctx.rally) {\n return stepFailed(step, 'Rally config not provided');\n }\n const { RallyTracker } = await import('../tracker/rally.js');\n const tracker = new RallyTracker({\n apiKey: ctx.rally.apiKey,\n server: ctx.rally.server,\n workspace: ctx.rally.workspace,\n project: ctx.rally.project,\n });\n // RallyTracker.transitionIssue returns Effect (migrated in PAN-1249).\n await Effect.runPromise(tracker.transitionIssue(ctx.issueId, 'closed'));\n await markWorkAgentStoppedForIssue(ctx.issueId);\n return stepOk(step, [`Closed Rally issue ${ctx.issueId}`]);\n}\n\n/**\n * Apply 'closed-out' label and remove workflow labels.\n * Uses tracker if available, falls back to direct calls.\n */\nfunction applyClosedOutLabel(\n ctx: LifecycleContext,\n tracker?: IssueTracker,\n): Effect.Effect<StepResult> {\n const step = 'close-issue:label';\n\n if (tracker) {\n return applyLabelViaTracker(ctx, tracker);\n }\n\n if (ctx.github) {\n return applyLabelGitHub(ctx);\n }\n\n return Effect.gen(function* () {\n const linearApiKey = yield* Effect.promise(() => getLinearApiKey());\n if (linearApiKey) {\n return yield* applyLabelLinear(ctx, linearApiKey);\n }\n return stepSkipped(step, ['No tracker available for label management']);\n });\n}\n\nfunction applyLabelViaTracker(\n ctx: LifecycleContext,\n tracker: IssueTracker,\n): Effect.Effect<StepResult> {\n const step = 'close-issue:label';\n return Effect.gen(function* () {\n const issue = yield* tracker.getIssue(ctx.issueId);\n const newLabels = issue.labels.filter((l: string) => !CLOSE_OUT_LABELS_TO_REMOVE.includes(l));\n if (!newLabels.includes(CLOSED_OUT_LABEL)) {\n newLabels.push(CLOSED_OUT_LABEL);\n }\n yield* tracker.updateIssue(ctx.issueId, { labels: newLabels });\n return stepOk(step, [`Applied '${CLOSED_OUT_LABEL}' label via ${tracker.name} tracker`]);\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepSkipped(step, [`Label management failed (non-fatal): ${(err as Error).message ?? String(err)}`])),\n ),\n );\n}\n\nfunction applyLabelGitHub(ctx: LifecycleContext): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => applyLabelGitHubImpl(ctx),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepSkipped('close-issue:label', [`Label management failed (non-fatal): ${(err as Error).message}`])),\n ),\n );\n}\n\nasync function applyLabelGitHubImpl(ctx: LifecycleContext): Promise<StepResult> {\n const step = 'close-issue:label';\n if (!ctx.github) return stepSkipped(step);\n const { owner, repo, number } = ctx.github;\n\n try {\n // Ensure label exists\n await execAsync(\n `gh label create \"${CLOSED_OUT_LABEL}\" --repo ${owner}/${repo} --color \"${CLOSED_OUT_COLOR}\" --description \"Verified and closed out\" --force 2>/dev/null || true`,\n { encoding: 'utf-8' },\n );\n // Only remove labels actually on the issue — `gh issue edit` fails the\n // whole edit if any --remove-label names a label missing from the repo.\n const { stdout: labelsJson } = await execAsync(\n `gh issue view ${number} --repo ${owner}/${repo} --json labels --jq '[.labels[].name]'`,\n { encoding: 'utf-8' },\n );\n const currentLabels: string[] = JSON.parse(labelsJson.trim() || '[]');\n const removeLabelArgs = CLOSE_OUT_LABELS_TO_REMOVE\n .filter(label => currentLabels.includes(label))\n .map(label => `--remove-label \"${label}\"`)\n .join(' ');\n await execAsync(\n `gh issue edit ${number} --repo ${owner}/${repo} --add-label \"${CLOSED_OUT_LABEL}\" ${removeLabelArgs}`.trim(),\n { encoding: 'utf-8' },\n );\n return stepOk(step, [`Applied '${CLOSED_OUT_LABEL}' label on GitHub`]);\n } catch (err) {\n return stepSkipped(step, [`Label management failed (non-fatal): ${(err as Error).message}`]);\n }\n}\n\nfunction applyLabelLinear(ctx: LifecycleContext, apiKey: string): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => applyLabelLinearImpl(ctx, apiKey),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepSkipped('close-issue:label', [`Linear label management failed (non-fatal): ${(err as Error).message}`])),\n ),\n );\n}\n\nasync function applyLabelLinearImpl(ctx: LifecycleContext, apiKey: string): Promise<StepResult> {\n const step = 'close-issue:label';\n try {\n const { LinearClient } = await import('@linear/sdk');\n const client = new LinearClient({ apiKey });\n\n const issueNum = extractNumberSync(ctx.issueId);\n const teamKey = extractPrefixSync(ctx.issueId);\n if (issueNum === null || teamKey === null) {\n return stepFailed(step, `Could not parse issue ID: ${ctx.issueId}`);\n }\n const results = await client.issues({\n filter: {\n number: { eq: issueNum },\n team: { key: { eq: teamKey } },\n },\n first: 1,\n });\n if (results.nodes.length === 0) {\n return stepSkipped(step, ['Issue not found for label management']);\n }\n\n const issue = results.nodes[0];\n\n // Find or create closed-out label\n const labels = await client.issueLabels({ filter: { name: { eq: CLOSED_OUT_LABEL } } });\n let labelId: string;\n if (labels.nodes.length > 0) {\n labelId = labels.nodes[0].id;\n } else {\n const created = await client.createIssueLabel({ name: CLOSED_OUT_LABEL, color: `#${CLOSED_OUT_COLOR}` });\n const createdLabel = await created.issueLabel;\n labelId = createdLabel ? createdLabel.id : '';\n }\n\n if (labelId) {\n const existingLabels = await issue.labels();\n const labelIds = existingLabels.nodes\n .filter(l => !CLOSE_OUT_LABELS_TO_REMOVE.includes(l.name))\n .map(l => l.id);\n if (!labelIds.includes(labelId)) {\n labelIds.push(labelId);\n }\n await issue.update({ labelIds });\n }\n\n return stepOk(step, [`Applied '${CLOSED_OUT_LABEL}' label on Linear`]);\n } catch (err) {\n return stepSkipped(step, [`Linear label management failed (non-fatal): ${(err as Error).message}`]);\n }\n}\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- import { K as getConcurrencyLimits, U as countRunningAgents, q as init_concurrency } from "./agents-CQHK0CSA.js";
1
+ import { K as getConcurrencyLimits, U as countRunningAgents, q as init_concurrency } from "./agents-CgVr77Qq.js";
2
2
  init_concurrency();
3
3
  export { countRunningAgents, getConcurrencyLimits };
@@ -2,13 +2,13 @@ import { t as __esmMin } from "./chunk-ruWRV7i3.js";
2
2
  import { et as init_paths } from "./paths-3R6b989k.js";
3
3
  import { l as loadConfigSync, t as init_config_yaml } from "./config-yaml-DoxSNHxD.js";
4
4
  import { O as init_model_capabilities } from "./harness-policy-CpXdXKL4.js";
5
- import { Pt as getProviderExportsForModel, Tt as getAgentRuntimeBaseCommand, t as init_agents } from "./agents-CQHK0CSA.js";
5
+ import { Pt as getProviderExportsForModel, Tt as getAgentRuntimeBaseCommand, t as init_agents } from "./agents-CgVr77Qq.js";
6
6
  import { a as init_event_store, n as getEventStore } from "./event-store-8BddVviQ.js";
7
7
  import { c as init_codex_parser, d as init_behavior } from "./codex-Czbp-N_y.js";
8
8
  import { c as isBackgroundFeatureEnabled, s as init_features } from "./settings-CDGbMNkl.js";
9
9
  import { f as init_cost } from "./cost-6uWF3Bnw.js";
10
- import { r as init_smart_compaction, t as generateSmartSummary } from "./smart-compaction-ByUEgQkF.js";
11
- import { n as init_summary_fork, t as generateFallbackSummary } from "./summary-fork-CPUTKcAT.js";
10
+ import { r as init_smart_compaction, t as generateSmartSummary } from "./smart-compaction-ByKXTQNy.js";
11
+ import { n as init_summary_fork, t as generateFallbackSummary } from "./summary-fork-Dx1RMuvU.js";
12
12
  import { Effect } from "effect";
13
13
  import { existsSync } from "node:fs";
14
14
  import { dirname, join } from "node:path";
@@ -389,4 +389,4 @@ __esmMin((() => {
389
389
  }))();
390
390
  export { buildCompactionRuntimeInfo, compactConversationNative, estimateContextTokens, getConversationCompactionSettings, isCompacting, maybeCompactBeforeRespawn, shouldInterceptManualCompact };
391
391
 
392
- //# sourceMappingURL=conversation-compaction-B_6s9EvM.js.map
392
+ //# sourceMappingURL=conversation-compaction-BQvZh09I.js.map