@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":"spawn-planning-session-BmJWHSCu.js","names":[],"sources":["../../src/lib/planning/spawn-planning-session.ts"],"sourcesContent":["/**\n * Spawn Planning Session — background workspace + agent setup\n *\n * Extracted from the old Express /api/issues/:id/start-planning handler.\n * Creates workspace, writes planning prompt, spawns Claude Code in tmux.\n * Used by both the dashboard route and CLI.\n *\n * This runs as a background task after the API responds — the UI shows\n * \"Waiting for session to start...\" until the tmux session is ready.\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { access, mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { Effect } from 'effect';\nimport { extractTeamPrefix, findProjectByTeamSync, findProjectByPathSync } from '../projects.js';\nimport {\n sessionExists,\n createSession,\n killSession,\n setOption,\n exactPaneTarget,\n buildTmuxCommandString,\n} from '../tmux.js';\nimport { createWorkspace } from '../workspace-manager.js';\nimport { renderPrompt } from '../cloister/prompts.js';\nimport { deliverInitialPromptWithRetry, getAgentRuntimeBaseCommand, getProviderExportsForModel, retrieveSpawnTimeMemoryContext, roleAgentDefinitionPath, saveAgentStateSync, getAgentStateSync } from '../agents.js';\nimport { getCodexLauncherFields, getOhmypiLauncherFields } from '../agents/runtime-command.js';\nimport { loadConfigSync, resolveModel } from '../config-yaml.js';\nimport { resolveHarness } from '../harness-resolve.js';\nimport { getHarnessBehavior } from '../runtimes/behavior.js';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport { generateLauncherScriptSync } from '../launcher-generator.js';\nimport { BLANKED_PROVIDER_ENV } from '../child-env.js';\nimport { ensureWorkspacePanDir, getWorkspacePanPaths, writeWorkspaceContext } from '../pan-dir/index.js';\nimport {\n appendSessionEntrySync,\n getIssueRecordPath,\n getProjectConfigFromWorkspacePath,\n resolveProjectForIssue,\n} from '../pan-dir/record.js';\nimport { workspaceContextFile } from '../context-layers/layers.js';\nimport { ensureSessionContextBriefingFile } from '../briefing-freshness.js';\n\nconst __dirname = fileURLToPath(new URL('.', import.meta.url));\n\n/**\n * Path to the per-issue \"auto-spawn the work agent on planning finalize\" flag,\n * written at planning spawn when the operator launched with --auto-start. This\n * is the single source of the launch-time auto-start intent; every finalize\n * path (CLI `pan plan finalize`, the dashboard Done button, host auto-finalize)\n * reads it so the intent is honored no matter how planning is completed.\n */\nexport function autoSpawnOnFinalizeFlagPath(issueId: string): string {\n const overdeckHome = process.env['OVERDECK_HOME'] ?? join(homedir(), '.overdeck');\n return join(overdeckHome, 'agents', `planning-${issueId.toLowerCase()}`, 'auto-spawn-on-finalize.json');\n}\n\n/** Read the persisted auto-spawn-on-finalize flag for an issue (false if unset/unreadable). */\nexport function readAutoSpawnOnFinalizeFlag(issueId: string): boolean {\n try {\n const flagFile = autoSpawnOnFinalizeFlagPath(issueId);\n if (!existsSync(flagFile)) return false;\n const flag = JSON.parse(readFileSync(flagFile, 'utf-8')) as { autoSpawnOnFinalize?: unknown };\n return flag.autoSpawnOnFinalize === true;\n } catch {\n return false;\n }\n}\n\n/**\n * Decide whether finalizing planning should auto-spawn the work agent. An\n * explicit request value always wins (`true`/`false`); otherwise fall back to\n * the persisted launch-time --auto-start flag. This keeps every finalize path —\n * the CLI `pan plan finalize`, the dashboard Done button, and host\n * auto-finalize — consistent with how the session was launched.\n */\nexport function resolveAutoSpawnOnFinalize(requestedAutoSpawn: unknown, issueId: string): boolean {\n if (requestedAutoSpawn === true) return true;\n if (requestedAutoSpawn === false) return false;\n return readAutoSpawnOnFinalizeFlag(issueId);\n}\n\nasync function getPackageVersion(): Promise<string> {\n try {\n const pkgPath = resolve(__dirname, '../../../package.json');\n const pkgRaw = await readFile(pkgPath, 'utf-8');\n const pkg = JSON.parse(pkgRaw) as { version: string };\n return pkg.version;\n } catch {\n return '0.0.0';\n }\n}\n\n/**\n * Discover PRD files matching an issue ID from docs/prds directories.\n * Returns list of { path, label } for use in references template.\n */\nasync function discoverPrdFiles(workspacePath: string, issueId: string): Promise<Array<{ path: string; label: string }>> {\n const issueLower = issueId.toLowerCase();\n const searchDirs = [\n join(workspacePath, 'docs', 'prds', 'planned'),\n join(workspacePath, 'docs', 'prds', 'active'),\n // Also check two levels up (worktrees)\n join(workspacePath, '..', '..', 'docs', 'prds', 'planned'),\n join(workspacePath, '..', '..', 'docs', 'prds', 'active'),\n ];\n\n const found: Array<{ path: string; label: string }> = [];\n for (const dir of searchDirs) {\n if (!existsSync(dir)) continue;\n try {\n const files = await readdir(dir);\n for (const file of files) {\n if (file.toLowerCase().includes(issueLower)) {\n found.push({ path: join(dir, file), label: file });\n }\n }\n } catch { /* ignore read errors */ }\n }\n return found;\n}\n\nconst execAsync = promisify(exec);\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface PlanningIssue {\n id: string;\n identifier: string;\n title: string;\n description: string;\n url: string;\n source: 'linear' | 'github' | 'rally';\n comments?: Array<{ author: string; body: string; createdAt: string }>;\n /** Rally artifact type (e.g. \"PortfolioItem/Feature\") */\n artifactType?: string;\n /** Child stories for Rally Features */\n childStories?: Array<{ ref: string; title: string; status: string; description: string }>;\n}\n\n/** Progress event emitted during planning session setup. */\nexport interface PlanningProgress {\n step: number;\n total: number;\n label: string;\n detail: string;\n status: 'active' | 'complete' | 'error';\n}\n\nexport interface SpawnPlanningOptions {\n issue: PlanningIssue;\n workspacePath: string;\n projectPath: string;\n sessionName: string;\n workspaceLocation: 'local' | 'remote';\n startDocker?: boolean;\n shadowMode?: boolean;\n /** Optional model override — if omitted, roles.plan.model is used. */\n model?: string;\n /** Optional harness override (PAN-636). */\n harness?: RuntimeName;\n /** Optional effort level — controls how thorough the planning agent is. */\n effort?: 'low' | 'medium' | 'high';\n /** Non-interactive planning: choose defensible defaults and record inferred choices. */\n auto?: boolean;\n /** Add the adversarial pre-finalize probe pass to the planning prompt. */\n probe?: boolean;\n /** Automatically start the work agent after finalize; stamped by trusted callers only. */\n autoSpawnOnFinalize?: boolean;\n /** Optional callback for streaming progress events to the client. */\n onProgress?: (event: PlanningProgress) => void;\n}\n\nexport interface SpawnPlanningResult {\n success: boolean;\n error?: string;\n}\n\nexport interface PlanningAgentStateInput {\n sessionName: string;\n issueId: string;\n workspacePath: string;\n model: string;\n harness: RuntimeName;\n workspaceLocation: 'local' | 'remote';\n auto?: boolean;\n autoSpawnOnFinalize?: boolean;\n startedAt?: string;\n}\n\nexport function buildPlanningAgentState(input: PlanningAgentStateInput): Record<string, unknown> {\n return {\n id: input.sessionName,\n issueId: input.issueId,\n workspace: input.workspacePath,\n model: input.model,\n status: 'running',\n startedAt: input.startedAt ?? new Date().toISOString(),\n role: 'plan',\n harness: input.harness,\n location: input.workspaceLocation,\n auto: input.auto === true,\n autoSpawnOnFinalize: input.autoSpawnOnFinalize === true,\n };\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nasync function ensureTmuxRunning(): Promise<void> {\n try {\n const exists = await Effect.runPromise(sessionExists('overdeck-init'));\n if (!exists) {\n await Effect.runPromise(createSession('overdeck-init', homedir(), undefined));\n console.log('Started tmux server');\n }\n } catch (startErr) {\n console.error('Failed to start tmux server:', startErr);\n }\n // Strip env vars from tmux global environment that should NOT leak into\n // agent sessions. The tmux server inherits the dashboard's process.env\n // (which includes all of .overdeck.env), but agents should only receive\n // explicitly-passed provider-specific vars via createSession().\n const varsToStrip = [\n 'CLAUDECODE', 'CLAUDE_CODE_ENTRYPOINT',\n 'OPENAI_API_KEY', 'LINEAR_API_KEY', 'GITHUB_TOKEN',\n 'HUME_API_KEY', 'KIMI_API_KEY', 'KIMI_CODING_API_KEY', 'GOOGLE_API_KEY',\n 'MINIMAX_API_KEY', 'ZAI_API_KEY', 'MIMO_API_KEY',\n 'OPENROUTER_API_KEY', 'NOUS_API_KEY', 'DASHSCOPE_API_KEY',\n ];\n for (const envVar of varsToStrip) {\n try {\n await execAsync(`${buildTmuxCommandString(['set-environment', '-g', '-u', envVar])} 2>/dev/null`, { encoding: 'utf-8' });\n } catch {\n // Variable wasn't set — fine\n }\n }\n}\n\n// ─── Planning prompt builder ─────────────────────────────────────────────────\n\nexport async function buildPlanningPrompt(issue: PlanningIssue, workspacePath: string, planningModel?: string, effort?: 'low' | 'medium' | 'high', auto = false, probe = false, memoryContext = ''): Promise<string> {\n const issueLower = issue.identifier.toLowerCase();\n const version = await getPackageVersion();\n const modelAuthor = planningModel ? `agent:${planningModel}` : 'agent:claude-opus-4-6';\n const prdFiles = await discoverPrdFiles(workspacePath, issue.identifier);\n\n // Build comments section\n let commentsSection = '';\n if (issue.comments && issue.comments.length > 0) {\n const commentLines = issue.comments\n .sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime())\n .map(c => {\n const date = c.createdAt.slice(0, 10);\n const body = c.body.length > 2000 ? c.body.slice(0, 2000) + ' [truncated]' : c.body;\n return `### ${c.author} (${date}):\\n${body}`;\n });\n commentsSection = `\\n## Issue Comments\\n\\n**IMPORTANT: Read these comments carefully — they contain context, decisions, and references to previous work.**\\n\\n${commentLines.join('\\n\\n---\\n\\n')}\\n`;\n }\n\n // Check for spec file\n let specSection = '';\n const specSearchDirs = [\n join(workspacePath, 'docs', 'prds', 'active'),\n join(workspacePath, '..', '..', 'docs', 'prds', 'active'),\n ];\n for (const specDir of specSearchDirs) {\n if (!existsSync(specDir)) continue;\n try {\n const files = await readdir(specDir);\n const specFile = files.find(f =>\n f.toLowerCase().includes(issueLower) && f.endsWith('-spec.md')\n );\n if (specFile) {\n const specContent = await readFile(join(specDir, specFile), 'utf-8');\n specSection = `\n## Feature Spec (Human-Written)\n\n**A spec has been written for this feature.** This is your primary input — read it carefully before starting discovery.\n\n**File:** \\`${join(specDir, specFile)}\\`\n\n<spec>\n${specContent}\n</spec>\n\n`;\n break;\n }\n } catch { /* ignore read errors */ }\n }\n\n // Check for polyrepo structure\n const teamPrefix = extractTeamPrefix(issue.identifier);\n const projectConfig = teamPrefix ? findProjectByTeamSync(teamPrefix) : null;\n let projectStructureSection = '';\n if (projectConfig?.workspace?.type === 'polyrepo' && projectConfig.workspace.repos) {\n const repos = projectConfig.workspace.repos;\n projectStructureSection = `\n## Project Structure (Polyrepo)\n\n**IMPORTANT:** This project uses a **polyrepo** structure. The workspace root is NOT a git repository.\nEach subdirectory is a separate git worktree:\n\n| Directory | Purpose |\n|-----------|---------|\n${repos.map((r: any) => `| \\`${r.name}/\\` | Git worktree for ${r.path} |`).join('\\n')}\n\n**Git operations:**\n- Run \\`git status\\`, \\`git log\\`, etc. INSIDE the subdirectories (e.g., \\`cd fe && git status\\`)\n- The workspace root (\\`${workspacePath}\\`) has no \\`.git\\` directory\n- Each subdirectory has its own branch: \\`${repos[0]?.branch_prefix || 'feature/'}${issueLower}\\`\n\n`;\n }\n\n // Build child stories section for Rally Features\n let childStoriesSection = '';\n if (issue.childStories && issue.childStories.length > 0) {\n const storyLines = issue.childStories.map(\n (s) => `- **${s.ref}**: ${s.title} (status: ${s.status})\\n ${s.description || ''}`.trim(),\n );\n childStoriesSection = `\\n## Child Stories\\n\\n**This Rally Feature has ${issue.childStories.length} child story(ies).** Reference these existing stories during planning — do NOT create new ones.\\n\\n${storyLines.join('\\n\\n')}\\n\\n**Cross-story dependencies:** If any child story must be completed before another, encode this as a \\\\\\`blocks\\\\\\` edge in the vBRIEF plan between the corresponding beads. Use \\\\\\`informs\\\\\\` for softer ordering hints.\\n`;\n }\n\n const effortSection = effort && effort !== 'medium' ? `\n## Planning Effort: ${effort === 'high' ? 'High (Deep Analysis)' : 'Low (Quick Planning)'}\n\n${effort === 'high'\n ? `**The user has requested HIGH effort planning.** Be exceptionally thorough:\n- Explore more of the codebase before concluding — check adjacent files, not just the obvious ones\n- Identify edge cases, potential failure modes, and risks\n- Consider multiple implementation approaches and explain tradeoffs\n- Ask more clarifying questions when scope is ambiguous\n- Break down tasks into finer-grained subtasks`\n : `**The user has requested LOW effort planning.** Be concise and fast:\n- Focus on the most critical decisions only\n- Keep the task list tight — 3–5 items max unless truly necessary\n- Skip deep exploration; read only the directly relevant files\n- Ask only essential clarifying questions`\n }\n\n` : '';\n\n const prdReferences = prdFiles.length > 0\n ? `,\\n ${prdFiles.map(p => `{ \"uri\": \"${p.path}\", \"label\": \"${p.label}\", \"type\": \"prd\" }`).join(',\\n ')}`\n : '';\n\n const autoSection = auto ? `\n## Auto Planning Mode\n\nThe user invoked \\`pan plan --auto\\`. Complete planning end-to-end without asking the user questions or waiting for interactive confirmation.\n\n- Do not use AskUserQuestion.\n- When normal planning would ask a question, choose the most defensible default and record it in \\`plan.autoDecisions[]\\` as \\`{ \"summary\": \"...\", \"rationale\": \"...\" }\\`.\n- If the issue body, PRD, or comments list options and name a recommended/default/smallest acceptable option, choose that option and record it in \\`plan.autoDecisions[]\\`; do not ask the operator to choose among already-documented options.\n- Halt only for a genuine contradiction between authoritative inputs, such as the issue body requiring one behavior while a linked PRD requires the opposite. If that happens, write the contradiction into continue.json hazards and stop with a clear escalation message so the dashboard surfaces it.\n- Still produce the same complete vBRIEF and beads via \\`pan plan finalize\\` when no contradiction exists.\n` : '';\n\n const probeSection = probe || effort === 'high' ? `\n## Probe Pass (required before finalize)\n\nAfter drafting the vBRIEF and BEFORE running \\`pan plan finalize\\`, attack your own plan:\n- For each item: what hidden assumption would make this item wrong? Name it or clear it.\n- Which item could produce two very different diffs that both look \"done\"? Tighten its\n ACs or set requiresInspection: true.\n- What input, state, or failure mode does no item handle? Add an item or a NonGoals line.\n- Which edge is missing (output→input you assumed implicitly)? Add it.\n\nRecord every probe finding you acted on in continue.json decisions[] (prefix \"PROBE:\").\nIf the probe pass changes nothing at all, record one decision: \"PROBE: no findings\".\n` : '';\n\n return await Effect.runPromise(renderPrompt({\n name: 'planning',\n vars: {\n ISSUE_ID: issue.identifier,\n ISSUE_ID_LOWER: issueLower,\n ISSUE_TITLE: issue.title,\n ISSUE_URL: issue.url,\n ISSUE_DESCRIPTION: issue.description || 'No description provided',\n VERSION: version,\n MODEL_AUTHOR: modelAuthor,\n COMMENTS_SECTION: commentsSection,\n SPEC_SECTION: specSection,\n CHILD_STORIES_SECTION: childStoriesSection,\n PROJECT_STRUCTURE_SECTION: projectStructureSection,\n EFFORT_SECTION: effortSection,\n AUTO_SECTION: autoSection,\n PROBE_SECTION: probeSection,\n PRD_REFERENCES: prdReferences,\n MEMORY_CONTEXT: memoryContext,\n TLDR_AVAILABLE: existsSync(join(workspacePath, '.venv')),\n },\n }));\n}\n\n/**\n * Write workspace `.pan/context.md` for Rally Features so story work agents can\n * reference feature-level context (child stories, description, URL).\n */\nasync function claudePlanningSystemPromptFiles(workspacePath: string, harness: 'claude-code' | 'ohmypi' | 'codex'): Promise<string[]> {\n const files: string[] = [];\n const contextFile = workspaceContextFile(workspacePath);\n try {\n await access(contextFile);\n files.push(contextFile);\n } catch (error) {\n if (!isNotFound(error)) throw error;\n }\n files.push(await ensureSessionContextBriefingFile());\n\n const behavior = getHarnessBehavior(harness);\n // PAN-1566: Pi/ohmypi also receives the rendered global context layer.\n if (behavior.contextLayerKind === 'pi') {\n const { piGlobalContextFile } = await import('../context-layers/index.js');\n const globalFile = piGlobalContextFile();\n if (existsSync(globalFile)) {\n files.unshift(globalFile);\n }\n }\n // PAN-1574: Codex receives its rendered global context layer (codex-global.md).\n // The per-agent CODEX_HOME/AGENTS.md is set up by initCodexHome at spawn time;\n // this file provides context for the planning session before spawn.\n if (behavior.contextLayerKind === 'codex') {\n const { codexGlobalContextFile } = await import('../context-layers/index.js');\n const globalFile = codexGlobalContextFile();\n if (existsSync(globalFile)) {\n files.unshift(globalFile);\n }\n }\n\n return files;\n}\n\nfunction isNotFound(error: unknown): boolean {\n return typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT';\n}\n\nexport async function writeFeatureContext(workspacePath: string, issue: PlanningIssue): Promise<void> {\n if (!issue.artifactType?.includes('PortfolioItem')) return;\n const childStoriesSection = issue.childStories && issue.childStories.length > 0\n ? issue.childStories.map(s =>\n `### ${s.ref}: ${s.title}\\n- **Status:** ${s.status}\\n- **Description:** ${s.description || '(none)'}`\n ).join('\\n\\n')\n : '_No child stories found._';\n await Effect.runPromise(writeWorkspaceContext(\n workspacePath,\n `# Feature Context: ${issue.identifier}\\n\\n` +\n `**Title:** ${issue.title}\\n\\n` +\n `**URL:** ${issue.url}\\n\\n` +\n `## Description\\n${issue.description || 'No description provided.'}\\n\\n` +\n `## Child Stories\\n${childStoriesSection}\\n\\n` +\n `---\\n` +\n `*This file is auto-generated for story-level workspaces to reference.*\\n`,\n ));\n}\n\nexport async function resolvePlanningSessionHarness(planningModel: string, explicit?: RuntimeName): Promise<RuntimeName> {\n return resolveHarness({ explicit, role: 'plan', model: planningModel });\n}\n\n// ─── Main spawn function ─────────────────────────────────────────────────────\n\n/**\n * Spawn a planning agent session in the background.\n *\n * Creates workspace (if needed), writes planning prompt, and spawns Claude Code\n * in a tmux session. The agent state directory at ~/.overdeck/agents/<sessionName>/\n * must already exist with a preliminary state.json (status: 'starting').\n *\n * This function is designed to run as fire-and-forget after the API response\n * is sent. It updates agent state to 'running' on success or 'failed' on error.\n */\nexport async function spawnPlanningSession(opts: SpawnPlanningOptions): Promise<SpawnPlanningResult> {\n const { issue, workspacePath, projectPath, sessionName, workspaceLocation, startDocker, shadowMode, model: modelOverride, effort, auto, probe, autoSpawnOnFinalize, onProgress } = opts;\n const issueLower = issue.identifier.toLowerCase();\n const agentStateDir = join(homedir(), '.overdeck', 'agents', sessionName);\n\n const TOTAL_STEPS = 5;\n const progress = (step: number, label: string, detail: string, status: 'active' | 'complete' | 'error' = 'active') => {\n onProgress?.({ step, total: TOTAL_STEPS, label, detail, status });\n };\n\n try {\n console.log(`[start-planning] Background setup starting for ${issue.identifier}`);\n\n // ── Step 1: Create workspace if needed ─────────────────────────────────\n progress(1, 'Creating workspace', `${issueLower} on ${projectPath.split('/').pop() || 'project'}`);\n\n let workspaceCreated = false;\n if (existsSync(workspacePath)) {\n const files = await readdir(workspacePath);\n workspaceCreated = !files.every((f: string) => f === '.pan' || f === '.beads');\n }\n\n if (!workspaceCreated) {\n try {\n const projectConfig = findProjectByPathSync(projectPath) || findProjectByTeamSync(extractTeamPrefix(issue.identifier) || '');\n if (projectConfig?.workspace) {\n // Use library directly for real-time progress streaming\n console.log(`[start-planning] Creating workspace via library for ${issue.identifier}, projectConfig=${projectConfig.name}`);\n const wsResult = await Effect.runPromise(createWorkspace({\n projectConfig,\n featureName: issueLower,\n startDocker,\n onProgress: (event) => {\n console.log(`[start-planning] Workspace progress: ${event.label} — ${event.detail} [${event.status}]`);\n // Forward workspace sub-step progress as step 1 sub-step events\n progress(1, event.label, event.detail, event.status);\n },\n }));\n console.log(`[start-planning] Workspace result: success=${wsResult.success}, steps=${wsResult.steps.length}, errors=${wsResult.errors.length}`);\n if (wsResult.errors.length > 0) {\n console.error(`[start-planning] Workspace errors:`, wsResult.errors);\n }\n if (!wsResult.success) {\n throw new Error(wsResult.errors.join('; '));\n }\n } else {\n // Fallback: use CLI for projects without workspace config\n const dockerFlag = startDocker ? ' --docker' : '';\n const locationFlag = workspaceLocation === 'remote' ? ' --remote' : ' --local';\n const createCmd = `pan workspace create ${issue.identifier}${locationFlag}${dockerFlag}`;\n console.log(`[start-planning] Creating workspace via CLI: ${createCmd}`);\n await execAsync(createCmd, {\n cwd: projectPath,\n encoding: 'utf-8',\n timeout: startDocker ? 300000 : 120000,\n });\n }\n workspaceCreated = true;\n console.log(`[start-planning] Workspace created successfully`);\n } catch (err: any) {\n // CRITICAL: workspace MUST exist for local planning. If creation failed,\n // abort — never fall back to project root, which causes beads and planning\n // artifacts to land in the wrong place (PAN-358).\n const errorMsg = `Workspace creation failed: ${err.message}`;\n console.error(`[start-planning] ABORTING: ${errorMsg}`);\n progress(1, 'Creating workspace', errorMsg, 'error');\n const existingErrState = getAgentStateSync(sessionName);\n if (existingErrState) saveAgentStateSync({ ...existingErrState, status: 'error' });\n return { success: false, error: errorMsg };\n }\n }\n\n progress(1, 'Creating workspace', workspaceCreated ? 'Workspace ready' : 'Already exists', 'complete');\n\n // ── Step 2: Prepare planning environment ──────────────────────────────\n progress(2, 'Preparing planning environment', '.pan/ workspace artifacts');\n\n // Kill existing planning session if any\n await Effect.runPromise(killSession(sessionName)).catch(() => {});\n\n const workspacePanPaths = await Effect.runPromise(ensureWorkspacePanDir(workspacePath));\n await Promise.all(\n ['transcripts', 'discussions', 'notes'].map((subdir) =>\n mkdir(join(workspacePanPaths.panDir, subdir), { recursive: true }),\n ),\n );\n\n if (existsSync(workspacePanPaths.continuePath)) {\n console.log('[start-planning] Clearing stale .pan/continue.json');\n await rm(workspacePanPaths.continuePath, { force: true });\n }\n\n // Initialize Shadow Engineering if enabled\n if (shadowMode) {\n const inferencePath = join(workspacePanPaths.panDir, 'INFERENCE.md');\n if (!existsSync(inferencePath)) {\n await writeFile(inferencePath,\n `# Inference Document - ${issue.identifier.toUpperCase()}\\n\\n*This document is maintained by the Shadow Engineering Monitoring Agent.*\\n\\n## Status\\n\\nAwaiting initial artifact analysis.\\n`,\n 'utf-8',\n );\n console.log(`[start-planning] Shadow Engineering: Initialized INFERENCE.md`);\n }\n }\n\n progress(2, 'Preparing planning environment', 'Environment ready', 'complete');\n\n // ── Step 3: Load specs & PRDs ────────────────────────────────────────\n progress(3, 'Loading specs & PRDs', `Searching for ${issue.identifier} specs`);\n\n // Determine planning model — explicit override takes precedence over role routing.\n // PAN-1048 R1: resolveModel must NOT be wrapped in try/catch with a silent\n // fallback. If the user's roles.plan.model points at a missing workhorse\n // slot, an unknown model id, or a chained reference we can't dereference,\n // we want spawn to fail loudly with the precise error so the operator can\n // fix their config. The previous fallback to claude-opus-4-7 hid those\n // bugs and silently overrode their explicit configuration.\n let settingsModel: string;\n let modelSource: string;\n if (modelOverride) {\n settingsModel = modelOverride;\n modelSource = 'modelOverride';\n } else {\n settingsModel = resolveModel('plan', undefined, loadConfigSync().config, `plan:${issue.identifier}`);\n modelSource = 'roles.plan.model';\n console.log(`[start-planning] Model resolution for role=plan: model=${settingsModel} source=${modelSource}`);\n }\n const planningModel = modelOverride || settingsModel;\n const effectiveHarness = await resolvePlanningSessionHarness(planningModel, opts.harness);\n console.log(`[start-planning] Final planning model: ${planningModel} (override=${modelOverride || '(none)'} settings=${settingsModel} source=${modelSource}) harness=${effectiveHarness}`);\n\n // Discover and copy PRD files to workspace\n const prdFiles = await discoverPrdFiles(workspacePath, issue.identifier);\n if (prdFiles.length > 0) {\n const prdDestPath = join(workspacePanPaths.panDir, 'prd.md');\n if (!existsSync(prdDestPath)) {\n try {\n const prdContent = await readFile(prdFiles[0].path, 'utf-8');\n await writeFile(prdDestPath, prdContent, 'utf-8');\n console.log(`[start-planning] Copied PRD to ${prdDestPath} from ${prdFiles[0].path}`);\n } catch (err: any) {\n console.warn(`[start-planning] Could not copy PRD: ${err.message}`);\n }\n }\n }\n\n progress(3, 'Loading specs & PRDs', prdFiles.length > 0 ? prdFiles[0].label : 'No PRDs found', 'complete');\n\n // ── Step 4: Configure agent ─────────────────────────────────────────\n progress(4, 'Configuring agent', planningModel);\n\n let planningPrompt = await buildPlanningPrompt(issue, workspacePath, planningModel, effort, auto === true, probe === true);\n const memoryContext = await retrieveSpawnTimeMemoryContext({\n prompt: planningPrompt,\n issueId: issue.identifier,\n workspace: workspacePath,\n agentId: sessionName,\n role: 'plan',\n harness: effectiveHarness,\n });\n if (memoryContext) {\n planningPrompt = await buildPlanningPrompt(issue, workspacePath, planningModel, effort, auto === true, probe === true, memoryContext);\n }\n\n // Capture planning prompt in per-issue record (PAN-1919: replaces workspace continue.json).\n const recordProject = resolveProjectForIssue(issue.identifier) ?? getProjectConfigFromWorkspacePath(workspacePath);\n appendSessionEntrySync(recordProject, issue.identifier, {\n reason: 'planning',\n content: planningPrompt,\n note: `Planning session started for ${issue.identifier}: ${issue.title}`,\n timestamp: new Date().toISOString(),\n });\n\n await writeFeatureContext(workspacePath, issue);\n\n // PAN-1048: emit 'claude --agent roles/plan.md --name <sessionName>'.\n // PAN-636: thread harness through so a planning kickoff with --harness pi\n // produces a `pi --mode rpc --model <id>` line and skips the --agent flag\n // (Pi has no agent-definition system).\n const cmdWithArgs = await getAgentRuntimeBaseCommand(planningModel, sessionName, roleAgentDefinitionPath('plan'), effectiveHarness);\n const behavior = getHarnessBehavior(effectiveHarness);\n const piLauncherFields = behavior.usesRpcFifo\n ? await getOhmypiLauncherFields(sessionName, planningModel)\n : {};\n const codexLauncherFields = behavior.usesCodexHome\n ? getCodexLauncherFields(sessionName, planningModel, workspacePath)\n : {};\n\n const providerExports = await getProviderExportsForModel(planningModel);\n\n // ── Write launcher script ──────────────────────────────────────────────\n const recordFilePath = getIssueRecordPath(recordProject, issue.identifier);\n const initMessage = `Please read the \\`content\\` field of the \\`planning\\` sessionHistory entry in ${recordFilePath} and begin the planning session for ${issue.identifier}: ${issue.title}`;\n const promptFile = join(agentStateDir, 'init-prompt.txt');\n const launcherScript = join(agentStateDir, 'launcher.sh');\n await writeFile(promptFile, initMessage);\n await writeFile(\n launcherScript,\n generateLauncherScriptSync({\n role: 'plan',\n harness: effectiveHarness,\n workingDir: workspacePath,\n setTerminalEnv: true,\n overdeckEnv: { agentId: sessionName, issueId: issue.identifier, sessionType: 'plan' },\n providerExports,\n promptFile,\n baseCommand: cmdWithArgs,\n appendSystemPromptFiles: await claudePlanningSystemPromptFiles(workspacePath, effectiveHarness),\n trapHup: true,\n debugLog: '/tmp/pan-launcher-debug.log',\n keepAlive: true,\n ...piLauncherFields,\n ...codexLauncherFields,\n }),\n { mode: 0o755 },\n );\n\n progress(4, 'Configuring agent', `${planningModel} — prompt & launcher ready`, 'complete');\n\n // ── Step 5: Launch planning session ───────────────────────────────────\n progress(5, 'Launching planning session', sessionName);\n\n console.log(`[claude-invoke] purpose=planning-agent | model=${planningModel} | source=spawn-planning-session.ts | session=${sessionName} | command=\"bash '${launcherScript}'\"`);\n\n await ensureTmuxRunning();\n await Effect.runPromise(createSession(sessionName, workspacePath, `bash '${launcherScript}'`, {\n env: {\n ...BLANKED_PROVIDER_ENV,\n TERM: 'xterm-256color',\n },\n }));\n // Protect the session from being destroyed when clients disconnect.\n // When the dashboard's WebSocket terminal attaches and then detaches,\n // tmux can destroy the session if destroy-unattached is on.\n await Effect.runPromise(setOption(sessionName, 'destroy-unattached', 'off'));\n await Effect.runPromise(setOption(exactPaneTarget(sessionName), 'remain-on-exit', 'on'));\n\n // NOTE: No pre-resize of tmux window here. The WebSocket terminal handler\n // defers PTY spawn until the client sends its actual dimensions, so the\n // tmux window will be sized correctly from the start. Pre-resizing to\n // 200×50 caused a dimension cascade (200→120→actual) that garbled output.\n // See PAN-417 for the full forensic timeline.\n\n // ── Update agent state to running ──────────────────────────────────────\n // PAN-1048 R2: legacy `runtime` field removed; AgentState carries `harness`.\n {\n const baseState = getAgentStateSync(sessionName);\n saveAgentStateSync({\n ...(baseState ?? { id: sessionName, issueId: issue.identifier, workspace: workspacePath, startedAt: new Date().toISOString() }),\n model: planningModel,\n status: 'running',\n role: 'plan',\n harness: effectiveHarness,\n auto: auto === true,\n });\n if (autoSpawnOnFinalize) {\n await writeFile(\n join(agentStateDir, 'auto-spawn-on-finalize.json'),\n JSON.stringify({ autoSpawnOnFinalize: true }),\n );\n }\n }\n\n if (behavior.usesCodexHome) {\n const delivery = await deliverInitialPromptWithRetry(\n sessionName,\n initMessage,\n 'spawnPlanningSession:initial-prompt',\n );\n if (!delivery.ok) {\n const errorMsg = `Codex planning kickoff prompt delivery failed: ${delivery.failure ?? 'unknown error'}`;\n console.error(`[start-planning] ${errorMsg}`);\n await Effect.runPromise(killSession(sessionName)).catch(() => {});\n const existingErrState = getAgentStateSync(sessionName);\n if (existingErrState) saveAgentStateSync({ ...existingErrState, status: 'error' });\n progress(5, 'Launching planning session', errorMsg, 'error');\n return { success: false, error: errorMsg };\n }\n }\n\n progress(5, 'Launching planning session', 'Agent running', 'complete');\n\n console.log(`[start-planning] Started local planning agent ${sessionName}`);\n return { success: true };\n\n } catch (err: any) {\n console.error(`[start-planning] Agent spawn failed for ${issue.identifier}:`, err);\n try {\n const existingCatchState = getAgentStateSync(sessionName);\n if (existingCatchState) saveAgentStateSync({ ...existingCatchState, status: 'error' });\n } catch { /* ignore state write errors */ }\n return { success: false, error: err.message };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"spawn-planning-session-CXc0NkwV.js","names":[],"sources":["../../src/lib/planning/spawn-planning-session.ts"],"sourcesContent":["/**\n * Spawn Planning Session — background workspace + agent setup\n *\n * Extracted from the old Express /api/issues/:id/start-planning handler.\n * Creates workspace, writes planning prompt, spawns Claude Code in tmux.\n * Used by both the dashboard route and CLI.\n *\n * This runs as a background task after the API responds — the UI shows\n * \"Waiting for session to start...\" until the tmux session is ready.\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { access, mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { Effect } from 'effect';\nimport { extractTeamPrefix, findProjectByTeamSync, findProjectByPathSync } from '../projects.js';\nimport {\n sessionExists,\n createSession,\n killSession,\n setOption,\n exactPaneTarget,\n buildTmuxCommandString,\n} from '../tmux.js';\nimport { createWorkspace } from '../workspace-manager.js';\nimport { renderPrompt } from '../cloister/prompts.js';\nimport { deliverInitialPromptWithRetry, getAgentRuntimeBaseCommand, getProviderExportsForModel, retrieveSpawnTimeMemoryContext, roleAgentDefinitionPath, saveAgentStateSync, getAgentStateSync } from '../agents.js';\nimport { getCodexLauncherFields, getOhmypiLauncherFields } from '../agents/runtime-command.js';\nimport { loadConfigSync, resolveModel } from '../config-yaml.js';\nimport { resolveHarness } from '../harness-resolve.js';\nimport { getHarnessBehavior } from '../runtimes/behavior.js';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport { generateLauncherScriptSync } from '../launcher-generator.js';\nimport { BLANKED_PROVIDER_ENV } from '../child-env.js';\nimport { ensureWorkspacePanDir, getWorkspacePanPaths, writeWorkspaceContext } from '../pan-dir/index.js';\nimport {\n appendSessionEntrySync,\n getIssueRecordPath,\n getProjectConfigFromWorkspacePath,\n resolveProjectForIssue,\n} from '../pan-dir/record.js';\nimport { workspaceContextFile } from '../context-layers/layers.js';\nimport { ensureSessionContextBriefingFile } from '../briefing-freshness.js';\n\nconst __dirname = fileURLToPath(new URL('.', import.meta.url));\n\n/**\n * Path to the per-issue \"auto-spawn the work agent on planning finalize\" flag,\n * written at planning spawn when the operator launched with --auto-start. This\n * is the single source of the launch-time auto-start intent; every finalize\n * path (CLI `pan plan finalize`, the dashboard Done button, host auto-finalize)\n * reads it so the intent is honored no matter how planning is completed.\n */\nexport function autoSpawnOnFinalizeFlagPath(issueId: string): string {\n const overdeckHome = process.env['OVERDECK_HOME'] ?? join(homedir(), '.overdeck');\n return join(overdeckHome, 'agents', `planning-${issueId.toLowerCase()}`, 'auto-spawn-on-finalize.json');\n}\n\n/** Read the persisted auto-spawn-on-finalize flag for an issue (false if unset/unreadable). */\nexport function readAutoSpawnOnFinalizeFlag(issueId: string): boolean {\n try {\n const flagFile = autoSpawnOnFinalizeFlagPath(issueId);\n if (!existsSync(flagFile)) return false;\n const flag = JSON.parse(readFileSync(flagFile, 'utf-8')) as { autoSpawnOnFinalize?: unknown };\n return flag.autoSpawnOnFinalize === true;\n } catch {\n return false;\n }\n}\n\n/**\n * Decide whether finalizing planning should auto-spawn the work agent. An\n * explicit request value always wins (`true`/`false`); otherwise fall back to\n * the persisted launch-time --auto-start flag. This keeps every finalize path —\n * the CLI `pan plan finalize`, the dashboard Done button, and host\n * auto-finalize — consistent with how the session was launched.\n */\nexport function resolveAutoSpawnOnFinalize(requestedAutoSpawn: unknown, issueId: string): boolean {\n if (requestedAutoSpawn === true) return true;\n if (requestedAutoSpawn === false) return false;\n return readAutoSpawnOnFinalizeFlag(issueId);\n}\n\nasync function getPackageVersion(): Promise<string> {\n try {\n const pkgPath = resolve(__dirname, '../../../package.json');\n const pkgRaw = await readFile(pkgPath, 'utf-8');\n const pkg = JSON.parse(pkgRaw) as { version: string };\n return pkg.version;\n } catch {\n return '0.0.0';\n }\n}\n\n/**\n * Discover PRD files matching an issue ID from docs/prds directories.\n * Returns list of { path, label } for use in references template.\n */\nasync function discoverPrdFiles(workspacePath: string, issueId: string): Promise<Array<{ path: string; label: string }>> {\n const issueLower = issueId.toLowerCase();\n const searchDirs = [\n join(workspacePath, 'docs', 'prds', 'planned'),\n join(workspacePath, 'docs', 'prds', 'active'),\n // Also check two levels up (worktrees)\n join(workspacePath, '..', '..', 'docs', 'prds', 'planned'),\n join(workspacePath, '..', '..', 'docs', 'prds', 'active'),\n ];\n\n const found: Array<{ path: string; label: string }> = [];\n for (const dir of searchDirs) {\n if (!existsSync(dir)) continue;\n try {\n const files = await readdir(dir);\n for (const file of files) {\n if (file.toLowerCase().includes(issueLower)) {\n found.push({ path: join(dir, file), label: file });\n }\n }\n } catch { /* ignore read errors */ }\n }\n return found;\n}\n\nconst execAsync = promisify(exec);\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface PlanningIssue {\n id: string;\n identifier: string;\n title: string;\n description: string;\n url: string;\n source: 'linear' | 'github' | 'rally';\n comments?: Array<{ author: string; body: string; createdAt: string }>;\n /** Rally artifact type (e.g. \"PortfolioItem/Feature\") */\n artifactType?: string;\n /** Child stories for Rally Features */\n childStories?: Array<{ ref: string; title: string; status: string; description: string }>;\n}\n\n/** Progress event emitted during planning session setup. */\nexport interface PlanningProgress {\n step: number;\n total: number;\n label: string;\n detail: string;\n status: 'active' | 'complete' | 'error';\n}\n\nexport interface SpawnPlanningOptions {\n issue: PlanningIssue;\n workspacePath: string;\n projectPath: string;\n sessionName: string;\n workspaceLocation: 'local' | 'remote';\n startDocker?: boolean;\n shadowMode?: boolean;\n /** Optional model override — if omitted, roles.plan.model is used. */\n model?: string;\n /** Optional harness override (PAN-636). */\n harness?: RuntimeName;\n /** Optional effort level — controls how thorough the planning agent is. */\n effort?: 'low' | 'medium' | 'high';\n /** Non-interactive planning: choose defensible defaults and record inferred choices. */\n auto?: boolean;\n /** Add the adversarial pre-finalize probe pass to the planning prompt. */\n probe?: boolean;\n /** Automatically start the work agent after finalize; stamped by trusted callers only. */\n autoSpawnOnFinalize?: boolean;\n /** Optional callback for streaming progress events to the client. */\n onProgress?: (event: PlanningProgress) => void;\n}\n\nexport interface SpawnPlanningResult {\n success: boolean;\n error?: string;\n}\n\nexport interface PlanningAgentStateInput {\n sessionName: string;\n issueId: string;\n workspacePath: string;\n model: string;\n harness: RuntimeName;\n workspaceLocation: 'local' | 'remote';\n auto?: boolean;\n autoSpawnOnFinalize?: boolean;\n startedAt?: string;\n}\n\nexport function buildPlanningAgentState(input: PlanningAgentStateInput): Record<string, unknown> {\n return {\n id: input.sessionName,\n issueId: input.issueId,\n workspace: input.workspacePath,\n model: input.model,\n status: 'running',\n startedAt: input.startedAt ?? new Date().toISOString(),\n role: 'plan',\n harness: input.harness,\n location: input.workspaceLocation,\n auto: input.auto === true,\n autoSpawnOnFinalize: input.autoSpawnOnFinalize === true,\n };\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nasync function ensureTmuxRunning(): Promise<void> {\n try {\n const exists = await Effect.runPromise(sessionExists('overdeck-init'));\n if (!exists) {\n await Effect.runPromise(createSession('overdeck-init', homedir(), undefined));\n console.log('Started tmux server');\n }\n } catch (startErr) {\n console.error('Failed to start tmux server:', startErr);\n }\n // Strip env vars from tmux global environment that should NOT leak into\n // agent sessions. The tmux server inherits the dashboard's process.env\n // (which includes all of .overdeck.env), but agents should only receive\n // explicitly-passed provider-specific vars via createSession().\n const varsToStrip = [\n 'CLAUDECODE', 'CLAUDE_CODE_ENTRYPOINT',\n 'OPENAI_API_KEY', 'LINEAR_API_KEY', 'GITHUB_TOKEN',\n 'HUME_API_KEY', 'KIMI_API_KEY', 'KIMI_CODING_API_KEY', 'GOOGLE_API_KEY',\n 'MINIMAX_API_KEY', 'ZAI_API_KEY', 'MIMO_API_KEY',\n 'OPENROUTER_API_KEY', 'NOUS_API_KEY', 'DASHSCOPE_API_KEY',\n ];\n for (const envVar of varsToStrip) {\n try {\n await execAsync(`${buildTmuxCommandString(['set-environment', '-g', '-u', envVar])} 2>/dev/null`, { encoding: 'utf-8' });\n } catch {\n // Variable wasn't set — fine\n }\n }\n}\n\n// ─── Planning prompt builder ─────────────────────────────────────────────────\n\nexport async function buildPlanningPrompt(issue: PlanningIssue, workspacePath: string, planningModel?: string, effort?: 'low' | 'medium' | 'high', auto = false, probe = false, memoryContext = ''): Promise<string> {\n const issueLower = issue.identifier.toLowerCase();\n const version = await getPackageVersion();\n const modelAuthor = planningModel ? `agent:${planningModel}` : 'agent:claude-opus-4-6';\n const prdFiles = await discoverPrdFiles(workspacePath, issue.identifier);\n\n // Build comments section\n let commentsSection = '';\n if (issue.comments && issue.comments.length > 0) {\n const commentLines = issue.comments\n .sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime())\n .map(c => {\n const date = c.createdAt.slice(0, 10);\n const body = c.body.length > 2000 ? c.body.slice(0, 2000) + ' [truncated]' : c.body;\n return `### ${c.author} (${date}):\\n${body}`;\n });\n commentsSection = `\\n## Issue Comments\\n\\n**IMPORTANT: Read these comments carefully — they contain context, decisions, and references to previous work.**\\n\\n${commentLines.join('\\n\\n---\\n\\n')}\\n`;\n }\n\n // Check for spec file\n let specSection = '';\n const specSearchDirs = [\n join(workspacePath, 'docs', 'prds', 'active'),\n join(workspacePath, '..', '..', 'docs', 'prds', 'active'),\n ];\n for (const specDir of specSearchDirs) {\n if (!existsSync(specDir)) continue;\n try {\n const files = await readdir(specDir);\n const specFile = files.find(f =>\n f.toLowerCase().includes(issueLower) && f.endsWith('-spec.md')\n );\n if (specFile) {\n const specContent = await readFile(join(specDir, specFile), 'utf-8');\n specSection = `\n## Feature Spec (Human-Written)\n\n**A spec has been written for this feature.** This is your primary input — read it carefully before starting discovery.\n\n**File:** \\`${join(specDir, specFile)}\\`\n\n<spec>\n${specContent}\n</spec>\n\n`;\n break;\n }\n } catch { /* ignore read errors */ }\n }\n\n // Check for polyrepo structure\n const teamPrefix = extractTeamPrefix(issue.identifier);\n const projectConfig = teamPrefix ? findProjectByTeamSync(teamPrefix) : null;\n let projectStructureSection = '';\n if (projectConfig?.workspace?.type === 'polyrepo' && projectConfig.workspace.repos) {\n const repos = projectConfig.workspace.repos;\n projectStructureSection = `\n## Project Structure (Polyrepo)\n\n**IMPORTANT:** This project uses a **polyrepo** structure. The workspace root is NOT a git repository.\nEach subdirectory is a separate git worktree:\n\n| Directory | Purpose |\n|-----------|---------|\n${repos.map((r: any) => `| \\`${r.name}/\\` | Git worktree for ${r.path} |`).join('\\n')}\n\n**Git operations:**\n- Run \\`git status\\`, \\`git log\\`, etc. INSIDE the subdirectories (e.g., \\`cd fe && git status\\`)\n- The workspace root (\\`${workspacePath}\\`) has no \\`.git\\` directory\n- Each subdirectory has its own branch: \\`${repos[0]?.branch_prefix || 'feature/'}${issueLower}\\`\n\n`;\n }\n\n // Build child stories section for Rally Features\n let childStoriesSection = '';\n if (issue.childStories && issue.childStories.length > 0) {\n const storyLines = issue.childStories.map(\n (s) => `- **${s.ref}**: ${s.title} (status: ${s.status})\\n ${s.description || ''}`.trim(),\n );\n childStoriesSection = `\\n## Child Stories\\n\\n**This Rally Feature has ${issue.childStories.length} child story(ies).** Reference these existing stories during planning — do NOT create new ones.\\n\\n${storyLines.join('\\n\\n')}\\n\\n**Cross-story dependencies:** If any child story must be completed before another, encode this as a \\\\\\`blocks\\\\\\` edge in the vBRIEF plan between the corresponding beads. Use \\\\\\`informs\\\\\\` for softer ordering hints.\\n`;\n }\n\n const effortSection = effort && effort !== 'medium' ? `\n## Planning Effort: ${effort === 'high' ? 'High (Deep Analysis)' : 'Low (Quick Planning)'}\n\n${effort === 'high'\n ? `**The user has requested HIGH effort planning.** Be exceptionally thorough:\n- Explore more of the codebase before concluding — check adjacent files, not just the obvious ones\n- Identify edge cases, potential failure modes, and risks\n- Consider multiple implementation approaches and explain tradeoffs\n- Ask more clarifying questions when scope is ambiguous\n- Break down tasks into finer-grained subtasks`\n : `**The user has requested LOW effort planning.** Be concise and fast:\n- Focus on the most critical decisions only\n- Keep the task list tight — 3–5 items max unless truly necessary\n- Skip deep exploration; read only the directly relevant files\n- Ask only essential clarifying questions`\n }\n\n` : '';\n\n const prdReferences = prdFiles.length > 0\n ? `,\\n ${prdFiles.map(p => `{ \"uri\": \"${p.path}\", \"label\": \"${p.label}\", \"type\": \"prd\" }`).join(',\\n ')}`\n : '';\n\n const autoSection = auto ? `\n## Auto Planning Mode\n\nThe user invoked \\`pan plan --auto\\`. Complete planning end-to-end without asking the user questions or waiting for interactive confirmation.\n\n- Do not use AskUserQuestion.\n- When normal planning would ask a question, choose the most defensible default and record it in \\`plan.autoDecisions[]\\` as \\`{ \"summary\": \"...\", \"rationale\": \"...\" }\\`.\n- If the issue body, PRD, or comments list options and name a recommended/default/smallest acceptable option, choose that option and record it in \\`plan.autoDecisions[]\\`; do not ask the operator to choose among already-documented options.\n- Halt only for a genuine contradiction between authoritative inputs, such as the issue body requiring one behavior while a linked PRD requires the opposite. If that happens, write the contradiction into continue.json hazards and stop with a clear escalation message so the dashboard surfaces it.\n- Still produce the same complete vBRIEF and beads via \\`pan plan finalize\\` when no contradiction exists.\n` : '';\n\n const probeSection = probe || effort === 'high' ? `\n## Probe Pass (required before finalize)\n\nAfter drafting the vBRIEF and BEFORE running \\`pan plan finalize\\`, attack your own plan:\n- For each item: what hidden assumption would make this item wrong? Name it or clear it.\n- Which item could produce two very different diffs that both look \"done\"? Tighten its\n ACs or set requiresInspection: true.\n- What input, state, or failure mode does no item handle? Add an item or a NonGoals line.\n- Which edge is missing (output→input you assumed implicitly)? Add it.\n\nRecord every probe finding you acted on in continue.json decisions[] (prefix \"PROBE:\").\nIf the probe pass changes nothing at all, record one decision: \"PROBE: no findings\".\n` : '';\n\n return await Effect.runPromise(renderPrompt({\n name: 'planning',\n vars: {\n ISSUE_ID: issue.identifier,\n ISSUE_ID_LOWER: issueLower,\n ISSUE_TITLE: issue.title,\n ISSUE_URL: issue.url,\n ISSUE_DESCRIPTION: issue.description || 'No description provided',\n VERSION: version,\n MODEL_AUTHOR: modelAuthor,\n COMMENTS_SECTION: commentsSection,\n SPEC_SECTION: specSection,\n CHILD_STORIES_SECTION: childStoriesSection,\n PROJECT_STRUCTURE_SECTION: projectStructureSection,\n EFFORT_SECTION: effortSection,\n AUTO_SECTION: autoSection,\n PROBE_SECTION: probeSection,\n PRD_REFERENCES: prdReferences,\n MEMORY_CONTEXT: memoryContext,\n TLDR_AVAILABLE: existsSync(join(workspacePath, '.venv')),\n },\n }));\n}\n\n/**\n * Write workspace `.pan/context.md` for Rally Features so story work agents can\n * reference feature-level context (child stories, description, URL).\n */\nasync function claudePlanningSystemPromptFiles(workspacePath: string, harness: 'claude-code' | 'ohmypi' | 'codex'): Promise<string[]> {\n const files: string[] = [];\n const contextFile = workspaceContextFile(workspacePath);\n try {\n await access(contextFile);\n files.push(contextFile);\n } catch (error) {\n if (!isNotFound(error)) throw error;\n }\n files.push(await ensureSessionContextBriefingFile());\n\n const behavior = getHarnessBehavior(harness);\n // PAN-1566: Pi/ohmypi also receives the rendered global context layer.\n if (behavior.contextLayerKind === 'pi') {\n const { piGlobalContextFile } = await import('../context-layers/index.js');\n const globalFile = piGlobalContextFile();\n if (existsSync(globalFile)) {\n files.unshift(globalFile);\n }\n }\n // PAN-1574: Codex receives its rendered global context layer (codex-global.md).\n // The per-agent CODEX_HOME/AGENTS.md is set up by initCodexHome at spawn time;\n // this file provides context for the planning session before spawn.\n if (behavior.contextLayerKind === 'codex') {\n const { codexGlobalContextFile } = await import('../context-layers/index.js');\n const globalFile = codexGlobalContextFile();\n if (existsSync(globalFile)) {\n files.unshift(globalFile);\n }\n }\n\n return files;\n}\n\nfunction isNotFound(error: unknown): boolean {\n return typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT';\n}\n\nexport async function writeFeatureContext(workspacePath: string, issue: PlanningIssue): Promise<void> {\n if (!issue.artifactType?.includes('PortfolioItem')) return;\n const childStoriesSection = issue.childStories && issue.childStories.length > 0\n ? issue.childStories.map(s =>\n `### ${s.ref}: ${s.title}\\n- **Status:** ${s.status}\\n- **Description:** ${s.description || '(none)'}`\n ).join('\\n\\n')\n : '_No child stories found._';\n await Effect.runPromise(writeWorkspaceContext(\n workspacePath,\n `# Feature Context: ${issue.identifier}\\n\\n` +\n `**Title:** ${issue.title}\\n\\n` +\n `**URL:** ${issue.url}\\n\\n` +\n `## Description\\n${issue.description || 'No description provided.'}\\n\\n` +\n `## Child Stories\\n${childStoriesSection}\\n\\n` +\n `---\\n` +\n `*This file is auto-generated for story-level workspaces to reference.*\\n`,\n ));\n}\n\nexport async function resolvePlanningSessionHarness(planningModel: string, explicit?: RuntimeName): Promise<RuntimeName> {\n return resolveHarness({ explicit, role: 'plan', model: planningModel });\n}\n\n// ─── Main spawn function ─────────────────────────────────────────────────────\n\n/**\n * Spawn a planning agent session in the background.\n *\n * Creates workspace (if needed), writes planning prompt, and spawns Claude Code\n * in a tmux session. The agent state directory at ~/.overdeck/agents/<sessionName>/\n * must already exist with a preliminary state.json (status: 'starting').\n *\n * This function is designed to run as fire-and-forget after the API response\n * is sent. It updates agent state to 'running' on success or 'failed' on error.\n */\nexport async function spawnPlanningSession(opts: SpawnPlanningOptions): Promise<SpawnPlanningResult> {\n const { issue, workspacePath, projectPath, sessionName, workspaceLocation, startDocker, shadowMode, model: modelOverride, effort, auto, probe, autoSpawnOnFinalize, onProgress } = opts;\n const issueLower = issue.identifier.toLowerCase();\n const agentStateDir = join(homedir(), '.overdeck', 'agents', sessionName);\n\n const TOTAL_STEPS = 5;\n const progress = (step: number, label: string, detail: string, status: 'active' | 'complete' | 'error' = 'active') => {\n onProgress?.({ step, total: TOTAL_STEPS, label, detail, status });\n };\n\n try {\n console.log(`[start-planning] Background setup starting for ${issue.identifier}`);\n\n // ── Step 1: Create workspace if needed ─────────────────────────────────\n progress(1, 'Creating workspace', `${issueLower} on ${projectPath.split('/').pop() || 'project'}`);\n\n let workspaceCreated = false;\n if (existsSync(workspacePath)) {\n const files = await readdir(workspacePath);\n workspaceCreated = !files.every((f: string) => f === '.pan' || f === '.beads');\n }\n\n if (!workspaceCreated) {\n try {\n const projectConfig = findProjectByPathSync(projectPath) || findProjectByTeamSync(extractTeamPrefix(issue.identifier) || '');\n if (projectConfig?.workspace) {\n // Use library directly for real-time progress streaming\n console.log(`[start-planning] Creating workspace via library for ${issue.identifier}, projectConfig=${projectConfig.name}`);\n const wsResult = await Effect.runPromise(createWorkspace({\n projectConfig,\n featureName: issueLower,\n startDocker,\n onProgress: (event) => {\n console.log(`[start-planning] Workspace progress: ${event.label} — ${event.detail} [${event.status}]`);\n // Forward workspace sub-step progress as step 1 sub-step events\n progress(1, event.label, event.detail, event.status);\n },\n }));\n console.log(`[start-planning] Workspace result: success=${wsResult.success}, steps=${wsResult.steps.length}, errors=${wsResult.errors.length}`);\n if (wsResult.errors.length > 0) {\n console.error(`[start-planning] Workspace errors:`, wsResult.errors);\n }\n if (!wsResult.success) {\n throw new Error(wsResult.errors.join('; '));\n }\n } else {\n // Fallback: use CLI for projects without workspace config\n const dockerFlag = startDocker ? ' --docker' : '';\n const locationFlag = workspaceLocation === 'remote' ? ' --remote' : ' --local';\n const createCmd = `pan workspace create ${issue.identifier}${locationFlag}${dockerFlag}`;\n console.log(`[start-planning] Creating workspace via CLI: ${createCmd}`);\n await execAsync(createCmd, {\n cwd: projectPath,\n encoding: 'utf-8',\n timeout: startDocker ? 300000 : 120000,\n });\n }\n workspaceCreated = true;\n console.log(`[start-planning] Workspace created successfully`);\n } catch (err: any) {\n // CRITICAL: workspace MUST exist for local planning. If creation failed,\n // abort — never fall back to project root, which causes beads and planning\n // artifacts to land in the wrong place (PAN-358).\n const errorMsg = `Workspace creation failed: ${err.message}`;\n console.error(`[start-planning] ABORTING: ${errorMsg}`);\n progress(1, 'Creating workspace', errorMsg, 'error');\n const existingErrState = getAgentStateSync(sessionName);\n if (existingErrState) saveAgentStateSync({ ...existingErrState, status: 'error' });\n return { success: false, error: errorMsg };\n }\n }\n\n progress(1, 'Creating workspace', workspaceCreated ? 'Workspace ready' : 'Already exists', 'complete');\n\n // ── Step 2: Prepare planning environment ──────────────────────────────\n progress(2, 'Preparing planning environment', '.pan/ workspace artifacts');\n\n // Kill existing planning session if any\n await Effect.runPromise(killSession(sessionName)).catch(() => {});\n\n const workspacePanPaths = await Effect.runPromise(ensureWorkspacePanDir(workspacePath));\n await Promise.all(\n ['transcripts', 'discussions', 'notes'].map((subdir) =>\n mkdir(join(workspacePanPaths.panDir, subdir), { recursive: true }),\n ),\n );\n\n if (existsSync(workspacePanPaths.continuePath)) {\n console.log('[start-planning] Clearing stale .pan/continue.json');\n await rm(workspacePanPaths.continuePath, { force: true });\n }\n\n // Initialize Shadow Engineering if enabled\n if (shadowMode) {\n const inferencePath = join(workspacePanPaths.panDir, 'INFERENCE.md');\n if (!existsSync(inferencePath)) {\n await writeFile(inferencePath,\n `# Inference Document - ${issue.identifier.toUpperCase()}\\n\\n*This document is maintained by the Shadow Engineering Monitoring Agent.*\\n\\n## Status\\n\\nAwaiting initial artifact analysis.\\n`,\n 'utf-8',\n );\n console.log(`[start-planning] Shadow Engineering: Initialized INFERENCE.md`);\n }\n }\n\n progress(2, 'Preparing planning environment', 'Environment ready', 'complete');\n\n // ── Step 3: Load specs & PRDs ────────────────────────────────────────\n progress(3, 'Loading specs & PRDs', `Searching for ${issue.identifier} specs`);\n\n // Determine planning model — explicit override takes precedence over role routing.\n // PAN-1048 R1: resolveModel must NOT be wrapped in try/catch with a silent\n // fallback. If the user's roles.plan.model points at a missing workhorse\n // slot, an unknown model id, or a chained reference we can't dereference,\n // we want spawn to fail loudly with the precise error so the operator can\n // fix their config. The previous fallback to claude-opus-4-7 hid those\n // bugs and silently overrode their explicit configuration.\n let settingsModel: string;\n let modelSource: string;\n if (modelOverride) {\n settingsModel = modelOverride;\n modelSource = 'modelOverride';\n } else {\n settingsModel = resolveModel('plan', undefined, loadConfigSync().config, `plan:${issue.identifier}`);\n modelSource = 'roles.plan.model';\n console.log(`[start-planning] Model resolution for role=plan: model=${settingsModel} source=${modelSource}`);\n }\n const planningModel = modelOverride || settingsModel;\n const effectiveHarness = await resolvePlanningSessionHarness(planningModel, opts.harness);\n console.log(`[start-planning] Final planning model: ${planningModel} (override=${modelOverride || '(none)'} settings=${settingsModel} source=${modelSource}) harness=${effectiveHarness}`);\n\n // Discover and copy PRD files to workspace\n const prdFiles = await discoverPrdFiles(workspacePath, issue.identifier);\n if (prdFiles.length > 0) {\n const prdDestPath = join(workspacePanPaths.panDir, 'prd.md');\n if (!existsSync(prdDestPath)) {\n try {\n const prdContent = await readFile(prdFiles[0].path, 'utf-8');\n await writeFile(prdDestPath, prdContent, 'utf-8');\n console.log(`[start-planning] Copied PRD to ${prdDestPath} from ${prdFiles[0].path}`);\n } catch (err: any) {\n console.warn(`[start-planning] Could not copy PRD: ${err.message}`);\n }\n }\n }\n\n progress(3, 'Loading specs & PRDs', prdFiles.length > 0 ? prdFiles[0].label : 'No PRDs found', 'complete');\n\n // ── Step 4: Configure agent ─────────────────────────────────────────\n progress(4, 'Configuring agent', planningModel);\n\n let planningPrompt = await buildPlanningPrompt(issue, workspacePath, planningModel, effort, auto === true, probe === true);\n const memoryContext = await retrieveSpawnTimeMemoryContext({\n prompt: planningPrompt,\n issueId: issue.identifier,\n workspace: workspacePath,\n agentId: sessionName,\n role: 'plan',\n harness: effectiveHarness,\n });\n if (memoryContext) {\n planningPrompt = await buildPlanningPrompt(issue, workspacePath, planningModel, effort, auto === true, probe === true, memoryContext);\n }\n\n // Capture planning prompt in per-issue record (PAN-1919: replaces workspace continue.json).\n const recordProject = resolveProjectForIssue(issue.identifier) ?? getProjectConfigFromWorkspacePath(workspacePath);\n appendSessionEntrySync(recordProject, issue.identifier, {\n reason: 'planning',\n content: planningPrompt,\n note: `Planning session started for ${issue.identifier}: ${issue.title}`,\n timestamp: new Date().toISOString(),\n });\n\n await writeFeatureContext(workspacePath, issue);\n\n // PAN-1048: emit 'claude --agent roles/plan.md --name <sessionName>'.\n // PAN-636: thread harness through so a planning kickoff with --harness pi\n // produces a `pi --mode rpc --model <id>` line and skips the --agent flag\n // (Pi has no agent-definition system).\n const cmdWithArgs = await getAgentRuntimeBaseCommand(planningModel, sessionName, roleAgentDefinitionPath('plan'), effectiveHarness);\n const behavior = getHarnessBehavior(effectiveHarness);\n const piLauncherFields = behavior.usesRpcFifo\n ? await getOhmypiLauncherFields(sessionName, planningModel)\n : {};\n const codexLauncherFields = behavior.usesCodexHome\n ? getCodexLauncherFields(sessionName, planningModel, workspacePath)\n : {};\n\n const providerExports = await getProviderExportsForModel(planningModel);\n\n // ── Write launcher script ──────────────────────────────────────────────\n const recordFilePath = getIssueRecordPath(recordProject, issue.identifier);\n const initMessage = `Please read the \\`content\\` field of the \\`planning\\` sessionHistory entry in ${recordFilePath} and begin the planning session for ${issue.identifier}: ${issue.title}`;\n const promptFile = join(agentStateDir, 'init-prompt.txt');\n const launcherScript = join(agentStateDir, 'launcher.sh');\n await writeFile(promptFile, initMessage);\n await writeFile(\n launcherScript,\n generateLauncherScriptSync({\n role: 'plan',\n harness: effectiveHarness,\n workingDir: workspacePath,\n setTerminalEnv: true,\n overdeckEnv: { agentId: sessionName, issueId: issue.identifier, sessionType: 'plan' },\n providerExports,\n promptFile,\n baseCommand: cmdWithArgs,\n appendSystemPromptFiles: await claudePlanningSystemPromptFiles(workspacePath, effectiveHarness),\n trapHup: true,\n debugLog: '/tmp/pan-launcher-debug.log',\n keepAlive: true,\n ...piLauncherFields,\n ...codexLauncherFields,\n }),\n { mode: 0o755 },\n );\n\n progress(4, 'Configuring agent', `${planningModel} — prompt & launcher ready`, 'complete');\n\n // ── Step 5: Launch planning session ───────────────────────────────────\n progress(5, 'Launching planning session', sessionName);\n\n console.log(`[claude-invoke] purpose=planning-agent | model=${planningModel} | source=spawn-planning-session.ts | session=${sessionName} | command=\"bash '${launcherScript}'\"`);\n\n await ensureTmuxRunning();\n await Effect.runPromise(createSession(sessionName, workspacePath, `bash '${launcherScript}'`, {\n env: {\n ...BLANKED_PROVIDER_ENV,\n TERM: 'xterm-256color',\n },\n }));\n // Protect the session from being destroyed when clients disconnect.\n // When the dashboard's WebSocket terminal attaches and then detaches,\n // tmux can destroy the session if destroy-unattached is on.\n await Effect.runPromise(setOption(sessionName, 'destroy-unattached', 'off'));\n await Effect.runPromise(setOption(exactPaneTarget(sessionName), 'remain-on-exit', 'on'));\n\n // NOTE: No pre-resize of tmux window here. The WebSocket terminal handler\n // defers PTY spawn until the client sends its actual dimensions, so the\n // tmux window will be sized correctly from the start. Pre-resizing to\n // 200×50 caused a dimension cascade (200→120→actual) that garbled output.\n // See PAN-417 for the full forensic timeline.\n\n // ── Update agent state to running ──────────────────────────────────────\n // PAN-1048 R2: legacy `runtime` field removed; AgentState carries `harness`.\n {\n const baseState = getAgentStateSync(sessionName);\n saveAgentStateSync({\n ...(baseState ?? { id: sessionName, issueId: issue.identifier, workspace: workspacePath, startedAt: new Date().toISOString() }),\n model: planningModel,\n status: 'running',\n role: 'plan',\n harness: effectiveHarness,\n auto: auto === true,\n });\n if (autoSpawnOnFinalize) {\n await writeFile(\n join(agentStateDir, 'auto-spawn-on-finalize.json'),\n JSON.stringify({ autoSpawnOnFinalize: true }),\n );\n }\n }\n\n if (behavior.usesCodexHome) {\n const delivery = await deliverInitialPromptWithRetry(\n sessionName,\n initMessage,\n 'spawnPlanningSession:initial-prompt',\n );\n if (!delivery.ok) {\n const errorMsg = `Codex planning kickoff prompt delivery failed: ${delivery.failure ?? 'unknown error'}`;\n console.error(`[start-planning] ${errorMsg}`);\n await Effect.runPromise(killSession(sessionName)).catch(() => {});\n const existingErrState = getAgentStateSync(sessionName);\n if (existingErrState) saveAgentStateSync({ ...existingErrState, status: 'error' });\n progress(5, 'Launching planning session', errorMsg, 'error');\n return { success: false, error: errorMsg };\n }\n }\n\n progress(5, 'Launching planning session', 'Agent running', 'complete');\n\n console.log(`[start-planning] Started local planning agent ${sessionName}`);\n return { success: true };\n\n } catch (err: any) {\n console.error(`[start-planning] Agent spawn failed for ${issue.identifier}:`, err);\n try {\n const existingCatchState = getAgentStateSync(sessionName);\n if (existingCatchState) saveAgentStateSync({ ...existingCatchState, status: 'error' });\n } catch { /* ignore state write errors */ }\n return { success: false, error: err.message };\n }\n}\n"],"mappings":"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"}
@@ -114,7 +114,7 @@ function getDigestModel(projectKey, specialistType) {
114
114
  async function generateContextDigestPromise(projectKey, specialistType, options = {}) {
115
115
  ensureContextDirectory(projectKey, specialistType);
116
116
  const runCount = options.runCount ?? getContextRunsCount(projectKey);
117
- const { getRecentRunLogs } = await import("./specialist-logs-CvDxquV_.js");
117
+ const { getRecentRunLogs } = await import("./specialist-logs--Uz-r8ia.js");
118
118
  const recentRuns = getRecentRunLogs(projectKey, specialistType, runCount);
119
119
  if (recentRuns.length === 0 && !options.force) {
120
120
  console.log(`[specialist-context] No recent runs for ${projectKey}/${specialistType}, skipping digest generation`);
@@ -128,7 +128,7 @@ async function generateContextDigestPromise(projectKey, specialistType, options
128
128
  if (!existsSync(tempDir)) mkdirSync(tempDir, { recursive: true });
129
129
  const promptFile = join(tempDir, `digest-prompt-${Date.now()}.md`);
130
130
  writeFileSync(promptFile, prompt, "utf-8");
131
- const { getProviderEnvForModel } = await import("./agents-BIggLvi_.js");
131
+ const { getProviderEnvForModel } = await import("./agents-C-vqmKDi.js");
132
132
  const providerEnv = await getProviderEnvForModel(model);
133
133
  const envPrefix = Object.entries(providerEnv).map(([k, v]) => `${k}="${v}"`).join(" ");
134
134
  const permissionFlags = getClaudePermissionFlagsStringSync();
@@ -283,4 +283,4 @@ __esmMin((() => {
283
283
  }))();
284
284
  export { deleteContextDigest, generateContextDigest, getContextDigestPath, getContextDirectory, hasContextDigest, loadContextDigest, regenerateContextDigest, scheduleDigestGeneration };
285
285
 
286
- //# sourceMappingURL=specialist-context-CwKkcXL9.js.map
286
+ //# sourceMappingURL=specialist-context-BGCJ24LH.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"specialist-context-CwKkcXL9.js","names":["loadYamlConfig"],"sources":["../../src/lib/cloister/specialist-context.ts"],"sourcesContent":["/**\n * Specialist Context Management\n *\n * Generates and manages AI-powered context digests from recent specialist runs.\n * These digests seed new specialist sessions with learned patterns and expertise.\n *\n * Directory structure:\n * ~/.overdeck/specialists/{projectKey}/{specialistType}/context/latest-digest.md\n */\n\nimport { Effect } from 'effect';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { exec } from 'child_process';\nimport { getOverdeckHome } from '../paths.js';\nimport { getClaudePermissionFlagsStringSync } from '../claude-permissions.js';\nimport type { RunLogEntry } from './specialist-logs.js';\nimport { getProjectSync } from '../projects.js';\nimport { loadConfigSync as loadYamlConfig, resolveModel } from '../config-yaml.js';\n\nfunction execAsync(command: string, options: { encoding: 'utf-8'; maxBuffer: number; timeout: number }): Promise<{ stdout: string; stderr: string }> {\n return new Promise((resolve, reject) => {\n exec(command, options, (error, stdout = '', stderr = '') => {\n if (error) {\n reject(error);\n return;\n }\n\n resolve({ stdout, stderr });\n });\n });\n}\n\n/** Get specialists directory (lazy to support test env overrides) */\nfunction getSpecialistsDir(): string {\n return join(getOverdeckHome(), 'specialists');\n}\n\n/**\n * Get the context directory for a project's specialist\n */\nexport function getContextDirectory(projectKey: string, specialistType: string): string {\n return join(getSpecialistsDir(), projectKey, specialistType, 'context');\n}\n\n/**\n * Get the path to the latest context digest file\n */\nexport function getContextDigestPath(projectKey: string, specialistType: string): string {\n const contextDir = getContextDirectory(projectKey, specialistType);\n return join(contextDir, 'latest-digest.md');\n}\n\n/**\n * Ensure context directory exists for a project's specialist\n */\nfunction ensureContextDirectory(projectKey: string, specialistType: string): void {\n const contextDir = getContextDirectory(projectKey, specialistType);\n if (!existsSync(contextDir)) {\n mkdirSync(contextDir, { recursive: true });\n }\n}\n\n/**\n * Load the context digest for a specialist\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns Context digest content or null if not found\n */\nexport function loadContextDigest(projectKey: string, specialistType: string): string | null {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n\n if (!existsSync(digestPath)) {\n return null;\n }\n\n try {\n return readFileSync(digestPath, 'utf-8');\n } catch (error) {\n console.error(`[specialist-context] Failed to load digest for ${projectKey}/${specialistType}:`, error);\n return null;\n }\n}\n\n/**\n * Get the number of recent runs to include in context\n *\n * Reads from project config or uses default.\n *\n * @param projectKey - Project identifier\n * @returns Number of runs to include (default: 5)\n */\nfunction getContextRunsCount(projectKey: string): number {\n const project = getProjectSync(projectKey);\n return project?.specialists?.context_runs ?? 5;\n}\n\n/**\n * Get the model to use for digest generation\n *\n * Reads from project config or uses the same model as the specialist.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns Model ID to use\n */\nfunction roleForSpecialist(specialistType: string): { role: 'plan' | 'work' | 'review' | 'test' | 'ship'; subRole?: string } {\n const normalized = specialistType.replace(/-agent$/, '');\n if (normalized === 'inspect') return { role: 'work', subRole: 'inspect' };\n if (normalized === 'review') return { role: 'review' };\n if (normalized === 'test' || normalized === 'uat') return { role: 'test' };\n if (normalized === 'merge' || normalized === 'ship') return { role: 'ship' };\n if (normalized === 'planning' || normalized === 'plan') return { role: 'plan' };\n return { role: 'work' };\n}\n\nfunction getDigestModel(projectKey: string, specialistType: string): string {\n const project = getProjectSync(projectKey);\n\n // Check for explicit digest model in project config\n if (project?.specialists?.digest_model) {\n return project.specialists.digest_model;\n }\n\n try {\n const { role, subRole } = roleForSpecialist(specialistType);\n return resolveModel(role, subRole, loadYamlConfig().config, subRole ? undefined : `${role}:${projectKey}`);\n } catch (error) {\n // Default to Sonnet if can't resolve\n return 'claude-sonnet-4-6';\n }\n}async function generateContextDigestPromise(\n projectKey: string,\n specialistType: string,\n options: {\n runCount?: number;\n model?: string;\n force?: boolean; // Generate even if no recent runs\n } = {}\n): Promise<string | null> {\n ensureContextDirectory(projectKey, specialistType);\n\n // Get recent runs\n const runCount = options.runCount ?? getContextRunsCount(projectKey);\n const { getRecentRunLogs } = await import('./specialist-logs.js');\n const recentRuns = getRecentRunLogs(projectKey, specialistType, runCount);\n\n if (recentRuns.length === 0 && !options.force) {\n console.log(`[specialist-context] No recent runs for ${projectKey}/${specialistType}, skipping digest generation`);\n return null;\n }\n\n // Build prompt for digest generation\n const prompt = buildDigestPrompt(projectKey, specialistType, recentRuns);\n const model = options.model ?? getDigestModel(projectKey, specialistType);\n\n try {\n console.log(`[claude-invoke] purpose=specialist-digest | model=${model} | source=specialist-context.ts:generateContextDigest | project=${projectKey} | specialist=${specialistType} | promptChars=${prompt.length}`);\n\n // Use Claude Code CLI to generate digest\n // Write prompt to temp file to avoid shell escaping issues\n const tempDir = join(getOverdeckHome(), 'tmp');\n if (!existsSync(tempDir)) {\n mkdirSync(tempDir, { recursive: true });\n }\n\n const promptFile = join(tempDir, `digest-prompt-${Date.now()}.md`);\n writeFileSync(promptFile, prompt, 'utf-8');\n\n // Run Claude Code with the prompt (include provider env vars for non-Anthropic models)\n const { getProviderEnvForModel } = await import('../agents.js');\n const providerEnv = await getProviderEnvForModel(model);\n const envPrefix = Object.entries(providerEnv).map(([k, v]) => `${k}=\"${v}\"`).join(' ');\n const permissionFlags = getClaudePermissionFlagsStringSync();\n const { stdout, stderr } = await execAsync(\n `${envPrefix ? envPrefix + ' ' : ''}claude ${permissionFlags} --model ${model} \"$(cat '${promptFile}')\"`,\n {\n encoding: 'utf-8',\n maxBuffer: 10 * 1024 * 1024, // 10MB buffer\n timeout: 60000, // 60 second timeout\n }\n );\n\n // Clean up temp file\n try {\n unlinkSync(promptFile);\n } catch {\n // Ignore cleanup errors\n }\n\n if (stderr && !stderr.includes('warning')) {\n console.error(`[claude-invoke] STDERR purpose=specialist-digest | model=${model} | project=${projectKey} | specialist=${specialistType} | stderr=\"${stderr.slice(0, 200)}\"`);\n }\n\n const digest = stdout.trim();\n\n if (!digest) {\n console.error(`[claude-invoke] FAILED purpose=specialist-digest | model=${model} | project=${projectKey} | specialist=${specialistType} | error=\"empty output\"`);\n return null;\n }\n\n // Save digest\n const digestPath = getContextDigestPath(projectKey, specialistType);\n writeFileSync(digestPath, digest, 'utf-8');\n\n console.log(`[claude-invoke] SUCCESS purpose=specialist-digest | model=${model} | project=${projectKey} | specialist=${specialistType} | outputChars=${digest.length}`);\n return digest;\n } catch (error: any) {\n console.error(`[claude-invoke] FAILED purpose=specialist-digest | model=${model} | project=${projectKey} | specialist=${specialistType} | error=\"${error.message}\"`);\n // Degrade gracefully - return null so specialist can continue without context\n return null;\n }\n}\n\n/**\n * Build the prompt for digest generation\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param recentRuns - Recent run logs\n * @returns Prompt for Claude\n */\nfunction buildDigestPrompt(\n projectKey: string,\n specialistType: string,\n recentRuns: RunLogEntry[]\n): string {\n const project = getProjectSync(projectKey);\n const projectName = project?.name || projectKey;\n\n let prompt = `You are analyzing the recent history of a ${specialistType} specialist for the ${projectName} project.\n\nYour task is to generate a concise context digest that will be provided to the specialist at the start of their next run. This digest should help them understand:\n- Common patterns and practices observed in recent runs\n- Recurring issues or failure modes\n- Successful approaches and best practices\n- Any project-specific context that would be helpful\n\nGenerate a digest in markdown format. Keep it focused and actionable - aim for 200-400 words total.\n\n## Recent Runs\n\n`;\n\n if (recentRuns.length === 0) {\n prompt += `No recent runs available yet. This is the specialist's first run.\\n\\n`;\n prompt += `Generate a brief introduction for the specialist explaining their role and what to expect.\\n`;\n } else {\n recentRuns.forEach((run, index) => {\n prompt += `### Run ${index + 1}: ${run.metadata.issueId} (${run.metadata.status || 'unknown'})\\n`;\n prompt += `Started: ${run.metadata.startedAt}\\n`;\n if (run.metadata.finishedAt) {\n prompt += `Finished: ${run.metadata.finishedAt}\\n`;\n }\n if (run.metadata.duration) {\n const durationSec = Math.floor(run.metadata.duration / 1000);\n const minutes = Math.floor(durationSec / 60);\n const seconds = durationSec % 60;\n prompt += `Duration: ${minutes}m ${seconds}s\\n`;\n }\n if (run.metadata.notes) {\n prompt += `Notes: ${run.metadata.notes}\\n`;\n }\n\n // Include snippets from the log if available\n try {\n const logContent = readFileSync(run.filePath, 'utf-8');\n // Extract key sections (limit to avoid overwhelming the prompt)\n const maxChars = 500;\n const transcriptMatch = logContent.match(/## Session Transcript\\n([\\s\\S]+?)(?=\\n## |$)/);\n if (transcriptMatch) {\n let transcript = transcriptMatch[1].trim();\n if (transcript.length > maxChars) {\n transcript = transcript.substring(0, maxChars) + '... [truncated]';\n }\n prompt += `\\nTranscript excerpt:\\n${transcript}\\n`;\n }\n } catch (error) {\n // If we can't read the log, skip the excerpt\n }\n\n prompt += `\\n`;\n });\n }\n\n prompt += `\\n## Your Task\n\nGenerate a context digest that summarizes the key insights from these runs. Format it as:\n\n# Recent ${specialistType} History for ${projectName}\n\n## Summary\n[2-3 sentence overview of patterns and trends]\n\n## Common Patterns\n[Bulleted list of observed patterns]\n\n## Recent Notable Runs\n[Brief highlights of 2-3 most interesting runs]\n\n## Recommendations\n[Specific guidance for the next run based on this history]\n\nKeep it concise, actionable, and focused on helping the specialist be more effective.`;\n\n return prompt;\n}async function regenerateContextDigestPromise(\n projectKey: string,\n specialistType: string\n): Promise<string | null> {\n return (await Effect.runPromise(generateContextDigest(projectKey, specialistType, { force: true })));\n}\n\n/**\n * Generate digest after a run completes (async, fire-and-forget)\n *\n * This is called after a specialist finishes a run to update the context\n * for the next run. It runs asynchronously and failures are logged but not thrown.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n */\nexport function scheduleDigestGeneration(projectKey: string, specialistType: string): void {\n // Run async without awaiting\n Effect.runPromise(generateContextDigest(projectKey, specialistType)).catch((error) => {\n console.error(\n `[specialist-context] Background digest generation failed for ${projectKey}/${specialistType}:`,\n error\n );\n });\n}\n\n/**\n * Check if a context digest exists\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns True if digest file exists\n */\nexport function hasContextDigest(projectKey: string, specialistType: string): boolean {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n return existsSync(digestPath);\n}\n\n/**\n * Delete the context digest\n *\n * Useful for forcing a fresh start or clearing stale context.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns True if digest was deleted, false if it didn't exist\n */\nexport function deleteContextDigest(projectKey: string, specialistType: string): boolean {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n\n if (!existsSync(digestPath)) {\n return false;\n }\n\n try {\n unlinkSync(digestPath);\n return true;\n } catch (error) {\n console.error(`[specialist-context] Failed to delete digest:`, error);\n return false;\n }\n}\n\n// ─── Effect variants (PAN-1249) ──────────────────────────────────────────────\n\n/**\n * Effect variant of {@link generateContextDigest}. The Promise version already\n * resolves to `null` on every failure mode (claude unavailable, empty runs,\n * write error), so the Effect form mirrors that contract via `Effect.promise`.\n */\nexport const generateContextDigest = (\n projectKey: string,\n specialistType: string,\n options: { runCount?: number; model?: string; force?: boolean } = {},\n): Effect.Effect<string | null> =>\n Effect.promise(() => generateContextDigestPromise(projectKey, specialistType, options));\n\n/**\n * Effect variant of {@link regenerateContextDigest}. Same swallowed-failure\n * semantics as the Promise version.\n */\nexport const regenerateContextDigest = (\n projectKey: string,\n specialistType: string,\n): Effect.Effect<string | null> =>\n Effect.promise(() => regenerateContextDigestPromise(projectKey, specialistType));\n"],"mappings":"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"}
1
+ {"version":3,"file":"specialist-context-BGCJ24LH.js","names":["loadYamlConfig"],"sources":["../../src/lib/cloister/specialist-context.ts"],"sourcesContent":["/**\n * Specialist Context Management\n *\n * Generates and manages AI-powered context digests from recent specialist runs.\n * These digests seed new specialist sessions with learned patterns and expertise.\n *\n * Directory structure:\n * ~/.overdeck/specialists/{projectKey}/{specialistType}/context/latest-digest.md\n */\n\nimport { Effect } from 'effect';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { exec } from 'child_process';\nimport { getOverdeckHome } from '../paths.js';\nimport { getClaudePermissionFlagsStringSync } from '../claude-permissions.js';\nimport type { RunLogEntry } from './specialist-logs.js';\nimport { getProjectSync } from '../projects.js';\nimport { loadConfigSync as loadYamlConfig, resolveModel } from '../config-yaml.js';\n\nfunction execAsync(command: string, options: { encoding: 'utf-8'; maxBuffer: number; timeout: number }): Promise<{ stdout: string; stderr: string }> {\n return new Promise((resolve, reject) => {\n exec(command, options, (error, stdout = '', stderr = '') => {\n if (error) {\n reject(error);\n return;\n }\n\n resolve({ stdout, stderr });\n });\n });\n}\n\n/** Get specialists directory (lazy to support test env overrides) */\nfunction getSpecialistsDir(): string {\n return join(getOverdeckHome(), 'specialists');\n}\n\n/**\n * Get the context directory for a project's specialist\n */\nexport function getContextDirectory(projectKey: string, specialistType: string): string {\n return join(getSpecialistsDir(), projectKey, specialistType, 'context');\n}\n\n/**\n * Get the path to the latest context digest file\n */\nexport function getContextDigestPath(projectKey: string, specialistType: string): string {\n const contextDir = getContextDirectory(projectKey, specialistType);\n return join(contextDir, 'latest-digest.md');\n}\n\n/**\n * Ensure context directory exists for a project's specialist\n */\nfunction ensureContextDirectory(projectKey: string, specialistType: string): void {\n const contextDir = getContextDirectory(projectKey, specialistType);\n if (!existsSync(contextDir)) {\n mkdirSync(contextDir, { recursive: true });\n }\n}\n\n/**\n * Load the context digest for a specialist\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns Context digest content or null if not found\n */\nexport function loadContextDigest(projectKey: string, specialistType: string): string | null {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n\n if (!existsSync(digestPath)) {\n return null;\n }\n\n try {\n return readFileSync(digestPath, 'utf-8');\n } catch (error) {\n console.error(`[specialist-context] Failed to load digest for ${projectKey}/${specialistType}:`, error);\n return null;\n }\n}\n\n/**\n * Get the number of recent runs to include in context\n *\n * Reads from project config or uses default.\n *\n * @param projectKey - Project identifier\n * @returns Number of runs to include (default: 5)\n */\nfunction getContextRunsCount(projectKey: string): number {\n const project = getProjectSync(projectKey);\n return project?.specialists?.context_runs ?? 5;\n}\n\n/**\n * Get the model to use for digest generation\n *\n * Reads from project config or uses the same model as the specialist.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns Model ID to use\n */\nfunction roleForSpecialist(specialistType: string): { role: 'plan' | 'work' | 'review' | 'test' | 'ship'; subRole?: string } {\n const normalized = specialistType.replace(/-agent$/, '');\n if (normalized === 'inspect') return { role: 'work', subRole: 'inspect' };\n if (normalized === 'review') return { role: 'review' };\n if (normalized === 'test' || normalized === 'uat') return { role: 'test' };\n if (normalized === 'merge' || normalized === 'ship') return { role: 'ship' };\n if (normalized === 'planning' || normalized === 'plan') return { role: 'plan' };\n return { role: 'work' };\n}\n\nfunction getDigestModel(projectKey: string, specialistType: string): string {\n const project = getProjectSync(projectKey);\n\n // Check for explicit digest model in project config\n if (project?.specialists?.digest_model) {\n return project.specialists.digest_model;\n }\n\n try {\n const { role, subRole } = roleForSpecialist(specialistType);\n return resolveModel(role, subRole, loadYamlConfig().config, subRole ? undefined : `${role}:${projectKey}`);\n } catch (error) {\n // Default to Sonnet if can't resolve\n return 'claude-sonnet-4-6';\n }\n}async function generateContextDigestPromise(\n projectKey: string,\n specialistType: string,\n options: {\n runCount?: number;\n model?: string;\n force?: boolean; // Generate even if no recent runs\n } = {}\n): Promise<string | null> {\n ensureContextDirectory(projectKey, specialistType);\n\n // Get recent runs\n const runCount = options.runCount ?? getContextRunsCount(projectKey);\n const { getRecentRunLogs } = await import('./specialist-logs.js');\n const recentRuns = getRecentRunLogs(projectKey, specialistType, runCount);\n\n if (recentRuns.length === 0 && !options.force) {\n console.log(`[specialist-context] No recent runs for ${projectKey}/${specialistType}, skipping digest generation`);\n return null;\n }\n\n // Build prompt for digest generation\n const prompt = buildDigestPrompt(projectKey, specialistType, recentRuns);\n const model = options.model ?? getDigestModel(projectKey, specialistType);\n\n try {\n console.log(`[claude-invoke] purpose=specialist-digest | model=${model} | source=specialist-context.ts:generateContextDigest | project=${projectKey} | specialist=${specialistType} | promptChars=${prompt.length}`);\n\n // Use Claude Code CLI to generate digest\n // Write prompt to temp file to avoid shell escaping issues\n const tempDir = join(getOverdeckHome(), 'tmp');\n if (!existsSync(tempDir)) {\n mkdirSync(tempDir, { recursive: true });\n }\n\n const promptFile = join(tempDir, `digest-prompt-${Date.now()}.md`);\n writeFileSync(promptFile, prompt, 'utf-8');\n\n // Run Claude Code with the prompt (include provider env vars for non-Anthropic models)\n const { getProviderEnvForModel } = await import('../agents.js');\n const providerEnv = await getProviderEnvForModel(model);\n const envPrefix = Object.entries(providerEnv).map(([k, v]) => `${k}=\"${v}\"`).join(' ');\n const permissionFlags = getClaudePermissionFlagsStringSync();\n const { stdout, stderr } = await execAsync(\n `${envPrefix ? envPrefix + ' ' : ''}claude ${permissionFlags} --model ${model} \"$(cat '${promptFile}')\"`,\n {\n encoding: 'utf-8',\n maxBuffer: 10 * 1024 * 1024, // 10MB buffer\n timeout: 60000, // 60 second timeout\n }\n );\n\n // Clean up temp file\n try {\n unlinkSync(promptFile);\n } catch {\n // Ignore cleanup errors\n }\n\n if (stderr && !stderr.includes('warning')) {\n console.error(`[claude-invoke] STDERR purpose=specialist-digest | model=${model} | project=${projectKey} | specialist=${specialistType} | stderr=\"${stderr.slice(0, 200)}\"`);\n }\n\n const digest = stdout.trim();\n\n if (!digest) {\n console.error(`[claude-invoke] FAILED purpose=specialist-digest | model=${model} | project=${projectKey} | specialist=${specialistType} | error=\"empty output\"`);\n return null;\n }\n\n // Save digest\n const digestPath = getContextDigestPath(projectKey, specialistType);\n writeFileSync(digestPath, digest, 'utf-8');\n\n console.log(`[claude-invoke] SUCCESS purpose=specialist-digest | model=${model} | project=${projectKey} | specialist=${specialistType} | outputChars=${digest.length}`);\n return digest;\n } catch (error: any) {\n console.error(`[claude-invoke] FAILED purpose=specialist-digest | model=${model} | project=${projectKey} | specialist=${specialistType} | error=\"${error.message}\"`);\n // Degrade gracefully - return null so specialist can continue without context\n return null;\n }\n}\n\n/**\n * Build the prompt for digest generation\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param recentRuns - Recent run logs\n * @returns Prompt for Claude\n */\nfunction buildDigestPrompt(\n projectKey: string,\n specialistType: string,\n recentRuns: RunLogEntry[]\n): string {\n const project = getProjectSync(projectKey);\n const projectName = project?.name || projectKey;\n\n let prompt = `You are analyzing the recent history of a ${specialistType} specialist for the ${projectName} project.\n\nYour task is to generate a concise context digest that will be provided to the specialist at the start of their next run. This digest should help them understand:\n- Common patterns and practices observed in recent runs\n- Recurring issues or failure modes\n- Successful approaches and best practices\n- Any project-specific context that would be helpful\n\nGenerate a digest in markdown format. Keep it focused and actionable - aim for 200-400 words total.\n\n## Recent Runs\n\n`;\n\n if (recentRuns.length === 0) {\n prompt += `No recent runs available yet. This is the specialist's first run.\\n\\n`;\n prompt += `Generate a brief introduction for the specialist explaining their role and what to expect.\\n`;\n } else {\n recentRuns.forEach((run, index) => {\n prompt += `### Run ${index + 1}: ${run.metadata.issueId} (${run.metadata.status || 'unknown'})\\n`;\n prompt += `Started: ${run.metadata.startedAt}\\n`;\n if (run.metadata.finishedAt) {\n prompt += `Finished: ${run.metadata.finishedAt}\\n`;\n }\n if (run.metadata.duration) {\n const durationSec = Math.floor(run.metadata.duration / 1000);\n const minutes = Math.floor(durationSec / 60);\n const seconds = durationSec % 60;\n prompt += `Duration: ${minutes}m ${seconds}s\\n`;\n }\n if (run.metadata.notes) {\n prompt += `Notes: ${run.metadata.notes}\\n`;\n }\n\n // Include snippets from the log if available\n try {\n const logContent = readFileSync(run.filePath, 'utf-8');\n // Extract key sections (limit to avoid overwhelming the prompt)\n const maxChars = 500;\n const transcriptMatch = logContent.match(/## Session Transcript\\n([\\s\\S]+?)(?=\\n## |$)/);\n if (transcriptMatch) {\n let transcript = transcriptMatch[1].trim();\n if (transcript.length > maxChars) {\n transcript = transcript.substring(0, maxChars) + '... [truncated]';\n }\n prompt += `\\nTranscript excerpt:\\n${transcript}\\n`;\n }\n } catch (error) {\n // If we can't read the log, skip the excerpt\n }\n\n prompt += `\\n`;\n });\n }\n\n prompt += `\\n## Your Task\n\nGenerate a context digest that summarizes the key insights from these runs. Format it as:\n\n# Recent ${specialistType} History for ${projectName}\n\n## Summary\n[2-3 sentence overview of patterns and trends]\n\n## Common Patterns\n[Bulleted list of observed patterns]\n\n## Recent Notable Runs\n[Brief highlights of 2-3 most interesting runs]\n\n## Recommendations\n[Specific guidance for the next run based on this history]\n\nKeep it concise, actionable, and focused on helping the specialist be more effective.`;\n\n return prompt;\n}async function regenerateContextDigestPromise(\n projectKey: string,\n specialistType: string\n): Promise<string | null> {\n return (await Effect.runPromise(generateContextDigest(projectKey, specialistType, { force: true })));\n}\n\n/**\n * Generate digest after a run completes (async, fire-and-forget)\n *\n * This is called after a specialist finishes a run to update the context\n * for the next run. It runs asynchronously and failures are logged but not thrown.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n */\nexport function scheduleDigestGeneration(projectKey: string, specialistType: string): void {\n // Run async without awaiting\n Effect.runPromise(generateContextDigest(projectKey, specialistType)).catch((error) => {\n console.error(\n `[specialist-context] Background digest generation failed for ${projectKey}/${specialistType}:`,\n error\n );\n });\n}\n\n/**\n * Check if a context digest exists\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns True if digest file exists\n */\nexport function hasContextDigest(projectKey: string, specialistType: string): boolean {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n return existsSync(digestPath);\n}\n\n/**\n * Delete the context digest\n *\n * Useful for forcing a fresh start or clearing stale context.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns True if digest was deleted, false if it didn't exist\n */\nexport function deleteContextDigest(projectKey: string, specialistType: string): boolean {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n\n if (!existsSync(digestPath)) {\n return false;\n }\n\n try {\n unlinkSync(digestPath);\n return true;\n } catch (error) {\n console.error(`[specialist-context] Failed to delete digest:`, error);\n return false;\n }\n}\n\n// ─── Effect variants (PAN-1249) ──────────────────────────────────────────────\n\n/**\n * Effect variant of {@link generateContextDigest}. The Promise version already\n * resolves to `null` on every failure mode (claude unavailable, empty runs,\n * write error), so the Effect form mirrors that contract via `Effect.promise`.\n */\nexport const generateContextDigest = (\n projectKey: string,\n specialistType: string,\n options: { runCount?: number; model?: string; force?: boolean } = {},\n): Effect.Effect<string | null> =>\n Effect.promise(() => generateContextDigestPromise(projectKey, specialistType, options));\n\n/**\n * Effect variant of {@link regenerateContextDigest}. Same swallowed-failure\n * semantics as the Promise version.\n */\nexport const regenerateContextDigest = (\n projectKey: string,\n specialistType: string,\n): Effect.Effect<string | null> =>\n Effect.promise(() => regenerateContextDigestPromise(projectKey, specialistType));\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { a as __toCommonJS, n as __esmMin } from "./rolldown-runtime-DNCzzKnI.js";
2
2
  import { Bn as projects_exports, Br as getOverdeckHome, Nn as init_projects, Vr as init_paths } from "./config-yaml-DPAieL_G.js";
3
- import { J as specialists_exports, O as init_specialists } from "./specialists-BYi0k8CZ.js";
3
+ import { J as specialists_exports, O as init_specialists } from "./specialists-nZ0mRwpZ.js";
4
4
  import { Data, Effect } from "effect";
5
5
  import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "fs";
6
6
  import { basename, join } from "path";
@@ -380,4 +380,4 @@ __esmMin((() => {
380
380
  }))();
381
381
  export { MAX_LOG_SIZE, SpecialistLogError, appendToRunLog, appendToRunLogSync, checkLogSizeLimit, cleanupAllLogs, cleanupAllLogsSync, cleanupOldLogs, cleanupOldLogsSync, createRunLog, createRunLogSync, finalizeRunLog, finalizeRunLogSync, generateRunId, getRecentRunLogs, getRunLog, getRunLogPath, getRunLogSize, getRunLogSync, getRunsDirectory, isRunLogActive, listRunLogs, listRunLogsSync, parseLogMetadata };
382
382
 
383
- //# sourceMappingURL=specialist-logs-CvDxquV_.js.map
383
+ //# sourceMappingURL=specialist-logs--Uz-r8ia.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"specialist-logs-CvDxquV_.js","names":[],"sources":["../../src/lib/cloister/specialist-logs.ts"],"sourcesContent":["/**\n * Specialist Log Management\n *\n * Manages persistent log files for specialist agent runs.\n * Each run produces a structured log file with metadata, context, and full transcript.\n *\n * Directory structure:\n * ~/.overdeck/specialists/{projectKey}/{specialistType}/runs/{timestamp}-{issueId}.log\n */\n\nimport { existsSync, mkdirSync, writeFileSync, appendFileSync, readFileSync, readdirSync, statSync, unlinkSync } from 'fs';\nimport { join, basename } from 'path';\nimport { Data, Effect } from 'effect';\nimport { getOverdeckHome } from '../paths.js';\n\n/** Get specialists directory (lazy to support test env overrides) */\nfunction getSpecialistsDir(): string {\n return join(getOverdeckHome(), 'specialists');\n}\n\n/**\n * Log file metadata\n */\nexport interface RunLogMetadata {\n runId: string;\n project: string;\n specialistType: string;\n issueId: string;\n startedAt: string;\n finishedAt?: string;\n status?: 'passed' | 'failed' | 'blocked' | 'incomplete';\n duration?: number; // in milliseconds\n notes?: string;\n}\n\n/**\n * Run log entry for listing\n */\nexport interface RunLogEntry {\n runId: string;\n filePath: string;\n metadata: RunLogMetadata;\n fileSize: number;\n createdAt: Date;\n}\n\n/**\n * Get the runs directory for a project's specialist\n */\nexport function getRunsDirectory(projectKey: string, specialistType: string): string {\n return join(getSpecialistsDir(), projectKey, specialistType, 'runs');\n}\n\n/**\n * Generate a run ID from timestamp and issue ID\n */\nexport function generateRunId(issueId: string): string {\n const timestamp = new Date().toISOString().replace(/[:.]/g, '-').substring(0, 19);\n return `${timestamp}-${issueId}`;\n}\n\n/**\n * Get the log file path for a run\n */\nexport function getRunLogPath(projectKey: string, specialistType: string, runId: string): string {\n const runsDir = getRunsDirectory(projectKey, specialistType);\n return join(runsDir, `${runId}.log`);\n}\n\n/**\n * Ensure runs directory exists for a project's specialist\n */\nfunction ensureRunsDirectory(projectKey: string, specialistType: string): void {\n const runsDir = getRunsDirectory(projectKey, specialistType);\n if (!existsSync(runsDir)) {\n mkdirSync(runsDir, { recursive: true });\n }\n}\n\n/**\n * Create a new run log file\n *\n * Initializes a log file with metadata header.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type (review-agent, test-agent, merge-agent)\n * @param issueId - Issue ID being worked on\n * @param contextSeed - Optional context digest that was provided to the specialist\n * @returns Run ID and file path\n */\nexport function createRunLogSync(\n projectKey: string,\n specialistType: string,\n issueId: string,\n contextSeed?: string\n): { runId: string; filePath: string } {\n ensureRunsDirectory(projectKey, specialistType);\n\n const runId = generateRunId(issueId);\n const filePath = getRunLogPath(projectKey, specialistType, runId);\n const startedAt = new Date().toISOString();\n\n // Create log header\n const header = `# ${specialistType} Run - ${issueId}\nProject: ${projectKey}\nStarted: ${startedAt}\nIssue: ${issueId}\nRun ID: ${runId}\n\n## Context Seed\n${contextSeed ? contextSeed : '[No context digest available]'}\n\n## Session Transcript\n`;\n\n writeFileSync(filePath, header, 'utf-8');\n\n return { runId, filePath };\n}\n\n/**\n * Append content to a run log\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param runId - Run identifier\n * @param content - Content to append\n */\nexport function appendToRunLogSync(\n projectKey: string,\n specialistType: string,\n runId: string,\n content: string\n): void {\n const filePath = getRunLogPath(projectKey, specialistType, runId);\n\n if (!existsSync(filePath)) {\n throw new Error(`Run log not found: ${filePath}`);\n }\n\n appendFileSync(filePath, content, 'utf-8');\n}\n\n/**\n * Finalize a run log with result metadata\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param runId - Run identifier\n * @param result - Run result\n */\nexport function finalizeRunLogSync(\n projectKey: string,\n specialistType: string,\n runId: string,\n result: {\n status: 'passed' | 'failed' | 'blocked' | 'incomplete';\n notes?: string;\n }\n): void {\n const filePath = getRunLogPath(projectKey, specialistType, runId);\n\n if (!existsSync(filePath)) {\n throw new Error(`Run log not found: ${filePath}`);\n }\n\n // Read the log to extract start time\n const content = readFileSync(filePath, 'utf-8');\n const startMatch = content.match(/^Started: (.+)$/m);\n const startedAt = startMatch ? new Date(startMatch[1]) : new Date();\n const finishedAt = new Date();\n const duration = finishedAt.getTime() - startedAt.getTime();\n\n // Format duration\n const durationSeconds = Math.floor(duration / 1000);\n const minutes = Math.floor(durationSeconds / 60);\n const seconds = durationSeconds % 60;\n const durationStr = `${minutes}m ${seconds}s`;\n\n // Append result section\n const resultSection = `\n\n## Result\nStatus: ${result.status}\n${result.notes ? `Notes: ${result.notes}` : ''}\nDuration: ${durationStr}\nFinished: ${finishedAt.toISOString()}\n`;\n\n appendFileSync(filePath, resultSection, 'utf-8');\n}\n\n/**\n * Read a run log file\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param runId - Run identifier\n * @returns Log content or null if not found\n */\nexport function getRunLogSync(\n projectKey: string,\n specialistType: string,\n runId: string\n): string | null {\n const filePath = getRunLogPath(projectKey, specialistType, runId);\n\n if (!existsSync(filePath)) {\n return null;\n }\n\n try {\n return readFileSync(filePath, 'utf-8');\n } catch (error) {\n console.error(`Failed to read run log ${runId}:`, error);\n return null;\n }\n}\n\n/**\n * Parse metadata from a log file\n *\n * @param logContent - Log file content\n * @returns Parsed metadata\n */\nexport function parseLogMetadata(logContent: string): Partial<RunLogMetadata> {\n const metadata: Partial<RunLogMetadata> = {};\n\n // Extract metadata from header\n const projectMatch = logContent.match(/^Project: (.+)$/m);\n const startedMatch = logContent.match(/^Started: (.+)$/m);\n const issueMatch = logContent.match(/^Issue: (.+)$/m);\n const runIdMatch = logContent.match(/^Run ID: (.+)$/m);\n const statusMatch = logContent.match(/^Status: (.+)$/m);\n const notesMatch = logContent.match(/^Notes: (.+)$/m);\n const finishedMatch = logContent.match(/^Finished: (.+)$/m);\n const durationMatch = logContent.match(/^Duration: (.+)$/m);\n\n if (projectMatch) metadata.project = projectMatch[1].trim();\n if (startedMatch) metadata.startedAt = startedMatch[1].trim();\n if (issueMatch) metadata.issueId = issueMatch[1].trim();\n if (runIdMatch) metadata.runId = runIdMatch[1].trim();\n if (statusMatch) metadata.status = statusMatch[1].trim() as RunLogMetadata['status'];\n if (notesMatch) metadata.notes = notesMatch[1].trim();\n if (finishedMatch) metadata.finishedAt = finishedMatch[1].trim();\n\n // Parse duration if available\n if (durationMatch) {\n const durationStr = durationMatch[1].trim();\n const minutesMatch = durationStr.match(/(\\d+)m/);\n const secondsMatch = durationStr.match(/(\\d+)s/);\n const minutes = minutesMatch ? parseInt(minutesMatch[1], 10) : 0;\n const seconds = secondsMatch ? parseInt(secondsMatch[1], 10) : 0;\n metadata.duration = (minutes * 60 + seconds) * 1000;\n }\n\n return metadata;\n}\n\n/**\n * List all run logs for a project's specialist\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param options - Listing options\n * @returns Array of run log entries, sorted by most recent first\n */\nexport function listRunLogsSync(\n projectKey: string,\n specialistType: string,\n options: {\n limit?: number;\n offset?: number;\n } = {}\n): RunLogEntry[] {\n const runsDir = getRunsDirectory(projectKey, specialistType);\n\n if (!existsSync(runsDir)) {\n return [];\n }\n\n try {\n const files = readdirSync(runsDir)\n .filter(f => f.endsWith('.log'))\n .map(f => {\n const filePath = join(runsDir, f);\n const stats = statSync(filePath);\n const runId = basename(f, '.log');\n\n // Read file to extract metadata\n const content = readFileSync(filePath, 'utf-8');\n const metadata = parseLogMetadata(content);\n\n return {\n runId,\n filePath,\n metadata: {\n runId,\n project: projectKey,\n specialistType,\n issueId: metadata.issueId || 'unknown',\n startedAt: metadata.startedAt || stats.birthtime.toISOString(),\n finishedAt: metadata.finishedAt,\n status: metadata.status,\n duration: metadata.duration,\n notes: metadata.notes,\n },\n fileSize: stats.size,\n createdAt: stats.birthtime,\n };\n });\n\n // Sort by most recent first, with runId as tiebreaker for stable ordering\n files.sort((a, b) => {\n const timeDiff = b.createdAt.getTime() - a.createdAt.getTime();\n if (timeDiff !== 0) return timeDiff;\n return b.runId.localeCompare(a.runId);\n });\n\n // Apply pagination\n const { limit, offset = 0 } = options;\n if (limit !== undefined) {\n return files.slice(offset, offset + limit);\n }\n\n return files.slice(offset);\n } catch (error) {\n console.error(`Failed to list run logs for ${projectKey}/${specialistType}:`, error);\n return [];\n }\n}\n\n/**\n * Get the most recent N run logs\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param count - Number of recent runs to retrieve\n * @returns Array of recent run log entries\n */\nexport function getRecentRunLogs(\n projectKey: string,\n specialistType: string,\n count: number\n): RunLogEntry[] {\n return listRunLogsSync(projectKey, specialistType, { limit: count });\n}\n\n/**\n * Clean up old run logs based on retention policy\n *\n * Keeps logs that match either criteria (whichever is more permissive):\n * - Within maxDays\n * - Within the last maxRuns count\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param retention - Retention policy\n * @returns Number of logs deleted\n */\nexport function cleanupOldLogsSync(\n projectKey: string,\n specialistType: string,\n retention: { maxDays: number; maxRuns: number }\n): number {\n const { maxDays, maxRuns } = retention;\n\n // Compute cutoff BEFORE reading file stats. This ensures all files that existed\n // when cleanup was invoked have birthtimes <= cutoffDate when maxDays=0, avoiding\n // a race where a file created in the same millisecond as cutoffDate would be\n // incorrectly retained by a >= comparison.\n const now = new Date();\n const cutoffDate = new Date(now.getTime() - maxDays * 24 * 60 * 60 * 1000);\n\n const allLogs = listRunLogsSync(projectKey, specialistType);\n\n if (allLogs.length === 0) {\n return 0;\n }\n\n let deletedCount = 0;\n\n allLogs.forEach((log, index) => {\n // Keep if within maxRuns (most recent N runs)\n if (index < maxRuns) {\n return;\n }\n\n // Keep if within maxDays. Skip the age check entirely when maxDays=0, because\n // \"within 0 days\" means no age-based protection — only maxRuns applies.\n // This avoids a timing/rounding issue: Node.js converts nanosecond birthtime\n // to milliseconds using standard rounding, so a file created at 431.6ms gets\n // birthtime 432ms. If cutoff is 431ms (same wall-clock millisecond), the file\n // incorrectly appears newer than the cutoff and gets retained.\n if (maxDays > 0 && log.createdAt >= cutoffDate) {\n return;\n }\n\n // Delete this log\n try {\n unlinkSync(log.filePath);\n deletedCount++;\n console.log(`[specialist-logs] Deleted old log: ${log.runId}`);\n } catch (error) {\n console.error(`[specialist-logs] Failed to delete ${log.runId}:`, error);\n }\n });\n\n return deletedCount;\n}\n\n/**\n * Check if a run log is still active (not finalized)\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param runId - Run identifier\n * @returns True if log exists but has no result section yet\n */\nexport function isRunLogActive(\n projectKey: string,\n specialistType: string,\n runId: string\n): boolean {\n const content = getRunLogSync(projectKey, specialistType, runId);\n\n if (!content) {\n return false;\n }\n\n // Check if Result section exists\n return !content.includes('## Result');\n}\n\n/**\n * Get file size of a run log (useful for truncation check)\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param runId - Run identifier\n * @returns File size in bytes or null if not found\n */\nexport function getRunLogSize(\n projectKey: string,\n specialistType: string,\n runId: string\n): number | null {\n const filePath = getRunLogPath(projectKey, specialistType, runId);\n\n if (!existsSync(filePath)) {\n return null;\n }\n\n try {\n const stats = statSync(filePath);\n return stats.size;\n } catch (error) {\n return null;\n }\n}\n\n/**\n * Maximum log file size (10MB) before truncation warning\n */\nexport const MAX_LOG_SIZE = 10 * 1024 * 1024;\n\n/**\n * Check if a log file is approaching or exceeding size limits\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param runId - Run identifier\n * @returns Warning info or null if size is OK\n */\nexport function checkLogSizeLimit(\n projectKey: string,\n specialistType: string,\n runId: string\n): { exceeded: boolean; size: number; limit: number } | null {\n const size = getRunLogSize(projectKey, specialistType, runId);\n\n if (size === null) {\n return null;\n }\n\n if (size >= MAX_LOG_SIZE) {\n return {\n exceeded: true,\n size,\n limit: MAX_LOG_SIZE,\n };\n }\n\n return null;\n}\n\n/**\n * Clean up old logs for all projects and specialists\n *\n * Runs cleanup based on retention policies configured in projects.yaml.\n * This should be called periodically (e.g., daily cron job).\n *\n * @returns Summary of cleanup results\n */\nexport function cleanupAllLogsSync(): {\n totalDeleted: number;\n byProject: Record<string, Record<string, number>>;\n} {\n const { listProjectsWithSpecialists } = require('./specialists.js');\n const { getSpecialistRetention } = require('../projects.js');\n\n const results = {\n totalDeleted: 0,\n byProject: {} as Record<string, Record<string, number>>,\n };\n\n const projects = listProjectsWithSpecialists();\n\n for (const projectKey of projects) {\n results.byProject[projectKey] = {};\n\n // Get retention policy for this project\n const retention = getSpecialistRetention(projectKey);\n\n // Clean up each specialist type\n const specialistTypes = ['review-agent', 'test-agent', 'merge-agent'];\n\n for (const specialistType of specialistTypes) {\n const deleted = cleanupOldLogsSync(projectKey, specialistType, retention);\n\n if (deleted > 0) {\n results.byProject[projectKey][specialistType] = deleted;\n results.totalDeleted += deleted;\n }\n }\n }\n\n console.log(`[specialist-logs] Cleanup complete: deleted ${results.totalDeleted} old logs`);\n\n return results;\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n//\n// Additive Effect wrappers around the existing sync APIs. The underlying file\n// I/O is sync (CLI-callable); these variants lift thrown exceptions into a\n// typed error channel so callers can compose specialist-log operations with\n// other Effect-native code. Migrate callers individually.\n\n/** Tagged error for specialist-log Effect variants. */\nexport class SpecialistLogError extends Data.TaggedError('SpecialistLogError')<{\n readonly projectKey: string;\n readonly specialistType: string;\n readonly operation: string;\n readonly message: string;\n readonly cause?: unknown;\n}> {}\n\nconst liftLogError = (\n projectKey: string,\n specialistType: string,\n operation: string,\n cause: unknown,\n): SpecialistLogError =>\n new SpecialistLogError({\n projectKey,\n specialistType,\n operation,\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n });\n\n/** Effect variant of `createRunLog`. */\nexport const createRunLog = (\n projectKey: string,\n specialistType: string,\n issueId: string,\n contextSeed?: string,\n): Effect.Effect<{ runId: string; filePath: string }, SpecialistLogError> =>\n Effect.try({\n try: () => createRunLogSync(projectKey, specialistType, issueId, contextSeed),\n catch: (cause) => liftLogError(projectKey, specialistType, 'createRunLog', cause),\n });\n\n/** Effect variant of `appendToRunLog`. */\nexport const appendToRunLog = (\n projectKey: string,\n specialistType: string,\n runId: string,\n content: string,\n): Effect.Effect<void, SpecialistLogError> =>\n Effect.try({\n try: () => appendToRunLogSync(projectKey, specialistType, runId, content),\n catch: (cause) => liftLogError(projectKey, specialistType, 'appendToRunLog', cause),\n });\n\n/** Effect variant of `finalizeRunLog`. */\nexport const finalizeRunLog = (\n projectKey: string,\n specialistType: string,\n runId: string,\n result: { status: 'passed' | 'failed' | 'blocked' | 'incomplete'; notes?: string },\n): Effect.Effect<void, SpecialistLogError> =>\n Effect.try({\n try: () => finalizeRunLogSync(projectKey, specialistType, runId, result),\n catch: (cause) => liftLogError(projectKey, specialistType, 'finalizeRunLog', cause),\n });\n\n/** Effect variant of `getRunLog`. */\nexport const getRunLog = (\n projectKey: string,\n specialistType: string,\n runId: string,\n): Effect.Effect<string | null, SpecialistLogError> =>\n Effect.try({\n try: () => getRunLogSync(projectKey, specialistType, runId),\n catch: (cause) => liftLogError(projectKey, specialistType, 'getRunLog', cause),\n });\n\n/** Effect variant of `listRunLogs`. */\nexport const listRunLogs = (\n projectKey: string,\n specialistType: string,\n options: { limit?: number; offset?: number } = {},\n): Effect.Effect<RunLogEntry[], SpecialistLogError> =>\n Effect.try({\n try: () => listRunLogsSync(projectKey, specialistType, options),\n catch: (cause) => liftLogError(projectKey, specialistType, 'listRunLogs', cause),\n });\n\n/** Effect variant of `cleanupOldLogs`. */\nexport const cleanupOldLogs = (\n projectKey: string,\n specialistType: string,\n retention: { maxDays: number; maxRuns: number },\n): Effect.Effect<number, SpecialistLogError> =>\n Effect.try({\n try: () => cleanupOldLogsSync(projectKey, specialistType, retention),\n catch: (cause) => liftLogError(projectKey, specialistType, 'cleanupOldLogs', cause),\n });\n\n/** Effect variant of `cleanupAllLogs`. */\nexport const cleanupAllLogs = (): Effect.Effect<\n { totalDeleted: number; byProject: Record<string, Record<string, number>> },\n SpecialistLogError\n> =>\n Effect.try({\n try: () => cleanupAllLogsSync(),\n catch: (cause) => liftLogError('*', '*', 'cleanupAllLogs', cause),\n });\n"],"mappings":"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"}
1
+ {"version":3,"file":"specialist-logs--Uz-r8ia.js","names":[],"sources":["../../src/lib/cloister/specialist-logs.ts"],"sourcesContent":["/**\n * Specialist Log Management\n *\n * Manages persistent log files for specialist agent runs.\n * Each run produces a structured log file with metadata, context, and full transcript.\n *\n * Directory structure:\n * ~/.overdeck/specialists/{projectKey}/{specialistType}/runs/{timestamp}-{issueId}.log\n */\n\nimport { existsSync, mkdirSync, writeFileSync, appendFileSync, readFileSync, readdirSync, statSync, unlinkSync } from 'fs';\nimport { join, basename } from 'path';\nimport { Data, Effect } from 'effect';\nimport { getOverdeckHome } from '../paths.js';\n\n/** Get specialists directory (lazy to support test env overrides) */\nfunction getSpecialistsDir(): string {\n return join(getOverdeckHome(), 'specialists');\n}\n\n/**\n * Log file metadata\n */\nexport interface RunLogMetadata {\n runId: string;\n project: string;\n specialistType: string;\n issueId: string;\n startedAt: string;\n finishedAt?: string;\n status?: 'passed' | 'failed' | 'blocked' | 'incomplete';\n duration?: number; // in milliseconds\n notes?: string;\n}\n\n/**\n * Run log entry for listing\n */\nexport interface RunLogEntry {\n runId: string;\n filePath: string;\n metadata: RunLogMetadata;\n fileSize: number;\n createdAt: Date;\n}\n\n/**\n * Get the runs directory for a project's specialist\n */\nexport function getRunsDirectory(projectKey: string, specialistType: string): string {\n return join(getSpecialistsDir(), projectKey, specialistType, 'runs');\n}\n\n/**\n * Generate a run ID from timestamp and issue ID\n */\nexport function generateRunId(issueId: string): string {\n const timestamp = new Date().toISOString().replace(/[:.]/g, '-').substring(0, 19);\n return `${timestamp}-${issueId}`;\n}\n\n/**\n * Get the log file path for a run\n */\nexport function getRunLogPath(projectKey: string, specialistType: string, runId: string): string {\n const runsDir = getRunsDirectory(projectKey, specialistType);\n return join(runsDir, `${runId}.log`);\n}\n\n/**\n * Ensure runs directory exists for a project's specialist\n */\nfunction ensureRunsDirectory(projectKey: string, specialistType: string): void {\n const runsDir = getRunsDirectory(projectKey, specialistType);\n if (!existsSync(runsDir)) {\n mkdirSync(runsDir, { recursive: true });\n }\n}\n\n/**\n * Create a new run log file\n *\n * Initializes a log file with metadata header.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type (review-agent, test-agent, merge-agent)\n * @param issueId - Issue ID being worked on\n * @param contextSeed - Optional context digest that was provided to the specialist\n * @returns Run ID and file path\n */\nexport function createRunLogSync(\n projectKey: string,\n specialistType: string,\n issueId: string,\n contextSeed?: string\n): { runId: string; filePath: string } {\n ensureRunsDirectory(projectKey, specialistType);\n\n const runId = generateRunId(issueId);\n const filePath = getRunLogPath(projectKey, specialistType, runId);\n const startedAt = new Date().toISOString();\n\n // Create log header\n const header = `# ${specialistType} Run - ${issueId}\nProject: ${projectKey}\nStarted: ${startedAt}\nIssue: ${issueId}\nRun ID: ${runId}\n\n## Context Seed\n${contextSeed ? contextSeed : '[No context digest available]'}\n\n## Session Transcript\n`;\n\n writeFileSync(filePath, header, 'utf-8');\n\n return { runId, filePath };\n}\n\n/**\n * Append content to a run log\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param runId - Run identifier\n * @param content - Content to append\n */\nexport function appendToRunLogSync(\n projectKey: string,\n specialistType: string,\n runId: string,\n content: string\n): void {\n const filePath = getRunLogPath(projectKey, specialistType, runId);\n\n if (!existsSync(filePath)) {\n throw new Error(`Run log not found: ${filePath}`);\n }\n\n appendFileSync(filePath, content, 'utf-8');\n}\n\n/**\n * Finalize a run log with result metadata\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param runId - Run identifier\n * @param result - Run result\n */\nexport function finalizeRunLogSync(\n projectKey: string,\n specialistType: string,\n runId: string,\n result: {\n status: 'passed' | 'failed' | 'blocked' | 'incomplete';\n notes?: string;\n }\n): void {\n const filePath = getRunLogPath(projectKey, specialistType, runId);\n\n if (!existsSync(filePath)) {\n throw new Error(`Run log not found: ${filePath}`);\n }\n\n // Read the log to extract start time\n const content = readFileSync(filePath, 'utf-8');\n const startMatch = content.match(/^Started: (.+)$/m);\n const startedAt = startMatch ? new Date(startMatch[1]) : new Date();\n const finishedAt = new Date();\n const duration = finishedAt.getTime() - startedAt.getTime();\n\n // Format duration\n const durationSeconds = Math.floor(duration / 1000);\n const minutes = Math.floor(durationSeconds / 60);\n const seconds = durationSeconds % 60;\n const durationStr = `${minutes}m ${seconds}s`;\n\n // Append result section\n const resultSection = `\n\n## Result\nStatus: ${result.status}\n${result.notes ? `Notes: ${result.notes}` : ''}\nDuration: ${durationStr}\nFinished: ${finishedAt.toISOString()}\n`;\n\n appendFileSync(filePath, resultSection, 'utf-8');\n}\n\n/**\n * Read a run log file\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param runId - Run identifier\n * @returns Log content or null if not found\n */\nexport function getRunLogSync(\n projectKey: string,\n specialistType: string,\n runId: string\n): string | null {\n const filePath = getRunLogPath(projectKey, specialistType, runId);\n\n if (!existsSync(filePath)) {\n return null;\n }\n\n try {\n return readFileSync(filePath, 'utf-8');\n } catch (error) {\n console.error(`Failed to read run log ${runId}:`, error);\n return null;\n }\n}\n\n/**\n * Parse metadata from a log file\n *\n * @param logContent - Log file content\n * @returns Parsed metadata\n */\nexport function parseLogMetadata(logContent: string): Partial<RunLogMetadata> {\n const metadata: Partial<RunLogMetadata> = {};\n\n // Extract metadata from header\n const projectMatch = logContent.match(/^Project: (.+)$/m);\n const startedMatch = logContent.match(/^Started: (.+)$/m);\n const issueMatch = logContent.match(/^Issue: (.+)$/m);\n const runIdMatch = logContent.match(/^Run ID: (.+)$/m);\n const statusMatch = logContent.match(/^Status: (.+)$/m);\n const notesMatch = logContent.match(/^Notes: (.+)$/m);\n const finishedMatch = logContent.match(/^Finished: (.+)$/m);\n const durationMatch = logContent.match(/^Duration: (.+)$/m);\n\n if (projectMatch) metadata.project = projectMatch[1].trim();\n if (startedMatch) metadata.startedAt = startedMatch[1].trim();\n if (issueMatch) metadata.issueId = issueMatch[1].trim();\n if (runIdMatch) metadata.runId = runIdMatch[1].trim();\n if (statusMatch) metadata.status = statusMatch[1].trim() as RunLogMetadata['status'];\n if (notesMatch) metadata.notes = notesMatch[1].trim();\n if (finishedMatch) metadata.finishedAt = finishedMatch[1].trim();\n\n // Parse duration if available\n if (durationMatch) {\n const durationStr = durationMatch[1].trim();\n const minutesMatch = durationStr.match(/(\\d+)m/);\n const secondsMatch = durationStr.match(/(\\d+)s/);\n const minutes = minutesMatch ? parseInt(minutesMatch[1], 10) : 0;\n const seconds = secondsMatch ? parseInt(secondsMatch[1], 10) : 0;\n metadata.duration = (minutes * 60 + seconds) * 1000;\n }\n\n return metadata;\n}\n\n/**\n * List all run logs for a project's specialist\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param options - Listing options\n * @returns Array of run log entries, sorted by most recent first\n */\nexport function listRunLogsSync(\n projectKey: string,\n specialistType: string,\n options: {\n limit?: number;\n offset?: number;\n } = {}\n): RunLogEntry[] {\n const runsDir = getRunsDirectory(projectKey, specialistType);\n\n if (!existsSync(runsDir)) {\n return [];\n }\n\n try {\n const files = readdirSync(runsDir)\n .filter(f => f.endsWith('.log'))\n .map(f => {\n const filePath = join(runsDir, f);\n const stats = statSync(filePath);\n const runId = basename(f, '.log');\n\n // Read file to extract metadata\n const content = readFileSync(filePath, 'utf-8');\n const metadata = parseLogMetadata(content);\n\n return {\n runId,\n filePath,\n metadata: {\n runId,\n project: projectKey,\n specialistType,\n issueId: metadata.issueId || 'unknown',\n startedAt: metadata.startedAt || stats.birthtime.toISOString(),\n finishedAt: metadata.finishedAt,\n status: metadata.status,\n duration: metadata.duration,\n notes: metadata.notes,\n },\n fileSize: stats.size,\n createdAt: stats.birthtime,\n };\n });\n\n // Sort by most recent first, with runId as tiebreaker for stable ordering\n files.sort((a, b) => {\n const timeDiff = b.createdAt.getTime() - a.createdAt.getTime();\n if (timeDiff !== 0) return timeDiff;\n return b.runId.localeCompare(a.runId);\n });\n\n // Apply pagination\n const { limit, offset = 0 } = options;\n if (limit !== undefined) {\n return files.slice(offset, offset + limit);\n }\n\n return files.slice(offset);\n } catch (error) {\n console.error(`Failed to list run logs for ${projectKey}/${specialistType}:`, error);\n return [];\n }\n}\n\n/**\n * Get the most recent N run logs\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param count - Number of recent runs to retrieve\n * @returns Array of recent run log entries\n */\nexport function getRecentRunLogs(\n projectKey: string,\n specialistType: string,\n count: number\n): RunLogEntry[] {\n return listRunLogsSync(projectKey, specialistType, { limit: count });\n}\n\n/**\n * Clean up old run logs based on retention policy\n *\n * Keeps logs that match either criteria (whichever is more permissive):\n * - Within maxDays\n * - Within the last maxRuns count\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param retention - Retention policy\n * @returns Number of logs deleted\n */\nexport function cleanupOldLogsSync(\n projectKey: string,\n specialistType: string,\n retention: { maxDays: number; maxRuns: number }\n): number {\n const { maxDays, maxRuns } = retention;\n\n // Compute cutoff BEFORE reading file stats. This ensures all files that existed\n // when cleanup was invoked have birthtimes <= cutoffDate when maxDays=0, avoiding\n // a race where a file created in the same millisecond as cutoffDate would be\n // incorrectly retained by a >= comparison.\n const now = new Date();\n const cutoffDate = new Date(now.getTime() - maxDays * 24 * 60 * 60 * 1000);\n\n const allLogs = listRunLogsSync(projectKey, specialistType);\n\n if (allLogs.length === 0) {\n return 0;\n }\n\n let deletedCount = 0;\n\n allLogs.forEach((log, index) => {\n // Keep if within maxRuns (most recent N runs)\n if (index < maxRuns) {\n return;\n }\n\n // Keep if within maxDays. Skip the age check entirely when maxDays=0, because\n // \"within 0 days\" means no age-based protection — only maxRuns applies.\n // This avoids a timing/rounding issue: Node.js converts nanosecond birthtime\n // to milliseconds using standard rounding, so a file created at 431.6ms gets\n // birthtime 432ms. If cutoff is 431ms (same wall-clock millisecond), the file\n // incorrectly appears newer than the cutoff and gets retained.\n if (maxDays > 0 && log.createdAt >= cutoffDate) {\n return;\n }\n\n // Delete this log\n try {\n unlinkSync(log.filePath);\n deletedCount++;\n console.log(`[specialist-logs] Deleted old log: ${log.runId}`);\n } catch (error) {\n console.error(`[specialist-logs] Failed to delete ${log.runId}:`, error);\n }\n });\n\n return deletedCount;\n}\n\n/**\n * Check if a run log is still active (not finalized)\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param runId - Run identifier\n * @returns True if log exists but has no result section yet\n */\nexport function isRunLogActive(\n projectKey: string,\n specialistType: string,\n runId: string\n): boolean {\n const content = getRunLogSync(projectKey, specialistType, runId);\n\n if (!content) {\n return false;\n }\n\n // Check if Result section exists\n return !content.includes('## Result');\n}\n\n/**\n * Get file size of a run log (useful for truncation check)\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param runId - Run identifier\n * @returns File size in bytes or null if not found\n */\nexport function getRunLogSize(\n projectKey: string,\n specialistType: string,\n runId: string\n): number | null {\n const filePath = getRunLogPath(projectKey, specialistType, runId);\n\n if (!existsSync(filePath)) {\n return null;\n }\n\n try {\n const stats = statSync(filePath);\n return stats.size;\n } catch (error) {\n return null;\n }\n}\n\n/**\n * Maximum log file size (10MB) before truncation warning\n */\nexport const MAX_LOG_SIZE = 10 * 1024 * 1024;\n\n/**\n * Check if a log file is approaching or exceeding size limits\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param runId - Run identifier\n * @returns Warning info or null if size is OK\n */\nexport function checkLogSizeLimit(\n projectKey: string,\n specialistType: string,\n runId: string\n): { exceeded: boolean; size: number; limit: number } | null {\n const size = getRunLogSize(projectKey, specialistType, runId);\n\n if (size === null) {\n return null;\n }\n\n if (size >= MAX_LOG_SIZE) {\n return {\n exceeded: true,\n size,\n limit: MAX_LOG_SIZE,\n };\n }\n\n return null;\n}\n\n/**\n * Clean up old logs for all projects and specialists\n *\n * Runs cleanup based on retention policies configured in projects.yaml.\n * This should be called periodically (e.g., daily cron job).\n *\n * @returns Summary of cleanup results\n */\nexport function cleanupAllLogsSync(): {\n totalDeleted: number;\n byProject: Record<string, Record<string, number>>;\n} {\n const { listProjectsWithSpecialists } = require('./specialists.js');\n const { getSpecialistRetention } = require('../projects.js');\n\n const results = {\n totalDeleted: 0,\n byProject: {} as Record<string, Record<string, number>>,\n };\n\n const projects = listProjectsWithSpecialists();\n\n for (const projectKey of projects) {\n results.byProject[projectKey] = {};\n\n // Get retention policy for this project\n const retention = getSpecialistRetention(projectKey);\n\n // Clean up each specialist type\n const specialistTypes = ['review-agent', 'test-agent', 'merge-agent'];\n\n for (const specialistType of specialistTypes) {\n const deleted = cleanupOldLogsSync(projectKey, specialistType, retention);\n\n if (deleted > 0) {\n results.byProject[projectKey][specialistType] = deleted;\n results.totalDeleted += deleted;\n }\n }\n }\n\n console.log(`[specialist-logs] Cleanup complete: deleted ${results.totalDeleted} old logs`);\n\n return results;\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n//\n// Additive Effect wrappers around the existing sync APIs. The underlying file\n// I/O is sync (CLI-callable); these variants lift thrown exceptions into a\n// typed error channel so callers can compose specialist-log operations with\n// other Effect-native code. Migrate callers individually.\n\n/** Tagged error for specialist-log Effect variants. */\nexport class SpecialistLogError extends Data.TaggedError('SpecialistLogError')<{\n readonly projectKey: string;\n readonly specialistType: string;\n readonly operation: string;\n readonly message: string;\n readonly cause?: unknown;\n}> {}\n\nconst liftLogError = (\n projectKey: string,\n specialistType: string,\n operation: string,\n cause: unknown,\n): SpecialistLogError =>\n new SpecialistLogError({\n projectKey,\n specialistType,\n operation,\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n });\n\n/** Effect variant of `createRunLog`. */\nexport const createRunLog = (\n projectKey: string,\n specialistType: string,\n issueId: string,\n contextSeed?: string,\n): Effect.Effect<{ runId: string; filePath: string }, SpecialistLogError> =>\n Effect.try({\n try: () => createRunLogSync(projectKey, specialistType, issueId, contextSeed),\n catch: (cause) => liftLogError(projectKey, specialistType, 'createRunLog', cause),\n });\n\n/** Effect variant of `appendToRunLog`. */\nexport const appendToRunLog = (\n projectKey: string,\n specialistType: string,\n runId: string,\n content: string,\n): Effect.Effect<void, SpecialistLogError> =>\n Effect.try({\n try: () => appendToRunLogSync(projectKey, specialistType, runId, content),\n catch: (cause) => liftLogError(projectKey, specialistType, 'appendToRunLog', cause),\n });\n\n/** Effect variant of `finalizeRunLog`. */\nexport const finalizeRunLog = (\n projectKey: string,\n specialistType: string,\n runId: string,\n result: { status: 'passed' | 'failed' | 'blocked' | 'incomplete'; notes?: string },\n): Effect.Effect<void, SpecialistLogError> =>\n Effect.try({\n try: () => finalizeRunLogSync(projectKey, specialistType, runId, result),\n catch: (cause) => liftLogError(projectKey, specialistType, 'finalizeRunLog', cause),\n });\n\n/** Effect variant of `getRunLog`. */\nexport const getRunLog = (\n projectKey: string,\n specialistType: string,\n runId: string,\n): Effect.Effect<string | null, SpecialistLogError> =>\n Effect.try({\n try: () => getRunLogSync(projectKey, specialistType, runId),\n catch: (cause) => liftLogError(projectKey, specialistType, 'getRunLog', cause),\n });\n\n/** Effect variant of `listRunLogs`. */\nexport const listRunLogs = (\n projectKey: string,\n specialistType: string,\n options: { limit?: number; offset?: number } = {},\n): Effect.Effect<RunLogEntry[], SpecialistLogError> =>\n Effect.try({\n try: () => listRunLogsSync(projectKey, specialistType, options),\n catch: (cause) => liftLogError(projectKey, specialistType, 'listRunLogs', cause),\n });\n\n/** Effect variant of `cleanupOldLogs`. */\nexport const cleanupOldLogs = (\n projectKey: string,\n specialistType: string,\n retention: { maxDays: number; maxRuns: number },\n): Effect.Effect<number, SpecialistLogError> =>\n Effect.try({\n try: () => cleanupOldLogsSync(projectKey, specialistType, retention),\n catch: (cause) => liftLogError(projectKey, specialistType, 'cleanupOldLogs', cause),\n });\n\n/** Effect variant of `cleanupAllLogs`. */\nexport const cleanupAllLogs = (): Effect.Effect<\n { totalDeleted: number; byProject: Record<string, Record<string, number>> },\n SpecialistLogError\n> =>\n Effect.try({\n try: () => cleanupAllLogsSync(),\n catch: (cause) => liftLogError('*', '*', 'cleanupAllLogs', cause),\n });\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import { i as validateSpecialistAutoCompleteMetadata, n as _serverManagedMerges, r as firePostMergeLifecycle, t as specialistsRouteLayer } from "./specialists-D07ybnam.js";
1
+ import { i as validateSpecialistAutoCompleteMetadata, n as _serverManagedMerges, r as firePostMergeLifecycle, t as specialistsRouteLayer } from "./specialists-DEfrAbWs.js";
2
2
  export { _serverManagedMerges, specialistsRouteLayer as default, specialistsRouteLayer, firePostMergeLifecycle, validateSpecialistAutoCompleteMetadata };