panopticon-cli 0.6.5 → 0.6.7

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 (311) hide show
  1. package/README.md +2 -2
  2. package/dist/{agents-DfYify9s.js → agents-CfFDs52G.js} +14 -14
  3. package/dist/{agents-DfYify9s.js.map → agents-CfFDs52G.js.map} +1 -1
  4. package/dist/{agents-BKsVoIc9.js → agents-D_2oRFVf.js} +1 -1
  5. package/dist/{archive-planning-BJrZ3tmN.js → archive-planning-D97ziGec.js} +3 -3
  6. package/dist/{archive-planning-BJrZ3tmN.js.map → archive-planning-D97ziGec.js.map} +1 -1
  7. package/dist/{archive-planning-C3m3hfa5.js → archive-planning-DK90wn9Q.js} +1 -1
  8. package/dist/{browser-Cvdznzc0.js → browser-CX7jXfXX.js} +1 -1
  9. package/dist/{browser-Cvdznzc0.js.map → browser-CX7jXfXX.js.map} +1 -1
  10. package/dist/{clean-planning-DvhZAUv4.js → clean-planning-D_lz4aQq.js} +2 -2
  11. package/dist/{clean-planning-DvhZAUv4.js.map → clean-planning-D_lz4aQq.js.map} +1 -1
  12. package/dist/clean-planning-x1S-JdmO.js +2 -0
  13. package/dist/cli/index.js +291 -760
  14. package/dist/cli/index.js.map +1 -1
  15. package/dist/{close-issue-Dr7yZmrr.js → close-issue-CaFE0stN.js} +11 -7
  16. package/dist/close-issue-CaFE0stN.js.map +1 -0
  17. package/dist/close-issue-CjcfZI9s.js +2 -0
  18. package/dist/compact-beads-B0_qE1w3.js +2 -0
  19. package/dist/{compact-beads-BCOtIIRl.js → compact-beads-CjFkteSU.js} +2 -2
  20. package/dist/{compact-beads-BCOtIIRl.js.map → compact-beads-CjFkteSU.js.map} +1 -1
  21. package/dist/{config-CRzMQRgA.js → config-BQNKsi9G.js} +2 -2
  22. package/dist/{config-CRzMQRgA.js.map → config-BQNKsi9G.js.map} +1 -1
  23. package/dist/{config-BYgUzQ21.js → config-agyKgF5C.js} +1 -1
  24. package/dist/{config-yaml-BgOACZAB.js → config-yaml-DGbLSMCa.js} +1 -1
  25. package/dist/{config-yaml-BgOACZAB.js.map → config-yaml-DGbLSMCa.js.map} +1 -1
  26. package/dist/{config-yaml-fdyvyL0S.js → config-yaml-Dqt4FWQH.js} +1 -1
  27. package/dist/dashboard/{acceptance-criteria-e5iiHlRx.js → acceptance-criteria-Dk9hhiYj.js} +1 -1
  28. package/dist/dashboard/{acceptance-criteria-e5iiHlRx.js.map → acceptance-criteria-Dk9hhiYj.js.map} +1 -1
  29. package/dist/dashboard/{agent-enrichment-C67LJBgD.js → agent-enrichment-DdO7ZqjI.js} +11 -7
  30. package/dist/dashboard/agent-enrichment-DdO7ZqjI.js.map +1 -0
  31. package/dist/dashboard/{agent-enrichment-Cq0P1cNZ.js → agent-enrichment-dLeGE1fX.js} +1 -1
  32. package/dist/dashboard/{agents-YyO6t5Xa.js → agents-DCpQQ_W5.js} +14 -14
  33. package/dist/dashboard/{agents-YyO6t5Xa.js.map → agents-DCpQQ_W5.js.map} +1 -1
  34. package/dist/dashboard/{agents-BVBVCyat.js → agents-Dgh2TjSp.js} +1 -1
  35. package/dist/dashboard/{archive-planning-h-hAjk0P.js → archive-planning-BmW9UDTr.js} +3 -3
  36. package/dist/dashboard/{archive-planning-h-hAjk0P.js.map → archive-planning-BmW9UDTr.js.map} +1 -1
  37. package/dist/dashboard/{archive-planning-CScs1MOC.js → archive-planning-C3Ebf9yC.js} +1 -1
  38. package/dist/dashboard/{beads-qNB0yAHV.js → beads-Bv-AdX7G.js} +3 -3
  39. package/dist/dashboard/{beads-qNB0yAHV.js.map → beads-Bv-AdX7G.js.map} +1 -1
  40. package/dist/dashboard/{beads-D_FRedEJ.js → beads-By6-X07V.js} +1 -1
  41. package/dist/dashboard/clean-planning-D60L8rPY.js +2 -0
  42. package/dist/dashboard/{clean-planning-qafw99vY.js → clean-planning-VEJu5suh.js} +2 -2
  43. package/dist/dashboard/{clean-planning-qafw99vY.js.map → clean-planning-VEJu5suh.js.map} +1 -1
  44. package/dist/dashboard/close-issue-C2KeSKKJ.js +2 -0
  45. package/dist/dashboard/{close-issue-DfIggeZD.js → close-issue-DtKdsSTm.js} +11 -7
  46. package/dist/dashboard/close-issue-DtKdsSTm.js.map +1 -0
  47. package/dist/dashboard/compact-beads-C7BN5N11.js +2 -0
  48. package/dist/dashboard/{compact-beads-Dt0qTqsC.js → compact-beads-D8Vt3qyv.js} +2 -2
  49. package/dist/dashboard/{compact-beads-Dt0qTqsC.js.map → compact-beads-D8Vt3qyv.js.map} +1 -1
  50. package/dist/dashboard/{config-CUREjHP7.js → config-CDkGjnwy.js} +2 -2
  51. package/dist/dashboard/{config-CUREjHP7.js.map → config-CDkGjnwy.js.map} +1 -1
  52. package/dist/dashboard/{config-BeI3uy-8.js → config-CTXkBATQ.js} +1 -1
  53. package/dist/dashboard/{database-CozA13Wy.js → database-DhqASALP.js} +1 -1
  54. package/dist/dashboard/{database-C0y0hXBx.js → database-cxmQryoh.js} +2 -2
  55. package/dist/dashboard/{database-C0y0hXBx.js.map → database-cxmQryoh.js.map} +1 -1
  56. package/dist/dashboard/{dist-src-oG2iHzgI.js → dist-src-DTm11oQr.js} +1 -1
  57. package/dist/dashboard/{dist-src-oG2iHzgI.js.map → dist-src-DTm11oQr.js.map} +1 -1
  58. package/dist/dashboard/{event-store-D7kLBd07.js → event-store-VWWUmOfn.js} +1 -1
  59. package/dist/dashboard/{event-store-O9q0Gweh.js → event-store-vSmAA3Zp.js} +9 -4
  60. package/dist/dashboard/event-store-vSmAA3Zp.js.map +1 -0
  61. package/dist/dashboard/{factory-BnLdiQW-.js → factory-C8nhLGHB.js} +3 -3
  62. package/dist/dashboard/{factory-BnLdiQW-.js.map → factory-C8nhLGHB.js.map} +1 -1
  63. package/dist/dashboard/{feedback-writer-DyovUANg.js → feedback-writer-CudSe1WK.js} +2 -2
  64. package/dist/dashboard/{feedback-writer-DyovUANg.js.map → feedback-writer-CudSe1WK.js.map} +1 -1
  65. package/dist/dashboard/{feedback-writer-gSUv_W0h.js → feedback-writer-Wgv1cd1r.js} +1 -1
  66. package/dist/dashboard/{git-utils-BJRioREj.js → git-utils-C1m4SwAe.js} +1 -1
  67. package/dist/dashboard/{git-utils-BJRioREj.js.map → git-utils-C1m4SwAe.js.map} +1 -1
  68. package/dist/dashboard/{git-utils-BtCRddq3.js → git-utils-DQI8EYoj.js} +1 -1
  69. package/dist/dashboard/{github-app-XO-LBUGk.js → github-app-DClWjjHr.js} +1 -1
  70. package/dist/dashboard/{github-app-XO-LBUGk.js.map → github-app-DClWjjHr.js.map} +1 -1
  71. package/dist/dashboard/{health-events-db-584nYgJB.js → health-events-db-BMXQfInV.js} +1 -1
  72. package/dist/dashboard/{health-events-db-B3ChzN65.js → health-events-db-Do4NrOhC.js} +2 -2
  73. package/dist/dashboard/{health-events-db-B3ChzN65.js.map → health-events-db-Do4NrOhC.js.map} +1 -1
  74. package/dist/dashboard/{hooks-CKhs3N68.js → hooks-CB4T47NC.js} +1 -1
  75. package/dist/dashboard/{hooks-CErbP8Oq.js → hooks-CjqXOlNb.js} +2 -2
  76. package/dist/dashboard/{hooks-CErbP8Oq.js.map → hooks-CjqXOlNb.js.map} +1 -1
  77. package/dist/dashboard/hume-CA2pftu_.js +3 -0
  78. package/dist/dashboard/{hume-CX_U3Qha.js → hume-JsAlMOJC.js} +2 -2
  79. package/dist/dashboard/{hume-CX_U3Qha.js.map → hume-JsAlMOJC.js.map} +1 -1
  80. package/dist/dashboard/{inspect-agent-B57kGDUV.js → inspect-agent-7eour7EA.js} +3 -3
  81. package/dist/dashboard/{inspect-agent-B57kGDUV.js.map → inspect-agent-7eour7EA.js.map} +1 -1
  82. package/dist/dashboard/{io-yGovuG4U.js → io-CWlFW78i.js} +1 -1
  83. package/dist/dashboard/{io-AJg-mzFi.js → io-DKS6359z.js} +1 -1
  84. package/dist/dashboard/{io-AJg-mzFi.js.map → io-DKS6359z.js.map} +1 -1
  85. package/dist/dashboard/issue-id-vwYJdsf8.js +62 -0
  86. package/dist/dashboard/issue-id-vwYJdsf8.js.map +1 -0
  87. package/dist/dashboard/{issue-service-singleton-DQK42EqH.js → issue-service-singleton-Co__-6kL.js} +1 -1
  88. package/dist/dashboard/{issue-service-singleton-sb2HkB9f.js → issue-service-singleton-Wv4xBm3y.js} +7 -7
  89. package/dist/dashboard/{issue-service-singleton-sb2HkB9f.js.map → issue-service-singleton-Wv4xBm3y.js.map} +1 -1
  90. package/dist/dashboard/{label-cleanup-CZEsbtq9.js → label-cleanup-nVKTmIIW.js} +7 -4
  91. package/dist/dashboard/label-cleanup-nVKTmIIW.js.map +1 -0
  92. package/dist/dashboard/lifecycle-BcUmtkR4.js +7 -0
  93. package/dist/dashboard/{merge-agent-GLtMEsTu.js → merge-agent-CGN3TT0a.js} +1 -1
  94. package/dist/dashboard/{merge-agent-twroFuAh.js → merge-agent-yudQOPZc.js} +148 -46
  95. package/dist/dashboard/merge-agent-yudQOPZc.js.map +1 -0
  96. package/dist/dashboard/{paths-COdEvoXR.js → paths-BDyJ7BiV.js} +19 -2
  97. package/dist/dashboard/{paths-COdEvoXR.js.map → paths-BDyJ7BiV.js.map} +1 -1
  98. package/dist/dashboard/{pipeline-notifier-DM5AHG5Q.js → pipeline-notifier-CCSN-jar.js} +1 -1
  99. package/dist/dashboard/{pipeline-notifier-DM5AHG5Q.js.map → pipeline-notifier-CCSN-jar.js.map} +1 -1
  100. package/dist/dashboard/{plan-utils-BkCIhn3B.js → plan-utils-Bkcsqr_s.js} +3 -3
  101. package/dist/dashboard/{plan-utils-BkCIhn3B.js.map → plan-utils-Bkcsqr_s.js.map} +1 -1
  102. package/dist/dashboard/{prd-draft-D09Afalc.js → prd-draft-BD8oMkZ1.js} +2 -2
  103. package/dist/dashboard/{prd-draft-D09Afalc.js.map → prd-draft-BD8oMkZ1.js.map} +1 -1
  104. package/dist/dashboard/{projection-cache-DQ9zegkK.js → projection-cache-C0EL8s8h.js} +1 -1
  105. package/dist/dashboard/{projection-cache-DQ9zegkK.js.map → projection-cache-C0EL8s8h.js.map} +1 -1
  106. package/dist/dashboard/{projects-DyT3vSy-.js → projects-C5ozxjwP.js} +1 -1
  107. package/dist/dashboard/{projects-Cq3TWdPS.js → projects-CFVl4oHn.js} +25 -13
  108. package/dist/dashboard/projects-CFVl4oHn.js.map +1 -0
  109. package/dist/dashboard/{providers-Ck2sQd_F.js → providers-B5Y4H2Mg.js} +4 -4
  110. package/dist/dashboard/providers-B5Y4H2Mg.js.map +1 -0
  111. package/dist/dashboard/{providers-DVQnDekG.js → providers-csVZVPkE.js} +1 -1
  112. package/dist/dashboard/public/assets/{dist-CCJbQrSB.js → dist-CXaO6nOE.js} +1 -1
  113. package/dist/dashboard/public/assets/index-CzFZIb87.js +212 -0
  114. package/dist/dashboard/public/assets/index-OEEbThNN.css +1 -0
  115. package/dist/dashboard/public/index.html +2 -2
  116. package/dist/dashboard/rally-6McpKKRa.js +3 -0
  117. package/dist/dashboard/{rally-Cwuae-4C.js → rally-YjFRxIiC.js} +2 -2
  118. package/dist/dashboard/{rally-Cwuae-4C.js.map → rally-YjFRxIiC.js.map} +1 -1
  119. package/dist/dashboard/{rally-api-DSUxm7EO.js → rally-api-C0WqCSkT.js} +1 -1
  120. package/dist/dashboard/{rally-api-DSUxm7EO.js.map → rally-api-C0WqCSkT.js.map} +1 -1
  121. package/dist/dashboard/{rally-api-CEH5KZi4.js → rally-api-DNttdCW4.js} +1 -1
  122. package/dist/dashboard/{remote-BHTTMpJJ.js → remote-Cigqjj3f.js} +2 -2
  123. package/dist/dashboard/{remote-BXo_iIku.js → remote-ObpNZ7hF.js} +2 -2
  124. package/dist/dashboard/{remote-BXo_iIku.js.map → remote-ObpNZ7hF.js.map} +1 -1
  125. package/dist/dashboard/{remote-agents-CTKVhFFY.js → remote-agents-Bf3GuM7t.js} +1 -1
  126. package/dist/dashboard/{remote-agents-C0_0LLNd.js → remote-agents-DFyjT1Le.js} +1 -1
  127. package/dist/dashboard/{remote-agents-C0_0LLNd.js.map → remote-agents-DFyjT1Le.js.map} +1 -1
  128. package/dist/dashboard/{review-status-CK3eBGyb.js → review-status-BtXqWBhS.js} +1 -1
  129. package/dist/dashboard/{review-status-CV55Tl-n.js → review-status-Bymwzh2i.js} +44 -4
  130. package/dist/dashboard/{review-status-CV55Tl-n.js.map → review-status-Bymwzh2i.js.map} +1 -1
  131. package/dist/dashboard/server.js +559 -253
  132. package/dist/dashboard/server.js.map +1 -1
  133. package/dist/dashboard/{settings-CuHV-wcv.js → settings-BHlDG7TK.js} +2 -2
  134. package/dist/dashboard/settings-BHlDG7TK.js.map +1 -0
  135. package/dist/dashboard/settings-XWvDcj-D.js +2 -0
  136. package/dist/dashboard/{shadow-engineering-BUeZunaE.js → shadow-engineering-lIn1W_95.js} +1 -1
  137. package/dist/dashboard/{shadow-engineering-BUeZunaE.js.map → shadow-engineering-lIn1W_95.js.map} +1 -1
  138. package/dist/dashboard/{shadow-state-DHQ-kASN.js → shadow-state-BIexcxkv.js} +1 -1
  139. package/dist/dashboard/{shadow-state-DHQ-kASN.js.map → shadow-state-BIexcxkv.js.map} +1 -1
  140. package/dist/dashboard/{spawn-planning-session-8FFAqLdK.js → spawn-planning-session-33Jf-d5T.js} +6 -6
  141. package/dist/dashboard/{spawn-planning-session-8FFAqLdK.js.map → spawn-planning-session-33Jf-d5T.js.map} +1 -1
  142. package/dist/dashboard/{spawn-planning-session-U0Lqpjen.js → spawn-planning-session-D5hrVdWM.js} +1 -1
  143. package/dist/dashboard/{specialist-context-ColzlmGE.js → specialist-context-DGukHSn8.js} +6 -6
  144. package/dist/dashboard/{specialist-context-ColzlmGE.js.map → specialist-context-DGukHSn8.js.map} +1 -1
  145. package/dist/dashboard/{specialist-logs-BhmDpFIq.js → specialist-logs-CIw4qfTy.js} +1 -1
  146. package/dist/dashboard/{specialists-C6s3U6tX.js → specialists-B_zrayaP.js} +37 -36
  147. package/dist/dashboard/specialists-B_zrayaP.js.map +1 -0
  148. package/dist/dashboard/{specialists-Cny632-T.js → specialists-Cp-PgspS.js} +1 -1
  149. package/dist/dashboard/{test-agent-queue-tqI4VDsu.js → test-agent-queue-ypF_ecHo.js} +4 -4
  150. package/dist/dashboard/{test-agent-queue-tqI4VDsu.js.map → test-agent-queue-ypF_ecHo.js.map} +1 -1
  151. package/dist/dashboard/{tldr-daemon-BNFyS7W_.js → tldr-daemon-B_oLRD8z.js} +2 -2
  152. package/dist/dashboard/{tldr-daemon-BNFyS7W_.js.map → tldr-daemon-B_oLRD8z.js.map} +1 -1
  153. package/dist/dashboard/{tldr-daemon-A6JqC59u.js → tldr-daemon-Cfs0bXTi.js} +1 -1
  154. package/dist/dashboard/{tmux-DYGAVJfb.js → tmux-BzxdKItf.js} +1 -1
  155. package/dist/dashboard/{tmux-IlN1Slv-.js → tmux-LwG0tHhU.js} +2 -2
  156. package/dist/dashboard/{tmux-IlN1Slv-.js.map → tmux-LwG0tHhU.js.map} +1 -1
  157. package/dist/dashboard/{tracker-config-BzNLnmcE.js → tracker-config-BP59uH4V.js} +1 -1
  158. package/dist/dashboard/{tracker-config-CNM_5rEf.js → tracker-config-e7ph1QqT.js} +2 -2
  159. package/dist/dashboard/{tracker-config-CNM_5rEf.js.map → tracker-config-e7ph1QqT.js.map} +1 -1
  160. package/dist/dashboard/{tunnel-D2BkwU7k.js → tunnel-0RzzuXPf.js} +1 -1
  161. package/dist/dashboard/{tunnel-Dub2hiAA.js → tunnel-DldbBPWL.js} +2 -2
  162. package/dist/dashboard/{tunnel-Dub2hiAA.js.map → tunnel-DldbBPWL.js.map} +1 -1
  163. package/dist/dashboard/{types-CWA-o4UN.js → types-RKZjGE5N.js} +1 -1
  164. package/dist/dashboard/{types-CWA-o4UN.js.map → types-RKZjGE5N.js.map} +1 -1
  165. package/dist/dashboard/{vtt-parser-BAXygRf0.js → vtt-parser-99vFekRQ.js} +1 -1
  166. package/dist/dashboard/{vtt-parser-BAXygRf0.js.map → vtt-parser-99vFekRQ.js.map} +1 -1
  167. package/dist/dashboard/{work-agent-prompt-JYq_OugP.js → work-agent-prompt-fCg67nyo.js} +65 -10
  168. package/dist/dashboard/{work-agent-prompt-JYq_OugP.js.map → work-agent-prompt-fCg67nyo.js.map} +1 -1
  169. package/dist/dashboard/{work-type-router-Cxp8_ur2.js → work-type-router-CWVW2Wk_.js} +1 -1
  170. package/dist/dashboard/{work-type-router-Cxp8_ur2.js.map → work-type-router-CWVW2Wk_.js.map} +1 -1
  171. package/dist/dashboard/{work-type-router-Com2amST.js → work-type-router-Di5gCQwh.js} +1 -1
  172. package/dist/dashboard/{workflows-N1UTipYl.js → workflows-BSMipN07.js} +35 -17
  173. package/dist/dashboard/workflows-BSMipN07.js.map +1 -0
  174. package/dist/dashboard/workflows-DaYWQIS2.js +2 -0
  175. package/dist/dashboard/{workspace-config-cmp5_ipD.js → workspace-config-DVDR-Ukh.js} +1 -1
  176. package/dist/dashboard/workspace-config-DVDR-Ukh.js.map +1 -0
  177. package/dist/dashboard/{workspace-manager-CjpWPgzL.js → workspace-manager-BYfzs_t2.js} +1 -1
  178. package/dist/dashboard/{workspace-manager-D_y9ZmW_.js → workspace-manager-C7OfT62A.js} +44 -24
  179. package/dist/dashboard/workspace-manager-C7OfT62A.js.map +1 -0
  180. package/dist/{dns-BKzHm-2q.js → dns-D_aKQJjb.js} +1 -1
  181. package/dist/{dns-DZwOWvVO.js → dns-Yxq4NNS7.js} +1 -1
  182. package/dist/{dns-DZwOWvVO.js.map → dns-Yxq4NNS7.js.map} +1 -1
  183. package/dist/{factory-DFu3IT4r.js → factory-BRBGw6OB.js} +1 -1
  184. package/dist/{factory-DfzczxN1.js → factory-DzsOiZVc.js} +3 -3
  185. package/dist/{factory-DfzczxN1.js.map → factory-DzsOiZVc.js.map} +1 -1
  186. package/dist/{feedback-writer-CwdnOkPO.js → feedback-writer-ygXN5F9N.js} +2 -2
  187. package/dist/{feedback-writer-CwdnOkPO.js.map → feedback-writer-ygXN5F9N.js.map} +1 -1
  188. package/dist/{github-app-CHKwxOeQ.js → github-app-DykduJ0X.js} +1 -1
  189. package/dist/{github-app-CHKwxOeQ.js.map → github-app-DykduJ0X.js.map} +1 -1
  190. package/dist/hume-9nv1VmMV.js +3 -0
  191. package/dist/{hume-DnV-tDsh.js → hume-DoCbph2h.js} +2 -2
  192. package/dist/{hume-DnV-tDsh.js.map → hume-DoCbph2h.js.map} +1 -1
  193. package/dist/index.d.ts +17 -2
  194. package/dist/index.d.ts.map +1 -1
  195. package/dist/index.js +8 -7
  196. package/dist/issue-id-CAcekoIw.js +62 -0
  197. package/dist/issue-id-CAcekoIw.js.map +1 -0
  198. package/dist/{label-cleanup-31ElPqqv.js → label-cleanup-C8R9Rspn.js} +7 -4
  199. package/dist/label-cleanup-C8R9Rspn.js.map +1 -0
  200. package/dist/{manifest-DL0oDbpv.js → manifest-B4ghOD-V.js} +1 -1
  201. package/dist/{manifest-DL0oDbpv.js.map → manifest-B4ghOD-V.js.map} +1 -1
  202. package/dist/{merge-agent-VQH9z9t8.js → merge-agent-DlUiUanN.js} +86 -33
  203. package/dist/merge-agent-DlUiUanN.js.map +1 -0
  204. package/dist/{paths-lMaxrYtT.js → paths-CDJ_HsbN.js} +19 -2
  205. package/dist/{paths-lMaxrYtT.js.map → paths-CDJ_HsbN.js.map} +1 -1
  206. package/dist/{pipeline-notifier-OJ-d3Y60.js → pipeline-notifier-XgDdCdvT.js} +1 -1
  207. package/dist/{pipeline-notifier-OJ-d3Y60.js.map → pipeline-notifier-XgDdCdvT.js.map} +1 -1
  208. package/dist/{projects-CvLepaxC.js → projects-Bk-5QhFQ.js} +25 -13
  209. package/dist/projects-Bk-5QhFQ.js.map +1 -0
  210. package/dist/{projects-DMWmPeIU.js → projects-DhU7rAVN.js} +1 -1
  211. package/dist/{providers-DcCPZ5K4.js → providers-DSU1vfQF.js} +4 -4
  212. package/dist/providers-DSU1vfQF.js.map +1 -0
  213. package/dist/rally-DdPvGa-w.js +3 -0
  214. package/dist/{rally-uUUZXp1h.js → rally-Dy00NElU.js} +1 -1
  215. package/dist/{rally-uUUZXp1h.js.map → rally-Dy00NElU.js.map} +1 -1
  216. package/dist/{remote-CkLBqLJc.js → remote-CYiOJg0q.js} +2 -2
  217. package/dist/{remote-CkLBqLJc.js.map → remote-CYiOJg0q.js.map} +1 -1
  218. package/dist/{remote-agents-C5Bd2fgt.js → remote-agents-CZXrUF4f.js} +1 -1
  219. package/dist/{remote-agents-C5Bd2fgt.js.map → remote-agents-CZXrUF4f.js.map} +1 -1
  220. package/dist/{remote-agents-BTzD-wMQ.js → remote-agents-ycHHVsgf.js} +1 -1
  221. package/dist/{remote-workspace-Dxghqiti.js → remote-workspace-CA33UuVI.js} +4 -4
  222. package/dist/{remote-workspace-Dxghqiti.js.map → remote-workspace-CA33UuVI.js.map} +1 -1
  223. package/dist/{review-status-2TdtHNcs.js → review-status-D6H2WOw8.js} +1 -1
  224. package/dist/{review-status-Bm1bWNEa.js → review-status-DEDvCKMP.js} +44 -4
  225. package/dist/{review-status-Bm1bWNEa.js.map → review-status-DEDvCKMP.js.map} +1 -1
  226. package/dist/{tracker-C_62ukEq.js → settings-BcWPTrua.js} +7 -199
  227. package/dist/settings-BcWPTrua.js.map +1 -0
  228. package/dist/shadow-state-BZzxfEGw.js +2 -0
  229. package/dist/{shadow-state-CFFHf05M.js → shadow-state-CE3dQfll.js} +1 -1
  230. package/dist/{shadow-state-CFFHf05M.js.map → shadow-state-CE3dQfll.js.map} +1 -1
  231. package/dist/{specialist-context-BdNFsfMG.js → specialist-context-BAUWL1Fl.js} +6 -6
  232. package/dist/{specialist-context-BdNFsfMG.js.map → specialist-context-BAUWL1Fl.js.map} +1 -1
  233. package/dist/{specialist-logs-CLztE_bE.js → specialist-logs-DQKKQV9B.js} +1 -1
  234. package/dist/{specialists-aUoUVWsN.js → specialists-Bfb9ATzw.js} +1 -1
  235. package/dist/{specialists-DEKqgkxp.js → specialists-D7Kj5o6s.js} +35 -34
  236. package/dist/specialists-D7Kj5o6s.js.map +1 -0
  237. package/dist/sync-DMfgd389.js +693 -0
  238. package/dist/sync-DMfgd389.js.map +1 -0
  239. package/dist/sync-TL6y-8K6.js +2 -0
  240. package/dist/{tldr-daemon-BCEFPItr.js → tldr-daemon-CFx4LXAl.js} +2 -2
  241. package/dist/{tldr-daemon-BCEFPItr.js.map → tldr-daemon-CFx4LXAl.js.map} +1 -1
  242. package/dist/{tldr-daemon-xBAx4cBE.js → tldr-daemon-D_EooADG.js} +1 -1
  243. package/dist/{tmux-DN6H886Y.js → tmux-CBtui_Cl.js} +1 -1
  244. package/dist/{tmux-CKdNxxJx.js → tmux-D6Ah4I8z.js} +2 -2
  245. package/dist/{tmux-CKdNxxJx.js.map → tmux-D6Ah4I8z.js.map} +1 -1
  246. package/dist/tracker-BhYYvU3p.js +198 -0
  247. package/dist/tracker-BhYYvU3p.js.map +1 -0
  248. package/dist/{tracker-utils-CVU2W1sX.js → tracker-utils-ChQyut8w.js} +34 -12
  249. package/dist/tracker-utils-ChQyut8w.js.map +1 -0
  250. package/dist/{traefik-DHgBoWXX.js → traefik-C80EbDu_.js} +4 -4
  251. package/dist/{traefik-DHgBoWXX.js.map → traefik-C80EbDu_.js.map} +1 -1
  252. package/dist/{traefik-BR-edbZv.js → traefik-CgHl7Bge.js} +1 -1
  253. package/dist/{tunnel-BZO9Q5oe.js → tunnel-DXOJ1wMM.js} +1 -1
  254. package/dist/{tunnel-Bl1qNSyQ.js → tunnel-DzXEPwIc.js} +2 -2
  255. package/dist/{tunnel-Bl1qNSyQ.js.map → tunnel-DzXEPwIc.js.map} +1 -1
  256. package/dist/{types-DewGdaIP.js → types-BhJj1SP1.js} +1 -1
  257. package/dist/{types-DewGdaIP.js.map → types-BhJj1SP1.js.map} +1 -1
  258. package/dist/{work-type-router-CS2BB1vS.js → work-type-router-CHjciPyS.js} +3 -3
  259. package/dist/{work-type-router-CS2BB1vS.js.map → work-type-router-CHjciPyS.js.map} +1 -1
  260. package/dist/{workspace-config-CNXOpKuj.js → workspace-config-fUafvYMp.js} +1 -1
  261. package/dist/workspace-config-fUafvYMp.js.map +1 -0
  262. package/dist/workspace-manager-B9jS4Dsq.js +3 -0
  263. package/dist/{workspace-manager-CncdZkIy.js → workspace-manager-DuLhnzJV.js} +112 -27
  264. package/dist/workspace-manager-DuLhnzJV.js.map +1 -0
  265. package/package.json +2 -1
  266. package/scripts/post-merge-deploy.sh +25 -5
  267. package/scripts/record-cost-event.js +57 -7
  268. package/scripts/record-cost-event.js.map +1 -1
  269. package/skills/pan-help/SKILL.md +1 -1
  270. package/skills/pan-sync/SKILL.md +6 -6
  271. package/skills/workspace-add-repo/skill.md +46 -0
  272. package/templates/claude-md/sections/warnings.md +15 -2
  273. package/dist/clean-planning-sZXvy3Y5.js +0 -2
  274. package/dist/close-issue-Dml437qV.js +0 -2
  275. package/dist/close-issue-Dr7yZmrr.js.map +0 -1
  276. package/dist/compact-beads-iu218JcO.js +0 -2
  277. package/dist/dashboard/agent-enrichment-C67LJBgD.js.map +0 -1
  278. package/dist/dashboard/clean-planning-DCu3cOTu.js +0 -2
  279. package/dist/dashboard/close-issue-DfIggeZD.js.map +0 -1
  280. package/dist/dashboard/close-issue-DwdwYtar.js +0 -2
  281. package/dist/dashboard/compact-beads-DXY2fK2s.js +0 -2
  282. package/dist/dashboard/event-store-O9q0Gweh.js.map +0 -1
  283. package/dist/dashboard/hume-MZndNDVU.js +0 -3
  284. package/dist/dashboard/label-cleanup-CZEsbtq9.js.map +0 -1
  285. package/dist/dashboard/lifecycle-ZTYdrr2O.js +0 -7
  286. package/dist/dashboard/merge-agent-twroFuAh.js.map +0 -1
  287. package/dist/dashboard/projects-Cq3TWdPS.js.map +0 -1
  288. package/dist/dashboard/providers-Ck2sQd_F.js.map +0 -1
  289. package/dist/dashboard/public/assets/index-CpSmB2ts.css +0 -1
  290. package/dist/dashboard/public/assets/index-yarWhi0M.js +0 -214
  291. package/dist/dashboard/rally-CQ1OBJrJ.js +0 -3
  292. package/dist/dashboard/settings-CuHV-wcv.js.map +0 -1
  293. package/dist/dashboard/settings-DMeGBRsk.js +0 -2
  294. package/dist/dashboard/specialists-C6s3U6tX.js.map +0 -1
  295. package/dist/dashboard/workflows-B2ARUpOa.js +0 -2
  296. package/dist/dashboard/workflows-N1UTipYl.js.map +0 -1
  297. package/dist/dashboard/workspace-config-cmp5_ipD.js.map +0 -1
  298. package/dist/dashboard/workspace-manager-D_y9ZmW_.js.map +0 -1
  299. package/dist/hume-BjmwmJ9E.js +0 -3
  300. package/dist/label-cleanup-31ElPqqv.js.map +0 -1
  301. package/dist/merge-agent-VQH9z9t8.js.map +0 -1
  302. package/dist/projects-CvLepaxC.js.map +0 -1
  303. package/dist/providers-DcCPZ5K4.js.map +0 -1
  304. package/dist/rally-DR9x8--6.js +0 -3
  305. package/dist/shadow-state-p3jpGRPJ.js +0 -2
  306. package/dist/specialists-DEKqgkxp.js.map +0 -1
  307. package/dist/tracker-C_62ukEq.js.map +0 -1
  308. package/dist/tracker-utils-CVU2W1sX.js.map +0 -1
  309. package/dist/workspace-config-CNXOpKuj.js.map +0 -1
  310. package/dist/workspace-manager-CncdZkIy.js.map +0 -1
  311. package/dist/workspace-manager-Cx0r2Jnv.js +0 -3
@@ -1,4 +1,4 @@
1
- import { g as init_paths } from "./paths-COdEvoXR.js";
1
+ import { _ as init_paths } from "./paths-BDyJ7BiV.js";
2
2
  import "fs";
3
3
  //#region ../../lib/settings.ts
4
4
  init_paths();
@@ -39,4 +39,4 @@ function getAgentCommand(modelId) {
39
39
  //#endregion
40
40
  export { getClaudeModelFlag as n, isAnthropicModel as r, getAgentCommand as t };
41
41
 
42
- //# sourceMappingURL=settings-CuHV-wcv.js.map
42
+ //# sourceMappingURL=settings-BHlDG7TK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-BHlDG7TK.js","names":[],"sources":["../../src/lib/settings.ts"],"sourcesContent":["import { readFileSync, writeFileSync, existsSync } from 'fs';\nimport { SETTINGS_FILE } from './paths.js';\n\n// Model identifiers\nexport type AnthropicModel = 'claude-opus-4-6' | 'claude-sonnet-4-6' | 'claude-sonnet-4-5' | 'claude-haiku-4-5';\nexport type OpenAIModel = 'gpt-5.2-codex' | 'o3-deep-research' | 'gpt-4o' | 'gpt-4o-mini';\nexport type GoogleModel = 'gemini-3-pro-preview' | 'gemini-3-flash-preview' | 'gemini-2.5-pro' | 'gemini-2.5-flash';\nexport type ZAIModel = 'glm-4.7-flash';\nexport type KimiModel = 'kimi-k2' | 'kimi-k2.5';\nexport type MiniMaxModel = 'minimax-m2.7' | 'minimax-m2.7-highspeed';\nexport type ModelId = AnthropicModel | OpenAIModel | GoogleModel | ZAIModel | KimiModel | MiniMaxModel;\n\n// Task complexity levels\nexport type ComplexityLevel = 'trivial' | 'simple' | 'medium' | 'complex' | 'expert';\n\n// Specialist agent types\nexport interface SpecialistModels {\n review_agent: ModelId;\n test_agent: ModelId;\n merge_agent: ModelId;\n}\n\n// Complexity-based model mapping\nexport type ComplexityModels = {\n [K in ComplexityLevel]: ModelId;\n};\n\n// All model configuration\nexport interface ModelsConfig {\n specialists: SpecialistModels;\n status_review: ModelId;\n complexity: ComplexityModels;\n}\n\n// API keys for external providers\nexport interface ApiKeysConfig {\n openai?: string;\n google?: string;\n zai?: string;\n kimi?: string;\n}\n\n// Complete settings structure\nexport interface SettingsConfig {\n models: ModelsConfig;\n api_keys: ApiKeysConfig;\n}\n\n// Default settings - match optimal defaults from settings-api.ts\nconst DEFAULT_SETTINGS: SettingsConfig = {\n models: {\n specialists: {\n review_agent: 'claude-opus-4-6',\n test_agent: 'claude-sonnet-4-6',\n merge_agent: 'claude-sonnet-4-6',\n },\n status_review: 'claude-opus-4-6',\n complexity: {\n trivial: 'claude-haiku-4-5',\n simple: 'claude-haiku-4-5',\n medium: 'kimi-k2.5',\n complex: 'kimi-k2.5',\n expert: 'claude-opus-4-6',\n },\n },\n api_keys: {},\n};\n\n/**\n * Deep merge utility that recursively merges objects.\n * - Recursively merges nested objects\n * - User values take precedence over defaults\n */\nfunction deepMerge<T extends object>(defaults: T, overrides: Partial<T>): T {\n const result = { ...defaults };\n\n for (const key of Object.keys(overrides) as (keyof T)[]) {\n const defaultVal = defaults[key];\n const overrideVal = overrides[key];\n\n // Skip undefined values in overrides\n if (overrideVal === undefined) continue;\n\n // Deep merge if both values are non-array objects\n if (\n typeof defaultVal === 'object' &&\n defaultVal !== null &&\n !Array.isArray(defaultVal) &&\n typeof overrideVal === 'object' &&\n overrideVal !== null &&\n !Array.isArray(overrideVal)\n ) {\n result[key] = deepMerge(defaultVal, overrideVal as any);\n } else {\n // For primitives or null - override wins\n result[key] = overrideVal as T[keyof T];\n }\n }\n\n return result;\n}\n\n/**\n * Load settings from ~/.panopticon/settings.json\n * Returns default settings if file doesn't exist or is invalid\n * Also loads API keys from environment variables as fallback\n */\nexport function loadSettings(): SettingsConfig {\n let settings: SettingsConfig;\n\n if (!existsSync(SETTINGS_FILE)) {\n settings = getDefaultSettings();\n } else {\n try {\n const content = readFileSync(SETTINGS_FILE, 'utf8');\n const parsed = JSON.parse(content) as Partial<SettingsConfig>;\n settings = deepMerge(DEFAULT_SETTINGS, parsed);\n } catch (error) {\n console.error('Warning: Failed to parse settings.json, using defaults');\n settings = getDefaultSettings();\n }\n }\n\n // Load API keys from environment variables as fallback\n // This allows using ~/.panopticon.env for API keys\n const envApiKeys: ApiKeysConfig = {};\n if (process.env.OPENAI_API_KEY) envApiKeys.openai = process.env.OPENAI_API_KEY;\n if (process.env.GOOGLE_API_KEY) envApiKeys.google = process.env.GOOGLE_API_KEY;\n if (process.env.ZAI_API_KEY) envApiKeys.zai = process.env.ZAI_API_KEY;\n if (process.env.KIMI_API_KEY) envApiKeys.kimi = process.env.KIMI_API_KEY;\n\n // Merge env vars as fallback (settings.json takes precedence)\n settings.api_keys = {\n ...envApiKeys,\n ...settings.api_keys,\n };\n\n return settings;\n}\n\n/**\n * Save settings to ~/.panopticon/settings.json\n * Writes with pretty formatting (2-space indent)\n */\nexport function saveSettings(settings: SettingsConfig): void {\n const content = JSON.stringify(settings, null, 2);\n writeFileSync(SETTINGS_FILE, content, 'utf8');\n}\n\n/**\n * Validate settings structure and model IDs\n * Returns error message if invalid, null if valid\n */\nexport function validateSettings(settings: SettingsConfig): string | null {\n // Validate models structure\n if (!settings.models) {\n return 'Missing models configuration';\n }\n\n // Validate specialists\n if (!settings.models.specialists) {\n return 'Missing specialists configuration';\n }\n const specialists = settings.models.specialists;\n if (!specialists.review_agent || !specialists.test_agent || !specialists.merge_agent) {\n return 'Missing specialist agent model configuration';\n }\n\n // Validate complexity levels\n if (!settings.models.complexity) {\n return 'Missing complexity configuration';\n }\n const complexity = settings.models.complexity;\n const requiredLevels: ComplexityLevel[] = ['trivial', 'simple', 'medium', 'complex', 'expert'];\n for (const level of requiredLevels) {\n if (!complexity[level]) {\n return `Missing complexity level: ${level}`;\n }\n }\n\n // Validate api_keys structure (optional keys)\n if (!settings.api_keys) {\n return 'Missing api_keys configuration';\n }\n\n return null;\n}\n\n/**\n * Get a deep copy of the default settings\n */\nexport function getDefaultSettings(): SettingsConfig {\n return JSON.parse(JSON.stringify(DEFAULT_SETTINGS));\n}\n\n/**\n * Get available models for a provider based on configured API keys\n * Returns empty array if provider API key is not configured\n */\nexport function getAvailableModels(settings: SettingsConfig): {\n anthropic: AnthropicModel[];\n openai: OpenAIModel[];\n google: GoogleModel[];\n zai: ZAIModel[];\n kimi: KimiModel[];\n} {\n const anthropicModels: AnthropicModel[] = [\n 'claude-opus-4-6',\n 'claude-sonnet-4-6',\n 'claude-haiku-4-5',\n ];\n\n const openaiModels: OpenAIModel[] = settings.api_keys.openai\n ? ['gpt-5.2-codex', 'o3-deep-research', 'gpt-4o', 'gpt-4o-mini']\n : [];\n\n const googleModels: GoogleModel[] = settings.api_keys.google\n ? ['gemini-3-pro-preview', 'gemini-3-flash-preview']\n : [];\n\n const zaiModels: ZAIModel[] = settings.api_keys.zai\n ? ['glm-4.7-flash']\n : [];\n\n const kimiModels: KimiModel[] = settings.api_keys.kimi\n ? ['kimi-k2', 'kimi-k2.5']\n : [];\n\n return {\n anthropic: anthropicModels,\n openai: openaiModels,\n google: googleModels,\n zai: zaiModels,\n kimi: kimiModels,\n };\n}\n\n/**\n * Check if a model ID is an Anthropic model\n * Anthropic models can be run directly with `claude` CLI\n */\nexport function isAnthropicModel(modelId: ModelId | string): boolean {\n return modelId.startsWith('claude-');\n}\n\n/**\n * Get the Claude CLI model flag for an Anthropic model\n * Maps our model IDs to Claude's expected format\n */\nexport function getClaudeModelFlag(modelId: ModelId | string): string {\n const modelMap: Record<string, string> = {\n 'claude-opus-4-6': 'opus',\n 'claude-sonnet-4-6': 'sonnet',\n 'claude-sonnet-4-5': 'sonnet',\n 'claude-haiku-4-5': 'haiku',\n };\n return modelMap[modelId] || 'sonnet';\n}\n\n/**\n * Get the command to run an agent with a specific model\n * Always uses 'claude' CLI — non-Anthropic models work via ANTHROPIC_BASE_URL env var\n * pointing to their Anthropic-compatible endpoint.\n */\nexport function getAgentCommand(modelId: ModelId | string): { command: string; args: string[] } {\n if (isAnthropicModel(modelId)) {\n return {\n command: 'claude',\n args: ['--model', getClaudeModelFlag(modelId)],\n };\n }\n // Non-Anthropic direct providers: use claude CLI with the model name as-is.\n // The caller must set ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN env vars.\n return {\n command: 'claude',\n args: ['--model', modelId],\n };\n}\n"],"mappings":";;;YAC2C;;;;;AAgP3C,SAAgB,iBAAiB,SAAoC;AACnE,QAAO,QAAQ,WAAW,UAAU;;;;;;AAOtC,SAAgB,mBAAmB,SAAmC;AAOpE,QANyC;EACvC,mBAAmB;EACnB,qBAAqB;EACrB,qBAAqB;EACrB,oBAAoB;EACrB,CACe,YAAY;;;;;;;AAQ9B,SAAgB,gBAAgB,SAAgE;AAC9F,KAAI,iBAAiB,QAAQ,CAC3B,QAAO;EACL,SAAS;EACT,MAAM,CAAC,WAAW,mBAAmB,QAAQ,CAAC;EAC/C;AAIH,QAAO;EACL,SAAS;EACT,MAAM,CAAC,WAAW,QAAQ;EAC3B"}
@@ -0,0 +1,2 @@
1
+ import { t as getAgentCommand } from "./settings-BHlDG7TK.js";
2
+ export { getAgentCommand };
@@ -191,4 +191,4 @@ async function runObserverCycle(config) {
191
191
  //#endregion
192
192
  export { gatherArtifacts, generateBasicInference, runObserverCycle, updateInferenceDocument };
193
193
 
194
- //# sourceMappingURL=shadow-engineering-BUeZunaE.js.map
194
+ //# sourceMappingURL=shadow-engineering-lIn1W_95.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"shadow-engineering-BUeZunaE.js","names":["execAsync"],"sources":["../../src/lib/shadow-engineering/monitoring-agent.ts","../../src/lib/shadow-engineering/observer-agent.ts"],"sourcesContent":["/**\n * Shadow Engineering - Monitoring Agent\n *\n * Analyzes artifacts (issue description, comments, transcripts, PRs, code changes)\n * and produces an INFERENCE.md - a living understanding document.\n *\n * The Monitoring Agent runs when a Shadow workspace is created and updates\n * INFERENCE.md as new artifacts arrive.\n */\n\nimport { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\n\nconst execAsync = promisify(exec);\n\nexport interface MonitoringAgentConfig {\n issueId: string;\n workspacePath: string;\n projectPath: string;\n}\n\nexport interface InferenceDocument {\n content: string;\n lastUpdated: string;\n artifactsAnalyzed: string[];\n gaps: string[];\n risks: string[];\n}\n\n/**\n * Gather all available artifacts for analysis\n */\nexport async function gatherArtifacts(config: MonitoringAgentConfig): Promise<{\n issueDescription?: string;\n comments: string[];\n transcripts: string[];\n notes: string[];\n codeChanges?: string;\n}> {\n const planningDir = join(config.workspacePath, '.planning');\n const artifacts: {\n issueDescription?: string;\n comments: string[];\n transcripts: string[];\n notes: string[];\n codeChanges?: string;\n } = {\n comments: [],\n transcripts: [],\n notes: [],\n };\n\n // Read issue description from tracker\n try {\n const { stdout } = await execAsync(\n `gh issue view ${config.issueId.replace(/^[A-Z]+-/, '')} --json body --jq '.body' 2>/dev/null`,\n { encoding: 'utf-8', timeout: 15000 }\n );\n if (stdout.trim()) {\n artifacts.issueDescription = stdout.trim();\n }\n } catch { /* GitHub may not be configured */ }\n\n // Read discussions from planning directory\n const discussionsDir = join(planningDir, 'discussions');\n if (existsSync(discussionsDir)) {\n for (const file of readdirSync(discussionsDir).filter(f => f.endsWith('.md'))) {\n artifacts.comments.push(readFileSync(join(discussionsDir, file), 'utf-8'));\n }\n }\n\n // Read transcripts\n const transcriptsDir = join(planningDir, 'transcripts');\n if (existsSync(transcriptsDir)) {\n for (const file of readdirSync(transcriptsDir).filter(f => f.endsWith('.md') || f.endsWith('.txt'))) {\n artifacts.transcripts.push(readFileSync(join(transcriptsDir, file), 'utf-8'));\n }\n }\n\n // Read notes\n const notesDir = join(planningDir, 'notes');\n if (existsSync(notesDir)) {\n for (const file of readdirSync(notesDir).filter(f => f.endsWith('.md') || f.endsWith('.txt'))) {\n artifacts.notes.push(readFileSync(join(notesDir, file), 'utf-8'));\n }\n }\n\n // Read recent code changes\n try {\n const { stdout } = await execAsync(\n `cd \"${config.workspacePath}\" && git log --oneline -20 --format=\"%h %s\" 2>/dev/null`,\n { encoding: 'utf-8', timeout: 10000 }\n );\n if (stdout.trim()) {\n artifacts.codeChanges = stdout.trim();\n }\n } catch { /* git may not be available */ }\n\n return artifacts;\n}\n\n/**\n * Generate the monitoring prompt for the Monitoring Agent\n */\nexport function generateMonitoringPrompt(\n config: MonitoringAgentConfig,\n artifacts: Awaited<ReturnType<typeof gatherArtifacts>>,\n existingInference?: string\n): string {\n const sections: string[] = [];\n\n sections.push(`# Shadow Engineering - Monitoring Task`);\n sections.push(`\\nYou are the Shadow Engineering Monitoring Agent for ${config.issueId}.`);\n sections.push(`Your job is to analyze all available artifacts and produce/update an INFERENCE.md document.`);\n sections.push(`\\nThis document should capture your understanding of what the team is building,`);\n sections.push(`identify gaps and ambiguities, surface risks, and track key decisions.`);\n\n if (existingInference) {\n sections.push(`\\n## Current INFERENCE.md\\n\\n${existingInference}`);\n }\n\n if (artifacts.issueDescription) {\n sections.push(`\\n## Issue Description\\n\\n${artifacts.issueDescription}`);\n }\n\n if (artifacts.comments.length > 0) {\n sections.push(`\\n## Discussion Comments\\n\\n${artifacts.comments.join('\\n\\n---\\n\\n')}`);\n }\n\n if (artifacts.transcripts.length > 0) {\n sections.push(`\\n## Meeting Transcripts\\n\\n${artifacts.transcripts.join('\\n\\n---\\n\\n')}`);\n }\n\n if (artifacts.notes.length > 0) {\n sections.push(`\\n## Notes\\n\\n${artifacts.notes.join('\\n\\n---\\n\\n')}`);\n }\n\n if (artifacts.codeChanges) {\n sections.push(`\\n## Recent Code Changes\\n\\n\\`\\`\\`\\n${artifacts.codeChanges}\\n\\`\\`\\``);\n }\n\n sections.push(`\\n## Your Task`);\n sections.push(`\\nAnalyze all the above artifacts and ${existingInference ? 'UPDATE' : 'CREATE'} the INFERENCE.md document.`);\n sections.push(`The document should include:`);\n sections.push(`1. **Summary**: What is the team building? (2-3 sentences)`);\n sections.push(`2. **Architecture**: Key technical decisions and patterns identified`);\n sections.push(`3. **Progress**: What has been done vs what remains`);\n sections.push(`4. **Gaps & Ambiguities**: Questions that need answers`);\n sections.push(`5. **Risks**: Potential issues or concerns`);\n sections.push(`6. **Team Patterns**: How the team works, conventions observed`);\n sections.push(`7. **Recommendations**: Suggestions for the team`);\n sections.push(`\\nWrite the INFERENCE.md content to: ${join(config.workspacePath, '.planning', 'INFERENCE.md')}`);\n\n return sections.join('\\n');\n}\n\n/**\n * Create a simple inference document from artifacts without using an LLM\n * (for cases where we want to generate it locally without spawning an agent)\n */\nexport function generateBasicInference(\n config: MonitoringAgentConfig,\n artifacts: Awaited<ReturnType<typeof gatherArtifacts>>\n): string {\n const now = new Date().toISOString();\n const sections: string[] = [];\n\n sections.push(`# Inference Document - ${config.issueId}`);\n sections.push(`\\n*Last updated: ${now}*`);\n sections.push(`\\n*Generated by Shadow Engineering Monitoring Agent*`);\n\n sections.push(`\\n## Status\\n`);\n const artifactCount = artifacts.comments.length + artifacts.transcripts.length + artifacts.notes.length;\n sections.push(`Analyzed ${artifactCount} artifact(s).`);\n\n if (artifacts.issueDescription) {\n sections.push(`\\n## Issue Summary\\n`);\n // Take first 500 chars as summary\n const summary = artifacts.issueDescription.slice(0, 500);\n sections.push(summary + (artifacts.issueDescription.length > 500 ? '...' : ''));\n }\n\n if (artifacts.codeChanges) {\n sections.push(`\\n## Recent Activity\\n`);\n sections.push('```');\n sections.push(artifacts.codeChanges);\n sections.push('```');\n }\n\n sections.push(`\\n## Artifacts Analyzed\\n`);\n if (artifacts.issueDescription) sections.push(`- Issue description`);\n sections.push(`- ${artifacts.comments.length} discussion comment(s)`);\n sections.push(`- ${artifacts.transcripts.length} transcript(s)`);\n sections.push(`- ${artifacts.notes.length} note(s)`);\n if (artifacts.codeChanges) sections.push(`- Code change history`);\n\n sections.push(`\\n## Gaps & Risks\\n`);\n sections.push(`(Requires deeper analysis - run full monitoring agent for detailed inference)`);\n\n return sections.join('\\n');\n}\n\n/**\n * Update the INFERENCE.md file\n */\nexport function updateInferenceDocument(workspacePath: string, content: string): void {\n const planningDir = join(workspacePath, '.planning');\n mkdirSync(planningDir, { recursive: true });\n writeFileSync(join(planningDir, 'INFERENCE.md'), content, 'utf-8');\n}\n\n/**\n * Read existing INFERENCE.md if it exists\n */\nexport function readInferenceDocument(workspacePath: string): string | null {\n const filePath = join(workspacePath, '.planning', 'INFERENCE.md');\n if (!existsSync(filePath)) return null;\n return readFileSync(filePath, 'utf-8');\n}\n","/**\n * Shadow Engineering - Observer Agent\n *\n * Watches team's development work and provides assistance.\n * Default mode: Watch-only (comments on PRs with observations).\n * Propose mode: Requires explicit `/shadow propose` command to create PRs.\n *\n * Monitors PR activity (polls initially), comments with observations,\n * tracks progress against Inference Document, flags deviations.\n */\n\nimport { readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { readInferenceDocument } from './monitoring-agent.js';\n\nconst execAsync = promisify(exec);\n\nexport interface ObserverAgentConfig {\n issueId: string;\n workspacePath: string;\n projectPath: string;\n repo: string; // owner/repo format\n mode: 'watch' | 'propose';\n}\n\nexport interface PRInfo {\n number: number;\n title: string;\n author: string;\n branch: string;\n state: string;\n body: string;\n additions: number;\n deletions: number;\n changedFiles: number;\n comments: Array<{ author: string; body: string; createdAt: string }>;\n}\n\n/**\n * Poll for PRs related to the issue\n */\nexport async function pollPRs(config: ObserverAgentConfig): Promise<PRInfo[]> {\n const prs: PRInfo[] = [];\n\n try {\n // Search for PRs mentioning the issue ID\n const { stdout } = await execAsync(\n `gh pr list --repo ${config.repo} --search \"${config.issueId}\" --json number,title,author,headRefName,state,body,additions,deletions,changedFiles,comments --limit 10`,\n { encoding: 'utf-8', timeout: 30000 }\n );\n\n const parsed = JSON.parse(stdout || '[]');\n for (const pr of parsed) {\n prs.push({\n number: pr.number,\n title: pr.title,\n author: pr.author?.login || 'unknown',\n branch: pr.headRefName,\n state: pr.state,\n body: pr.body || '',\n additions: pr.additions || 0,\n deletions: pr.deletions || 0,\n changedFiles: pr.changedFiles || 0,\n comments: (pr.comments || []).map((c: any) => ({\n author: c.author?.login || 'unknown',\n body: c.body,\n createdAt: c.createdAt,\n })),\n });\n }\n } catch (err: any) {\n console.warn(`Observer: Failed to poll PRs for ${config.issueId}:`, err.message);\n }\n\n return prs;\n}\n\n/**\n * Generate an observation for a PR\n */\nexport function generateObservation(\n pr: PRInfo,\n inference: string | null,\n config: ObserverAgentConfig\n): string | null {\n // Skip if we've already commented (check for our marker)\n const hasOurComment = pr.comments.some(c =>\n c.body.includes('[Shadow Engineering Observer]')\n );\n if (hasOurComment) return null;\n\n const sections: string[] = [];\n sections.push(`### [Shadow Engineering Observer] - Observations for #${pr.number}`);\n sections.push('');\n\n // Basic observations\n sections.push(`**PR Summary**: ${pr.title}`);\n sections.push(`**Changes**: +${pr.additions}/-${pr.deletions} across ${pr.changedFiles} file(s)`);\n sections.push('');\n\n if (inference) {\n sections.push(`**Alignment with Inference Document**: This PR appears to contribute to the documented plan.`);\n sections.push('');\n }\n\n // Size-based observation\n if (pr.additions + pr.deletions > 500) {\n sections.push(`> **Note**: This is a large PR (${pr.additions + pr.deletions} lines changed). Consider splitting into smaller reviews for easier review.`);\n sections.push('');\n }\n\n if (pr.changedFiles > 20) {\n sections.push(`> **Note**: This PR touches ${pr.changedFiles} files. Ensure adequate test coverage for all changed modules.`);\n sections.push('');\n }\n\n sections.push('---');\n sections.push('*This observation was generated by the Shadow Engineering Observer Agent in watch-only mode.*');\n\n return sections.join('\\n');\n}\n\n/**\n * Post a comment on a PR\n */\nexport async function postPRComment(\n repo: string,\n prNumber: number,\n comment: string\n): Promise<boolean> {\n try {\n await execAsync(\n `gh pr comment ${prNumber} --repo ${repo} --body \"${comment.replace(/\"/g, '\\\\\"')}\"`,\n { encoding: 'utf-8', timeout: 15000 }\n );\n return true;\n } catch (err: any) {\n console.warn(`Observer: Failed to comment on PR #${prNumber}:`, err.message);\n return false;\n }\n}\n\n/**\n * Generate the observer prompt for the Observer Agent\n */\nexport function generateObserverPrompt(\n config: ObserverAgentConfig,\n prs: PRInfo[],\n inference: string | null\n): string {\n const sections: string[] = [];\n\n sections.push(`# Shadow Engineering - Observer Task`);\n sections.push(`\\nYou are the Shadow Engineering Observer Agent for ${config.issueId}.`);\n sections.push(`Mode: ${config.mode === 'watch' ? 'WATCH-ONLY (comment only, no changes)' : 'PROPOSE (can create PRs when asked)'}`);\n\n if (config.mode === 'watch') {\n sections.push(`\\n**IMPORTANT**: You are in watch-only mode. You may ONLY:`);\n sections.push(`- Comment on PRs with observations, suggestions, and potential issues`);\n sections.push(`- Track progress against the Inference Document`);\n sections.push(`- Flag deviations or scope changes`);\n sections.push(`- Document patterns the team is using`);\n sections.push(`\\nYou may NOT create PRs, push code, or make changes.`);\n }\n\n if (inference) {\n sections.push(`\\n## Inference Document\\n\\n${inference}`);\n }\n\n if (prs.length > 0) {\n sections.push(`\\n## Active PRs\\n`);\n for (const pr of prs) {\n sections.push(`### PR #${pr.number}: ${pr.title}`);\n sections.push(`- Author: ${pr.author}`);\n sections.push(`- Branch: ${pr.branch}`);\n sections.push(`- Changes: +${pr.additions}/-${pr.deletions} (${pr.changedFiles} files)`);\n if (pr.body) sections.push(`- Description: ${pr.body.slice(0, 300)}`);\n sections.push('');\n }\n } else {\n sections.push(`\\n## Active PRs\\n\\nNo PRs found for ${config.issueId}.`);\n }\n\n sections.push(`\\n## Your Task`);\n if (config.mode === 'watch') {\n sections.push(`Review the PRs above and provide helpful observations.`);\n sections.push(`Focus on: code quality, alignment with the inference document, potential issues, and helpful suggestions.`);\n } else {\n sections.push(`Review the PRs above. You have been asked to propose changes.`);\n sections.push(`Create focused, well-tested PRs that address specific gaps identified in the Inference Document.`);\n }\n\n return sections.join('\\n');\n}\n\n/**\n * Run the observer agent once (poll and comment)\n * Returns the number of comments posted\n */\nexport async function runObserverCycle(config: ObserverAgentConfig): Promise<number> {\n const inference = readInferenceDocument(config.workspacePath);\n const prs = await pollPRs(config);\n\n let commentsPosted = 0;\n\n for (const pr of prs) {\n const observation = generateObservation(pr, inference, config);\n if (observation) {\n const posted = await postPRComment(config.repo, pr.number, observation);\n if (posted) commentsPosted++;\n }\n }\n\n return commentsPosted;\n}\n"],"mappings":";;;;;;;;;;;;;;AAgBA,MAAMA,cAAY,UAAU,KAAK;;;;AAmBjC,eAAsB,gBAAgB,QAMnC;CACD,MAAM,cAAc,KAAK,OAAO,eAAe,YAAY;CAC3D,MAAM,YAMF;EACF,UAAU,EAAE;EACZ,aAAa,EAAE;EACf,OAAO,EAAE;EACV;AAGD,KAAI;EACF,MAAM,EAAE,WAAW,MAAMA,YACvB,iBAAiB,OAAO,QAAQ,QAAQ,YAAY,GAAG,CAAC,wCACxD;GAAE,UAAU;GAAS,SAAS;GAAO,CACtC;AACD,MAAI,OAAO,MAAM,CACf,WAAU,mBAAmB,OAAO,MAAM;SAEtC;CAGR,MAAM,iBAAiB,KAAK,aAAa,cAAc;AACvD,KAAI,WAAW,eAAe,CAC5B,MAAK,MAAM,QAAQ,YAAY,eAAe,CAAC,QAAO,MAAK,EAAE,SAAS,MAAM,CAAC,CAC3E,WAAU,SAAS,KAAK,aAAa,KAAK,gBAAgB,KAAK,EAAE,QAAQ,CAAC;CAK9E,MAAM,iBAAiB,KAAK,aAAa,cAAc;AACvD,KAAI,WAAW,eAAe,CAC5B,MAAK,MAAM,QAAQ,YAAY,eAAe,CAAC,QAAO,MAAK,EAAE,SAAS,MAAM,IAAI,EAAE,SAAS,OAAO,CAAC,CACjG,WAAU,YAAY,KAAK,aAAa,KAAK,gBAAgB,KAAK,EAAE,QAAQ,CAAC;CAKjF,MAAM,WAAW,KAAK,aAAa,QAAQ;AAC3C,KAAI,WAAW,SAAS,CACtB,MAAK,MAAM,QAAQ,YAAY,SAAS,CAAC,QAAO,MAAK,EAAE,SAAS,MAAM,IAAI,EAAE,SAAS,OAAO,CAAC,CAC3F,WAAU,MAAM,KAAK,aAAa,KAAK,UAAU,KAAK,EAAE,QAAQ,CAAC;AAKrE,KAAI;EACF,MAAM,EAAE,WAAW,MAAMA,YACvB,OAAO,OAAO,cAAc,0DAC5B;GAAE,UAAU;GAAS,SAAS;GAAO,CACtC;AACD,MAAI,OAAO,MAAM,CACf,WAAU,cAAc,OAAO,MAAM;SAEjC;AAER,QAAO;;;;;;AA8DT,SAAgB,uBACd,QACA,WACQ;CACR,MAAM,uBAAM,IAAI,MAAM,EAAC,aAAa;CACpC,MAAM,WAAqB,EAAE;AAE7B,UAAS,KAAK,0BAA0B,OAAO,UAAU;AACzD,UAAS,KAAK,oBAAoB,IAAI,GAAG;AACzC,UAAS,KAAK,uDAAuD;AAErE,UAAS,KAAK,gBAAgB;CAC9B,MAAM,gBAAgB,UAAU,SAAS,SAAS,UAAU,YAAY,SAAS,UAAU,MAAM;AACjG,UAAS,KAAK,YAAY,cAAc,eAAe;AAEvD,KAAI,UAAU,kBAAkB;AAC9B,WAAS,KAAK,uBAAuB;EAErC,MAAM,UAAU,UAAU,iBAAiB,MAAM,GAAG,IAAI;AACxD,WAAS,KAAK,WAAW,UAAU,iBAAiB,SAAS,MAAM,QAAQ,IAAI;;AAGjF,KAAI,UAAU,aAAa;AACzB,WAAS,KAAK,yBAAyB;AACvC,WAAS,KAAK,MAAM;AACpB,WAAS,KAAK,UAAU,YAAY;AACpC,WAAS,KAAK,MAAM;;AAGtB,UAAS,KAAK,4BAA4B;AAC1C,KAAI,UAAU,iBAAkB,UAAS,KAAK,sBAAsB;AACpE,UAAS,KAAK,KAAK,UAAU,SAAS,OAAO,wBAAwB;AACrE,UAAS,KAAK,KAAK,UAAU,YAAY,OAAO,gBAAgB;AAChE,UAAS,KAAK,KAAK,UAAU,MAAM,OAAO,UAAU;AACpD,KAAI,UAAU,YAAa,UAAS,KAAK,wBAAwB;AAEjE,UAAS,KAAK,sBAAsB;AACpC,UAAS,KAAK,gFAAgF;AAE9F,QAAO,SAAS,KAAK,KAAK;;;;;AAM5B,SAAgB,wBAAwB,eAAuB,SAAuB;CACpF,MAAM,cAAc,KAAK,eAAe,YAAY;AACpD,WAAU,aAAa,EAAE,WAAW,MAAM,CAAC;AAC3C,eAAc,KAAK,aAAa,eAAe,EAAE,SAAS,QAAQ;;;;;AAMpE,SAAgB,sBAAsB,eAAsC;CAC1E,MAAM,WAAW,KAAK,eAAe,aAAa,eAAe;AACjE,KAAI,CAAC,WAAW,SAAS,CAAE,QAAO;AAClC,QAAO,aAAa,UAAU,QAAQ;;;;AC3MxC,MAAM,YAAY,UAAU,KAAK;;;;AA0BjC,eAAsB,QAAQ,QAAgD;CAC5E,MAAM,MAAgB,EAAE;AAExB,KAAI;EAEF,MAAM,EAAE,WAAW,MAAM,UACvB,qBAAqB,OAAO,KAAK,aAAa,OAAO,QAAQ,2GAC7D;GAAE,UAAU;GAAS,SAAS;GAAO,CACtC;EAED,MAAM,SAAS,KAAK,MAAM,UAAU,KAAK;AACzC,OAAK,MAAM,MAAM,OACf,KAAI,KAAK;GACP,QAAQ,GAAG;GACX,OAAO,GAAG;GACV,QAAQ,GAAG,QAAQ,SAAS;GAC5B,QAAQ,GAAG;GACX,OAAO,GAAG;GACV,MAAM,GAAG,QAAQ;GACjB,WAAW,GAAG,aAAa;GAC3B,WAAW,GAAG,aAAa;GAC3B,cAAc,GAAG,gBAAgB;GACjC,WAAW,GAAG,YAAY,EAAE,EAAE,KAAK,OAAY;IAC7C,QAAQ,EAAE,QAAQ,SAAS;IAC3B,MAAM,EAAE;IACR,WAAW,EAAE;IACd,EAAE;GACJ,CAAC;UAEG,KAAU;AACjB,UAAQ,KAAK,oCAAoC,OAAO,QAAQ,IAAI,IAAI,QAAQ;;AAGlF,QAAO;;;;;AAMT,SAAgB,oBACd,IACA,WACA,QACe;AAKf,KAHsB,GAAG,SAAS,MAAK,MACrC,EAAE,KAAK,SAAS,gCAAgC,CACjD,CACkB,QAAO;CAE1B,MAAM,WAAqB,EAAE;AAC7B,UAAS,KAAK,yDAAyD,GAAG,SAAS;AACnF,UAAS,KAAK,GAAG;AAGjB,UAAS,KAAK,mBAAmB,GAAG,QAAQ;AAC5C,UAAS,KAAK,iBAAiB,GAAG,UAAU,IAAI,GAAG,UAAU,UAAU,GAAG,aAAa,UAAU;AACjG,UAAS,KAAK,GAAG;AAEjB,KAAI,WAAW;AACb,WAAS,KAAK,+FAA+F;AAC7G,WAAS,KAAK,GAAG;;AAInB,KAAI,GAAG,YAAY,GAAG,YAAY,KAAK;AACrC,WAAS,KAAK,mCAAmC,GAAG,YAAY,GAAG,UAAU,6EAA6E;AAC1J,WAAS,KAAK,GAAG;;AAGnB,KAAI,GAAG,eAAe,IAAI;AACxB,WAAS,KAAK,+BAA+B,GAAG,aAAa,gEAAgE;AAC7H,WAAS,KAAK,GAAG;;AAGnB,UAAS,KAAK,MAAM;AACpB,UAAS,KAAK,gGAAgG;AAE9G,QAAO,SAAS,KAAK,KAAK;;;;;AAM5B,eAAsB,cACpB,MACA,UACA,SACkB;AAClB,KAAI;AACF,QAAM,UACJ,iBAAiB,SAAS,UAAU,KAAK,WAAW,QAAQ,QAAQ,MAAM,OAAM,CAAC,IACjF;GAAE,UAAU;GAAS,SAAS;GAAO,CACtC;AACD,SAAO;UACA,KAAU;AACjB,UAAQ,KAAK,sCAAsC,SAAS,IAAI,IAAI,QAAQ;AAC5E,SAAO;;;;;;;AA6DX,eAAsB,iBAAiB,QAA8C;CACnF,MAAM,YAAY,sBAAsB,OAAO,cAAc;CAC7D,MAAM,MAAM,MAAM,QAAQ,OAAO;CAEjC,IAAI,iBAAiB;AAErB,MAAK,MAAM,MAAM,KAAK;EACpB,MAAM,cAAc,oBAAoB,IAAI,WAAW,OAAO;AAC9D,MAAI;OACa,MAAM,cAAc,OAAO,MAAM,GAAG,QAAQ,YAAY,CAC3D;;;AAIhB,QAAO"}
1
+ {"version":3,"file":"shadow-engineering-lIn1W_95.js","names":["execAsync"],"sources":["../../src/lib/shadow-engineering/monitoring-agent.ts","../../src/lib/shadow-engineering/observer-agent.ts"],"sourcesContent":["/**\n * Shadow Engineering - Monitoring Agent\n *\n * Analyzes artifacts (issue description, comments, transcripts, PRs, code changes)\n * and produces an INFERENCE.md - a living understanding document.\n *\n * The Monitoring Agent runs when a Shadow workspace is created and updates\n * INFERENCE.md as new artifacts arrive.\n */\n\nimport { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\n\nconst execAsync = promisify(exec);\n\nexport interface MonitoringAgentConfig {\n issueId: string;\n workspacePath: string;\n projectPath: string;\n}\n\nexport interface InferenceDocument {\n content: string;\n lastUpdated: string;\n artifactsAnalyzed: string[];\n gaps: string[];\n risks: string[];\n}\n\n/**\n * Gather all available artifacts for analysis\n */\nexport async function gatherArtifacts(config: MonitoringAgentConfig): Promise<{\n issueDescription?: string;\n comments: string[];\n transcripts: string[];\n notes: string[];\n codeChanges?: string;\n}> {\n const planningDir = join(config.workspacePath, '.planning');\n const artifacts: {\n issueDescription?: string;\n comments: string[];\n transcripts: string[];\n notes: string[];\n codeChanges?: string;\n } = {\n comments: [],\n transcripts: [],\n notes: [],\n };\n\n // Read issue description from tracker\n try {\n const { stdout } = await execAsync(\n `gh issue view ${config.issueId.replace(/^[A-Z]+-/, '')} --json body --jq '.body' 2>/dev/null`,\n { encoding: 'utf-8', timeout: 15000 }\n );\n if (stdout.trim()) {\n artifacts.issueDescription = stdout.trim();\n }\n } catch { /* GitHub may not be configured */ }\n\n // Read discussions from planning directory\n const discussionsDir = join(planningDir, 'discussions');\n if (existsSync(discussionsDir)) {\n for (const file of readdirSync(discussionsDir).filter(f => f.endsWith('.md'))) {\n artifacts.comments.push(readFileSync(join(discussionsDir, file), 'utf-8'));\n }\n }\n\n // Read transcripts\n const transcriptsDir = join(planningDir, 'transcripts');\n if (existsSync(transcriptsDir)) {\n for (const file of readdirSync(transcriptsDir).filter(f => f.endsWith('.md') || f.endsWith('.txt'))) {\n artifacts.transcripts.push(readFileSync(join(transcriptsDir, file), 'utf-8'));\n }\n }\n\n // Read notes\n const notesDir = join(planningDir, 'notes');\n if (existsSync(notesDir)) {\n for (const file of readdirSync(notesDir).filter(f => f.endsWith('.md') || f.endsWith('.txt'))) {\n artifacts.notes.push(readFileSync(join(notesDir, file), 'utf-8'));\n }\n }\n\n // Read recent code changes\n try {\n const { stdout } = await execAsync(\n `cd \"${config.workspacePath}\" && git log --oneline -20 --format=\"%h %s\" 2>/dev/null`,\n { encoding: 'utf-8', timeout: 10000 }\n );\n if (stdout.trim()) {\n artifacts.codeChanges = stdout.trim();\n }\n } catch { /* git may not be available */ }\n\n return artifacts;\n}\n\n/**\n * Generate the monitoring prompt for the Monitoring Agent\n */\nexport function generateMonitoringPrompt(\n config: MonitoringAgentConfig,\n artifacts: Awaited<ReturnType<typeof gatherArtifacts>>,\n existingInference?: string\n): string {\n const sections: string[] = [];\n\n sections.push(`# Shadow Engineering - Monitoring Task`);\n sections.push(`\\nYou are the Shadow Engineering Monitoring Agent for ${config.issueId}.`);\n sections.push(`Your job is to analyze all available artifacts and produce/update an INFERENCE.md document.`);\n sections.push(`\\nThis document should capture your understanding of what the team is building,`);\n sections.push(`identify gaps and ambiguities, surface risks, and track key decisions.`);\n\n if (existingInference) {\n sections.push(`\\n## Current INFERENCE.md\\n\\n${existingInference}`);\n }\n\n if (artifacts.issueDescription) {\n sections.push(`\\n## Issue Description\\n\\n${artifacts.issueDescription}`);\n }\n\n if (artifacts.comments.length > 0) {\n sections.push(`\\n## Discussion Comments\\n\\n${artifacts.comments.join('\\n\\n---\\n\\n')}`);\n }\n\n if (artifacts.transcripts.length > 0) {\n sections.push(`\\n## Meeting Transcripts\\n\\n${artifacts.transcripts.join('\\n\\n---\\n\\n')}`);\n }\n\n if (artifacts.notes.length > 0) {\n sections.push(`\\n## Notes\\n\\n${artifacts.notes.join('\\n\\n---\\n\\n')}`);\n }\n\n if (artifacts.codeChanges) {\n sections.push(`\\n## Recent Code Changes\\n\\n\\`\\`\\`\\n${artifacts.codeChanges}\\n\\`\\`\\``);\n }\n\n sections.push(`\\n## Your Task`);\n sections.push(`\\nAnalyze all the above artifacts and ${existingInference ? 'UPDATE' : 'CREATE'} the INFERENCE.md document.`);\n sections.push(`The document should include:`);\n sections.push(`1. **Summary**: What is the team building? (2-3 sentences)`);\n sections.push(`2. **Architecture**: Key technical decisions and patterns identified`);\n sections.push(`3. **Progress**: What has been done vs what remains`);\n sections.push(`4. **Gaps & Ambiguities**: Questions that need answers`);\n sections.push(`5. **Risks**: Potential issues or concerns`);\n sections.push(`6. **Team Patterns**: How the team works, conventions observed`);\n sections.push(`7. **Recommendations**: Suggestions for the team`);\n sections.push(`\\nWrite the INFERENCE.md content to: ${join(config.workspacePath, '.planning', 'INFERENCE.md')}`);\n\n return sections.join('\\n');\n}\n\n/**\n * Create a simple inference document from artifacts without using an LLM\n * (for cases where we want to generate it locally without spawning an agent)\n */\nexport function generateBasicInference(\n config: MonitoringAgentConfig,\n artifacts: Awaited<ReturnType<typeof gatherArtifacts>>\n): string {\n const now = new Date().toISOString();\n const sections: string[] = [];\n\n sections.push(`# Inference Document - ${config.issueId}`);\n sections.push(`\\n*Last updated: ${now}*`);\n sections.push(`\\n*Generated by Shadow Engineering Monitoring Agent*`);\n\n sections.push(`\\n## Status\\n`);\n const artifactCount = artifacts.comments.length + artifacts.transcripts.length + artifacts.notes.length;\n sections.push(`Analyzed ${artifactCount} artifact(s).`);\n\n if (artifacts.issueDescription) {\n sections.push(`\\n## Issue Summary\\n`);\n // Take first 500 chars as summary\n const summary = artifacts.issueDescription.slice(0, 500);\n sections.push(summary + (artifacts.issueDescription.length > 500 ? '...' : ''));\n }\n\n if (artifacts.codeChanges) {\n sections.push(`\\n## Recent Activity\\n`);\n sections.push('```');\n sections.push(artifacts.codeChanges);\n sections.push('```');\n }\n\n sections.push(`\\n## Artifacts Analyzed\\n`);\n if (artifacts.issueDescription) sections.push(`- Issue description`);\n sections.push(`- ${artifacts.comments.length} discussion comment(s)`);\n sections.push(`- ${artifacts.transcripts.length} transcript(s)`);\n sections.push(`- ${artifacts.notes.length} note(s)`);\n if (artifacts.codeChanges) sections.push(`- Code change history`);\n\n sections.push(`\\n## Gaps & Risks\\n`);\n sections.push(`(Requires deeper analysis - run full monitoring agent for detailed inference)`);\n\n return sections.join('\\n');\n}\n\n/**\n * Update the INFERENCE.md file\n */\nexport function updateInferenceDocument(workspacePath: string, content: string): void {\n const planningDir = join(workspacePath, '.planning');\n mkdirSync(planningDir, { recursive: true });\n writeFileSync(join(planningDir, 'INFERENCE.md'), content, 'utf-8');\n}\n\n/**\n * Read existing INFERENCE.md if it exists\n */\nexport function readInferenceDocument(workspacePath: string): string | null {\n const filePath = join(workspacePath, '.planning', 'INFERENCE.md');\n if (!existsSync(filePath)) return null;\n return readFileSync(filePath, 'utf-8');\n}\n","/**\n * Shadow Engineering - Observer Agent\n *\n * Watches team's development work and provides assistance.\n * Default mode: Watch-only (comments on PRs with observations).\n * Propose mode: Requires explicit `/shadow propose` command to create PRs.\n *\n * Monitors PR activity (polls initially), comments with observations,\n * tracks progress against Inference Document, flags deviations.\n */\n\nimport { readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { readInferenceDocument } from './monitoring-agent.js';\n\nconst execAsync = promisify(exec);\n\nexport interface ObserverAgentConfig {\n issueId: string;\n workspacePath: string;\n projectPath: string;\n repo: string; // owner/repo format\n mode: 'watch' | 'propose';\n}\n\nexport interface PRInfo {\n number: number;\n title: string;\n author: string;\n branch: string;\n state: string;\n body: string;\n additions: number;\n deletions: number;\n changedFiles: number;\n comments: Array<{ author: string; body: string; createdAt: string }>;\n}\n\n/**\n * Poll for PRs related to the issue\n */\nexport async function pollPRs(config: ObserverAgentConfig): Promise<PRInfo[]> {\n const prs: PRInfo[] = [];\n\n try {\n // Search for PRs mentioning the issue ID\n const { stdout } = await execAsync(\n `gh pr list --repo ${config.repo} --search \"${config.issueId}\" --json number,title,author,headRefName,state,body,additions,deletions,changedFiles,comments --limit 10`,\n { encoding: 'utf-8', timeout: 30000 }\n );\n\n const parsed = JSON.parse(stdout || '[]');\n for (const pr of parsed) {\n prs.push({\n number: pr.number,\n title: pr.title,\n author: pr.author?.login || 'unknown',\n branch: pr.headRefName,\n state: pr.state,\n body: pr.body || '',\n additions: pr.additions || 0,\n deletions: pr.deletions || 0,\n changedFiles: pr.changedFiles || 0,\n comments: (pr.comments || []).map((c: any) => ({\n author: c.author?.login || 'unknown',\n body: c.body,\n createdAt: c.createdAt,\n })),\n });\n }\n } catch (err: any) {\n console.warn(`Observer: Failed to poll PRs for ${config.issueId}:`, err.message);\n }\n\n return prs;\n}\n\n/**\n * Generate an observation for a PR\n */\nexport function generateObservation(\n pr: PRInfo,\n inference: string | null,\n config: ObserverAgentConfig\n): string | null {\n // Skip if we've already commented (check for our marker)\n const hasOurComment = pr.comments.some(c =>\n c.body.includes('[Shadow Engineering Observer]')\n );\n if (hasOurComment) return null;\n\n const sections: string[] = [];\n sections.push(`### [Shadow Engineering Observer] - Observations for #${pr.number}`);\n sections.push('');\n\n // Basic observations\n sections.push(`**PR Summary**: ${pr.title}`);\n sections.push(`**Changes**: +${pr.additions}/-${pr.deletions} across ${pr.changedFiles} file(s)`);\n sections.push('');\n\n if (inference) {\n sections.push(`**Alignment with Inference Document**: This PR appears to contribute to the documented plan.`);\n sections.push('');\n }\n\n // Size-based observation\n if (pr.additions + pr.deletions > 500) {\n sections.push(`> **Note**: This is a large PR (${pr.additions + pr.deletions} lines changed). Consider splitting into smaller reviews for easier review.`);\n sections.push('');\n }\n\n if (pr.changedFiles > 20) {\n sections.push(`> **Note**: This PR touches ${pr.changedFiles} files. Ensure adequate test coverage for all changed modules.`);\n sections.push('');\n }\n\n sections.push('---');\n sections.push('*This observation was generated by the Shadow Engineering Observer Agent in watch-only mode.*');\n\n return sections.join('\\n');\n}\n\n/**\n * Post a comment on a PR\n */\nexport async function postPRComment(\n repo: string,\n prNumber: number,\n comment: string\n): Promise<boolean> {\n try {\n await execAsync(\n `gh pr comment ${prNumber} --repo ${repo} --body \"${comment.replace(/\"/g, '\\\\\"')}\"`,\n { encoding: 'utf-8', timeout: 15000 }\n );\n return true;\n } catch (err: any) {\n console.warn(`Observer: Failed to comment on PR #${prNumber}:`, err.message);\n return false;\n }\n}\n\n/**\n * Generate the observer prompt for the Observer Agent\n */\nexport function generateObserverPrompt(\n config: ObserverAgentConfig,\n prs: PRInfo[],\n inference: string | null\n): string {\n const sections: string[] = [];\n\n sections.push(`# Shadow Engineering - Observer Task`);\n sections.push(`\\nYou are the Shadow Engineering Observer Agent for ${config.issueId}.`);\n sections.push(`Mode: ${config.mode === 'watch' ? 'WATCH-ONLY (comment only, no changes)' : 'PROPOSE (can create PRs when asked)'}`);\n\n if (config.mode === 'watch') {\n sections.push(`\\n**IMPORTANT**: You are in watch-only mode. You may ONLY:`);\n sections.push(`- Comment on PRs with observations, suggestions, and potential issues`);\n sections.push(`- Track progress against the Inference Document`);\n sections.push(`- Flag deviations or scope changes`);\n sections.push(`- Document patterns the team is using`);\n sections.push(`\\nYou may NOT create PRs, push code, or make changes.`);\n }\n\n if (inference) {\n sections.push(`\\n## Inference Document\\n\\n${inference}`);\n }\n\n if (prs.length > 0) {\n sections.push(`\\n## Active PRs\\n`);\n for (const pr of prs) {\n sections.push(`### PR #${pr.number}: ${pr.title}`);\n sections.push(`- Author: ${pr.author}`);\n sections.push(`- Branch: ${pr.branch}`);\n sections.push(`- Changes: +${pr.additions}/-${pr.deletions} (${pr.changedFiles} files)`);\n if (pr.body) sections.push(`- Description: ${pr.body.slice(0, 300)}`);\n sections.push('');\n }\n } else {\n sections.push(`\\n## Active PRs\\n\\nNo PRs found for ${config.issueId}.`);\n }\n\n sections.push(`\\n## Your Task`);\n if (config.mode === 'watch') {\n sections.push(`Review the PRs above and provide helpful observations.`);\n sections.push(`Focus on: code quality, alignment with the inference document, potential issues, and helpful suggestions.`);\n } else {\n sections.push(`Review the PRs above. You have been asked to propose changes.`);\n sections.push(`Create focused, well-tested PRs that address specific gaps identified in the Inference Document.`);\n }\n\n return sections.join('\\n');\n}\n\n/**\n * Run the observer agent once (poll and comment)\n * Returns the number of comments posted\n */\nexport async function runObserverCycle(config: ObserverAgentConfig): Promise<number> {\n const inference = readInferenceDocument(config.workspacePath);\n const prs = await pollPRs(config);\n\n let commentsPosted = 0;\n\n for (const pr of prs) {\n const observation = generateObservation(pr, inference, config);\n if (observation) {\n const posted = await postPRComment(config.repo, pr.number, observation);\n if (posted) commentsPosted++;\n }\n }\n\n return commentsPosted;\n}\n"],"mappings":"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"}
@@ -218,4 +218,4 @@ __esmMin((() => {
218
218
  }))();
219
219
  export { createShadowState, getDisplayStatus, getPendingSyncCount, getShadowState, getUnsyncedHistory, isShadowed, listShadowedIssues, markAsSynced, needsSync, removeShadowState, updateShadowState, updateTrackerStatusCache };
220
220
 
221
- //# sourceMappingURL=shadow-state-DHQ-kASN.js.map
221
+ //# sourceMappingURL=shadow-state-BIexcxkv.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"shadow-state-DHQ-kASN.js","names":[],"sources":["../../src/lib/shadow-state.ts"],"sourcesContent":["/**\n * Shadow State Storage Module\n *\n * Manages shadow state for issues - tracking status locally without updating\n * the issue tracker until explicitly synced.\n *\n * Storage Location: ~/.panopticon/shadow-state/\n */\n\nimport { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport type { IssueState } from './tracker/interface.js';\n\n// Storage directory for shadow state files\nconst SHADOW_STATE_DIR = join(homedir(), '.panopticon', 'shadow-state');\n\n/**\n * Shadow history entry - tracks state transitions\n */\nexport interface ShadowHistoryEntry {\n /** Previous state */\n from: IssueState;\n /** New state */\n to: IssueState;\n /** When the transition occurred */\n at: string;\n /** Command that triggered the transition (e.g., \"pan work plan\", \"dashboard\") */\n by: string;\n /** Whether this transition was synced to the tracker */\n syncedToTracker: boolean;\n}\n\n/**\n * Canonical state for Kanban column placement\n */\nexport type CanonicalState = 'backlog' | 'todo' | 'in_progress' | 'in_review' | 'done' | 'canceled';\n\n/**\n * Shadow state for an issue\n */\nexport interface ShadowState {\n /** Issue ID (e.g., \"MIN-123\") */\n issueId: string;\n /** Panopticon's view of the issue status */\n shadowStatus: IssueState;\n /** Target canonical state for Kanban column placement */\n targetCanonicalState?: CanonicalState;\n /** Last known tracker status (cached) */\n trackerStatus: IssueState;\n /** When tracker status was last fetched */\n trackerStatusUpdatedAt: string;\n /** When shadow mode was enabled for this issue */\n shadowedAt: string;\n /** When shadow state was last synced to tracker */\n syncedAt?: string;\n /** Audit trail of state transitions */\n history: ShadowHistoryEntry[];\n}\n\n/**\n * Result of a sync operation\n */\nexport interface SyncResult {\n success: boolean;\n /** The state that was synced */\n syncedState?: IssueState;\n /** Previous tracker state */\n previousState?: IssueState;\n /** Error message if sync failed */\n error?: string;\n /** Number of history entries marked as synced */\n entriesSynced?: number;\n}\n\n/**\n * Ensure the shadow state directory exists\n */\nfunction ensureShadowStateDir(): void {\n if (!existsSync(SHADOW_STATE_DIR)) {\n mkdirSync(SHADOW_STATE_DIR, { recursive: true });\n }\n}\n\n/**\n * Get the file path for a shadow state file\n */\nfunction getShadowStatePath(issueId: string): string {\n // Normalize issue ID for filename (uppercase, replace special chars)\n const normalizedId = issueId.toUpperCase().replace(/[^A-Z0-9-]/g, '');\n return join(SHADOW_STATE_DIR, `${normalizedId}.json`);\n}\n\n/**\n * Get shadow state for an issue\n * @returns ShadowState or null if not shadowed\n */\nexport function getShadowState(issueId: string): ShadowState | null {\n const filePath = getShadowStatePath(issueId);\n\n if (!existsSync(filePath)) {\n return null;\n }\n\n try {\n const content = readFileSync(filePath, 'utf-8');\n return JSON.parse(content) as ShadowState;\n } catch (error) {\n console.error(`Error reading shadow state for ${issueId}:`, error);\n return null;\n }\n}\n\n/**\n * Check if an issue is in shadow mode\n */\nexport function isShadowed(issueId: string): boolean {\n return getShadowState(issueId) !== null;\n}\n\n/**\n * Create a new shadow state for an issue\n */\nexport function createShadowState(\n issueId: string,\n initialTrackerStatus: IssueState = 'open',\n triggeredBy: string = 'unknown'\n): ShadowState {\n ensureShadowStateDir();\n\n const now = new Date().toISOString();\n\n const shadowState: ShadowState = {\n issueId: issueId.toUpperCase(),\n shadowStatus: initialTrackerStatus,\n trackerStatus: initialTrackerStatus,\n trackerStatusUpdatedAt: now,\n shadowedAt: now,\n history: [],\n };\n\n const filePath = getShadowStatePath(issueId);\n writeFileSync(filePath, JSON.stringify(shadowState, null, 2), 'utf-8');\n\n return shadowState;\n}\n\n/**\n * Update shadow state for an issue\n */\nexport function updateShadowState(\n issueId: string,\n newStatus: IssueState,\n triggeredBy: string,\n targetCanonicalState?: CanonicalState\n): ShadowState {\n ensureShadowStateDir();\n\n let state = getShadowState(issueId);\n\n // Create new shadow state if it doesn't exist\n if (!state) {\n state = {\n issueId: issueId.toUpperCase(),\n shadowStatus: newStatus,\n targetCanonicalState,\n trackerStatus: newStatus,\n trackerStatusUpdatedAt: new Date().toISOString(),\n shadowedAt: new Date().toISOString(),\n history: [],\n };\n }\n\n // Only record transition if status changed\n if (state.shadowStatus !== newStatus) {\n const transition: ShadowHistoryEntry = {\n from: state.shadowStatus,\n to: newStatus,\n at: new Date().toISOString(),\n by: triggeredBy,\n syncedToTracker: false,\n };\n\n state.history.push(transition);\n state.shadowStatus = newStatus;\n }\n\n // Always update target canonical state if provided\n if (targetCanonicalState) {\n state.targetCanonicalState = targetCanonicalState;\n }\n\n const filePath = getShadowStatePath(issueId);\n writeFileSync(filePath, JSON.stringify(state, null, 2), 'utf-8');\n\n return state;\n}\n\n/**\n * Update tracker status cache (refresh from tracker)\n */\nexport function updateTrackerStatusCache(\n issueId: string,\n trackerStatus: IssueState\n): ShadowState {\n const state = getShadowState(issueId);\n\n if (!state) {\n throw new Error(`Cannot update tracker status: ${issueId} is not in shadow mode`);\n }\n\n state.trackerStatus = trackerStatus;\n state.trackerStatusUpdatedAt = new Date().toISOString();\n\n const filePath = getShadowStatePath(issueId);\n writeFileSync(filePath, JSON.stringify(state, null, 2), 'utf-8');\n\n return state;\n}\n\n/**\n * Sync shadow state to tracker (mark as synced)\n * This is called after successfully updating the tracker\n */\nexport function markAsSynced(\n issueId: string,\n syncedState: IssueState,\n previousTrackerState?: IssueState\n): SyncResult {\n const state = getShadowState(issueId);\n\n if (!state) {\n return {\n success: false,\n error: `Issue ${issueId} is not in shadow mode`,\n };\n }\n\n const now = new Date().toISOString();\n let entriesSynced = 0;\n\n // Mark all unsynced history entries as synced\n for (const entry of state.history) {\n if (!entry.syncedToTracker) {\n entry.syncedToTracker = true;\n entriesSynced++;\n }\n }\n\n // Update sync timestamp and tracker status\n state.syncedAt = now;\n state.trackerStatus = syncedState;\n state.trackerStatusUpdatedAt = now;\n\n const filePath = getShadowStatePath(issueId);\n writeFileSync(filePath, JSON.stringify(state, null, 2), 'utf-8');\n\n return {\n success: true,\n syncedState,\n previousState: previousTrackerState,\n entriesSynced,\n };\n}\n\n/**\n * List all shadowed issues\n */\nexport function listShadowedIssues(): ShadowState[] {\n if (!existsSync(SHADOW_STATE_DIR)) {\n return [];\n }\n\n const files = readdirSync(SHADOW_STATE_DIR);\n const states: ShadowState[] = [];\n\n for (const file of files) {\n if (!file.endsWith('.json')) continue;\n\n try {\n const content = readFileSync(join(SHADOW_STATE_DIR, file), 'utf-8');\n const state = JSON.parse(content) as ShadowState;\n states.push(state);\n } catch (error) {\n console.error(`Error reading shadow state file ${file}:`, error);\n }\n }\n\n // Sort by shadowedAt (newest first)\n return states.sort((a, b) =>\n new Date(b.shadowedAt).getTime() - new Date(a.shadowedAt).getTime()\n );\n}\n\n/**\n * Remove shadow state for an issue (unshadow)\n * @param syncFirst - If true, attempts to sync to tracker before removing\n */\nexport function removeShadowState(\n issueId: string,\n syncFirst: boolean = false\n): { success: boolean; error?: string; synced?: boolean } {\n const filePath = getShadowStatePath(issueId);\n\n if (!existsSync(filePath)) {\n return {\n success: false,\n error: `Issue ${issueId} is not in shadow mode`,\n };\n }\n\n try {\n // If syncFirst is true, we should have already synced by this point\n // This parameter is just for API clarity\n\n unlinkSync(filePath);\n return {\n success: true,\n synced: syncFirst,\n };\n } catch (error: any) {\n return {\n success: false,\n error: `Failed to remove shadow state: ${error.message}`,\n };\n }\n}\n\n/**\n * Get the display status for an issue\n * Returns shadow status with tracker status info if in shadow mode\n */\nexport function getDisplayStatus(\n issueId: string,\n trackerStatus: IssueState\n): {\n status: IssueState;\n isShadowed: boolean;\n trackerStatus?: IssueState;\n outOfSync?: boolean;\n} {\n const state = getShadowState(issueId);\n\n if (!state) {\n return {\n status: trackerStatus,\n isShadowed: false,\n };\n }\n\n return {\n status: state.shadowStatus,\n isShadowed: true,\n trackerStatus: state.trackerStatus,\n outOfSync: state.shadowStatus !== state.trackerStatus,\n };\n}\n\n/**\n * Check if an issue needs to be synced to tracker\n * (shadow status differs from tracker status)\n */\nexport function needsSync(issueId: string): boolean {\n const state = getShadowState(issueId);\n\n if (!state) {\n return false;\n }\n\n return state.shadowStatus !== state.trackerStatus;\n}\n\n/**\n * Get unsynced history entries for an issue\n */\nexport function getUnsyncedHistory(issueId: string): ShadowHistoryEntry[] {\n const state = getShadowState(issueId);\n\n if (!state) {\n return [];\n }\n\n return state.history.filter(entry => !entry.syncedToTracker);\n}\n\n/**\n * Get the count of issues that need sync\n */\nexport function getPendingSyncCount(): number {\n return listShadowedIssues().filter(state =>\n state.shadowStatus !== state.trackerStatus\n ).length;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"shadow-state-BIexcxkv.js","names":[],"sources":["../../src/lib/shadow-state.ts"],"sourcesContent":["/**\n * Shadow State Storage Module\n *\n * Manages shadow state for issues - tracking status locally without updating\n * the issue tracker until explicitly synced.\n *\n * Storage Location: ~/.panopticon/shadow-state/\n */\n\nimport { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport type { IssueState } from './tracker/interface.js';\n\n// Storage directory for shadow state files\nconst SHADOW_STATE_DIR = join(homedir(), '.panopticon', 'shadow-state');\n\n/**\n * Shadow history entry - tracks state transitions\n */\nexport interface ShadowHistoryEntry {\n /** Previous state */\n from: IssueState;\n /** New state */\n to: IssueState;\n /** When the transition occurred */\n at: string;\n /** Command that triggered the transition (e.g., \"pan work plan\", \"dashboard\") */\n by: string;\n /** Whether this transition was synced to the tracker */\n syncedToTracker: boolean;\n}\n\n/**\n * Canonical state for Kanban column placement\n */\nexport type CanonicalState = 'backlog' | 'todo' | 'in_progress' | 'in_review' | 'done' | 'canceled';\n\n/**\n * Shadow state for an issue\n */\nexport interface ShadowState {\n /** Issue ID (e.g., \"MIN-123\") */\n issueId: string;\n /** Panopticon's view of the issue status */\n shadowStatus: IssueState;\n /** Target canonical state for Kanban column placement */\n targetCanonicalState?: CanonicalState;\n /** Last known tracker status (cached) */\n trackerStatus: IssueState;\n /** When tracker status was last fetched */\n trackerStatusUpdatedAt: string;\n /** When shadow mode was enabled for this issue */\n shadowedAt: string;\n /** When shadow state was last synced to tracker */\n syncedAt?: string;\n /** Audit trail of state transitions */\n history: ShadowHistoryEntry[];\n}\n\n/**\n * Result of a sync operation\n */\nexport interface SyncResult {\n success: boolean;\n /** The state that was synced */\n syncedState?: IssueState;\n /** Previous tracker state */\n previousState?: IssueState;\n /** Error message if sync failed */\n error?: string;\n /** Number of history entries marked as synced */\n entriesSynced?: number;\n}\n\n/**\n * Ensure the shadow state directory exists\n */\nfunction ensureShadowStateDir(): void {\n if (!existsSync(SHADOW_STATE_DIR)) {\n mkdirSync(SHADOW_STATE_DIR, { recursive: true });\n }\n}\n\n/**\n * Get the file path for a shadow state file\n */\nfunction getShadowStatePath(issueId: string): string {\n // Normalize issue ID for filename (uppercase, replace special chars)\n const normalizedId = issueId.toUpperCase().replace(/[^A-Z0-9-]/g, '');\n return join(SHADOW_STATE_DIR, `${normalizedId}.json`);\n}\n\n/**\n * Get shadow state for an issue\n * @returns ShadowState or null if not shadowed\n */\nexport function getShadowState(issueId: string): ShadowState | null {\n const filePath = getShadowStatePath(issueId);\n\n if (!existsSync(filePath)) {\n return null;\n }\n\n try {\n const content = readFileSync(filePath, 'utf-8');\n return JSON.parse(content) as ShadowState;\n } catch (error) {\n console.error(`Error reading shadow state for ${issueId}:`, error);\n return null;\n }\n}\n\n/**\n * Check if an issue is in shadow mode\n */\nexport function isShadowed(issueId: string): boolean {\n return getShadowState(issueId) !== null;\n}\n\n/**\n * Create a new shadow state for an issue\n */\nexport function createShadowState(\n issueId: string,\n initialTrackerStatus: IssueState = 'open',\n triggeredBy: string = 'unknown'\n): ShadowState {\n ensureShadowStateDir();\n\n const now = new Date().toISOString();\n\n const shadowState: ShadowState = {\n issueId: issueId.toUpperCase(),\n shadowStatus: initialTrackerStatus,\n trackerStatus: initialTrackerStatus,\n trackerStatusUpdatedAt: now,\n shadowedAt: now,\n history: [],\n };\n\n const filePath = getShadowStatePath(issueId);\n writeFileSync(filePath, JSON.stringify(shadowState, null, 2), 'utf-8');\n\n return shadowState;\n}\n\n/**\n * Update shadow state for an issue\n */\nexport function updateShadowState(\n issueId: string,\n newStatus: IssueState,\n triggeredBy: string,\n targetCanonicalState?: CanonicalState\n): ShadowState {\n ensureShadowStateDir();\n\n let state = getShadowState(issueId);\n\n // Create new shadow state if it doesn't exist\n if (!state) {\n state = {\n issueId: issueId.toUpperCase(),\n shadowStatus: newStatus,\n targetCanonicalState,\n trackerStatus: newStatus,\n trackerStatusUpdatedAt: new Date().toISOString(),\n shadowedAt: new Date().toISOString(),\n history: [],\n };\n }\n\n // Only record transition if status changed\n if (state.shadowStatus !== newStatus) {\n const transition: ShadowHistoryEntry = {\n from: state.shadowStatus,\n to: newStatus,\n at: new Date().toISOString(),\n by: triggeredBy,\n syncedToTracker: false,\n };\n\n state.history.push(transition);\n state.shadowStatus = newStatus;\n }\n\n // Always update target canonical state if provided\n if (targetCanonicalState) {\n state.targetCanonicalState = targetCanonicalState;\n }\n\n const filePath = getShadowStatePath(issueId);\n writeFileSync(filePath, JSON.stringify(state, null, 2), 'utf-8');\n\n return state;\n}\n\n/**\n * Update tracker status cache (refresh from tracker)\n */\nexport function updateTrackerStatusCache(\n issueId: string,\n trackerStatus: IssueState\n): ShadowState {\n const state = getShadowState(issueId);\n\n if (!state) {\n throw new Error(`Cannot update tracker status: ${issueId} is not in shadow mode`);\n }\n\n state.trackerStatus = trackerStatus;\n state.trackerStatusUpdatedAt = new Date().toISOString();\n\n const filePath = getShadowStatePath(issueId);\n writeFileSync(filePath, JSON.stringify(state, null, 2), 'utf-8');\n\n return state;\n}\n\n/**\n * Sync shadow state to tracker (mark as synced)\n * This is called after successfully updating the tracker\n */\nexport function markAsSynced(\n issueId: string,\n syncedState: IssueState,\n previousTrackerState?: IssueState\n): SyncResult {\n const state = getShadowState(issueId);\n\n if (!state) {\n return {\n success: false,\n error: `Issue ${issueId} is not in shadow mode`,\n };\n }\n\n const now = new Date().toISOString();\n let entriesSynced = 0;\n\n // Mark all unsynced history entries as synced\n for (const entry of state.history) {\n if (!entry.syncedToTracker) {\n entry.syncedToTracker = true;\n entriesSynced++;\n }\n }\n\n // Update sync timestamp and tracker status\n state.syncedAt = now;\n state.trackerStatus = syncedState;\n state.trackerStatusUpdatedAt = now;\n\n const filePath = getShadowStatePath(issueId);\n writeFileSync(filePath, JSON.stringify(state, null, 2), 'utf-8');\n\n return {\n success: true,\n syncedState,\n previousState: previousTrackerState,\n entriesSynced,\n };\n}\n\n/**\n * List all shadowed issues\n */\nexport function listShadowedIssues(): ShadowState[] {\n if (!existsSync(SHADOW_STATE_DIR)) {\n return [];\n }\n\n const files = readdirSync(SHADOW_STATE_DIR);\n const states: ShadowState[] = [];\n\n for (const file of files) {\n if (!file.endsWith('.json')) continue;\n\n try {\n const content = readFileSync(join(SHADOW_STATE_DIR, file), 'utf-8');\n const state = JSON.parse(content) as ShadowState;\n states.push(state);\n } catch (error) {\n console.error(`Error reading shadow state file ${file}:`, error);\n }\n }\n\n // Sort by shadowedAt (newest first)\n return states.sort((a, b) =>\n new Date(b.shadowedAt).getTime() - new Date(a.shadowedAt).getTime()\n );\n}\n\n/**\n * Remove shadow state for an issue (unshadow)\n * @param syncFirst - If true, attempts to sync to tracker before removing\n */\nexport function removeShadowState(\n issueId: string,\n syncFirst: boolean = false\n): { success: boolean; error?: string; synced?: boolean } {\n const filePath = getShadowStatePath(issueId);\n\n if (!existsSync(filePath)) {\n return {\n success: false,\n error: `Issue ${issueId} is not in shadow mode`,\n };\n }\n\n try {\n // If syncFirst is true, we should have already synced by this point\n // This parameter is just for API clarity\n\n unlinkSync(filePath);\n return {\n success: true,\n synced: syncFirst,\n };\n } catch (error: any) {\n return {\n success: false,\n error: `Failed to remove shadow state: ${error.message}`,\n };\n }\n}\n\n/**\n * Get the display status for an issue\n * Returns shadow status with tracker status info if in shadow mode\n */\nexport function getDisplayStatus(\n issueId: string,\n trackerStatus: IssueState\n): {\n status: IssueState;\n isShadowed: boolean;\n trackerStatus?: IssueState;\n outOfSync?: boolean;\n} {\n const state = getShadowState(issueId);\n\n if (!state) {\n return {\n status: trackerStatus,\n isShadowed: false,\n };\n }\n\n return {\n status: state.shadowStatus,\n isShadowed: true,\n trackerStatus: state.trackerStatus,\n outOfSync: state.shadowStatus !== state.trackerStatus,\n };\n}\n\n/**\n * Check if an issue needs to be synced to tracker\n * (shadow status differs from tracker status)\n */\nexport function needsSync(issueId: string): boolean {\n const state = getShadowState(issueId);\n\n if (!state) {\n return false;\n }\n\n return state.shadowStatus !== state.trackerStatus;\n}\n\n/**\n * Get unsynced history entries for an issue\n */\nexport function getUnsyncedHistory(issueId: string): ShadowHistoryEntry[] {\n const state = getShadowState(issueId);\n\n if (!state) {\n return [];\n }\n\n return state.history.filter(entry => !entry.syncedToTracker);\n}\n\n/**\n * Get the count of issues that need sync\n */\nexport function getPendingSyncCount(): number {\n return listShadowedIssues().filter(state =>\n state.shadowStatus !== state.trackerStatus\n ).length;\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { i as loadConfig, r as init_config_yaml } from "./config-yaml-DSfYpzN6.js";
2
- import { a as findProjectByTeam, i as findProjectByPath, p as init_projects, r as extractTeamPrefix } from "./projects-Cq3TWdPS.js";
3
- import { t as getAgentCommand } from "./settings-CuHV-wcv.js";
4
- import { a as init_providers, i as getProviderForModel, r as getProviderEnv } from "./providers-Ck2sQd_F.js";
5
- import { r as init_workspace_manager, t as createWorkspace } from "./workspace-manager-D_y9ZmW_.js";
2
+ import { a as findProjectByTeam, i as findProjectByPath, p as init_projects, r as extractTeamPrefix } from "./projects-CFVl4oHn.js";
3
+ import { t as getAgentCommand } from "./settings-BHlDG7TK.js";
4
+ import { a as init_providers, i as getProviderForModel, r as getProviderEnv } from "./providers-B5Y4H2Mg.js";
5
+ import { r as init_workspace_manager, t as createWorkspace } from "./workspace-manager-C7OfT62A.js";
6
6
  import { homedir } from "node:os";
7
7
  import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
8
8
  import { join, resolve } from "node:path";
@@ -417,7 +417,7 @@ async function spawnPlanningSession(opts) {
417
417
  progress(3, "Loading specs & PRDs", `Searching for ${issue.identifier} specs`);
418
418
  let settingsModel = "claude-opus-4-6";
419
419
  try {
420
- const { getModelId } = await import("./work-type-router-Com2amST.js");
420
+ const { getModelId } = await import("./work-type-router-Di5gCQwh.js");
421
421
  settingsModel = getModelId("planning-agent");
422
422
  } catch {}
423
423
  const planningModel = modelOverride || settingsModel;
@@ -518,4 +518,4 @@ while true; do sleep 60; done
518
518
  //#endregion
519
519
  export { spawnPlanningSession as n, buildPlanningPrompt as t };
520
520
 
521
- //# sourceMappingURL=spawn-planning-session-8FFAqLdK.js.map
521
+ //# sourceMappingURL=spawn-planning-session-33Jf-d5T.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"spawn-planning-session-8FFAqLdK.js","names":["loadYamlConfig"],"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, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';\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 { extractTeamPrefix, findProjectByTeam, findProjectByPath } from '../projects.js';\nimport { getAgentCommand, isAnthropicModel } from '../settings.js';\nimport { loadConfig as loadYamlConfig } from '../config-yaml.js';\nimport { getProviderForModel, getProviderEnv } from '../providers.js';\nimport { createWorkspace } from '../workspace-manager.js';\n\nconst __dirname = fileURLToPath(new URL('.', import.meta.url));\n\nfunction getPackageVersion(): string {\n try {\n const pkgPath = resolve(__dirname, '../../../package.json');\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) 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 */\nfunction discoverPrdFiles(workspacePath: string, issueId: string): 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 = readdirSync(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}\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, the planning-agent setting is used. */\n model?: string;\n /** Optional effort level — controls how thorough the planning agent is. */\n effort?: 'low' | 'medium' | 'high';\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\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nasync function ensureTmuxRunning(): Promise<void> {\n try {\n await execAsync('tmux list-sessions 2>/dev/null', { encoding: 'utf-8' });\n } catch {\n // Tmux server not running, start it\n try {\n await execAsync('tmux new-session -d -s panopticon-init', { encoding: 'utf-8' });\n console.log('Started tmux server');\n } catch (startErr) {\n console.error('Failed to start tmux server:', startErr);\n }\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 .panopticon.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 'ZAI_API_KEY', 'HUME_API_KEY', 'KIMI_API_KEY', 'GOOGLE_API_KEY',\n ];\n for (const envVar of varsToStrip) {\n try {\n await execAsync(`tmux 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 function buildPlanningPrompt(issue: PlanningIssue, workspacePath: string, planningModel?: string, effort?: 'low' | 'medium' | 'high'): string {\n const issueLower = issue.identifier.toLowerCase();\n const version = getPackageVersion();\n const modelAuthor = planningModel ? `agent:${planningModel}` : 'agent:claude-opus-4-6';\n const prdFiles = 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 = readdirSync(specDir);\n const specFile = files.find(f =>\n f.toLowerCase().includes(issueLower) && f.endsWith('-spec.md')\n );\n if (specFile) {\n const specContent = readFileSync(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 ? findProjectByTeam(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 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 return `<!-- panopticon:orchestration-context-start -->\n<!-- This is Panopticon orchestration context injected automatically.\n It contains planning session setup instructions, not agent reasoning.\n Session summarizers should SKIP this block and focus on the agent's\n actual work, decisions, and tradeoffs that follow. -->\n\n# Planning Session: ${issue.identifier}\n\n## CRITICAL: PLANNING ONLY - NO IMPLEMENTATION\n\n**YOU ARE IN PLANNING MODE. DO NOT:**\n- Write or modify any code files (except STATE.md)\n- Run implementation commands (npm install, docker compose, make, etc.)\n- Create actual features or functionality\n- Start implementing the solution\n\n**YOU SHOULD ONLY:**\n- Ask clarifying questions (use AskUserQuestion tool)\n- Explore the codebase to understand context (read files, grep)\n- Generate planning artifacts:\n - STATE.md (decisions, approach, architecture)\n - Beads tasks (via \\`bd create\\`)\n - Implementation plan at \\`docs/prds/active/{issue-id}/STATE.md\\` (copy of STATE.md, required for dashboard)\n- Present options and tradeoffs for the user to decide\n\nWhen planning is complete, STOP and tell the user: \"Planning complete - click Done when ready to hand off to an agent for implementation.\"\n\n---\n${effortSection}\n## Issue Details\n- **ID:** ${issue.identifier}\n- **Title:** ${issue.title}\n- **URL:** ${issue.url}\n\n## Description\n${issue.description || 'No description provided'}\n${commentsSection}${specSection}${projectStructureSection}\n---\n\n## Your Mission\n\nYou are a planning agent conducting a **discovery session** for this issue.\n\n### Phase 1: Understand Context\n1. **If a spec file was provided above**, read it thoroughly — it's your primary input\n2. Read the codebase to understand relevant files and patterns\n3. Identify what subsystems/files this issue affects\n4. Note any existing patterns we should follow\n\n### Phase 2: Discovery Conversation\nUse AskUserQuestion tool to ask contextual questions:\n- What's the scope? What's explicitly OUT of scope?\n- Any technical constraints or preferences?\n- What does \"done\" look like?\n- Are there edge cases we need to handle?\n\n### Difficulty Estimation\n\nFor each sub-task, estimate difficulty using this rubric:\n\n| Level | When to Use | Model |\n|-------|-------------|-------|\n| \\`trivial\\` | Typo, comment, formatting only | haiku |\n| \\`simple\\` | Bug fix, single file, obvious change | haiku |\n| \\`medium\\` | New feature, 3-5 files, standard patterns | sonnet |\n| \\`complex\\` | Refactor, migration, 6+ files, some risk | sonnet |\n| \\`expert\\` | Architecture, security, performance, high risk | opus |\n\n### Phase 3: Generate Artifacts (NO CODE!)\nWhen discovery is complete:\n1. Create STATE.md with decisions made\n2. Copy STATE.md to implementation plan at \\`docs/prds/active/{issue-id}/STATE.md\\` (required for dashboard)\n3. Create a vBRIEF plan file at \\`.planning/plan.vbrief.json\\` — **MUST follow the exact format below**\n4. Summarize the plan and STOP\n\n**DO NOT run \\`bd create\\` commands.** Beads tasks are created automatically from \\`plan.vbrief.json\\` by Cloister when planning completes.\n\n### vBRIEF Plan Format (REQUIRED)\n\nThe plan file MUST conform to vBRIEF v0.5 spec (https://github.com/deftai/vBRIEF).\nIt MUST have exactly two top-level keys: \\`vBRIEFInfo\\` and \\`plan\\`.\n\n\\`\\`\\`json\n{\n \"vBRIEFInfo\": {\n \"version\": \"0.5\",\n \"created\": \"<ISO 8601 timestamp>\",\n \"author\": \"panopticon-cli/${version}\",\n \"description\": \"Plan for ${issue.identifier}: <issue title>\"\n },\n \"plan\": {\n \"id\": \"${issueLower}\",\n \"title\": \"<issue title>\",\n \"status\": \"approved\",\n \"uid\": \"<generate a UUID v4>\",\n \"author\": \"${modelAuthor}\",\n \"sequence\": 1,\n \"created\": \"<ISO 8601 timestamp — same as vBRIEFInfo.created>\",\n \"updated\": \"<ISO 8601 timestamp — same as created>\",\n \"references\": [\n { \"uri\": \"${issue.url}\", \"label\": \"${issue.identifier}\", \"type\": \"issue\" }${prdFiles.length > 0 ? `,\n ${prdFiles.map(p => `{ \"uri\": \"${p.path}\", \"label\": \"${p.label}\", \"type\": \"prd\" }`).join(',\\n ')}` : ''}\n ],\n \"tags\": [\"<relevant tags>\"],\n \"narratives\": {\n \"Problem\": \"<what problem this solves>\",\n \"Proposal\": \"<the approach chosen>\"\n },\n \"items\": [\n {\n \"id\": \"<short-kebab-id>\",\n \"title\": \"<task title>\",\n \"status\": \"pending\",\n \"priority\": \"medium\",\n \"created\": \"<ISO 8601 timestamp>\",\n \"metadata\": {\n \"difficulty\": \"trivial|simple|medium|complex|expert\",\n \"issueLabel\": \"${issueLower}\"\n },\n \"narrative\": { \"Action\": \"<what needs to be done>\" },\n \"subItems\": [\n {\n \"id\": \"<parent-id>.ac1\",\n \"title\": \"<specific testable acceptance criterion>\",\n \"status\": \"pending\",\n \"metadata\": { \"kind\": \"acceptance_criterion\" }\n }\n ]\n }\n ],\n \"edges\": [\n { \"from\": \"<source-item-id>\", \"to\": \"<target-item-id>\", \"type\": \"blocks\" }\n ]\n }\n}\n\\`\\`\\`\n\n**CRITICAL vBRIEF rules:**\n- The file MUST have \\`vBRIEFInfo\\` and \\`plan\\` as the ONLY top-level keys\n- \\`plan.id\\` MUST be the issue ID in lowercase (e.g., \"${issueLower}\")\n- \\`plan.uid\\` MUST be a freshly generated UUID v4\n- Do NOT use \\`issue\\`, \\`issueId\\`, or \\`issue_id\\` — use \\`plan.id\\`\n- \\`items[].status\\` MUST be one of: draft, proposed, approved, pending, running, completed, blocked, cancelled\n- Acceptance criteria MUST be \\`subItems\\` with \\`metadata.kind: \"acceptance_criterion\"\\`\n- \\`metadata.difficulty\\` and \\`metadata.issueLabel\\` are Panopticon extensions to the vBRIEF spec\n- Edge types: \\`blocks\\` (hard dependency), \\`informs\\` (soft), \\`invalidates\\`, \\`suggests\\`\n\n**IMPORTANT:** Create the plan file BEFORE creating beads tasks.\n**NOTE:** \\`*-spec.md\\` files are human-written specs — do NOT overwrite them. Your output is \\`*-plan.md\\`.\n\n**Remember:** Be a thinking partner, not an interviewer. Ask questions that help clarify.\n\nStart by exploring the codebase to understand the context, then begin the discovery conversation.\n\n<!-- panopticon:orchestration-context-end -->\n`;\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 ~/.panopticon/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, onProgress } = opts;\n const issueLower = issue.identifier.toLowerCase();\n const agentStateDir = join(homedir(), '.panopticon', '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 = existsSync(workspacePath) &&\n !readdirSync(workspacePath).every((f: string) => f === '.planning');\n\n if (!workspaceCreated) {\n try {\n const projectConfig = findProjectByPath(projectPath) || findProjectByTeam(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 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 writeFileSync(join(agentStateDir, 'state.json'), JSON.stringify({\n id: sessionName, issueId: issue.identifier, workspace: workspacePath,\n status: 'failed', error: errorMsg,\n startedAt: new Date().toISOString(), type: 'planning', location: workspaceLocation,\n }, null, 2));\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', '.planning/ directory structure');\n\n // Kill existing planning session if any\n await execAsync(`tmux kill-session -t ${sessionName} 2>/dev/null || true`, { encoding: 'utf-8' });\n\n // Create planning directory structure\n const planningDir = join(workspacePath, '.planning');\n mkdirSync(planningDir, { recursive: true });\n for (const subdir of ['transcripts', 'discussions', 'notes']) {\n mkdirSync(join(planningDir, subdir), { recursive: true });\n }\n\n // Clear stale STATE.md and .planning-complete from previous session\n for (const staleFile of ['STATE.md', '.planning-complete']) {\n const stalePath = join(planningDir, staleFile);\n if (existsSync(stalePath)) {\n console.log(`[start-planning] Clearing stale ${staleFile}`);\n rmSync(stalePath, { force: true });\n }\n }\n\n // Initialize Shadow Engineering if enabled\n if (shadowMode) {\n const inferencePath = join(planningDir, 'INFERENCE.md');\n if (!existsSync(inferencePath)) {\n writeFileSync(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 work-type router\n let settingsModel = 'claude-opus-4-6';\n try {\n const { getModelId } = await import('../work-type-router.js');\n settingsModel = getModelId('planning-agent');\n } catch { /* fall back to default */ }\n const planningModel = modelOverride || settingsModel;\n\n // Discover and copy PRD files to workspace\n const prdFiles = discoverPrdFiles(workspacePath, issue.identifier);\n if (prdFiles.length > 0) {\n const prdDestPath = join(planningDir, 'prd.md');\n if (!existsSync(prdDestPath)) {\n // Copy the first matching PRD (prefer active over planned)\n try {\n const prdContent = readFileSync(prdFiles[0].path, 'utf-8');\n writeFileSync(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 const planningPromptPath = join(planningDir, 'PLANNING_PROMPT.md');\n const planningPrompt = buildPlanningPrompt(issue, workspacePath, planningModel, effort);\n writeFileSync(planningPromptPath, planningPrompt);\n const agentCmd = getAgentCommand(planningModel);\n const cmdWithArgs = agentCmd.args.length > 0\n ? `${agentCmd.command} ${agentCmd.args.join(' ')} --dangerously-skip-permissions`\n : `${agentCmd.command} --dangerously-skip-permissions`;\n\n // Get provider env vars for non-Anthropic models\n let providerExports = '';\n const provider = getProviderForModel(planningModel);\n if (provider.name !== 'anthropic') {\n const { config } = loadYamlConfig();\n const apiKey = config.apiKeys[provider.name as keyof typeof config.apiKeys];\n if (apiKey) {\n const envVars = getProviderEnv(provider, apiKey);\n providerExports = Object.entries(envVars)\n .map(([k, v]) => `export ${k}=\"${v.replace(/\"/g, '\\\\\"')}\"`)\n .join('\\n');\n }\n }\n\n // ── Write launcher script ──────────────────────────────────────────────\n const initMessage = `Please read the planning prompt file at ${planningPromptPath} 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 writeFileSync(promptFile, initMessage);\n writeFileSync(launcherScript, `#!/bin/bash\n# Set terminal environment for proper rendering (match remote launcher)\nexport TERM=xterm-256color\nexport COLORTERM=truecolor\nexport LANG=C.UTF-8\nexport LC_ALL=C.UTF-8\nexport PANOPTICON_AGENT_ID=\"${sessionName}\"\nexport PANOPTICON_ISSUE_ID=\"${issue.identifier}\"\nexport PANOPTICON_SESSION_TYPE=\"planning\"\n${providerExports}\ncd \"${workspacePath}\"\nprompt=$(cat \"${promptFile}\")\ntrap '' HUP\necho \"[launcher] Claude starting at $(date)\" >> /tmp/pan-launcher-debug.log\n${cmdWithArgs} \"$prompt\"\nCLAUDE_EXIT=$?\necho \"[launcher] Claude exited with code $CLAUDE_EXIT at $(date)\" >> /tmp/pan-launcher-debug.log\n# Keep session alive after Claude exits so user can review and click Done\necho \"\"\necho \"Planning agent has exited. Session kept alive for review.\"\necho \"Click 'Done' in the dashboard when ready to hand off to implementation.\"\necho \"[launcher] Keep-alive loop starting at $(date)\" >> /tmp/pan-launcher-debug.log\nwhile true; do sleep 60; done\n`, { mode: 0o755 });\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 await ensureTmuxRunning();\n await execAsync(\n `TERM=xterm-256color tmux new-session -d -s ${sessionName} \"bash '${launcherScript}'\"`,\n { encoding: 'utf-8' },\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 execAsync(`tmux set-option -t ${sessionName} destroy-unattached off 2>/dev/null || true`, { encoding: 'utf-8' });\n await execAsync(`tmux set-option -t ${sessionName} remain-on-exit on 2>/dev/null || true`, { encoding: 'utf-8' });\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 writeFileSync(join(agentStateDir, 'state.json'), JSON.stringify({\n id: sessionName,\n issueId: issue.identifier,\n workspace: workspacePath,\n runtime: 'claude',\n model: planningModel,\n status: 'running',\n startedAt: new Date().toISOString(),\n type: 'planning',\n location: workspaceLocation,\n }, null, 2));\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 // Update state file to reflect failure\n try {\n writeFileSync(join(agentStateDir, 'state.json'), JSON.stringify({\n id: sessionName,\n issueId: issue.identifier,\n workspace: workspacePath,\n status: 'failed',\n error: err.message,\n startedAt: new Date().toISOString(),\n type: 'planning',\n location: workspaceLocation,\n }, null, 2));\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-33Jf-d5T.js","names":["loadYamlConfig"],"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, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';\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 { extractTeamPrefix, findProjectByTeam, findProjectByPath } from '../projects.js';\nimport { getAgentCommand, isAnthropicModel } from '../settings.js';\nimport { loadConfig as loadYamlConfig } from '../config-yaml.js';\nimport { getProviderForModel, getProviderEnv } from '../providers.js';\nimport { createWorkspace } from '../workspace-manager.js';\n\nconst __dirname = fileURLToPath(new URL('.', import.meta.url));\n\nfunction getPackageVersion(): string {\n try {\n const pkgPath = resolve(__dirname, '../../../package.json');\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) 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 */\nfunction discoverPrdFiles(workspacePath: string, issueId: string): 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 = readdirSync(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}\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, the planning-agent setting is used. */\n model?: string;\n /** Optional effort level — controls how thorough the planning agent is. */\n effort?: 'low' | 'medium' | 'high';\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\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nasync function ensureTmuxRunning(): Promise<void> {\n try {\n await execAsync('tmux list-sessions 2>/dev/null', { encoding: 'utf-8' });\n } catch {\n // Tmux server not running, start it\n try {\n await execAsync('tmux new-session -d -s panopticon-init', { encoding: 'utf-8' });\n console.log('Started tmux server');\n } catch (startErr) {\n console.error('Failed to start tmux server:', startErr);\n }\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 .panopticon.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 'ZAI_API_KEY', 'HUME_API_KEY', 'KIMI_API_KEY', 'GOOGLE_API_KEY',\n ];\n for (const envVar of varsToStrip) {\n try {\n await execAsync(`tmux 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 function buildPlanningPrompt(issue: PlanningIssue, workspacePath: string, planningModel?: string, effort?: 'low' | 'medium' | 'high'): string {\n const issueLower = issue.identifier.toLowerCase();\n const version = getPackageVersion();\n const modelAuthor = planningModel ? `agent:${planningModel}` : 'agent:claude-opus-4-6';\n const prdFiles = 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 = readdirSync(specDir);\n const specFile = files.find(f =>\n f.toLowerCase().includes(issueLower) && f.endsWith('-spec.md')\n );\n if (specFile) {\n const specContent = readFileSync(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 ? findProjectByTeam(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 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 return `<!-- panopticon:orchestration-context-start -->\n<!-- This is Panopticon orchestration context injected automatically.\n It contains planning session setup instructions, not agent reasoning.\n Session summarizers should SKIP this block and focus on the agent's\n actual work, decisions, and tradeoffs that follow. -->\n\n# Planning Session: ${issue.identifier}\n\n## CRITICAL: PLANNING ONLY - NO IMPLEMENTATION\n\n**YOU ARE IN PLANNING MODE. DO NOT:**\n- Write or modify any code files (except STATE.md)\n- Run implementation commands (npm install, docker compose, make, etc.)\n- Create actual features or functionality\n- Start implementing the solution\n\n**YOU SHOULD ONLY:**\n- Ask clarifying questions (use AskUserQuestion tool)\n- Explore the codebase to understand context (read files, grep)\n- Generate planning artifacts:\n - STATE.md (decisions, approach, architecture)\n - Beads tasks (via \\`bd create\\`)\n - Implementation plan at \\`docs/prds/active/{issue-id}/STATE.md\\` (copy of STATE.md, required for dashboard)\n- Present options and tradeoffs for the user to decide\n\nWhen planning is complete, STOP and tell the user: \"Planning complete - click Done when ready to hand off to an agent for implementation.\"\n\n---\n${effortSection}\n## Issue Details\n- **ID:** ${issue.identifier}\n- **Title:** ${issue.title}\n- **URL:** ${issue.url}\n\n## Description\n${issue.description || 'No description provided'}\n${commentsSection}${specSection}${projectStructureSection}\n---\n\n## Your Mission\n\nYou are a planning agent conducting a **discovery session** for this issue.\n\n### Phase 1: Understand Context\n1. **If a spec file was provided above**, read it thoroughly — it's your primary input\n2. Read the codebase to understand relevant files and patterns\n3. Identify what subsystems/files this issue affects\n4. Note any existing patterns we should follow\n\n### Phase 2: Discovery Conversation\nUse AskUserQuestion tool to ask contextual questions:\n- What's the scope? What's explicitly OUT of scope?\n- Any technical constraints or preferences?\n- What does \"done\" look like?\n- Are there edge cases we need to handle?\n\n### Difficulty Estimation\n\nFor each sub-task, estimate difficulty using this rubric:\n\n| Level | When to Use | Model |\n|-------|-------------|-------|\n| \\`trivial\\` | Typo, comment, formatting only | haiku |\n| \\`simple\\` | Bug fix, single file, obvious change | haiku |\n| \\`medium\\` | New feature, 3-5 files, standard patterns | sonnet |\n| \\`complex\\` | Refactor, migration, 6+ files, some risk | sonnet |\n| \\`expert\\` | Architecture, security, performance, high risk | opus |\n\n### Phase 3: Generate Artifacts (NO CODE!)\nWhen discovery is complete:\n1. Create STATE.md with decisions made\n2. Copy STATE.md to implementation plan at \\`docs/prds/active/{issue-id}/STATE.md\\` (required for dashboard)\n3. Create a vBRIEF plan file at \\`.planning/plan.vbrief.json\\` — **MUST follow the exact format below**\n4. Summarize the plan and STOP\n\n**DO NOT run \\`bd create\\` commands.** Beads tasks are created automatically from \\`plan.vbrief.json\\` by Cloister when planning completes.\n\n### vBRIEF Plan Format (REQUIRED)\n\nThe plan file MUST conform to vBRIEF v0.5 spec (https://github.com/deftai/vBRIEF).\nIt MUST have exactly two top-level keys: \\`vBRIEFInfo\\` and \\`plan\\`.\n\n\\`\\`\\`json\n{\n \"vBRIEFInfo\": {\n \"version\": \"0.5\",\n \"created\": \"<ISO 8601 timestamp>\",\n \"author\": \"panopticon-cli/${version}\",\n \"description\": \"Plan for ${issue.identifier}: <issue title>\"\n },\n \"plan\": {\n \"id\": \"${issueLower}\",\n \"title\": \"<issue title>\",\n \"status\": \"approved\",\n \"uid\": \"<generate a UUID v4>\",\n \"author\": \"${modelAuthor}\",\n \"sequence\": 1,\n \"created\": \"<ISO 8601 timestamp — same as vBRIEFInfo.created>\",\n \"updated\": \"<ISO 8601 timestamp — same as created>\",\n \"references\": [\n { \"uri\": \"${issue.url}\", \"label\": \"${issue.identifier}\", \"type\": \"issue\" }${prdFiles.length > 0 ? `,\n ${prdFiles.map(p => `{ \"uri\": \"${p.path}\", \"label\": \"${p.label}\", \"type\": \"prd\" }`).join(',\\n ')}` : ''}\n ],\n \"tags\": [\"<relevant tags>\"],\n \"narratives\": {\n \"Problem\": \"<what problem this solves>\",\n \"Proposal\": \"<the approach chosen>\"\n },\n \"items\": [\n {\n \"id\": \"<short-kebab-id>\",\n \"title\": \"<task title>\",\n \"status\": \"pending\",\n \"priority\": \"medium\",\n \"created\": \"<ISO 8601 timestamp>\",\n \"metadata\": {\n \"difficulty\": \"trivial|simple|medium|complex|expert\",\n \"issueLabel\": \"${issueLower}\"\n },\n \"narrative\": { \"Action\": \"<what needs to be done>\" },\n \"subItems\": [\n {\n \"id\": \"<parent-id>.ac1\",\n \"title\": \"<specific testable acceptance criterion>\",\n \"status\": \"pending\",\n \"metadata\": { \"kind\": \"acceptance_criterion\" }\n }\n ]\n }\n ],\n \"edges\": [\n { \"from\": \"<source-item-id>\", \"to\": \"<target-item-id>\", \"type\": \"blocks\" }\n ]\n }\n}\n\\`\\`\\`\n\n**CRITICAL vBRIEF rules:**\n- The file MUST have \\`vBRIEFInfo\\` and \\`plan\\` as the ONLY top-level keys\n- \\`plan.id\\` MUST be the issue ID in lowercase (e.g., \"${issueLower}\")\n- \\`plan.uid\\` MUST be a freshly generated UUID v4\n- Do NOT use \\`issue\\`, \\`issueId\\`, or \\`issue_id\\` — use \\`plan.id\\`\n- \\`items[].status\\` MUST be one of: draft, proposed, approved, pending, running, completed, blocked, cancelled\n- Acceptance criteria MUST be \\`subItems\\` with \\`metadata.kind: \"acceptance_criterion\"\\`\n- \\`metadata.difficulty\\` and \\`metadata.issueLabel\\` are Panopticon extensions to the vBRIEF spec\n- Edge types: \\`blocks\\` (hard dependency), \\`informs\\` (soft), \\`invalidates\\`, \\`suggests\\`\n\n**IMPORTANT:** Create the plan file BEFORE creating beads tasks.\n**NOTE:** \\`*-spec.md\\` files are human-written specs — do NOT overwrite them. Your output is \\`*-plan.md\\`.\n\n**Remember:** Be a thinking partner, not an interviewer. Ask questions that help clarify.\n\nStart by exploring the codebase to understand the context, then begin the discovery conversation.\n\n<!-- panopticon:orchestration-context-end -->\n`;\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 ~/.panopticon/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, onProgress } = opts;\n const issueLower = issue.identifier.toLowerCase();\n const agentStateDir = join(homedir(), '.panopticon', '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 = existsSync(workspacePath) &&\n !readdirSync(workspacePath).every((f: string) => f === '.planning');\n\n if (!workspaceCreated) {\n try {\n const projectConfig = findProjectByPath(projectPath) || findProjectByTeam(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 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 writeFileSync(join(agentStateDir, 'state.json'), JSON.stringify({\n id: sessionName, issueId: issue.identifier, workspace: workspacePath,\n status: 'failed', error: errorMsg,\n startedAt: new Date().toISOString(), type: 'planning', location: workspaceLocation,\n }, null, 2));\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', '.planning/ directory structure');\n\n // Kill existing planning session if any\n await execAsync(`tmux kill-session -t ${sessionName} 2>/dev/null || true`, { encoding: 'utf-8' });\n\n // Create planning directory structure\n const planningDir = join(workspacePath, '.planning');\n mkdirSync(planningDir, { recursive: true });\n for (const subdir of ['transcripts', 'discussions', 'notes']) {\n mkdirSync(join(planningDir, subdir), { recursive: true });\n }\n\n // Clear stale STATE.md and .planning-complete from previous session\n for (const staleFile of ['STATE.md', '.planning-complete']) {\n const stalePath = join(planningDir, staleFile);\n if (existsSync(stalePath)) {\n console.log(`[start-planning] Clearing stale ${staleFile}`);\n rmSync(stalePath, { force: true });\n }\n }\n\n // Initialize Shadow Engineering if enabled\n if (shadowMode) {\n const inferencePath = join(planningDir, 'INFERENCE.md');\n if (!existsSync(inferencePath)) {\n writeFileSync(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 work-type router\n let settingsModel = 'claude-opus-4-6';\n try {\n const { getModelId } = await import('../work-type-router.js');\n settingsModel = getModelId('planning-agent');\n } catch { /* fall back to default */ }\n const planningModel = modelOverride || settingsModel;\n\n // Discover and copy PRD files to workspace\n const prdFiles = discoverPrdFiles(workspacePath, issue.identifier);\n if (prdFiles.length > 0) {\n const prdDestPath = join(planningDir, 'prd.md');\n if (!existsSync(prdDestPath)) {\n // Copy the first matching PRD (prefer active over planned)\n try {\n const prdContent = readFileSync(prdFiles[0].path, 'utf-8');\n writeFileSync(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 const planningPromptPath = join(planningDir, 'PLANNING_PROMPT.md');\n const planningPrompt = buildPlanningPrompt(issue, workspacePath, planningModel, effort);\n writeFileSync(planningPromptPath, planningPrompt);\n const agentCmd = getAgentCommand(planningModel);\n const cmdWithArgs = agentCmd.args.length > 0\n ? `${agentCmd.command} ${agentCmd.args.join(' ')} --dangerously-skip-permissions`\n : `${agentCmd.command} --dangerously-skip-permissions`;\n\n // Get provider env vars for non-Anthropic models\n let providerExports = '';\n const provider = getProviderForModel(planningModel);\n if (provider.name !== 'anthropic') {\n const { config } = loadYamlConfig();\n const apiKey = config.apiKeys[provider.name as keyof typeof config.apiKeys];\n if (apiKey) {\n const envVars = getProviderEnv(provider, apiKey);\n providerExports = Object.entries(envVars)\n .map(([k, v]) => `export ${k}=\"${v.replace(/\"/g, '\\\\\"')}\"`)\n .join('\\n');\n }\n }\n\n // ── Write launcher script ──────────────────────────────────────────────\n const initMessage = `Please read the planning prompt file at ${planningPromptPath} 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 writeFileSync(promptFile, initMessage);\n writeFileSync(launcherScript, `#!/bin/bash\n# Set terminal environment for proper rendering (match remote launcher)\nexport TERM=xterm-256color\nexport COLORTERM=truecolor\nexport LANG=C.UTF-8\nexport LC_ALL=C.UTF-8\nexport PANOPTICON_AGENT_ID=\"${sessionName}\"\nexport PANOPTICON_ISSUE_ID=\"${issue.identifier}\"\nexport PANOPTICON_SESSION_TYPE=\"planning\"\n${providerExports}\ncd \"${workspacePath}\"\nprompt=$(cat \"${promptFile}\")\ntrap '' HUP\necho \"[launcher] Claude starting at $(date)\" >> /tmp/pan-launcher-debug.log\n${cmdWithArgs} \"$prompt\"\nCLAUDE_EXIT=$?\necho \"[launcher] Claude exited with code $CLAUDE_EXIT at $(date)\" >> /tmp/pan-launcher-debug.log\n# Keep session alive after Claude exits so user can review and click Done\necho \"\"\necho \"Planning agent has exited. Session kept alive for review.\"\necho \"Click 'Done' in the dashboard when ready to hand off to implementation.\"\necho \"[launcher] Keep-alive loop starting at $(date)\" >> /tmp/pan-launcher-debug.log\nwhile true; do sleep 60; done\n`, { mode: 0o755 });\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 await ensureTmuxRunning();\n await execAsync(\n `TERM=xterm-256color tmux new-session -d -s ${sessionName} \"bash '${launcherScript}'\"`,\n { encoding: 'utf-8' },\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 execAsync(`tmux set-option -t ${sessionName} destroy-unattached off 2>/dev/null || true`, { encoding: 'utf-8' });\n await execAsync(`tmux set-option -t ${sessionName} remain-on-exit on 2>/dev/null || true`, { encoding: 'utf-8' });\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 writeFileSync(join(agentStateDir, 'state.json'), JSON.stringify({\n id: sessionName,\n issueId: issue.identifier,\n workspace: workspacePath,\n runtime: 'claude',\n model: planningModel,\n status: 'running',\n startedAt: new Date().toISOString(),\n type: 'planning',\n location: workspaceLocation,\n }, null, 2));\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 // Update state file to reflect failure\n try {\n writeFileSync(join(agentStateDir, 'state.json'), JSON.stringify({\n id: sessionName,\n issueId: issue.identifier,\n workspace: workspacePath,\n status: 'failed',\n error: err.message,\n startedAt: new Date().toISOString(),\n type: 'planning',\n location: workspaceLocation,\n }, null, 2));\n } catch { /* ignore state write errors */ }\n return { success: false, error: err.message };\n }\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import { n as spawnPlanningSession } from "./spawn-planning-session-8FFAqLdK.js";
1
+ import { n as spawnPlanningSession } from "./spawn-planning-session-33Jf-d5T.js";
2
2
  export { spawnPlanningSession };
@@ -1,8 +1,8 @@
1
1
  import { n as __esmMin } from "./chunk-DORXReHP.js";
2
- import { g as init_paths, h as getPanopticonHome } from "./paths-COdEvoXR.js";
3
- import { c as getProject, p as init_projects } from "./projects-Cq3TWdPS.js";
4
- import { mt as getRecentRunLogs, yt as init_specialist_logs } from "./specialists-C6s3U6tX.js";
5
- import { a as getModelId, s as init_work_type_router } from "./work-type-router-Cxp8_ur2.js";
2
+ import { _ as init_paths, g as getPanopticonHome } from "./paths-BDyJ7BiV.js";
3
+ import { c as getProject, p as init_projects } from "./projects-CFVl4oHn.js";
4
+ import { mt as getRecentRunLogs, yt as init_specialist_logs } from "./specialists-B_zrayaP.js";
5
+ import { a as getModelId, s as init_work_type_router } from "./work-type-router-CWVW2Wk_.js";
6
6
  import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
7
7
  import { join } from "path";
8
8
  import { exec } from "child_process";
@@ -112,7 +112,7 @@ async function generateContextDigest(projectKey, specialistType, options = {}) {
112
112
  if (!existsSync(tempDir)) mkdirSync(tempDir, { recursive: true });
113
113
  const promptFile = join(tempDir, `digest-prompt-${Date.now()}.md`);
114
114
  writeFileSync(promptFile, prompt, "utf-8");
115
- const { getProviderEnvForModel } = await import("./agents-BVBVCyat.js");
115
+ const { getProviderEnvForModel } = await import("./agents-Dgh2TjSp.js");
116
116
  const providerEnv = getProviderEnvForModel(model);
117
117
  const envPrefix = Object.entries(providerEnv).map(([k, v]) => `${k}="${v}"`).join(" ");
118
118
  const { stdout, stderr } = await execAsync(`${envPrefix ? envPrefix + " " : ""}claude --dangerously-skip-permissions --model ${model} "$(cat '${promptFile}')"`, {
@@ -274,4 +274,4 @@ __esmMin((() => {
274
274
  }))();
275
275
  export { deleteContextDigest, generateContextDigest, getContextDigestPath, getContextDirectory, hasContextDigest, loadContextDigest, regenerateContextDigest, scheduleDigestGeneration };
276
276
 
277
- //# sourceMappingURL=specialist-context-ColzlmGE.js.map
277
+ //# sourceMappingURL=specialist-context-DGukHSn8.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"specialist-context-ColzlmGE.js","names":[],"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 * ~/.panopticon/specialists/{projectKey}/{specialistType}/context/latest-digest.md\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { getPanopticonHome } from '../paths.js';\nimport { getRecentRunLogs, type RunLogEntry } from './specialist-logs.js';\nimport { getProject } from '../projects.js';\nimport { getModelId } from '../work-type-router.js';\n\nconst execAsync = promisify(exec);\n\n/** Get specialists directory (lazy to support test env overrides) */\nfunction getSpecialistsDir(): string {\n return join(getPanopticonHome(), '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 = getProject(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 getDigestModel(projectKey: string, specialistType: string): string {\n const project = getProject(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 // Fall back to specialist's model\n try {\n const workTypeId = `specialist-${specialistType}` as any;\n return getModelId(workTypeId);\n } catch (error) {\n // Default to Sonnet if can't resolve\n return 'claude-sonnet-4-6';\n }\n}\n\n/**\n * Generate a context digest from recent runs using AI\n *\n * Creates an AI-generated summary of recent specialist runs to provide\n * context for the next run. This includes patterns, learnings, and common issues.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param options - Generation options\n * @returns Generated digest or null if generation failed\n */\nexport async function generateContextDigest(\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 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(`[specialist-context] Generating digest for ${projectKey}/${specialistType} using ${model}...`);\n\n // Use Claude Code CLI to generate digest\n // Write prompt to temp file to avoid shell escaping issues\n const tempDir = join(getPanopticonHome(), '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 = getProviderEnvForModel(model);\n const envPrefix = Object.entries(providerEnv).map(([k, v]) => `${k}=\"${v}\"`).join(' ');\n const { stdout, stderr } = await execAsync(\n `${envPrefix ? envPrefix + ' ' : ''}claude --dangerously-skip-permissions --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(`[specialist-context] Claude stderr:`, stderr);\n }\n\n const digest = stdout.trim();\n\n if (!digest) {\n console.error(`[specialist-context] Empty digest generated`);\n return null;\n }\n\n // Save digest\n const digestPath = getContextDigestPath(projectKey, specialistType);\n writeFileSync(digestPath, digest, 'utf-8');\n\n console.log(`[specialist-context] Generated digest (${digest.length} chars)`);\n return digest;\n } catch (error: any) {\n console.error(`[specialist-context] Failed to generate digest:`, 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 = getProject(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}\n\n/**\n * Regenerate the context digest\n *\n * Forces regeneration even if a digest already exists.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns Generated digest or null if generation failed\n */\nexport async function regenerateContextDigest(\n projectKey: string,\n specialistType: string\n): Promise<string | null> {\n return 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 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"],"mappings":"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"}
1
+ {"version":3,"file":"specialist-context-DGukHSn8.js","names":[],"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 * ~/.panopticon/specialists/{projectKey}/{specialistType}/context/latest-digest.md\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { getPanopticonHome } from '../paths.js';\nimport { getRecentRunLogs, type RunLogEntry } from './specialist-logs.js';\nimport { getProject } from '../projects.js';\nimport { getModelId } from '../work-type-router.js';\n\nconst execAsync = promisify(exec);\n\n/** Get specialists directory (lazy to support test env overrides) */\nfunction getSpecialistsDir(): string {\n return join(getPanopticonHome(), '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 = getProject(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 getDigestModel(projectKey: string, specialistType: string): string {\n const project = getProject(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 // Fall back to specialist's model\n try {\n const workTypeId = `specialist-${specialistType}` as any;\n return getModelId(workTypeId);\n } catch (error) {\n // Default to Sonnet if can't resolve\n return 'claude-sonnet-4-6';\n }\n}\n\n/**\n * Generate a context digest from recent runs using AI\n *\n * Creates an AI-generated summary of recent specialist runs to provide\n * context for the next run. This includes patterns, learnings, and common issues.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param options - Generation options\n * @returns Generated digest or null if generation failed\n */\nexport async function generateContextDigest(\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 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(`[specialist-context] Generating digest for ${projectKey}/${specialistType} using ${model}...`);\n\n // Use Claude Code CLI to generate digest\n // Write prompt to temp file to avoid shell escaping issues\n const tempDir = join(getPanopticonHome(), '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 = getProviderEnvForModel(model);\n const envPrefix = Object.entries(providerEnv).map(([k, v]) => `${k}=\"${v}\"`).join(' ');\n const { stdout, stderr } = await execAsync(\n `${envPrefix ? envPrefix + ' ' : ''}claude --dangerously-skip-permissions --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(`[specialist-context] Claude stderr:`, stderr);\n }\n\n const digest = stdout.trim();\n\n if (!digest) {\n console.error(`[specialist-context] Empty digest generated`);\n return null;\n }\n\n // Save digest\n const digestPath = getContextDigestPath(projectKey, specialistType);\n writeFileSync(digestPath, digest, 'utf-8');\n\n console.log(`[specialist-context] Generated digest (${digest.length} chars)`);\n return digest;\n } catch (error: any) {\n console.error(`[specialist-context] Failed to generate digest:`, 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 = getProject(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}\n\n/**\n * Regenerate the context digest\n *\n * Forces regeneration even if a digest already exists.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns Generated digest or null if generation failed\n */\nexport async function regenerateContextDigest(\n projectKey: string,\n specialistType: string\n): Promise<string | null> {\n return 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 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"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- import { St as parseLogMetadata, _t as getRunLogSize, bt as isRunLogActive, ct as checkLogSizeLimit, dt as createRunLog, ft as finalizeRunLog, gt as getRunLogPath, ht as getRunLog, lt as cleanupAllLogs, mt as getRecentRunLogs, ot as MAX_LOG_SIZE, pt as generateRunId, st as appendToRunLog, ut as cleanupOldLogs, vt as getRunsDirectory, xt as listRunLogs, yt as init_specialist_logs } from "./specialists-C6s3U6tX.js";
1
+ import { St as parseLogMetadata, _t as getRunLogSize, bt as isRunLogActive, ct as checkLogSizeLimit, dt as createRunLog, ft as finalizeRunLog, gt as getRunLogPath, ht as getRunLog, lt as cleanupAllLogs, mt as getRecentRunLogs, ot as MAX_LOG_SIZE, pt as generateRunId, st as appendToRunLog, ut as cleanupOldLogs, vt as getRunsDirectory, xt as listRunLogs, yt as init_specialist_logs } from "./specialists-B_zrayaP.js";
2
2
  init_specialist_logs();
3
3
  export { MAX_LOG_SIZE, appendToRunLog, checkLogSizeLimit, cleanupAllLogs, cleanupOldLogs, createRunLog, finalizeRunLog, generateRunId, getRecentRunLogs, getRunLog, getRunLogPath, getRunLogSize, getRunsDirectory, isRunLogActive, listRunLogs, parseLogMetadata };