pi-lens 4.3.0 → 4.4.0

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 (650) hide show
  1. package/CHANGELOG.md +1064 -0
  2. package/README.md +115 -25
  3. package/config/dependency-cruiser-eager-allowlist.json +6 -2
  4. package/dist/chunk-22AVIGXS.js +560 -0
  5. package/dist/chunk-27BBHSZC.js +106 -0
  6. package/dist/chunk-2BEMIWEW.js +51 -0
  7. package/dist/chunk-2EECBNC5.js +89 -0
  8. package/dist/chunk-34U7PLGE.js +23 -0
  9. package/dist/chunk-3I5QFT3N.js +181 -0
  10. package/dist/chunk-3MED75HT.js +37 -0
  11. package/dist/chunk-47D2BO4S.js +87 -0
  12. package/dist/chunk-4RERDEXR.js +348 -0
  13. package/dist/chunk-4W2PLC4J.js +27 -0
  14. package/dist/chunk-52H5B5TC.js +202 -0
  15. package/dist/chunk-54B42YRX.js +27 -0
  16. package/dist/chunk-5RXBNSW2.js +266 -0
  17. package/dist/chunk-5THXD6X5.js +201 -0
  18. package/dist/chunk-5W3R6LFT.js +24 -0
  19. package/dist/chunk-5ZEJHV35.js +341 -0
  20. package/dist/chunk-6P62QVEA.js +118 -0
  21. package/dist/chunk-6QIUX4EP.js +459 -0
  22. package/dist/chunk-6VCD4VKZ.js +78 -0
  23. package/dist/chunk-7F72PXY2.js +326 -0
  24. package/dist/chunk-7FBF7NPR.js +998 -0
  25. package/dist/chunk-7U2IDNXN.js +37 -0
  26. package/dist/chunk-ABBOA7UD.js +157 -0
  27. package/dist/chunk-AHDW3GPE.js +50 -0
  28. package/dist/chunk-AJ3QXTRQ.js +756 -0
  29. package/dist/chunk-AJVLWNFN.js +1610 -0
  30. package/dist/chunk-AJXODZY6.js +112 -0
  31. package/dist/chunk-ARFCXG7S.js +84 -0
  32. package/dist/chunk-AS44CFQ4.js +708 -0
  33. package/dist/chunk-AWE5TMPO.js +38 -0
  34. package/dist/chunk-B3I2UUT4.js +4692 -0
  35. package/dist/chunk-BRJKOBZP.js +331 -0
  36. package/dist/chunk-BW5KFIQC.js +59 -0
  37. package/dist/chunk-C2AUYXQH.js +149 -0
  38. package/dist/chunk-CGGAIP7J.js +29 -0
  39. package/dist/chunk-D6QS3U6M.js +30 -0
  40. package/dist/chunk-DFVMIKMQ.js +349 -0
  41. package/dist/chunk-DIULIWKU.js +169 -0
  42. package/dist/chunk-DQ2NZ7C4.js +2317 -0
  43. package/dist/chunk-E2WFBRHM.js +213 -0
  44. package/dist/chunk-E4MAEDPM.js +50 -0
  45. package/dist/chunk-EOKRQ3SA.js +30 -0
  46. package/dist/chunk-F3AU2TMM.js +2626 -0
  47. package/dist/chunk-F3P7VQ7H.js +170 -0
  48. package/dist/chunk-FC4RP4I2.js +12 -0
  49. package/dist/chunk-FJNKANOS.js +32 -0
  50. package/dist/chunk-GUOGVTCQ.js +146 -0
  51. package/dist/chunk-GVXA6CCE.js +18 -0
  52. package/dist/chunk-HHYAELLJ.js +2334 -0
  53. package/dist/chunk-HMFXILPV.js +83 -0
  54. package/dist/chunk-HVUZXSWK.js +421 -0
  55. package/dist/chunk-HWBZIGMJ.js +62 -0
  56. package/dist/chunk-I3R7GKDR.js +17 -0
  57. package/dist/chunk-I7SPGOWI.js +55 -0
  58. package/dist/chunk-IIXOAGHR.js +38 -0
  59. package/dist/chunk-IVP5KAB7.js +2250 -0
  60. package/dist/chunk-IXEBJSUE.js +22 -0
  61. package/dist/chunk-JOLITGCC.js +27 -0
  62. package/dist/chunk-K6VYLB2U.js +145 -0
  63. package/dist/chunk-LJDODBBZ.js +78 -0
  64. package/dist/chunk-LNAKEO63.js +78 -0
  65. package/dist/chunk-LSMJ6KBE.js +4139 -0
  66. package/dist/chunk-LW6XKDZQ.js +5565 -0
  67. package/dist/chunk-N3YQJI6O.js +899 -0
  68. package/dist/chunk-NABLC7W3.js +148 -0
  69. package/dist/chunk-NKOUFPQF.js +659 -0
  70. package/dist/chunk-NL5GLMFI.js +211 -0
  71. package/dist/chunk-NMABWBZN.js +327 -0
  72. package/dist/chunk-NTHH2J74.js +27 -0
  73. package/dist/chunk-NTUJEGDZ.js +59 -0
  74. package/dist/chunk-NXL4FFQQ.js +47 -0
  75. package/dist/chunk-NXPS2L6U.js +466 -0
  76. package/dist/chunk-O5WILGMI.js +42 -0
  77. package/dist/chunk-O6TQT6RI.js +48 -0
  78. package/dist/chunk-OAXNDZJ7.js +323 -0
  79. package/dist/chunk-ODEEOXPH.js +44 -0
  80. package/dist/chunk-OQGFCZJ4.js +4533 -0
  81. package/dist/chunk-ORTMY4Q4.js +282 -0
  82. package/dist/chunk-OS32RNR2.js +442 -0
  83. package/dist/chunk-PAK6IDME.js +189 -0
  84. package/dist/chunk-PWFHBTSF.js +406 -0
  85. package/dist/chunk-Q5U6FMDI.js +2352 -0
  86. package/dist/chunk-QOZXJ4NA.js +3364 -0
  87. package/dist/chunk-QYF5S6P6.js +45 -0
  88. package/dist/chunk-RIISMHKN.js +36 -0
  89. package/dist/chunk-RPD4ZSB7.js +25 -0
  90. package/dist/chunk-RPD5K5NC.js +79 -0
  91. package/dist/chunk-RUOG5D2C.js +262 -0
  92. package/dist/chunk-T4ERT73Y.js +599 -0
  93. package/dist/chunk-T6D4Q4AQ.js +54 -0
  94. package/dist/chunk-TGPXLY2J.js +391 -0
  95. package/dist/chunk-TLGFBM3F.js +12116 -0
  96. package/dist/chunk-TO6WL3GM.js +88 -0
  97. package/dist/chunk-TT22N74J.js +517 -0
  98. package/dist/chunk-TVN5N6GY.js +21216 -0
  99. package/dist/chunk-U3VGR4VU.js +308 -0
  100. package/dist/chunk-UK3CMEAL.js +1250 -0
  101. package/dist/chunk-UOPVJQ4N.js +103 -0
  102. package/dist/chunk-UXVNL3D7.js +23 -0
  103. package/dist/chunk-UY752QSN.js +45 -0
  104. package/dist/chunk-V5PXFSZK.js +188 -0
  105. package/dist/chunk-V7R425XG.js +398 -0
  106. package/dist/chunk-VCSRSYLF.js +81 -0
  107. package/dist/chunk-VD4JQCQC.js +162 -0
  108. package/dist/chunk-VDBPVIKD.js +62 -0
  109. package/dist/chunk-VHQLPIRJ.js +114 -0
  110. package/dist/chunk-VN2AXLEX.js +5414 -0
  111. package/dist/chunk-VNMT7C64.js +478 -0
  112. package/dist/chunk-WONERJTP.js +309 -0
  113. package/dist/chunk-XECLWQZN.js +85 -0
  114. package/dist/chunk-XIMYZ3JA.js +27 -0
  115. package/dist/chunk-XK4CIILI.js +144 -0
  116. package/dist/chunk-Y4T27COV.js +562 -0
  117. package/dist/chunk-YGVYKNSN.js +1860 -0
  118. package/dist/chunk-YLF3PPT5.js +308 -0
  119. package/dist/chunk-YSNFMJKN.js +14 -0
  120. package/dist/chunk-Z3CCVDUB.js +76 -0
  121. package/dist/chunk-Z54ZSOBZ.js +61 -0
  122. package/dist/chunk-ZAWW3AF6.js +97 -0
  123. package/dist/chunk-ZMOL6RQC.js +167 -0
  124. package/dist/index.js +16456 -8335
  125. package/dist/mcp/analyze-cli.js +186 -232
  126. package/dist/mcp/cli.js +137 -98
  127. package/dist/mcp/server.js +22589 -1573
  128. package/dist/mcp/worker.js +97 -38
  129. package/dist/probes/installer.js +135 -0
  130. package/dist/workers/project-snapshot-persist-worker.js +22 -0
  131. package/dist/workers/review-graph-persist-worker.js +15 -0
  132. package/docs/agent-guide.md +13 -4
  133. package/docs/agent-tools.md +42 -0
  134. package/docs/ast-grep_rules_catalog.md +3 -2
  135. package/docs/configuration.md +20 -1
  136. package/docs/environment-variables.md +52 -2
  137. package/docs/features.md +8 -4
  138. package/docs/globalconfig.md +1 -0
  139. package/docs/io-bridge-v2-migration.md +94 -0
  140. package/docs/lsp-capability-matrix.md +36 -4
  141. package/docs/lsp-idle-eviction.md +124 -0
  142. package/docs/mcp.md +21 -5
  143. package/docs/merge-queue-rollout.md +77 -0
  144. package/docs/pi-lens-fixer.md +156 -88
  145. package/docs/pi-lens-investigator.md +74 -26
  146. package/docs/pi-lens-merge-policy.md +694 -0
  147. package/docs/pi-lens-monitor.md +73 -7
  148. package/docs/pi-lens-retro.md +6 -4
  149. package/docs/pi-lens-reviewer.md +188 -48
  150. package/docs/pi-lens-subagent.md +246 -45
  151. package/docs/pi-lens-warden.md +18 -7
  152. package/docs/public-api-stability.md +55 -0
  153. package/docs/release-qa-baseline.md +84 -17
  154. package/docs/rfcs/3654-unified-io-bridge-v2.md +316 -0
  155. package/docs/servercapabilities.md +53 -49
  156. package/docs/settings.md +1 -0
  157. package/docs/usage.md +5 -1
  158. package/grammars/tree-sitter-bash.wasm +0 -0
  159. package/grammars/tree-sitter-bash.wasm.json +3 -3
  160. package/package.json +19 -7
  161. package/rules/ast-grep-rules/rule-tests/advisory-registry-subset-test.yml +41 -0
  162. package/rules/ast-grep-rules/rules/advisory-registry-subset.yml +50 -0
  163. package/scripts/analyze-pi-lens-logs.mjs +1275 -62
  164. package/scripts/download-grammars.js +5 -0
  165. package/scripts/grammars.lock.json +6 -1
  166. package/scripts/install-selftest.mjs +33 -34
  167. package/scripts/lib/host-provided-deps.mjs +13 -4
  168. package/scripts/lib/web-tree-sitter-dir.mjs +18 -0
  169. package/dist/clients/actionable-warnings-logger.js +0 -35
  170. package/dist/clients/actionable-warnings.js +0 -1610
  171. package/dist/clients/advisory-provenance.js +0 -593
  172. package/dist/clients/agent-behavior-client.js +0 -147
  173. package/dist/clients/agent-nudge.js +0 -325
  174. package/dist/clients/analysed-root.js +0 -1
  175. package/dist/clients/ast-grep-client.js +0 -731
  176. package/dist/clients/ast-grep-rule-manager.js +0 -146
  177. package/dist/clients/ast-grep-tool-logger.js +0 -118
  178. package/dist/clients/ast-grep-types.js +0 -10
  179. package/dist/clients/ast-grep-yaml-synth.js +0 -154
  180. package/dist/clients/atomic-write-staging.js +0 -75
  181. package/dist/clients/atomic-write.js +0 -189
  182. package/dist/clients/availability-probe-flight.js +0 -12
  183. package/dist/clients/bash-file-access.js +0 -1056
  184. package/dist/clients/biome-client.js +0 -357
  185. package/dist/clients/blocker-freshness.js +0 -837
  186. package/dist/clients/blocker-past-eof.js +0 -129
  187. package/dist/clients/bootstrap.js +0 -569
  188. package/dist/clients/bounded-cache.js +0 -190
  189. package/dist/clients/bounded-pid-file-lock.js +0 -202
  190. package/dist/clients/bounded-telemetry.js +0 -295
  191. package/dist/clients/build-identity.js +0 -95
  192. package/dist/clients/bundled-resource-health.js +0 -113
  193. package/dist/clients/bus-events-logger.js +0 -105
  194. package/dist/clients/bus-publish.js +0 -171
  195. package/dist/clients/cache/rule-cache.js +0 -190
  196. package/dist/clients/cache-manager.js +0 -524
  197. package/dist/clients/cache-observability.js +0 -1313
  198. package/dist/clients/call-graph.js +0 -852
  199. package/dist/clients/cargo-manifest.js +0 -422
  200. package/dist/clients/cascade-budget.js +0 -196
  201. package/dist/clients/cascade-format.js +0 -315
  202. package/dist/clients/cascade-logger.js +0 -31
  203. package/dist/clients/cascade-types.js +0 -9
  204. package/dist/clients/child-unref.js +0 -124
  205. package/dist/clients/code-quality-warnings.js +0 -286
  206. package/dist/clients/codebase-model.js +0 -173
  207. package/dist/clients/collateral-test-role.js +0 -40
  208. package/dist/clients/complexity-client.js +0 -462
  209. package/dist/clients/config-core/deny.js +0 -221
  210. package/dist/clients/config-core/index.js +0 -50
  211. package/dist/clients/config-core/merge.js +0 -357
  212. package/dist/clients/config-core/normalize.js +0 -340
  213. package/dist/clients/config-core/process-spec.js +0 -248
  214. package/dist/clients/config-core/provenance.js +0 -164
  215. package/dist/clients/config-core/records.js +0 -218
  216. package/dist/clients/config-core/resolve.js +0 -125
  217. package/dist/clients/config-core/safe-object.js +0 -78
  218. package/dist/clients/config-core/schema.js +0 -165
  219. package/dist/clients/config-diagnostic-codes.js +0 -285
  220. package/dist/clients/config-locations.js +0 -388
  221. package/dist/clients/config-resolve.js +0 -798
  222. package/dist/clients/config-schema.js +0 -251
  223. package/dist/clients/config-warn.js +0 -407
  224. package/dist/clients/console-guard-install.js +0 -26
  225. package/dist/clients/cooperative-budget.js +0 -42
  226. package/dist/clients/dead-code-client.js +0 -510
  227. package/dist/clients/dead-code-logger.js +0 -30
  228. package/dist/clients/deadline-utils.js +0 -280
  229. package/dist/clients/debug-handles.js +0 -211
  230. package/dist/clients/debug-heap.js +0 -174
  231. package/dist/clients/deferred-lsp-work.js +0 -106
  232. package/dist/clients/degradation-ledger.js +0 -426
  233. package/dist/clients/demoted-finding-render.js +0 -169
  234. package/dist/clients/dependency-checker.js +0 -885
  235. package/dist/clients/deps/ast-grep-napi.js +0 -22
  236. package/dist/clients/deps/js-yaml.js +0 -9
  237. package/dist/clients/deps/minimatch.js +0 -4
  238. package/dist/clients/deps/pi-tui.js +0 -14
  239. package/dist/clients/deps/typebox.js +0 -14
  240. package/dist/clients/deps/vscode-jsonrpc.js +0 -1
  241. package/dist/clients/deps/web-tree-sitter.js +0 -24
  242. package/dist/clients/diagnostic-dispositions.js +0 -630
  243. package/dist/clients/diagnostic-line-freshness.js +0 -305
  244. package/dist/clients/diagnostic-logger.js +0 -73
  245. package/dist/clients/diagnostic-tracker.js +0 -105
  246. package/dist/clients/diagnostics-publish.js +0 -230
  247. package/dist/clients/dispatch/ast-grep-catalog.js +0 -65
  248. package/dist/clients/dispatch/auxiliary-lsp.js +0 -343
  249. package/dist/clients/dispatch/collect-later-tier.js +0 -35
  250. package/dist/clients/dispatch/diagnostic-taxonomy.js +0 -66
  251. package/dist/clients/dispatch/dispatcher.js +0 -1269
  252. package/dist/clients/dispatch/fact-provider-types.js +0 -1
  253. package/dist/clients/dispatch/fact-rule-runner.js +0 -17
  254. package/dist/clients/dispatch/fact-runner.js +0 -30
  255. package/dist/clients/dispatch/fact-scheduler.js +0 -68
  256. package/dist/clients/dispatch/fact-store.js +0 -377
  257. package/dist/clients/dispatch/facts/comment-facts.js +0 -22
  258. package/dist/clients/dispatch/facts/file-content.js +0 -19
  259. package/dist/clients/dispatch/facts/function-facts.js +0 -348
  260. package/dist/clients/dispatch/facts/import-facts.js +0 -204
  261. package/dist/clients/dispatch/facts/tree-sitter-facts.js +0 -34
  262. package/dist/clients/dispatch/facts/try-catch-facts.js +0 -119
  263. package/dist/clients/dispatch/finding-policy.js +0 -339
  264. package/dist/clients/dispatch/indent-detect.js +0 -417
  265. package/dist/clients/dispatch/inline-suppressions.js +0 -89
  266. package/dist/clients/dispatch/integration.js +0 -2377
  267. package/dist/clients/dispatch/lazy.js +0 -11
  268. package/dist/clients/dispatch/pending-runner-findings.js +0 -87
  269. package/dist/clients/dispatch/plan.js +0 -278
  270. package/dist/clients/dispatch/priorities.js +0 -20
  271. package/dist/clients/dispatch/rule-id-normalize.js +0 -50
  272. package/dist/clients/dispatch/rule-ignores.js +0 -69
  273. package/dist/clients/dispatch/rule-policy.js +0 -135
  274. package/dist/clients/dispatch/rules/async-noise.js +0 -40
  275. package/dist/clients/dispatch/rules/async-unnecessary-wrapper.js +0 -27
  276. package/dist/clients/dispatch/rules/cors-wildcard.js +0 -54
  277. package/dist/clients/dispatch/rules/error-obscuring.js +0 -32
  278. package/dist/clients/dispatch/rules/error-swallowing.js +0 -31
  279. package/dist/clients/dispatch/rules/framework-call-noise.js +0 -67
  280. package/dist/clients/dispatch/rules/high-complexity.js +0 -72
  281. package/dist/clients/dispatch/rules/high-fan-out.js +0 -74
  282. package/dist/clients/dispatch/rules/high-import-coupling.js +0 -37
  283. package/dist/clients/dispatch/rules/missing-error-propagation.js +0 -62
  284. package/dist/clients/dispatch/rules/no-commented-credentials.js +0 -52
  285. package/dist/clients/dispatch/rules/pass-through-wrappers.js +0 -42
  286. package/dist/clients/dispatch/rules/placeholder-comments.js +0 -40
  287. package/dist/clients/dispatch/rules/unsafe-boundary.js +0 -126
  288. package/dist/clients/dispatch/runner-context.js +0 -52
  289. package/dist/clients/dispatch/runners/actionlint.js +0 -111
  290. package/dist/clients/dispatch/runners/ast-grep-napi.js +0 -1196
  291. package/dist/clients/dispatch/runners/biome-check.js +0 -296
  292. package/dist/clients/dispatch/runners/cpp-check.js +0 -219
  293. package/dist/clients/dispatch/runners/credo.js +0 -82
  294. package/dist/clients/dispatch/runners/cue-vet.js +0 -416
  295. package/dist/clients/dispatch/runners/dart-analyze.js +0 -183
  296. package/dist/clients/dispatch/runners/detekt.js +0 -176
  297. package/dist/clients/dispatch/runners/dotnet-build.js +0 -164
  298. package/dist/clients/dispatch/runners/elixir-check.js +0 -195
  299. package/dist/clients/dispatch/runners/eslint.js +0 -137
  300. package/dist/clients/dispatch/runners/fact-rules.js +0 -29
  301. package/dist/clients/dispatch/runners/fish-indent.js +0 -69
  302. package/dist/clients/dispatch/runners/gleam-check.js +0 -167
  303. package/dist/clients/dispatch/runners/go-vet.js +0 -104
  304. package/dist/clients/dispatch/runners/golangci-lint.js +0 -135
  305. package/dist/clients/dispatch/runners/hadolint.js +0 -91
  306. package/dist/clients/dispatch/runners/helm-lint.js +0 -171
  307. package/dist/clients/dispatch/runners/helm-render.js +0 -940
  308. package/dist/clients/dispatch/runners/htmlhint.js +0 -117
  309. package/dist/clients/dispatch/runners/index.js +0 -108
  310. package/dist/clients/dispatch/runners/javac.js +0 -99
  311. package/dist/clients/dispatch/runners/ktlint.js +0 -110
  312. package/dist/clients/dispatch/runners/lsp.js +0 -379
  313. package/dist/clients/dispatch/runners/markdownlint.js +0 -167
  314. package/dist/clients/dispatch/runners/mypy.js +0 -102
  315. package/dist/clients/dispatch/runners/oxlint.js +0 -414
  316. package/dist/clients/dispatch/runners/php-lint.js +0 -85
  317. package/dist/clients/dispatch/runners/phpstan.js +0 -120
  318. package/dist/clients/dispatch/runners/prisma-validate.js +0 -62
  319. package/dist/clients/dispatch/runners/psscriptanalyzer.js +0 -445
  320. package/dist/clients/dispatch/runners/pyright.js +0 -169
  321. package/dist/clients/dispatch/runners/rubocop.js +0 -99
  322. package/dist/clients/dispatch/runners/ruff.js +0 -88
  323. package/dist/clients/dispatch/runners/rust-clippy.js +0 -256
  324. package/dist/clients/dispatch/runners/shellcheck.js +0 -173
  325. package/dist/clients/dispatch/runners/shfmt.js +0 -102
  326. package/dist/clients/dispatch/runners/spellcheck.js +0 -128
  327. package/dist/clients/dispatch/runners/spotbugs.js +0 -259
  328. package/dist/clients/dispatch/runners/sqlfluff.js +0 -183
  329. package/dist/clients/dispatch/runners/stylelint.js +0 -166
  330. package/dist/clients/dispatch/runners/swiftlint.js +0 -163
  331. package/dist/clients/dispatch/runners/taplo.js +0 -174
  332. package/dist/clients/dispatch/runners/terragrunt.js +0 -168
  333. package/dist/clients/dispatch/runners/tflint.js +0 -125
  334. package/dist/clients/dispatch/runners/tree-sitter.js +0 -643
  335. package/dist/clients/dispatch/runners/trivy-config.js +0 -253
  336. package/dist/clients/dispatch/runners/utils/availability-policy.js +0 -496
  337. package/dist/clients/dispatch/runners/utils/candidate-probe.js +0 -90
  338. package/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +0 -95
  339. package/dist/clients/dispatch/runners/utils/lazy-installer.js +0 -274
  340. package/dist/clients/dispatch/runners/utils/runner-helpers.js +0 -1701
  341. package/dist/clients/dispatch/runners/utils/spawn-outcome.js +0 -101
  342. package/dist/clients/dispatch/runners/utils/tool-failure.js +0 -235
  343. package/dist/clients/dispatch/runners/utils/toolchain-availability.js +0 -169
  344. package/dist/clients/dispatch/runners/utils.js +0 -15
  345. package/dist/clients/dispatch/runners/vale.js +0 -128
  346. package/dist/clients/dispatch/runners/yaml-rule-parser.js +0 -242
  347. package/dist/clients/dispatch/runners/yamllint.js +0 -95
  348. package/dist/clients/dispatch/runners/zig-check.js +0 -79
  349. package/dist/clients/dispatch/suppress-writer.js +0 -79
  350. package/dist/clients/dispatch/tool-profile.js +0 -30
  351. package/dist/clients/dispatch/types.js +0 -21
  352. package/dist/clients/dispatch/utils/format-utils.js +0 -45
  353. package/dist/clients/dispatch/utils/lsp-diagnostics.js +0 -35
  354. package/dist/clients/disposition-logger.js +0 -32
  355. package/dist/clients/disposition-publish.js +0 -124
  356. package/dist/clients/durable-store.js +0 -97
  357. package/dist/clients/effective-config.js +0 -415
  358. package/dist/clients/env-utils.js +0 -83
  359. package/dist/clients/error-class.js +0 -23
  360. package/dist/clients/eval-timestamp.js +0 -17
  361. package/dist/clients/event-loop-hold.js +0 -274
  362. package/dist/clients/event-loop-monitor.js +0 -250
  363. package/dist/clients/extension-log.js +0 -504
  364. package/dist/clients/extension-mode.js +0 -73
  365. package/dist/clients/feature-hints.js +0 -103
  366. package/dist/clients/file-kinds.js +0 -412
  367. package/dist/clients/file-role.js +0 -134
  368. package/dist/clients/file-time.js +0 -160
  369. package/dist/clients/file-utils.js +0 -1032
  370. package/dist/clients/finding-delivery-gate.js +0 -494
  371. package/dist/clients/finding-identity.js +0 -88
  372. package/dist/clients/fix-worklog.js +0 -103
  373. package/dist/clients/format-events-publish.js +0 -343
  374. package/dist/clients/format-service.js +0 -222
  375. package/dist/clients/formatters-lazy.js +0 -9
  376. package/dist/clients/formatters.js +0 -2032
  377. package/dist/clients/freshness-cadence.js +0 -17
  378. package/dist/clients/freshness.js +0 -38
  379. package/dist/clients/generated-artifacts.js +0 -394
  380. package/dist/clients/generation-guard.js +0 -215
  381. package/dist/clients/git-guard.js +0 -1136
  382. package/dist/clients/git-tracked-ignore.js +0 -243
  383. package/dist/clients/gitleaks-client.js +0 -509
  384. package/dist/clients/go-client.js +0 -104
  385. package/dist/clients/govulncheck-client.js +0 -605
  386. package/dist/clients/gradle-ktfmt-style.js +0 -348
  387. package/dist/clients/grammar-source.js +0 -465
  388. package/dist/clients/gzip-stage-write.js +0 -135
  389. package/dist/clients/hashline-anchor.js +0 -424
  390. package/dist/clients/hook-budgets.js +0 -76
  391. package/dist/clients/host-edit-normalize.js +0 -140
  392. package/dist/clients/host-ports.js +0 -39
  393. package/dist/clients/indent-retarget.js +0 -154
  394. package/dist/clients/inline-blocker-dispositions.js +0 -186
  395. package/dist/clients/install-diagnostics.js +0 -156
  396. package/dist/clients/installer/index.js +0 -5416
  397. package/dist/clients/installer/managed-tool-refresh-session.js +0 -58
  398. package/dist/clients/installer/managed-tool-refresh.js +0 -783
  399. package/dist/clients/instance-reaper-state.js +0 -7
  400. package/dist/clients/instance-reaper.js +0 -1137
  401. package/dist/clients/instance-registry-lock.js +0 -145
  402. package/dist/clients/instance-registry.js +0 -773
  403. package/dist/clients/jscpd-client.js +0 -378
  404. package/dist/clients/json-cache-read.js +0 -65
  405. package/dist/clients/knip-client.js +0 -788
  406. package/dist/clients/language-policy.js +0 -252
  407. package/dist/clients/language-profile.js +0 -361
  408. package/dist/clients/language-registry.js +0 -603
  409. package/dist/clients/latency-logger.js +0 -727
  410. package/dist/clients/lazy-import.js +0 -21
  411. package/dist/clients/ledger-bounds.js +0 -54
  412. package/dist/clients/lens-config.js +0 -404
  413. package/dist/clients/lens-engine.js +0 -358
  414. package/dist/clients/lens-events.js +0 -125
  415. package/dist/clients/lens-flag-registry.js +0 -467
  416. package/dist/clients/lens-map.js +0 -1156
  417. package/dist/clients/live-bus-emitter.js +0 -74
  418. package/dist/clients/log-cleanup.js +0 -222
  419. package/dist/clients/lsp/aggregation.js +0 -178
  420. package/dist/clients/lsp/cascade-tier.js +0 -421
  421. package/dist/clients/lsp/client.js +0 -4593
  422. package/dist/clients/lsp/config.js +0 -691
  423. package/dist/clients/lsp/content-limits.js +0 -41
  424. package/dist/clients/lsp/diagnostic-binding.js +0 -225
  425. package/dist/clients/lsp/document-drift.js +0 -467
  426. package/dist/clients/lsp/edits.js +0 -1219
  427. package/dist/clients/lsp/index.js +0 -8305
  428. package/dist/clients/lsp/inferred-project.js +0 -216
  429. package/dist/clients/lsp/jvm-runtime.js +0 -298
  430. package/dist/clients/lsp/language.js +0 -63
  431. package/dist/clients/lsp/launch.js +0 -746
  432. package/dist/clients/lsp/lombok.js +0 -158
  433. package/dist/clients/lsp/path-utils.js +0 -5
  434. package/dist/clients/lsp/pending-aux-coverage.js +0 -251
  435. package/dist/clients/lsp/position-encoding.js +0 -61
  436. package/dist/clients/lsp/ruby-drive-dirs.js +0 -83
  437. package/dist/clients/lsp/server.js +0 -3290
  438. package/dist/clients/lsp/session-roots.js +0 -192
  439. package/dist/clients/lsp/spawn-history.js +0 -17
  440. package/dist/clients/lsp/sync-kind.js +0 -56
  441. package/dist/clients/lsp/tsserver-sync.js +0 -332
  442. package/dist/clients/lsp/wait-policy/capability-snapshot.js +0 -1
  443. package/dist/clients/lsp/wait-policy/classification.js +0 -85
  444. package/dist/clients/lsp/wait-policy/index.js +0 -10
  445. package/dist/clients/lsp/wait-policy/strategies.js +0 -392
  446. package/dist/clients/lsp/watch-queue.js +0 -61
  447. package/dist/clients/lsp/workspace-diagnostics-cache.js +0 -639
  448. package/dist/clients/lsp/workspace-diagnostics-session.js +0 -30
  449. package/dist/clients/lsp/workspace-sweep-hold.js +0 -184
  450. package/dist/clients/lsp-budget.js +0 -186
  451. package/dist/clients/lsp-document-symbols.js +0 -110
  452. package/dist/clients/lsp-lazy.js +0 -9
  453. package/dist/clients/lsp-mutation.js +0 -396
  454. package/dist/clients/lsp-status.js +0 -59
  455. package/dist/clients/map-with-concurrency.js +0 -40
  456. package/dist/clients/mcp/analyze.js +0 -420
  457. package/dist/clients/mcp/host-shim.js +0 -31
  458. package/dist/clients/mcp/ipc.js +0 -523
  459. package/dist/clients/mcp/review.js +0 -197
  460. package/dist/clients/mcp/session.js +0 -412
  461. package/dist/clients/memory-sampler.js +0 -208
  462. package/dist/clients/message-end-attribution.js +0 -30
  463. package/dist/clients/metrics-client.js +0 -107
  464. package/dist/clients/metrics-history.js +0 -327
  465. package/dist/clients/middle-man-analysis.js +0 -263
  466. package/dist/clients/model-provider.js +0 -53
  467. package/dist/clients/module-report-lsp.js +0 -189
  468. package/dist/clients/module-report.js +0 -2150
  469. package/dist/clients/mutating-tool.js +0 -651
  470. package/dist/clients/mutation-attribution.js +0 -368
  471. package/dist/clients/mutation-bridge.js +0 -240
  472. package/dist/clients/ndjson-logger.js +0 -734
  473. package/dist/clients/observed-mutation-sources.js +0 -101
  474. package/dist/clients/observed-mutation.js +0 -1253
  475. package/dist/clients/oldtext-autopatch.js +0 -128
  476. package/dist/clients/opaque-mutation-scan.js +0 -496
  477. package/dist/clients/opengrep-client.js +0 -333
  478. package/dist/clients/opengrep-config.js +0 -76
  479. package/dist/clients/package-manager.js +0 -607
  480. package/dist/clients/package-root.js +0 -38
  481. package/dist/clients/partial-edit-apply.js +0 -439
  482. package/dist/clients/path-attribution-telemetry.js +0 -43
  483. package/dist/clients/path-keyed-map.js +0 -108
  484. package/dist/clients/path-utils.js +0 -1244
  485. package/dist/clients/performance-report.js +0 -331
  486. package/dist/clients/persist-debounce.js +0 -70
  487. package/dist/clients/persistent-reverify.js +0 -271
  488. package/dist/clients/php-cs-fixer-config.js +0 -119
  489. package/dist/clients/pipeline.js +0 -1480
  490. package/dist/clients/post-autofix-notice.js +0 -37
  491. package/dist/clients/print-mode.js +0 -21
  492. package/dist/clients/probe-home-state.js +0 -228
  493. package/dist/clients/process-bridge.js +0 -66
  494. package/dist/clients/process-singletons.js +0 -210
  495. package/dist/clients/process-snapshot.js +0 -68
  496. package/dist/clients/project-changes.js +0 -181
  497. package/dist/clients/project-conventions.js +0 -171
  498. package/dist/clients/project-diagnostics/cache.js +0 -137
  499. package/dist/clients/project-diagnostics/extractors.js +0 -109
  500. package/dist/clients/project-diagnostics/fresh-fetch.js +0 -668
  501. package/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +0 -118
  502. package/dist/clients/project-diagnostics/runner-adapters/dead-code.js +0 -47
  503. package/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +0 -40
  504. package/dist/clients/project-diagnostics/runner-adapters/govulncheck.js +0 -34
  505. package/dist/clients/project-diagnostics/runner-adapters/jscpd.js +0 -38
  506. package/dist/clients/project-diagnostics/runner-adapters/knip.js +0 -47
  507. package/dist/clients/project-diagnostics/runner-adapters/madge.js +0 -48
  508. package/dist/clients/project-diagnostics/runner-adapters/opengrep.js +0 -40
  509. package/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +0 -127
  510. package/dist/clients/project-diagnostics/runner-adapters/trivy.js +0 -81
  511. package/dist/clients/project-diagnostics/scanner.js +0 -520
  512. package/dist/clients/project-diagnostics/types.js +0 -1
  513. package/dist/clients/project-lens-config.js +0 -807
  514. package/dist/clients/project-report.js +0 -701
  515. package/dist/clients/project-scale.js +0 -237
  516. package/dist/clients/project-scan-policy.js +0 -11
  517. package/dist/clients/project-snapshot-fingerprint.js +0 -48
  518. package/dist/clients/project-snapshot-persist-worker.js +0 -20
  519. package/dist/clients/project-snapshot.js +0 -1653
  520. package/dist/clients/project-trust.js +0 -181
  521. package/dist/clients/python-environment.js +0 -217
  522. package/dist/clients/python-provenance.js +0 -639
  523. package/dist/clients/quiet-window-config.js +0 -47
  524. package/dist/clients/quiet-window.js +0 -236
  525. package/dist/clients/read-bridge.js +0 -139
  526. package/dist/clients/read-expansion.js +0 -266
  527. package/dist/clients/read-guard-logger.js +0 -251
  528. package/dist/clients/read-guard-tool-lines.js +0 -1306
  529. package/dist/clients/read-guard.js +0 -1561
  530. package/dist/clients/recent-touches.js +0 -240
  531. package/dist/clients/redact/secrets.js +0 -449
  532. package/dist/clients/resource-sampler.js +0 -628
  533. package/dist/clients/reverse-deps.js +0 -270
  534. package/dist/clients/review-graph/builder.js +0 -5126
  535. package/dist/clients/review-graph/format.js +0 -33
  536. package/dist/clients/review-graph/git-identity.js +0 -163
  537. package/dist/clients/review-graph/import-resolvers.js +0 -481
  538. package/dist/clients/review-graph/persist-worker.js +0 -10
  539. package/dist/clients/review-graph/query.js +0 -244
  540. package/dist/clients/review-graph/revision-drift.js +0 -21
  541. package/dist/clients/review-graph/service.js +0 -96
  542. package/dist/clients/review-graph/shared-extraction-ir.js +0 -82
  543. package/dist/clients/review-graph/symbol-id.js +0 -103
  544. package/dist/clients/review-graph/tsconfig-paths.js +0 -299
  545. package/dist/clients/review-graph/types.js +0 -1
  546. package/dist/clients/review-graph/workspace-modules.js +0 -449
  547. package/dist/clients/review-graph-logger.js +0 -81
  548. package/dist/clients/ruff-client.js +0 -179
  549. package/dist/clients/rules-scanner.js +0 -131
  550. package/dist/clients/run-duration.js +0 -55
  551. package/dist/clients/runtime-agent-end.js +0 -746
  552. package/dist/clients/runtime-config.js +0 -77
  553. package/dist/clients/runtime-context.js +0 -211
  554. package/dist/clients/runtime-coordinator.js +0 -1324
  555. package/dist/clients/runtime-session.js +0 -2485
  556. package/dist/clients/runtime-tool-call.js +0 -1295
  557. package/dist/clients/runtime-tool-result.js +0 -2049
  558. package/dist/clients/runtime-turn.js +0 -3767
  559. package/dist/clients/rust-client.js +0 -97
  560. package/dist/clients/safe-spawn.js +0 -2357
  561. package/dist/clients/sanitize.js +0 -286
  562. package/dist/clients/scan-utils.js +0 -25
  563. package/dist/clients/scratch-tree-policy.js +0 -165
  564. package/dist/clients/search-read-registration.js +0 -94
  565. package/dist/clients/secret-findings.js +0 -114
  566. package/dist/clients/security-scan-client.js +0 -352
  567. package/dist/clients/session-event-guard.js +0 -297
  568. package/dist/clients/session-lifecycle.js +0 -484
  569. package/dist/clients/session-start-observability.js +0 -94
  570. package/dist/clients/session-state-store.js +0 -101
  571. package/dist/clients/session-summary.js +0 -15
  572. package/dist/clients/sessionstart-logger.js +0 -27
  573. package/dist/clients/sg-runner.js +0 -876
  574. package/dist/clients/sgconfig.js +0 -347
  575. package/dist/clients/shared-checkout-guard.js +0 -338
  576. package/dist/clients/single-flight.js +0 -158
  577. package/dist/clients/situational-tool-telemetry.js +0 -108
  578. package/dist/clients/skills-resolver.js +0 -141
  579. package/dist/clients/slow-fs.js +0 -138
  580. package/dist/clients/smells-rollup.js +0 -231
  581. package/dist/clients/source-filter.js +0 -596
  582. package/dist/clients/source-walker.js +0 -211
  583. package/dist/clients/spawn-output-cap.js +0 -120
  584. package/dist/clients/spawn-timeout-cooldown.js +0 -110
  585. package/dist/clients/stale-marker.js +0 -17
  586. package/dist/clients/startup-scan.js +0 -400
  587. package/dist/clients/startup-timing.js +0 -130
  588. package/dist/clients/string-utils.js +0 -33
  589. package/dist/clients/subagent-mode.js +0 -111
  590. package/dist/clients/symbol-containment.js +0 -24
  591. package/dist/clients/symbol-types.js +0 -5
  592. package/dist/clients/test-runner-client.js +0 -2507
  593. package/dist/clients/test-runner-delivery.js +0 -287
  594. package/dist/clients/todo-scanner.js +0 -229
  595. package/dist/clients/tool-agreement.js +0 -423
  596. package/dist/clients/tool-config.js +0 -249
  597. package/dist/clients/tool-cwd.js +0 -270
  598. package/dist/clients/tool-definition.js +0 -47
  599. package/dist/clients/tool-event.js +0 -105
  600. package/dist/clients/tool-policy.js +0 -2755
  601. package/dist/clients/tool-probe.js +0 -53
  602. package/dist/clients/tool-render.js +0 -156
  603. package/dist/clients/tool-set-policy.js +0 -118
  604. package/dist/clients/tree-sitter-cache.js +0 -364
  605. package/dist/clients/tree-sitter-client.js +0 -4195
  606. package/dist/clients/tree-sitter-logger.js +0 -100
  607. package/dist/clients/tree-sitter-navigator.js +0 -269
  608. package/dist/clients/tree-sitter-query-loader.js +0 -569
  609. package/dist/clients/tree-sitter-shared.js +0 -158
  610. package/dist/clients/tree-sitter-symbol-extractor.js +0 -1265
  611. package/dist/clients/trivy-client.js +0 -467
  612. package/dist/clients/tui-fit.js +0 -50
  613. package/dist/clients/turn-context.js +0 -52
  614. package/dist/clients/turn-end/lane.js +0 -51
  615. package/dist/clients/turn-end/lanes/govulncheck.js +0 -112
  616. package/dist/clients/turn-end/lanes/secrets.js +0 -204
  617. package/dist/clients/turn-summary-render.js +0 -72
  618. package/dist/clients/turn-summary.js +0 -132
  619. package/dist/clients/typos-config.js +0 -29
  620. package/dist/clients/user-notify.js +0 -51
  621. package/dist/clients/vanished-instance-marker.js +0 -57
  622. package/dist/clients/warm-attach.js +0 -305
  623. package/dist/clients/widget-state.js +0 -1808
  624. package/dist/clients/word-index-logger.js +0 -49
  625. package/dist/clients/word-index-store.js +0 -459
  626. package/dist/clients/word-index.js +0 -2080
  627. package/dist/clients/workspace-topology.js +0 -443
  628. package/dist/clients/write-ordering-guard.js +0 -65
  629. package/dist/clients/zizmor-config.js +0 -340
  630. package/dist/i18n.js +0 -69
  631. package/dist/mcp/build-staleness.js +0 -124
  632. package/dist/scripts/lib/process-scan.mjs +0 -583
  633. package/dist/scripts/lib/skills-predicate.mjs +0 -129
  634. package/dist/scripts/lib/web-tree-sitter-dir.mjs +0 -123
  635. package/dist/tools/activate-tools.js +0 -87
  636. package/dist/tools/ast-grep-outline.js +0 -137
  637. package/dist/tools/ast-grep-replace.js +0 -244
  638. package/dist/tools/ast-grep-search.js +0 -762
  639. package/dist/tools/effective-config.js +0 -90
  640. package/dist/tools/lens-diagnostic-mark.js +0 -375
  641. package/dist/tools/lens-diagnostics.js +0 -2571
  642. package/dist/tools/lsp-diagnostics.js +0 -1745
  643. package/dist/tools/lsp-navigation.js +0 -1367
  644. package/dist/tools/lsp-structured-output.js +0 -294
  645. package/dist/tools/module-report.js +0 -382
  646. package/dist/tools/project-report.js +0 -86
  647. package/dist/tools/render-compact.js +0 -374
  648. package/dist/tools/scan-progress.js +0 -53
  649. package/dist/tools/shared.js +0 -29
  650. package/dist/tools/symbol-search.js +0 -155
@@ -1,876 +0,0 @@
1
- /**
2
- * SgRunner - encapsulates ast-grep subprocess management
3
- *
4
- * Extracted from AstGrepClient to simplify the main client.
5
- * Handles: spawn, spawnSync, temp dir management, JSON parsing.
6
- */
7
- import { createSubsystemLogger } from "./extension-log.js";
8
- import * as fs from "node:fs";
9
- import * as os from "node:os";
10
- import * as path from "node:path";
11
- import { getSgCommand, resolveManagedToolClient, } from "./dispatch/runners/utils/runner-helpers.js";
12
- import { getProjectIgnoreGlobs } from "./file-utils.js";
13
- import { findGlobalBinary } from "./package-manager.js";
14
- import { safeSpawnAsync } from "./safe-spawn.js";
15
- import { probeToolAsync } from "./tool-probe.js";
16
- import { truncatedByOutputCap } from "./spawn-output-cap.js";
17
- import { createSingleFlight } from "./single-flight.js";
18
- import { classifyProbeFailure, describeInstallAttempt, createAvailabilityLatch, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
19
- /**
20
- * Build the `bash -c` argv that runs `cmd` with `allArgs` as POSITIONAL
21
- * parameters. The script is the constant `"$0" "$@"`, so bash re-emits the
22
- * command and every arg verbatim — no parameter expansion, no word-splitting.
23
- *
24
- * Two properties this guarantees, neither of which string-interpolation could:
25
- * - ast-grep `$METAVAR` patterns reach the binary literally (not shell-expanded).
26
- * - an environment-derived command path (PATH-resolved `ast-grep`/`sg`/`npx`)
27
- * cannot inject shell — it's argv[0], never part of the script string
28
- * (CodeQL js/shell-command-injection-from-environment).
29
- */
30
- export function buildBashRunArgs(cmd, allArgs) {
31
- return ["-c", '"$0" "$@"', cmd, ...allArgs];
32
- }
33
- function sgExcludeArgsForProject(rootDir) {
34
- return getProjectIgnoreGlobs(rootDir).flatMap((glob) => [
35
- "--globs",
36
- `!${glob}`,
37
- ]);
38
- }
39
- const DEFAULT_EXEC_TIMEOUT_MS = 30_000;
40
- /** Budget for a single `--version` candidate probe, ms. */
41
- const PROBE_TIMEOUT_MS = 5_000;
42
- const MAX_SG_OUTPUT_BYTES = 8 * 1024 * 1024;
43
- /**
44
- * Format metavariable captures for display below a match line.
45
- * Single captures: $VAR=x $NAME=foo
46
- * Multi captures: $$$ARGS=a,b,c
47
- * Returns undefined when there are no meaningful captures.
48
- */
49
- function formatMetaVarCaptures(mv) {
50
- if (!mv)
51
- return undefined;
52
- const parts = [];
53
- for (const [name, node] of Object.entries(mv.single)) {
54
- if (node.text)
55
- parts.push(`$${name}=${node.text}`);
56
- }
57
- for (const [name, nodes] of Object.entries(mv.multi)) {
58
- if (nodes.length > 0) {
59
- const joined = nodes.map((n) => n.text).join("");
60
- if (joined)
61
- parts.push(`$$$${name}=${joined}`);
62
- }
63
- }
64
- for (const [name, value] of Object.entries(mv.transformed)) {
65
- if (value)
66
- parts.push(`@${name}=${value}`);
67
- }
68
- if (parts.length === 0)
69
- return undefined;
70
- return ` ${parts.join(" ")}`;
71
- }
72
- /**
73
- * Parse ast-grep `--json` stdout into a match array, or return `null` when the
74
- * text is not a match payload. ast-grep emits either a JSON array or (rarely) a
75
- * single object; both are normalized to an array. A JSON scalar/`null` is
76
- * rejected (returns `null`) so a hypothetical error-report scalar on stdout can
77
- * never be misread as a phantom match.
78
- */
79
- function tryParseSgMatches(stdout) {
80
- try {
81
- const parsed = JSON.parse(stdout);
82
- if (Array.isArray(parsed))
83
- return parsed;
84
- if (parsed !== null && typeof parsed === "object")
85
- return [parsed];
86
- return null;
87
- }
88
- catch {
89
- return null;
90
- }
91
- }
92
- // The truncation veto lives at the head of `exec`/`interpretScanResult` now
93
- // (#2100), so this stays a pure parser rather than re-asking a settled question.
94
- function tryParseNonZeroSgMatches(result) {
95
- if (!result.stdout.trim())
96
- return null;
97
- // Trimmed on purpose: a leading BOM survives JSON.parse's whitespace
98
- // tolerance but String.trim() strips it (matches the pre-refactor guard).
99
- return tryParseSgMatches(result.stdout.trim());
100
- }
101
- export class SgRunner {
102
- log;
103
- sgPath = null;
104
- sgArgsPrefix = [];
105
- /**
106
- * Availability memo, backed by the shared transient-aware latch (#1476).
107
- *
108
- * Before this, one failed sweep over every candidate — timeouts included —
109
- * latched `false` for the life of the process AND ran a full auto-install
110
- * first. A host stall during warm-up therefore both disabled ast-grep until
111
- * restart and paid for an install nobody needed.
112
- */
113
- availabilityLatch = createAvailabilityLatch();
114
- /**
115
- * At-most-one sweep in flight, via the shared primitive (#1753). One
116
- * instance owns one question, so the key is a constant.
117
- */
118
- ensureFlight = createSingleFlight();
119
- /**
120
- * Whether a DIRECT candidate — one that would have been ast-grep itself —
121
- * failed for a transient reason in the current sweep. Only these block the
122
- * install: a slow `npx` says nothing about whether ast-grep is on this
123
- * machine, and letting it block turned "ast-grep is genuinely absent on a
124
- * slow host" into "ast-grep is never installed and npx is re-spawned every
125
- * sweep, forever".
126
- */
127
- sweepSawTransient = false;
128
- sweepTransientCause = "probe-timeout";
129
- /**
130
- * The DIRECT candidates that were unreachable, in ask order (#1568).
131
- *
132
- * The sweep returns at the first candidate that answers, so at the moment of
133
- * a win this is exactly the set of preferred tiers the winner did not beat on
134
- * the merits. Fallbacks are excluded on purpose: `npx` is asked before the
135
- * global-bin and platform-package tiers, and a slow `npx` says nothing about
136
- * a winner that is a real binary.
137
- *
138
- * Basenames, because a candidate can be an absolute global-bin or
139
- * platform-package path and this list is written to latency.log (#1568
140
- * review F3).
141
- */
142
- sweepUnreachable = [];
143
- /** A transient on the npx fallback: not evidence, but not nothing either. */
144
- sweepFallbackTransient = false;
145
- sweepFallbackCause = "probe-timeout";
146
- /** Host stall summed over every probe of the current sweep, ms. */
147
- sweepHostStallMs = 0;
148
- /**
149
- * Candidates — direct or fallback — that this sweep probed and found
150
- * DURABLY missing (real ENOENT/non-installable, not a stall) (#1593). The
151
- * retained-arm fallback below only sees whether SOME candidate stalled; this
152
- * list lets it also check whether the memoized winner itself is one of the
153
- * candidates this very sweep just disproved, rather than re-serving a
154
- * command that ENOENTed a moment ago because an unrelated sibling stalled.
155
- */
156
- sweepDurablyMissing = [];
157
- constructor(verbose = false) {
158
- this.log = verbose ? createSubsystemLogger("sg-runner") : () => { };
159
- }
160
- /**
161
- * Check if ast-grep CLI is available, auto-install if not.
162
- *
163
- * Re-entrancy safe: concurrent first-time callers share one flight, so
164
- * probing/auto-install isn't duplicated across session-start tasks. The
165
- * share and the clear-in-finally belong to `singleFlight` (#1753); this
166
- * method owns only the latch short-circuit above it. The sweep-local
167
- * bookkeeping `doEnsureAvailable` resets on entry is untouched — it is
168
- * per-sweep state, not concurrency state.
169
- */
170
- async ensureAvailable() {
171
- // Fast path: already decided. `read()` returns null when the last verdict
172
- // was transient and its cooldown expired, which re-enters the sweep.
173
- const memo = this.availabilityLatch.read();
174
- if (memo !== null)
175
- return memo;
176
- return this.ensureFlight.run("ast-grep", () => this.doEnsureAvailable());
177
- }
178
- async doEnsureAvailable() {
179
- const startedAt = Date.now();
180
- this.sweepSawTransient = false;
181
- this.sweepTransientCause = "probe-timeout";
182
- this.sweepUnreachable = [];
183
- this.sweepFallbackTransient = false;
184
- this.sweepFallbackCause = "probe-timeout";
185
- this.sweepHostStallMs = 0;
186
- this.sweepDurablyMissing = [];
187
- // Step 1: PATH — canonical binary names + npx fallback.
188
- // Prefer ast-grep over sg on Linux: /usr/bin/sg is util-linux, not ast-grep.
189
- const pathCommand = await this.probeCommandCandidates([
190
- { cmd: "ast-grep", argsPrefix: [] },
191
- { cmd: "sg", argsPrefix: [] },
192
- // `npx --no -- ast-grep` starts a Node process before it can answer, so
193
- // it is the candidate most likely to blow a 5 s budget on a cold or busy
194
- // box. Marked a fallback so its timeout cannot veto the install.
195
- { cmd: "npx", argsPrefix: ["--no", "--", "ast-grep"], fallback: true },
196
- ]);
197
- if (pathCommand) {
198
- this.sgPath = pathCommand.cmd;
199
- this.sgArgsPrefix = pathCommand.argsPrefix;
200
- this.noteAvailable(startedAt, `ast-grep found on PATH: ${pathCommand.cmd}`);
201
- return true;
202
- }
203
- // Step 1b: any package manager's global bin dir (npm/pnpm/yarn/bun).
204
- // Catches a `pnpm add -g @ast-grep/cli` shim whose bin dir is off PATH (so
205
- // Step 1 misses) and which is a global — not a local node_modules platform
206
- // package — so Step 2 misses it too (#375).
207
- for (const name of ["ast-grep", "sg"]) {
208
- const globalBin = await findGlobalBinary(name);
209
- if (globalBin && (await this.probeCommand(globalBin, []))) {
210
- this.sgPath = globalBin;
211
- this.sgArgsPrefix = [];
212
- this.noteAvailable(startedAt, `ast-grep found in global bin: ${globalBin}`);
213
- return true;
214
- }
215
- }
216
- // Step 2: platform-specific npm package binaries.
217
- // Covers setups where @ast-grep/cli-{os}-{arch} is installed but the binary
218
- // directory is not on PATH (common with pnpm, Yarn PnP, or isolated installs).
219
- const platformBinary = await this.probePlatformPackageBinary();
220
- if (platformBinary) {
221
- this.sgPath = platformBinary;
222
- this.sgArgsPrefix = [];
223
- this.noteAvailable(startedAt, `ast-grep found via platform package: ${platformBinary}`);
224
- return true;
225
- }
226
- // Step 3: Homebrew (macOS only).
227
- if (process.platform === "darwin") {
228
- const brewBinary = await this.probeHomebrew();
229
- if (brewBinary) {
230
- this.sgPath = brewBinary;
231
- this.sgArgsPrefix = [];
232
- this.noteAvailable(startedAt, `ast-grep found via Homebrew: ${brewBinary}`);
233
- return true;
234
- }
235
- }
236
- // A timeout on a DIRECT candidate means the machine answered, not the tool
237
- // (#1476). Installing ast-grep because the host event loop stalled is a
238
- // heavyweight reaction to a hiccup, and latching the result disabled the
239
- // tool until restart. Retry the sweep later instead.
240
- //
241
- // The npx fallback is deliberately NOT part of this test. Gating the
242
- // install on it regressed the very host this change targets: a slow box
243
- // with no ast-grep timed out `npx --no -- ast-grep` every sweep, so the
244
- // install below was never reached and the slow npx was re-spawned on each
245
- // escalating retry instead — worse than the latch it replaced.
246
- if (this.sweepSawTransient) {
247
- // Nothing answered, transiently, while we are still holding a command a
248
- // previous provisional sweep proved working. Discarding it here would
249
- // run #1476 backwards — a timeout erasing a positive result — and it
250
- // would send the sweep on to Step 4 to install a tool that is already
251
- // runnable. Keep the winner and re-arm the cooldown (#1568 review F1).
252
- // #1593: a sibling tier stalling this sweep is not license to re-serve
253
- // a winner that THIS SAME sweep just proved durably missing.
254
- if (this.availabilityLatch.isProvisional() &&
255
- this.sgPath &&
256
- !this.sweepDurablyMissing.includes(this.sgPath)) {
257
- this.noteAvailable(startedAt, `ast-grep re-probe stalled; keeping ${this.sgPath}`, { retained: true });
258
- return true;
259
- }
260
- this.log("ast-grep availability probe timed out; will retry (not installing)");
261
- return this.noteUnavailable(startedAt, "transient", this.sweepTransientCause);
262
- }
263
- // Step 4: install via the typed shared seam, then validate the returned
264
- // absolute binary before publishing it.
265
- const installed = await resolveManagedToolClient({
266
- toolId: "ast-grep",
267
- cwd: process.cwd(),
268
- probe: async () => ({ outcome: "missing" }),
269
- acceptInstalled: async (installedPath) => (await this.probeCommand(installedPath, [])) ? installedPath : null,
270
- });
271
- if (installed.outcome === "success") {
272
- this.sgPath = installed.value;
273
- this.sgArgsPrefix = [];
274
- this.noteAvailable(startedAt, `ast-grep auto-installed: ${installed.value}`);
275
- return true;
276
- }
277
- // The install failed AND the npx fallback never got a fair hearing, so
278
- // "ast-grep is not installed" is not a fact yet. Expire the verdict.
279
- if (this.sweepFallbackTransient) {
280
- return this.noteUnavailable(startedAt, "transient", this.sweepFallbackCause);
281
- }
282
- // #1500: ASSERTED, not derived — and justified, because every candidate
283
- // probe answered "not found", so the absence is real. What the install did
284
- // is recorded as evidence rather than folded into the verdict: it is the one
285
- // fact separating "never installable here" from "the download failed", and
286
- // from "no install was attempted at all" (auto-install off, trust denied,
287
- // or an attempt already suppressed this session).
288
- const { getInstallAttempt } = await import("./installer/index.js");
289
- return this.noteUnavailable(startedAt, "missing", "not-found", describeInstallAttempt(getInstallAttempt("ast-grep"), {
290
- installedButRejected: installed.outcome === "non-installable",
291
- }), "caller");
292
- }
293
- /**
294
- * Record a successful sweep, with one decision record.
295
- *
296
- * The win is latched for the session UNLESS a direct candidate ahead of it
297
- * was unreachable (#1568). `sweepSawTransient` is set only by candidates the
298
- * sweep already asked, and the sweep returns at the first one that answers,
299
- * so at this point the flag means "a tier the winner is supposed to lose to
300
- * never got a fair hearing". Latching that pinned the session to
301
- * `npx --no -- ast-grep` — a Node start per invocation — over a healthy
302
- * ast-grep on PATH, until the next restart. Provisional instead: served now,
303
- * re-swept once the stalled tier's cooldown expires.
304
- *
305
- * The install arm cannot reach here provisionally: `doEnsureAvailable`
306
- * returns before Step 4 whenever `sweepSawTransient` is set.
307
- *
308
- * `retained` marks the other provisional case (#1568 review F1): no candidate
309
- * answered at all, and the winner being reported is the one the previous
310
- * sweep found, kept rather than discarded on a timeout.
311
- */
312
- noteAvailable(startedAt, message, opts = {}) {
313
- const provisional = this.sweepSawTransient;
314
- let retryAfterMs = 0;
315
- if (provisional) {
316
- retryAfterMs = this.availabilityLatch.noteProvisionallyAvailable(this.sweepTransientCause);
317
- }
318
- else {
319
- this.availabilityLatch.noteAvailable();
320
- }
321
- this.log(message);
322
- logAvailabilityDecision({
323
- tool: "ast-grep",
324
- verdict: "available",
325
- outcome: "success",
326
- cause: provisional ? this.sweepTransientCause : "ok",
327
- elapsedMs: Date.now() - startedAt,
328
- latched: !provisional,
329
- classifiedBy: "probe",
330
- hostStallMs: this.sweepHostStallMs,
331
- budgetMs: PROBE_TIMEOUT_MS,
332
- ...(provisional && {
333
- provisional: true,
334
- unreachablePreferred: [...this.sweepUnreachable],
335
- ...(opts.retained === true && { retained: true }),
336
- ...(retryAfterMs > 0 && { retryAfterMs }),
337
- }),
338
- });
339
- }
340
- /**
341
- * Record a failed sweep. A `transient` outcome expires after a bounded
342
- * cooldown; anything else is remembered for the session, as before. The
343
- * elapsed/stall numbers describe the WHOLE sweep, because the verdict is
344
- * about the sweep rather than any single candidate — reporting zero here
345
- * would erase the evidence that cracked #1467.
346
- */
347
- noteUnavailable(startedAt, outcome, cause, evidence,
348
- /**
349
- * How this arm reached its verdict. Per arm on purpose (#1500 review): the
350
- * sweep's own transient/missing conclusions ARE derived from candidate
351
- * probes, but the post-install assertion is not, and hardcoding `"probe"`
352
- * here labelled an assertion as a derivation — the exact confusion the
353
- * field was added to remove.
354
- */
355
- classifiedBy = "probe") {
356
- const retryAfterMs = this.availabilityLatch.noteUnavailable(outcome, cause);
357
- logAvailabilityDecision({
358
- tool: "ast-grep",
359
- verdict: "unavailable",
360
- outcome,
361
- cause,
362
- elapsedMs: Date.now() - startedAt,
363
- latched: outcome !== "transient",
364
- hostStallMs: this.sweepHostStallMs,
365
- ...(retryAfterMs > 0 && { retryAfterMs }),
366
- budgetMs: PROBE_TIMEOUT_MS,
367
- classifiedBy,
368
- ...(evidence !== undefined && { evidence }),
369
- });
370
- return false;
371
- }
372
- /**
373
- * Probe platform-specific @ast-grep/cli-{os}-{arch} npm packages.
374
- * These ship the binary at the package root (sg / sg.exe).
375
- */
376
- async probePlatformPackageBinary() {
377
- const { platform, arch } = process;
378
- const exeName = platform === "win32" ? "sg.exe" : "sg";
379
- // Map Node.js platform/arch to @ast-grep/cli package suffix.
380
- const pkgSuffixes = [];
381
- if (platform === "linux" && arch === "x64")
382
- pkgSuffixes.push("linux-x64-gnu");
383
- if (platform === "linux" && arch === "arm64")
384
- pkgSuffixes.push("linux-arm64-gnu");
385
- if (platform === "darwin" && arch === "arm64")
386
- pkgSuffixes.push("darwin-arm64");
387
- if (platform === "darwin" && arch === "x64")
388
- pkgSuffixes.push("darwin-x64");
389
- if (platform === "win32" && arch === "x64")
390
- pkgSuffixes.push("win32-x64-msvc");
391
- if (platform === "win32" && arch === "arm64")
392
- pkgSuffixes.push("win32-arm64-msvc");
393
- // Search roots: local node_modules and any parent node_modules directories.
394
- const searchRoots = [];
395
- let dir = process.cwd();
396
- for (let depth = 0; depth < 5; depth++) {
397
- searchRoots.push(path.join(dir, "node_modules"));
398
- const parent = path.dirname(dir);
399
- if (parent === dir)
400
- break;
401
- dir = parent;
402
- }
403
- for (const suffix of pkgSuffixes) {
404
- const pkgName = `@ast-grep/cli-${suffix}`;
405
- for (const root of searchRoots) {
406
- const candidate = path.join(root, pkgName, exeName);
407
- try {
408
- if (fs.existsSync(candidate) &&
409
- (await this.probeCommand(candidate, []))) {
410
- return candidate;
411
- }
412
- }
413
- catch {
414
- // not found or not executable — try next
415
- }
416
- }
417
- }
418
- return undefined;
419
- }
420
- /**
421
- * Probe Homebrew installation (macOS only).
422
- * Runs `brew --prefix ast-grep` and checks the resulting bin directory.
423
- */
424
- async probeHomebrew() {
425
- try {
426
- const result = await safeSpawnAsync("brew", ["--prefix", "ast-grep"], {
427
- timeout: 3000,
428
- });
429
- if (result.error || result.status !== 0)
430
- return undefined;
431
- const prefix = result.stdout.trim();
432
- if (!prefix)
433
- return undefined;
434
- for (const name of ["ast-grep", "sg"]) {
435
- const candidate = path.join(prefix, "bin", name);
436
- if (fs.existsSync(candidate) &&
437
- (await this.probeCommand(candidate, []))) {
438
- return candidate;
439
- }
440
- }
441
- }
442
- catch {
443
- // brew not installed or timed out
444
- }
445
- return undefined;
446
- }
447
- isAstGrepVersionOutput(output) {
448
- return /\bast[- ]grep\b/i.test(output);
449
- }
450
- /**
451
- * Probe one candidate. A failure is CLASSIFIED, not merely counted: a
452
- * timeout/abort marks the whole sweep transient so the caller retries later
453
- * instead of installing and latching. A command that answers but is not
454
- * ast-grep (Linux `/usr/bin/sg` is util-linux) is a durable no.
455
- */
456
- async probeCommand(cmd, argsPrefix, fallback = false) {
457
- // Host-side budget: measure the loop stall that overlapped the window so
458
- // the classifier can tell "the tool is slow" from "the host was busy".
459
- const sampler = startHostStallSampler();
460
- let result;
461
- let hostStallMs;
462
- try {
463
- result = await probeToolAsync(cmd, [...argsPrefix, "--version"], {
464
- timeout: PROBE_TIMEOUT_MS,
465
- });
466
- }
467
- finally {
468
- hostStallMs = sampler.stop();
469
- this.sweepHostStallMs += hostStallMs;
470
- }
471
- if (!result.error &&
472
- result.status === 0 &&
473
- this.isAstGrepVersionOutput(`${result.stdout}\n${result.stderr}`)) {
474
- return true;
475
- }
476
- const { outcome, cause } = classifyProbeFailure(result, { hostStallMs });
477
- if (outcome === "transient") {
478
- if (fallback) {
479
- this.sweepFallbackTransient = true;
480
- this.sweepFallbackCause = cause;
481
- }
482
- else {
483
- this.sweepSawTransient = true;
484
- this.sweepTransientCause = cause;
485
- const name = path.basename(cmd);
486
- if (!this.sweepUnreachable.includes(name)) {
487
- this.sweepUnreachable.push(name);
488
- }
489
- }
490
- }
491
- else if (!this.sweepDurablyMissing.includes(cmd)) {
492
- this.sweepDurablyMissing.push(cmd);
493
- }
494
- return false;
495
- }
496
- async probeCommandCandidates(candidates) {
497
- for (const candidate of candidates) {
498
- if (await this.probeCommand(candidate.cmd, candidate.argsPrefix, candidate.fallback ?? false)) {
499
- return candidate;
500
- }
501
- }
502
- return undefined;
503
- }
504
- /**
505
- * Get the ast-grep command to use, plus any npx prefix arguments.
506
- */
507
- getSgCommand() {
508
- return {
509
- cmd: this.sgPath || "ast-grep",
510
- argsPrefix: this.sgArgsPrefix,
511
- };
512
- }
513
- failureForSpawnResult(result) {
514
- if (result.failure === "aborted")
515
- return "aborted";
516
- switch (result.spawnFailure?.kind) {
517
- case "tool-not-found":
518
- return "unavailable";
519
- case "timeout":
520
- return "timeout";
521
- case "killed":
522
- return result.failure === "aborted" ? "aborted" : "cli-failure";
523
- case "cwd-unresolvable":
524
- case "permission-denied":
525
- case "spawn-failed":
526
- case undefined:
527
- return result.error ? "cli-failure" : undefined;
528
- }
529
- }
530
- formatPatternError(stderr, args) {
531
- if (stderr.includes("Multiple AST nodes are detected")) {
532
- return (`Invalid AST pattern: The pattern appears to contain multiple AST nodes or is malformed.\n` +
533
- `Common causes:\n` +
534
- ` 1. Missing parentheses: use it($TEST) not it"test"\n` +
535
- ` 2. Raw text without structure: use console.log($MSG) not just "console.log"\n` +
536
- ` 3. Unclosed quotes or brackets\n\n` +
537
- `Original error: ${stderr}`);
538
- }
539
- if (stderr.includes("Cannot parse query")) {
540
- return (`Pattern syntax error: The pattern could not be parsed as valid code.\n` +
541
- `Tips:\n` +
542
- ` - Patterns must be valid ${args.includes("--lang") ? args[args.indexOf("--lang") + 1] : "language"} syntax\n` +
543
- ` - Use metavariables like $NAME, $ARGS for variable parts\n` +
544
- ` - Example: 'function $NAME($$$PARAMS) { $$$BODY }'\n\n` +
545
- `Original error: ${stderr}`);
546
- }
547
- return stderr;
548
- }
549
- async execRaw(args, timeout = DEFAULT_EXEC_TIMEOUT_MS, options = {}) {
550
- const command = this.getSgCommand();
551
- const result = await safeSpawnAsync(command.cmd, [...command.argsPrefix, ...args], {
552
- timeout,
553
- deadlineAt: options.deadlineAt,
554
- signal: options.signal,
555
- maxOutputBytes: MAX_SG_OUTPUT_BYTES,
556
- });
557
- const failure = this.failureForSpawnResult(result);
558
- return {
559
- stdout: result.stdout,
560
- stderr: result.stderr,
561
- status: result.status,
562
- error: result.error?.message,
563
- failure,
564
- ...(result.outputTruncated ? { outputTruncated: true } : {}),
565
- };
566
- }
567
- /**
568
- * Run ast-grep asynchronously, return parsed matches. The Windows Git
569
- * Bash/MSYS path deliberately remains positional-argument based, but the
570
- * child itself now goes through safeSpawnAsync so cancellation, deadlines,
571
- * tree-kill, and output caps are shared with every other CLI runner.
572
- */
573
- async exec(args, options = {}) {
574
- const command = this.getSgCommand();
575
- const allArgs = [...command.argsPrefix, ...args];
576
- const isWindows = process.platform === "win32";
577
- const hasBash = Boolean(process.env.MSYSTEM || process.env.GIT_SHELL);
578
- const useBash = isWindows && hasBash;
579
- const result = await safeSpawnAsync(useBash ? "bash" : command.cmd, useBash ? buildBashRunArgs(command.cmd, allArgs) : allArgs, {
580
- timeout: DEFAULT_EXEC_TIMEOUT_MS,
581
- deadlineAt: options.deadlineAt,
582
- signal: options.signal,
583
- maxOutputBytes: MAX_SG_OUTPUT_BYTES,
584
- });
585
- const empty = () => ({
586
- matches: [],
587
- totalMatches: 0,
588
- truncated: false,
589
- });
590
- // FIRST (#2100): hitting the cap makes safe-spawn SIGTERM ast-grep, so a
591
- // truncated run also classifies as a killed spawn. Read after the two
592
- // gates below, this guard could only ever describe a run that exited
593
- // before the signal reached it. A timed-out or aborted run carries the
594
- // flag too and keeps its own classification below.
595
- if (truncatedByOutputCap(result)) {
596
- return {
597
- ...empty(),
598
- error: "Failed to parse output: output was truncated",
599
- };
600
- }
601
- const spawnFailure = this.failureForSpawnResult(result);
602
- if (spawnFailure) {
603
- return {
604
- ...empty(),
605
- error: spawnFailure === "unavailable"
606
- ? "ast-grep CLI not found. Install: npm i -D @ast-grep/cli"
607
- : result.error?.message || "ast-grep CLI failed to start",
608
- };
609
- }
610
- if (result.status !== 0) {
611
- const stdout = result.stdout.trim();
612
- const stderr = result.stderr.trim();
613
- // ast-grep's linter-style contract: a rule with `severity: error`
614
- // that MATCHES exits 1 with valid JSON matches on stdout (stderr
615
- // carries "Scan succeeded and found error level diagnostics"). An
616
- // exit code that means "scan succeeded with findings" must never be
617
- // classified as a CLI failure — parse the matches. Only fall through
618
- // to failure when the JSON isn't parseable (a real diagnostic).
619
- if (result.status === 1 && stdout) {
620
- const matches = tryParseNonZeroSgMatches(result);
621
- if (matches) {
622
- return {
623
- matches,
624
- totalMatches: matches.length,
625
- truncated: false,
626
- };
627
- }
628
- }
629
- // ast-grep uses status 1 with no output for a genuine no-match in
630
- // some CLI versions. Preserve that historical empty-result behavior;
631
- // any stderr (including an invalid kind/YAML diagnostic) is a failure.
632
- if (result.status === 1 && !stdout && !stderr)
633
- return empty();
634
- return {
635
- ...empty(),
636
- error: this.formatPatternError(stderr || `Command failed with exit code ${result.status}`, args),
637
- };
638
- }
639
- if (!result.stdout.trim())
640
- return empty();
641
- {
642
- const matches = tryParseSgMatches(result.stdout);
643
- if (matches) {
644
- return {
645
- matches,
646
- totalMatches: matches.length,
647
- truncated: false,
648
- };
649
- }
650
- return { ...empty(), error: "Failed to parse output" };
651
- }
652
- }
653
- // --- Shared helpers for temp-dir rule scans ---
654
- prepareTempScan(ruleId, ruleYaml) {
655
- const sessionDir = fs.mkdtempSync(path.join(os.tmpdir(), `pi-lens-temp-${ruleId}-`));
656
- try {
657
- const rulesSubdir = path.join(sessionDir, "rules");
658
- const configFile = path.join(sessionDir, ".sgconfig.yml");
659
- fs.mkdirSync(rulesSubdir, { recursive: true });
660
- fs.writeFileSync(configFile, `ruleDirs:\n - ./rules\n`);
661
- fs.writeFileSync(path.join(rulesSubdir, `${ruleId}.yml`), ruleYaml);
662
- return { sessionDir, configFile };
663
- }
664
- catch (error) {
665
- this.cleanupTempScan(sessionDir);
666
- throw error;
667
- }
668
- }
669
- cleanupTempScan(sessionDir) {
670
- try {
671
- fs.rmSync(sessionDir, { recursive: true, force: true });
672
- }
673
- catch (err) {
674
- this.log(`Cleanup failed: ${err.message}`);
675
- }
676
- }
677
- interpretScanResult(result, args) {
678
- // FIRST (#2100): same reason as `exec` above — the cap kill is our own
679
- // SIGTERM, so `failureForSpawnResult` answers "cli-failure" for it and
680
- // the truncation guard below never speaks.
681
- if (truncatedByOutputCap(result)) {
682
- return {
683
- matches: [],
684
- status: result.status,
685
- error: "Failed to parse ast-grep scan output: output was truncated",
686
- failure: "parse-failure",
687
- };
688
- }
689
- const failure = this.failureForSpawnResult(result);
690
- if (failure) {
691
- return {
692
- matches: [],
693
- status: result.status,
694
- error: result.error?.message || "ast-grep CLI failed to start",
695
- failure,
696
- };
697
- }
698
- if (result.status !== 0) {
699
- const stdout = result.stdout.trim();
700
- const stderr = result.stderr.trim();
701
- // ast-grep's linter-style contract: a rule with `severity: error`
702
- // that MATCHES exits 1 with valid JSON matches on stdout (stderr
703
- // carries "Scan succeeded and found error level diagnostics"). An
704
- // exit code that means "scan succeeded with findings" must never be
705
- // classified as a CLI failure — parse the matches. Only fall through
706
- // to failure when the JSON isn't parseable (a real diagnostic).
707
- if (result.status === 1 && stdout) {
708
- const matches = tryParseNonZeroSgMatches(result);
709
- if (matches) {
710
- return { matches, status: result.status };
711
- }
712
- }
713
- // Preserve ast-grep's status-1/no-output no-match convention. A
714
- // diagnostic on stderr is never treated as a no-match.
715
- if (result.status === 1 && !stdout && !stderr) {
716
- return { matches: [], status: result.status };
717
- }
718
- return {
719
- matches: [],
720
- status: result.status,
721
- error: this.formatPatternError(stderr || `ast-grep scan failed with exit code ${result.status}`, args),
722
- failure: "cli-failure",
723
- };
724
- }
725
- if (!result.stdout.trim())
726
- return { matches: [], status: result.status };
727
- {
728
- const matches = tryParseSgMatches(result.stdout);
729
- if (matches) {
730
- return { matches, status: result.status };
731
- }
732
- return {
733
- matches: [],
734
- status: result.status,
735
- error: "Failed to parse ast-grep scan output: invalid JSON",
736
- failure: "parse-failure",
737
- };
738
- }
739
- }
740
- async tempScanDetailedAsync(dir, ruleId, ruleYaml, timeout = DEFAULT_EXEC_TIMEOUT_MS, options = {}) {
741
- let sessionDir;
742
- try {
743
- ({ sessionDir } = this.prepareTempScan(ruleId, ruleYaml));
744
- const configFile = path.join(sessionDir, ".sgconfig.yml");
745
- const { cmd: sgCmd, args: sgPre } = getSgCommand();
746
- const result = await safeSpawnAsync(sgCmd, [
747
- ...sgPre,
748
- "scan",
749
- "--config",
750
- configFile,
751
- "--json",
752
- ...sgExcludeArgsForProject(dir),
753
- dir,
754
- ], {
755
- timeout,
756
- deadlineAt: options.deadlineAt,
757
- signal: options.signal,
758
- maxOutputBytes: MAX_SG_OUTPUT_BYTES,
759
- });
760
- return this.interpretScanResult(result, ["scan"]);
761
- }
762
- finally {
763
- if (sessionDir)
764
- this.cleanupTempScan(sessionDir);
765
- }
766
- }
767
- /** Backwards-compatible match-only wrapper for existing session scanners. */
768
- async tempScanAsync(dir, ruleId, ruleYaml, timeout = DEFAULT_EXEC_TIMEOUT_MS, options = {}) {
769
- const result = await this.tempScanDetailedAsync(dir, ruleId, ruleYaml, timeout, options);
770
- return result.matches;
771
- }
772
- /**
773
- * Run a rule scan with optional fix application.
774
- * Dry-run: --json (returns matches for preview).
775
- * Apply: --update-all (writes fixes defined in the YAML `fix:` field).
776
- */
777
- async tempScanWithFixAsync(dir, ruleId, ruleYaml, applyFixes, timeout = DEFAULT_EXEC_TIMEOUT_MS, options = {}) {
778
- let sessionDir;
779
- try {
780
- ({ sessionDir } = this.prepareTempScan(ruleId, ruleYaml));
781
- const configFile = path.join(sessionDir, ".sgconfig.yml");
782
- const { cmd: sgCmd, args: sgPre } = getSgCommand();
783
- const scanArgs = [
784
- ...sgPre,
785
- "scan",
786
- "--config",
787
- configFile,
788
- "--json",
789
- ...sgExcludeArgsForProject(dir),
790
- dir,
791
- ];
792
- const spawnOptions = {
793
- timeout,
794
- deadlineAt: options.deadlineAt,
795
- signal: options.signal,
796
- maxOutputBytes: MAX_SG_OUTPUT_BYTES,
797
- };
798
- if (!applyFixes) {
799
- const result = await safeSpawnAsync(sgCmd, scanArgs, spawnOptions);
800
- const scan = this.interpretScanResult(result, ["scan"]);
801
- return scan.failure || scan.error
802
- ? { matches: [], error: scan.error }
803
- : { matches: scan.matches };
804
- }
805
- // Apply: capture matches BEFORE writing — once --update-all applies
806
- // the fix the rule no longer matches, so a post-apply json pass would
807
- // report zero even on a successful apply. Count first, then write.
808
- const jsonResult = await safeSpawnAsync(sgCmd, scanArgs, spawnOptions);
809
- const scan = this.interpretScanResult(jsonResult, ["scan"]);
810
- if (scan.failure || scan.error) {
811
- return { matches: [], error: scan.error };
812
- }
813
- const applyResult = await safeSpawnAsync(sgCmd, [
814
- ...sgPre,
815
- "scan",
816
- "--config",
817
- configFile,
818
- "--update-all",
819
- ...sgExcludeArgsForProject(dir),
820
- dir,
821
- ], spawnOptions);
822
- if (applyResult.error ||
823
- applyResult.failure ||
824
- applyResult.status !== 0) {
825
- return {
826
- matches: [],
827
- error: applyResult.error?.message ||
828
- `ast-grep apply failed with exit code ${applyResult.status}`,
829
- };
830
- }
831
- return { matches: scan.matches };
832
- }
833
- catch (err) {
834
- return { matches: [], error: String(err) };
835
- }
836
- finally {
837
- if (sessionDir)
838
- this.cleanupTempScan(sessionDir);
839
- }
840
- }
841
- /**
842
- * Format matches for display
843
- */
844
- formatMatches(matches, isDryRun = false, maxItems = 50, showModeIndicator = false) {
845
- if (matches.length === 0) {
846
- if (showModeIndicator) {
847
- return isDryRun
848
- ? "[DRY-RUN] No matches found."
849
- : "[NOT APPLIED] No matches found — nothing was changed. Run ast_grep_search to confirm the pattern matches before applying.";
850
- }
851
- return "No matches found";
852
- }
853
- const shown = matches.slice(0, maxItems);
854
- const lines = shown.map((m) => {
855
- const loc = `${m.file}:${m.range.start.line + 1}:${m.range.start.column + 1}`;
856
- const text = m.text.length > 100 ? `${m.text.slice(0, 100)}...` : m.text;
857
- const langSuffix = m.language ? ` [${m.language}]` : "";
858
- const base = isDryRun && m.replacement
859
- ? `${loc}\n - ${text}\n + ${m.replacement}`
860
- : `${loc}: ${text}${langSuffix}`;
861
- const captures = formatMetaVarCaptures(m.metaVariables);
862
- return captures ? `${base}\n${captures}` : base;
863
- });
864
- if (matches.length > maxItems) {
865
- lines.unshift(`Found ${matches.length} matches (showing first ${maxItems}):`);
866
- }
867
- if (showModeIndicator) {
868
- const prefix = isDryRun ? "[DRY-RUN]" : "[APPLIED]";
869
- const suffix = isDryRun
870
- ? "\n\n(Dry run — use apply=true to apply changes)"
871
- : "";
872
- return `${prefix} ${matches.length} replacement(s):\n\n${lines.join("\n")}${suffix}`;
873
- }
874
- return lines.join("\n");
875
- }
876
- }