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,192 @@
1
+ /**
2
+ * The session-root registry: which project roots does this process serve?
3
+ *
4
+ * #2052. A file is IN-SESSION when it lies within any initialized session cwd.
5
+ * `initLSPConfig` (`clients/lsp/config.ts`) is the single writer, and every
6
+ * entry point funnels through it — `ensureLSPConfigInitialized` (`index.ts`),
7
+ * `ensureReady` (`mcp/server.ts`), `clients/runtime-session.ts`, and
8
+ * `clients/lens-engine.ts` — so registration needs no second call site.
9
+ *
10
+ * WHY THIS IS ITS OWN MODULE, not a helper inside `config.ts`: `clients/lsp/
11
+ * index.ts`'s import surface from `config.js` is mirrored by an explicit
12
+ * `vi.mock` factory in ~58 test files. Importing a new symbol from `config.js`
13
+ * breaks every one of them with "No export is defined on the mock", which is a
14
+ * standing tax on any future export.
15
+ *
16
+ * WHY THE CONFIG PAYLOAD LIVES HERE TOO (#2518). Until this fix the payload
17
+ * lived in `config.ts`'s own 32-entry `workspaceConfigs` LRU while this
18
+ * registry held 128, and the two answered halves of ONE question: the registry
19
+ * decides whether a root still needs initializing (`ensureReady`,
20
+ * `mcp/server.ts`), the payload decides whether the operator's
21
+ * `lsp.disabledServers` denial applies (`isServerDisabled`). Thirty-three
22
+ * `ensureReady` calls on other cwds therefore dropped a LIVE root's denial
23
+ * while the registry still reported the root ready, so nothing re-initialized
24
+ * it and a server the operator had turned off came back — #2415 AC 3's exact
25
+ * prohibition, silently.
26
+ *
27
+ * Tying the two CAPS would not have fixed it. `initLSPConfig` registers the
28
+ * root BEFORE its `await` and writes the payload AFTER, so two interleaved
29
+ * initializations enter the two containers in different orders; with separate
30
+ * containers the same cap still evicts different keys from each. One entry per
31
+ * root — the key IS the registration, the value IS the payload — makes a
32
+ * payload miss for a served root impossible by construction instead of by an
33
+ * ordering agreement between two modules.
34
+ *
35
+ * WHY IT IS NOT A LATCH. The first attempt at #2052 used a single
36
+ * last-writer-wins session cwd that fell back to `process.cwd()`. Two bugs
37
+ * followed directly from that shape, and this module's contract excludes both:
38
+ *
39
+ * - MULTI-ROOT. A process that initializes projA and then projB serves BOTH.
40
+ * A latch silently made projA's files foreign the moment projB registered.
41
+ * - FAIL-OPEN ON EMPTY. An empty registry declines NOTHING. Declining is a
42
+ * hard refusal to answer, so it must rest on positive evidence that a
43
+ * session exists AND that the file sits outside it — never on the absence
44
+ * of evidence. Callers that never declared a session (isolated tests, API
45
+ * consumers, a request racing initialization) therefore keep the pre-#2052
46
+ * clamp behavior instead of having `process.cwd()` gate a refusal.
47
+ *
48
+ * This is also the seam #2053 needs: an explicit `analysisRoot` registers here.
49
+ */
50
+ import { BoundedFifoMap } from "../bounded-cache.js";
51
+ import { incrementDegradationCount } from "../degradation-ledger.js";
52
+ import { isSameOrWithin } from "./server.js";
53
+ import path from "node:path";
54
+ /**
55
+ * Insertion-ordered, so eviction drops the OLDEST root. `BoundedFifoMap`
56
+ * (#2442) owns the eviction; this module no longer hand-rolls it. FIFO and not
57
+ * LRU on purpose: eviction order is REGISTRATION order, so a config write for
58
+ * an already-registered root cannot reorder the registry underneath the
59
+ * `ensureReady` memo that reads it.
60
+ *
61
+ * The value is `undefined` between `registerSessionRoot` and the config write
62
+ * that follows the loader's `await` — the same window in which, before #2518,
63
+ * no payload existed for the root at all. {@link sessionRootConfigEntries}
64
+ * skips those entries, so an in-flight root resolves exactly as it did before:
65
+ * to a shorter ancestor root's config, or to none.
66
+ */
67
+ const SESSION_ROOT_CAP = 128;
68
+ const sessionRoots = new BoundedFifoMap(SESSION_ROOT_CAP);
69
+ /**
70
+ * The cap dropped a root this process was serving. Its files fall back to
71
+ * pre-#2052 clamp behavior and its `lsp.disabledServers` denial stops applying
72
+ * until something initializes the root again — which `shouldInitializeSessionRoot`
73
+ * now guarantees the next SESSION START OR TOOL CALL naming that root does,
74
+ * because the registration went with the payload. Nothing else re-initializes:
75
+ * the readers of the denial (`isServerDisabled`, `getServersForFileWithConfig`,
76
+ * `getServerInitOverride`) ask the store, they never load it.
77
+ *
78
+ * A CONSTANT subject, counted rather than latched (#2518 review F3). The
79
+ * subject cannot be the evicted root: the ledger's `recordDegradationOnce` /
80
+ * `incrementDegradationCount` dedupe keys are unbounded, so a per-root subject
81
+ * would grow one key per root a long-lived process ever cycles through — a
82
+ * container bounded on one axis and unbounded on another, which is the shape
83
+ * this whole fix is about. That argument bounds the SUBJECT; it is not an
84
+ * argument for throwing the tally away, and the number of roots this process
85
+ * has had to drop is exactly what an operator tunes the cap against. The
86
+ * ledger keeps one entry per kind/subject and emits durable rows only on
87
+ * power-of-two counts, so counting is as bounded here as latching.
88
+ *
89
+ * Returns the evicted roots so the caller can drop the per-root session
90
+ * bookkeeping that described them — `initLSPConfig` releases each one's
91
+ * `config_resolved` claim (review F1).
92
+ */
93
+ function noteEvictedRoots(evicted) {
94
+ const roots = evicted.map(([root]) => root);
95
+ if (roots.length > 0) {
96
+ incrementDegradationCount({
97
+ kind: "lsp-session-root-evicted",
98
+ subject: `cap=${SESSION_ROOT_CAP}`,
99
+ reason: `session root registry at capacity; dropped ${roots[0]} and will load it again on the next session start or tool call naming that root`,
100
+ });
101
+ }
102
+ return roots;
103
+ }
104
+ /**
105
+ * Record a session cwd as served. Idempotent; re-registering refreshes nothing.
106
+ * Returns the roots this insertion dropped at the cap, oldest first.
107
+ */
108
+ export function registerSessionRoot(cwd) {
109
+ const root = path.resolve(cwd);
110
+ // Present already: leave the entry alone. Writing the `undefined`
111
+ // placeholder over a loaded config would blank a served root's denial for
112
+ // the length of the re-initializing load — the #2518 window in miniature.
113
+ if (sessionRoots.has(root))
114
+ return [];
115
+ return noteEvictedRoots(sessionRoots.set(root, undefined));
116
+ }
117
+ /**
118
+ * Store the config `initLSPConfig` just loaded for a root it registered.
119
+ *
120
+ * Re-inserts the root when the cap dropped it while the load was in flight:
121
+ * the caller is a live session declaring this root right now, so the pair
122
+ * belongs in the registry — and it is a PAIR, which is what keeps the payload
123
+ * from outliving the registration or the registration from outliving the
124
+ * payload.
125
+ *
126
+ * Returns the roots this write dropped at the cap, oldest first.
127
+ */
128
+ export function setSessionRootConfig(cwd, config) {
129
+ return noteEvictedRoots(sessionRoots.set(path.resolve(cwd), config));
130
+ }
131
+ /**
132
+ * Every served root that has a loaded config, in registration order —
133
+ * `getConfigForFile`'s (`config.ts`) longest-prefix walk. Entries still
134
+ * awaiting their first load are skipped; see the store's doc comment.
135
+ */
136
+ export function sessionRootConfigEntries() {
137
+ return sessionRoots;
138
+ }
139
+ /** Return whether this exact root is still served after cap eviction. */
140
+ export function isSessionRootRegistered(cwd) {
141
+ return sessionRoots.has(path.resolve(cwd));
142
+ }
143
+ /**
144
+ * True when a readiness memo must run initialization for this root.
145
+ *
146
+ * The memo alone is never enough (#2052 R1, #2518): a readiness set caps
147
+ * itself, and `initLSPConfig` has callers (`clients/runtime-session.ts`,
148
+ * `clients/lens-engine.ts`) that no memo sees, so a memo hit can outlive the
149
+ * registration — and with it the config that carries the operator's denial.
150
+ * Every readiness memo asks this function, never its own `has` alone.
151
+ */
152
+ export function shouldInitializeSessionRoot(cwd, readyRoots) {
153
+ const normalized = path.resolve(cwd);
154
+ return !readyRoots.has(normalized) || !isSessionRootRegistered(normalized);
155
+ }
156
+ /**
157
+ * Is `filePath` outside EVERY registered session root?
158
+ *
159
+ * Returns false (serve it) when the registry is empty — see the fail-open rule
160
+ * in this module's header. Containment uses `isSameOrWithin`, the same
161
+ * comparator the LSP root ceiling uses, which selects `path.win32` by path
162
+ * SHAPE rather than by `process.platform` (shape 2 / #1150).
163
+ */
164
+ export function isOutsideAllSessionRoots(filePath) {
165
+ if (sessionRoots.size === 0)
166
+ return false;
167
+ const resolved = path.resolve(filePath);
168
+ for (const root of sessionRoots.keys()) {
169
+ if (isSameOrWithin(root, resolved))
170
+ return false;
171
+ }
172
+ return true;
173
+ }
174
+ /**
175
+ * The registered roots, for the decline telemetry record. Sorted with a
176
+ * code-unit comparator so the emitted string is deterministic across locales
177
+ * (#1883), and capped because the record is BOUNDED — a process with many
178
+ * roots must not emit an unbounded path list.
179
+ */
180
+ export function getSessionRootsForTelemetry(limit = 4) {
181
+ return [...sessionRoots.keys()]
182
+ .sort((a, b) => Number(a > b) - Number(a < b))
183
+ .slice(0, limit);
184
+ }
185
+ /**
186
+ * Clear the registry — the roots AND the configs they carry, which are one
187
+ * store since #2518, so a reset cannot leave the two disagreeing about which
188
+ * roots exist. Called by `resetLSPConfigStateForTests`.
189
+ */
190
+ export function resetSessionRootsForTests() {
191
+ sessionRoots.clear();
192
+ }
@@ -1,17 +1,12 @@
1
1
  /** Process-lifetime history of successful LSP spawn plus initialize durations. */
2
+ import { BoundedFifoMap } from "../bounded-cache.js";
2
3
  const MAX_SERVER_HISTORIES = 64;
3
- const successfulSpawnDurationMs = new Map();
4
+ const successfulSpawnDurationMs = new BoundedFifoMap(MAX_SERVER_HISTORIES);
4
5
  export function recordSuccessfulLspSpawn(serverId, durationMs) {
5
6
  if (!Number.isFinite(durationMs) || durationMs < 0)
6
7
  return;
7
8
  successfulSpawnDurationMs.delete(serverId);
8
9
  successfulSpawnDurationMs.set(serverId, durationMs);
9
- while (successfulSpawnDurationMs.size > MAX_SERVER_HISTORIES) {
10
- const oldest = successfulSpawnDurationMs.keys().next().value;
11
- if (oldest === undefined)
12
- break;
13
- successfulSpawnDurationMs.delete(oldest);
14
- }
15
10
  }
16
11
  export function getSuccessfulLspSpawnDurationMs(serverId) {
17
12
  return successfulSpawnDurationMs.get(serverId);
@@ -303,11 +303,18 @@ export async function runTsserverSyncCommand(svc, file, command) {
303
303
  * server had computed but never published (silentOnClean) — these must be
304
304
  * surfaced to the caller, not discarded. `confirmed: false` = sync path
305
305
  * unavailable, fall through to existing behavior.
306
+ *
307
+ * #2598: `getAdvertisedCommands` is REQUIRED here rather than probed for. The
308
+ * real `LSPService` defines it unconditionally (clients/lsp/index.ts), and both
309
+ * production callers hand this function that service, so the former
310
+ * `typeof … !== "function"` bail was reachable only from a partial test double
311
+ * (AGENTS.md shape 7). It was also unobservable even there: an absent method
312
+ * throws a `TypeError` that this function's own catch turns into the same
313
+ * `undefined` the bail returned. `executeCommand` stays optional on
314
+ * {@link TsserverSyncCapableService} — see {@link runTsserverSyncCommand}.
306
315
  */
307
316
  export async function attemptTsserverSyncDiagnostics(file, svc) {
308
317
  try {
309
- if (typeof svc.getAdvertisedCommands !== "function")
310
- return undefined;
311
318
  const advertised = await svc.getAdvertisedCommands(file);
312
319
  if (!advertised.includes(TSSERVER_REQUEST_COMMAND))
313
320
  return undefined;
@@ -8,6 +8,7 @@ import { normalizeMapKey } from "../path-utils.js";
8
8
  import { loadReverseDependencyIndexFromSnapshot } from "../reverse-deps.js";
9
9
  import { freshnessFromMtime } from "../freshness.js";
10
10
  import { logLatency } from "../latency-logger.js";
11
+ import { compareOrdinal } from "../string-utils.js";
11
12
  import { workspaceDiagnosticsCacheSessionStart } from "./workspace-diagnostics-session.js";
12
13
  import { createDiskBindingCache, } from "./diagnostic-binding.js";
13
14
  /**
@@ -281,7 +282,11 @@ export function classifyWorkspaceDiagnosticsCacheExpiry(entry, now, sessionStart
281
282
  * Two invalidation layers:
282
283
  * 1. The file's OWN mtime must be unchanged since it was scanned (exact
283
284
  * match against `entry.mtimeMs`) — any drift, or a stat failure
284
- * (deleted/unreadable), invalidates.
285
+ * (deleted/unreadable), invalidates. #2300: when the entry also carries a
286
+ * `sizeBytes`, the current size must match too — an external rewrite that
287
+ * lands in the same mtime bucket with a different length is otherwise
288
+ * invisible to mtime alone. A pre-#2300 entry without `sizeBytes` fails
289
+ * OPEN to the mtime-only check (see the field's doc comment).
285
290
  * 2. When a reverse-dependency index is available (`getImports` returns an
286
291
  * array rather than `undefined`), every file THIS file imports must not
287
292
  * have changed after `entry.scannedAt` either — this is the fix for the
@@ -326,15 +331,19 @@ export function classifyWorkspaceDiagnosticsCacheExpiry(entry, now, sessionStart
326
331
  * (#1491/#1498) rather than just a redundant re-touch.
327
332
  */
328
333
  export function isEntryFresh(filePath, entry, getImports) {
329
- let ownMtime;
334
+ let ownStat;
330
335
  try {
331
- ownMtime = fs.statSync(filePath).mtimeMs;
336
+ ownStat = fs.statSync(filePath);
332
337
  }
333
338
  catch {
334
339
  return false; // deleted / unreadable
335
340
  }
336
- if (ownMtime !== entry.mtimeMs)
341
+ if (ownStat.mtimeMs !== entry.mtimeMs)
337
342
  return false;
343
+ // #2300: same axis reused from the stat above — no extra syscall.
344
+ if (entry.sizeBytes !== undefined && ownStat.size !== entry.sizeBytes) {
345
+ return false;
346
+ }
338
347
  const imports = getImports(filePath);
339
348
  if (imports === undefined) {
340
349
  // #1793: an entry recorded WITH dependency knowledge must not fall
@@ -376,7 +385,7 @@ export function cacheKeyFor(filePath) {
376
385
  */
377
386
  export function buildScopeKey(clientScope, excludeServerIds) {
378
387
  const excluded = excludeServerIds
379
- ? [...excludeServerIds].sort((a, b) => a.localeCompare(b))
388
+ ? [...excludeServerIds].sort(compareOrdinal)
380
389
  : [];
381
390
  return `${clientScope}|${excluded.join(",")}`;
382
391
  }
@@ -389,7 +398,7 @@ export function createWorkspaceDiagnosticsCacheContext(cwd) {
389
398
  const epoch = _cacheEpochs.capture(root);
390
399
  const existing = loadWorkspaceDiagnosticsCache(root);
391
400
  const entries = {
392
- ...(existing?.entries ?? {}),
401
+ ...existing?.entries,
393
402
  };
394
403
  const reverseDepsIndex = loadReverseDependencyIndexFromSnapshot({
395
404
  cwd: root,
@@ -542,7 +551,7 @@ export function createWorkspaceDiagnosticsCacheContext(cwd) {
542
551
  scannedAt: entry.scannedAt,
543
552
  };
544
553
  },
545
- record(filePath, scopeKey, diagnostics, mtimeMs, contentHash) {
554
+ record(filePath, scopeKey, diagnostics, mtimeMs, contentHash, sizeBytes) {
546
555
  entries[cacheKeyFor(filePath)] = {
547
556
  diagnostics,
548
557
  count: diagnostics.length,
@@ -556,6 +565,7 @@ export function createWorkspaceDiagnosticsCacheContext(cwd) {
556
565
  // weaker mtime-only path only when that refusal is warranted.
557
566
  depIndexAtScan: hasDepKnowledge(filePath),
558
567
  ...(contentHash !== undefined && { contentHash }),
568
+ ...(sizeBytes !== undefined && { sizeBytes }),
559
569
  };
560
570
  dirty = true;
561
571
  },
@@ -40,22 +40,26 @@
40
40
  * apart again (#1618 acceptance criterion 6).
41
41
  */
42
42
  import { logLatency } from "../latency-logger.js";
43
+ import { getProcessSingleton } from "../process-singletons.js";
43
44
  /** Extra headroom beyond the sweep's own wall-clock ceiling before an
44
45
  * idle-reset delay (or the max-hold-age failsafe) trusts that a real sweep
45
46
  * must be done. Single source for both derivations below AND for every
46
47
  * idle-reset path `clients/runtime-turn.ts` derives (base, subagent, and
47
48
  * budget-shortened) — never a second independently-tunable literal. */
48
49
  export const SWEEP_IDLE_SAFETY_MARGIN_MS = 60_000;
49
- let holds = new Map(); // holdId -> acquiredAt (Date.now())
50
- let nextHoldId = 1;
51
- let idleWaiters = [];
50
+ const WORKSPACE_SWEEP_HOLD_FAMILY = "lsp.workspace-sweep-hold";
51
+ const WORKSPACE_SWEEP_HOLD_VERSION = 1;
52
+ function state() {
53
+ return getProcessSingleton(WORKSPACE_SWEEP_HOLD_FAMILY, WORKSPACE_SWEEP_HOLD_VERSION, () => ({ holds: new Map(), nextHoldId: 1, idleWaiters: [] }));
54
+ }
52
55
  function flushIdleWaitersIfEmpty() {
53
- if (holds.size > 0)
56
+ const current = state();
57
+ if (current.holds.size > 0)
54
58
  return;
55
- if (idleWaiters.length === 0)
59
+ if (current.idleWaiters.length === 0)
56
60
  return;
57
- const waiters = idleWaiters;
58
- idleWaiters = [];
61
+ const waiters = current.idleWaiters;
62
+ current.idleWaiters = [];
59
63
  for (const waiter of waiters) {
60
64
  try {
61
65
  waiter();
@@ -77,15 +81,16 @@ export function getWorkspaceSweepMaxHoldAgeMs() {
77
81
  return getFullScanWallClockMs() + SWEEP_IDLE_SAFETY_MARGIN_MS;
78
82
  }
79
83
  function reapStaleHolds() {
80
- if (holds.size === 0)
84
+ const current = state();
85
+ if (current.holds.size === 0)
81
86
  return;
82
87
  const maxAgeMs = getWorkspaceSweepMaxHoldAgeMs();
83
88
  const now = Date.now();
84
- for (const [holdId, acquiredAt] of holds) {
89
+ for (const [holdId, acquiredAt] of current.holds) {
85
90
  const ageMs = now - acquiredAt;
86
91
  if (ageMs <= maxAgeMs)
87
92
  continue;
88
- holds.delete(holdId);
93
+ current.holds.delete(holdId);
89
94
  logLatency({
90
95
  type: "phase",
91
96
  phase: "lsp_workspace_sweep_hold_force_released",
@@ -104,10 +109,11 @@ function reapStaleHolds() {
104
109
  * force-released (session reset / max-age reap), is a no-op.
105
110
  */
106
111
  export function acquireWorkspaceSweepHold() {
107
- const holdId = nextHoldId++;
108
- holds.set(holdId, Date.now());
112
+ const current = state();
113
+ const holdId = current.nextHoldId++;
114
+ current.holds.set(holdId, Date.now());
109
115
  return () => {
110
- if (!holds.delete(holdId))
116
+ if (!state().holds.delete(holdId))
111
117
  return;
112
118
  flushIdleWaitersIfEmpty();
113
119
  };
@@ -117,7 +123,7 @@ export function acquireWorkspaceSweepHold() {
117
123
  * "active" past its own max age. */
118
124
  export function isWorkspaceSweepActive() {
119
125
  reapStaleHolds();
120
- return holds.size > 0;
126
+ return state().holds.size > 0;
121
127
  }
122
128
  /**
123
129
  * Run `cb` once the hold count next returns to zero. Runs synchronously and
@@ -126,11 +132,12 @@ export function isWorkspaceSweepActive() {
126
132
  */
127
133
  export function runWhenWorkspaceSweepIdle(cb) {
128
134
  reapStaleHolds();
129
- if (holds.size === 0) {
135
+ const current = state();
136
+ if (current.holds.size === 0) {
130
137
  cb();
131
138
  return;
132
139
  }
133
- idleWaiters.push(cb);
140
+ current.idleWaiters.push(cb);
134
141
  }
135
142
  /**
136
143
  * `resetLSPService({reason: "session_start"})` calls this: a session
@@ -142,23 +149,25 @@ export function runWhenWorkspaceSweepIdle(cb) {
142
149
  * silent.
143
150
  */
144
151
  export function clearWorkspaceSweepHoldForSessionStart() {
145
- if (holds.size === 0)
152
+ const current = state();
153
+ if (current.holds.size === 0)
146
154
  return;
147
155
  logLatency({
148
156
  type: "phase",
149
157
  phase: "lsp_workspace_sweep_hold_session_reset",
150
158
  filePath: "",
151
159
  durationMs: 0,
152
- metadata: { clearedHolds: holds.size },
160
+ metadata: { clearedHolds: current.holds.size },
153
161
  });
154
- holds.clear();
162
+ current.holds.clear();
155
163
  flushIdleWaitersIfEmpty();
156
164
  }
157
- /** Test-only: reset the module-scope hold state between tests. */
165
+ /** Test-only: reset the process-singleton hold state between tests. */
158
166
  export function _resetWorkspaceSweepHoldForTests() {
159
- holds = new Map();
160
- idleWaiters = [];
161
- nextHoldId = 1;
167
+ const current = state();
168
+ current.holds.clear();
169
+ current.idleWaiters = [];
170
+ current.nextHoldId = 1;
162
171
  }
163
172
  /**
164
173
  * Wall-clock ceiling for one `lens_diagnostics mode=full` scan