pi-lens 4.1.2 → 4.1.4

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 (359) hide show
  1. package/CHANGELOG.md +1308 -0
  2. package/README.md +3 -0
  3. package/config/biome/core.jsonc +11 -2
  4. package/config/dependency-cruiser-eager-allowlist.json +91 -0
  5. package/dist/clients/actionable-warnings-logger.js +18 -3
  6. package/dist/clients/actionable-warnings.js +927 -80
  7. package/dist/clients/agent-behavior-client.js +13 -4
  8. package/dist/clients/ast-grep-client.js +136 -9
  9. package/dist/clients/ast-grep-rule-manager.js +60 -5
  10. package/dist/clients/ast-grep-tool-logger.js +2 -2
  11. package/dist/clients/availability-probe-flight.js +12 -0
  12. package/dist/clients/bash-file-access.js +1 -3
  13. package/dist/clients/biome-client.js +16 -2
  14. package/dist/clients/blocker-freshness.js +34 -0
  15. package/dist/clients/bootstrap.js +509 -73
  16. package/dist/clients/bounded-cache.js +152 -12
  17. package/dist/clients/bounded-telemetry.js +115 -23
  18. package/dist/clients/build-identity.js +95 -0
  19. package/dist/clients/bundled-resource-health.js +113 -0
  20. package/dist/clients/bus-events-logger.js +2 -2
  21. package/dist/clients/bus-publish.js +3 -0
  22. package/dist/clients/cache/rule-cache.js +33 -5
  23. package/dist/clients/cache-manager.js +105 -6
  24. package/dist/clients/cache-observability.js +46 -19
  25. package/dist/clients/cargo-manifest.js +422 -0
  26. package/dist/clients/cascade-logger.js +17 -3
  27. package/dist/clients/code-quality-warnings.js +13 -3
  28. package/dist/clients/complexity-client.js +16 -5
  29. package/dist/clients/config-core/deny.js +221 -0
  30. package/dist/clients/config-core/index.js +47 -0
  31. package/dist/clients/config-core/merge.js +357 -0
  32. package/dist/clients/config-core/normalize.js +340 -0
  33. package/dist/clients/config-core/process-spec.js +248 -0
  34. package/dist/clients/config-core/provenance.js +171 -0
  35. package/dist/clients/config-core/records.js +218 -0
  36. package/dist/clients/config-core/resolve.js +125 -0
  37. package/dist/clients/config-core/safe-object.js +78 -0
  38. package/dist/clients/config-core/schema.js +167 -0
  39. package/dist/clients/config-diagnostic-codes.js +281 -0
  40. package/dist/clients/config-locations.js +160 -0
  41. package/dist/clients/config-resolve.js +789 -0
  42. package/dist/clients/config-schema.js +197 -0
  43. package/dist/clients/config-warn.js +407 -0
  44. package/dist/clients/dead-code-client.js +33 -25
  45. package/dist/clients/dead-code-logger.js +2 -2
  46. package/dist/clients/deadline-utils.js +178 -0
  47. package/dist/clients/debug-handles.js +2 -2
  48. package/dist/clients/debug-heap.js +3 -3
  49. package/dist/clients/deferred-lsp-work.js +106 -0
  50. package/dist/clients/degradation-ledger.js +166 -8
  51. package/dist/clients/demoted-finding-render.js +14 -0
  52. package/dist/clients/dependency-checker.js +28 -22
  53. package/dist/clients/deps/minimatch.js +1 -1
  54. package/dist/clients/diagnostic-line-freshness.js +12 -9
  55. package/dist/clients/diagnostic-logger.js +14 -4
  56. package/dist/clients/dispatch/collect-later-tier.js +39 -0
  57. package/dist/clients/dispatch/dispatcher.js +304 -19
  58. package/dist/clients/dispatch/fact-store.js +328 -9
  59. package/dist/clients/dispatch/integration.js +214 -107
  60. package/dist/clients/dispatch/pending-runner-findings.js +87 -0
  61. package/dist/clients/dispatch/plan.js +21 -5
  62. package/dist/clients/dispatch/runners/actionlint.js +0 -1
  63. package/dist/clients/dispatch/runners/ast-grep-napi.js +550 -77
  64. package/dist/clients/dispatch/runners/biome-check.js +0 -1
  65. package/dist/clients/dispatch/runners/cpp-check.js +0 -1
  66. package/dist/clients/dispatch/runners/credo.js +0 -1
  67. package/dist/clients/dispatch/runners/cue-vet.js +0 -1
  68. package/dist/clients/dispatch/runners/dart-analyze.js +0 -1
  69. package/dist/clients/dispatch/runners/detekt.js +0 -1
  70. package/dist/clients/dispatch/runners/dotnet-build.js +0 -1
  71. package/dist/clients/dispatch/runners/elixir-check.js +0 -1
  72. package/dist/clients/dispatch/runners/eslint.js +5 -2
  73. package/dist/clients/dispatch/runners/fact-rules.js +0 -1
  74. package/dist/clients/dispatch/runners/fish-indent.js +0 -1
  75. package/dist/clients/dispatch/runners/gleam-check.js +0 -1
  76. package/dist/clients/dispatch/runners/go-vet.js +1 -3
  77. package/dist/clients/dispatch/runners/golangci-lint.js +0 -1
  78. package/dist/clients/dispatch/runners/hadolint.js +0 -1
  79. package/dist/clients/dispatch/runners/helm-lint.js +16 -4
  80. package/dist/clients/dispatch/runners/helm-render.js +54 -10
  81. package/dist/clients/dispatch/runners/htmlhint.js +0 -1
  82. package/dist/clients/dispatch/runners/index.js +1 -3
  83. package/dist/clients/dispatch/runners/javac.js +0 -1
  84. package/dist/clients/dispatch/runners/ktlint.js +0 -1
  85. package/dist/clients/dispatch/runners/lsp.js +37 -2
  86. package/dist/clients/dispatch/runners/markdownlint.js +4 -2
  87. package/dist/clients/dispatch/runners/mypy.js +0 -1
  88. package/dist/clients/dispatch/runners/oxlint.js +24 -22
  89. package/dist/clients/dispatch/runners/php-lint.js +0 -1
  90. package/dist/clients/dispatch/runners/phpstan.js +0 -1
  91. package/dist/clients/dispatch/runners/prisma-validate.js +0 -1
  92. package/dist/clients/dispatch/runners/psscriptanalyzer.js +4 -1
  93. package/dist/clients/dispatch/runners/pyright.js +7 -4
  94. package/dist/clients/dispatch/runners/rubocop.js +0 -1
  95. package/dist/clients/dispatch/runners/ruff.js +0 -1
  96. package/dist/clients/dispatch/runners/rust-clippy.js +9 -6
  97. package/dist/clients/dispatch/runners/shellcheck.js +0 -1
  98. package/dist/clients/dispatch/runners/shfmt.js +0 -1
  99. package/dist/clients/dispatch/runners/spellcheck.js +0 -1
  100. package/dist/clients/dispatch/runners/spotbugs.js +0 -1
  101. package/dist/clients/dispatch/runners/sqlfluff.js +0 -1
  102. package/dist/clients/dispatch/runners/stylelint.js +0 -1
  103. package/dist/clients/dispatch/runners/swiftlint.js +0 -1
  104. package/dist/clients/dispatch/runners/taplo.js +0 -1
  105. package/dist/clients/dispatch/runners/terragrunt.js +0 -1
  106. package/dist/clients/dispatch/runners/tflint.js +0 -1
  107. package/dist/clients/dispatch/runners/tree-sitter.js +0 -1
  108. package/dist/clients/dispatch/runners/trivy-config.js +0 -1
  109. package/dist/clients/dispatch/runners/utils/availability-policy.js +3 -0
  110. package/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +3 -4
  111. package/dist/clients/dispatch/runners/utils/runner-helpers.js +270 -105
  112. package/dist/clients/dispatch/runners/utils/spawn-outcome.js +4 -0
  113. package/dist/clients/dispatch/runners/utils/tool-failure.js +19 -7
  114. package/dist/clients/dispatch/runners/utils/toolchain-availability.js +89 -26
  115. package/dist/clients/dispatch/runners/vale.js +0 -1
  116. package/dist/clients/dispatch/runners/yaml-rule-parser.js +51 -17
  117. package/dist/clients/dispatch/runners/yamllint.js +0 -1
  118. package/dist/clients/dispatch/runners/zig-check.js +0 -1
  119. package/dist/clients/disposition-logger.js +3 -3
  120. package/dist/clients/effective-config.js +403 -0
  121. package/dist/clients/error-class.js +23 -0
  122. package/dist/clients/event-loop-hold.js +274 -0
  123. package/dist/clients/event-loop-monitor.js +66 -1
  124. package/dist/clients/extension-log.js +2 -2
  125. package/dist/clients/feature-hints.js +2 -1
  126. package/dist/clients/file-time.js +6 -1
  127. package/dist/clients/file-utils.js +266 -13
  128. package/dist/clients/finding-delivery-gate.js +55 -13
  129. package/dist/clients/format-service.js +6 -1
  130. package/dist/clients/formatters.js +303 -104
  131. package/dist/clients/freshness-cadence.js +17 -0
  132. package/dist/clients/generated-artifacts.js +114 -19
  133. package/dist/clients/generation-guard.js +4 -9
  134. package/dist/clients/git-guard.js +198 -48
  135. package/dist/clients/git-tracked-ignore.js +32 -1
  136. package/dist/clients/go-client.js +39 -0
  137. package/dist/clients/govulncheck-client.js +5 -1
  138. package/dist/clients/gradle-ktfmt-style.js +252 -0
  139. package/dist/clients/gzip-stage-write.js +7 -0
  140. package/dist/clients/hashline-anchor.js +424 -0
  141. package/dist/clients/hook-budgets.js +76 -0
  142. package/dist/clients/host-edit-normalize.js +5 -2
  143. package/dist/clients/host-ports.js +1 -1
  144. package/dist/clients/installer/index.js +200 -84
  145. package/dist/clients/installer/managed-tool-refresh.js +3 -1
  146. package/dist/clients/instance-reaper.js +108 -243
  147. package/dist/clients/instance-registry-lock.js +145 -0
  148. package/dist/clients/instance-registry.js +435 -122
  149. package/dist/clients/jscpd-client.js +6 -1
  150. package/dist/clients/language-profile.js +4 -2
  151. package/dist/clients/language-registry.js +595 -0
  152. package/dist/clients/latency-logger.js +179 -3
  153. package/dist/clients/ledger-bounds.js +13 -0
  154. package/dist/clients/lens-config.js +141 -30
  155. package/dist/clients/lens-engine.js +30 -37
  156. package/dist/clients/lens-flag-registry.js +67 -6
  157. package/dist/clients/lens-map.js +3 -1
  158. package/dist/clients/log-cleanup.js +2 -2
  159. package/dist/clients/lsp/aggregation.js +3 -1
  160. package/dist/clients/lsp/cascade-tier.js +4 -0
  161. package/dist/clients/lsp/client.js +672 -49
  162. package/dist/clients/lsp/config.js +504 -156
  163. package/dist/clients/lsp/diagnostic-binding.js +10 -5
  164. package/dist/clients/lsp/edits.js +15 -2
  165. package/dist/clients/lsp/index.js +1179 -247
  166. package/dist/clients/lsp/jvm-runtime.js +5 -0
  167. package/dist/clients/lsp/language.js +18 -169
  168. package/dist/clients/lsp/launch.js +38 -9
  169. package/dist/clients/lsp/pending-aux-coverage.js +113 -12
  170. package/dist/clients/lsp/server.js +426 -160
  171. package/dist/clients/lsp/session-roots.js +192 -0
  172. package/dist/clients/lsp/spawn-history.js +2 -7
  173. package/dist/clients/lsp/tsserver-sync.js +9 -2
  174. package/dist/clients/lsp/workspace-diagnostics-cache.js +17 -7
  175. package/dist/clients/lsp/workspace-sweep-hold.js +32 -23
  176. package/dist/clients/lsp-mutation.js +179 -23
  177. package/dist/clients/map-with-concurrency.js +36 -0
  178. package/dist/clients/mcp/analyze.js +45 -10
  179. package/dist/clients/mcp/session.js +19 -17
  180. package/dist/clients/memory-sampler.js +28 -1
  181. package/dist/clients/message-end-attribution.js +30 -0
  182. package/dist/clients/metrics-history.js +21 -4
  183. package/dist/clients/middle-man-analysis.js +2 -4
  184. package/dist/clients/module-report.js +20 -45
  185. package/dist/clients/mutating-tool.js +651 -0
  186. package/dist/clients/mutation-attribution.js +368 -0
  187. package/dist/clients/mutation-bridge.js +240 -0
  188. package/dist/clients/ndjson-logger.js +247 -20
  189. package/dist/clients/observed-mutation-sources.js +101 -0
  190. package/dist/clients/observed-mutation.js +1215 -0
  191. package/dist/clients/opaque-mutation-scan.js +300 -40
  192. package/dist/clients/opengrep-config.js +4 -0
  193. package/dist/clients/package-manager.js +201 -44
  194. package/dist/clients/partial-edit-apply.js +359 -80
  195. package/dist/clients/path-attribution-telemetry.js +23 -7
  196. package/dist/clients/path-keyed-map.js +35 -3
  197. package/dist/clients/path-utils.js +460 -13
  198. package/dist/clients/persist-debounce.js +8 -1
  199. package/dist/clients/php-cs-fixer-config.js +114 -0
  200. package/dist/clients/pipeline.js +77 -24
  201. package/dist/clients/probe-home-state.js +228 -0
  202. package/dist/clients/process-bridge.js +66 -0
  203. package/dist/clients/process-singletons.js +210 -0
  204. package/dist/clients/process-snapshot.js +68 -0
  205. package/dist/clients/project-diagnostics/fresh-fetch.js +4 -3
  206. package/dist/clients/project-diagnostics/runner-adapters/madge.js +2 -1
  207. package/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +27 -3
  208. package/dist/clients/project-diagnostics/scanner.js +61 -24
  209. package/dist/clients/project-lens-config.js +393 -122
  210. package/dist/clients/project-snapshot.js +85 -35
  211. package/dist/clients/python-environment.js +217 -0
  212. package/dist/clients/python-provenance.js +639 -0
  213. package/dist/clients/quiet-window.js +6 -1
  214. package/dist/clients/read-bridge.js +13 -21
  215. package/dist/clients/read-expansion.js +9 -46
  216. package/dist/clients/read-guard-logger.js +39 -6
  217. package/dist/clients/read-guard-tool-lines.js +364 -212
  218. package/dist/clients/read-guard.js +95 -7
  219. package/dist/clients/recent-touches.js +17 -5
  220. package/dist/clients/resource-sampler.js +195 -96
  221. package/dist/clients/review-graph/builder.js +752 -132
  222. package/dist/clients/review-graph/service.js +55 -3
  223. package/dist/clients/review-graph/shared-extraction-ir.js +7 -15
  224. package/dist/clients/review-graph/tsconfig-paths.js +5 -1
  225. package/dist/clients/review-graph/workspace-modules.js +10 -74
  226. package/dist/clients/review-graph-logger.js +30 -4
  227. package/dist/clients/runtime-agent-end.js +18 -2
  228. package/dist/clients/runtime-context.js +49 -16
  229. package/dist/clients/runtime-coordinator.js +93 -6
  230. package/dist/clients/runtime-session.js +169 -34
  231. package/dist/clients/runtime-tool-call.js +183 -35
  232. package/dist/clients/runtime-tool-result.js +894 -241
  233. package/dist/clients/runtime-turn.js +1264 -74
  234. package/dist/clients/rust-client.js +31 -0
  235. package/dist/clients/safe-spawn.js +191 -28
  236. package/dist/clients/sanitize.js +7 -1
  237. package/dist/clients/scratch-tree-policy.js +1 -3
  238. package/dist/clients/security-scan-client.js +51 -17
  239. package/dist/clients/session-lifecycle.js +238 -42
  240. package/dist/clients/session-start-observability.js +79 -0
  241. package/dist/clients/sessionstart-logger.js +12 -3
  242. package/dist/clients/sg-runner.js +27 -15
  243. package/dist/clients/sgconfig.js +6 -1
  244. package/dist/clients/shared-checkout-guard.js +338 -0
  245. package/dist/clients/skills-resolver.js +105 -0
  246. package/dist/clients/smells-rollup.js +2 -2
  247. package/dist/clients/spawn-output-cap.js +37 -0
  248. package/dist/clients/spawn-timeout-cooldown.js +15 -1
  249. package/dist/clients/startup-scan.js +6 -7
  250. package/dist/clients/startup-timing.js +69 -4
  251. package/dist/clients/string-utils.js +33 -0
  252. package/dist/clients/subagent-mode.js +17 -4
  253. package/dist/clients/test-runner-client.js +586 -118
  254. package/dist/clients/test-runner-delivery.js +239 -0
  255. package/dist/clients/tool-definition.js +41 -1
  256. package/dist/clients/tool-policy.js +92 -22
  257. package/dist/clients/tree-sitter-cache.js +40 -40
  258. package/dist/clients/tree-sitter-client.js +97 -53
  259. package/dist/clients/tree-sitter-logger.js +19 -3
  260. package/dist/clients/tree-sitter-query-loader.js +81 -1
  261. package/dist/clients/tree-sitter-shared.js +51 -46
  262. package/dist/clients/tree-sitter-symbol-extractor.js +28 -0
  263. package/dist/clients/typos-config.js +5 -0
  264. package/dist/clients/user-notify.js +6 -2
  265. package/dist/clients/warm-attach.js +8 -10
  266. package/dist/clients/widget-state.js +191 -26
  267. package/dist/clients/word-index-logger.js +15 -3
  268. package/dist/clients/word-index-store.js +463 -0
  269. package/dist/clients/word-index.js +815 -125
  270. package/dist/clients/workspace-topology.js +25 -1
  271. package/dist/clients/zizmor-config.js +29 -6
  272. package/dist/index.js +68577 -53950
  273. package/dist/mcp/analyze-cli.js +3 -1
  274. package/dist/mcp/build-staleness.js +8 -7
  275. package/dist/mcp/cli.js +3 -1
  276. package/dist/mcp/server.js +133 -6
  277. package/dist/scripts/lib/process-scan.mjs +583 -0
  278. package/dist/scripts/lib/skills-predicate.mjs +129 -0
  279. package/dist/tools/effective-config.js +89 -0
  280. package/dist/tools/lens-diagnostics.js +99 -24
  281. package/dist/tools/lsp-diagnostics.js +81 -85
  282. package/dist/tools/lsp-navigation.js +40 -17
  283. package/dist/tools/shared.js +0 -1
  284. package/docs/agent-guide.md +68 -3
  285. package/docs/ast-grep_rules_catalog.md +41 -10
  286. package/docs/astplayground.md +26 -17
  287. package/docs/configuration.md +222 -0
  288. package/docs/dependencies.md +3 -3
  289. package/docs/features.md +44 -5
  290. package/docs/language-coverage.md +2 -2
  291. package/docs/pi-lens-fixer.md +25 -0
  292. package/docs/pi-lens-investigator.md +25 -0
  293. package/docs/pi-lens-reviewer.md +27 -0
  294. package/docs/pi-lens-subagent.md +38 -0
  295. package/docs/pi-lens-warden.md +55 -0
  296. package/docs/public-api-stability.md +359 -0
  297. package/docs/release-qa-baseline.md +182 -0
  298. package/docs/subagent-compat.md +110 -30
  299. package/docs/tree-sitter_rules_catalog.md +2 -2
  300. package/package.json +17 -21
  301. package/rules/ast-grep-rules/rule-tests/c-no-malloc-free-test.yml +20 -0
  302. package/rules/ast-grep-rules/rule-tests/cpp-no-malloc-free-test.yml +25 -0
  303. package/rules/ast-grep-rules/rule-tests/cpp-no-printf-test.yml +19 -0
  304. package/rules/ast-grep-rules/rule-tests/go-no-fmt-println-test.yml +19 -0
  305. package/rules/ast-grep-rules/rule-tests/go-no-panic-in-lib-test.yml +17 -0
  306. package/rules/ast-grep-rules/rule-tests/go-no-underscore-func-name-test.yml +18 -0
  307. package/rules/ast-grep-rules/rule-tests/go-prefer-errors-is-test.yml +15 -0
  308. package/rules/ast-grep-rules/rule-tests/go-prefer-string-builder-test.yml +21 -0
  309. package/rules/ast-grep-rules/rule-tests/hyphenated-svg-attribute-test.yml +11 -0
  310. package/rules/ast-grep-rules/rule-tests/kotlin-no-lateinit-test.yml +17 -0
  311. package/rules/ast-grep-rules/rule-tests/kotlin-no-nullable-boolean-test.yml +15 -0
  312. package/rules/ast-grep-rules/rule-tests/kotlin-no-println-test.yml +19 -0
  313. package/rules/ast-grep-rules/rule-tests/kotlin-no-unnecessary-let-test.yml +7 -0
  314. package/rules/ast-grep-rules/rule-tests/kotlin-no-unsafe-cast-test.yml +7 -0
  315. package/rules/ast-grep-rules/rule-tests/kotlin-no-var-test.yml +7 -0
  316. package/rules/ast-grep-rules/rule-tests/kotlin-prefer-data-class-test.yml +25 -0
  317. package/rules/ast-grep-rules/rule-tests/kotlin-prefer-is-empty-test.yml +11 -0
  318. package/rules/ast-grep-rules/rule-tests/no-important-test.yml +21 -0
  319. package/rules/ast-grep-rules/rule-tests/no-raw-types-test.yml +30 -0
  320. package/rules/ast-grep-rules/rule-tests/no-string-concat-in-loop-test.yml +100 -0
  321. package/rules/ast-grep-rules/rule-tests/no-system-out-println-test.yml +7 -0
  322. package/rules/ast-grep-rules/rule-tests/prefer-string-is-empty-test.yml +7 -0
  323. package/rules/ast-grep-rules/rule-tests/python-optional-type-test.yml +9 -0
  324. package/rules/ast-grep-rules/rule-tests/reducible-list-comprehension-test.yml +11 -0
  325. package/rules/ast-grep-rules/rule-tests/ruby-symbol-to-proc-candidate-test.yml +10 -0
  326. package/rules/ast-grep-rules/rule-tests/rust-no-chars-enumerate-test.yml +7 -0
  327. package/rules/ast-grep-rules/rules/c-no-malloc-free.yml +39 -0
  328. package/rules/ast-grep-rules/rules/cpp-no-malloc-free.yml +45 -0
  329. package/rules/ast-grep-rules/rules/cpp-no-printf.yml +41 -0
  330. package/rules/ast-grep-rules/rules/go-no-fmt-println.yml +43 -0
  331. package/rules/ast-grep-rules/rules/go-no-panic-in-lib.yml +41 -0
  332. package/rules/ast-grep-rules/rules/go-no-underscore-func-name.yml +35 -0
  333. package/rules/ast-grep-rules/rules/go-prefer-errors-is.yml +40 -0
  334. package/rules/ast-grep-rules/rules/go-prefer-string-builder.yml +43 -0
  335. package/rules/ast-grep-rules/rules/hyphenated-svg-attribute.yml +32 -0
  336. package/rules/ast-grep-rules/rules/kotlin-no-lateinit.yml +37 -0
  337. package/rules/ast-grep-rules/rules/kotlin-no-nullable-boolean.yml +33 -0
  338. package/rules/ast-grep-rules/rules/kotlin-no-println.yml +42 -0
  339. package/rules/ast-grep-rules/rules/kotlin-no-unnecessary-let.yml +28 -0
  340. package/rules/ast-grep-rules/rules/kotlin-no-unsafe-cast.yml +28 -0
  341. package/rules/ast-grep-rules/rules/kotlin-no-var.yml +35 -0
  342. package/rules/ast-grep-rules/rules/kotlin-prefer-data-class.yml +66 -0
  343. package/rules/ast-grep-rules/rules/kotlin-prefer-is-empty.yml +35 -0
  344. package/rules/ast-grep-rules/rules/no-important.yml +21 -0
  345. package/rules/ast-grep-rules/rules/no-raw-types.yml +33 -0
  346. package/rules/ast-grep-rules/rules/no-string-concat-in-loop.yml +90 -0
  347. package/rules/ast-grep-rules/rules/no-system-out-println.yml +16 -0
  348. package/rules/ast-grep-rules/rules/prefer-string-is-empty.yml +11 -0
  349. package/rules/ast-grep-rules/rules/python-optional-type.yml +22 -0
  350. package/rules/ast-grep-rules/rules/reducible-list-comprehension.yml +29 -0
  351. package/rules/ast-grep-rules/rules/ruby-symbol-to-proc-candidate.yml +25 -0
  352. package/rules/ast-grep-rules/rules/rust-no-chars-enumerate.yml +19 -0
  353. package/rules/tree-sitter-queries/python/python-cross-language-method.yml +3 -3
  354. package/rules/tree-sitter-queries/python/python-hallucinated-import.yml +2 -2
  355. package/rules/tree-sitter-queries/python/python-sql-injection.yml +12 -2
  356. package/scripts/analyze-pi-lens-logs.mjs +192 -1
  357. package/scripts/install-selftest.mjs +58 -2
  358. package/scripts/lib/skills-predicate.mjs +129 -0
  359. package/scripts/rpc-load-check.mjs +3 -1
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Process singletons — state that must exist ONCE PER PROCESS, not once per
3
+ * module evaluation (#2146).
4
+ *
5
+ * WHY THIS MODULE EXISTS. pi evaluates the pi-lens module graph more than once
6
+ * in a single process: dogfood pass 3 measured one pid emitting `host_boot`
7
+ * nine times, another four times. Source and compiled entries load through
8
+ * separate module graphs, in-process subagent sessions re-enter the extension
9
+ * loader, and vitest re-evaluates modules on `vi.resetModules()`. Every
10
+ * module-scope `let` therefore exists N times, and any state whose CORRECTNESS
11
+ * depends on being the process's only copy silently breaks:
12
+ *
13
+ * - `session-lifecycle.ts`'s registered primary: evaluation 2 starts with an
14
+ * empty registration, classifies a subagent temp root as `primary`, and runs
15
+ * the full session_start battery the #473/#2129/#2133 guard exists to
16
+ * decline. Measured cost: three identical word-index full rebuilds in one
17
+ * 90s burst, 240.8s of CPU for one index.
18
+ * - `instance-registry.ts`'s mutation tail: N tails are N concurrent
19
+ * read-modify-write cycles over one `instances.json`. Measured: three
20
+ * `instance-registry-corrupt` records in a 9s window, with two project roots
21
+ * and one live instance lost from the file.
22
+ *
23
+ * PRECEDENT, both in-repo and both cited rather than invented:
24
+ * - `clients/runtime-session.ts:1826` hangs `__piLensFirstSessionDone` /
25
+ * `__piLensWarmupScheduled` on `globalThis` for exactly this reason.
26
+ * - `clients/ndjson-logger.ts:125` does the versioned form: a
27
+ * `Symbol.for()` key, a schema string, a version number, and an explicit
28
+ * adopt-or-decline decision for a state written by a different build.
29
+ *
30
+ * This module generalizes the second one so each state family gets the same
31
+ * treatment without re-deriving the protocol per site.
32
+ *
33
+ * WHAT BELONGS HERE. Only state that is WRONG when duplicated: process-wide
34
+ * registrations, serialization points, and once-per-process latches. A memo or
35
+ * cache that re-derives the same answer from a stable source (an env read, a
36
+ * host probe) is merely wasteful when duplicated, not wrong, and stays at
37
+ * module scope — see the class-sweep table in the #2146 PR body for the
38
+ * per-family verdicts.
39
+ *
40
+ * VERSIONING AND THE OLDER-SHAPE FALLBACK. Two builds can meet in one process:
41
+ * a stale `dist/` graph and a fresh source graph, or an extension reload after
42
+ * an upgrade. Each family carries its own `version`. The rule is
43
+ * adopt-if-compatible, reset otherwise:
44
+ *
45
+ * - Same schema and same version -> ADOPT the existing value. This is the
46
+ * common case and the whole point of the module.
47
+ * - Anything else (missing schema, different schema, older version, NEWER
48
+ * version) -> do NOT adopt. A shape this build cannot read is not safely
49
+ * readable in either direction, so guessing is worse than starting clean.
50
+ * The cell is replaced with a fresh value from `create()` and ONE bounded
51
+ * `process-singleton-reset` entry is logged per family, so a nine-evaluation
52
+ * process still reports at most one row per family. Behavior after a reset is
53
+ * exactly today's module-scope behavior for that family, which is the
54
+ * fail-safe direction.
55
+ *
56
+ * The container key itself is versioned (`SINGLETON_HOST_KEY`). A future change
57
+ * to the CONTAINER shape bumps the key, so an old container is simply not
58
+ * found rather than mis-read.
59
+ */
60
+ /**
61
+ * STATIC IMPORTS: none, deliberately. This module is a dependency leaf.
62
+ *
63
+ * It cannot import the degradation ledger. `instance-registry.ts` imports this
64
+ * module, and the ledger reaches `instance-reaper.ts` (through `extension-log`
65
+ * -> `file-utils` -> `git-tracked-ignore` -> `safe-spawn` -> `resource-sampler`),
66
+ * and `instance-reaper` imports the registry back. Any edge from here to the
67
+ * ledger closes a `no-client-cycles` cycle, which CI's dependency-boundaries
68
+ * lane rejects. A dynamic import does not help: the rule excludes
69
+ * `dynamic-import` only on the edge it evaluates, not on the intermediate edges
70
+ * of the cycle it walks.
71
+ *
72
+ * So the direction is inverted, exactly as `ndjson-logger.ts` already does for
73
+ * its own sink-write failures: this module keeps a bounded reset log, and
74
+ * `degradation-ledger.ts` PULLS it at read time through
75
+ * {@link getProcessSingletonResets} and folds it into `getDegradationSummary()`.
76
+ * The record still reaches `pilens_health`, with no import from here.
77
+ */
78
+ /** Bump only when the CONTAINER shape changes, never for a family's shape. */
79
+ const SINGLETON_HOST_KEY = Symbol.for("pi-lens.process-singletons.v1");
80
+ const SINGLETON_SCHEMA = "pi-lens.process-singletons";
81
+ /** Degradation kind emitted when an incompatible cell is discarded. */
82
+ export const PROCESS_SINGLETON_RESET_KIND = "process-singleton-reset";
83
+ const singletonHost = globalThis;
84
+ /**
85
+ * The process's one container. Deliberately created on first read rather than
86
+ * at module scope of a single graph: whichever evaluation runs first wins, and
87
+ * every later evaluation adopts it.
88
+ */
89
+ function container() {
90
+ const existing = singletonHost[SINGLETON_HOST_KEY];
91
+ if (existing instanceof Map)
92
+ return existing;
93
+ const fresh = new Map();
94
+ singletonHost[SINGLETON_HOST_KEY] = fresh;
95
+ return fresh;
96
+ }
97
+ function isAdoptable(cell, version) {
98
+ if (!cell || typeof cell !== "object")
99
+ return false;
100
+ const candidate = cell;
101
+ return (candidate.schema === SINGLETON_SCHEMA &&
102
+ candidate.version === version &&
103
+ candidate.value !== undefined);
104
+ }
105
+ /** One bounded reset log per process, capped so a pathological build pair
106
+ * cannot grow it without limit. */
107
+ export const PROCESS_SINGLETON_RESET_LOG_FAMILY = "process-singleton-reset-log";
108
+ const RESET_LOG_FAMILY = PROCESS_SINGLETON_RESET_LOG_FAMILY;
109
+ const RESET_LOG_VERSION = 1;
110
+ const RESET_LOG_CAP = 16;
111
+ function resetLog() {
112
+ return getProcessSingleton(RESET_LOG_FAMILY, RESET_LOG_VERSION, () => ({
113
+ entries: [],
114
+ }));
115
+ }
116
+ /**
117
+ * Record that an incompatible cell was discarded. Bounded twice: once per
118
+ * family (a nine-evaluation process logs at most one entry per family) and once
119
+ * overall by {@link RESET_LOG_CAP}.
120
+ */
121
+ function recordIncompatibleCell(family, wantedVersion, found) {
122
+ // The reset log is itself a family, so recording ITS reset would recurse.
123
+ // Dropping that one record is correct: the log it would be written to is the
124
+ // thing being discarded.
125
+ if (family === RESET_LOG_FAMILY)
126
+ return;
127
+ const log = resetLog();
128
+ if (log.entries.some((entry) => entry.family === family))
129
+ return;
130
+ if (log.entries.length >= RESET_LOG_CAP)
131
+ return;
132
+ log.entries.push({
133
+ family,
134
+ reason: `incompatible process singleton discarded (found schema=${String(found.schema)} ` +
135
+ `version=${String(found.version)}, this build wants schema=${SINGLETON_SCHEMA} ` +
136
+ `version=${wantedVersion})`,
137
+ });
138
+ }
139
+ /**
140
+ * Read-time view for `degradation-ledger.ts` (see the header). Never performs
141
+ * I/O and never throws, so folding it into a health summary is free.
142
+ */
143
+ export function getProcessSingletonResets() {
144
+ return resetLog().entries.map((entry) => ({ ...entry }));
145
+ }
146
+ /**
147
+ * Return the process-wide value for `family`, creating it on the first
148
+ * evaluation that asks and adopting it on every later one.
149
+ *
150
+ * `version` describes the SHAPE of the value `create()` builds. Bump it when a
151
+ * build changes that shape, so a graph carrying the old shape is discarded
152
+ * instead of mis-read (see the module docstring's fallback rules).
153
+ *
154
+ * `create()` runs at most once per process per family, unless an incompatible
155
+ * cell forced a reset.
156
+ *
157
+ * `onIncompatible` is for values that own external resources. It runs before
158
+ * the cell is replaced, so the owner can tear those resources down safely.
159
+ */
160
+ export function getProcessSingleton(family, version, create, onIncompatible) {
161
+ const cells = container();
162
+ const existing = cells.get(family);
163
+ if (isAdoptable(existing, version))
164
+ return existing.value;
165
+ if (existing !== undefined) {
166
+ // An incompatible cell from another build. One bounded row per family:
167
+ // nine evaluations must not write nine records (AGENTS.md's bounded-record
168
+ // rule; `recordDegradationOnce` keys on kind + subject).
169
+ recordIncompatibleCell(family, version, existing);
170
+ onIncompatible?.(existing.value);
171
+ }
172
+ const value = create();
173
+ cells.set(family, { schema: SINGLETON_SCHEMA, version, value });
174
+ return value;
175
+ }
176
+ /**
177
+ * Test-only: drop the process-wide container so the next
178
+ * {@link getProcessSingleton} call rebuilds every family from `create()`.
179
+ *
180
+ * This clears the GLOBAL state, not a module-local copy — a reset that only
181
+ * cleared module scope would be the very defect this module fixes, and would
182
+ * make every suite that relies on isolation pass vacuously (catalog shape 7).
183
+ */
184
+ export function _resetProcessSingletonsForTests() {
185
+ singletonHost[SINGLETON_HOST_KEY] = new Map();
186
+ }
187
+ /**
188
+ * Test-only: install a raw cell so a suite can exercise the older/newer-shape
189
+ * fallback without needing two real builds in one process.
190
+ */
191
+ export function _seedProcessSingletonCellForTests(family, cell) {
192
+ container().set(family, cell);
193
+ }
194
+ // --- Module-evaluation ordinal (#2146 observability) ---
195
+ const EVALUATION_ORDINAL_FAMILY = "module-evaluation-ordinal";
196
+ const EVALUATION_ORDINAL_VERSION = 1;
197
+ /**
198
+ * How many times this process has evaluated the pi-lens module graph, 1-based.
199
+ *
200
+ * Called once, at module scope of `clients/startup-timing.ts`, so the number it
201
+ * returns is the evaluation count of the graph that contains the extension
202
+ * entry. It is carried on `host_boot.metadata.evaluationOrdinal` so a multi-eval
203
+ * process is greppable from `latency.log` instead of having to be inferred by
204
+ * counting `host_boot` lines per pid — the observability gap #2146 names.
205
+ */
206
+ export function nextModuleEvaluationOrdinal() {
207
+ const state = getProcessSingleton(EVALUATION_ORDINAL_FAMILY, EVALUATION_ORDINAL_VERSION, () => ({ evaluations: 0 }));
208
+ state.evaluations += 1;
209
+ return state.evaluations;
210
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * The extension runtime's door onto the ONE process-table seam (#2443).
3
+ *
4
+ * `scripts/lib/process-scan.mjs` owns what a process listing IS: the field →
5
+ * platform-column table, the Windows CIM/WQL and POSIX `ps` command
6
+ * composition, the WQL escaping, and the row parser. This module owns how
7
+ * clients/ RUNS one, which is genuinely different from how a script runs one
8
+ * and stays different on purpose:
9
+ *
10
+ * - the child and its stdout pipe are `unref`'d (#1155) so a one-shot
11
+ * `pi --print` can settle without waiting on a CIM query;
12
+ * - a child that blows its timeout is terminated through the caller's own
13
+ * tree-kill-and-verify machinery (#1864 F3), injected as `onTimeout` — a
14
+ * sweep that leaks its own scanner is the defect the sweep exists to fix;
15
+ * - the outcome carries a `SpawnCollectStatus`, so "the table is empty" and
16
+ * "the query never ran" stay distinguishable and the caller can record the
17
+ * degradation (#1857).
18
+ *
19
+ * WHY THE SEAM IS IMPORTED FROM scripts/ RATHER THAN LIVING HERE: the .mjs
20
+ * header argues it in full. Short version — `scripts/prune-agent-worktrees.mjs`
21
+ * is a SessionStart/SubagentStop hook that runs inside freshly created agent
22
+ * worktrees, where `clients/*.js` (gitignored build output) does not exist
23
+ * yet, so the shared half has to be the side that needs no build. This file
24
+ * is the single crossing point; nothing else in clients/ imports scripts/.
25
+ */
26
+ import { spawnCollectStdoutResult, } from "./child-unref.js";
27
+ import { buildProcessQuery, parseProcessTable, } from "../scripts/lib/process-scan.mjs";
28
+ /**
29
+ * Re-exported, not redefined. `windowsExe` resolves an absolute System32
30
+ * interpreter path for a spawn this process is about to make (a bare
31
+ * `powershell.exe`/`taskkill.exe` is resolvable through a PATH a caller can
32
+ * shadow, and these spawns decide what gets killed) — one definition of that
33
+ * rule is the whole point of the seam.
34
+ */
35
+ export { windowsExe } from "../scripts/lib/process-scan.mjs";
36
+ /**
37
+ * Run one process-table query on the extension's spawn rails.
38
+ *
39
+ * Never throws and never rejects: an unbuildable query (a Windows-only column
40
+ * asked for on POSIX, an empty filter) resolves as `spawn-error` with no rows,
41
+ * the same shape every other failure takes, because every caller here is
42
+ * best-effort instrumentation or a best-effort sweep.
43
+ */
44
+ export async function queryProcessTable(request, options) {
45
+ let query;
46
+ try {
47
+ query = buildProcessQuery(request.fields, {
48
+ filter: request.filter,
49
+ excludeSelfPid: request.excludeSelfPid,
50
+ });
51
+ }
52
+ catch {
53
+ return { rows: [], status: "spawn-error", serverSideFiltered: false };
54
+ }
55
+ const result = await spawnCollectStdoutResult(query.command, query.args, { shell: false, windowsHide: true, stdio: ["ignore", "pipe", "ignore"] }, { timeoutMs: options.timeoutMs, onTimeout: options.onTimeout });
56
+ return {
57
+ // Whatever the collector kept is parsed rather than discarded here: it
58
+ // drops stdout on a non-zero exit (that output is not evidence of what
59
+ // is NOT running) but KEEPS the partial table from a timed-out child,
60
+ // and the caller decides what a partial table is worth by reading
61
+ // `status`. Re-deciding that here would be a second policy.
62
+ rows: parseProcessTable(result.stdout, query.tabSeparated, query.fields),
63
+ status: result.status,
64
+ exitCode: result.exitCode,
65
+ timeoutKill: result.timeoutKill,
66
+ serverSideFiltered: query.serverSideFiltered,
67
+ };
68
+ }
@@ -415,9 +415,10 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
415
415
  // Deliberately never calls `writeCache`: there is nothing fresher to write
416
416
  // back, only what turn_end already produced.
417
417
  //
418
- // No double-count / honesty gap (#533): `consumeTestFindings`
419
- // (`runtime-context.ts`) reads-and-clears this SAME cache key once, to
420
- // inject a one-shot "fix before continuing" message into the NEXT turn.
418
+ // No double-count / honesty gap (#533): explicit `pilens_turn_end`
419
+ // delivery may call `consumeTestFindings` (`runtime-context.ts`) to
420
+ // read-and-clear this SAME cache key once. Automatic Pi delivery uses a
421
+ // non-context custom entry and never consumes this pull-diagnostics cache.
421
422
  // This task only ever reads (never clears) it, so it can't race that
422
423
  // consumption into re-delivering a message twice — at most it surfaces
423
424
  // the same underlying failures a second time, through a different
@@ -1,4 +1,5 @@
1
1
  import * as path from "node:path";
2
+ import { compareOrdinal } from "../../string-utils.js";
2
3
  function resolveCyclePath(cwd, file) {
3
4
  return path.isAbsolute(file) ? file : path.resolve(cwd, file);
4
5
  }
@@ -36,7 +37,7 @@ export function circularDepsToProjectDiagnostics(cwd, circular) {
36
37
  for (const dep of circular) {
37
38
  const key = cycleMembers(dep)
38
39
  .map((f) => resolveCyclePath(cwd, f))
39
- .sort((a, b) => a.localeCompare(b))
40
+ .sort(compareOrdinal)
40
41
  .join("|");
41
42
  if (seen.has(key))
42
43
  continue;
@@ -1,3 +1,4 @@
1
+ import { isRunnerErrorResult, } from "../../test-runner-client.js";
1
2
  import * as fs from "node:fs";
2
3
  import * as path from "node:path";
3
4
  import { advisoryPathKey, validateAdvisoryProvenance, } from "../../advisory-provenance.js";
@@ -59,6 +60,31 @@ export function testResultToProjectDiagnostics(result, stale = false) {
59
60
  source: "project-scan",
60
61
  }));
61
62
  }
63
+ // #2532: a RUNNER error (timeout, missing provider/binary, a config
64
+ // failure — the suite itself never produced a verdict) is not a finding
65
+ // the agent introduced, exactly like the turn-end delivery framing
66
+ // (#2522) already treats it. `isRunnerErrorResult` is the single seam
67
+ // both surfaces read so the identical `TestResult` cannot classify
68
+ // differently here than it does in the turn-end message.
69
+ if (isRunnerErrorResult(result)) {
70
+ return [
71
+ {
72
+ filePath: result.file,
73
+ severity: "info",
74
+ semantic: "none",
75
+ tool: "test-runner",
76
+ runner: result.runner,
77
+ rule: `test:${result.runner}`,
78
+ message: `${stalePrefix}Test run error: ${result.error}`,
79
+ source: "project-scan",
80
+ },
81
+ ];
82
+ }
83
+ // #2532 review S3: a counted failure stays blocking even when `error` is
84
+ // ALSO set (pytest exit 2 "Interrupted" after `2 failed, 1 passed` — see
85
+ // `isRunnerErrorResult`'s doc). The pre-fix message mentioned `error`
86
+ // here via a ternary this PR's first version dropped; restored so the
87
+ // interruption is not silently lost from a still-blocking finding.
62
88
  return [
63
89
  {
64
90
  filePath: result.file,
@@ -67,9 +93,7 @@ export function testResultToProjectDiagnostics(result, stale = false) {
67
93
  tool: "test-runner",
68
94
  runner: result.runner,
69
95
  rule: `test:${result.runner}`,
70
- message: `${stalePrefix}${result.error
71
- ? `Test run error: ${result.error}`
72
- : `${result.failed} test(s) failed`}`,
96
+ message: `${stalePrefix}${result.failed} test(s) failed${result.error ? ` (runner also reported: ${result.error})` : ""}`,
73
97
  source: "project-scan",
74
98
  },
75
99
  ];
@@ -5,6 +5,7 @@ import { evaluateRules } from "../dispatch/fact-rule-runner.js";
5
5
  import { runProviders } from "../dispatch/fact-runner.js";
6
6
  import { FactStore } from "../dispatch/fact-store.js";
7
7
  import { canHandle as astGrepCanHandle, getLang as astGrepGetLang, evaluateAstGrepRules, loadSg, } from "../dispatch/runners/ast-grep-napi.js";
8
+ import { detectFileKind } from "../file-kinds.js";
8
9
  import { isTestFile } from "../file-utils.js";
9
10
  import { isAtOrAboveHomeDir } from "../path-utils.js";
10
11
  import { getProjectDiagnosticsScannerMaxFiles } from "../project-scale.js";
@@ -32,27 +33,30 @@ const FACT_RULE_EXTENSIONS = new Set([
32
33
  ".mjs",
33
34
  ".cjs",
34
35
  ]);
35
- // Which languages the scan runs tree-sitter rules for: every language that has
36
- // (a loadable grammar) AND (a non-disabled rules/tree-sitter-queries/<lang>/ dir).
36
+ // Which languages the scan runs tree-sitter rules for. Identical to the shared
37
+ // per-edit resolver (EXT_TO_LANG), which since #2424 is itself a projection of
38
+ // the canonical language registry — so the scan and the per-edit path cannot
39
+ // disagree about `.tsx`→tsx / `.jsx`→javascript / the c-vs-cpp header split.
40
+ // (`.tsx` resolves to the tsx grammar, not typescript: the typescript grammar
41
+ // ERRORs on JSX; typescript RULES still apply because `queriesForLanguage(...)`
42
+ // below merges the typescript rule set onto tsx.)
37
43
  //
38
- // The base is the shared per-edit resolver (EXT_TO_LANG — the single
39
- // ext→grammar-id authority), so c/cpp/csharp/php/css and the .tsx→tsx /
40
- // .jsx→javascript nuances can never drift from the per-edit path. `.tsx` resolves
41
- // to the tsx grammar (not typescript: the typescript grammar ERRORs on JSX); // spellchecker:disable-line
42
- // typescript RULES still apply because `queriesForLanguage(...)` below merges the
43
- // typescript rule set onto tsx.
44
+ // java + kotlin used to be layered on here because EXT_TO_LANG lacked them:
45
+ // they have loadable grammars and rule dirs (java: 24 rules, kotlin: 1) but no
46
+ // per-edit runner `appliesTo` entry, so only this broad project scan runs them.
47
+ // The registry now owns their extensions, so the spread is gone and this is a
48
+ // plain alias. scanner.test.ts asserts this map covers every non-disabled rule
49
+ // dir whose grammar is loadable, so adding a language dir fails a test until
50
+ // its extension is registered in the registry.
44
51
  //
45
- // java + kotlin are layered on here: they have loadable grammars and rule dirs
46
- // (java: 24 rules, kotlin: 1) but no per-edit runner `appliesTo` entry, so only
47
- // this broad project scan runs them. scanner.test.ts asserts this map covers
48
- // every non-disabled rule dir whose grammar is loadable, so adding a language dir
49
- // fails a test until its extension is registered here (or in EXT_TO_LANG).
50
- export const TREE_SITTER_EXT_TO_LANG = {
51
- ...EXT_TO_LANG,
52
- ".java": "java",
53
- ".kt": "kotlin",
54
- ".kts": "kotlin",
55
- };
52
+ // This map is the scan's language RESOLVER, not its eligibility gate: it now
53
+ // answers for grammars with no rule dir too (bash/dart/elixir/lua/ocaml/swift/
54
+ // zig), and the pass gates each file on `hasTreeSitterRules` so a grammar
55
+ // without rules costs no read (#2424 review, F2).
56
+ // `Readonly` because the projection is `Object.freeze`d at the source (#2424
57
+ // review, S4) — the mutable `Record` type is what let the pre-#2424 spread
58
+ // layer java/kotlin on by hand in the first place.
59
+ export const TREE_SITTER_EXT_TO_LANG = EXT_TO_LANG;
56
60
  function normalizeSeverity(severity) {
57
61
  if (severity === "error" || severity === "warning" || severity === "hint") {
58
62
  return severity;
@@ -100,7 +104,9 @@ async function scanFileMajorRules(cwd, files, signal) {
100
104
  const queryMap = treeSitterReady
101
105
  ? await queryLoader.loadQueries(cwd)
102
106
  : undefined;
103
- const facts = new FactStore();
107
+ // Subject labels this store's capacity-eviction telemetry distinctly from
108
+ // the other five production FactStore instances (#2243 review round 3, F1).
109
+ const facts = new FactStore("project-diagnostics-scanner");
104
110
  const pi = { getFlag: () => undefined };
105
111
  const treeSitter = [];
106
112
  const factRules = [];
@@ -113,6 +119,29 @@ async function scanFileMajorRules(cwd, files, signal) {
113
119
  // file out, so the caller can report a truncated filesScanned honestly.
114
120
  let filesScanned = 0;
115
121
  let wasmAborted = isTreeSitterWasmAborted();
122
+ /**
123
+ * Does this project actually have ENABLED tree-sitter rules for `langId`?
124
+ *
125
+ * The gate below used to read "does the extension have a grammar", which was
126
+ * the same question only because the ext -> grammar map hand-listed exactly
127
+ * the twelve grammars that also ship a `rules/tree-sitter-queries/<lang>/`
128
+ * dir. #2424 projected that map from the registry's full grammar column, so
129
+ * bash/dart/elixir/lua/ocaml/swift/zig arrived WITHOUT rules and every such
130
+ * file started being read, walked and counted for a rule set that is empty
131
+ * (#2424 review, F2). Memoized per language: `queriesForLanguage` rebuilds
132
+ * and filters its array on every call, and the scan asks once per file.
133
+ */
134
+ const ruleCoverage = new Map();
135
+ const hasTreeSitterRules = (langId) => {
136
+ if (!queryMap || !client)
137
+ return false;
138
+ const memo = ruleCoverage.get(langId);
139
+ if (memo !== undefined)
140
+ return memo;
141
+ const covered = queriesForLanguage(queryMap, langId).length > 0;
142
+ ruleCoverage.set(langId, covered);
143
+ return covered;
144
+ };
116
145
  const scanTreeSitterFile = async (filePath, langId, content) => {
117
146
  if (!queryMap || !langId || !client || content === null)
118
147
  return;
@@ -141,7 +170,7 @@ async function scanFileMajorRules(cwd, files, signal) {
141
170
  }
142
171
  };
143
172
  const scanFactRulesFile = async (filePath, content) => {
144
- facts.clearFileFactsFor(filePath);
173
+ facts.dropFileFacts(filePath);
145
174
  // Seed the exact bytes already supplied to tree-sitter so the file-content
146
175
  // provider is skipped by runProviders.
147
176
  facts.setFileFact(filePath, "file.content", content);
@@ -160,9 +189,11 @@ async function scanFileMajorRules(cwd, files, signal) {
160
189
  const startedAt = Date.now();
161
190
  try {
162
191
  const rootNode = lang.parse(content).root();
163
- const fileDiagnostics = evaluateAstGrepRules(filePath, rootNode, cwd, "jsts", {
192
+ const fileDiagnostics = evaluateAstGrepRules(filePath, rootNode, cwd, detectFileKind(filePath), {
164
193
  maxMatchesPerRule: AST_GREP_SCAN_MAX_MATCHES_PER_RULE,
165
194
  maxTotalDiagnostics: AST_GREP_SCAN_MAX_DIAGNOSTICS_PER_FILE,
195
+ content,
196
+ sgModule,
166
197
  });
167
198
  for (const diagnostic of fileDiagnostics) {
168
199
  astGrep.push(fromDispatchDiagnostic(diagnostic, "ast-grep-napi"));
@@ -187,7 +218,10 @@ async function scanFileMajorRules(cwd, files, signal) {
187
218
  if (isTestFile(filePath))
188
219
  continue;
189
220
  const ext = path.extname(filePath);
190
- const langId = TREE_SITTER_EXT_TO_LANG[ext];
221
+ // A grammar is not a consumer: only a language this project has
222
+ // ENABLED rules for can turn this file's bytes into a finding.
223
+ const grammarId = TREE_SITTER_EXT_TO_LANG[ext];
224
+ const langId = grammarId && hasTreeSitterRules(grammarId) ? grammarId : undefined;
191
225
  const factEligible = FACT_RULE_EXTENSIONS.has(ext);
192
226
  let astGrepLang;
193
227
  if (sgModule && astGrepCanHandle(filePath)) {
@@ -264,7 +298,10 @@ async function scanFileMajorRules(cwd, files, signal) {
264
298
  finally {
265
299
  // This store belongs to the scan, and every consumer of this file's
266
300
  // content and derived facts has completed by the end of the iteration.
267
- facts.clearFileFactsFor(filePath);
301
+ // Drop without pinning: the scan is sequential and owns this store, so
302
+ // there is no concurrent walk to guard against, and pinning every
303
+ // scanned file would exempt it from the capacity cap (#2243).
304
+ facts.dropFileFacts(filePath);
268
305
  }
269
306
  }
270
307
  if (wasmAborted) {