@probelabs/visor 0.1.127 → 0.1.129

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 (255) hide show
  1. package/README.md +31 -1
  2. package/defaults/.visor.yaml +420 -0
  3. package/dist/ai-review-service.d.ts +1 -0
  4. package/dist/ai-review-service.d.ts.map +1 -1
  5. package/dist/cli-main.d.ts.map +1 -1
  6. package/dist/config.d.ts +4 -0
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/daemon.js +19 -0
  9. package/dist/defaults/.visor.yaml +420 -0
  10. package/dist/docs/commands.md +1 -1
  11. package/dist/docs/debugging.md +133 -0
  12. package/dist/docs/dev-playbook.md +10 -0
  13. package/dist/docs/index.md +1 -0
  14. package/dist/docs/scheduler.md +503 -0
  15. package/dist/docs/slack-integration.md +21 -0
  16. package/dist/docs/timeouts.md +1 -1
  17. package/dist/docs/workflow-creation-guide.md +39 -0
  18. package/dist/examples/README.md +30 -0
  19. package/dist/examples/calculator-config.yaml +4 -4
  20. package/dist/examples/sandbox-basic.yaml +18 -0
  21. package/dist/examples/sandbox-cache.yaml +32 -0
  22. package/dist/examples/sandbox-dockerfile-inline.yaml +22 -0
  23. package/dist/examples/sandbox-env-passthrough.yaml +26 -0
  24. package/dist/examples/sandbox-multi-env.yaml +27 -0
  25. package/dist/examples/sandbox-read-only.yaml +33 -0
  26. package/dist/examples/scheduler-example.yaml +118 -0
  27. package/dist/frontends/host.d.ts.map +1 -1
  28. package/dist/frontends/slack-frontend.d.ts.map +1 -1
  29. package/dist/generated/config-schema.d.ts +230 -9
  30. package/dist/generated/config-schema.d.ts.map +1 -1
  31. package/dist/index.js +13676 -1604
  32. package/dist/mcp-server.d.ts +8 -8
  33. package/dist/{traces/run-2026-02-01T09-59-08-165Z.ndjson → output/traces/run-2026-02-08T18-16-04-160Z.ndjson} +84 -84
  34. package/dist/{traces/run-2026-02-01T09-59-52-595Z.ndjson → output/traces/run-2026-02-08T18-16-51-253Z.ndjson} +1029 -1029
  35. package/dist/providers/ai-check-provider.d.ts +16 -0
  36. package/dist/providers/ai-check-provider.d.ts.map +1 -1
  37. package/dist/providers/check-provider.interface.d.ts +5 -0
  38. package/dist/providers/check-provider.interface.d.ts.map +1 -1
  39. package/dist/providers/command-check-provider.d.ts.map +1 -1
  40. package/dist/providers/log-check-provider.d.ts.map +1 -1
  41. package/dist/providers/mcp-check-provider.d.ts +3 -0
  42. package/dist/providers/mcp-check-provider.d.ts.map +1 -1
  43. package/dist/providers/mcp-custom-sse-server.d.ts +22 -2
  44. package/dist/providers/mcp-custom-sse-server.d.ts.map +1 -1
  45. package/dist/providers/workflow-check-provider.d.ts.map +1 -1
  46. package/dist/providers/workflow-tool-executor.d.ts +2 -0
  47. package/dist/providers/workflow-tool-executor.d.ts.map +1 -1
  48. package/dist/sandbox/cache-volume-manager.d.ts +48 -0
  49. package/dist/sandbox/cache-volume-manager.d.ts.map +1 -0
  50. package/dist/sandbox/check-runner.d.ts +25 -0
  51. package/dist/sandbox/check-runner.d.ts.map +1 -0
  52. package/dist/sandbox/docker-compose-sandbox.d.ts +25 -0
  53. package/dist/sandbox/docker-compose-sandbox.d.ts.map +1 -0
  54. package/dist/sandbox/docker-image-sandbox.d.ts +32 -0
  55. package/dist/sandbox/docker-image-sandbox.d.ts.map +1 -0
  56. package/dist/sandbox/env-filter.d.ts +19 -0
  57. package/dist/sandbox/env-filter.d.ts.map +1 -0
  58. package/dist/sandbox/index.d.ts +9 -0
  59. package/dist/sandbox/index.d.ts.map +1 -0
  60. package/dist/sandbox/sandbox-manager.d.ts +39 -0
  61. package/dist/sandbox/sandbox-manager.d.ts.map +1 -0
  62. package/dist/sandbox/sandbox-telemetry.d.ts +9 -0
  63. package/dist/sandbox/sandbox-telemetry.d.ts.map +1 -0
  64. package/dist/sandbox/trace-ingester.d.ts +19 -0
  65. package/dist/sandbox/trace-ingester.d.ts.map +1 -0
  66. package/dist/sandbox/types.d.ts +149 -0
  67. package/dist/sandbox/types.d.ts.map +1 -0
  68. package/dist/scheduler/cli-handler.d.ts +6 -0
  69. package/dist/scheduler/cli-handler.d.ts.map +1 -0
  70. package/dist/scheduler/index.d.ts +14 -0
  71. package/dist/scheduler/index.d.ts.map +1 -0
  72. package/dist/scheduler/schedule-parser.d.ts +34 -0
  73. package/dist/scheduler/schedule-parser.d.ts.map +1 -0
  74. package/dist/scheduler/schedule-store.d.ts +182 -0
  75. package/dist/scheduler/schedule-store.d.ts.map +1 -0
  76. package/dist/scheduler/schedule-tool.d.ts +137 -0
  77. package/dist/scheduler/schedule-tool.d.ts.map +1 -0
  78. package/dist/scheduler/scheduler.d.ts +195 -0
  79. package/dist/scheduler/scheduler.d.ts.map +1 -0
  80. package/dist/sdk/check-provider-registry-ACRGIYOB.mjs +28 -0
  81. package/dist/sdk/check-provider-registry-VYHKFHK2.mjs +28 -0
  82. package/dist/sdk/{chunk-CNX7V5JK.mjs → chunk-25IC7KXZ.mjs} +2 -2
  83. package/dist/sdk/{chunk-IHZOSIF4.mjs → chunk-2KB35MB7.mjs} +3 -3
  84. package/dist/sdk/{chunk-HQL734ZI.mjs → chunk-6W75IMDC.mjs} +3 -3
  85. package/dist/sdk/{chunk-XWJPT5KQ.mjs → chunk-7YSOINAQ.mjs} +392 -18
  86. package/dist/sdk/chunk-7YSOINAQ.mjs.map +1 -0
  87. package/dist/sdk/{chunk-3OMWVM6J.mjs → chunk-B7BVQM5K.mjs} +2 -2
  88. package/dist/sdk/chunk-BDGUM6BA.mjs +38825 -0
  89. package/dist/sdk/chunk-BDGUM6BA.mjs.map +1 -0
  90. package/dist/sdk/{chunk-VW2GBXQT.mjs → chunk-D5KI4YQ4.mjs} +3 -3
  91. package/dist/sdk/chunk-DGZPPGJJ.mjs +38825 -0
  92. package/dist/sdk/chunk-DGZPPGJJ.mjs.map +1 -0
  93. package/dist/sdk/chunk-H5BOW5CR.mjs +91 -0
  94. package/dist/sdk/chunk-H5BOW5CR.mjs.map +1 -0
  95. package/dist/sdk/{chunk-YSN4G6CI.mjs → chunk-HEX3RL32.mjs} +81 -3
  96. package/dist/sdk/{chunk-YSN4G6CI.mjs.map → chunk-HEX3RL32.mjs.map} +1 -1
  97. package/dist/sdk/{chunk-ZYAUYXSW.mjs → chunk-J5RGJQ53.mjs} +14 -3
  98. package/dist/sdk/{chunk-ZYAUYXSW.mjs.map → chunk-J5RGJQ53.mjs.map} +1 -1
  99. package/dist/sdk/{chunk-WMJKH4XE.mjs → chunk-J7LXIPZS.mjs} +16 -1
  100. package/dist/sdk/{chunk-EXFGO4FX.mjs → chunk-KFKHU6CM.mjs} +2 -2
  101. package/dist/sdk/{chunk-MPS4HVQI.mjs → chunk-N7HO6KKC.mjs} +8 -8
  102. package/dist/sdk/{chunk-O5EZDNYL.mjs → chunk-NCWIZVOT.mjs} +2 -2
  103. package/dist/sdk/{chunk-3NMLT3YS.mjs → chunk-PO7X5XI7.mjs} +3 -3
  104. package/dist/sdk/{chunk-BHOKBQPB.mjs → chunk-R5Z7YWPB.mjs} +5 -5
  105. package/dist/sdk/{chunk-EORMDOZU.mjs → chunk-SGS2VMEL.mjs} +7 -7
  106. package/dist/sdk/{chunk-BOVFH3LI.mjs → chunk-VF6XIUE4.mjs} +21 -10
  107. package/dist/sdk/chunk-VF6XIUE4.mjs.map +1 -0
  108. package/dist/sdk/{chunk-J2QWVDXK.mjs → chunk-XDLQ3UNF.mjs} +3 -3
  109. package/dist/sdk/{chunk-S2RUE2RG.mjs → chunk-XR7XXGL7.mjs} +3 -3
  110. package/dist/sdk/{chunk-NAW3DB3I.mjs → chunk-XXAEN5KU.mjs} +3 -3
  111. package/dist/sdk/command-executor-DVVXERLR.mjs +14 -0
  112. package/dist/sdk/config-7VTT64SQ.mjs +16 -0
  113. package/dist/sdk/config-merger-RKCZJQ44.mjs +10 -0
  114. package/dist/sdk/event-bus-5K3Y2FCS.mjs +43 -0
  115. package/dist/sdk/{event-bus-5BEVPQ6T.mjs.map → event-bus-5K3Y2FCS.mjs.map} +1 -1
  116. package/dist/sdk/failure-condition-evaluator-4WMDF4Q3.mjs +17 -0
  117. package/dist/sdk/git-repository-analyzer-QFMW6WIS.mjs +471 -0
  118. package/dist/sdk/git-repository-analyzer-QFMW6WIS.mjs.map +1 -0
  119. package/dist/sdk/{github-frontend-5PCKKHVC.mjs → github-frontend-3N2NLO66.mjs} +520 -588
  120. package/dist/sdk/github-frontend-3N2NLO66.mjs.map +1 -0
  121. package/dist/sdk/host-ONVMEHAA.mjs +63 -0
  122. package/dist/sdk/host-ONVMEHAA.mjs.map +1 -0
  123. package/dist/sdk/{liquid-extensions-I7O7KMHF.mjs → liquid-extensions-5IZLTFSZ.mjs} +8 -8
  124. package/dist/sdk/memory-store-3N4AZCYB.mjs +12 -0
  125. package/dist/sdk/{metrics-7PP3EJUH.mjs → metrics-GXQ2EDXA.mjs} +4 -4
  126. package/dist/sdk/ndjson-sink-FD2PSXGD.mjs +52 -0
  127. package/dist/sdk/{ndjson-sink-B4V4NTAQ.mjs.map → ndjson-sink-FD2PSXGD.mjs.map} +1 -1
  128. package/dist/sdk/{prompt-state-EZYOUG75.mjs → prompt-state-YHGXB2OA.mjs} +5 -5
  129. package/dist/sdk/{renderer-schema-CKFB5NDB.mjs → renderer-schema-CMXOLNIG.mjs} +4 -4
  130. package/dist/sdk/routing-S3Y7T2X3.mjs +24 -0
  131. package/dist/sdk/sdk.d.mts +212 -4
  132. package/dist/sdk/sdk.d.ts +212 -4
  133. package/dist/sdk/sdk.js +26927 -6264
  134. package/dist/sdk/sdk.js.map +1 -1
  135. package/dist/sdk/sdk.mjs +23 -1278
  136. package/dist/sdk/sdk.mjs.map +1 -1
  137. package/dist/sdk/session-registry-6PV6SGEJ.mjs +10 -0
  138. package/dist/sdk/slack-frontend-R3M2CACB.mjs +899 -0
  139. package/dist/sdk/slack-frontend-R3M2CACB.mjs.map +1 -0
  140. package/dist/sdk/{trace-helpers-VP6QYVBX.mjs → trace-helpers-YHNPC7MR.mjs} +4 -4
  141. package/dist/sdk/tracer-init-XPRWKMZT.mjs +10 -0
  142. package/dist/sdk/tui-frontend-S546M7A7.mjs +281 -0
  143. package/dist/sdk/tui-frontend-S546M7A7.mjs.map +1 -0
  144. package/dist/sdk/workflow-check-provider-4F3432ZP.mjs +28 -0
  145. package/dist/sdk/workflow-check-provider-A44PBPG2.mjs +28 -0
  146. package/dist/sdk/workflow-check-provider-A44PBPG2.mjs.map +1 -0
  147. package/dist/sdk/workflow-registry-ZAYYXLEP.mjs +12 -0
  148. package/dist/sdk/workflow-registry-ZAYYXLEP.mjs.map +1 -0
  149. package/dist/slack/client.d.ts +28 -0
  150. package/dist/slack/client.d.ts.map +1 -1
  151. package/dist/slack/schedule-tool-handler.d.ts +46 -0
  152. package/dist/slack/schedule-tool-handler.d.ts.map +1 -0
  153. package/dist/slack/slack-output-adapter.d.ts +44 -0
  154. package/dist/slack/slack-output-adapter.d.ts.map +1 -0
  155. package/dist/slack/socket-runner.d.ts +22 -0
  156. package/dist/slack/socket-runner.d.ts.map +1 -1
  157. package/dist/state-machine/dispatch/execution-invoker.d.ts.map +1 -1
  158. package/dist/state-machine/dispatch/foreach-processor.d.ts.map +1 -1
  159. package/dist/state-machine/dispatch/sandbox-routing.d.ts +21 -0
  160. package/dist/state-machine/dispatch/sandbox-routing.d.ts.map +1 -0
  161. package/dist/state-machine/states/level-dispatch.d.ts.map +1 -1
  162. package/dist/state-machine-execution-engine.d.ts.map +1 -1
  163. package/dist/test-runner/index.d.ts +5 -0
  164. package/dist/test-runner/index.d.ts.map +1 -1
  165. package/dist/{output/traces/run-2026-02-01T09-59-08-165Z.ndjson → traces/run-2026-02-08T18-16-04-160Z.ndjson} +84 -84
  166. package/dist/{output/traces/run-2026-02-01T09-59-52-595Z.ndjson → traces/run-2026-02-08T18-16-51-253Z.ndjson} +1029 -1029
  167. package/dist/tui/chat-runner.d.ts +39 -0
  168. package/dist/tui/chat-runner.d.ts.map +1 -0
  169. package/dist/tui/chat-state.d.ts +56 -0
  170. package/dist/tui/chat-state.d.ts.map +1 -0
  171. package/dist/tui/chat-tui.d.ts +69 -0
  172. package/dist/tui/chat-tui.d.ts.map +1 -0
  173. package/dist/tui/components/chat-box.d.ts +33 -0
  174. package/dist/tui/components/chat-box.d.ts.map +1 -0
  175. package/dist/tui/components/input-bar.d.ts +50 -0
  176. package/dist/tui/components/input-bar.d.ts.map +1 -0
  177. package/dist/tui/components/status-bar.d.ts +31 -0
  178. package/dist/tui/components/status-bar.d.ts.map +1 -0
  179. package/dist/tui/components/trace-viewer.d.ts +73 -0
  180. package/dist/tui/components/trace-viewer.d.ts.map +1 -0
  181. package/dist/tui/index.d.ts +14 -0
  182. package/dist/tui/index.d.ts.map +1 -0
  183. package/dist/tui/tui-frontend.d.ts +29 -0
  184. package/dist/tui/tui-frontend.d.ts.map +1 -0
  185. package/dist/types/bot.d.ts +35 -0
  186. package/dist/types/bot.d.ts.map +1 -1
  187. package/dist/types/config.d.ts +152 -3
  188. package/dist/types/config.d.ts.map +1 -1
  189. package/dist/types/engine.d.ts +3 -0
  190. package/dist/types/engine.d.ts.map +1 -1
  191. package/dist/utils/sandbox.d.ts.map +1 -1
  192. package/dist/utils/workspace-manager.d.ts +22 -2
  193. package/dist/utils/workspace-manager.d.ts.map +1 -1
  194. package/dist/utils/worktree-manager.d.ts +2 -1
  195. package/dist/utils/worktree-manager.d.ts.map +1 -1
  196. package/package.json +4 -2
  197. package/dist/docs/NPM_USAGE.md +0 -281
  198. package/dist/generated/config-schema.json +0 -2161
  199. package/dist/sdk/check-provider-registry-CVUONJ5A.mjs +0 -28
  200. package/dist/sdk/chunk-BOVFH3LI.mjs.map +0 -1
  201. package/dist/sdk/chunk-TS6BUNAI.mjs +0 -17722
  202. package/dist/sdk/chunk-TS6BUNAI.mjs.map +0 -1
  203. package/dist/sdk/chunk-XWJPT5KQ.mjs.map +0 -1
  204. package/dist/sdk/command-executor-Q7MHJKZJ.mjs +0 -14
  205. package/dist/sdk/config-DXX64GD3.mjs +0 -16
  206. package/dist/sdk/config-merger-PX3WIT57.mjs +0 -10
  207. package/dist/sdk/event-bus-5BEVPQ6T.mjs +0 -35
  208. package/dist/sdk/failure-condition-evaluator-G4HMJPXF.mjs +0 -17
  209. package/dist/sdk/git-repository-analyzer-HJC4MYW4.mjs +0 -458
  210. package/dist/sdk/git-repository-analyzer-HJC4MYW4.mjs.map +0 -1
  211. package/dist/sdk/github-frontend-5PCKKHVC.mjs.map +0 -1
  212. package/dist/sdk/host-H3AWNZ2F.mjs +0 -52
  213. package/dist/sdk/host-H3AWNZ2F.mjs.map +0 -1
  214. package/dist/sdk/memory-store-RW5N2NGJ.mjs +0 -12
  215. package/dist/sdk/ndjson-sink-B4V4NTAQ.mjs +0 -44
  216. package/dist/sdk/routing-QHTGDIXF.mjs +0 -24
  217. package/dist/sdk/session-registry-4E6YRQ77.mjs +0 -10
  218. package/dist/sdk/slack-frontend-JUT3TYVC.mjs +0 -821
  219. package/dist/sdk/slack-frontend-JUT3TYVC.mjs.map +0 -1
  220. package/dist/sdk/tracer-init-GSLPPLCD.mjs +0 -10
  221. package/dist/sdk/workflow-check-provider-3IWBAZP7.mjs +0 -28
  222. package/dist/sdk/workflow-registry-KFWSDSLM.mjs +0 -12
  223. package/dist/tui.d.ts +0 -51
  224. package/dist/tui.d.ts.map +0 -1
  225. /package/dist/sdk/{check-provider-registry-CVUONJ5A.mjs.map → check-provider-registry-ACRGIYOB.mjs.map} +0 -0
  226. /package/dist/sdk/{chunk-WMJKH4XE.mjs.map → check-provider-registry-VYHKFHK2.mjs.map} +0 -0
  227. /package/dist/sdk/{chunk-CNX7V5JK.mjs.map → chunk-25IC7KXZ.mjs.map} +0 -0
  228. /package/dist/sdk/{chunk-IHZOSIF4.mjs.map → chunk-2KB35MB7.mjs.map} +0 -0
  229. /package/dist/sdk/{chunk-HQL734ZI.mjs.map → chunk-6W75IMDC.mjs.map} +0 -0
  230. /package/dist/sdk/{chunk-3OMWVM6J.mjs.map → chunk-B7BVQM5K.mjs.map} +0 -0
  231. /package/dist/sdk/{chunk-VW2GBXQT.mjs.map → chunk-D5KI4YQ4.mjs.map} +0 -0
  232. /package/dist/sdk/{command-executor-Q7MHJKZJ.mjs.map → chunk-J7LXIPZS.mjs.map} +0 -0
  233. /package/dist/sdk/{chunk-EXFGO4FX.mjs.map → chunk-KFKHU6CM.mjs.map} +0 -0
  234. /package/dist/sdk/{chunk-MPS4HVQI.mjs.map → chunk-N7HO6KKC.mjs.map} +0 -0
  235. /package/dist/sdk/{chunk-O5EZDNYL.mjs.map → chunk-NCWIZVOT.mjs.map} +0 -0
  236. /package/dist/sdk/{chunk-3NMLT3YS.mjs.map → chunk-PO7X5XI7.mjs.map} +0 -0
  237. /package/dist/sdk/{chunk-BHOKBQPB.mjs.map → chunk-R5Z7YWPB.mjs.map} +0 -0
  238. /package/dist/sdk/{chunk-EORMDOZU.mjs.map → chunk-SGS2VMEL.mjs.map} +0 -0
  239. /package/dist/sdk/{chunk-J2QWVDXK.mjs.map → chunk-XDLQ3UNF.mjs.map} +0 -0
  240. /package/dist/sdk/{chunk-S2RUE2RG.mjs.map → chunk-XR7XXGL7.mjs.map} +0 -0
  241. /package/dist/sdk/{chunk-NAW3DB3I.mjs.map → chunk-XXAEN5KU.mjs.map} +0 -0
  242. /package/dist/sdk/{config-DXX64GD3.mjs.map → command-executor-DVVXERLR.mjs.map} +0 -0
  243. /package/dist/sdk/{config-merger-PX3WIT57.mjs.map → config-7VTT64SQ.mjs.map} +0 -0
  244. /package/dist/sdk/{failure-condition-evaluator-G4HMJPXF.mjs.map → config-merger-RKCZJQ44.mjs.map} +0 -0
  245. /package/dist/sdk/{liquid-extensions-I7O7KMHF.mjs.map → failure-condition-evaluator-4WMDF4Q3.mjs.map} +0 -0
  246. /package/dist/sdk/{memory-store-RW5N2NGJ.mjs.map → liquid-extensions-5IZLTFSZ.mjs.map} +0 -0
  247. /package/dist/sdk/{metrics-7PP3EJUH.mjs.map → memory-store-3N4AZCYB.mjs.map} +0 -0
  248. /package/dist/sdk/{prompt-state-EZYOUG75.mjs.map → metrics-GXQ2EDXA.mjs.map} +0 -0
  249. /package/dist/sdk/{routing-QHTGDIXF.mjs.map → prompt-state-YHGXB2OA.mjs.map} +0 -0
  250. /package/dist/sdk/{renderer-schema-CKFB5NDB.mjs.map → renderer-schema-CMXOLNIG.mjs.map} +0 -0
  251. /package/dist/sdk/{session-registry-4E6YRQ77.mjs.map → routing-S3Y7T2X3.mjs.map} +0 -0
  252. /package/dist/sdk/{trace-helpers-VP6QYVBX.mjs.map → session-registry-6PV6SGEJ.mjs.map} +0 -0
  253. /package/dist/sdk/{tracer-init-GSLPPLCD.mjs.map → trace-helpers-YHNPC7MR.mjs.map} +0 -0
  254. /package/dist/sdk/{workflow-check-provider-3IWBAZP7.mjs.map → tracer-init-XPRWKMZT.mjs.map} +0 -0
  255. /package/dist/sdk/{workflow-registry-KFWSDSLM.mjs.map → workflow-check-provider-4F3432ZP.mjs.map} +0 -0
@@ -1,26 +1,30 @@
1
+ import {
2
+ extractTextFromJson,
3
+ init_json_text_extractor
4
+ } from "./chunk-H5BOW5CR.mjs";
1
5
  import {
2
6
  generateShortHumanId,
3
7
  init_human_id
4
- } from "./chunk-EXFGO4FX.mjs";
8
+ } from "./chunk-KFKHU6CM.mjs";
5
9
  import {
6
10
  failure_condition_evaluator_exports,
7
11
  init_failure_condition_evaluator
8
- } from "./chunk-MPS4HVQI.mjs";
9
- import "./chunk-ZYAUYXSW.mjs";
10
- import "./chunk-S2RUE2RG.mjs";
11
- import "./chunk-CNX7V5JK.mjs";
12
- import "./chunk-BOVFH3LI.mjs";
13
- import "./chunk-IHZOSIF4.mjs";
12
+ } from "./chunk-N7HO6KKC.mjs";
13
+ import "./chunk-J5RGJQ53.mjs";
14
+ import "./chunk-XR7XXGL7.mjs";
15
+ import "./chunk-25IC7KXZ.mjs";
16
+ import "./chunk-VF6XIUE4.mjs";
17
+ import "./chunk-2KB35MB7.mjs";
14
18
  import {
15
19
  init_logger,
16
20
  logger
17
- } from "./chunk-3NMLT3YS.mjs";
18
- import "./chunk-YSN4G6CI.mjs";
21
+ } from "./chunk-PO7X5XI7.mjs";
22
+ import "./chunk-HEX3RL32.mjs";
19
23
  import {
20
24
  __esm,
21
25
  __export,
22
26
  __toCommonJS
23
- } from "./chunk-WMJKH4XE.mjs";
27
+ } from "./chunk-J7LXIPZS.mjs";
24
28
 
25
29
  // src/footer.ts
26
30
  function generateFooter(options = {}) {
@@ -804,621 +808,549 @@ ${content}
804
808
  });
805
809
 
806
810
  // src/frontends/github-frontend.ts
807
- init_logger();
811
+ var GitHubFrontend;
812
+ var init_github_frontend = __esm({
813
+ "src/frontends/github-frontend.ts"() {
814
+ init_logger();
815
+ init_json_text_extractor();
816
+ GitHubFrontend = class {
817
+ name = "github";
818
+ subs = [];
819
+ checkRunIds = /* @__PURE__ */ new Map();
820
+ revision = 0;
821
+ cachedCommentId;
822
+ // legacy single-thread id (kept for compatibility)
823
+ // Group → (checkId → SectionState)
824
+ stepStatusByGroup = /* @__PURE__ */ new Map();
825
+ // Debounce/coalescing state
826
+ debounceMs = 400;
827
+ maxWaitMs = 2e3;
828
+ _timer = null;
829
+ _lastFlush = 0;
830
+ _pendingIds = /* @__PURE__ */ new Set();
831
+ // Mutex for serializing comment updates per group
832
+ updateLocks = /* @__PURE__ */ new Map();
833
+ minUpdateDelayMs = 1e3;
834
+ // Minimum delay between updates (public for testing)
835
+ // Cache of created GitHub comment IDs per group to handle API eventual consistency
836
+ createdCommentGithubIds = /* @__PURE__ */ new Map();
837
+ start(ctx) {
838
+ const log = ctx.logger;
839
+ const bus = ctx.eventBus;
840
+ const octokit = ctx.octokit;
841
+ const repo = ctx.run.repo;
842
+ const pr = ctx.run.pr;
843
+ const headSha = ctx.run.headSha;
844
+ const canPostComments = !!(octokit && repo && pr);
845
+ const canPostChecks = !!(octokit && repo && pr && headSha);
846
+ const svc = canPostChecks ? new (init_github_check_service(), __toCommonJS(github_check_service_exports)).GitHubCheckService(octokit) : null;
847
+ const CommentManager2 = (init_github_comments(), __toCommonJS(github_comments_exports)).CommentManager;
848
+ const comments = canPostComments ? new CommentManager2(octokit) : null;
849
+ const threadKey = repo && pr && headSha ? `${repo.owner}/${repo.name}#${pr}@${(headSha || "").substring(0, 7)}` : ctx.run.runId;
850
+ this.cachedCommentId = `visor-thread-${threadKey}`;
851
+ this.subs.push(
852
+ bus.on("CheckScheduled", async (env) => {
853
+ const ev = env && env.payload || env;
854
+ try {
855
+ if (!canPostChecks || !svc) return;
856
+ if (this.checkRunIds.has(ev.checkId)) return;
857
+ const group = this.getGroupForCheck(ctx, ev.checkId);
858
+ this.upsertSectionState(group, ev.checkId, {
859
+ status: "queued",
860
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
861
+ });
862
+ const res = await svc.createCheckRun(
863
+ {
864
+ owner: repo.owner,
865
+ repo: repo.name,
866
+ head_sha: headSha,
867
+ name: `Visor: ${ev.checkId}`,
868
+ external_id: `visor:${ctx.run.runId}:${ev.checkId}`,
869
+ engine_mode: "state-machine"
870
+ },
871
+ { title: `${ev.checkId}`, summary: "Queued" }
872
+ );
873
+ this.checkRunIds.set(ev.checkId, res.id);
874
+ } catch (e) {
875
+ log.warn(
876
+ `[github-frontend] createCheckRun failed for ${ev.checkId}: ${e instanceof Error ? e.message : e}`
877
+ );
878
+ }
879
+ })
880
+ );
881
+ this.subs.push(
882
+ bus.on("CheckCompleted", async (env) => {
883
+ const ev = env && env.payload || env;
884
+ try {
885
+ if (canPostChecks && svc && this.checkRunIds.has(ev.checkId)) {
886
+ const id = this.checkRunIds.get(ev.checkId);
887
+ const issues = Array.isArray(ev.result?.issues) ? ev.result.issues : [];
888
+ const failureResults = await this.evaluateFailureResults(ctx, ev.checkId, ev.result);
889
+ await svc.completeCheckRun(
890
+ repo.owner,
891
+ repo.name,
892
+ id,
893
+ ev.checkId,
894
+ failureResults,
895
+ issues,
896
+ void 0,
897
+ void 0,
898
+ pr,
899
+ headSha
900
+ );
901
+ }
902
+ if (canPostComments && comments) {
903
+ const count = Array.isArray(ev.result?.issues) ? ev.result.issues.length : 0;
904
+ const failureResults = await this.evaluateFailureResults(ctx, ev.checkId, ev.result);
905
+ const failed = Array.isArray(failureResults) ? failureResults.some((r) => r && r.failed) : false;
906
+ const group = this.getGroupForCheck(ctx, ev.checkId);
907
+ const rawContent = ev?.result?.content;
908
+ const extractedContent = extractTextFromJson(rawContent);
909
+ this.upsertSectionState(group, ev.checkId, {
910
+ status: "completed",
911
+ conclusion: failed ? "failure" : "success",
912
+ issues: count,
913
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
914
+ content: extractedContent
915
+ });
916
+ await this.updateGroupedComment(ctx, comments, group, ev.checkId);
917
+ }
918
+ } catch (e) {
919
+ log.warn(
920
+ `[github-frontend] handle CheckCompleted failed: ${e instanceof Error ? e.message : e}`
921
+ );
922
+ }
923
+ })
924
+ );
925
+ this.subs.push(
926
+ bus.on("CheckErrored", async (env) => {
927
+ const ev = env && env.payload || env;
928
+ try {
929
+ if (canPostChecks && svc && this.checkRunIds.has(ev.checkId)) {
930
+ const id = this.checkRunIds.get(ev.checkId);
931
+ await svc.completeCheckRun(
932
+ repo.owner,
933
+ repo.name,
934
+ id,
935
+ ev.checkId,
936
+ [],
937
+ [],
938
+ ev.error?.message || "Execution error",
939
+ void 0,
940
+ pr,
941
+ headSha
942
+ );
943
+ }
944
+ if (canPostComments && comments) {
945
+ const group = this.getGroupForCheck(ctx, ev.checkId);
946
+ this.upsertSectionState(group, ev.checkId, {
947
+ status: "errored",
948
+ conclusion: "failure",
949
+ issues: 0,
950
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
951
+ error: ev.error?.message || "Execution error"
952
+ });
953
+ await this.updateGroupedComment(ctx, comments, group, ev.checkId);
954
+ }
955
+ } catch (e) {
956
+ log.warn(
957
+ `[github-frontend] handle CheckErrored failed: ${e instanceof Error ? e.message : e}`
958
+ );
959
+ }
960
+ })
961
+ );
962
+ this.subs.push(
963
+ bus.on("StateTransition", async (env) => {
964
+ const ev = env && env.payload || env;
965
+ try {
966
+ if (ev.to === "Completed" || ev.to === "Error") {
967
+ if (canPostComments && comments) {
968
+ for (const group of this.stepStatusByGroup.keys()) {
969
+ await this.updateGroupedComment(ctx, comments, group);
970
+ }
971
+ }
972
+ }
973
+ } catch (e) {
974
+ log.warn(
975
+ `[github-frontend] handle StateTransition failed: ${e instanceof Error ? e.message : e}`
976
+ );
977
+ }
978
+ })
979
+ );
980
+ }
981
+ stop() {
982
+ for (const s of this.subs) s.unsubscribe();
983
+ this.subs = [];
984
+ }
985
+ async buildFullBody(ctx, group) {
986
+ const header = this.renderThreadHeader(ctx, group);
987
+ const sections = this.renderSections(ctx, group);
988
+ return `${header}
808
989
 
809
- // src/utils/json-text-extractor.ts
810
- function extractTextFieldFromMalformedJson(content) {
811
- const fieldPatterns = [
812
- /^\s*\{\s*"text"\s*:\s*"/i,
813
- /^\s*\{\s*"response"\s*:\s*"/i,
814
- /^\s*\{\s*"message"\s*:\s*"/i
815
- ];
816
- for (const pattern of fieldPatterns) {
817
- const match = pattern.exec(content);
818
- if (match) {
819
- const valueStart = match[0].length;
820
- const remaining = content.substring(valueStart);
821
- let value = "";
822
- let i = 0;
823
- while (i < remaining.length) {
824
- const char = remaining[i];
825
- if (char === "\\" && i + 1 < remaining.length) {
826
- const nextChar = remaining[i + 1];
827
- if (nextChar === "n") {
828
- value += "\n";
829
- } else if (nextChar === "r") {
830
- value += "\r";
831
- } else if (nextChar === "t") {
832
- value += " ";
833
- } else if (nextChar === '"') {
834
- value += '"';
835
- } else if (nextChar === "\\") {
836
- value += "\\";
837
- } else {
838
- value += char + nextChar;
839
- }
840
- i += 2;
841
- } else if (char === '"') {
842
- break;
843
- } else {
844
- value += char;
845
- i++;
846
- }
990
+ ${sections}
991
+
992
+ <!-- visor:thread-end key="${this.threadKeyFor(ctx)}" -->`;
847
993
  }
848
- if (value.trim().length > 0) {
849
- return value.trim();
994
+ threadKeyFor(ctx) {
995
+ const r = ctx.run;
996
+ return r.repo && r.pr && r.headSha ? `${r.repo.owner}/${r.repo.name}#${r.pr}@${(r.headSha || "").substring(0, 7)}` : r.runId;
850
997
  }
851
- }
852
- }
853
- return void 0;
854
- }
855
- function extractTextFromJson(content) {
856
- if (content === void 0 || content === null) return void 0;
857
- let parsed = content;
858
- if (typeof content === "string") {
859
- const trimmed = content.trim();
860
- if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
861
- return trimmed.length > 0 ? trimmed : void 0;
862
- }
863
- try {
864
- parsed = JSON.parse(trimmed);
865
- } catch {
866
- const extracted = extractTextFieldFromMalformedJson(trimmed);
867
- if (extracted) {
868
- return extracted;
998
+ renderThreadHeader(ctx, group) {
999
+ const header = {
1000
+ key: this.threadKeyFor(ctx),
1001
+ runId: ctx.run.runId,
1002
+ workflowId: ctx.run.workflowId,
1003
+ revision: this.revision,
1004
+ group,
1005
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
1006
+ };
1007
+ return `<!-- visor:thread=${JSON.stringify(header)} -->`;
869
1008
  }
870
- return trimmed.length > 0 ? trimmed : void 0;
871
- }
872
- }
873
- if (parsed && typeof parsed === "object") {
874
- const txt = parsed.text || parsed.response || parsed.message;
875
- if (typeof txt === "string" && txt.trim()) {
876
- return txt.trim();
877
- }
878
- }
879
- if (typeof content === "string") {
880
- const trimmed = content.trim();
881
- return trimmed.length > 0 ? trimmed : void 0;
882
- }
883
- return void 0;
884
- }
885
-
886
- // src/frontends/github-frontend.ts
887
- var GitHubFrontend = class {
888
- name = "github";
889
- subs = [];
890
- checkRunIds = /* @__PURE__ */ new Map();
891
- revision = 0;
892
- cachedCommentId;
893
- // legacy single-thread id (kept for compatibility)
894
- // Group → (checkId → SectionState)
895
- stepStatusByGroup = /* @__PURE__ */ new Map();
896
- // Debounce/coalescing state
897
- debounceMs = 400;
898
- maxWaitMs = 2e3;
899
- _timer = null;
900
- _lastFlush = 0;
901
- _pendingIds = /* @__PURE__ */ new Set();
902
- // Mutex for serializing comment updates per group
903
- updateLocks = /* @__PURE__ */ new Map();
904
- minUpdateDelayMs = 1e3;
905
- // Minimum delay between updates (public for testing)
906
- // Cache of created GitHub comment IDs per group to handle API eventual consistency
907
- createdCommentGithubIds = /* @__PURE__ */ new Map();
908
- start(ctx) {
909
- const log = ctx.logger;
910
- const bus = ctx.eventBus;
911
- const octokit = ctx.octokit;
912
- const repo = ctx.run.repo;
913
- const pr = ctx.run.pr;
914
- const headSha = ctx.run.headSha;
915
- const canPostComments = !!(octokit && repo && pr);
916
- const canPostChecks = !!(octokit && repo && pr && headSha);
917
- const svc = canPostChecks ? new (init_github_check_service(), __toCommonJS(github_check_service_exports)).GitHubCheckService(octokit) : null;
918
- const CommentManager2 = (init_github_comments(), __toCommonJS(github_comments_exports)).CommentManager;
919
- const comments = canPostComments ? new CommentManager2(octokit) : null;
920
- const threadKey = repo && pr && headSha ? `${repo.owner}/${repo.name}#${pr}@${(headSha || "").substring(0, 7)}` : ctx.run.runId;
921
- this.cachedCommentId = `visor-thread-${threadKey}`;
922
- this.subs.push(
923
- bus.on("CheckScheduled", async (env) => {
924
- const ev = env && env.payload || env;
925
- try {
926
- if (!canPostChecks || !svc) return;
927
- if (this.checkRunIds.has(ev.checkId)) return;
928
- const group = this.getGroupForCheck(ctx, ev.checkId);
929
- this.upsertSectionState(group, ev.checkId, {
930
- status: "queued",
931
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
932
- });
933
- const res = await svc.createCheckRun(
934
- {
935
- owner: repo.owner,
936
- repo: repo.name,
937
- head_sha: headSha,
938
- name: `Visor: ${ev.checkId}`,
939
- external_id: `visor:${ctx.run.runId}:${ev.checkId}`,
940
- engine_mode: "state-machine"
941
- },
942
- { title: `${ev.checkId}`, summary: "Queued" }
943
- );
944
- this.checkRunIds.set(ev.checkId, res.id);
945
- } catch (e) {
946
- log.warn(
947
- `[github-frontend] createCheckRun failed for ${ev.checkId}: ${e instanceof Error ? e.message : e}`
948
- );
1009
+ renderSections(ctx, group) {
1010
+ const lines = [];
1011
+ const groupMap = this.stepStatusByGroup.get(group) || /* @__PURE__ */ new Map();
1012
+ for (const [checkId, st] of groupMap.entries()) {
1013
+ const start = `<!-- visor:section=${JSON.stringify({ id: checkId, revision: this.revision })} -->`;
1014
+ const end = `<!-- visor:section-end id="${checkId}" -->`;
1015
+ const body = st.content && st.content.toString().trim().length > 0 ? st.content.toString().trim() : "";
1016
+ lines.push(`${start}
1017
+ ${body}
1018
+ ${end}`);
949
1019
  }
950
- })
951
- );
952
- this.subs.push(
953
- bus.on("CheckCompleted", async (env) => {
954
- const ev = env && env.payload || env;
1020
+ return lines.join("\\n\\n");
1021
+ }
1022
+ /**
1023
+ * Acquires a mutex lock for the given group and executes the update.
1024
+ * This ensures only one comment update happens at a time per group,
1025
+ * preventing race conditions where updates overwrite each other.
1026
+ *
1027
+ * Uses a proper queue-based mutex: each new caller chains onto the previous
1028
+ * lock, ensuring strict serialization even when multiple callers wait
1029
+ * simultaneously.
1030
+ */
1031
+ async updateGroupedComment(ctx, comments, group, changedIds) {
1032
+ const existingLock = this.updateLocks.get(group);
1033
+ let resolveLock;
1034
+ const ourLock = new Promise((resolve) => {
1035
+ resolveLock = resolve;
1036
+ });
1037
+ this.updateLocks.set(group, ourLock);
955
1038
  try {
956
- if (canPostChecks && svc && this.checkRunIds.has(ev.checkId)) {
957
- const id = this.checkRunIds.get(ev.checkId);
958
- const issues = Array.isArray(ev.result?.issues) ? ev.result.issues : [];
959
- const failureResults = await this.evaluateFailureResults(ctx, ev.checkId, ev.result);
960
- await svc.completeCheckRun(
961
- repo.owner,
962
- repo.name,
963
- id,
964
- ev.checkId,
965
- failureResults,
966
- issues,
967
- void 0,
968
- void 0,
969
- pr,
970
- headSha
971
- );
1039
+ if (existingLock) {
1040
+ try {
1041
+ await existingLock;
1042
+ } catch (error) {
1043
+ logger.warn(
1044
+ `[github-frontend] Previous update for group ${group} failed: ${error instanceof Error ? error.message : error}`
1045
+ );
1046
+ }
972
1047
  }
973
- if (canPostComments && comments) {
974
- const count = Array.isArray(ev.result?.issues) ? ev.result.issues.length : 0;
975
- const failureResults = await this.evaluateFailureResults(ctx, ev.checkId, ev.result);
976
- const failed = Array.isArray(failureResults) ? failureResults.some((r) => r && r.failed) : false;
977
- const group = this.getGroupForCheck(ctx, ev.checkId);
978
- const rawContent = ev?.result?.content;
979
- const extractedContent = extractTextFromJson(rawContent);
980
- this.upsertSectionState(group, ev.checkId, {
981
- status: "completed",
982
- conclusion: failed ? "failure" : "success",
983
- issues: count,
984
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
985
- content: extractedContent
986
- });
987
- await this.updateGroupedComment(ctx, comments, group, ev.checkId);
1048
+ await this.performGroupedCommentUpdate(ctx, comments, group, changedIds);
1049
+ } finally {
1050
+ if (this.updateLocks.get(group) === ourLock) {
1051
+ this.updateLocks.delete(group);
988
1052
  }
989
- } catch (e) {
990
- log.warn(
991
- `[github-frontend] handle CheckCompleted failed: ${e instanceof Error ? e.message : e}`
992
- );
1053
+ resolveLock();
993
1054
  }
994
- })
995
- );
996
- this.subs.push(
997
- bus.on("CheckErrored", async (env) => {
998
- const ev = env && env.payload || env;
1055
+ }
1056
+ /**
1057
+ * Performs the actual comment update with delay enforcement.
1058
+ */
1059
+ async performGroupedCommentUpdate(ctx, comments, group, changedIds) {
999
1060
  try {
1000
- if (canPostChecks && svc && this.checkRunIds.has(ev.checkId)) {
1001
- const id = this.checkRunIds.get(ev.checkId);
1002
- await svc.completeCheckRun(
1003
- repo.owner,
1004
- repo.name,
1005
- id,
1006
- ev.checkId,
1007
- [],
1008
- [],
1009
- ev.error?.message || "Execution error",
1010
- void 0,
1011
- pr,
1012
- headSha
1061
+ if (!ctx.run.repo || !ctx.run.pr) return;
1062
+ const config = ctx.config;
1063
+ const prCommentEnabled = config?.output?.pr_comment?.enabled !== false;
1064
+ if (!prCommentEnabled) {
1065
+ logger.debug(
1066
+ `[github-frontend] PR comments disabled in config, skipping comment for group: ${group}`
1013
1067
  );
1068
+ return;
1014
1069
  }
1015
- if (canPostComments && comments) {
1016
- const group = this.getGroupForCheck(ctx, ev.checkId);
1017
- this.upsertSectionState(group, ev.checkId, {
1018
- status: "errored",
1019
- conclusion: "failure",
1020
- issues: 0,
1021
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
1022
- error: ev.error?.message || "Execution error"
1023
- });
1024
- await this.updateGroupedComment(ctx, comments, group, ev.checkId);
1070
+ const timeSinceLastFlush = Date.now() - this._lastFlush;
1071
+ if (this._lastFlush > 0 && timeSinceLastFlush < this.minUpdateDelayMs) {
1072
+ const delay = this.minUpdateDelayMs - timeSinceLastFlush;
1073
+ logger.debug(
1074
+ `[github-frontend] Waiting ${delay}ms before next update to prevent rate limiting`
1075
+ );
1076
+ await this.sleep(delay);
1025
1077
  }
1078
+ this.revision++;
1079
+ const commentId = this.commentIdForGroup(ctx, group);
1080
+ const mergedBody = await this.mergeIntoExistingBody(ctx, comments, group, changedIds);
1081
+ const cachedGithubId = this.createdCommentGithubIds.get(commentId);
1082
+ const result = await comments.updateOrCreateComment(
1083
+ ctx.run.repo.owner,
1084
+ ctx.run.repo.name,
1085
+ ctx.run.pr,
1086
+ mergedBody,
1087
+ {
1088
+ commentId,
1089
+ triggeredBy: this.deriveTriggeredBy(ctx),
1090
+ commitSha: ctx.run.headSha,
1091
+ // Pass the cached GitHub comment ID if available
1092
+ cachedGithubCommentId: cachedGithubId
1093
+ }
1094
+ );
1095
+ if (result && result.id) {
1096
+ this.createdCommentGithubIds.set(commentId, result.id);
1097
+ }
1098
+ this._lastFlush = Date.now();
1026
1099
  } catch (e) {
1027
- log.warn(
1028
- `[github-frontend] handle CheckErrored failed: ${e instanceof Error ? e.message : e}`
1100
+ logger.debug(
1101
+ `[github-frontend] updateGroupedComment failed: ${e instanceof Error ? e.message : e}`
1029
1102
  );
1030
1103
  }
1031
- })
1032
- );
1033
- this.subs.push(
1034
- bus.on("StateTransition", async (env) => {
1035
- const ev = env && env.payload || env;
1036
- try {
1037
- if (ev.to === "Completed" || ev.to === "Error") {
1038
- if (canPostComments && comments) {
1039
- for (const group of this.stepStatusByGroup.keys()) {
1040
- await this.updateGroupedComment(ctx, comments, group);
1041
- }
1104
+ }
1105
+ deriveTriggeredBy(ctx) {
1106
+ const ev = ctx.run.event || "";
1107
+ const actor = ctx.run.actor;
1108
+ const commentEvents = /* @__PURE__ */ new Set([
1109
+ "issue_comment",
1110
+ "issue_comment_created",
1111
+ "pr_comment",
1112
+ "comment",
1113
+ "pull_request_review_comment"
1114
+ ]);
1115
+ if (commentEvents.has(ev) && actor) return actor;
1116
+ if (ev) return ev;
1117
+ return actor || "unknown";
1118
+ }
1119
+ async mergeIntoExistingBody(ctx, comments, group, changedIds) {
1120
+ const repo = ctx.run.repo;
1121
+ const pr = ctx.run.pr;
1122
+ const existing = await comments.findVisorComment(
1123
+ repo.owner,
1124
+ repo.name,
1125
+ pr,
1126
+ this.commentIdForGroup(ctx, group)
1127
+ );
1128
+ if (!existing || !existing.body) return this.buildFullBody(ctx, group);
1129
+ const body = String(existing.body);
1130
+ const doc = this.parseSections(body);
1131
+ doc.header = {
1132
+ ...doc.header || {},
1133
+ key: this.threadKeyFor(ctx),
1134
+ revision: this.revision,
1135
+ group
1136
+ };
1137
+ if (changedIds) {
1138
+ const ids = Array.isArray(changedIds) ? changedIds : [changedIds];
1139
+ const fresh = this.renderSections(ctx, group);
1140
+ for (const id of ids) {
1141
+ const block = this.extractSectionById(fresh, id);
1142
+ if (block) doc.sections.set(id, block);
1143
+ }
1144
+ } else {
1145
+ const fresh = this.renderSections(ctx, group);
1146
+ const map = this.stepStatusByGroup.get(group) || /* @__PURE__ */ new Map();
1147
+ for (const [checkId] of map.entries()) {
1148
+ if (!doc.sections.has(checkId)) {
1149
+ const block = this.extractSectionById(fresh, checkId);
1150
+ if (block) doc.sections.set(checkId, block);
1042
1151
  }
1043
1152
  }
1044
- } catch (e) {
1045
- log.warn(
1046
- `[github-frontend] handle StateTransition failed: ${e instanceof Error ? e.message : e}`
1047
- );
1048
1153
  }
1049
- })
1050
- );
1051
- }
1052
- stop() {
1053
- for (const s of this.subs) s.unsubscribe();
1054
- this.subs = [];
1055
- }
1056
- async buildFullBody(ctx, group) {
1057
- const header = this.renderThreadHeader(ctx, group);
1058
- const sections = this.renderSections(ctx, group);
1059
- return `${header}
1060
-
1061
- ${sections}
1062
-
1063
- <!-- visor:thread-end key="${this.threadKeyFor(ctx)}" -->`;
1064
- }
1065
- threadKeyFor(ctx) {
1066
- const r = ctx.run;
1067
- return r.repo && r.pr && r.headSha ? `${r.repo.owner}/${r.repo.name}#${r.pr}@${(r.headSha || "").substring(0, 7)}` : r.runId;
1068
- }
1069
- renderThreadHeader(ctx, group) {
1070
- const header = {
1071
- key: this.threadKeyFor(ctx),
1072
- runId: ctx.run.runId,
1073
- workflowId: ctx.run.workflowId,
1074
- revision: this.revision,
1075
- group,
1076
- generatedAt: (/* @__PURE__ */ new Date()).toISOString()
1077
- };
1078
- return `<!-- visor:thread=${JSON.stringify(header)} -->`;
1079
- }
1080
- renderSections(ctx, group) {
1081
- const lines = [];
1082
- const groupMap = this.stepStatusByGroup.get(group) || /* @__PURE__ */ new Map();
1083
- for (const [checkId, st] of groupMap.entries()) {
1084
- const start = `<!-- visor:section=${JSON.stringify({ id: checkId, revision: this.revision })} -->`;
1085
- const end = `<!-- visor:section-end id="${checkId}" -->`;
1086
- const body = st.content && st.content.toString().trim().length > 0 ? st.content.toString().trim() : "";
1087
- lines.push(`${start}
1088
- ${body}
1089
- ${end}`);
1090
- }
1091
- return lines.join("\\n\\n");
1092
- }
1093
- /**
1094
- * Acquires a mutex lock for the given group and executes the update.
1095
- * This ensures only one comment update happens at a time per group,
1096
- * preventing race conditions where updates overwrite each other.
1097
- *
1098
- * Uses a proper queue-based mutex: each new caller chains onto the previous
1099
- * lock, ensuring strict serialization even when multiple callers wait
1100
- * simultaneously.
1101
- */
1102
- async updateGroupedComment(ctx, comments, group, changedIds) {
1103
- const existingLock = this.updateLocks.get(group);
1104
- let resolveLock;
1105
- const ourLock = new Promise((resolve) => {
1106
- resolveLock = resolve;
1107
- });
1108
- this.updateLocks.set(group, ourLock);
1109
- try {
1110
- if (existingLock) {
1154
+ return this.serializeSections(doc);
1155
+ }
1156
+ parseSections(body) {
1157
+ const sections = /* @__PURE__ */ new Map();
1158
+ const headerRe = /<!--\s*visor:thread=(\{[\s\S]*?\})\s*-->/m;
1159
+ const startRe = /<!--\s*visor:section=(\{[\s\S]*?\})\s*-->/g;
1160
+ const endRe = /<!--\s*visor:section-end\s+id=\"([^\"]+)\"\s*-->/g;
1161
+ const safePick = (obj, allowed) => {
1162
+ if (!obj || typeof obj !== "object" || Array.isArray(obj)) return void 0;
1163
+ const out = /* @__PURE__ */ Object.create(null);
1164
+ for (const [k, t] of Object.entries(allowed)) {
1165
+ if (Object.prototype.hasOwnProperty.call(obj, k)) {
1166
+ const v = obj[k];
1167
+ if (t === "string" && typeof v === "string") out[k] = v;
1168
+ else if (t === "number" && typeof v === "number" && Number.isFinite(v)) out[k] = v;
1169
+ }
1170
+ }
1171
+ return out;
1172
+ };
1173
+ const safeParse = (text) => {
1174
+ try {
1175
+ return JSON.parse(text);
1176
+ } catch {
1177
+ return void 0;
1178
+ }
1179
+ };
1180
+ let header;
1111
1181
  try {
1112
- await existingLock;
1113
- } catch (error) {
1114
- logger.warn(
1115
- `[github-frontend] Previous update for group ${group} failed: ${error instanceof Error ? error.message : error}`
1116
- );
1182
+ const h = headerRe.exec(body);
1183
+ if (h) {
1184
+ const parsed = safeParse(h[1]);
1185
+ const picked = safePick(parsed, {
1186
+ key: "string",
1187
+ runId: "string",
1188
+ workflowId: "string",
1189
+ revision: "number",
1190
+ group: "string",
1191
+ generatedAt: "string"
1192
+ });
1193
+ header = picked;
1194
+ }
1195
+ } catch {
1117
1196
  }
1197
+ let cursor = 0;
1198
+ while (true) {
1199
+ const s = startRe.exec(body);
1200
+ if (!s) break;
1201
+ const metaRaw = safeParse(s[1]);
1202
+ const meta = safePick(metaRaw, { id: "string", revision: "number" }) || { id: "" };
1203
+ const startIdx = startRe.lastIndex;
1204
+ endRe.lastIndex = startIdx;
1205
+ const e = endRe.exec(body);
1206
+ if (!e) break;
1207
+ const id = typeof meta.id === "string" && meta.id ? String(meta.id) : String(e[1]);
1208
+ const content = body.substring(startIdx, e.index).trim();
1209
+ const block = `<!-- visor:section=${JSON.stringify(meta)} -->
1210
+ ${content}
1211
+ <!-- visor:section-end id="${id}" -->`;
1212
+ sections.set(id, block);
1213
+ cursor = endRe.lastIndex;
1214
+ startRe.lastIndex = cursor;
1215
+ }
1216
+ return { header, sections };
1118
1217
  }
1119
- await this.performGroupedCommentUpdate(ctx, comments, group, changedIds);
1120
- } finally {
1121
- if (this.updateLocks.get(group) === ourLock) {
1122
- this.updateLocks.delete(group);
1218
+ serializeSections(doc) {
1219
+ const header = `<!-- visor:thread=${JSON.stringify({ ...doc.header || {}, generatedAt: (/* @__PURE__ */ new Date()).toISOString() })} -->`;
1220
+ const blocks = Array.from(doc.sections.values()).join("\n\n");
1221
+ const key = doc.header && doc.header.key || "";
1222
+ return `${header}
1223
+
1224
+ ${blocks}
1225
+
1226
+ <!-- visor:thread-end key="${key}" -->`;
1123
1227
  }
1124
- resolveLock();
1125
- }
1126
- }
1127
- /**
1128
- * Performs the actual comment update with delay enforcement.
1129
- */
1130
- async performGroupedCommentUpdate(ctx, comments, group, changedIds) {
1131
- try {
1132
- if (!ctx.run.repo || !ctx.run.pr) return;
1133
- const config = ctx.config;
1134
- const prCommentEnabled = config?.output?.pr_comment?.enabled !== false;
1135
- if (!prCommentEnabled) {
1136
- logger.debug(
1137
- `[github-frontend] PR comments disabled in config, skipping comment for group: ${group}`
1228
+ extractSectionById(rendered, id) {
1229
+ const rx = new RegExp(
1230
+ `<!--\\s*visor:section=(\\{[\\s\\S]*?\\})\\s*-->[\\s\\S]*?<!--\\s*visor:section-end\\s+id=\\"${this.escapeRegExp(id)}\\"\\s*-->`,
1231
+ "m"
1138
1232
  );
1139
- return;
1233
+ const m = rx.exec(rendered);
1234
+ return m ? m[0] : void 0;
1140
1235
  }
1141
- const timeSinceLastFlush = Date.now() - this._lastFlush;
1142
- if (this._lastFlush > 0 && timeSinceLastFlush < this.minUpdateDelayMs) {
1143
- const delay = this.minUpdateDelayMs - timeSinceLastFlush;
1144
- logger.debug(
1145
- `[github-frontend] Waiting ${delay}ms before next update to prevent rate limiting`
1146
- );
1147
- await this.sleep(delay);
1236
+ escapeRegExp(s) {
1237
+ return s.replace(/[.*+?^${}()|[\\]\\]/g, "\\$&");
1148
1238
  }
1149
- this.revision++;
1150
- const commentId = this.commentIdForGroup(ctx, group);
1151
- const mergedBody = await this.mergeIntoExistingBody(ctx, comments, group, changedIds);
1152
- const cachedGithubId = this.createdCommentGithubIds.get(commentId);
1153
- const result = await comments.updateOrCreateComment(
1154
- ctx.run.repo.owner,
1155
- ctx.run.repo.name,
1156
- ctx.run.pr,
1157
- mergedBody,
1158
- {
1159
- commentId,
1160
- triggeredBy: this.deriveTriggeredBy(ctx),
1161
- commitSha: ctx.run.headSha,
1162
- // Pass the cached GitHub comment ID if available
1163
- cachedGithubCommentId: cachedGithubId
1239
+ getGroupForCheck(ctx, checkId) {
1240
+ try {
1241
+ const cfg = ctx.config || {};
1242
+ const g = cfg?.checks?.[checkId]?.group || cfg?.steps?.[checkId]?.group;
1243
+ if (typeof g === "string" && g.trim().length > 0) return g;
1244
+ } catch {
1164
1245
  }
1165
- );
1166
- if (result && result.id) {
1167
- this.createdCommentGithubIds.set(commentId, result.id);
1168
- }
1169
- this._lastFlush = Date.now();
1170
- } catch (e) {
1171
- logger.debug(
1172
- `[github-frontend] updateGroupedComment failed: ${e instanceof Error ? e.message : e}`
1173
- );
1174
- }
1175
- }
1176
- deriveTriggeredBy(ctx) {
1177
- const ev = ctx.run.event || "";
1178
- const actor = ctx.run.actor;
1179
- const commentEvents = /* @__PURE__ */ new Set([
1180
- "issue_comment",
1181
- "issue_comment_created",
1182
- "pr_comment",
1183
- "comment",
1184
- "pull_request_review_comment"
1185
- ]);
1186
- if (commentEvents.has(ev) && actor) return actor;
1187
- if (ev) return ev;
1188
- return actor || "unknown";
1189
- }
1190
- async mergeIntoExistingBody(ctx, comments, group, changedIds) {
1191
- const repo = ctx.run.repo;
1192
- const pr = ctx.run.pr;
1193
- const existing = await comments.findVisorComment(
1194
- repo.owner,
1195
- repo.name,
1196
- pr,
1197
- this.commentIdForGroup(ctx, group)
1198
- );
1199
- if (!existing || !existing.body) return this.buildFullBody(ctx, group);
1200
- const body = String(existing.body);
1201
- const doc = this.parseSections(body);
1202
- doc.header = {
1203
- ...doc.header || {},
1204
- key: this.threadKeyFor(ctx),
1205
- revision: this.revision,
1206
- group
1207
- };
1208
- if (changedIds) {
1209
- const ids = Array.isArray(changedIds) ? changedIds : [changedIds];
1210
- const fresh = this.renderSections(ctx, group);
1211
- for (const id of ids) {
1212
- const block = this.extractSectionById(fresh, id);
1213
- if (block) doc.sections.set(id, block);
1246
+ return "review";
1214
1247
  }
1215
- } else {
1216
- const fresh = this.renderSections(ctx, group);
1217
- const map = this.stepStatusByGroup.get(group) || /* @__PURE__ */ new Map();
1218
- for (const [checkId] of map.entries()) {
1219
- if (!doc.sections.has(checkId)) {
1220
- const block = this.extractSectionById(fresh, checkId);
1221
- if (block) doc.sections.set(checkId, block);
1248
+ upsertSectionState(group, checkId, patch) {
1249
+ let groupMap = this.stepStatusByGroup.get(group);
1250
+ if (!groupMap) {
1251
+ groupMap = /* @__PURE__ */ new Map();
1252
+ this.stepStatusByGroup.set(group, groupMap);
1222
1253
  }
1254
+ const prev = groupMap.get(checkId) || { status: "queued", lastUpdated: (/* @__PURE__ */ new Date()).toISOString() };
1255
+ groupMap.set(checkId, { ...prev, ...patch });
1223
1256
  }
1224
- }
1225
- return this.serializeSections(doc);
1226
- }
1227
- parseSections(body) {
1228
- const sections = /* @__PURE__ */ new Map();
1229
- const headerRe = /<!--\s*visor:thread=(\{[\s\S]*?\})\s*-->/m;
1230
- const startRe = /<!--\s*visor:section=(\{[\s\S]*?\})\s*-->/g;
1231
- const endRe = /<!--\s*visor:section-end\s+id=\"([^\"]+)\"\s*-->/g;
1232
- const safePick = (obj, allowed) => {
1233
- if (!obj || typeof obj !== "object" || Array.isArray(obj)) return void 0;
1234
- const out = /* @__PURE__ */ Object.create(null);
1235
- for (const [k, t] of Object.entries(allowed)) {
1236
- if (Object.prototype.hasOwnProperty.call(obj, k)) {
1237
- const v = obj[k];
1238
- if (t === "string" && typeof v === "string") out[k] = v;
1239
- else if (t === "number" && typeof v === "number" && Number.isFinite(v)) out[k] = v;
1257
+ commentIdForGroup(ctx, group) {
1258
+ if (group === "dynamic") {
1259
+ return `visor-thread-dynamic-${ctx.run.runId}`;
1240
1260
  }
1261
+ const r = ctx.run;
1262
+ const base = r.repo && r.pr ? `${r.repo.owner}/${r.repo.name}#${r.pr}` : r.runId;
1263
+ return `visor-thread-${group}-${base}`;
1241
1264
  }
1242
- return out;
1243
- };
1244
- const safeParse = (text) => {
1245
- try {
1246
- return JSON.parse(text);
1247
- } catch {
1248
- return void 0;
1265
+ /**
1266
+ * Compute failure condition results for a completed check so Check Runs map to the
1267
+ * correct GitHub conclusion. This mirrors the engine's evaluation for fail_if.
1268
+ */
1269
+ async evaluateFailureResults(ctx, checkId, result) {
1270
+ try {
1271
+ const config = ctx.config || {};
1272
+ const checks = config && config.checks || {};
1273
+ const checkCfg = checks[checkId] || {};
1274
+ const checkSchema = typeof checkCfg.schema === "string" ? checkCfg.schema : "code-review";
1275
+ const checkGroup = checkCfg.group || "default";
1276
+ const { FailureConditionEvaluator } = (init_failure_condition_evaluator(), __toCommonJS(failure_condition_evaluator_exports));
1277
+ const evaluator = new FailureConditionEvaluator();
1278
+ const reviewSummary = { issues: Array.isArray(result?.issues) ? result.issues : [] };
1279
+ const failures = [];
1280
+ if (config.fail_if) {
1281
+ const failed = await evaluator.evaluateSimpleCondition(
1282
+ checkId,
1283
+ checkSchema,
1284
+ checkGroup,
1285
+ reviewSummary,
1286
+ config.fail_if
1287
+ );
1288
+ failures.push({
1289
+ conditionName: "global_fail_if",
1290
+ failed,
1291
+ expression: config.fail_if,
1292
+ severity: "error",
1293
+ haltExecution: false
1294
+ });
1295
+ }
1296
+ if (checkCfg.fail_if) {
1297
+ const failed = await evaluator.evaluateSimpleCondition(
1298
+ checkId,
1299
+ checkSchema,
1300
+ checkGroup,
1301
+ reviewSummary,
1302
+ checkCfg.fail_if
1303
+ );
1304
+ failures.push({
1305
+ conditionName: `${checkId}_fail_if`,
1306
+ failed,
1307
+ expression: checkCfg.fail_if,
1308
+ severity: "error",
1309
+ haltExecution: false
1310
+ });
1311
+ }
1312
+ return failures;
1313
+ } catch {
1314
+ return [];
1315
+ }
1249
1316
  }
1250
- };
1251
- let header;
1252
- try {
1253
- const h = headerRe.exec(body);
1254
- if (h) {
1255
- const parsed = safeParse(h[1]);
1256
- const picked = safePick(parsed, {
1257
- key: "string",
1258
- runId: "string",
1259
- workflowId: "string",
1260
- revision: "number",
1261
- group: "string",
1262
- generatedAt: "string"
1263
- });
1264
- header = picked;
1317
+ // Debounce helpers
1318
+ scheduleUpdate(ctx, comments, group, id) {
1319
+ if (id) this._pendingIds.add(id);
1320
+ const now = Date.now();
1321
+ const since = now - this._lastFlush;
1322
+ const remaining = this.maxWaitMs - since;
1323
+ if (this._timer) clearTimeout(this._timer);
1324
+ const wait = Math.max(0, Math.min(this.debounceMs, remaining));
1325
+ this._timer = setTimeout(async () => {
1326
+ const ids = Array.from(this._pendingIds);
1327
+ this._pendingIds.clear();
1328
+ this._timer = null;
1329
+ await this.updateGroupedComment(ctx, comments, group, ids.length > 0 ? ids : void 0);
1330
+ this._lastFlush = Date.now();
1331
+ }, wait);
1265
1332
  }
1266
- } catch {
1267
- }
1268
- let cursor = 0;
1269
- while (true) {
1270
- const s = startRe.exec(body);
1271
- if (!s) break;
1272
- const metaRaw = safeParse(s[1]);
1273
- const meta = safePick(metaRaw, { id: "string", revision: "number" }) || { id: "" };
1274
- const startIdx = startRe.lastIndex;
1275
- endRe.lastIndex = startIdx;
1276
- const e = endRe.exec(body);
1277
- if (!e) break;
1278
- const id = typeof meta.id === "string" && meta.id ? String(meta.id) : String(e[1]);
1279
- const content = body.substring(startIdx, e.index).trim();
1280
- const block = `<!-- visor:section=${JSON.stringify(meta)} -->
1281
- ${content}
1282
- <!-- visor:section-end id="${id}" -->`;
1283
- sections.set(id, block);
1284
- cursor = endRe.lastIndex;
1285
- startRe.lastIndex = cursor;
1286
- }
1287
- return { header, sections };
1288
- }
1289
- serializeSections(doc) {
1290
- const header = `<!-- visor:thread=${JSON.stringify({ ...doc.header || {}, generatedAt: (/* @__PURE__ */ new Date()).toISOString() })} -->`;
1291
- const blocks = Array.from(doc.sections.values()).join("\n\n");
1292
- const key = doc.header && doc.header.key || "";
1293
- return `${header}
1294
-
1295
- ${blocks}
1296
-
1297
- <!-- visor:thread-end key="${key}" -->`;
1298
- }
1299
- extractSectionById(rendered, id) {
1300
- const rx = new RegExp(
1301
- `<!--\\s*visor:section=(\\{[\\s\\S]*?\\})\\s*-->[\\s\\S]*?<!--\\s*visor:section-end\\s+id=\\"${this.escapeRegExp(id)}\\"\\s*-->`,
1302
- "m"
1303
- );
1304
- const m = rx.exec(rendered);
1305
- return m ? m[0] : void 0;
1306
- }
1307
- escapeRegExp(s) {
1308
- return s.replace(/[.*+?^${}()|[\\]\\]/g, "\\$&");
1309
- }
1310
- getGroupForCheck(ctx, checkId) {
1311
- try {
1312
- const cfg = ctx.config || {};
1313
- const g = cfg?.checks?.[checkId]?.group || cfg?.steps?.[checkId]?.group;
1314
- if (typeof g === "string" && g.trim().length > 0) return g;
1315
- } catch {
1316
- }
1317
- return "review";
1318
- }
1319
- upsertSectionState(group, checkId, patch) {
1320
- let groupMap = this.stepStatusByGroup.get(group);
1321
- if (!groupMap) {
1322
- groupMap = /* @__PURE__ */ new Map();
1323
- this.stepStatusByGroup.set(group, groupMap);
1324
- }
1325
- const prev = groupMap.get(checkId) || { status: "queued", lastUpdated: (/* @__PURE__ */ new Date()).toISOString() };
1326
- groupMap.set(checkId, { ...prev, ...patch });
1327
- }
1328
- commentIdForGroup(ctx, group) {
1329
- if (group === "dynamic") {
1330
- return `visor-thread-dynamic-${ctx.run.runId}`;
1331
- }
1332
- const r = ctx.run;
1333
- const base = r.repo && r.pr ? `${r.repo.owner}/${r.repo.name}#${r.pr}` : r.runId;
1334
- return `visor-thread-${group}-${base}`;
1335
- }
1336
- /**
1337
- * Compute failure condition results for a completed check so Check Runs map to the
1338
- * correct GitHub conclusion. This mirrors the engine's evaluation for fail_if.
1339
- */
1340
- async evaluateFailureResults(ctx, checkId, result) {
1341
- try {
1342
- const config = ctx.config || {};
1343
- const checks = config && config.checks || {};
1344
- const checkCfg = checks[checkId] || {};
1345
- const checkSchema = typeof checkCfg.schema === "string" ? checkCfg.schema : "code-review";
1346
- const checkGroup = checkCfg.group || "default";
1347
- const { FailureConditionEvaluator } = (init_failure_condition_evaluator(), __toCommonJS(failure_condition_evaluator_exports));
1348
- const evaluator = new FailureConditionEvaluator();
1349
- const reviewSummary = { issues: Array.isArray(result?.issues) ? result.issues : [] };
1350
- const failures = [];
1351
- if (config.fail_if) {
1352
- const failed = await evaluator.evaluateSimpleCondition(
1353
- checkId,
1354
- checkSchema,
1355
- checkGroup,
1356
- reviewSummary,
1357
- config.fail_if
1358
- );
1359
- failures.push({
1360
- conditionName: "global_fail_if",
1361
- failed,
1362
- expression: config.fail_if,
1363
- severity: "error",
1364
- haltExecution: false
1365
- });
1333
+ async flushNow(ctx, comments, group) {
1334
+ if (this._timer) {
1335
+ clearTimeout(this._timer);
1336
+ this._timer = null;
1337
+ }
1338
+ const ids = Array.from(this._pendingIds);
1339
+ this._pendingIds.clear();
1340
+ await this.updateGroupedComment(ctx, comments, group, ids.length > 0 ? ids : void 0);
1341
+ this._lastFlush = Date.now();
1366
1342
  }
1367
- if (checkCfg.fail_if) {
1368
- const failed = await evaluator.evaluateSimpleCondition(
1369
- checkId,
1370
- checkSchema,
1371
- checkGroup,
1372
- reviewSummary,
1373
- checkCfg.fail_if
1374
- );
1375
- failures.push({
1376
- conditionName: `${checkId}_fail_if`,
1377
- failed,
1378
- expression: checkCfg.fail_if,
1379
- severity: "error",
1380
- haltExecution: false
1381
- });
1343
+ /**
1344
+ * Sleep utility for enforcing delays
1345
+ */
1346
+ sleep(ms) {
1347
+ return new Promise((resolve) => setTimeout(resolve, ms));
1382
1348
  }
1383
- return failures;
1384
- } catch {
1385
- return [];
1386
- }
1387
- }
1388
- // Debounce helpers
1389
- scheduleUpdate(ctx, comments, group, id) {
1390
- if (id) this._pendingIds.add(id);
1391
- const now = Date.now();
1392
- const since = now - this._lastFlush;
1393
- const remaining = this.maxWaitMs - since;
1394
- if (this._timer) clearTimeout(this._timer);
1395
- const wait = Math.max(0, Math.min(this.debounceMs, remaining));
1396
- this._timer = setTimeout(async () => {
1397
- const ids = Array.from(this._pendingIds);
1398
- this._pendingIds.clear();
1399
- this._timer = null;
1400
- await this.updateGroupedComment(ctx, comments, group, ids.length > 0 ? ids : void 0);
1401
- this._lastFlush = Date.now();
1402
- }, wait);
1403
- }
1404
- async flushNow(ctx, comments, group) {
1405
- if (this._timer) {
1406
- clearTimeout(this._timer);
1407
- this._timer = null;
1408
- }
1409
- const ids = Array.from(this._pendingIds);
1410
- this._pendingIds.clear();
1411
- await this.updateGroupedComment(ctx, comments, group, ids.length > 0 ? ids : void 0);
1412
- this._lastFlush = Date.now();
1413
- }
1414
- /**
1415
- * Sleep utility for enforcing delays
1416
- */
1417
- sleep(ms) {
1418
- return new Promise((resolve) => setTimeout(resolve, ms));
1349
+ };
1419
1350
  }
1420
- };
1351
+ });
1352
+ init_github_frontend();
1421
1353
  export {
1422
1354
  GitHubFrontend
1423
1355
  };
1424
- //# sourceMappingURL=github-frontend-5PCKKHVC.mjs.map
1356
+ //# sourceMappingURL=github-frontend-3N2NLO66.mjs.map