@probelabs/visor 0.1.106 → 0.1.111

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 (530) hide show
  1. package/README.md +71 -2
  2. package/action.yml +1 -1
  3. package/defaults/code-refiner.yaml +114 -0
  4. package/defaults/{.visor.yaml → code-review.yaml} +35 -226
  5. package/defaults/override.yaml +52 -0
  6. package/defaults/task-refinement.yaml +624 -0
  7. package/defaults/visor.tests.yaml +685 -0
  8. package/defaults/visor.yaml +483 -0
  9. package/dist/action-cli-bridge.d.ts +11 -82
  10. package/dist/action-cli-bridge.d.ts.map +1 -1
  11. package/dist/ai-review-service.d.ts +28 -9
  12. package/dist/ai-review-service.d.ts.map +1 -1
  13. package/dist/check-execution-engine.d.ts +19 -331
  14. package/dist/check-execution-engine.d.ts.map +1 -1
  15. package/dist/cli-main.d.ts.map +1 -1
  16. package/dist/cli.d.ts +0 -1
  17. package/dist/cli.d.ts.map +1 -1
  18. package/dist/config.d.ts +16 -0
  19. package/dist/config.d.ts.map +1 -1
  20. package/dist/cron-scheduler.d.ts +3 -3
  21. package/dist/cron-scheduler.d.ts.map +1 -1
  22. package/dist/debug-visualizer/ws-server.d.ts +7 -1
  23. package/dist/debug-visualizer/ws-server.d.ts.map +1 -1
  24. package/dist/defaults/code-refiner.yaml +114 -0
  25. package/dist/defaults/{.visor.yaml → code-review.yaml} +35 -226
  26. package/dist/defaults/override.yaml +52 -0
  27. package/dist/defaults/task-refinement.yaml +624 -0
  28. package/dist/defaults/visor.tests.yaml +685 -0
  29. package/dist/defaults/visor.yaml +483 -0
  30. package/dist/docs/DEPLOYMENT.md +118 -0
  31. package/dist/docs/GITHUB_CHECKS.md +280 -0
  32. package/dist/docs/NPM_USAGE.md +208 -0
  33. package/dist/docs/action-reference.md +19 -0
  34. package/dist/docs/advanced-ai.md +237 -0
  35. package/dist/docs/ai-configuration.md +535 -0
  36. package/dist/docs/ai-custom-tools-usage.md +261 -0
  37. package/dist/docs/ai-custom-tools.md +392 -0
  38. package/dist/docs/author-permissions.md +610 -0
  39. package/dist/docs/bot-transports-rfc.md +23 -0
  40. package/dist/docs/ci-cli-mode.md +34 -0
  41. package/dist/docs/claude-code.md +74 -0
  42. package/dist/docs/command-provider.md +559 -0
  43. package/dist/docs/commands.md +8 -0
  44. package/dist/docs/configuration.md +324 -0
  45. package/dist/docs/custom-tools.md +424 -0
  46. package/dist/docs/dashboards/README.md +23 -0
  47. package/dist/docs/dashboards/grafana-visor-diagrams.json +20 -0
  48. package/dist/docs/dashboards/grafana-visor-overview.json +33 -0
  49. package/dist/docs/debug-visualizer-progress.md +572 -0
  50. package/dist/docs/debug-visualizer-rfc.md +691 -0
  51. package/dist/docs/debug-visualizer.md +114 -0
  52. package/dist/docs/debugging.md +636 -0
  53. package/dist/docs/default-output-schema.md +28 -0
  54. package/dist/docs/dependencies.md +369 -0
  55. package/dist/docs/dev-playbook.md +9 -0
  56. package/dist/docs/engine-pause-resume-rfc.md +192 -0
  57. package/dist/docs/engine-state-machine-plan.md +333 -0
  58. package/dist/docs/event-driven-github-integration-rfc.md +743 -0
  59. package/dist/docs/event-triggers.md +292 -0
  60. package/dist/docs/execution-statistics-rfc.md +290 -0
  61. package/dist/docs/fact-validator-gap-analysis.md +178 -0
  62. package/dist/docs/fact-validator-implementation-plan.md +1235 -0
  63. package/dist/docs/fail-if.md +95 -0
  64. package/dist/docs/failure-conditions-implementation.md +271 -0
  65. package/dist/docs/failure-conditions-schema.md +173 -0
  66. package/dist/docs/failure-routing-rfc.md +193 -0
  67. package/dist/docs/failure-routing.md +507 -0
  68. package/dist/docs/foreach-dependency-propagation.md +473 -0
  69. package/dist/docs/github-ops.md +89 -0
  70. package/dist/docs/goto-forward-run-plan.md +113 -0
  71. package/dist/docs/guides/criticality-modes.md +332 -0
  72. package/dist/docs/guides/fault-management-and-contracts.md +738 -0
  73. package/dist/docs/guides/workflow-style-guide.md +224 -0
  74. package/dist/docs/http.md +299 -0
  75. package/dist/docs/human-input-provider.md +372 -0
  76. package/dist/docs/lifecycle-hooks.md +253 -0
  77. package/dist/docs/limits.md +64 -0
  78. package/dist/docs/liquid-templates.md +490 -0
  79. package/dist/docs/loop-routing-refactor.md +89 -0
  80. package/dist/docs/mcp-provider.md +557 -0
  81. package/dist/docs/mcp.md +124 -0
  82. package/dist/docs/memory.md +903 -0
  83. package/dist/docs/observability.md +12 -0
  84. package/dist/docs/output-formats.md +20 -0
  85. package/dist/docs/output-formatting.md +29 -0
  86. package/dist/docs/output-history.md +383 -0
  87. package/dist/docs/performance.md +6 -0
  88. package/dist/docs/pluggable.md +124 -0
  89. package/dist/docs/proposals/snapshot-scope-execution.md +236 -0
  90. package/dist/docs/providers/git-checkout.md +589 -0
  91. package/dist/docs/recipes.md +474 -0
  92. package/dist/docs/rfc/git-checkout-step.md +601 -0
  93. package/dist/docs/rfc/on_init-hook.md +1294 -0
  94. package/dist/docs/rfc/workspace-isolation.md +216 -0
  95. package/dist/docs/roadmap/criticality-implementation-tasks.md +92 -0
  96. package/dist/docs/router-patterns.md +339 -0
  97. package/dist/docs/schema-next-pr.md +10 -0
  98. package/dist/docs/schema-templates.md +68 -0
  99. package/dist/docs/script.md +34 -0
  100. package/dist/docs/sdk.md +222 -0
  101. package/dist/docs/security.md +7 -0
  102. package/dist/docs/suppressions.md +89 -0
  103. package/dist/docs/tag-filtering.md +258 -0
  104. package/dist/docs/telemetry-setup.md +119 -0
  105. package/dist/docs/telemetry-tracing-rfc.md +275 -0
  106. package/dist/docs/test-framework-rfc.md +680 -0
  107. package/dist/docs/testing/assertions.md +85 -0
  108. package/dist/docs/testing/ci.md +44 -0
  109. package/dist/docs/testing/cli.md +41 -0
  110. package/dist/docs/testing/cookbook.md +172 -0
  111. package/dist/docs/testing/dsl-reference.md +199 -0
  112. package/dist/docs/testing/fixtures-and-mocks.md +91 -0
  113. package/dist/docs/testing/flows.md +92 -0
  114. package/dist/docs/testing/getting-started.md +93 -0
  115. package/dist/docs/testing/troubleshooting.md +55 -0
  116. package/dist/docs/timeouts.md +50 -0
  117. package/dist/docs/troubleshooting.md +7 -0
  118. package/dist/docs/visor-sdk-rfc.md +186 -0
  119. package/dist/docs/workflows.md +569 -0
  120. package/dist/engine/on-finish/orchestrator.d.ts +19 -0
  121. package/dist/engine/on-finish/orchestrator.d.ts.map +1 -0
  122. package/dist/engine/on-finish/utils.d.ts +44 -0
  123. package/dist/engine/on-finish/utils.d.ts.map +1 -0
  124. package/dist/event-bus/event-bus.d.ts +13 -0
  125. package/dist/event-bus/event-bus.d.ts.map +1 -0
  126. package/dist/event-bus/types.d.ts +71 -0
  127. package/dist/event-bus/types.d.ts.map +1 -0
  128. package/dist/examples/.claude/agents/code-reviewer.md +69 -0
  129. package/dist/examples/.mcp.json +34 -0
  130. package/dist/examples/CALCULATOR-SDK.md +364 -0
  131. package/dist/examples/README.md +384 -0
  132. package/dist/examples/ai-custom-tools-example.yaml +206 -0
  133. package/dist/examples/ai-custom-tools-simple.yaml +76 -0
  134. package/dist/examples/ai-retry-fallback-config.yaml +180 -0
  135. package/dist/examples/ai-with-bash.yaml +126 -0
  136. package/dist/examples/ai-with-mcp.yaml +82 -0
  137. package/dist/examples/basic-human-input.yaml +15 -0
  138. package/dist/examples/bedrock-config.yaml +77 -0
  139. package/dist/examples/calculator-config.yaml +133 -0
  140. package/dist/examples/calculator-json-output-guide.md +311 -0
  141. package/dist/examples/calculator-sdk-automated.ts +340 -0
  142. package/dist/examples/calculator-sdk-example.ts +275 -0
  143. package/dist/examples/calculator-sdk-json.ts +331 -0
  144. package/dist/examples/calculator-sdk-real.ts +374 -0
  145. package/dist/examples/calculator-sdk-test.ts +148 -0
  146. package/dist/examples/claude-code-config.yaml +191 -0
  147. package/dist/examples/cron-webhook-config.yaml +215 -0
  148. package/dist/examples/custom-template.liquid +57 -0
  149. package/dist/examples/custom-tools-example.yaml +281 -0
  150. package/dist/examples/enhanced-config.yaml +165 -0
  151. package/dist/examples/environments/visor.base.yaml +92 -0
  152. package/dist/examples/environments/visor.dev.yaml +33 -0
  153. package/dist/examples/environments/visor.prod.yaml +95 -0
  154. package/dist/examples/environments/visor.staging.yaml +46 -0
  155. package/dist/examples/fact-validator.yaml +361 -0
  156. package/dist/examples/fail-if-simple.yaml +90 -0
  157. package/dist/examples/failure-conditions-advanced.yaml +136 -0
  158. package/dist/examples/failure-conditions-basic.yaml +48 -0
  159. package/dist/examples/failure-conditions-github-style.yaml +119 -0
  160. package/dist/examples/failure-conditions-migration.yaml +74 -0
  161. package/dist/examples/for-loop-example.yaml +176 -0
  162. package/dist/examples/forEach-example.yaml +120 -0
  163. package/dist/examples/git-checkout-basic.yaml +32 -0
  164. package/dist/examples/git-checkout-compare.yaml +59 -0
  165. package/dist/examples/git-checkout-cross-repo.yaml +76 -0
  166. package/dist/examples/github-workflow-with-tags.yml +163 -0
  167. package/dist/examples/http-integration-config.yaml +240 -0
  168. package/dist/examples/https-server-config.yaml +209 -0
  169. package/dist/examples/human-input-example.yaml +63 -0
  170. package/dist/examples/if-conditions.yaml +173 -0
  171. package/dist/examples/jira-simple-example.yaml +56 -0
  172. package/dist/examples/jira-single-issue-workflow.yaml +166 -0
  173. package/dist/examples/jira-workflow-mcp.yaml +182 -0
  174. package/dist/examples/mcp/analyzer.py +119 -0
  175. package/dist/examples/mcp-provider-example.yaml +301 -0
  176. package/dist/examples/memory-counter.yaml +99 -0
  177. package/dist/examples/memory-error-collection.yaml +104 -0
  178. package/dist/examples/memory-exec-js.yaml +247 -0
  179. package/dist/examples/memory-namespace-isolation.yaml +184 -0
  180. package/dist/examples/memory-retry-counter.yaml +65 -0
  181. package/dist/examples/memory-state-machine.yaml +170 -0
  182. package/dist/examples/on-init-import-demo.yaml +179 -0
  183. package/dist/examples/outputs-raw-basic.yaml +26 -0
  184. package/dist/examples/project-with-tools.yaml +174 -0
  185. package/dist/examples/prompts/architecture-analysis.liquid +116 -0
  186. package/dist/examples/prompts/security-comprehensive.liquid +107 -0
  187. package/dist/examples/quick-start-tags.yaml +53 -0
  188. package/dist/examples/reusable-tools.yaml +92 -0
  189. package/dist/examples/reusable-workflows.yaml +88 -0
  190. package/dist/examples/routing-basic.yaml +35 -0
  191. package/dist/examples/routing-dynamic-js.yaml +46 -0
  192. package/dist/examples/routing-foreach.yaml +34 -0
  193. package/dist/examples/routing-goto-event.yaml +34 -0
  194. package/dist/examples/routing-on-success.yaml +25 -0
  195. package/dist/examples/run-calculator-demo.sh +71 -0
  196. package/dist/examples/sdk-basic.mjs +10 -0
  197. package/dist/examples/sdk-cjs.cjs +10 -0
  198. package/dist/examples/sdk-comprehensive.mjs +175 -0
  199. package/dist/examples/sdk-manual-config.mjs +65 -0
  200. package/dist/examples/sdk-typescript.js +81 -0
  201. package/dist/examples/sdk-typescript.ts +92 -0
  202. package/dist/examples/session-reuse-config.yaml +151 -0
  203. package/dist/examples/session-reuse-self.yaml +81 -0
  204. package/dist/examples/slack-simple-chat.yaml +775 -0
  205. package/dist/examples/templates/security-report.liquid +137 -0
  206. package/dist/examples/tools-library.yaml +281 -0
  207. package/dist/examples/transform-example.yaml +199 -0
  208. package/dist/examples/visor-with-tags.yaml +198 -0
  209. package/dist/examples/webhook-pipeline-config.yaml +218 -0
  210. package/dist/examples/workflows/calculator-workflow.yaml +163 -0
  211. package/dist/examples/workflows/code-quality.yaml +222 -0
  212. package/dist/examples/workflows/quick-pr-check.yaml +90 -0
  213. package/dist/examples/workflows/workflow-composition-example.yaml +130 -0
  214. package/dist/failure-condition-evaluator.d.ts +3 -0
  215. package/dist/failure-condition-evaluator.d.ts.map +1 -1
  216. package/dist/frontends/github-frontend.d.ts +58 -0
  217. package/dist/frontends/github-frontend.d.ts.map +1 -0
  218. package/dist/frontends/host.d.ts +47 -0
  219. package/dist/frontends/host.d.ts.map +1 -0
  220. package/dist/frontends/ndjson-sink.d.ts +12 -0
  221. package/dist/frontends/ndjson-sink.d.ts.map +1 -0
  222. package/dist/frontends/slack-frontend.d.ts +58 -0
  223. package/dist/frontends/slack-frontend.d.ts.map +1 -0
  224. package/dist/generated/config-schema.d.ts +967 -57
  225. package/dist/generated/config-schema.d.ts.map +1 -1
  226. package/dist/generated/config-schema.json +1033 -56
  227. package/dist/github-check-service.d.ts +4 -6
  228. package/dist/github-check-service.d.ts.map +1 -1
  229. package/dist/github-comments.d.ts +2 -4
  230. package/dist/github-comments.d.ts.map +1 -1
  231. package/dist/index.d.ts.map +1 -1
  232. package/dist/index.js +134327 -99004
  233. package/dist/liquid-extensions.d.ts.map +1 -1
  234. package/dist/logger.d.ts +2 -0
  235. package/dist/logger.d.ts.map +1 -1
  236. package/dist/memory-store.d.ts +6 -0
  237. package/dist/memory-store.d.ts.map +1 -1
  238. package/dist/output/assistant-json/template.liquid +0 -0
  239. package/dist/output/traces/run-2026-01-20T19-22-58-043Z.ndjson +138 -0
  240. package/dist/output/traces/run-2026-01-20T19-23-52-175Z.ndjson +1067 -0
  241. package/dist/output-formatters.d.ts +1 -1
  242. package/dist/output-formatters.d.ts.map +1 -1
  243. package/dist/providers/ai-check-provider.d.ts +12 -0
  244. package/dist/providers/ai-check-provider.d.ts.map +1 -1
  245. package/dist/providers/check-provider-registry.d.ts +6 -0
  246. package/dist/providers/check-provider-registry.d.ts.map +1 -1
  247. package/dist/providers/check-provider.interface.d.ts +43 -1
  248. package/dist/providers/check-provider.interface.d.ts.map +1 -1
  249. package/dist/providers/claude-code-check-provider.d.ts.map +1 -1
  250. package/dist/providers/command-check-provider.d.ts +1 -1
  251. package/dist/providers/command-check-provider.d.ts.map +1 -1
  252. package/dist/providers/custom-tool-executor.d.ts +61 -0
  253. package/dist/providers/custom-tool-executor.d.ts.map +1 -0
  254. package/dist/providers/git-checkout-provider.d.ts +25 -0
  255. package/dist/providers/git-checkout-provider.d.ts.map +1 -0
  256. package/dist/providers/github-ops-provider.d.ts.map +1 -1
  257. package/dist/providers/http-client-provider.d.ts +4 -4
  258. package/dist/providers/http-client-provider.d.ts.map +1 -1
  259. package/dist/providers/human-input-check-provider.d.ts +5 -0
  260. package/dist/providers/human-input-check-provider.d.ts.map +1 -1
  261. package/dist/providers/index.d.ts +1 -0
  262. package/dist/providers/index.d.ts.map +1 -1
  263. package/dist/providers/log-check-provider.d.ts +2 -5
  264. package/dist/providers/log-check-provider.d.ts.map +1 -1
  265. package/dist/providers/mcp-check-provider.d.ts +10 -4
  266. package/dist/providers/mcp-check-provider.d.ts.map +1 -1
  267. package/dist/providers/mcp-custom-sse-server.d.ts +66 -0
  268. package/dist/providers/mcp-custom-sse-server.d.ts.map +1 -0
  269. package/dist/providers/memory-check-provider.d.ts +2 -8
  270. package/dist/providers/memory-check-provider.d.ts.map +1 -1
  271. package/dist/providers/script-check-provider.d.ts +25 -0
  272. package/dist/providers/script-check-provider.d.ts.map +1 -0
  273. package/dist/providers/workflow-check-provider.d.ts +56 -0
  274. package/dist/providers/workflow-check-provider.d.ts.map +1 -0
  275. package/dist/reviewer.d.ts +2 -1
  276. package/dist/reviewer.d.ts.map +1 -1
  277. package/dist/sdk/check-provider-registry-534KL5HT.mjs +27 -0
  278. package/dist/sdk/chunk-23L3QRYX.mjs +16872 -0
  279. package/dist/sdk/chunk-23L3QRYX.mjs.map +1 -0
  280. package/dist/sdk/{chunk-TUTOLSFV.mjs → chunk-3OMWVM6J.mjs} +11 -1
  281. package/dist/sdk/chunk-3OMWVM6J.mjs.map +1 -0
  282. package/dist/sdk/chunk-7UK3NIIT.mjs +482 -0
  283. package/dist/sdk/chunk-7UK3NIIT.mjs.map +1 -0
  284. package/dist/sdk/chunk-AGIZJ4UZ.mjs +173 -0
  285. package/dist/sdk/chunk-AGIZJ4UZ.mjs.map +1 -0
  286. package/dist/sdk/chunk-AIVFBIS4.mjs +1371 -0
  287. package/dist/sdk/chunk-AIVFBIS4.mjs.map +1 -0
  288. package/dist/sdk/chunk-AK6BVWIT.mjs +426 -0
  289. package/dist/sdk/chunk-AK6BVWIT.mjs.map +1 -0
  290. package/dist/sdk/chunk-AUT26LHW.mjs +139 -0
  291. package/dist/sdk/chunk-AUT26LHW.mjs.map +1 -0
  292. package/dist/sdk/chunk-BOVFH3LI.mjs +232 -0
  293. package/dist/sdk/chunk-BOVFH3LI.mjs.map +1 -0
  294. package/dist/sdk/chunk-CNX7V5JK.mjs +89 -0
  295. package/dist/sdk/chunk-CNX7V5JK.mjs.map +1 -0
  296. package/dist/sdk/chunk-HTOKWMPO.mjs +157 -0
  297. package/dist/sdk/chunk-HTOKWMPO.mjs.map +1 -0
  298. package/dist/sdk/chunk-NAW3DB3I.mjs +197 -0
  299. package/dist/sdk/chunk-NAW3DB3I.mjs.map +1 -0
  300. package/dist/sdk/chunk-O5EZDNYL.mjs +274 -0
  301. package/dist/sdk/chunk-O5EZDNYL.mjs.map +1 -0
  302. package/dist/sdk/chunk-QR7MOMJH.mjs +558 -0
  303. package/dist/sdk/chunk-QR7MOMJH.mjs.map +1 -0
  304. package/dist/sdk/chunk-QY2XYPEV.mjs +3556 -0
  305. package/dist/sdk/chunk-QY2XYPEV.mjs.map +1 -0
  306. package/dist/sdk/chunk-S2RUE2RG.mjs +145 -0
  307. package/dist/sdk/chunk-S2RUE2RG.mjs.map +1 -0
  308. package/dist/sdk/chunk-SIWNBRTK.mjs +800 -0
  309. package/dist/sdk/chunk-SIWNBRTK.mjs.map +1 -0
  310. package/dist/sdk/chunk-YSN4G6CI.mjs +146 -0
  311. package/dist/sdk/chunk-YSN4G6CI.mjs.map +1 -0
  312. package/dist/sdk/chunk-ZYAUYXSW.mjs +206 -0
  313. package/dist/sdk/chunk-ZYAUYXSW.mjs.map +1 -0
  314. package/dist/sdk/command-executor-TYUV6HUS.mjs +14 -0
  315. package/dist/sdk/config-YNC2EOOT.mjs +16 -0
  316. package/dist/sdk/config-merger-PX3WIT57.mjs +10 -0
  317. package/dist/sdk/event-bus-5BEVPQ6T.mjs +35 -0
  318. package/dist/sdk/event-bus-5BEVPQ6T.mjs.map +1 -0
  319. package/dist/sdk/failure-condition-evaluator-YGTF2GHG.mjs +17 -0
  320. package/dist/sdk/git-repository-analyzer-HJC4MYW4.mjs +458 -0
  321. package/dist/sdk/git-repository-analyzer-HJC4MYW4.mjs.map +1 -0
  322. package/dist/sdk/github-frontend-SIAEOCON.mjs +1420 -0
  323. package/dist/sdk/github-frontend-SIAEOCON.mjs.map +1 -0
  324. package/dist/sdk/host-DXUYTNMU.mjs +52 -0
  325. package/dist/sdk/host-DXUYTNMU.mjs.map +1 -0
  326. package/dist/sdk/{liquid-extensions-KVL4MKRH.mjs → liquid-extensions-PKWCKK7E.mjs} +8 -2
  327. package/dist/sdk/memory-store-XGBB7LX7.mjs +12 -0
  328. package/dist/sdk/memory-store-XGBB7LX7.mjs.map +1 -0
  329. package/dist/sdk/metrics-7PP3EJUH.mjs +29 -0
  330. package/dist/sdk/metrics-7PP3EJUH.mjs.map +1 -0
  331. package/dist/sdk/ndjson-sink-B4V4NTAQ.mjs +44 -0
  332. package/dist/sdk/ndjson-sink-B4V4NTAQ.mjs.map +1 -0
  333. package/dist/sdk/prompt-state-YRJY6QAL.mjs +16 -0
  334. package/dist/sdk/prompt-state-YRJY6QAL.mjs.map +1 -0
  335. package/dist/sdk/renderer-schema-LPKN5UJS.mjs +51 -0
  336. package/dist/sdk/renderer-schema-LPKN5UJS.mjs.map +1 -0
  337. package/dist/sdk/routing-6N45MJ4F.mjs +24 -0
  338. package/dist/sdk/routing-6N45MJ4F.mjs.map +1 -0
  339. package/dist/sdk/sdk.d.mts +541 -22
  340. package/dist/sdk/sdk.d.ts +541 -22
  341. package/dist/sdk/sdk.js +27963 -16505
  342. package/dist/sdk/sdk.js.map +1 -1
  343. package/dist/sdk/sdk.mjs +1116 -2169
  344. package/dist/sdk/sdk.mjs.map +1 -1
  345. package/dist/sdk/session-registry-4E6YRQ77.mjs +10 -0
  346. package/dist/sdk/session-registry-4E6YRQ77.mjs.map +1 -0
  347. package/dist/sdk/slack-frontend-BVKW3GD5.mjs +735 -0
  348. package/dist/sdk/slack-frontend-BVKW3GD5.mjs.map +1 -0
  349. package/dist/sdk/trace-helpers-VP6QYVBX.mjs +23 -0
  350. package/dist/sdk/trace-helpers-VP6QYVBX.mjs.map +1 -0
  351. package/dist/sdk/{tracer-init-WC75N5NW.mjs → tracer-init-GSLPPLCD.mjs} +2 -2
  352. package/dist/sdk/tracer-init-GSLPPLCD.mjs.map +1 -0
  353. package/dist/sdk/workflow-registry-R6KSACFR.mjs +12 -0
  354. package/dist/sdk/workflow-registry-R6KSACFR.mjs.map +1 -0
  355. package/dist/sdk.d.ts.map +1 -1
  356. package/dist/slack/adapter.d.ts +36 -0
  357. package/dist/slack/adapter.d.ts.map +1 -0
  358. package/dist/slack/cache-prewarmer.d.ts +31 -0
  359. package/dist/slack/cache-prewarmer.d.ts.map +1 -0
  360. package/dist/slack/client.d.ts +77 -0
  361. package/dist/slack/client.d.ts.map +1 -0
  362. package/dist/slack/markdown.d.ts +45 -0
  363. package/dist/slack/markdown.d.ts.map +1 -0
  364. package/dist/slack/prompt-state.d.ts +33 -0
  365. package/dist/slack/prompt-state.d.ts.map +1 -0
  366. package/dist/slack/rate-limiter.d.ts +56 -0
  367. package/dist/slack/rate-limiter.d.ts.map +1 -0
  368. package/dist/slack/signature.d.ts +2 -0
  369. package/dist/slack/signature.d.ts.map +1 -0
  370. package/dist/slack/socket-runner.d.ts +42 -0
  371. package/dist/slack/socket-runner.d.ts.map +1 -0
  372. package/dist/slack/thread-cache.d.ts +51 -0
  373. package/dist/slack/thread-cache.d.ts.map +1 -0
  374. package/dist/snapshot-store.d.ts +59 -0
  375. package/dist/snapshot-store.d.ts.map +1 -0
  376. package/dist/state-machine/context/build-engine-context.d.ts +17 -0
  377. package/dist/state-machine/context/build-engine-context.d.ts.map +1 -0
  378. package/dist/state-machine/dispatch/dependency-gating.d.ts +12 -0
  379. package/dist/state-machine/dispatch/dependency-gating.d.ts.map +1 -0
  380. package/dist/state-machine/dispatch/execution-invoker.d.ts +14 -0
  381. package/dist/state-machine/dispatch/execution-invoker.d.ts.map +1 -0
  382. package/dist/state-machine/dispatch/foreach-processor.d.ts +8 -0
  383. package/dist/state-machine/dispatch/foreach-processor.d.ts.map +1 -0
  384. package/dist/state-machine/dispatch/history-snapshot.d.ts +8 -0
  385. package/dist/state-machine/dispatch/history-snapshot.d.ts.map +1 -0
  386. package/dist/state-machine/dispatch/on-init-handlers.d.ts +43 -0
  387. package/dist/state-machine/dispatch/on-init-handlers.d.ts.map +1 -0
  388. package/dist/state-machine/dispatch/renderer-schema.d.ts +8 -0
  389. package/dist/state-machine/dispatch/renderer-schema.d.ts.map +1 -0
  390. package/dist/state-machine/dispatch/stats-manager.d.ts +15 -0
  391. package/dist/state-machine/dispatch/stats-manager.d.ts.map +1 -0
  392. package/dist/state-machine/dispatch/template-renderer.d.ts +7 -0
  393. package/dist/state-machine/dispatch/template-renderer.d.ts.map +1 -0
  394. package/dist/state-machine/execution/summary.d.ts +8 -0
  395. package/dist/state-machine/execution/summary.d.ts.map +1 -0
  396. package/dist/state-machine/runner.d.ts +79 -0
  397. package/dist/state-machine/runner.d.ts.map +1 -0
  398. package/dist/state-machine/states/check-running.d.ts +14 -0
  399. package/dist/state-machine/states/check-running.d.ts.map +1 -0
  400. package/dist/state-machine/states/completed.d.ts +12 -0
  401. package/dist/state-machine/states/completed.d.ts.map +1 -0
  402. package/dist/state-machine/states/error.d.ts +11 -0
  403. package/dist/state-machine/states/error.d.ts.map +1 -0
  404. package/dist/state-machine/states/init.d.ts +11 -0
  405. package/dist/state-machine/states/init.d.ts.map +1 -0
  406. package/dist/state-machine/states/level-dispatch.d.ts +17 -0
  407. package/dist/state-machine/states/level-dispatch.d.ts.map +1 -0
  408. package/dist/state-machine/states/plan-ready.d.ts +12 -0
  409. package/dist/state-machine/states/plan-ready.d.ts.map +1 -0
  410. package/dist/state-machine/states/routing.d.ts +52 -0
  411. package/dist/state-machine/states/routing.d.ts.map +1 -0
  412. package/dist/state-machine/states/wave-planning.d.ts +14 -0
  413. package/dist/state-machine/states/wave-planning.d.ts.map +1 -0
  414. package/dist/state-machine/workflow-projection.d.ts +47 -0
  415. package/dist/state-machine/workflow-projection.d.ts.map +1 -0
  416. package/dist/state-machine-execution-engine.d.ts +159 -0
  417. package/dist/state-machine-execution-engine.d.ts.map +1 -0
  418. package/dist/telemetry/opentelemetry.d.ts.map +1 -1
  419. package/dist/telemetry/state-capture.d.ts +5 -0
  420. package/dist/telemetry/state-capture.d.ts.map +1 -1
  421. package/dist/test-runner/assertions.d.ts +59 -0
  422. package/dist/test-runner/assertions.d.ts.map +1 -0
  423. package/dist/test-runner/core/environment.d.ts +8 -0
  424. package/dist/test-runner/core/environment.d.ts.map +1 -0
  425. package/dist/test-runner/core/fixture.d.ts +3 -0
  426. package/dist/test-runner/core/fixture.d.ts.map +1 -0
  427. package/dist/test-runner/core/flow-stage.d.ts +32 -0
  428. package/dist/test-runner/core/flow-stage.d.ts.map +1 -0
  429. package/dist/test-runner/core/mocks.d.ts +8 -0
  430. package/dist/test-runner/core/mocks.d.ts.map +1 -0
  431. package/dist/test-runner/core/test-execution-wrapper.d.ts +18 -0
  432. package/dist/test-runner/core/test-execution-wrapper.d.ts.map +1 -0
  433. package/dist/test-runner/evaluators.d.ts +45 -0
  434. package/dist/test-runner/evaluators.d.ts.map +1 -0
  435. package/dist/test-runner/fixture-loader.d.ts +30 -0
  436. package/dist/test-runner/fixture-loader.d.ts.map +1 -0
  437. package/dist/test-runner/index.d.ts +127 -0
  438. package/dist/test-runner/index.d.ts.map +1 -0
  439. package/dist/test-runner/recorders/github-recorder.d.ts +23 -0
  440. package/dist/test-runner/recorders/github-recorder.d.ts.map +1 -0
  441. package/dist/test-runner/recorders/global-recorder.d.ts +4 -0
  442. package/dist/test-runner/recorders/global-recorder.d.ts.map +1 -0
  443. package/dist/test-runner/recorders/slack-recorder.d.ts +17 -0
  444. package/dist/test-runner/recorders/slack-recorder.d.ts.map +1 -0
  445. package/dist/test-runner/utils/selectors.d.ts +2 -0
  446. package/dist/test-runner/utils/selectors.d.ts.map +1 -0
  447. package/dist/test-runner/validator.d.ts +8 -0
  448. package/dist/test-runner/validator.d.ts.map +1 -0
  449. package/dist/traces/run-2026-01-20T19-22-58-043Z.ndjson +138 -0
  450. package/dist/traces/run-2026-01-20T19-23-52-175Z.ndjson +1067 -0
  451. package/dist/types/bot.d.ts +109 -0
  452. package/dist/types/bot.d.ts.map +1 -0
  453. package/dist/types/cli.d.ts +8 -1
  454. package/dist/types/cli.d.ts.map +1 -1
  455. package/dist/types/config.d.ts +459 -9
  456. package/dist/types/config.d.ts.map +1 -1
  457. package/dist/types/engine.d.ts +177 -0
  458. package/dist/types/engine.d.ts.map +1 -0
  459. package/dist/types/execution.d.ts +73 -0
  460. package/dist/types/execution.d.ts.map +1 -0
  461. package/dist/types/git-checkout.d.ts +76 -0
  462. package/dist/types/git-checkout.d.ts.map +1 -0
  463. package/dist/types/github.d.ts +51 -0
  464. package/dist/types/github.d.ts.map +1 -0
  465. package/dist/types/workflow.d.ts +237 -0
  466. package/dist/types/workflow.d.ts.map +1 -0
  467. package/dist/utils/command-executor.d.ts +43 -0
  468. package/dist/utils/command-executor.d.ts.map +1 -0
  469. package/dist/utils/comment-metadata.d.ts +21 -0
  470. package/dist/utils/comment-metadata.d.ts.map +1 -0
  471. package/dist/utils/config-loader.d.ts.map +1 -1
  472. package/dist/utils/config-merger.d.ts.map +1 -1
  473. package/dist/utils/env-exposure.d.ts +3 -0
  474. package/dist/utils/env-exposure.d.ts.map +1 -0
  475. package/dist/utils/file-exclusion.d.ts.map +1 -1
  476. package/dist/utils/interactive-prompt.d.ts +1 -1
  477. package/dist/utils/interactive-prompt.d.ts.map +1 -1
  478. package/dist/utils/json-text-extractor.d.ts +17 -0
  479. package/dist/utils/json-text-extractor.d.ts.map +1 -0
  480. package/dist/utils/sandbox.d.ts +10 -0
  481. package/dist/utils/sandbox.d.ts.map +1 -1
  482. package/dist/utils/script-memory-ops.d.ts +21 -0
  483. package/dist/utils/script-memory-ops.d.ts.map +1 -0
  484. package/dist/utils/template-context.d.ts +8 -0
  485. package/dist/utils/template-context.d.ts.map +1 -0
  486. package/dist/utils/tracer-init.d.ts.map +1 -1
  487. package/dist/utils/workspace-manager.d.ts +118 -0
  488. package/dist/utils/workspace-manager.d.ts.map +1 -0
  489. package/dist/utils/worktree-cleanup.d.ts +33 -0
  490. package/dist/utils/worktree-cleanup.d.ts.map +1 -0
  491. package/dist/utils/worktree-manager.d.ts +153 -0
  492. package/dist/utils/worktree-manager.d.ts.map +1 -0
  493. package/dist/webhook-server.d.ts +3 -3
  494. package/dist/webhook-server.d.ts.map +1 -1
  495. package/dist/workflow-executor.d.ts +81 -0
  496. package/dist/workflow-executor.d.ts.map +1 -0
  497. package/dist/workflow-registry.d.ts +79 -0
  498. package/dist/workflow-registry.d.ts.map +1 -0
  499. package/package.json +12 -5
  500. package/dist/output/traces/run-2025-10-22T18-22-56-873Z.ndjson +0 -218
  501. package/dist/sdk/check-execution-engine-2YYKUUSH.mjs +0 -11
  502. package/dist/sdk/check-execution-engine-6QJXYYON.mjs +0 -11
  503. package/dist/sdk/check-execution-engine-PJZ4ZOKG.mjs +0 -11
  504. package/dist/sdk/chunk-33QVZ2D4.mjs +0 -316
  505. package/dist/sdk/chunk-33QVZ2D4.mjs.map +0 -1
  506. package/dist/sdk/chunk-B5QBV2QJ.mjs +0 -752
  507. package/dist/sdk/chunk-B5QBV2QJ.mjs.map +0 -1
  508. package/dist/sdk/chunk-BVFNRCHT.mjs +0 -14129
  509. package/dist/sdk/chunk-BVFNRCHT.mjs.map +0 -1
  510. package/dist/sdk/chunk-KWZW23FG.mjs +0 -14129
  511. package/dist/sdk/chunk-KWZW23FG.mjs.map +0 -1
  512. package/dist/sdk/chunk-O4RP4BRH.mjs +0 -14092
  513. package/dist/sdk/chunk-O4RP4BRH.mjs.map +0 -1
  514. package/dist/sdk/chunk-TUTOLSFV.mjs.map +0 -1
  515. package/dist/sdk/chunk-U5D2LY66.mjs +0 -245
  516. package/dist/sdk/chunk-U5D2LY66.mjs.map +0 -1
  517. package/dist/sdk/chunk-U7X54EMV.mjs +0 -331
  518. package/dist/sdk/chunk-U7X54EMV.mjs.map +0 -1
  519. package/dist/sdk/config-merger-TWUBWFC2.mjs +0 -8
  520. package/dist/sdk/mermaid-telemetry-SN6A2TKW.mjs +0 -61
  521. package/dist/sdk/mermaid-telemetry-SN6A2TKW.mjs.map +0 -1
  522. package/dist/sdk/mermaid-telemetry-YCTIG76M.mjs +0 -61
  523. package/dist/sdk/mermaid-telemetry-YCTIG76M.mjs.map +0 -1
  524. package/dist/traces/run-2025-10-22T18-22-56-873Z.ndjson +0 -218
  525. /package/dist/sdk/{check-execution-engine-2YYKUUSH.mjs.map → check-provider-registry-534KL5HT.mjs.map} +0 -0
  526. /package/dist/sdk/{check-execution-engine-6QJXYYON.mjs.map → command-executor-TYUV6HUS.mjs.map} +0 -0
  527. /package/dist/sdk/{check-execution-engine-PJZ4ZOKG.mjs.map → config-YNC2EOOT.mjs.map} +0 -0
  528. /package/dist/sdk/{config-merger-TWUBWFC2.mjs.map → config-merger-PX3WIT57.mjs.map} +0 -0
  529. /package/dist/sdk/{liquid-extensions-KVL4MKRH.mjs.map → failure-condition-evaluator-YGTF2GHG.mjs.map} +0 -0
  530. /package/dist/sdk/{tracer-init-WC75N5NW.mjs.map → liquid-extensions-PKWCKK7E.mjs.map} +0 -0
package/dist/sdk/sdk.mjs CHANGED
@@ -1,2320 +1,1267 @@
1
1
  import {
2
- CheckExecutionEngine
3
- } from "./chunk-BVFNRCHT.mjs";
4
- import "./chunk-TUTOLSFV.mjs";
2
+ StateMachineRunner,
3
+ check_provider_registry_exports,
4
+ init_check_provider_registry,
5
+ init_runner
6
+ } from "./chunk-23L3QRYX.mjs";
7
+ import "./chunk-NAW3DB3I.mjs";
8
+ import {
9
+ commandExecutor,
10
+ init_command_executor
11
+ } from "./chunk-AUT26LHW.mjs";
12
+ import "./chunk-HTOKWMPO.mjs";
13
+ import "./chunk-QR7MOMJH.mjs";
14
+ import {
15
+ ConfigManager,
16
+ init_config
17
+ } from "./chunk-QY2XYPEV.mjs";
18
+ import "./chunk-O5EZDNYL.mjs";
19
+ import {
20
+ ExecutionJournal,
21
+ init_snapshot_store
22
+ } from "./chunk-AIVFBIS4.mjs";
23
+ import "./chunk-SIWNBRTK.mjs";
24
+ import "./chunk-BOVFH3LI.mjs";
25
+ import "./chunk-ZYAUYXSW.mjs";
26
+ import "./chunk-S2RUE2RG.mjs";
27
+ import "./chunk-AK6BVWIT.mjs";
28
+ import "./chunk-CNX7V5JK.mjs";
29
+ import {
30
+ MemoryStore,
31
+ init_memory_store
32
+ } from "./chunk-7UK3NIIT.mjs";
5
33
  import {
6
34
  init_logger,
7
35
  logger
8
- } from "./chunk-B5QBV2QJ.mjs";
9
- import "./chunk-U7X54EMV.mjs";
10
- import {
11
- ConfigMerger
12
- } from "./chunk-U5D2LY66.mjs";
36
+ } from "./chunk-AGIZJ4UZ.mjs";
37
+ import "./chunk-YSN4G6CI.mjs";
38
+ import "./chunk-3OMWVM6J.mjs";
13
39
  import {
14
40
  __esm,
15
41
  __export,
16
- __require,
17
42
  __toCommonJS
18
43
  } from "./chunk-WMJKH4XE.mjs";
19
44
 
20
- // src/generated/config-schema.ts
21
- var config_schema_exports = {};
22
- __export(config_schema_exports, {
23
- configSchema: () => configSchema,
24
- default: () => config_schema_default
25
- });
26
- var configSchema, config_schema_default;
27
- var init_config_schema = __esm({
28
- "src/generated/config-schema.ts"() {
45
+ // src/utils/workspace-manager.ts
46
+ import * as fsp from "fs/promises";
47
+ import * as path from "path";
48
+ function shellEscape(str) {
49
+ return "'" + str.replace(/'/g, "'\\''") + "'";
50
+ }
51
+ function sanitizePathComponent(name) {
52
+ return name.replace(/\.\./g, "").replace(/[\/\\]/g, "-").replace(/^\.+/, "").trim() || "unnamed";
53
+ }
54
+ var WorkspaceManager;
55
+ var init_workspace_manager = __esm({
56
+ "src/utils/workspace-manager.ts"() {
29
57
  "use strict";
30
- configSchema = {
31
- $schema: "http://json-schema.org/draft-07/schema#",
32
- $ref: "#/definitions/VisorConfig",
33
- definitions: {
34
- VisorConfig: {
35
- type: "object",
36
- properties: {
37
- version: {
38
- type: "string",
39
- description: "Configuration version"
40
- },
41
- extends: {
42
- anyOf: [
43
- {
44
- type: "string"
45
- },
46
- {
47
- type: "array",
48
- items: {
49
- type: "string"
50
- }
51
- }
52
- ],
53
- description: 'Extends from other configurations - can be file path, HTTP(S) URL, or "default"'
54
- },
55
- steps: {
56
- $ref: "#/definitions/Record%3Cstring%2CCheckConfig%3E",
57
- description: "Step configurations (recommended)"
58
- },
59
- checks: {
60
- $ref: "#/definitions/Record%3Cstring%2CCheckConfig%3E",
61
- description: "Check configurations (legacy, use 'steps' instead) - always populated after normalization"
62
- },
63
- output: {
64
- $ref: "#/definitions/OutputConfig",
65
- description: "Output configuration"
66
- },
67
- http_server: {
68
- $ref: "#/definitions/HttpServerConfig",
69
- description: "HTTP server configuration for receiving webhooks"
70
- },
71
- memory: {
72
- $ref: "#/definitions/MemoryConfig",
73
- description: "Memory storage configuration"
74
- },
75
- env: {
76
- $ref: "#/definitions/EnvConfig",
77
- description: "Global environment variables"
78
- },
79
- ai_model: {
80
- type: "string",
81
- description: "Global AI model setting"
82
- },
83
- ai_provider: {
84
- type: "string",
85
- description: "Global AI provider setting"
86
- },
87
- ai_mcp_servers: {
88
- $ref: "#/definitions/Record%3Cstring%2CMcpServerConfig%3E",
89
- description: "Global MCP servers configuration for AI checks"
90
- },
91
- max_parallelism: {
92
- type: "number",
93
- description: "Maximum number of checks to run in parallel (default: 3)"
94
- },
95
- fail_fast: {
96
- type: "boolean",
97
- description: "Stop execution when any check fails (default: false)"
98
- },
99
- fail_if: {
100
- type: "string",
101
- description: "Simple global fail condition - fails if expression evaluates to true"
102
- },
103
- failure_conditions: {
104
- $ref: "#/definitions/FailureConditions",
105
- description: "Global failure conditions - optional (deprecated, use fail_if)"
106
- },
107
- tag_filter: {
108
- $ref: "#/definitions/TagFilter",
109
- description: "Tag filter for selective check execution"
110
- },
111
- routing: {
112
- $ref: "#/definitions/RoutingDefaults",
113
- description: "Optional routing defaults for retry/goto/run policies"
114
- }
115
- },
116
- required: ["version", "output"],
117
- additionalProperties: false,
118
- description: "Main Visor configuration",
119
- patternProperties: {
120
- "^x-": {}
121
- }
122
- },
123
- "Record<string,CheckConfig>": {
124
- type: "object",
125
- additionalProperties: {
126
- $ref: "#/definitions/CheckConfig"
127
- }
128
- },
129
- CheckConfig: {
130
- type: "object",
131
- properties: {
132
- type: {
133
- $ref: "#/definitions/ConfigCheckType",
134
- description: "Type of check to perform (defaults to 'ai' if not specified)"
135
- },
136
- prompt: {
137
- type: "string",
138
- description: "AI prompt for the check - can be inline string or file path (auto-detected) - required for AI checks"
139
- },
140
- appendPrompt: {
141
- type: "string",
142
- description: "Additional prompt to append when extending configurations - merged with parent prompt"
143
- },
144
- exec: {
145
- type: "string",
146
- description: "Command execution with Liquid template support - required for command checks"
147
- },
148
- stdin: {
149
- type: "string",
150
- description: "Stdin input for tools with Liquid template support - optional for tool checks"
151
- },
152
- url: {
153
- type: "string",
154
- description: "HTTP URL - required for http output checks"
155
- },
156
- body: {
157
- type: "string",
158
- description: "HTTP body template (Liquid) - required for http output checks"
159
- },
160
- method: {
161
- type: "string",
162
- description: "HTTP method (defaults to POST)"
163
- },
164
- headers: {
165
- $ref: "#/definitions/Record%3Cstring%2Cstring%3E",
166
- description: "HTTP headers"
167
- },
168
- endpoint: {
169
- type: "string",
170
- description: "HTTP endpoint path - required for http_input checks"
171
- },
172
- transform: {
173
- type: "string",
174
- description: "Transform template for http_input data (Liquid) - optional"
175
- },
176
- transform_js: {
177
- type: "string",
178
- description: "Transform using JavaScript expressions (evaluated in secure sandbox) - optional"
179
- },
180
- schedule: {
181
- type: "string",
182
- description: 'Cron schedule expression (e.g., "0 2 * * *") - optional for any check type'
183
- },
184
- focus: {
185
- type: "string",
186
- description: "Focus area for the check (security/performance/style/architecture/all) - optional"
187
- },
188
- command: {
189
- type: "string",
190
- description: 'Command that triggers this check (e.g., "review", "security-scan") - optional'
191
- },
192
- on: {
193
- type: "array",
194
- items: {
195
- $ref: "#/definitions/EventTrigger"
196
- },
197
- description: "Events that trigger this check (defaults to ['manual'] if not specified)"
198
- },
199
- triggers: {
200
- type: "array",
201
- items: {
202
- type: "string"
203
- },
204
- description: "File patterns that trigger this check (optional)"
205
- },
206
- ai: {
207
- $ref: "#/definitions/AIProviderConfig",
208
- description: "AI provider configuration (optional)"
209
- },
210
- ai_model: {
211
- type: "string",
212
- description: "AI model to use for this check - overrides global setting"
213
- },
214
- ai_provider: {
215
- type: "string",
216
- description: "AI provider to use for this check - overrides global setting"
217
- },
218
- ai_mcp_servers: {
219
- $ref: "#/definitions/Record%3Cstring%2CMcpServerConfig%3E",
220
- description: "MCP servers for this AI check - overrides global setting"
221
- },
222
- claude_code: {
223
- $ref: "#/definitions/ClaudeCodeConfig",
224
- description: "Claude Code configuration (for claude-code type checks)"
225
- },
226
- env: {
227
- $ref: "#/definitions/EnvConfig",
228
- description: "Environment variables for this check"
229
- },
230
- timeout: {
231
- type: "number",
232
- description: "Timeout in seconds for command execution (default: 60)"
233
- },
234
- depends_on: {
235
- type: "array",
236
- items: {
237
- type: "string"
238
- },
239
- description: "Check IDs that this check depends on (optional)"
240
- },
241
- group: {
242
- type: "string",
243
- description: 'Group name for comment separation (e.g., "code-review", "pr-overview") - optional'
244
- },
245
- schema: {
246
- anyOf: [
247
- {
248
- type: "string"
249
- },
250
- {
251
- $ref: "#/definitions/Record%3Cstring%2Cunknown%3E"
252
- }
253
- ],
254
- description: 'Schema type for template rendering (e.g., "code-review", "markdown") or inline JSON schema object - optional'
255
- },
256
- template: {
257
- $ref: "#/definitions/CustomTemplateConfig",
258
- description: "Custom template configuration - optional"
259
- },
260
- if: {
261
- type: "string",
262
- description: "Condition to determine if check should run - runs if expression evaluates to true"
263
- },
264
- reuse_ai_session: {
265
- type: ["string", "boolean"],
266
- description: "Check name to reuse AI session from, or true to use first dependency (only works with depends_on)"
267
- },
268
- session_mode: {
269
- type: "string",
270
- enum: ["clone", "append"],
271
- description: "How to reuse AI session: 'clone' (default, copy history) or 'append' (share history)"
272
- },
273
- fail_if: {
274
- type: "string",
275
- description: "Simple fail condition - fails check if expression evaluates to true"
276
- },
277
- failure_conditions: {
278
- $ref: "#/definitions/FailureConditions",
279
- description: "Check-specific failure conditions - optional (deprecated, use fail_if)"
280
- },
281
- tags: {
282
- type: "array",
283
- items: {
284
- type: "string"
285
- },
286
- description: 'Tags for categorizing and filtering checks (e.g., ["local", "fast", "security"])'
287
- },
288
- forEach: {
289
- type: "boolean",
290
- description: "Process output as array and run dependent checks for each item"
291
- },
292
- on_fail: {
293
- $ref: "#/definitions/OnFailConfig",
294
- description: "Failure routing configuration for this check (retry/goto/run)"
295
- },
296
- on_success: {
297
- $ref: "#/definitions/OnSuccessConfig",
298
- description: "Success routing configuration for this check (post-actions and optional goto)"
299
- },
300
- message: {
301
- type: "string",
302
- description: "Message template for log checks"
303
- },
304
- level: {
305
- type: "string",
306
- enum: ["debug", "info", "warn", "error"],
307
- description: "Log level for log checks"
308
- },
309
- include_pr_context: {
310
- type: "boolean",
311
- description: "Include PR context in log output"
312
- },
313
- include_dependencies: {
314
- type: "boolean",
315
- description: "Include dependency summaries in log output"
316
- },
317
- include_metadata: {
318
- type: "boolean",
319
- description: "Include execution metadata in log output"
320
- },
321
- operation: {
322
- type: "string",
323
- enum: ["get", "set", "append", "increment", "delete", "clear", "list", "exec_js"],
324
- description: "Memory operation to perform"
325
- },
326
- key: {
327
- type: "string",
328
- description: "Key for memory operation"
329
- },
330
- value: {
331
- description: "Value for set/append operations"
332
- },
333
- value_js: {
334
- type: "string",
335
- description: "JavaScript expression to compute value dynamically"
336
- },
337
- memory_js: {
338
- type: "string",
339
- description: "JavaScript code for exec_js operation with full memory access"
340
- },
341
- namespace: {
342
- type: "string",
343
- description: "Override namespace for this check"
344
- },
345
- op: {
346
- type: "string",
347
- description: "GitHub operation to perform (e.g., 'labels.add', 'labels.remove', 'comment.create')"
348
- },
349
- values: {
350
- anyOf: [
351
- {
352
- type: "array",
353
- items: {
354
- type: "string"
355
- }
356
- },
357
- {
358
- type: "string"
359
- }
360
- ],
361
- description: "Values for GitHub operations (can be array or single value)"
362
- },
363
- transport: {
364
- type: "string",
365
- enum: ["stdio", "sse", "http"],
366
- description: "Transport type for MCP: stdio (default), sse (legacy), or http (streamable HTTP)"
367
- },
368
- methodArgs: {
369
- $ref: "#/definitions/Record%3Cstring%2Cunknown%3E",
370
- description: "Arguments to pass to the MCP method (supports Liquid templates)"
371
- },
372
- argsTransform: {
373
- type: "string",
374
- description: "Transform template for method arguments (Liquid)"
375
- },
376
- sessionId: {
377
- type: "string",
378
- description: "Session ID for HTTP transport (optional, server may generate one)"
379
- },
380
- args: {
381
- type: "array",
382
- items: {
383
- type: "string"
384
- },
385
- description: "Command arguments (for stdio transport in MCP checks)"
386
- },
387
- workingDirectory: {
388
- type: "string",
389
- description: "Working directory (for stdio transport in MCP checks)"
390
- }
391
- },
392
- additionalProperties: false,
393
- description: "Configuration for a single check",
394
- patternProperties: {
395
- "^x-": {}
396
- }
397
- },
398
- ConfigCheckType: {
399
- type: "string",
400
- enum: [
401
- "ai",
402
- "command",
403
- "http",
404
- "http_input",
405
- "http_client",
406
- "noop",
407
- "log",
408
- "memory",
409
- "github",
410
- "claude-code",
411
- "mcp",
412
- "human-input"
413
- ],
414
- description: "Valid check types in configuration"
415
- },
416
- "Record<string,string>": {
417
- type: "object",
418
- additionalProperties: {
419
- type: "string"
420
- }
421
- },
422
- EventTrigger: {
423
- type: "string",
424
- enum: [
425
- "pr_opened",
426
- "pr_updated",
427
- "pr_closed",
428
- "issue_opened",
429
- "issue_comment",
430
- "manual",
431
- "schedule",
432
- "webhook_received"
433
- ],
434
- description: "Valid event triggers for checks"
435
- },
436
- AIProviderConfig: {
437
- type: "object",
438
- properties: {
439
- provider: {
440
- type: "string",
441
- enum: ["google", "anthropic", "openai", "bedrock", "mock"],
442
- description: "AI provider to use"
443
- },
444
- model: {
445
- type: "string",
446
- description: "Model name to use"
447
- },
448
- apiKey: {
449
- type: "string",
450
- description: "API key (usually from environment variables)"
451
- },
452
- timeout: {
453
- type: "number",
454
- description: "Request timeout in milliseconds"
455
- },
456
- debug: {
457
- type: "boolean",
458
- description: "Enable debug mode"
459
- },
460
- mcpServers: {
461
- $ref: "#/definitions/Record%3Cstring%2CMcpServerConfig%3E",
462
- description: "MCP servers configuration"
463
- }
464
- },
465
- additionalProperties: false,
466
- description: "AI provider configuration",
467
- patternProperties: {
468
- "^x-": {}
469
- }
470
- },
471
- "Record<string,McpServerConfig>": {
472
- type: "object",
473
- additionalProperties: {
474
- $ref: "#/definitions/McpServerConfig"
475
- }
476
- },
477
- McpServerConfig: {
478
- type: "object",
479
- properties: {
480
- command: {
481
- type: "string",
482
- description: "Command to execute for the MCP server"
483
- },
484
- args: {
485
- type: "array",
486
- items: {
487
- type: "string"
488
- },
489
- description: "Arguments to pass to the command"
490
- },
491
- env: {
492
- $ref: "#/definitions/Record%3Cstring%2Cstring%3E",
493
- description: "Environment variables for the MCP server"
494
- }
495
- },
496
- required: ["command"],
497
- additionalProperties: false,
498
- description: "MCP Server configuration",
499
- patternProperties: {
500
- "^x-": {}
58
+ init_command_executor();
59
+ init_logger();
60
+ WorkspaceManager = class _WorkspaceManager {
61
+ static instances = /* @__PURE__ */ new Map();
62
+ sessionId;
63
+ basePath;
64
+ workspacePath;
65
+ originalPath;
66
+ config;
67
+ initialized = false;
68
+ mainProjectInfo = null;
69
+ projects = /* @__PURE__ */ new Map();
70
+ cleanupHandlersRegistered = false;
71
+ usedNames = /* @__PURE__ */ new Set();
72
+ constructor(sessionId, originalPath, config) {
73
+ this.sessionId = sessionId;
74
+ this.originalPath = originalPath;
75
+ this.config = {
76
+ enabled: true,
77
+ basePath: process.env.VISOR_WORKSPACE_PATH || "/tmp/visor-workspaces",
78
+ cleanupOnExit: true,
79
+ ...config
80
+ };
81
+ this.basePath = this.config.basePath;
82
+ this.workspacePath = path.join(this.basePath, sanitizePathComponent(this.sessionId));
83
+ }
84
+ /**
85
+ * Get or create a WorkspaceManager instance for a session
86
+ */
87
+ static getInstance(sessionId, originalPath, config) {
88
+ if (!_WorkspaceManager.instances.has(sessionId)) {
89
+ _WorkspaceManager.instances.set(
90
+ sessionId,
91
+ new _WorkspaceManager(sessionId, originalPath, config)
92
+ );
93
+ }
94
+ return _WorkspaceManager.instances.get(sessionId);
95
+ }
96
+ /**
97
+ * Clear all instances (for testing)
98
+ */
99
+ static clearInstances() {
100
+ _WorkspaceManager.instances.clear();
101
+ }
102
+ /**
103
+ * Check if workspace isolation is enabled
104
+ */
105
+ isEnabled() {
106
+ return this.config.enabled;
107
+ }
108
+ /**
109
+ * Get the workspace path
110
+ */
111
+ getWorkspacePath() {
112
+ return this.workspacePath;
113
+ }
114
+ /**
115
+ * Get the original working directory
116
+ */
117
+ getOriginalPath() {
118
+ return this.originalPath;
119
+ }
120
+ /**
121
+ * Get workspace info (only available after initialize)
122
+ */
123
+ getWorkspaceInfo() {
124
+ return this.mainProjectInfo;
125
+ }
126
+ /**
127
+ * Initialize the workspace - creates workspace directory and main project worktree
128
+ */
129
+ async initialize() {
130
+ if (!this.config.enabled) {
131
+ throw new Error("Workspace isolation is not enabled");
132
+ }
133
+ if (this.initialized && this.mainProjectInfo) {
134
+ return this.mainProjectInfo;
135
+ }
136
+ logger.info(`Initializing workspace: ${this.workspacePath}`);
137
+ await fsp.mkdir(this.workspacePath, { recursive: true });
138
+ logger.debug(`Created workspace directory: ${this.workspacePath}`);
139
+ const mainProjectName = sanitizePathComponent(this.extractProjectName(this.originalPath));
140
+ this.usedNames.add(mainProjectName);
141
+ const mainProjectPath = path.join(this.workspacePath, mainProjectName);
142
+ const isGitRepo = await this.isGitRepository(this.originalPath);
143
+ if (isGitRepo) {
144
+ await this.createMainProjectWorktree(mainProjectPath);
145
+ } else {
146
+ logger.debug(`Original path is not a git repo, creating symlink`);
147
+ try {
148
+ await fsp.symlink(this.originalPath, mainProjectPath);
149
+ } catch (error) {
150
+ throw new Error(`Failed to create symlink for main project: ${error}`);
501
151
  }
502
- },
503
- ClaudeCodeConfig: {
504
- type: "object",
505
- properties: {
506
- allowedTools: {
507
- type: "array",
508
- items: {
509
- type: "string"
510
- },
511
- description: "List of allowed tools for Claude Code to use"
512
- },
513
- maxTurns: {
514
- type: "number",
515
- description: "Maximum number of turns in conversation"
516
- },
517
- systemPrompt: {
518
- type: "string",
519
- description: "System prompt for Claude Code"
520
- },
521
- mcpServers: {
522
- $ref: "#/definitions/Record%3Cstring%2CMcpServerConfig%3E",
523
- description: "MCP servers configuration"
524
- },
525
- subagent: {
526
- type: "string",
527
- description: "Path to subagent script"
528
- },
529
- hooks: {
530
- type: "object",
531
- properties: {
532
- onStart: {
533
- type: "string",
534
- description: "Called when check starts"
535
- },
536
- onEnd: {
537
- type: "string",
538
- description: "Called when check ends"
539
- },
540
- onError: {
541
- type: "string",
542
- description: "Called when check encounters an error"
543
- }
544
- },
545
- additionalProperties: false,
546
- description: "Event hooks for lifecycle management",
547
- patternProperties: {
548
- "^x-": {}
152
+ }
153
+ this.registerCleanupHandlers();
154
+ this.mainProjectInfo = {
155
+ sessionId: this.sessionId,
156
+ workspacePath: this.workspacePath,
157
+ mainProjectPath,
158
+ mainProjectName,
159
+ originalPath: this.originalPath
160
+ };
161
+ this.initialized = true;
162
+ logger.info(`Workspace initialized: ${this.workspacePath}`);
163
+ return this.mainProjectInfo;
164
+ }
165
+ /**
166
+ * Add a project to the workspace (creates symlink to worktree)
167
+ */
168
+ async addProject(repository, worktreePath, description) {
169
+ if (!this.initialized) {
170
+ throw new Error("Workspace not initialized. Call initialize() first.");
171
+ }
172
+ let projectName = sanitizePathComponent(description || this.extractRepoName(repository));
173
+ projectName = this.getUniqueName(projectName);
174
+ this.usedNames.add(projectName);
175
+ const workspacePath = path.join(this.workspacePath, projectName);
176
+ await fsp.rm(workspacePath, { recursive: true, force: true });
177
+ try {
178
+ await fsp.symlink(worktreePath, workspacePath);
179
+ } catch (error) {
180
+ throw new Error(`Failed to create symlink for project ${projectName}: ${error}`);
181
+ }
182
+ this.projects.set(projectName, {
183
+ name: projectName,
184
+ path: workspacePath,
185
+ worktreePath,
186
+ repository
187
+ });
188
+ logger.info(`Added project to workspace: ${projectName} -> ${worktreePath}`);
189
+ return workspacePath;
190
+ }
191
+ /**
192
+ * List all projects in the workspace
193
+ */
194
+ listProjects() {
195
+ return Array.from(this.projects.values());
196
+ }
197
+ /**
198
+ * Cleanup the workspace
199
+ */
200
+ async cleanup() {
201
+ logger.info(`Cleaning up workspace: ${this.workspacePath}`);
202
+ try {
203
+ if (this.mainProjectInfo) {
204
+ const mainProjectPath = this.mainProjectInfo.mainProjectPath;
205
+ try {
206
+ const stats = await fsp.lstat(mainProjectPath);
207
+ if (!stats.isSymbolicLink()) {
208
+ await this.removeMainProjectWorktree(mainProjectPath);
549
209
  }
210
+ } catch {
550
211
  }
551
- },
552
- additionalProperties: false,
553
- description: "Claude Code configuration",
554
- patternProperties: {
555
- "^x-": {}
556
212
  }
557
- },
558
- EnvConfig: {
559
- type: "object",
560
- additionalProperties: {
561
- type: ["string", "number", "boolean"]
562
- },
563
- description: "Environment variable reference configuration"
564
- },
565
- "Record<string,unknown>": {
566
- type: "object",
567
- additionalProperties: {}
568
- },
569
- CustomTemplateConfig: {
570
- type: "object",
571
- properties: {
572
- file: {
573
- type: "string",
574
- description: "Path to custom template file (relative to config file or absolute)"
575
- },
576
- content: {
577
- type: "string",
578
- description: "Raw template content as string"
579
- }
580
- },
581
- additionalProperties: false,
582
- description: "Custom template configuration",
583
- patternProperties: {
584
- "^x-": {}
213
+ await fsp.rm(this.workspacePath, { recursive: true, force: true });
214
+ logger.debug(`Removed workspace directory: ${this.workspacePath}`);
215
+ _WorkspaceManager.instances.delete(this.sessionId);
216
+ this.initialized = false;
217
+ this.mainProjectInfo = null;
218
+ this.projects.clear();
219
+ this.usedNames.clear();
220
+ logger.info(`Workspace cleanup completed: ${this.sessionId}`);
221
+ } catch (error) {
222
+ logger.warn(`Failed to cleanup workspace: ${error}`);
223
+ }
224
+ }
225
+ /**
226
+ * Create worktree for the main project
227
+ *
228
+ * visor-disable: architecture - Not using WorktreeManager here because:
229
+ * 1. WorktreeManager expects remote URLs and clones to bare repos first
230
+ * 2. This operates on the LOCAL repo we're already in (no cloning needed)
231
+ * 3. Adding a "local mode" to WorktreeManager would add complexity for minimal benefit
232
+ * The git commands here are simpler (just rev-parse + worktree add) vs WorktreeManager's
233
+ * full clone/bare-repo/fetch/worktree pipeline.
234
+ */
235
+ async createMainProjectWorktree(targetPath) {
236
+ logger.debug(`Creating main project worktree: ${targetPath}`);
237
+ const headResult = await commandExecutor.execute(
238
+ `git -C ${shellEscape(this.originalPath)} rev-parse HEAD`,
239
+ {
240
+ timeout: 1e4
585
241
  }
586
- },
587
- FailureConditions: {
588
- type: "object",
589
- additionalProperties: {
590
- $ref: "#/definitions/FailureCondition"
591
- },
592
- description: "Collection of failure conditions"
593
- },
594
- FailureCondition: {
595
- anyOf: [
596
- {
597
- $ref: "#/definitions/SimpleFailureCondition"
598
- },
242
+ );
243
+ if (headResult.exitCode !== 0) {
244
+ throw new Error(`Failed to get HEAD: ${headResult.stderr}`);
245
+ }
246
+ const headRef = headResult.stdout.trim();
247
+ const createCmd = `git -C ${shellEscape(this.originalPath)} worktree add --detach ${shellEscape(targetPath)} ${shellEscape(headRef)}`;
248
+ const result = await commandExecutor.execute(createCmd, { timeout: 6e4 });
249
+ if (result.exitCode !== 0) {
250
+ throw new Error(`Failed to create main project worktree: ${result.stderr}`);
251
+ }
252
+ logger.debug(`Created main project worktree at ${targetPath}`);
253
+ }
254
+ /**
255
+ * Remove main project worktree
256
+ */
257
+ async removeMainProjectWorktree(worktreePath) {
258
+ logger.debug(`Removing main project worktree: ${worktreePath}`);
259
+ const removeCmd = `git -C ${shellEscape(this.originalPath)} worktree remove ${shellEscape(worktreePath)} --force`;
260
+ const result = await commandExecutor.execute(removeCmd, { timeout: 3e4 });
261
+ if (result.exitCode !== 0) {
262
+ logger.warn(`Failed to remove worktree via git: ${result.stderr}`);
263
+ }
264
+ }
265
+ /**
266
+ * Check if a path is a git repository
267
+ */
268
+ async isGitRepository(dirPath) {
269
+ try {
270
+ const result = await commandExecutor.execute(
271
+ `git -C ${shellEscape(dirPath)} rev-parse --git-dir`,
599
272
  {
600
- $ref: "#/definitions/ComplexFailureCondition"
601
- }
602
- ],
603
- description: "Failure condition - can be a simple expression string or complex object"
604
- },
605
- SimpleFailureCondition: {
606
- type: "string",
607
- description: "Simple failure condition - just an expression string"
608
- },
609
- ComplexFailureCondition: {
610
- type: "object",
611
- properties: {
612
- condition: {
613
- type: "string",
614
- description: "Expression to evaluate using Function Constructor"
615
- },
616
- message: {
617
- type: "string",
618
- description: "Human-readable message when condition is met"
619
- },
620
- severity: {
621
- $ref: "#/definitions/FailureConditionSeverity",
622
- description: "Severity level of the failure"
623
- },
624
- halt_execution: {
625
- type: "boolean",
626
- description: "Whether this condition should halt execution"
627
- }
628
- },
629
- required: ["condition"],
630
- additionalProperties: false,
631
- description: "Complex failure condition with additional metadata",
632
- patternProperties: {
633
- "^x-": {}
634
- }
635
- },
636
- FailureConditionSeverity: {
637
- type: "string",
638
- enum: ["error", "warning", "info"],
639
- description: "Failure condition severity levels"
640
- },
641
- OnFailConfig: {
642
- type: "object",
643
- properties: {
644
- retry: {
645
- $ref: "#/definitions/RetryPolicy",
646
- description: "Retry policy"
647
- },
648
- run: {
649
- type: "array",
650
- items: {
651
- type: "string"
652
- },
653
- description: "Remediation steps to run before reattempt"
654
- },
655
- goto: {
656
- type: "string",
657
- description: "Jump back to an ancestor step (by id)"
658
- },
659
- goto_event: {
660
- $ref: "#/definitions/EventTrigger",
661
- description: "Simulate a different event when performing goto (e.g., 'pr_updated')"
662
- },
663
- goto_js: {
664
- type: "string",
665
- description: "Dynamic goto: JS expression returning step id or null"
666
- },
667
- run_js: {
668
- type: "string",
669
- description: "Dynamic remediation list: JS expression returning string[]"
670
- }
671
- },
672
- additionalProperties: false,
673
- description: "Failure routing configuration per check",
674
- patternProperties: {
675
- "^x-": {}
676
- }
677
- },
678
- RetryPolicy: {
679
- type: "object",
680
- properties: {
681
- max: {
682
- type: "number",
683
- description: "Maximum retry attempts (excluding the first attempt)"
684
- },
685
- backoff: {
686
- $ref: "#/definitions/BackoffPolicy",
687
- description: "Backoff policy"
688
- }
689
- },
690
- additionalProperties: false,
691
- description: "Retry policy for a step",
692
- patternProperties: {
693
- "^x-": {}
694
- }
695
- },
696
- BackoffPolicy: {
697
- type: "object",
698
- properties: {
699
- mode: {
700
- type: "string",
701
- enum: ["fixed", "exponential"],
702
- description: "Backoff mode"
703
- },
704
- delay_ms: {
705
- type: "number",
706
- description: "Initial delay in milliseconds"
707
- }
708
- },
709
- additionalProperties: false,
710
- description: "Backoff policy for retries",
711
- patternProperties: {
712
- "^x-": {}
713
- }
714
- },
715
- OnSuccessConfig: {
716
- type: "object",
717
- properties: {
718
- run: {
719
- type: "array",
720
- items: {
721
- type: "string"
722
- },
723
- description: "Post-success steps to run"
724
- },
725
- goto: {
726
- type: "string",
727
- description: "Optional jump back to ancestor step (by id)"
728
- },
729
- goto_event: {
730
- $ref: "#/definitions/EventTrigger",
731
- description: "Simulate a different event when performing goto (e.g., 'pr_updated')"
732
- },
733
- goto_js: {
734
- type: "string",
735
- description: "Dynamic goto: JS expression returning step id or null"
736
- },
737
- run_js: {
738
- type: "string",
739
- description: "Dynamic post-success steps: JS expression returning string[]"
740
- }
741
- },
742
- additionalProperties: false,
743
- description: "Success routing configuration per check",
744
- patternProperties: {
745
- "^x-": {}
746
- }
747
- },
748
- OutputConfig: {
749
- type: "object",
750
- properties: {
751
- pr_comment: {
752
- $ref: "#/definitions/PrCommentOutput",
753
- description: "PR comment configuration"
754
- },
755
- file_comment: {
756
- $ref: "#/definitions/FileCommentOutput",
757
- description: "File comment configuration (optional)"
758
- },
759
- github_checks: {
760
- $ref: "#/definitions/GitHubCheckOutput",
761
- description: "GitHub check runs configuration (optional)"
762
- },
763
- suppressionEnabled: {
764
- type: "boolean",
765
- description: "Whether to enable issue suppression via visor-disable comments (default: true)"
766
- }
767
- },
768
- required: ["pr_comment"],
769
- additionalProperties: false,
770
- description: "Output configuration",
771
- patternProperties: {
772
- "^x-": {}
773
- }
774
- },
775
- PrCommentOutput: {
776
- type: "object",
777
- properties: {
778
- format: {
779
- $ref: "#/definitions/ConfigOutputFormat",
780
- description: "Format of the output"
781
- },
782
- group_by: {
783
- $ref: "#/definitions/GroupByOption",
784
- description: "How to group the results"
785
- },
786
- collapse: {
787
- type: "boolean",
788
- description: "Whether to collapse sections by default"
789
- },
790
- debug: {
791
- $ref: "#/definitions/DebugConfig",
792
- description: "Debug mode configuration (optional)"
793
- }
794
- },
795
- required: ["format", "group_by", "collapse"],
796
- additionalProperties: false,
797
- description: "PR comment output configuration",
798
- patternProperties: {
799
- "^x-": {}
800
- }
801
- },
802
- ConfigOutputFormat: {
803
- type: "string",
804
- enum: ["table", "json", "markdown", "sarif"],
805
- description: "Valid output formats"
806
- },
807
- GroupByOption: {
808
- type: "string",
809
- enum: ["check", "file", "severity", "group"],
810
- description: "Valid grouping options"
811
- },
812
- DebugConfig: {
813
- type: "object",
814
- properties: {
815
- enabled: {
816
- type: "boolean",
817
- description: "Enable debug mode"
818
- },
819
- includePrompts: {
820
- type: "boolean",
821
- description: "Include AI prompts in debug output"
822
- },
823
- includeRawResponses: {
824
- type: "boolean",
825
- description: "Include raw AI responses in debug output"
826
- },
827
- includeTiming: {
828
- type: "boolean",
829
- description: "Include timing information"
830
- },
831
- includeProviderInfo: {
832
- type: "boolean",
833
- description: "Include provider information"
834
- }
835
- },
836
- required: [
837
- "enabled",
838
- "includePrompts",
839
- "includeRawResponses",
840
- "includeTiming",
841
- "includeProviderInfo"
842
- ],
843
- additionalProperties: false,
844
- description: "Debug mode configuration",
845
- patternProperties: {
846
- "^x-": {}
847
- }
848
- },
849
- FileCommentOutput: {
850
- type: "object",
851
- properties: {
852
- enabled: {
853
- type: "boolean",
854
- description: "Whether file comments are enabled"
855
- },
856
- inline: {
857
- type: "boolean",
858
- description: "Whether to show inline comments"
859
- }
860
- },
861
- required: ["enabled", "inline"],
862
- additionalProperties: false,
863
- description: "File comment output configuration",
864
- patternProperties: {
865
- "^x-": {}
866
- }
867
- },
868
- GitHubCheckOutput: {
869
- type: "object",
870
- properties: {
871
- enabled: {
872
- type: "boolean",
873
- description: "Whether GitHub check runs are enabled"
874
- },
875
- per_check: {
876
- type: "boolean",
877
- description: "Whether to create individual check runs per configured check"
878
- },
879
- name_prefix: {
880
- type: "string",
881
- description: "Custom name prefix for check runs"
273
+ timeout: 5e3
882
274
  }
883
- },
884
- required: ["enabled", "per_check"],
885
- additionalProperties: false,
886
- description: "GitHub Check Runs output configuration",
887
- patternProperties: {
888
- "^x-": {}
889
- }
890
- },
891
- HttpServerConfig: {
892
- type: "object",
893
- properties: {
894
- enabled: {
895
- type: "boolean",
896
- description: "Whether HTTP server is enabled"
897
- },
898
- port: {
899
- type: "number",
900
- description: "Port to listen on"
901
- },
902
- host: {
903
- type: "string",
904
- description: "Host/IP to bind to (defaults to 0.0.0.0)"
905
- },
906
- tls: {
907
- $ref: "#/definitions/TlsConfig",
908
- description: "TLS/SSL configuration for HTTPS"
909
- },
910
- auth: {
911
- $ref: "#/definitions/HttpAuthConfig",
912
- description: "Authentication configuration"
913
- },
914
- endpoints: {
915
- type: "array",
916
- items: {
917
- $ref: "#/definitions/HttpEndpointConfig"
918
- },
919
- description: "HTTP endpoints configuration"
920
- }
921
- },
922
- required: ["enabled", "port"],
923
- additionalProperties: false,
924
- description: "HTTP server configuration for receiving webhooks",
925
- patternProperties: {
926
- "^x-": {}
927
- }
928
- },
929
- TlsConfig: {
930
- type: "object",
931
- properties: {
932
- enabled: {
933
- type: "boolean",
934
- description: "Enable TLS/HTTPS"
935
- },
936
- cert: {
937
- type: "string",
938
- description: "Path to TLS certificate file or certificate content"
939
- },
940
- key: {
941
- type: "string",
942
- description: "Path to TLS key file or key content"
943
- },
944
- ca: {
945
- type: "string",
946
- description: "Path to CA certificate file or CA content (optional)"
947
- },
948
- rejectUnauthorized: {
949
- type: "boolean",
950
- description: "Reject unauthorized connections (default: true)"
951
- }
952
- },
953
- required: ["enabled"],
954
- additionalProperties: false,
955
- description: "TLS/SSL configuration for HTTPS server",
956
- patternProperties: {
957
- "^x-": {}
958
- }
959
- },
960
- HttpAuthConfig: {
961
- type: "object",
962
- properties: {
963
- type: {
964
- type: "string",
965
- enum: ["bearer_token", "hmac", "basic", "none"],
966
- description: "Authentication type"
967
- },
968
- secret: {
969
- type: "string",
970
- description: "Secret or token for authentication"
971
- },
972
- username: {
973
- type: "string",
974
- description: "Username for basic auth"
975
- },
976
- password: {
977
- type: "string",
978
- description: "Password for basic auth"
979
- }
980
- },
981
- required: ["type"],
982
- additionalProperties: false,
983
- description: "HTTP server authentication configuration",
984
- patternProperties: {
985
- "^x-": {}
986
- }
987
- },
988
- HttpEndpointConfig: {
989
- type: "object",
990
- properties: {
991
- path: {
992
- type: "string",
993
- description: "Path for the webhook endpoint"
994
- },
995
- transform: {
996
- type: "string",
997
- description: "Optional transform template (Liquid) for the received data"
998
- },
999
- name: {
1000
- type: "string",
1001
- description: "Optional name/ID for this endpoint"
1002
- }
1003
- },
1004
- required: ["path"],
1005
- additionalProperties: false,
1006
- description: "HTTP server endpoint configuration",
1007
- patternProperties: {
1008
- "^x-": {}
1009
- }
1010
- },
1011
- MemoryConfig: {
1012
- type: "object",
1013
- properties: {
1014
- storage: {
1015
- type: "string",
1016
- enum: ["memory", "file"],
1017
- description: 'Storage mode: "memory" (in-memory, default) or "file" (persistent)'
1018
- },
1019
- format: {
1020
- type: "string",
1021
- enum: ["json", "csv"],
1022
- description: "Storage format (only for file storage, default: json)"
1023
- },
1024
- file: {
1025
- type: "string",
1026
- description: "File path (required if storage: file)"
1027
- },
1028
- namespace: {
1029
- type: "string",
1030
- description: 'Default namespace (default: "default")'
1031
- },
1032
- auto_load: {
1033
- type: "boolean",
1034
- description: "Auto-load on startup (default: true if storage: file)"
1035
- },
1036
- auto_save: {
1037
- type: "boolean",
1038
- description: "Auto-save after operations (default: true if storage: file)"
1039
- }
1040
- },
1041
- additionalProperties: false,
1042
- description: "Memory storage configuration",
1043
- patternProperties: {
1044
- "^x-": {}
1045
- }
1046
- },
1047
- TagFilter: {
1048
- type: "object",
1049
- properties: {
1050
- include: {
1051
- type: "array",
1052
- items: {
1053
- type: "string"
1054
- },
1055
- description: "Tags that checks must have to be included (ANY match)"
1056
- },
1057
- exclude: {
1058
- type: "array",
1059
- items: {
1060
- type: "string"
1061
- },
1062
- description: "Tags that will exclude checks if present (ANY match)"
1063
- }
1064
- },
1065
- additionalProperties: false,
1066
- description: "Tag filter configuration for selective check execution",
1067
- patternProperties: {
1068
- "^x-": {}
1069
- }
1070
- },
1071
- RoutingDefaults: {
1072
- type: "object",
1073
- properties: {
1074
- max_loops: {
1075
- type: "number",
1076
- description: "Per-scope cap on routing transitions (success + failure)"
1077
- },
1078
- defaults: {
1079
- type: "object",
1080
- properties: {
1081
- on_fail: {
1082
- $ref: "#/definitions/OnFailConfig"
1083
- }
1084
- },
1085
- additionalProperties: false,
1086
- description: "Default policies applied to checks (step-level overrides take precedence)",
1087
- patternProperties: {
1088
- "^x-": {}
1089
- }
1090
- }
1091
- },
1092
- additionalProperties: false,
1093
- description: "Global routing defaults",
1094
- patternProperties: {
1095
- "^x-": {}
275
+ );
276
+ return result.exitCode === 0;
277
+ } catch {
278
+ return false;
279
+ }
280
+ }
281
+ /**
282
+ * Extract project name from path
283
+ */
284
+ extractProjectName(dirPath) {
285
+ return path.basename(dirPath);
286
+ }
287
+ /**
288
+ * Extract repository name from owner/repo format
289
+ */
290
+ extractRepoName(repository) {
291
+ if (repository.includes("://") || repository.startsWith("git@")) {
292
+ const match = repository.match(/[/:]([^/:]+\/[^/:]+?)(?:\.git)?$/);
293
+ if (match) {
294
+ return match[1].split("/").pop() || repository;
1096
295
  }
1097
296
  }
297
+ if (repository.includes("/")) {
298
+ return repository.split("/").pop() || repository;
299
+ }
300
+ return repository;
301
+ }
302
+ /**
303
+ * Get a unique name by appending a number if needed
304
+ */
305
+ getUniqueName(baseName) {
306
+ if (!this.usedNames.has(baseName)) {
307
+ return baseName;
308
+ }
309
+ let counter = 2;
310
+ let uniqueName = `${baseName}-${counter}`;
311
+ while (this.usedNames.has(uniqueName)) {
312
+ counter++;
313
+ uniqueName = `${baseName}-${counter}`;
314
+ }
315
+ return uniqueName;
316
+ }
317
+ /**
318
+ * Register cleanup handlers for process exit
319
+ */
320
+ registerCleanupHandlers() {
321
+ if (this.cleanupHandlersRegistered || !this.config.cleanupOnExit) {
322
+ return;
323
+ }
324
+ this.cleanupHandlersRegistered = true;
1098
325
  }
1099
326
  };
1100
- config_schema_default = configSchema;
1101
327
  }
1102
328
  });
1103
329
 
1104
- // src/config.ts
1105
- init_logger();
1106
- import * as yaml2 from "js-yaml";
1107
- import * as fs2 from "fs";
1108
- import * as path2 from "path";
1109
- import simpleGit from "simple-git";
1110
-
1111
- // src/utils/config-loader.ts
1112
- import * as fs from "fs";
1113
- import * as path from "path";
1114
- import * as yaml from "js-yaml";
1115
- var ConfigLoader = class {
1116
- constructor(options = {}) {
1117
- this.options = options;
1118
- this.options = {
1119
- allowRemote: true,
1120
- cacheTTL: 5 * 60 * 1e3,
1121
- // 5 minutes
1122
- timeout: 30 * 1e3,
1123
- // 30 seconds
1124
- maxDepth: 10,
1125
- allowedRemotePatterns: [],
1126
- // Empty by default for security
1127
- projectRoot: this.findProjectRoot(),
1128
- ...options
330
+ // src/state-machine/context/build-engine-context.ts
331
+ var build_engine_context_exports = {};
332
+ __export(build_engine_context_exports, {
333
+ buildEngineContextForRun: () => buildEngineContextForRun,
334
+ initializeWorkspace: () => initializeWorkspace
335
+ });
336
+ import { v4 as uuidv4 } from "uuid";
337
+ function applyCriticalityDefaults(cfg) {
338
+ const checks = cfg.checks || {};
339
+ for (const id of Object.keys(checks)) {
340
+ const c = checks[id];
341
+ if (!c.criticality) c.criticality = "policy";
342
+ if (c.criticality === "info" && typeof c.continue_on_failure === "undefined")
343
+ c.continue_on_failure = true;
344
+ }
345
+ }
346
+ function buildEngineContextForRun(workingDirectory, config, prInfo, debug, maxParallelism, failFast, requestedChecks) {
347
+ const clonedConfig = JSON.parse(JSON.stringify(config));
348
+ const checks = {};
349
+ applyCriticalityDefaults(clonedConfig);
350
+ for (const [checkId, checkConfig] of Object.entries(clonedConfig.checks || {})) {
351
+ checks[checkId] = {
352
+ tags: checkConfig.tags || [],
353
+ triggers: (Array.isArray(checkConfig.on) ? checkConfig.on : [checkConfig.on]).filter(
354
+ Boolean
355
+ ),
356
+ group: checkConfig.group,
357
+ providerType: checkConfig.type || "ai",
358
+ // Normalize depends_on to array (supports string | string[])
359
+ dependencies: Array.isArray(checkConfig.depends_on) ? checkConfig.depends_on : checkConfig.depends_on ? [checkConfig.depends_on] : []
1129
360
  };
1130
361
  }
1131
- cache = /* @__PURE__ */ new Map();
1132
- loadedConfigs = /* @__PURE__ */ new Set();
1133
- /**
1134
- * Determine the source type from a string
1135
- */
1136
- getSourceType(source) {
1137
- if (source === "default") {
1138
- return "default" /* DEFAULT */;
1139
- }
1140
- if (source.startsWith("http://") || source.startsWith("https://")) {
1141
- return "remote" /* REMOTE */;
362
+ if (requestedChecks && requestedChecks.length > 0) {
363
+ for (const checkName of requestedChecks) {
364
+ if (!checks[checkName] && !clonedConfig.checks?.[checkName]) {
365
+ logger.debug(`[StateMachine] Synthesizing minimal config for legacy check: ${checkName}`);
366
+ if (!clonedConfig.checks) {
367
+ clonedConfig.checks = {};
368
+ }
369
+ clonedConfig.checks[checkName] = {
370
+ type: "ai",
371
+ prompt: `Perform ${checkName} analysis`
372
+ };
373
+ checks[checkName] = {
374
+ tags: [],
375
+ triggers: [],
376
+ group: "default",
377
+ providerType: "ai",
378
+ dependencies: []
379
+ };
380
+ }
1142
381
  }
1143
- return "local" /* LOCAL */;
1144
382
  }
1145
- /**
1146
- * Fetch configuration from any source
1147
- */
1148
- async fetchConfig(source, currentDepth = 0) {
1149
- if (currentDepth >= (this.options.maxDepth || 10)) {
1150
- throw new Error(
1151
- `Maximum extends depth (${this.options.maxDepth}) exceeded. Check for circular dependencies.`
1152
- );
1153
- }
1154
- const normalizedSource = this.normalizeSource(source);
1155
- if (this.loadedConfigs.has(normalizedSource)) {
1156
- throw new Error(
1157
- `Circular dependency detected: ${normalizedSource} is already in the extends chain`
1158
- );
1159
- }
1160
- const sourceType = this.getSourceType(source);
1161
- try {
1162
- this.loadedConfigs.add(normalizedSource);
1163
- switch (sourceType) {
1164
- case "default" /* DEFAULT */:
1165
- return await this.fetchDefaultConfig();
1166
- case "remote" /* REMOTE */:
1167
- if (!this.options.allowRemote) {
1168
- throw new Error(
1169
- "Remote extends are disabled. Enable with --allow-remote-extends or remove VISOR_NO_REMOTE_EXTENDS environment variable."
1170
- );
1171
- }
1172
- return await this.fetchRemoteConfig(source);
1173
- case "local" /* LOCAL */:
1174
- return await this.fetchLocalConfig(source);
1175
- default:
1176
- throw new Error(`Unknown configuration source: ${source}`);
383
+ const journal = new ExecutionJournal();
384
+ const memory = MemoryStore.getInstance(clonedConfig.memory);
385
+ return {
386
+ mode: "state-machine",
387
+ config: clonedConfig,
388
+ checks,
389
+ journal,
390
+ memory,
391
+ workingDirectory,
392
+ originalWorkingDirectory: workingDirectory,
393
+ sessionId: uuidv4(),
394
+ event: prInfo.eventType,
395
+ debug,
396
+ maxParallelism,
397
+ failFast,
398
+ requestedChecks: requestedChecks && requestedChecks.length > 0 ? requestedChecks : void 0,
399
+ // Store prInfo for later access (e.g., in getOutputHistorySnapshot)
400
+ prInfo
401
+ };
402
+ }
403
+ async function initializeWorkspace(context) {
404
+ const workspaceConfig = context.config.workspace;
405
+ const isEnabled = workspaceConfig?.enabled !== false && process.env.VISOR_WORKSPACE_ENABLED !== "false";
406
+ if (!isEnabled) {
407
+ logger.debug("[Workspace] Workspace isolation is disabled");
408
+ return context;
409
+ }
410
+ const originalPath = context.workingDirectory || process.cwd();
411
+ try {
412
+ const keepWorkspace = process.env.VISOR_KEEP_WORKSPACE === "true";
413
+ const workspace = WorkspaceManager.getInstance(context.sessionId, originalPath, {
414
+ enabled: true,
415
+ basePath: workspaceConfig?.base_path || process.env.VISOR_WORKSPACE_PATH || "/tmp/visor-workspaces",
416
+ cleanupOnExit: keepWorkspace ? false : workspaceConfig?.cleanup_on_exit !== false
417
+ });
418
+ const info = await workspace.initialize();
419
+ context.workspace = workspace;
420
+ context.workingDirectory = info.mainProjectPath;
421
+ context.originalWorkingDirectory = originalPath;
422
+ logger.info(`[Workspace] Initialized workspace: ${info.workspacePath}`);
423
+ logger.debug(`[Workspace] Main project at: ${info.mainProjectPath}`);
424
+ if (keepWorkspace) {
425
+ logger.info(`[Workspace] Keeping workspace after execution (--keep-workspace)`);
426
+ }
427
+ return context;
428
+ } catch (error) {
429
+ logger.warn(`[Workspace] Failed to initialize workspace: ${error}`);
430
+ logger.debug("[Workspace] Continuing without workspace isolation");
431
+ return context;
432
+ }
433
+ }
434
+ var init_build_engine_context = __esm({
435
+ "src/state-machine/context/build-engine-context.ts"() {
436
+ "use strict";
437
+ init_snapshot_store();
438
+ init_memory_store();
439
+ init_logger();
440
+ init_workspace_manager();
441
+ }
442
+ });
443
+
444
+ // src/state-machine/execution/summary.ts
445
+ var summary_exports = {};
446
+ __export(summary_exports, {
447
+ convertToReviewSummary: () => convertToReviewSummary
448
+ });
449
+ function convertToReviewSummary(groupedResults, statistics) {
450
+ const allIssues = [];
451
+ for (const checkResults of Object.values(groupedResults)) {
452
+ for (const checkResult of checkResults) {
453
+ if (checkResult.issues && checkResult.issues.length > 0) {
454
+ allIssues.push(...checkResult.issues);
1177
455
  }
1178
- } finally {
1179
- this.loadedConfigs.delete(normalizedSource);
1180
456
  }
1181
457
  }
1182
- /**
1183
- * Normalize source path/URL for comparison
1184
- */
1185
- normalizeSource(source) {
1186
- const sourceType = this.getSourceType(source);
1187
- switch (sourceType) {
1188
- case "default" /* DEFAULT */:
1189
- return "default";
1190
- case "remote" /* REMOTE */:
1191
- return source.toLowerCase();
1192
- case "local" /* LOCAL */:
1193
- const basePath = this.options.baseDir || process.cwd();
1194
- return path.resolve(basePath, source);
1195
- default:
1196
- return source;
458
+ if (statistics) {
459
+ for (const checkStats of statistics.checks) {
460
+ if (checkStats.errorMessage) {
461
+ allIssues.push({
462
+ file: "system",
463
+ line: 0,
464
+ endLine: void 0,
465
+ ruleId: "system/error",
466
+ message: checkStats.errorMessage,
467
+ severity: "error",
468
+ category: "logic",
469
+ suggestion: void 0,
470
+ replacement: void 0
471
+ });
472
+ }
1197
473
  }
1198
474
  }
475
+ return {
476
+ issues: allIssues
477
+ };
478
+ }
479
+ var init_summary = __esm({
480
+ "src/state-machine/execution/summary.ts"() {
481
+ "use strict";
482
+ }
483
+ });
484
+
485
+ // src/state-machine-execution-engine.ts
486
+ init_runner();
487
+ init_logger();
488
+ import * as path2 from "path";
489
+ import * as fs from "fs";
490
+ var StateMachineExecutionEngine = class _StateMachineExecutionEngine {
491
+ workingDirectory;
492
+ executionContext;
493
+ debugServer;
494
+ _lastContext;
495
+ _lastRunner;
496
+ constructor(workingDirectory, octokit, debugServer) {
497
+ this.workingDirectory = workingDirectory || process.cwd();
498
+ this.debugServer = debugServer;
499
+ }
1199
500
  /**
1200
- * Load configuration from local file system
501
+ * Execute checks using the state machine engine
502
+ *
503
+ * Converts CheckExecutionOptions -> executeGroupedChecks() -> AnalysisResult
1201
504
  */
1202
- async fetchLocalConfig(filePath) {
1203
- const basePath = this.options.baseDir || process.cwd();
1204
- const resolvedPath = path.resolve(basePath, filePath);
1205
- this.validateLocalPath(resolvedPath);
1206
- if (!fs.existsSync(resolvedPath)) {
1207
- throw new Error(`Configuration file not found: ${resolvedPath}`);
1208
- }
505
+ async executeChecks(options) {
506
+ const startTime = Date.now();
507
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1209
508
  try {
1210
- const content = fs.readFileSync(resolvedPath, "utf8");
1211
- const config = yaml.load(content);
1212
- if (!config || typeof config !== "object") {
1213
- throw new Error(`Invalid YAML in configuration file: ${resolvedPath}`);
509
+ if (options.config?.memory) {
510
+ const { MemoryStore: MemoryStore2 } = await import("./memory-store-XGBB7LX7.mjs");
511
+ const memoryStore = MemoryStore2.getInstance(options.config.memory);
512
+ await memoryStore.initialize();
513
+ logger.debug("Memory store initialized");
1214
514
  }
1215
- const previousBaseDir = this.options.baseDir;
1216
- this.options.baseDir = path.dirname(resolvedPath);
515
+ const { GitRepositoryAnalyzer } = await import("./git-repository-analyzer-HJC4MYW4.mjs");
516
+ const gitAnalyzer = new GitRepositoryAnalyzer(options.workingDirectory);
517
+ logger.info("Analyzing local git repository...");
518
+ const repositoryInfo = await gitAnalyzer.analyzeRepository();
519
+ if (!repositoryInfo.isGitRepository) {
520
+ return this.createErrorResult(
521
+ repositoryInfo,
522
+ "Not a git repository or no changes found",
523
+ startTime,
524
+ timestamp,
525
+ options.checks
526
+ );
527
+ }
528
+ const prInfo = gitAnalyzer.toPRInfo(repositoryInfo);
1217
529
  try {
1218
- if (config.extends) {
1219
- const processedConfig = await this.processExtends(config);
1220
- return processedConfig;
1221
- }
1222
- return config;
1223
- } finally {
1224
- this.options.baseDir = previousBaseDir;
530
+ const evt = options.webhookContext?.eventType;
531
+ if (evt) prInfo.eventType = evt;
532
+ } catch {
1225
533
  }
1226
- } catch (error) {
1227
- if (error instanceof Error) {
1228
- throw new Error(`Failed to load configuration from ${resolvedPath}: ${error.message}`);
534
+ const filteredChecks = this.filterChecksByTags(
535
+ options.checks,
536
+ options.config,
537
+ options.tagFilter || options.config?.tag_filter
538
+ );
539
+ if (filteredChecks.length === 0) {
540
+ logger.warn("No checks match the tag filter criteria");
541
+ return this.createErrorResult(
542
+ repositoryInfo,
543
+ "No checks match the tag filter criteria",
544
+ startTime,
545
+ timestamp,
546
+ options.checks
547
+ );
1229
548
  }
1230
- throw error;
1231
- }
1232
- }
1233
- /**
1234
- * Fetch configuration from remote URL
1235
- */
1236
- async fetchRemoteConfig(url) {
1237
- if (!url.startsWith("http://") && !url.startsWith("https://")) {
1238
- throw new Error(`Invalid URL: ${url}. Only HTTP and HTTPS protocols are supported.`);
1239
- }
1240
- this.validateRemoteURL(url);
1241
- const cacheEntry = this.cache.get(url);
1242
- if (cacheEntry && Date.now() - cacheEntry.timestamp < cacheEntry.ttl) {
1243
- const outputFormat2 = process.env.VISOR_OUTPUT_FORMAT;
1244
- const logFn2 = outputFormat2 === "json" || outputFormat2 === "sarif" ? console.error : console.log;
1245
- logFn2(`\u{1F4E6} Using cached configuration from: ${url}`);
1246
- return cacheEntry.config;
1247
- }
1248
- const outputFormat = process.env.VISOR_OUTPUT_FORMAT;
1249
- const logFn = outputFormat === "json" || outputFormat === "sarif" ? console.error : console.log;
1250
- logFn(`\u2B07\uFE0F Fetching remote configuration from: ${url}`);
1251
- const controller = new AbortController();
1252
- const timeoutMs = this.options.timeout ?? 3e4;
1253
- const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
1254
- try {
1255
- const response = await fetch(url, {
1256
- signal: controller.signal,
1257
- headers: {
1258
- "User-Agent": "Visor/1.0"
549
+ try {
550
+ const map = options?.webhookContext?.webhookData;
551
+ if (map) {
552
+ const { CheckProviderRegistry } = await import("./check-provider-registry-534KL5HT.mjs");
553
+ const reg = CheckProviderRegistry.getInstance();
554
+ const p = reg.getProvider("http_input");
555
+ if (p && typeof p.setWebhookContext === "function") p.setWebhookContext(map);
556
+ const prev = this.executionContext || {};
557
+ this.setExecutionContext({ ...prev, webhookContext: { webhookData: map } });
1259
558
  }
1260
- });
1261
- if (!response.ok) {
1262
- throw new Error(`Failed to fetch config: ${response.status} ${response.statusText}`);
559
+ } catch {
1263
560
  }
1264
- const content = await response.text();
1265
- const config = yaml.load(content);
1266
- if (!config || typeof config !== "object") {
1267
- throw new Error(`Invalid YAML in remote configuration: ${url}`);
561
+ logger.info(`Executing checks: ${filteredChecks.join(", ")}`);
562
+ const executionResult = await this.executeGroupedChecks(
563
+ prInfo,
564
+ filteredChecks,
565
+ options.timeout,
566
+ options.config,
567
+ options.outputFormat,
568
+ options.debug,
569
+ options.maxParallelism,
570
+ options.failFast,
571
+ options.tagFilter
572
+ );
573
+ const executionTime = Date.now() - startTime;
574
+ const reviewSummary = this.convertGroupedResultsToReviewSummary(
575
+ executionResult.results,
576
+ executionResult.statistics
577
+ );
578
+ let debugInfo;
579
+ if (options.debug && reviewSummary.debug) {
580
+ debugInfo = {
581
+ provider: reviewSummary.debug.provider,
582
+ model: reviewSummary.debug.model,
583
+ processingTime: reviewSummary.debug.processingTime,
584
+ parallelExecution: options.checks.length > 1,
585
+ checksExecuted: options.checks,
586
+ totalApiCalls: reviewSummary.debug.totalApiCalls || options.checks.length,
587
+ apiCallDetails: reviewSummary.debug.apiCallDetails
588
+ };
1268
589
  }
1269
- this.cache.set(url, {
1270
- config,
1271
- timestamp: Date.now(),
1272
- ttl: this.options.cacheTTL || 5 * 60 * 1e3
1273
- });
1274
- if (config.extends) {
1275
- return await this.processExtends(config);
590
+ try {
591
+ const histSnap = this.getOutputHistorySnapshot();
592
+ reviewSummary.history = histSnap;
593
+ } catch {
1276
594
  }
1277
- return config;
595
+ return {
596
+ repositoryInfo,
597
+ reviewSummary,
598
+ executionTime,
599
+ timestamp,
600
+ checksExecuted: filteredChecks,
601
+ executionStatistics: executionResult.statistics,
602
+ debug: debugInfo
603
+ };
1278
604
  } catch (error) {
1279
- if (error instanceof Error) {
1280
- if (error.name === "AbortError") {
1281
- throw new Error(`Timeout fetching configuration from ${url} (${timeoutMs}ms)`);
1282
- }
1283
- throw new Error(`Failed to fetch remote configuration from ${url}: ${error.message}`);
1284
- }
1285
- throw error;
1286
- } finally {
1287
- clearTimeout(timeoutId);
1288
- }
1289
- }
1290
- /**
1291
- * Load bundled default configuration
1292
- */
1293
- async fetchDefaultConfig() {
1294
- const possiblePaths = [
1295
- // When running as GitHub Action (bundled in dist/)
1296
- path.join(__dirname, "defaults", ".visor.yaml"),
1297
- // When running from source
1298
- path.join(__dirname, "..", "..", "defaults", ".visor.yaml"),
1299
- // Try via package root
1300
- this.findPackageRoot() ? path.join(this.findPackageRoot(), "defaults", ".visor.yaml") : "",
1301
- // GitHub Action environment variable
1302
- process.env.GITHUB_ACTION_PATH ? path.join(process.env.GITHUB_ACTION_PATH, "defaults", ".visor.yaml") : "",
1303
- process.env.GITHUB_ACTION_PATH ? path.join(process.env.GITHUB_ACTION_PATH, "dist", "defaults", ".visor.yaml") : ""
1304
- ].filter((p) => p);
1305
- let defaultConfigPath;
1306
- for (const possiblePath of possiblePaths) {
1307
- if (fs.existsSync(possiblePath)) {
1308
- defaultConfigPath = possiblePath;
1309
- break;
1310
- }
1311
- }
1312
- if (defaultConfigPath && fs.existsSync(defaultConfigPath)) {
1313
- console.error(`\u{1F4E6} Loading bundled default configuration from ${defaultConfigPath}`);
1314
- const content = fs.readFileSync(defaultConfigPath, "utf8");
1315
- let config = yaml.load(content);
1316
- if (!config || typeof config !== "object") {
1317
- throw new Error("Invalid default configuration");
1318
- }
1319
- config = this.normalizeStepsAndChecks(config);
1320
- if (config.extends) {
1321
- return await this.processExtends(config);
1322
- }
1323
- return config;
1324
- }
1325
- console.warn("\u26A0\uFE0F Bundled default configuration not found, using minimal defaults");
1326
- return {
1327
- version: "1.0",
1328
- checks: {},
1329
- output: {
1330
- pr_comment: {
1331
- format: "markdown",
1332
- group_by: "check",
1333
- collapse: true
1334
- }
605
+ const message = error instanceof Error ? error.message : "Unknown error occurred";
606
+ logger.error("Error executing checks: " + message);
607
+ const strictEnv = process.env.VISOR_STRICT_ERRORS === "true";
608
+ if (strictEnv) {
609
+ throw error;
1335
610
  }
1336
- };
1337
- }
1338
- /**
1339
- * Process extends directive in a configuration
1340
- */
1341
- async processExtends(config) {
1342
- if (!config.extends) {
1343
- return config;
1344
- }
1345
- const extends_ = Array.isArray(config.extends) ? config.extends : [config.extends];
1346
- const { extends: _extendsField, ...configWithoutExtends } = config;
1347
- const parentConfigs = [];
1348
- for (const source of extends_) {
1349
- const parentConfig = await this.fetchConfig(source, this.loadedConfigs.size);
1350
- parentConfigs.push(parentConfig);
1351
- }
1352
- const { ConfigMerger: ConfigMerger2 } = await import("./config-merger-TWUBWFC2.mjs");
1353
- const merger = new ConfigMerger2();
1354
- let mergedParents = {};
1355
- for (const parentConfig of parentConfigs) {
1356
- mergedParents = merger.merge(mergedParents, parentConfig);
611
+ const fallbackRepositoryInfo = {
612
+ title: "Error during analysis",
613
+ body: `Error: ${message || "Unknown error"}`,
614
+ author: "system",
615
+ base: "main",
616
+ head: "HEAD",
617
+ files: [],
618
+ totalAdditions: 0,
619
+ totalDeletions: 0,
620
+ isGitRepository: false,
621
+ workingDirectory: options.workingDirectory || process.cwd()
622
+ };
623
+ return this.createErrorResult(
624
+ fallbackRepositoryInfo,
625
+ message || "Unknown error occurred",
626
+ startTime,
627
+ timestamp,
628
+ options.checks
629
+ );
1357
630
  }
1358
- return merger.merge(mergedParents, configWithoutExtends);
1359
631
  }
1360
632
  /**
1361
- * Find project root directory (for security validation)
633
+ * Get execution context (used by state machine to propagate hooks)
1362
634
  */
1363
- findProjectRoot() {
1364
- try {
1365
- const { execSync } = __require("child_process");
1366
- const gitRoot = execSync("git rev-parse --show-toplevel", { encoding: "utf8" }).trim();
1367
- if (gitRoot) return gitRoot;
1368
- } catch {
1369
- }
1370
- const packageRoot = this.findPackageRoot();
1371
- if (packageRoot) return packageRoot;
1372
- return process.cwd();
635
+ getExecutionContext() {
636
+ return this.executionContext;
1373
637
  }
1374
638
  /**
1375
- * Validate remote URL against allowlist
639
+ * Set execution context for external callers
1376
640
  */
1377
- validateRemoteURL(url) {
1378
- const allowedPatterns = this.options.allowedRemotePatterns || [];
1379
- if (allowedPatterns.length === 0) {
1380
- return;
1381
- }
1382
- const isAllowed = allowedPatterns.some((pattern) => url.startsWith(pattern));
1383
- if (!isAllowed) {
1384
- throw new Error(
1385
- `Security error: URL ${url} is not in the allowed list. Allowed patterns: ${allowedPatterns.join(", ")}`
1386
- );
1387
- }
641
+ setExecutionContext(context) {
642
+ this.executionContext = context;
1388
643
  }
1389
644
  /**
1390
- * Validate local path against traversal attacks
645
+ * Reset per-run state (no-op for state machine engine)
646
+ *
647
+ * The state machine engine is stateless per-run by design.
648
+ * Each execution creates a fresh journal and context.
649
+ * This method exists only for backward compatibility with test framework.
650
+ *
651
+ * @deprecated This is a no-op. State machine engine doesn't maintain per-run state.
1391
652
  */
1392
- validateLocalPath(resolvedPath) {
1393
- const projectRoot = this.options.projectRoot || process.cwd();
1394
- const normalizedPath = path.normalize(resolvedPath);
1395
- const normalizedRoot = path.normalize(projectRoot);
1396
- if (!normalizedPath.startsWith(normalizedRoot)) {
1397
- throw new Error(
1398
- `Security error: Path traversal detected. Cannot access files outside project root: ${projectRoot}`
1399
- );
1400
- }
1401
- const sensitivePatterns = [
1402
- "/etc/passwd",
1403
- "/etc/shadow",
1404
- "/.ssh/",
1405
- "/.aws/",
1406
- "/.env",
1407
- "/private/"
1408
- ];
1409
- const lowerPath = normalizedPath.toLowerCase();
1410
- for (const pattern of sensitivePatterns) {
1411
- if (lowerPath.includes(pattern)) {
1412
- throw new Error(`Security error: Cannot access potentially sensitive file: ${pattern}`);
1413
- }
1414
- }
653
+ resetPerRunState() {
1415
654
  }
1416
655
  /**
1417
- * Find package root directory
656
+ * Execute grouped checks using the state machine engine
657
+ *
658
+ * M4: Production-ready with full telemetry and debug server support
1418
659
  */
1419
- findPackageRoot() {
1420
- let currentDir = __dirname;
1421
- const root = path.parse(currentDir).root;
1422
- while (currentDir !== root) {
1423
- const packageJsonPath = path.join(currentDir, "package.json");
1424
- if (fs.existsSync(packageJsonPath)) {
660
+ async executeGroupedChecks(prInfo, checks, timeout, config, outputFormat, debug, maxParallelism, failFast, tagFilter, _pauseGate) {
661
+ if (debug) {
662
+ logger.info("[StateMachine] Using state machine engine");
663
+ }
664
+ if (!config) {
665
+ const { ConfigManager: ConfigManager2 } = await import("./config-YNC2EOOT.mjs");
666
+ const configManager = new ConfigManager2();
667
+ config = await configManager.getDefaultConfig();
668
+ logger.debug("[StateMachine] Using default configuration (no config provided)");
669
+ }
670
+ const configWithTagFilter = tagFilter ? {
671
+ ...config,
672
+ tag_filter: tagFilter
673
+ } : config;
674
+ const context = this.buildEngineContext(
675
+ configWithTagFilter,
676
+ prInfo,
677
+ debug,
678
+ maxParallelism,
679
+ failFast,
680
+ checks
681
+ // Pass the explicit checks list
682
+ );
683
+ const { initializeWorkspace: initializeWorkspace2 } = (init_build_engine_context(), __toCommonJS(build_engine_context_exports));
684
+ await initializeWorkspace2(context);
685
+ context.executionContext = this.getExecutionContext();
686
+ this._lastContext = context;
687
+ let frontendsHost;
688
+ if (Array.isArray(configWithTagFilter.frontends) && configWithTagFilter.frontends.length > 0) {
689
+ try {
690
+ const { EventBus } = await import("./event-bus-5BEVPQ6T.mjs");
691
+ const { FrontendsHost } = await import("./host-DXUYTNMU.mjs");
692
+ const bus = new EventBus();
693
+ context.eventBus = bus;
694
+ frontendsHost = new FrontendsHost(bus, logger);
695
+ if (process.env.VISOR_DEBUG === "true") {
696
+ try {
697
+ const fns = (configWithTagFilter.frontends || []).map((f) => ({
698
+ name: f?.name,
699
+ hasConfig: !!f?.config,
700
+ cfg: f?.config || void 0
701
+ }));
702
+ logger.info(`[Frontends] Loading specs: ${JSON.stringify(fns)}`);
703
+ } catch {
704
+ }
705
+ }
706
+ await frontendsHost.load(configWithTagFilter.frontends);
707
+ let owner;
708
+ let name;
709
+ let prNum;
710
+ let headSha;
711
+ try {
712
+ const anyInfo = prInfo;
713
+ owner = anyInfo?.eventContext?.repository?.owner?.login || process.env.GITHUB_REPOSITORY?.split("/")?.[0];
714
+ name = anyInfo?.eventContext?.repository?.name || process.env.GITHUB_REPOSITORY?.split("/")?.[1];
715
+ prNum = typeof anyInfo?.number === "number" ? anyInfo.number : void 0;
716
+ headSha = anyInfo?.eventContext?.pull_request?.head?.sha || process.env.GITHUB_SHA;
717
+ } catch {
718
+ }
719
+ const repoObj = owner && name ? { owner, name } : void 0;
720
+ const octokit = this.executionContext?.octokit;
721
+ if (!headSha && repoObj && prNum && octokit && typeof octokit.rest?.pulls?.get === "function") {
722
+ try {
723
+ const { data } = await octokit.rest.pulls.get({
724
+ owner: repoObj.owner,
725
+ repo: repoObj.name,
726
+ pull_number: prNum
727
+ });
728
+ headSha = data && data.head && data.head.sha || headSha;
729
+ } catch {
730
+ }
731
+ }
1425
732
  try {
1426
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
1427
- if (packageJson.name === "@probelabs/visor") {
1428
- return currentDir;
733
+ const prev = this.getExecutionContext() || {};
734
+ this.setExecutionContext({ ...prev, eventBus: bus });
735
+ try {
736
+ context.executionContext = this.getExecutionContext();
737
+ } catch {
1429
738
  }
1430
739
  } catch {
1431
740
  }
741
+ await frontendsHost.startAll(() => ({
742
+ eventBus: bus,
743
+ logger,
744
+ // Provide the active (possibly tag-filtered) config so frontends can read groups, etc.
745
+ config: configWithTagFilter,
746
+ run: {
747
+ runId: context.sessionId,
748
+ repo: repoObj,
749
+ pr: prNum,
750
+ headSha,
751
+ event: context.event || prInfo?.eventType,
752
+ actor: prInfo?.eventContext?.sender?.login || (typeof process.env.GITHUB_ACTOR === "string" ? process.env.GITHUB_ACTOR : void 0)
753
+ },
754
+ octokit,
755
+ webhookContext: this.executionContext?.webhookContext,
756
+ // Surface any injected test doubles for Slack as well
757
+ slack: this.executionContext?.slack || this.executionContext?.slackClient
758
+ }));
759
+ try {
760
+ bus.on("HumanInputRequested", async (envelope) => {
761
+ try {
762
+ const ev = envelope && envelope.payload || envelope;
763
+ let channel = ev?.channel;
764
+ let threadTs = ev?.threadTs;
765
+ if (!channel || !threadTs) {
766
+ try {
767
+ const anyCfg = configWithTagFilter || {};
768
+ const slackCfg = anyCfg.slack || {};
769
+ const endpoint = slackCfg.endpoint || "/bots/slack/support";
770
+ const map = this.executionContext?.webhookContext?.webhookData;
771
+ const payload = map?.get(endpoint);
772
+ const e = payload?.event;
773
+ const derivedTs = String(e?.thread_ts || e?.ts || e?.event_ts || "");
774
+ const derivedCh = String(e?.channel || "");
775
+ if (derivedCh && derivedTs) {
776
+ channel = channel || derivedCh;
777
+ threadTs = threadTs || derivedTs;
778
+ }
779
+ } catch {
780
+ }
781
+ }
782
+ const checkId = String(ev?.checkId || "unknown");
783
+ const threadKey = ev?.threadKey || (channel && threadTs ? `${channel}:${threadTs}` : "session");
784
+ const baseDir = process.env.VISOR_SNAPSHOT_DIR || path2.resolve(process.cwd(), ".visor", "snapshots");
785
+ fs.mkdirSync(baseDir, { recursive: true });
786
+ const filePath = path2.join(baseDir, `${threadKey}-${checkId}.json`);
787
+ await this.saveSnapshotToFile(filePath);
788
+ logger.info(`[Snapshot] Saved run snapshot: ${filePath}`);
789
+ try {
790
+ await bus.emit({
791
+ type: "SnapshotSaved",
792
+ checkId: ev?.checkId || "unknown",
793
+ channel,
794
+ threadTs,
795
+ threadKey,
796
+ filePath
797
+ });
798
+ } catch {
799
+ }
800
+ } catch (e) {
801
+ logger.warn(
802
+ `[Snapshot] Failed to save snapshot on HumanInputRequested: ${e instanceof Error ? e.message : String(e)}`
803
+ );
804
+ }
805
+ });
806
+ } catch {
807
+ }
808
+ } catch (err) {
809
+ logger.warn(
810
+ `[Frontends] Failed to initialize frontends: ${err instanceof Error ? err.message : String(err)}`
811
+ );
1432
812
  }
1433
- currentDir = path.dirname(currentDir);
1434
813
  }
1435
- return null;
1436
- }
1437
- /**
1438
- * Clear the configuration cache
1439
- */
1440
- clearCache() {
1441
- this.cache.clear();
1442
- }
1443
- /**
1444
- * Reset the loaded configs tracking (for testing)
1445
- */
1446
- reset() {
1447
- this.loadedConfigs.clear();
1448
- this.clearCache();
814
+ const runner = new StateMachineRunner(context, this.debugServer);
815
+ this._lastRunner = runner;
816
+ const result = await runner.run();
817
+ if (frontendsHost && typeof frontendsHost.stopAll === "function") {
818
+ try {
819
+ await frontendsHost.stopAll();
820
+ } catch {
821
+ }
822
+ }
823
+ if (debug) {
824
+ logger.info("[StateMachine] Execution complete");
825
+ }
826
+ try {
827
+ const { SessionRegistry } = await import("./session-registry-4E6YRQ77.mjs");
828
+ const sessionRegistry = SessionRegistry.getInstance();
829
+ sessionRegistry.clearAllSessions();
830
+ } catch (error) {
831
+ logger.debug(`[StateMachine] Failed to cleanup sessions: ${error}`);
832
+ }
833
+ if (context.workspace) {
834
+ try {
835
+ await context.workspace.cleanup();
836
+ } catch (error) {
837
+ logger.debug(`[StateMachine] Failed to cleanup workspace: ${error}`);
838
+ }
839
+ }
840
+ return result;
1449
841
  }
1450
842
  /**
1451
- * Normalize 'checks' and 'steps' keys for backward compatibility
1452
- * Ensures both keys are present and contain the same data
843
+ * Build the engine context for state machine execution
1453
844
  */
1454
- normalizeStepsAndChecks(config) {
1455
- if (config.steps && config.checks) {
1456
- config.checks = config.steps;
1457
- } else if (config.steps && !config.checks) {
1458
- config.checks = config.steps;
1459
- } else if (config.checks && !config.steps) {
1460
- config.steps = config.checks;
1461
- }
1462
- return config;
845
+ buildEngineContext(config, prInfo, debug, maxParallelism, failFast, requestedChecks) {
846
+ const { buildEngineContextForRun: buildEngineContextForRun2 } = (init_build_engine_context(), __toCommonJS(build_engine_context_exports));
847
+ return buildEngineContextForRun2(
848
+ this.workingDirectory,
849
+ config,
850
+ prInfo,
851
+ debug,
852
+ maxParallelism,
853
+ failFast,
854
+ requestedChecks
855
+ );
1463
856
  }
1464
- };
1465
-
1466
- // src/config.ts
1467
- import Ajv from "ajv";
1468
- import addFormats from "ajv-formats";
1469
- var VALID_EVENT_TRIGGERS = [
1470
- "pr_opened",
1471
- "pr_updated",
1472
- "pr_closed",
1473
- "issue_opened",
1474
- "issue_comment",
1475
- "manual",
1476
- "schedule",
1477
- "webhook_received"
1478
- ];
1479
- var ConfigManager = class {
1480
- validCheckTypes = [
1481
- "ai",
1482
- "claude-code",
1483
- "mcp",
1484
- "command",
1485
- "http",
1486
- "http_input",
1487
- "http_client",
1488
- "memory",
1489
- "noop",
1490
- "log",
1491
- "memory",
1492
- "github",
1493
- "human-input"
1494
- ];
1495
- validEventTriggers = [...VALID_EVENT_TRIGGERS];
1496
- validOutputFormats = ["table", "json", "markdown", "sarif"];
1497
- validGroupByOptions = ["check", "file", "severity", "group"];
1498
857
  /**
1499
- * Load configuration from a file
858
+ * Get output history snapshot for test framework compatibility
859
+ * Extracts output history from the journal
1500
860
  */
1501
- async loadConfig(configPath, options = {}) {
1502
- const { validate = true, mergeDefaults = true, allowedRemotePatterns } = options;
1503
- const resolvedPath = path2.isAbsolute(configPath) ? configPath : path2.resolve(process.cwd(), configPath);
1504
- try {
1505
- if (!fs2.existsSync(resolvedPath)) {
1506
- throw new Error(`Configuration file not found: ${resolvedPath}`);
861
+ getOutputHistorySnapshot() {
862
+ const journal = this._lastContext?.journal;
863
+ if (!journal) {
864
+ logger.debug("[StateMachine][DEBUG] getOutputHistorySnapshot: No journal found");
865
+ return {};
866
+ }
867
+ const sessionId = this._lastContext?.sessionId;
868
+ if (!sessionId) {
869
+ logger.debug("[StateMachine][DEBUG] getOutputHistorySnapshot: No sessionId found");
870
+ return {};
871
+ }
872
+ const snapshot = journal.beginSnapshot();
873
+ const allEntries = journal.readVisible(sessionId, snapshot, void 0);
874
+ logger.debug(
875
+ `[StateMachine][DEBUG] getOutputHistorySnapshot: Found ${allEntries.length} journal entries`
876
+ );
877
+ const outputHistory = {};
878
+ for (const entry of allEntries) {
879
+ const checkId = entry.checkId;
880
+ if (!outputHistory[checkId]) {
881
+ outputHistory[checkId] = [];
1507
882
  }
1508
- const configContent = fs2.readFileSync(resolvedPath, "utf8");
1509
- let parsedConfig;
1510
883
  try {
1511
- parsedConfig = yaml2.load(configContent);
1512
- } catch (yamlError) {
1513
- const errorMessage = yamlError instanceof Error ? yamlError.message : String(yamlError);
1514
- throw new Error(`Invalid YAML syntax in ${resolvedPath}: ${errorMessage}`);
1515
- }
1516
- if (!parsedConfig || typeof parsedConfig !== "object") {
1517
- throw new Error("Configuration file must contain a valid YAML object");
1518
- }
1519
- if (parsedConfig.extends) {
1520
- const loaderOptions = {
1521
- baseDir: path2.dirname(resolvedPath),
1522
- allowRemote: this.isRemoteExtendsAllowed(),
1523
- maxDepth: 10,
1524
- allowedRemotePatterns
1525
- };
1526
- const loader = new ConfigLoader(loaderOptions);
1527
- const merger = new ConfigMerger();
1528
- const extends_ = Array.isArray(parsedConfig.extends) ? parsedConfig.extends : [parsedConfig.extends];
1529
- const { extends: _extendsField, ...configWithoutExtends } = parsedConfig;
1530
- let mergedConfig = {};
1531
- for (const source of extends_) {
1532
- console.log(`\u{1F4E6} Extending from: ${source}`);
1533
- const parentConfig = await loader.fetchConfig(source);
1534
- mergedConfig = merger.merge(mergedConfig, parentConfig);
1535
- }
1536
- parsedConfig = merger.merge(mergedConfig, configWithoutExtends);
1537
- parsedConfig = merger.removeDisabledChecks(parsedConfig);
1538
- }
1539
- parsedConfig = this.normalizeStepsAndChecks(parsedConfig);
1540
- if (validate) {
1541
- this.validateConfig(parsedConfig);
1542
- }
1543
- let finalConfig = parsedConfig;
1544
- if (mergeDefaults) {
1545
- finalConfig = this.mergeWithDefaults(parsedConfig);
1546
- }
1547
- return finalConfig;
1548
- } catch (error) {
1549
- if (error instanceof Error) {
1550
- if (error.message.includes("not found") || error.message.includes("Invalid YAML") || error.message.includes("extends") || error.message.includes("EACCES") || error.message.includes("EISDIR")) {
1551
- throw error;
884
+ if (entry && typeof entry.result === "object" && entry.result.__skipped) {
885
+ continue;
1552
886
  }
1553
- if (error.message.includes("ENOENT")) {
1554
- throw new Error(`Configuration file not found: ${resolvedPath}`);
1555
- }
1556
- if (error.message.includes("EPERM")) {
1557
- throw new Error(`Permission denied reading configuration file: ${resolvedPath}`);
1558
- }
1559
- throw new Error(`Failed to read configuration file ${resolvedPath}: ${error.message}`);
887
+ } catch {
1560
888
  }
1561
- throw error;
1562
- }
1563
- }
1564
- /**
1565
- * Find and load configuration from default locations
1566
- */
1567
- async findAndLoadConfig(options = {}) {
1568
- const gitRoot = await this.findGitRepositoryRoot();
1569
- const searchDirs = [gitRoot, process.cwd()].filter(Boolean);
1570
- for (const baseDir of searchDirs) {
1571
- const possiblePaths = [path2.join(baseDir, ".visor.yaml"), path2.join(baseDir, ".visor.yml")];
1572
- for (const configPath of possiblePaths) {
1573
- if (fs2.existsSync(configPath)) {
1574
- return this.loadConfig(configPath, options);
889
+ const payload = entry.result.output !== void 0 ? entry.result.output : entry.result;
890
+ try {
891
+ if (payload && typeof payload === "object" && payload.forEachItems && Array.isArray(payload.forEachItems)) {
892
+ continue;
1575
893
  }
894
+ } catch {
1576
895
  }
896
+ if (payload !== void 0) outputHistory[checkId].push(payload);
1577
897
  }
1578
- const bundledConfig = this.loadBundledDefaultConfig();
1579
- if (bundledConfig) {
1580
- return bundledConfig;
898
+ logger.debug(
899
+ `[StateMachine][DEBUG] getOutputHistorySnapshot result: ${JSON.stringify(Object.keys(outputHistory))}`
900
+ );
901
+ for (const [checkId, outputs] of Object.entries(outputHistory)) {
902
+ logger.debug(`[StateMachine][DEBUG] ${checkId}: ${outputs.length} outputs`);
1581
903
  }
1582
- return this.getDefaultConfig();
904
+ return outputHistory;
1583
905
  }
1584
906
  /**
1585
- * Find the git repository root directory
907
+ * Save a JSON snapshot of the last run's state and journal to a file (experimental).
908
+ * Does not include secrets. Intended for debugging and future resume support.
1586
909
  */
1587
- async findGitRepositoryRoot() {
1588
- try {
1589
- const git = simpleGit();
1590
- const isRepo = await git.checkIsRepo();
1591
- if (!isRepo) {
1592
- return null;
1593
- }
1594
- const rootDir = await git.revparse(["--show-toplevel"]);
1595
- return rootDir.trim();
1596
- } catch {
1597
- return null;
1598
- }
910
+ async saveSnapshotToFile(filePath) {
911
+ const fs2 = await import("fs/promises");
912
+ const ctx = this._lastContext;
913
+ const runner = this._lastRunner;
914
+ if (!ctx || !runner) {
915
+ throw new Error("No prior execution context to snapshot");
916
+ }
917
+ const journal = ctx.journal;
918
+ const snapshotId = journal.beginSnapshot();
919
+ const entries = journal.readVisible(ctx.sessionId, snapshotId, void 0);
920
+ const state = runner.getState();
921
+ const serializableState = serializeRunState(state);
922
+ const payload = {
923
+ version: 1,
924
+ sessionId: ctx.sessionId,
925
+ event: ctx.event,
926
+ wave: state.wave,
927
+ state: serializableState,
928
+ journal: entries,
929
+ requestedChecks: ctx.requestedChecks || []
930
+ };
931
+ await fs2.writeFile(filePath, JSON.stringify(payload, null, 2), "utf8");
1599
932
  }
1600
933
  /**
1601
- * Get default configuration
934
+ * Load a snapshot JSON from file and return it. Resume support can build on this.
1602
935
  */
1603
- async getDefaultConfig() {
1604
- return {
1605
- version: "1.0",
1606
- steps: {},
1607
- checks: {},
1608
- // Keep for backward compatibility
1609
- max_parallelism: 3,
1610
- output: {
1611
- pr_comment: {
1612
- format: "markdown",
1613
- group_by: "check",
1614
- collapse: true
1615
- }
1616
- }
1617
- };
936
+ async loadSnapshotFromFile(filePath) {
937
+ const fs2 = await import("fs/promises");
938
+ const raw = await fs2.readFile(filePath, "utf8");
939
+ return JSON.parse(raw);
1618
940
  }
1619
941
  /**
1620
- * Load bundled default configuration from the package
942
+ * Filter checks by tag filter
1621
943
  */
1622
- loadBundledDefaultConfig() {
1623
- try {
1624
- const possiblePaths = [];
1625
- if (typeof __dirname !== "undefined") {
1626
- possiblePaths.push(
1627
- path2.join(__dirname, "defaults", ".visor.yaml"),
1628
- path2.join(__dirname, "..", "defaults", ".visor.yaml")
1629
- );
944
+ filterChecksByTags(checks, config, tagFilter) {
945
+ return checks.filter((checkName) => {
946
+ const checkConfig = config?.checks?.[checkName];
947
+ if (!checkConfig) {
948
+ return true;
1630
949
  }
1631
- const pkgRoot = this.findPackageRoot();
1632
- if (pkgRoot) {
1633
- possiblePaths.push(path2.join(pkgRoot, "defaults", ".visor.yaml"));
950
+ const checkTags = checkConfig.tags || [];
951
+ if (!tagFilter || !tagFilter.include && !tagFilter.exclude) {
952
+ return checkTags.length === 0;
1634
953
  }
1635
- if (process.env.GITHUB_ACTION_PATH) {
1636
- possiblePaths.push(
1637
- path2.join(process.env.GITHUB_ACTION_PATH, "defaults", ".visor.yaml"),
1638
- path2.join(process.env.GITHUB_ACTION_PATH, "dist", "defaults", ".visor.yaml")
1639
- );
954
+ if (checkTags.length === 0) {
955
+ return true;
1640
956
  }
1641
- let bundledConfigPath;
1642
- for (const possiblePath of possiblePaths) {
1643
- if (fs2.existsSync(possiblePath)) {
1644
- bundledConfigPath = possiblePath;
1645
- break;
1646
- }
957
+ if (tagFilter.exclude && tagFilter.exclude.length > 0) {
958
+ const hasExcludedTag = tagFilter.exclude.some((tag) => checkTags.includes(tag));
959
+ if (hasExcludedTag) return false;
1647
960
  }
1648
- if (bundledConfigPath && fs2.existsSync(bundledConfigPath)) {
1649
- console.error(`\u{1F4E6} Loading bundled default configuration from ${bundledConfigPath}`);
1650
- const configContent = fs2.readFileSync(bundledConfigPath, "utf8");
1651
- let parsedConfig = yaml2.load(configContent);
1652
- if (!parsedConfig || typeof parsedConfig !== "object") {
1653
- return null;
1654
- }
1655
- parsedConfig = this.normalizeStepsAndChecks(parsedConfig);
1656
- this.validateConfig(parsedConfig);
1657
- return this.mergeWithDefaults(parsedConfig);
961
+ if (tagFilter.include && tagFilter.include.length > 0) {
962
+ const hasIncludedTag = tagFilter.include.some((tag) => checkTags.includes(tag));
963
+ if (!hasIncludedTag) return false;
1658
964
  }
1659
- } catch (error) {
1660
- console.warn(
1661
- "Failed to load bundled default config:",
1662
- error instanceof Error ? error.message : String(error)
1663
- );
1664
- }
1665
- return null;
965
+ return true;
966
+ });
1666
967
  }
1667
968
  /**
1668
- * Find the root directory of the Visor package
969
+ * Create an error result in AnalysisResult format
1669
970
  */
1670
- findPackageRoot() {
1671
- let currentDir = __dirname;
1672
- while (currentDir !== path2.dirname(currentDir)) {
1673
- const packageJsonPath = path2.join(currentDir, "package.json");
1674
- if (fs2.existsSync(packageJsonPath)) {
1675
- try {
1676
- const packageJson = JSON.parse(fs2.readFileSync(packageJsonPath, "utf8"));
1677
- if (packageJson.name === "@probelabs/visor") {
1678
- return currentDir;
971
+ createErrorResult(repositoryInfo, errorMessage, startTime, timestamp, checksExecuted) {
972
+ const executionTime = Date.now() - startTime;
973
+ return {
974
+ repositoryInfo,
975
+ reviewSummary: {
976
+ issues: [
977
+ {
978
+ file: "system",
979
+ line: 0,
980
+ endLine: void 0,
981
+ ruleId: "system/error",
982
+ message: errorMessage,
983
+ severity: "error",
984
+ category: "logic",
985
+ suggestion: void 0,
986
+ replacement: void 0
1679
987
  }
1680
- } catch {
1681
- }
1682
- }
1683
- currentDir = path2.dirname(currentDir);
1684
- }
1685
- return null;
1686
- }
1687
- /**
1688
- * Normalize 'checks' and 'steps' keys for backward compatibility
1689
- * Ensures both keys are present and contain the same data
1690
- */
1691
- normalizeStepsAndChecks(config) {
1692
- if (config.steps && config.checks) {
1693
- config.checks = config.steps;
1694
- } else if (config.steps && !config.checks) {
1695
- config.checks = config.steps;
1696
- } else if (config.checks && !config.steps) {
1697
- config.steps = config.checks;
1698
- }
1699
- return config;
988
+ ]
989
+ },
990
+ executionTime,
991
+ timestamp,
992
+ checksExecuted
993
+ };
1700
994
  }
1701
995
  /**
1702
- * Merge configuration with CLI options
996
+ * Convert GroupedCheckResults to ReviewSummary
997
+ * Aggregates all check results into a single ReviewSummary
1703
998
  */
1704
- mergeWithCliOptions(config, cliOptions) {
1705
- const mergedConfig = { ...config };
1706
- if (cliOptions.maxParallelism !== void 0) {
1707
- mergedConfig.max_parallelism = cliOptions.maxParallelism;
1708
- }
1709
- if (cliOptions.failFast !== void 0) {
1710
- mergedConfig.fail_fast = cliOptions.failFast;
1711
- }
1712
- return {
1713
- config: mergedConfig,
1714
- cliChecks: cliOptions.checks || [],
1715
- cliOutput: cliOptions.output || "table"
1716
- };
999
+ convertGroupedResultsToReviewSummary(groupedResults, statistics) {
1000
+ const { convertToReviewSummary: convertToReviewSummary2 } = (init_summary(), __toCommonJS(summary_exports));
1001
+ return convertToReviewSummary2(groupedResults, statistics);
1717
1002
  }
1718
1003
  /**
1719
- * Load configuration with environment variable overrides
1004
+ * Evaluate failure conditions for a check result
1005
+ *
1006
+ * This method provides backward compatibility with the legacy engine by
1007
+ * delegating to the FailureConditionEvaluator.
1008
+ *
1009
+ * @param checkName - The name of the check being evaluated
1010
+ * @param reviewSummary - The review summary containing check results
1011
+ * @param config - The Visor configuration containing failure conditions
1012
+ * @param previousOutputs - Optional previous check outputs for cross-check conditions
1013
+ * @param authorAssociation - Optional GitHub author association for permission checks
1014
+ * @returns Array of failure condition evaluation results
1720
1015
  */
1721
- async loadConfigWithEnvOverrides() {
1722
- const environmentOverrides = {};
1723
- if (process.env.VISOR_CONFIG_PATH) {
1724
- environmentOverrides.configPath = process.env.VISOR_CONFIG_PATH;
1725
- }
1726
- if (process.env.VISOR_OUTPUT_FORMAT) {
1727
- environmentOverrides.outputFormat = process.env.VISOR_OUTPUT_FORMAT;
1728
- }
1729
- let config;
1730
- if (environmentOverrides.configPath) {
1016
+ async evaluateFailureConditions(checkName, reviewSummary, config, previousOutputs, authorAssociation) {
1017
+ const { FailureConditionEvaluator } = await import("./failure-condition-evaluator-YGTF2GHG.mjs");
1018
+ const evaluator = new FailureConditionEvaluator();
1019
+ const { addEvent } = await import("./trace-helpers-VP6QYVBX.mjs");
1020
+ const { addFailIfTriggered } = await import("./metrics-7PP3EJUH.mjs");
1021
+ const checkConfig = config.checks?.[checkName];
1022
+ if (!checkConfig) {
1023
+ return [];
1024
+ }
1025
+ const rawSchema = checkConfig.schema || "code-review";
1026
+ const checkSchema = typeof rawSchema === "string" ? rawSchema : "code-review";
1027
+ const checkGroup = checkConfig.group || "default";
1028
+ const results = [];
1029
+ if (config.fail_if) {
1030
+ const failed = await evaluator.evaluateSimpleCondition(
1031
+ checkName,
1032
+ checkSchema,
1033
+ checkGroup,
1034
+ reviewSummary,
1035
+ config.fail_if,
1036
+ previousOutputs || {}
1037
+ );
1731
1038
  try {
1732
- config = await this.loadConfig(environmentOverrides.configPath);
1039
+ addEvent("fail_if.evaluated", {
1040
+ "visor.check.id": checkName,
1041
+ scope: "global",
1042
+ expression: String(config.fail_if),
1043
+ result: failed ? "triggered" : "not_triggered"
1044
+ });
1045
+ if (failed) {
1046
+ addEvent("fail_if.triggered", {
1047
+ "visor.check.id": checkName,
1048
+ scope: "global",
1049
+ expression: String(config.fail_if)
1050
+ });
1051
+ addFailIfTriggered(checkName, "global");
1052
+ }
1733
1053
  } catch {
1734
- config = await this.findAndLoadConfig();
1735
1054
  }
1736
- } else {
1737
- config = await this.findAndLoadConfig();
1738
- }
1739
- return { config, environmentOverrides };
1740
- }
1741
- /**
1742
- * Validate configuration against schema
1743
- * @param config The config to validate
1744
- * @param strict If true, treat warnings as errors (default: false)
1745
- */
1746
- validateConfig(config, strict = false) {
1747
- const errors = [];
1748
- const warnings = [];
1749
- this.validateWithAjvSchema(config, errors, warnings);
1750
- if (!config.version) {
1751
- errors.push({
1752
- field: "version",
1753
- message: "Missing required field: version"
1754
- });
1755
- }
1756
- if (!config.checks && !config.steps) {
1757
- errors.push({
1758
- field: "checks/steps",
1759
- message: 'Missing required field: either "checks" or "steps" must be defined. "steps" is recommended for new configurations.'
1055
+ results.push({
1056
+ conditionName: "global_fail_if",
1057
+ failed,
1058
+ expression: config.fail_if,
1059
+ message: failed ? `Global failure condition met: ${config.fail_if}` : void 0,
1060
+ severity: "error",
1061
+ haltExecution: false
1760
1062
  });
1761
1063
  }
1762
- const checksToValidate = config.checks || config.steps;
1763
- if (checksToValidate) {
1764
- for (const [checkName, checkConfig] of Object.entries(checksToValidate)) {
1765
- if (!checkConfig.type) {
1766
- checkConfig.type = "ai";
1767
- }
1768
- this.validateCheckConfig(checkName, checkConfig, errors, config);
1769
- if (checkConfig.ai_mcp_servers) {
1770
- this.validateMcpServersObject(
1771
- checkConfig.ai_mcp_servers,
1772
- `checks.${checkName}.ai_mcp_servers`,
1773
- errors,
1774
- warnings
1775
- );
1776
- }
1777
- if (checkConfig.ai?.mcpServers) {
1778
- this.validateMcpServersObject(
1779
- checkConfig.ai.mcpServers,
1780
- `checks.${checkName}.ai.mcpServers`,
1781
- errors,
1782
- warnings
1783
- );
1784
- }
1785
- if (checkConfig.ai_mcp_servers && checkConfig.ai?.mcpServers) {
1786
- const lower = Object.keys(checkConfig.ai_mcp_servers);
1787
- const higher = Object.keys(checkConfig.ai.mcpServers);
1788
- const overridden = lower.filter((k) => higher.includes(k));
1789
- warnings.push({
1790
- field: `checks.${checkName}.ai.mcpServers`,
1791
- message: overridden.length > 0 ? `Both ai_mcp_servers and ai.mcpServers are set; ai.mcpServers overrides these servers: ${overridden.join(
1792
- ", "
1793
- )}` : "Both ai_mcp_servers and ai.mcpServers are set; ai.mcpServers takes precedence for this check."
1064
+ if (checkConfig.fail_if) {
1065
+ const failed = await evaluator.evaluateSimpleCondition(
1066
+ checkName,
1067
+ checkSchema,
1068
+ checkGroup,
1069
+ reviewSummary,
1070
+ checkConfig.fail_if,
1071
+ previousOutputs || {}
1072
+ );
1073
+ try {
1074
+ addEvent("fail_if.evaluated", {
1075
+ "visor.check.id": checkName,
1076
+ scope: "check",
1077
+ expression: String(checkConfig.fail_if),
1078
+ result: failed ? "triggered" : "not_triggered"
1079
+ });
1080
+ if (failed) {
1081
+ addEvent("fail_if.triggered", {
1082
+ "visor.check.id": checkName,
1083
+ scope: "check",
1084
+ expression: String(checkConfig.fail_if)
1794
1085
  });
1086
+ addFailIfTriggered(checkName, "check");
1795
1087
  }
1796
- try {
1797
- const anyCheck = checkConfig;
1798
- const aiObj = anyCheck.ai || void 0;
1799
- const hasBareMcpAtCheck = Object.prototype.hasOwnProperty.call(anyCheck, "mcpServers");
1800
- const hasAiMcp = aiObj && Object.prototype.hasOwnProperty.call(aiObj, "mcpServers");
1801
- const hasClaudeCodeMcp = anyCheck.claude_code && typeof anyCheck.claude_code === "object" && Object.prototype.hasOwnProperty.call(
1802
- anyCheck.claude_code,
1803
- "mcpServers"
1804
- );
1805
- if (checkConfig.type === "ai") {
1806
- if (hasBareMcpAtCheck) {
1807
- warnings.push({
1808
- field: `checks.${checkName}.mcpServers`,
1809
- message: "'mcpServers' at the check root is ignored for type 'ai'. Use 'ai.mcpServers' or 'ai_mcp_servers' instead.",
1810
- value: anyCheck.mcpServers
1811
- });
1812
- }
1813
- if (hasClaudeCodeMcp) {
1814
- warnings.push({
1815
- field: `checks.${checkName}.claude_code.mcpServers`,
1816
- message: "'claude_code.mcpServers' is ignored for type 'ai'. Use 'ai.mcpServers' or 'ai_mcp_servers' instead."
1817
- });
1818
- }
1819
- }
1820
- if (checkConfig.type === "claude-code") {
1821
- if (hasAiMcp || checkConfig.ai_mcp_servers) {
1822
- warnings.push({
1823
- field: hasAiMcp ? `checks.${checkName}.ai.mcpServers` : `checks.${checkName}.ai_mcp_servers`,
1824
- message: "For type 'claude-code', MCP must be configured under 'claude_code.mcpServers'. 'ai.mcpServers' and 'ai_mcp_servers' are ignored for this check."
1825
- });
1826
- }
1827
- }
1828
- } catch {
1829
- }
1088
+ } catch {
1830
1089
  }
1090
+ results.push({
1091
+ conditionName: `${checkName}_fail_if`,
1092
+ failed,
1093
+ expression: checkConfig.fail_if,
1094
+ message: failed ? `Check failure condition met: ${checkConfig.fail_if}` : void 0,
1095
+ severity: "error",
1096
+ haltExecution: false
1097
+ });
1831
1098
  }
1832
- if (config.ai_mcp_servers) {
1833
- this.validateMcpServersObject(config.ai_mcp_servers, "ai_mcp_servers", errors, warnings);
1834
- }
1835
- if (config.output) {
1836
- this.validateOutputConfig(config.output, errors);
1837
- }
1838
- if (config.http_server) {
1839
- this.validateHttpServerConfig(
1840
- config.http_server,
1841
- errors
1099
+ const globalConditions = config.failure_conditions;
1100
+ const checkConditions = checkConfig.failure_conditions;
1101
+ if (globalConditions || checkConditions) {
1102
+ const legacyResults = await evaluator.evaluateConditions(
1103
+ checkName,
1104
+ checkSchema,
1105
+ checkGroup,
1106
+ reviewSummary,
1107
+ globalConditions,
1108
+ checkConditions,
1109
+ previousOutputs,
1110
+ authorAssociation
1842
1111
  );
1112
+ results.push(...legacyResults);
1843
1113
  }
1844
- if (config.max_parallelism !== void 0) {
1845
- if (typeof config.max_parallelism !== "number" || config.max_parallelism < 1 || !Number.isInteger(config.max_parallelism)) {
1846
- errors.push({
1847
- field: "max_parallelism",
1848
- message: "max_parallelism must be a positive integer (minimum 1)",
1849
- value: config.max_parallelism
1850
- });
1851
- }
1852
- }
1853
- if (config.tag_filter) {
1854
- this.validateTagFilter(config.tag_filter, errors);
1855
- }
1856
- if (strict && warnings.length > 0) {
1857
- errors.push(...warnings);
1858
- }
1859
- if (errors.length > 0) {
1860
- throw new Error(errors[0].message);
1861
- }
1862
- if (!strict && warnings.length > 0) {
1863
- for (const w of warnings) {
1864
- logger.warn(`\u26A0\uFE0F Config warning [${w.field}]: ${w.message}`);
1865
- }
1866
- }
1114
+ return results;
1867
1115
  }
1868
1116
  /**
1869
- * Validate individual check configuration
1117
+ * Get repository status
1118
+ * @returns Repository status information
1870
1119
  */
1871
- validateCheckConfig(checkName, checkConfig, errors, config) {
1872
- if (!checkConfig.type) {
1873
- checkConfig.type = "ai";
1874
- }
1875
- if (checkConfig.type === "logger") {
1876
- checkConfig.type = "log";
1877
- }
1878
- if (!this.validCheckTypes.includes(checkConfig.type)) {
1879
- errors.push({
1880
- field: `checks.${checkName}.type`,
1881
- message: `Invalid check type "${checkConfig.type}". Must be: ${this.validCheckTypes.join(", ")}`,
1882
- value: checkConfig.type
1883
- });
1884
- }
1885
- if (checkConfig.type === "ai" && !checkConfig.prompt) {
1886
- errors.push({
1887
- field: `checks.${checkName}.prompt`,
1888
- message: `Invalid check configuration for "${checkName}": missing prompt (required for AI checks)`
1889
- });
1890
- }
1891
- if (checkConfig.type === "command" && !checkConfig.exec) {
1892
- errors.push({
1893
- field: `checks.${checkName}.exec`,
1894
- message: `Invalid check configuration for "${checkName}": missing exec field (required for command checks)`
1895
- });
1896
- }
1897
- if (checkConfig.type === "http") {
1898
- if (!checkConfig.url) {
1899
- errors.push({
1900
- field: `checks.${checkName}.url`,
1901
- message: `Invalid check configuration for "${checkName}": missing url field (required for http checks)`
1902
- });
1903
- }
1904
- if (!checkConfig.body) {
1905
- errors.push({
1906
- field: `checks.${checkName}.body`,
1907
- message: `Invalid check configuration for "${checkName}": missing body field (required for http checks)`
1908
- });
1909
- }
1910
- }
1911
- if (checkConfig.type === "http_input" && !checkConfig.endpoint) {
1912
- errors.push({
1913
- field: `checks.${checkName}.endpoint`,
1914
- message: `Invalid check configuration for "${checkName}": missing endpoint field (required for http_input checks)`
1915
- });
1916
- }
1917
- if (checkConfig.type === "http_client" && !checkConfig.url) {
1918
- errors.push({
1919
- field: `checks.${checkName}.url`,
1920
- message: `Invalid check configuration for "${checkName}": missing url field (required for http_client checks)`
1921
- });
1922
- }
1923
- if (checkConfig.schedule) {
1924
- const cronParts = checkConfig.schedule.split(" ");
1925
- if (cronParts.length < 5 || cronParts.length > 6) {
1926
- errors.push({
1927
- field: `checks.${checkName}.schedule`,
1928
- message: `Invalid cron expression for "${checkName}": ${checkConfig.schedule}`,
1929
- value: checkConfig.schedule
1930
- });
1931
- }
1932
- }
1933
- if (checkConfig.on) {
1934
- if (!Array.isArray(checkConfig.on)) {
1935
- errors.push({
1936
- field: `checks.${checkName}.on`,
1937
- message: `Invalid check configuration for "${checkName}": 'on' field must be an array`
1938
- });
1939
- } else {
1940
- for (const event of checkConfig.on) {
1941
- if (!this.validEventTriggers.includes(event)) {
1942
- errors.push({
1943
- field: `checks.${checkName}.on`,
1944
- message: `Invalid event "${event}". Must be one of: ${this.validEventTriggers.join(", ")}`,
1945
- value: event
1946
- });
1947
- }
1948
- }
1949
- }
1950
- }
1951
- if (checkConfig.reuse_ai_session !== void 0) {
1952
- const isString = typeof checkConfig.reuse_ai_session === "string";
1953
- const isBoolean = typeof checkConfig.reuse_ai_session === "boolean";
1954
- if (!isString && !isBoolean) {
1955
- errors.push({
1956
- field: `checks.${checkName}.reuse_ai_session`,
1957
- message: `Invalid reuse_ai_session value for "${checkName}": must be string (check name) or boolean`,
1958
- value: checkConfig.reuse_ai_session
1959
- });
1960
- } else if (isString) {
1961
- const targetCheckName = checkConfig.reuse_ai_session;
1962
- if (!config?.checks || !config.checks[targetCheckName]) {
1963
- errors.push({
1964
- field: `checks.${checkName}.reuse_ai_session`,
1965
- message: `Check "${checkName}" references non-existent check "${targetCheckName}" for session reuse`,
1966
- value: checkConfig.reuse_ai_session
1967
- });
1968
- }
1969
- } else if (checkConfig.reuse_ai_session === true) {
1970
- if (!checkConfig.depends_on || !Array.isArray(checkConfig.depends_on) || checkConfig.depends_on.length === 0) {
1971
- errors.push({
1972
- field: `checks.${checkName}.reuse_ai_session`,
1973
- message: `Check "${checkName}" has reuse_ai_session=true but missing or empty depends_on. Session reuse requires dependency on another check.`,
1974
- value: checkConfig.reuse_ai_session
1975
- });
1976
- }
1977
- }
1978
- }
1979
- if (checkConfig.session_mode !== void 0) {
1980
- if (checkConfig.session_mode !== "clone" && checkConfig.session_mode !== "append") {
1981
- errors.push({
1982
- field: `checks.${checkName}.session_mode`,
1983
- message: `Invalid session_mode value for "${checkName}": must be 'clone' or 'append'`,
1984
- value: checkConfig.session_mode
1985
- });
1986
- }
1987
- if (!checkConfig.reuse_ai_session) {
1988
- errors.push({
1989
- field: `checks.${checkName}.session_mode`,
1990
- message: `Check "${checkName}" has session_mode but no reuse_ai_session. session_mode requires reuse_ai_session to be set.`,
1991
- value: checkConfig.session_mode
1992
- });
1993
- }
1994
- }
1995
- if (checkConfig.tags !== void 0) {
1996
- if (!Array.isArray(checkConfig.tags)) {
1997
- errors.push({
1998
- field: `checks.${checkName}.tags`,
1999
- message: `Invalid tags value for "${checkName}": must be an array of strings`,
2000
- value: checkConfig.tags
2001
- });
2002
- } else {
2003
- const validTagPattern = /^[a-zA-Z0-9][a-zA-Z0-9-_]*$/;
2004
- checkConfig.tags.forEach((tag, index) => {
2005
- if (typeof tag !== "string") {
2006
- errors.push({
2007
- field: `checks.${checkName}.tags[${index}]`,
2008
- message: `Invalid tag at index ${index} for "${checkName}": must be a string`,
2009
- value: tag
2010
- });
2011
- } else if (!validTagPattern.test(tag)) {
2012
- errors.push({
2013
- field: `checks.${checkName}.tags[${index}]`,
2014
- message: `Invalid tag "${tag}" for "${checkName}": tags must be alphanumeric with hyphens or underscores (start with alphanumeric)`,
2015
- value: tag
2016
- });
2017
- }
2018
- });
2019
- }
1120
+ async getRepositoryStatus() {
1121
+ try {
1122
+ const { GitRepositoryAnalyzer } = await import("./git-repository-analyzer-HJC4MYW4.mjs");
1123
+ const analyzer = new GitRepositoryAnalyzer(this.workingDirectory);
1124
+ const info = await analyzer.analyzeRepository();
1125
+ return {
1126
+ isGitRepository: info.isGitRepository,
1127
+ branch: info.head,
1128
+ // Use head as branch name
1129
+ hasChanges: info.isGitRepository && (info.files?.length > 0 || false),
1130
+ filesChanged: info.isGitRepository ? info.files?.length || 0 : 0
1131
+ };
1132
+ } catch {
1133
+ return {
1134
+ isGitRepository: false,
1135
+ hasChanges: false
1136
+ };
2020
1137
  }
2021
1138
  }
2022
1139
  /**
2023
- * Validate MCP servers object shape and values (basic shape only)
1140
+ * Check if current directory is a git repository
1141
+ * @returns True if git repository, false otherwise
2024
1142
  */
2025
- validateMcpServersObject(mcpServers, fieldPrefix, errors, _warnings) {
2026
- if (typeof mcpServers !== "object" || mcpServers === null) {
2027
- errors.push({
2028
- field: fieldPrefix,
2029
- message: `${fieldPrefix} must be an object mapping server names to { command, args?, env? }`,
2030
- value: mcpServers
2031
- });
2032
- return;
2033
- }
2034
- for (const [serverName, cfg] of Object.entries(mcpServers)) {
2035
- const pathStr = `${fieldPrefix}.${serverName}`;
2036
- if (!cfg || typeof cfg !== "object") {
2037
- errors.push({ field: pathStr, message: `${pathStr} must be an object`, value: cfg });
2038
- continue;
2039
- }
2040
- const { command, args, env } = cfg;
2041
- if (typeof command !== "string" || command.trim() === "") {
2042
- errors.push({
2043
- field: `${pathStr}.command`,
2044
- message: `${pathStr}.command must be a non-empty string`,
2045
- value: command
2046
- });
2047
- }
2048
- if (args !== void 0 && !Array.isArray(args)) {
2049
- errors.push({
2050
- field: `${pathStr}.args`,
2051
- message: `${pathStr}.args must be an array of strings`,
2052
- value: args
2053
- });
2054
- }
2055
- if (env !== void 0) {
2056
- if (typeof env !== "object" || env === null) {
2057
- errors.push({
2058
- field: `${pathStr}.env`,
2059
- message: `${pathStr}.env must be an object of string values`,
2060
- value: env
2061
- });
2062
- } else {
2063
- for (const [k, v] of Object.entries(env)) {
2064
- if (typeof v !== "string") {
2065
- errors.push({
2066
- field: `${pathStr}.env.${k}`,
2067
- message: `${pathStr}.env.${k} must be a string`,
2068
- value: v
2069
- });
2070
- }
2071
- }
2072
- }
2073
- }
2074
- }
1143
+ async isGitRepository() {
1144
+ const status = await this.getRepositoryStatus();
1145
+ return status.isGitRepository;
2075
1146
  }
2076
1147
  /**
2077
- * Validate configuration using generated JSON Schema via Ajv, if available.
2078
- * Adds to errors/warnings but does not throw directly.
1148
+ * Get list of available check types
1149
+ * @returns Array of check type names
2079
1150
  */
2080
- validateWithAjvSchema(config, errors, warnings) {
2081
- try {
2082
- if (!__ajvValidate) {
2083
- try {
2084
- const jsonPath = path2.resolve(__dirname, "generated", "config-schema.json");
2085
- const jsonSchema = __require(jsonPath);
2086
- if (jsonSchema) {
2087
- const ajv = new Ajv({ allErrors: true, allowUnionTypes: true, strict: false });
2088
- addFormats(ajv);
2089
- const validate = ajv.compile(jsonSchema);
2090
- __ajvValidate = (data) => validate(data);
2091
- __ajvErrors = () => validate.errors;
2092
- }
2093
- } catch {
2094
- }
2095
- if (!__ajvValidate) {
2096
- try {
2097
- const mod = (init_config_schema(), __toCommonJS(config_schema_exports));
2098
- const schema = mod?.configSchema || mod?.default || mod;
2099
- if (schema) {
2100
- const ajv = new Ajv({ allErrors: true, allowUnionTypes: true, strict: false });
2101
- addFormats(ajv);
2102
- const validate = ajv.compile(schema);
2103
- __ajvValidate = (data) => validate(data);
2104
- __ajvErrors = () => validate.errors;
2105
- } else {
2106
- return;
2107
- }
2108
- } catch {
2109
- return;
2110
- }
2111
- }
2112
- }
2113
- const ok = __ajvValidate(config);
2114
- const errs = __ajvErrors ? __ajvErrors() : null;
2115
- if (!ok && Array.isArray(errs)) {
2116
- for (const e of errs) {
2117
- const pathStr = e.instancePath ? e.instancePath.replace(/^\//, "").replace(/\//g, ".") : "";
2118
- const msg = e.message || "Invalid configuration";
2119
- if (e.keyword === "additionalProperties") {
2120
- const addl = e.params && e.params.additionalProperty || "unknown";
2121
- const fullField = pathStr ? `${pathStr}.${addl}` : addl;
2122
- const topLevel = !pathStr;
2123
- warnings.push({
2124
- field: fullField || "config",
2125
- message: topLevel ? `Unknown top-level key '${addl}' will be ignored.` : `Unknown key '${addl}' will be ignored`
2126
- });
2127
- } else {
2128
- logger.debug(`Ajv note [${pathStr || "config"}]: ${msg}`);
2129
- }
2130
- }
2131
- }
2132
- } catch (err) {
2133
- logger.debug(`Ajv validation skipped: ${err instanceof Error ? err.message : String(err)}`);
2134
- }
1151
+ static getAvailableCheckTypes() {
1152
+ const { CheckProviderRegistry } = (init_check_provider_registry(), __toCommonJS(check_provider_registry_exports));
1153
+ const registry = CheckProviderRegistry.getInstance();
1154
+ return registry.getAvailableProviders();
2135
1155
  }
2136
- // Unknown-key warnings are fully handled by Ajv using the generated schema
2137
- // Unknown-key hints are produced by Ajv (additionalProperties=false)
2138
1156
  /**
2139
- * Validate tag filter configuration
1157
+ * Validate check types and return valid/invalid lists
1158
+ * @param checks - Array of check type names to validate
1159
+ * @returns Object with valid and invalid check types
2140
1160
  */
2141
- validateTagFilter(tagFilter, errors) {
2142
- const validTagPattern = /^[a-zA-Z0-9][a-zA-Z0-9-_]*$/;
2143
- if (tagFilter.include !== void 0) {
2144
- if (!Array.isArray(tagFilter.include)) {
2145
- errors.push({
2146
- field: "tag_filter.include",
2147
- message: "tag_filter.include must be an array of strings",
2148
- value: tagFilter.include
2149
- });
2150
- } else {
2151
- tagFilter.include.forEach((tag, index) => {
2152
- if (typeof tag !== "string") {
2153
- errors.push({
2154
- field: `tag_filter.include[${index}]`,
2155
- message: `Invalid tag at index ${index}: must be a string`,
2156
- value: tag
2157
- });
2158
- } else if (!validTagPattern.test(tag)) {
2159
- errors.push({
2160
- field: `tag_filter.include[${index}]`,
2161
- message: `Invalid tag "${tag}": tags must be alphanumeric with hyphens or underscores`,
2162
- value: tag
2163
- });
2164
- }
2165
- });
2166
- }
2167
- }
2168
- if (tagFilter.exclude !== void 0) {
2169
- if (!Array.isArray(tagFilter.exclude)) {
2170
- errors.push({
2171
- field: "tag_filter.exclude",
2172
- message: "tag_filter.exclude must be an array of strings",
2173
- value: tagFilter.exclude
2174
- });
1161
+ static validateCheckTypes(checks) {
1162
+ const availableTypes = _StateMachineExecutionEngine.getAvailableCheckTypes();
1163
+ const valid = [];
1164
+ const invalid = [];
1165
+ for (const check of checks) {
1166
+ if (availableTypes.includes(check)) {
1167
+ valid.push(check);
2175
1168
  } else {
2176
- tagFilter.exclude.forEach((tag, index) => {
2177
- if (typeof tag !== "string") {
2178
- errors.push({
2179
- field: `tag_filter.exclude[${index}]`,
2180
- message: `Invalid tag at index ${index}: must be a string`,
2181
- value: tag
2182
- });
2183
- } else if (!validTagPattern.test(tag)) {
2184
- errors.push({
2185
- field: `tag_filter.exclude[${index}]`,
2186
- message: `Invalid tag "${tag}": tags must be alphanumeric with hyphens or underscores`,
2187
- value: tag
2188
- });
2189
- }
2190
- });
1169
+ invalid.push(check);
2191
1170
  }
2192
1171
  }
1172
+ return { valid, invalid };
2193
1173
  }
2194
1174
  /**
2195
- * Validate HTTP server configuration
1175
+ * Format the status column for execution statistics
1176
+ * Used by execution-statistics-formatting tests
2196
1177
  */
2197
- validateHttpServerConfig(httpServerConfig, errors) {
2198
- if (typeof httpServerConfig.enabled !== "boolean") {
2199
- errors.push({
2200
- field: "http_server.enabled",
2201
- message: "http_server.enabled must be a boolean",
2202
- value: httpServerConfig.enabled
2203
- });
2204
- }
2205
- if (httpServerConfig.enabled === true) {
2206
- if (typeof httpServerConfig.port !== "number" || httpServerConfig.port < 1 || httpServerConfig.port > 65535) {
2207
- errors.push({
2208
- field: "http_server.port",
2209
- message: "http_server.port must be a number between 1 and 65535",
2210
- value: httpServerConfig.port
2211
- });
2212
- }
2213
- if (httpServerConfig.auth) {
2214
- const auth = httpServerConfig.auth;
2215
- const validAuthTypes = ["bearer_token", "hmac", "basic", "none"];
2216
- if (!auth.type || !validAuthTypes.includes(auth.type)) {
2217
- errors.push({
2218
- field: "http_server.auth.type",
2219
- message: `Invalid auth type. Must be one of: ${validAuthTypes.join(", ")}`,
2220
- value: auth.type
2221
- });
2222
- }
2223
- }
2224
- if (httpServerConfig.tls && typeof httpServerConfig.tls === "object") {
2225
- const tls = httpServerConfig.tls;
2226
- if (tls.enabled === true) {
2227
- if (!tls.cert) {
2228
- errors.push({
2229
- field: "http_server.tls.cert",
2230
- message: "TLS certificate is required when TLS is enabled"
2231
- });
2232
- }
2233
- if (!tls.key) {
2234
- errors.push({
2235
- field: "http_server.tls.key",
2236
- message: "TLS key is required when TLS is enabled"
2237
- });
2238
- }
2239
- }
2240
- }
2241
- if (httpServerConfig.endpoints && Array.isArray(httpServerConfig.endpoints)) {
2242
- for (let i = 0; i < httpServerConfig.endpoints.length; i++) {
2243
- const endpoint = httpServerConfig.endpoints[i];
2244
- if (!endpoint.path || typeof endpoint.path !== "string") {
2245
- errors.push({
2246
- field: `http_server.endpoints[${i}].path`,
2247
- message: "Endpoint path must be a string",
2248
- value: endpoint.path
2249
- });
2250
- }
2251
- }
1178
+ formatStatusColumn(stats) {
1179
+ if (stats.skipped) {
1180
+ if (stats.skipReason === "if_condition") {
1181
+ return "\u23ED if";
1182
+ } else if (stats.skipReason === "fail_fast") {
1183
+ return "\u23ED ff";
1184
+ } else if (stats.skipReason === "dependency_failed") {
1185
+ return "\u23ED dep";
2252
1186
  }
1187
+ return "\u23ED";
1188
+ }
1189
+ const totalRuns = stats.totalRuns;
1190
+ const successfulRuns = stats.successfulRuns;
1191
+ const failedRuns = stats.failedRuns;
1192
+ if (failedRuns > 0 && successfulRuns > 0) {
1193
+ return `\u2714/\u2716 ${successfulRuns}/${totalRuns}`;
1194
+ } else if (failedRuns > 0) {
1195
+ return totalRuns === 1 ? "\u2716" : `\u2716 \xD7${totalRuns}`;
1196
+ } else {
1197
+ return totalRuns === 1 ? "\u2714" : `\u2714 \xD7${totalRuns}`;
2253
1198
  }
2254
1199
  }
2255
1200
  /**
2256
- * Validate output configuration
1201
+ * Format the details column for execution statistics
1202
+ * Used by execution-statistics-formatting tests
2257
1203
  */
2258
- validateOutputConfig(outputConfig, errors) {
2259
- if (outputConfig.pr_comment) {
2260
- const prComment = outputConfig.pr_comment;
2261
- if (typeof prComment.format === "string" && !this.validOutputFormats.includes(prComment.format)) {
2262
- errors.push({
2263
- field: "output.pr_comment.format",
2264
- message: `Invalid output format "${prComment.format}". Must be one of: ${this.validOutputFormats.join(", ")}`,
2265
- value: prComment.format
2266
- });
2267
- }
2268
- if (typeof prComment.group_by === "string" && !this.validGroupByOptions.includes(prComment.group_by)) {
2269
- errors.push({
2270
- field: "output.pr_comment.group_by",
2271
- message: `Invalid group_by option "${prComment.group_by}". Must be one of: ${this.validGroupByOptions.join(", ")}`,
2272
- value: prComment.group_by
2273
- });
2274
- }
1204
+ formatDetailsColumn(stats) {
1205
+ const parts = [];
1206
+ if (stats.outputsProduced !== void 0 && stats.outputsProduced > 0) {
1207
+ parts.push(`\u2192${stats.outputsProduced}`);
2275
1208
  }
2276
- }
2277
- /**
2278
- * Check if remote extends are allowed
2279
- */
2280
- isRemoteExtendsAllowed() {
2281
- if (process.env.VISOR_NO_REMOTE_EXTENDS === "true" || process.env.VISOR_NO_REMOTE_EXTENDS === "1") {
2282
- return false;
1209
+ if (stats.issuesBySeverity.critical > 0) {
1210
+ parts.push(`${stats.issuesBySeverity.critical}\u{1F534}`);
1211
+ }
1212
+ if (stats.issuesBySeverity.error > 0 && stats.issuesBySeverity.critical === 0) {
1213
+ parts.push(`${stats.issuesBySeverity.error}\u274C`);
2283
1214
  }
2284
- return true;
1215
+ if (stats.issuesBySeverity.warning > 0) {
1216
+ parts.push(`${stats.issuesBySeverity.warning}\u26A0\uFE0F`);
1217
+ }
1218
+ if (stats.issuesBySeverity.info > 0 && stats.issuesBySeverity.critical === 0 && stats.issuesBySeverity.error === 0 && stats.issuesBySeverity.warning === 0) {
1219
+ parts.push(`${stats.issuesBySeverity.info}\u{1F4A1}`);
1220
+ }
1221
+ if (stats.errorMessage) {
1222
+ parts.push(this.truncate(stats.errorMessage, 40));
1223
+ }
1224
+ if (stats.skipCondition) {
1225
+ parts.push(this.truncate(stats.skipCondition, 40));
1226
+ }
1227
+ return parts.join(" ");
2285
1228
  }
2286
1229
  /**
2287
- * Merge configuration with default values
1230
+ * Truncate a string to a maximum length
1231
+ * Used by formatDetailsColumn
2288
1232
  */
2289
- mergeWithDefaults(config) {
2290
- const defaultConfig = {
2291
- version: "1.0",
2292
- checks: {},
2293
- max_parallelism: 3,
2294
- output: {
2295
- pr_comment: {
2296
- format: "markdown",
2297
- group_by: "check",
2298
- collapse: true
2299
- }
2300
- }
2301
- };
2302
- const merged = { ...defaultConfig, ...config };
2303
- if (merged.output) {
2304
- merged.output.pr_comment = {
2305
- ...defaultConfig.output.pr_comment,
2306
- ...merged.output.pr_comment
2307
- };
2308
- } else {
2309
- merged.output = defaultConfig.output;
1233
+ truncate(str, maxLength) {
1234
+ if (str.length <= maxLength) {
1235
+ return str;
2310
1236
  }
2311
- return merged;
1237
+ return str.substring(0, maxLength - 3) + "...";
2312
1238
  }
2313
1239
  };
2314
- var __ajvValidate = null;
2315
- var __ajvErrors = null;
1240
+ function serializeRunState(state) {
1241
+ return {
1242
+ ...state,
1243
+ levelQueue: state.levelQueue,
1244
+ eventQueue: state.eventQueue,
1245
+ activeDispatches: Array.from(state.activeDispatches.entries()),
1246
+ completedChecks: Array.from(state.completedChecks.values()),
1247
+ stats: Array.from(state.stats.entries()),
1248
+ historyLog: state.historyLog,
1249
+ forwardRunGuards: Array.from(state.forwardRunGuards.values()),
1250
+ currentLevelChecks: Array.from(state.currentLevelChecks.values()),
1251
+ currentWaveCompletions: Array.from(
1252
+ state.currentWaveCompletions || []
1253
+ ),
1254
+ // failedChecks is an internal Set added by stats/dispatch layers; keep it if present
1255
+ failedChecks: Array.from(state.failedChecks || []),
1256
+ pendingRunScopes: Array.from((state.pendingRunScopes || /* @__PURE__ */ new Map()).entries()).map(([k, v]) => [
1257
+ k,
1258
+ v
1259
+ ])
1260
+ };
1261
+ }
2316
1262
 
2317
1263
  // src/sdk.ts
1264
+ init_config();
2318
1265
  async function loadConfig(configOrPath, options) {
2319
1266
  const cm = new ConfigManager();
2320
1267
  if (typeof configOrPath === "object" && configOrPath !== null) {
@@ -2369,7 +1316,7 @@ async function runChecks(opts = {}) {
2369
1316
  config = await cm.findAndLoadConfig();
2370
1317
  }
2371
1318
  const checks = opts.checks && opts.checks.length > 0 ? resolveChecks(opts.checks, config) : Object.keys(config.checks || {});
2372
- const engine = new CheckExecutionEngine(opts.cwd);
1319
+ const engine = new StateMachineExecutionEngine(opts.cwd);
2373
1320
  if (opts.executionContext) {
2374
1321
  engine.setExecutionContext(opts.executionContext);
2375
1322
  }