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
@@ -7,10 +7,12 @@
7
7
  import * as crypto from "node:crypto";
8
8
  import * as fs from "node:fs";
9
9
  import * as path from "node:path";
10
+ import { reportBundledResourceDirHealth } from "../bundled-resource-health.js";
10
11
  import { getProjectDataDir } from "../file-utils.js";
11
12
  import { readJsonCache } from "../json-cache-read.js";
12
- import { resolvePackagePath } from "../package-root.js";
13
+ import { BUNDLED_QUERIES_ROOT, getBundledQueriesRootHealth, } from "../tree-sitter-query-loader.js";
13
14
  import { writeFileAtomic } from "../atomic-write.js";
15
+ import { compareOrdinal } from "../string-utils.js";
14
16
  // v4: cache skip_test_files + fix_action — v3 entries silently dropped them,
15
17
  // and ruleHash (rule-file mtimes) never invalidates on a code-only fix.
16
18
  // v5 (#675): rule SELECTION changed in code — javascript no longer inherits the
@@ -47,11 +49,36 @@ export const CACHE_VERSION = "v7";
47
49
  * on this prefix mirrors the split `yaml-rule-parser.ts`/`ast-grep-napi.ts`
48
50
  * already use for the ast-grep side of the same class (#1105): project-origin
49
51
  * trees get a content-hash CONFIRM, bundled trees stay mtime-cheap.
52
+ *
53
+ * #2636 review F4: re-exported from `tree-sitter-query-loader.ts`'s
54
+ * `BUNDLED_QUERIES_ROOT` rather than computed a second time here — the two
55
+ * modules read the IDENTICAL physical directory, and the "one ledger row"
56
+ * claim below rests on that being reference equality, not two independently
57
+ * `resolvePackagePath`-resolved strings that happen to match.
50
58
  */
51
- const BUNDLED_RULES_ROOT = resolvePackagePath(import.meta.url, "rules", "tree-sitter-queries");
59
+ export { BUNDLED_QUERIES_ROOT as BUNDLED_RULES_ROOT } from "../tree-sitter-query-loader.js";
52
60
  function isBundledRuleFile(resolvedFile) {
53
- return (resolvedFile === BUNDLED_RULES_ROOT ||
54
- resolvedFile.startsWith(BUNDLED_RULES_ROOT + path.sep));
61
+ return (resolvedFile === BUNDLED_QUERIES_ROOT ||
62
+ resolvedFile.startsWith(BUNDLED_QUERIES_ROOT + path.sep));
63
+ }
64
+ /**
65
+ * #2636 (the #2626 class sweep's tree-sitter leg): the bundled
66
+ * tree-sitter-queries root above was used unconditionally with no existence
67
+ * check — same managed-cache-relocation gap #2626 fixed for `skills/`.
68
+ * Purely observational (never gates `isBundledRuleFile`'s classification):
69
+ * records a bounded `tree-sitter-queries-dir-missing` degradation only when
70
+ * the bundled root is absent, unreadable, or (uncommonly) present but empty.
71
+ * Shares the exact kind + subject `clients/tree-sitter-query-loader.ts`'s
72
+ * `ruleFilesForLanguage` reports under — both read the SAME physical
73
+ * directory (`getBundledQueriesRootHealth`'s process-lifetime memo, #2636
74
+ * review F6 — this constructor runs per dispatched file, so an unmemoized
75
+ * `readdirSync` here would be the same unconditional hot-path cost the
76
+ * class doc comment above forbids for rule-file hashing), so the ledger's
77
+ * own (kind, subject) dedup collapses whichever call site observes it first
78
+ * into ONE row rather than two duplicates.
79
+ */
80
+ function reportBundledRulesRootHealth() {
81
+ reportBundledResourceDirHealth("tree-sitter-queries-dir-missing", BUNDLED_QUERIES_ROOT, getBundledQueriesRootHealth(), "bundled tree-sitter query rules");
55
82
  }
56
83
  export class RuleCache {
57
84
  cacheFile;
@@ -59,6 +86,7 @@ export class RuleCache {
59
86
  language;
60
87
  constructor(language, rootDir = process.cwd()) {
61
88
  this.language = language;
89
+ reportBundledRulesRootHealth();
62
90
  this.cacheDir = path.join(getProjectDataDir(rootDir), "cache");
63
91
  this.cacheFile = path.join(this.cacheDir, `${language}-rules-${CACHE_VERSION}.json`);
64
92
  }
@@ -69,7 +97,7 @@ export class RuleCache {
69
97
  }
70
98
  computeRuleHash(ruleFiles) {
71
99
  const hash = crypto.createHash("sha256");
72
- for (const file of ruleFiles.sort((a, b) => a.localeCompare(b))) {
100
+ for (const file of ruleFiles.sort(compareOrdinal)) {
73
101
  const resolved = path.resolve(file);
74
102
  if (!fs.existsSync(resolved))
75
103
  continue;
@@ -3,10 +3,14 @@
3
3
  *
4
4
  * Manages persistent cache for scanner results and turn state.
5
5
  * Provides read/write/freshness checks for:
6
- * - Scanner cache: .pi-lens/cache/{scanner}.json
7
- * - Turn state: .pi-lens/turn-state.json
6
+ * - Scanner cache: <project-data-dir>/cache/{scanner}.json
7
+ * - Turn state: <project-data-dir>/turn-state.json
8
8
  *
9
- * All paths are relative to project root (process.cwd()).
9
+ * `<project-data-dir>` is `getProjectDataDir(cwd)` (clients/file-utils.ts) --
10
+ * `<cwd>/.pi-lens` only when that legacy directory already exists, otherwise
11
+ * `~/.pi-lens/projects/<slug>` or a `PILENS_DATA_DIR` location. Never assume
12
+ * the legacy spelling, and never show it to an agent: build a displayed path
13
+ * with `displayProjectDataPath` (#2521).
10
14
  */
11
15
  import { createSubsystemLogger } from "./extension-log.js";
12
16
  import * as fs from "node:fs";
@@ -14,7 +18,7 @@ import * as path from "node:path";
14
18
  import { getProjectDataDir } from "./file-utils.js";
15
19
  import { writeFileAtomic } from "./atomic-write.js";
16
20
  import { readJsonCache } from "./json-cache-read.js";
17
- import { normalizeMapKey } from "./path-utils.js";
21
+ import { isUnderDir, normalizeMapKey } from "./path-utils.js";
18
22
  // --- Defaults ---
19
23
  const DEFAULT_MAX_AGE_MS = 30 * 60 * 1000; // 30 minutes
20
24
  const DEFAULT_TURN_STATE = {
@@ -58,6 +62,31 @@ export class CacheManager {
58
62
  /**
59
63
  * Get turn-state entry for a file path using normalized lookup.
60
64
  */
65
+ /**
66
+ * #2504: is this path inside the PROJECT the worklist belongs to?
67
+ *
68
+ * Routed through `isUnderDir` (which normalises via `normalizeFilePath`) so
69
+ * the answer is separator- and case-form independent, the same key rule
70
+ * every other turn-state map obeys.
71
+ *
72
+ * The root is the PROJECT root, not the caller's `cwd` (#2504 review round
73
+ * 2, F1). The two coincide for every producer whose `cwd` IS the session
74
+ * root, but not for `clients/lsp-mutation.ts`: `tools/lsp-navigation.ts`
75
+ * threads a `cwd`-scoped context for a call issued from a sub-package
76
+ * directory, so a monorepo-wide server's edit on a SIBLING package is
77
+ * inside the project and outside that `cwd`. Judging it against `cwd`
78
+ * dropped exactly the server-initiated edits #2450/#2479 exist to record.
79
+ * This is the same root `isRecordableProjectPath` (clients/file-utils.ts)
80
+ * is given at that call site — one predicate root, taken from
81
+ * `runtime.projectRoot`, not two that can disagree.
82
+ */
83
+ isTurnStatePathWithinRoot(filePath, projectRoot) {
84
+ const root = path.resolve(projectRoot);
85
+ const abs = path.resolve(root, filePath);
86
+ if (normalizeMapKey(abs) === normalizeMapKey(root))
87
+ return false;
88
+ return isUnderDir(abs, root);
89
+ }
61
90
  getTurnFileState(filePath, cwd) {
62
91
  const state = this.readTurnState(cwd);
63
92
  const key = this.toTurnStateKey(filePath, cwd);
@@ -217,9 +246,22 @@ export class CacheManager {
217
246
  /** Return whether a writer may read/write this workspace worklist. */
218
247
  getTurnStateAccess(cwd, owner) {
219
248
  const state = this.readTurnState(cwd);
249
+ const hasFiles = Object.keys(state.files ?? {}).length > 0;
220
250
  if (!state.owner) {
221
- if (!state.sessionId || state.sessionId === owner.id)
251
+ if (!state.sessionId || state.sessionId === owner.id) {
252
+ // #2504: an OWNERLESS state was unconditionally "owned" here, and
253
+ // ownerless was the resting shape (pre-#2504 `clearTurnState`
254
+ // dropped the owner and `addModifiedRange` only stamps one when a
255
+ // sessionId is supplied). A worklist persisted by a session that
256
+ // has since exited was therefore adopted wholesale by the next
257
+ // session — 154 historical paths reported as "modified this turn"
258
+ // on a turn that made no tool calls at all. A worklist whose last
259
+ // write predates this session's start is not this session's work.
260
+ if (hasFiles && this.turnStatePredatesSession(state, owner)) {
261
+ return "available";
262
+ }
222
263
  return "owned";
264
+ }
223
265
  // Pre-owner files have no liveness information. Preserve the existing
224
266
  // stale-session eviction behavior for this legacy shape.
225
267
  return "available";
@@ -227,10 +269,35 @@ export class CacheManager {
227
269
  if (state.owner.kind === owner.kind && state.owner.id === owner.id) {
228
270
  return "owned";
229
271
  }
272
+ // #2504: an EMPTY worklist carries nothing to protect, so it never
273
+ // latches a foreign writer out. This keeps the new `clearTurnState`
274
+ // owner stamp from turning a cleared worklist into a cross-process lock:
275
+ // an MCP writer in another process must still be able to register work
276
+ // against a worklist a pi session just cleared, exactly as it could when
277
+ // clearing left the state ownerless.
278
+ if (!hasFiles)
279
+ return "owned";
230
280
  return this.isTurnStateOwnerStale(state.owner)
231
281
  ? "available"
232
282
  : "foreign-live";
233
283
  }
284
+ /**
285
+ * #2504: true when this worklist was last written before the asking
286
+ * session began. `lastUpdated` is restamped by `writeTurnState` on EVERY
287
+ * write (add/clear/cycle), so a live session's own worklist is always
288
+ * newer than its start; only a carried-over file can be older. An
289
+ * unparseable stamp is treated as stale — a worklist we cannot date is
290
+ * exactly the legacy shape this gate exists to evict.
291
+ */
292
+ turnStatePredatesSession(state, owner) {
293
+ const startedAt = owner.sessionStartedAt;
294
+ if (startedAt === undefined || !Number.isFinite(startedAt))
295
+ return false;
296
+ const lastUpdated = Date.parse(state.lastUpdated ?? "");
297
+ if (!Number.isFinite(lastUpdated))
298
+ return true;
299
+ return lastUpdated < startedAt;
300
+ }
234
301
  isTurnStateOwnerStale(owner) {
235
302
  if (owner.pid > 0 && owner.pid !== process.pid) {
236
303
  try {
@@ -250,7 +317,24 @@ export class CacheManager {
250
317
  * current owner; callers must provide an explicit owner id to claim a stale
251
318
  * worklist.
252
319
  */
253
- addModifiedRange(filePath, range, importsChanged, cwd, sessionId, ownerKind = "pi") {
320
+ addModifiedRange(filePath, range, importsChanged, cwd, sessionId, ownerKind = "pi", projectRoot) {
321
+ // #2504: the worklist is a PROJECT worklist. A path outside the project
322
+ // was accepted and keyed by its absolute path, so a prior session's
323
+ // scratchpad, `~/.claude/plans/*.md` and `~/.plegma/work/.../TASK.md`
324
+ // all became "modified files" that turn_end then fed to the test runner
325
+ // and opened in an LSP client. Reject before the owner stamp below, so
326
+ // an out-of-project write cannot claim the worklist either.
327
+ //
328
+ // `projectRoot` defaults to `cwd` — true for every producer whose cwd
329
+ // IS the session root. A caller whose `cwd` is NARROWER than the
330
+ // project (an LSP call issued from a sub-package directory) passes the
331
+ // real root explicitly; `cwd` still selects which `turn-state.json`
332
+ // the entry lands in, exactly as before (#2504 review round 2, F1).
333
+ const containmentRoot = projectRoot ?? cwd;
334
+ if (!this.isTurnStatePathWithinRoot(filePath, containmentRoot)) {
335
+ this.log(`turn-state: rejected out-of-project path ${filePath} (project root ${containmentRoot})`);
336
+ return this.readTurnState(cwd);
337
+ }
254
338
  const state = this.readTurnState(cwd);
255
339
  if (sessionId) {
256
340
  const owner = {
@@ -293,10 +377,25 @@ export class CacheManager {
293
377
  const isCurrentOwner = this.getTurnStateAccess(cwd, owner) !== "foreign-live";
294
378
  if (!isCurrentOwner && process.pid !== currentState.owner?.pid)
295
379
  return false;
380
+ // #2504: stamp the CLEARING owner. Dropping `owner`/`sessionId` here made
381
+ // ownerless the resting shape of turn-state.json, which is what let the
382
+ // next session read a carried-over worklist as its own. A cleared state
383
+ // now says who cleared it and when, so `getTurnStateAccess` can judge it
384
+ // by liveness like any other owned state — and, since the worklist is
385
+ // empty, the stamp never latches another writer out (see the
386
+ // empty-worklist branch there).
296
387
  const state = {
297
388
  ...DEFAULT_TURN_STATE,
298
389
  files: {}, // fresh object — DEFAULT_TURN_STATE.files can be polluted by addModifiedRange
299
390
  lastUpdated: new Date().toISOString(),
391
+ sessionId: owner.id,
392
+ owner: {
393
+ kind: owner.kind,
394
+ id: owner.id,
395
+ pid: process.pid,
396
+ lastSeen: new Date().toISOString(),
397
+ sessionStartedAt: owner.sessionStartedAt,
398
+ },
300
399
  };
301
400
  this.writeTurnState(state, cwd);
302
401
  return true;
@@ -48,8 +48,10 @@
48
48
  * event handlers.
49
49
  */
50
50
  import { createHash } from "node:crypto";
51
+ import { BoundedFifoMap, BoundedLruCache } from "./bounded-cache.js";
51
52
  import { lazyEnvNumber } from "./env-utils.js";
52
53
  import { logLatency } from "./latency-logger.js";
54
+ import { compareOrdinal } from "./string-utils.js";
53
55
  /** RuntimeCoordinator's counter is process-global when sessions share a host. */
54
56
  const PROCESS_TURN_SCOPE = "process-global-runtime";
55
57
  const SECONDARY_TURN_SCOPE = "unavailable-concurrent-secondary";
@@ -160,7 +162,11 @@ function newCacheUsageSummary() {
160
162
  unknownEvidenceReasons: emptyUnknownEvidenceReasons(),
161
163
  };
162
164
  }
163
- const attributionBySession = new Map();
165
+ // Shared cap for every per-session bounded map in this module (also used by
166
+ // `prefixHashBySession` below) — a long-lived process cycling through many
167
+ // sessions cannot grow any of them without limit.
168
+ export const MAX_TRACKED_SESSIONS = 32;
169
+ const attributionBySession = new BoundedLruCache(MAX_TRACKED_SESSIONS);
164
170
  function newAttributionState() {
165
171
  return {
166
172
  prefixBrokeSinceLastUsage: false,
@@ -182,14 +188,9 @@ function newAttributionState() {
182
188
  function attributionFor(key) {
183
189
  const existing = attributionBySession.get(key);
184
190
  const state = existing ?? newAttributionState();
185
- attributionBySession.delete(key);
191
+ // `get` above already moved an existing entry to MRU; `set` below also
192
+ // refreshes recency on a create, so every fetch counts as a touch.
186
193
  attributionBySession.set(key, state);
187
- while (attributionBySession.size > MAX_TRACKED_SESSIONS) {
188
- const oldest = attributionBySession.keys().next().value;
189
- if (oldest === undefined)
190
- break;
191
- attributionBySession.delete(oldest);
192
- }
193
194
  return state;
194
195
  }
195
196
  /**
@@ -490,7 +491,7 @@ function boundedHashArray(value, depth, seen, state) {
490
491
  return `array:${value.length}:[${items.join(",")}]`;
491
492
  }
492
493
  function boundedHashObject(value, depth, seen, state) {
493
- const keys = Object.keys(value).sort((left, right) => left.localeCompare(right));
494
+ const keys = Object.keys(value).sort((left, right) => compareOrdinal(left, right));
494
495
  if (keys.length > 24)
495
496
  state.contentTruncated = true;
496
497
  const fields = keys
@@ -998,11 +999,13 @@ function hashFirstMessage(first) {
998
999
  * session state (read guard #1041, widget #190) which IS rehydrated from the
999
1000
  * sidecar.
1000
1001
  *
1001
- * Bounded as an insertion-ordered LRU (evict oldest-inserted past the cap) so a
1002
- * long-lived process cycling through many sessions can't grow this unbounded.
1002
+ * Bounded as insertion-ordered FIFO (evict oldest-inserted past the cap,
1003
+ * refreshed on every WRITE by {@link recordSessionHash}'s delete+set — a
1004
+ * plain read never reorders) so a long-lived process cycling through many
1005
+ * sessions can't grow this unbounded. Shares {@link MAX_TRACKED_SESSIONS}
1006
+ * with `attributionBySession` above.
1003
1007
  */
1004
- const MAX_TRACKED_SESSIONS = 32;
1005
- const prefixHashBySession = new Map();
1008
+ const prefixHashBySession = new BoundedFifoMap(MAX_TRACKED_SESSIONS);
1006
1009
  /**
1007
1010
  * Reported key used when no session id is available (undefined/empty). Internal
1008
1011
  * attribution additionally separates primary and concurrent-secondary roles;
@@ -1018,12 +1021,6 @@ const NO_SESSION_KEY = "<no-session>";
1018
1021
  function recordSessionHash(key, hash) {
1019
1022
  prefixHashBySession.delete(key);
1020
1023
  prefixHashBySession.set(key, hash);
1021
- while (prefixHashBySession.size > MAX_TRACKED_SESSIONS) {
1022
- const oldest = prefixHashBySession.keys().next().value;
1023
- if (oldest === undefined)
1024
- break;
1025
- prefixHashBySession.delete(oldest);
1026
- }
1027
1024
  }
1028
1025
  /**
1029
1026
  * Part 2 — observe `messages[0]` stability turn-over-turn, keyed by session id.
@@ -1168,3 +1165,33 @@ export function resetCachePrefixObservation() {
1168
1165
  prefixHashBySession.clear();
1169
1166
  attributionBySession.clear();
1170
1167
  }
1168
+ /** #2442 test-only membership reads, bypassing emitCacheUsageSummaryAtSessionEnd's
1169
+ * usageRecords>0 gate and log side effects. */
1170
+ export function _attributionKeyForTests(sessionId, sessionRole) {
1171
+ return attributionKey(sessionId, sessionRole);
1172
+ }
1173
+ export function _attributionBySessionHasForTests(key) {
1174
+ return attributionBySession.has(key);
1175
+ }
1176
+ export function _prefixHashBySessionHasForTests(key) {
1177
+ return prefixHashBySession.has(key);
1178
+ }
1179
+ /** #2442 test-only: drive attributionFor's fetch-or-create touch directly
1180
+ * (every call refreshes LRU recency, matching production — see
1181
+ * attributionFor's own doc comment). */
1182
+ export function _touchAttributionForTests(key) {
1183
+ attributionFor(key);
1184
+ }
1185
+ /** #2442 test-only: drive recordSessionHash's write-refresh directly. */
1186
+ export function _recordSessionHashForTests(key, hash) {
1187
+ recordSessionHash(key, hash);
1188
+ }
1189
+ /**
1190
+ * #2442 test-only: the exact `prefixHashBySession.get(key)` that
1191
+ * `observeCachePrefix` performs to fetch a session's previous hash. A
1192
+ * `.has()` reorders nothing on either bounded class, so only a real `get`
1193
+ * can pin FIFO-vs-LRU behavior (#2442 review F4).
1194
+ */
1195
+ export function _prefixHashBySessionGetForTests(key) {
1196
+ return prefixHashBySession.get(key);
1197
+ }