pi-lens 3.8.73 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (346) hide show
  1. package/CHANGELOG.md +2614 -12
  2. package/README.md +50 -21
  3. package/dist/clients/actionable-warnings.js +73 -18
  4. package/dist/clients/agent-behavior-client.js +21 -4
  5. package/dist/clients/ast-grep-client.js +68 -16
  6. package/dist/clients/ast-grep-types.js +0 -1
  7. package/dist/clients/ast-grep-yaml-synth.js +37 -10
  8. package/dist/clients/atomic-write-staging.js +63 -0
  9. package/dist/clients/atomic-write.js +116 -16
  10. package/dist/clients/bash-file-access.js +193 -23
  11. package/dist/clients/biome-client.js +29 -25
  12. package/dist/clients/bootstrap.js +11 -2
  13. package/dist/clients/bounded-cache.js +34 -0
  14. package/dist/clients/bounded-pid-file-lock.js +192 -0
  15. package/dist/clients/bus-events-logger.js +5 -1
  16. package/dist/clients/bus-publish.js +10 -3
  17. package/dist/clients/cache/rule-cache.js +89 -6
  18. package/dist/clients/cache-manager.js +97 -9
  19. package/dist/clients/cache-observability.js +498 -0
  20. package/dist/clients/call-graph.js +460 -108
  21. package/dist/clients/cascade-logger.js +5 -1
  22. package/dist/clients/child-unref.js +85 -0
  23. package/dist/clients/codebase-model.js +52 -23
  24. package/dist/clients/collateral-test-role.js +40 -0
  25. package/dist/clients/complexity-client.js +6 -8
  26. package/dist/clients/console-guard-install.js +14 -0
  27. package/dist/clients/cooperative-budget.js +42 -0
  28. package/dist/clients/dead-code-client.js +2 -1
  29. package/dist/clients/debug-handles.js +206 -0
  30. package/dist/clients/debug-heap.js +167 -0
  31. package/dist/clients/degradation-ledger.js +153 -0
  32. package/dist/clients/dependency-checker.js +466 -63
  33. package/dist/clients/diagnostic-dispositions.js +83 -13
  34. package/dist/clients/diagnostics-publish.js +10 -3
  35. package/dist/clients/dispatch/auxiliary-lsp.js +34 -0
  36. package/dist/clients/dispatch/dispatcher.js +71 -13
  37. package/dist/clients/dispatch/fact-store.js +11 -0
  38. package/dist/clients/dispatch/facts/function-facts.js +37 -7
  39. package/dist/clients/dispatch/facts/import-facts.js +108 -119
  40. package/dist/clients/dispatch/facts/tree-sitter-facts.js +12 -12
  41. package/dist/clients/dispatch/facts/try-catch-facts.js +13 -0
  42. package/dist/clients/dispatch/indent-detect.js +35 -0
  43. package/dist/clients/dispatch/inline-suppressions.js +17 -8
  44. package/dist/clients/dispatch/integration.js +1094 -611
  45. package/dist/clients/dispatch/lazy.js +14 -0
  46. package/dist/clients/dispatch/plan.js +11 -1
  47. package/dist/clients/dispatch/rule-id-normalize.js +50 -0
  48. package/dist/clients/dispatch/rule-policy.js +135 -0
  49. package/dist/clients/dispatch/rules/async-noise.js +1 -0
  50. package/dist/clients/dispatch/rules/unsafe-boundary.js +7 -0
  51. package/dist/clients/dispatch/runners/ast-grep-napi.js +47 -6
  52. package/dist/clients/dispatch/runners/biome-check.js +5 -9
  53. package/dist/clients/dispatch/runners/detekt.js +3 -2
  54. package/dist/clients/dispatch/runners/hadolint.js +2 -1
  55. package/dist/clients/dispatch/runners/helm-lint.js +154 -0
  56. package/dist/clients/dispatch/runners/htmlhint.js +3 -2
  57. package/dist/clients/dispatch/runners/index.js +5 -1
  58. package/dist/clients/dispatch/runners/lsp.js +14 -6
  59. package/dist/clients/dispatch/runners/markdownlint.js +4 -9
  60. package/dist/clients/dispatch/runners/psscriptanalyzer.js +10 -1
  61. package/dist/clients/dispatch/runners/pyright.js +9 -5
  62. package/dist/clients/dispatch/runners/ruff.js +4 -8
  63. package/dist/clients/dispatch/runners/shellcheck.js +2 -3
  64. package/dist/clients/dispatch/runners/shfmt.js +2 -3
  65. package/dist/clients/dispatch/runners/spotbugs.js +2 -1
  66. package/dist/clients/dispatch/runners/taplo.js +2 -1
  67. package/dist/clients/dispatch/runners/terragrunt.js +163 -0
  68. package/dist/clients/dispatch/runners/tflint.js +37 -5
  69. package/dist/clients/dispatch/runners/tree-sitter.js +155 -166
  70. package/dist/clients/dispatch/runners/trivy-config.js +13 -7
  71. package/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
  72. package/dist/clients/dispatch/runners/utils/runner-helpers.js +284 -34
  73. package/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
  74. package/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
  75. package/dist/clients/disposition-publish.js +10 -3
  76. package/dist/clients/durable-store.js +97 -0
  77. package/dist/clients/event-loop-monitor.js +94 -3
  78. package/dist/clients/extension-log.js +139 -0
  79. package/dist/clients/extension-mode.js +73 -0
  80. package/dist/clients/feature-hints.js +53 -7
  81. package/dist/clients/file-kinds.js +143 -31
  82. package/dist/clients/file-role.js +18 -3
  83. package/dist/clients/file-utils.js +56 -34
  84. package/dist/clients/fix-worklog.js +14 -2
  85. package/dist/clients/format-events-publish.js +16 -3
  86. package/dist/clients/format-service.js +24 -8
  87. package/dist/clients/formatters-lazy.js +8 -0
  88. package/dist/clients/formatters.js +358 -61
  89. package/dist/clients/generated-artifacts.js +169 -15
  90. package/dist/clients/git-guard.js +681 -11
  91. package/dist/clients/git-tracked-ignore.js +4 -0
  92. package/dist/clients/go-client.js +2 -1
  93. package/dist/clients/govulncheck-client.js +8 -0
  94. package/dist/clients/grammar-source.js +12 -4
  95. package/dist/clients/gzip-stage-write.js +102 -0
  96. package/dist/clients/host-ports.js +31 -0
  97. package/dist/clients/installer/index.js +733 -213
  98. package/dist/clients/instance-reaper.js +142 -172
  99. package/dist/clients/jscpd-client.js +31 -54
  100. package/dist/clients/knip-client.js +78 -36
  101. package/dist/clients/language-policy.js +14 -2
  102. package/dist/clients/language-profile.js +72 -77
  103. package/dist/clients/latency-logger.js +28 -16
  104. package/dist/clients/lens-config.js +140 -166
  105. package/dist/clients/lens-engine.js +124 -21
  106. package/dist/clients/lens-events.js +27 -4
  107. package/dist/clients/lens-flag-registry.js +293 -0
  108. package/dist/clients/lens-map.js +96 -26
  109. package/dist/clients/live-bus-emitter.js +30 -0
  110. package/dist/clients/log-cleanup.js +8 -18
  111. package/dist/clients/lsp/cascade-tier.js +23 -3
  112. package/dist/clients/lsp/client.js +566 -57
  113. package/dist/clients/lsp/config.js +74 -10
  114. package/dist/clients/lsp/diagnostic-binding.js +86 -0
  115. package/dist/clients/lsp/edits.js +1012 -121
  116. package/dist/clients/lsp/index.js +1840 -803
  117. package/dist/clients/lsp/language.js +4 -1
  118. package/dist/clients/lsp/launch.js +40 -32
  119. package/dist/clients/lsp/path-utils.js +1 -1
  120. package/dist/clients/lsp/ruby-drive-dirs.js +83 -0
  121. package/dist/clients/lsp/server.js +279 -60
  122. package/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
  123. package/dist/clients/lsp-document-symbols.js +112 -0
  124. package/dist/clients/lsp-lazy.js +8 -0
  125. package/dist/clients/lsp-mutation.js +248 -0
  126. package/dist/clients/mcp/analyze.js +128 -36
  127. package/dist/clients/mcp/ipc.js +157 -24
  128. package/dist/clients/mcp/review.js +36 -3
  129. package/dist/clients/mcp/session.js +216 -8
  130. package/dist/clients/memory-sampler.js +115 -0
  131. package/dist/clients/metrics-client.js +2 -1
  132. package/dist/clients/metrics-history.js +2 -1
  133. package/dist/clients/module-report-lsp.js +5 -0
  134. package/dist/clients/module-report.js +267 -82
  135. package/dist/clients/ndjson-logger.js +334 -103
  136. package/dist/clients/opengrep-client.js +1 -1
  137. package/dist/clients/partial-edit-apply.js +120 -5
  138. package/dist/clients/path-keyed-map.js +76 -0
  139. package/dist/clients/path-utils.js +141 -5
  140. package/dist/clients/performance-report.js +327 -0
  141. package/dist/clients/pipeline.js +54 -13
  142. package/dist/clients/print-mode.js +21 -0
  143. package/dist/clients/project-changes.js +117 -4
  144. package/dist/clients/project-diagnostics/cache.js +3 -2
  145. package/dist/clients/project-diagnostics/extractors.js +26 -115
  146. package/dist/clients/project-diagnostics/fresh-fetch.js +149 -19
  147. package/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +117 -0
  148. package/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +37 -5
  149. package/dist/clients/project-diagnostics/scanner.js +268 -128
  150. package/dist/clients/project-lens-config.js +138 -34
  151. package/dist/clients/project-report.js +64 -25
  152. package/dist/clients/project-scale.js +8 -10
  153. package/dist/clients/project-snapshot-persist-worker.js +6 -0
  154. package/dist/clients/project-snapshot.js +767 -8
  155. package/dist/clients/project-trust.js +181 -0
  156. package/dist/clients/quiet-window.js +27 -5
  157. package/dist/clients/read-bridge.js +147 -0
  158. package/dist/clients/read-expansion.js +22 -12
  159. package/dist/clients/read-guard-logger.js +159 -1
  160. package/dist/clients/read-guard-tool-lines.js +150 -29
  161. package/dist/clients/read-guard.js +262 -3
  162. package/dist/clients/redact/secrets.js +449 -0
  163. package/dist/clients/resource-sampler.js +144 -39
  164. package/dist/clients/reverse-deps.js +48 -0
  165. package/dist/clients/review-graph/builder.js +2570 -326
  166. package/dist/clients/review-graph/git-identity.js +2 -1
  167. package/dist/clients/review-graph/import-resolvers.js +21 -10
  168. package/dist/clients/review-graph/persist-worker.js +10 -0
  169. package/dist/clients/review-graph/query.js +5 -0
  170. package/dist/clients/review-graph/shared-extraction-ir.js +90 -0
  171. package/dist/clients/review-graph/symbol-id.js +46 -0
  172. package/dist/clients/review-graph/tsconfig-paths.js +57 -6
  173. package/dist/clients/review-graph/workspace-modules.js +51 -3
  174. package/dist/clients/review-graph-logger.js +53 -0
  175. package/dist/clients/ruff-client.js +27 -44
  176. package/dist/clients/runtime-agent-end.js +111 -75
  177. package/dist/clients/runtime-context.js +36 -12
  178. package/dist/clients/runtime-coordinator.js +76 -8
  179. package/dist/clients/runtime-session.js +799 -137
  180. package/dist/clients/runtime-tool-call.js +117 -62
  181. package/dist/clients/runtime-tool-result.js +218 -23
  182. package/dist/clients/runtime-turn.js +284 -40
  183. package/dist/clients/rust-client.js +2 -1
  184. package/dist/clients/safe-spawn.js +873 -71
  185. package/dist/clients/security-scan-client.js +2 -1
  186. package/dist/clients/session-start-observability.js +15 -0
  187. package/dist/clients/session-state-store.js +5 -3
  188. package/dist/clients/sessionstart-logger.js +18 -0
  189. package/dist/clients/sg-runner.js +284 -159
  190. package/dist/clients/slow-fs.js +2 -1
  191. package/dist/clients/smells-rollup.js +224 -0
  192. package/dist/clients/source-filter.js +231 -43
  193. package/dist/clients/source-walker.js +66 -10
  194. package/dist/clients/startup-scan.js +15 -16
  195. package/dist/clients/startup-timing.js +9 -0
  196. package/dist/clients/test-runner-client.js +38 -8
  197. package/dist/clients/todo-scanner.js +30 -3
  198. package/dist/clients/tool-event.js +35 -0
  199. package/dist/clients/tool-policy.js +388 -39
  200. package/dist/clients/tool-render.js +155 -0
  201. package/dist/clients/tree-sitter-cache.js +153 -140
  202. package/dist/clients/tree-sitter-client.js +1722 -80
  203. package/dist/clients/tree-sitter-logger.js +64 -1
  204. package/dist/clients/tree-sitter-query-loader.js +117 -12
  205. package/dist/clients/tree-sitter-shared.js +48 -19
  206. package/dist/clients/tree-sitter-symbol-extractor.js +115 -11
  207. package/dist/clients/user-notify.js +51 -0
  208. package/dist/clients/vanished-instance-marker.js +57 -0
  209. package/dist/clients/warm-attach.js +33 -13
  210. package/dist/clients/widget-state.js +342 -49
  211. package/dist/clients/word-index-logger.js +44 -0
  212. package/dist/clients/word-index.js +612 -68
  213. package/dist/clients/workspace-topology.js +100 -7
  214. package/dist/index.js +65923 -47161
  215. package/dist/mcp/analyze-cli.js +185 -29
  216. package/dist/mcp/cli.js +110 -0
  217. package/dist/mcp/server.js +272 -50
  218. package/dist/tools/ast-grep-search.js +93 -27
  219. package/dist/tools/lens-diagnostics.js +247 -41
  220. package/dist/tools/lsp-diagnostics.js +153 -40
  221. package/dist/tools/lsp-navigation.js +51 -31
  222. package/dist/tools/module-report.js +9 -0
  223. package/dist/tools/symbol-search.js +21 -2
  224. package/docs/agent-guide.md +247 -0
  225. package/docs/agent-tools.md +21 -10
  226. package/docs/analysisall.md +192 -0
  227. package/docs/api-ports-inventory.md +165 -0
  228. package/docs/ast-grep_rules_catalog.md +5 -2
  229. package/docs/astplayground.md +3 -4
  230. package/docs/audit1.md +5 -5
  231. package/docs/durable-store-audit-1202.md +32 -0
  232. package/docs/environment-variables.md +197 -11
  233. package/docs/fable.md +27 -0
  234. package/docs/features.md +35 -5
  235. package/docs/globalconfig.md +101 -5
  236. package/docs/language-coverage.md +6 -5
  237. package/docs/lsp-capability-matrix.md +2 -2
  238. package/docs/mcp.md +54 -20
  239. package/docs/servercapabilities.md +7 -4
  240. package/docs/settings.md +192 -0
  241. package/docs/tools.md +18 -1
  242. package/docs/tree-sitter_rules_catalog.md +11 -31
  243. package/docs/usage.md +31 -0
  244. package/package.json +139 -138
  245. package/rules/ast-grep-rules/rule-tests/hardcoded-url-js-test.yml +1 -0
  246. package/rules/ast-grep-rules/rule-tests/hardcoded-url-test.yml +2 -0
  247. package/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
  248. package/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
  249. package/rules/ast-grep-rules/rule-tests/no-case-declarations-js-test.yml +10 -0
  250. package/rules/ast-grep-rules/rule-tests/no-case-declarations-test.yml +10 -0
  251. package/rules/ast-grep-rules/rule-tests/no-javascript-url-js-test.yml +10 -0
  252. package/rules/ast-grep-rules/rule-tests/no-javascript-url-test.yml +10 -0
  253. package/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
  254. package/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
  255. package/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
  256. package/rules/ast-grep-rules/rule-tests/strict-equality-js-test.yml +3 -0
  257. package/rules/ast-grep-rules/rule-tests/strict-equality-test.yml +3 -0
  258. package/rules/ast-grep-rules/rule-tests/strict-inequality-js-test.yml +3 -0
  259. package/rules/ast-grep-rules/rule-tests/strict-inequality-test.yml +4 -0
  260. package/rules/ast-grep-rules/rules/hardcoded-url-js.yml +22 -4
  261. package/rules/ast-grep-rules/rules/hardcoded-url.yml +22 -4
  262. package/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
  263. package/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
  264. package/rules/ast-grep-rules/rules/no-case-declarations-js.yml +4 -3
  265. package/rules/ast-grep-rules/rules/no-case-declarations.yml +4 -3
  266. package/rules/ast-grep-rules/rules/no-console-except-error-js.yml +10 -0
  267. package/rules/ast-grep-rules/rules/no-console-except-error.yml +10 -0
  268. package/rules/ast-grep-rules/rules/no-javascript-url-js.yml +42 -6
  269. package/rules/ast-grep-rules/rules/no-javascript-url.yml +42 -6
  270. package/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
  271. package/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
  272. package/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
  273. package/rules/ast-grep-rules/rules/strict-equality-js.yml +14 -1
  274. package/rules/ast-grep-rules/rules/strict-equality.yml +14 -1
  275. package/rules/ast-grep-rules/rules/strict-inequality-js.yml +14 -1
  276. package/rules/ast-grep-rules/rules/strict-inequality.yml +14 -1
  277. package/rules/tree-sitter-queries/cpp/no-memset-sensitive-data.yml +9 -3
  278. package/rules/tree-sitter-queries/cpp/no-scoped-lock-without-args.yml +6 -6
  279. package/rules/tree-sitter-queries/css/calc-spacing.yml +8 -6
  280. package/rules/tree-sitter-queries/go/go-mutex-copy.yml +21 -18
  281. package/rules/tree-sitter-queries/go/go-shared-map-write-goroutine.yml +3 -3
  282. package/rules/tree-sitter-queries/java/infinite-loop-java.yml +3 -3
  283. package/rules/tree-sitter-queries/java/junit-call-super.yml +6 -4
  284. package/rules/tree-sitter-queries/java/no-double-checked-locking.yml +10 -8
  285. package/rules/tree-sitter-queries/java/no-field-shadowing.yml +1 -1
  286. package/rules/tree-sitter-queries/java/prepared-statement-valid-indices.yml +2 -3
  287. package/rules/tree-sitter-queries/java/resources-closed.yml +2 -2
  288. package/rules/tree-sitter-queries/java/springboot-default-package.yml +6 -3
  289. package/rules/tree-sitter-queries/java/switch-fall-through.yml +6 -4
  290. package/rules/tree-sitter-queries/java/switch-non-case-labels.yml +4 -3
  291. package/rules/tree-sitter-queries/java/tests-include-assertions.yml +6 -5
  292. package/rules/tree-sitter-queries/javascript/switch-case-termination-js.yml +5 -8
  293. package/rules/tree-sitter-queries/javascript/switch-non-case-labels.yml +3 -2
  294. package/rules/tree-sitter-queries/kotlin/prepared-statement-indices.yml +9 -5
  295. package/rules/tree-sitter-queries/php/this-in-static-context.yml +5 -5
  296. package/rules/tree-sitter-queries/python/exit-signature-check.yml +2 -9
  297. package/rules/tree-sitter-queries/python/in-operator-unsupported.yml +3 -4
  298. package/rules/tree-sitter-queries/python/no-super-torchscript.yml +4 -8
  299. package/rules/tree-sitter-queries/python/notimplemented-boolean-context.yml +1 -5
  300. package/rules/tree-sitter-queries/python/python-empty-except.yml +1 -1
  301. package/rules/tree-sitter-queries/python/return-in-init.yml +0 -2
  302. package/rules/tree-sitter-queries/python/yield-return-outside-function.yml +0 -3
  303. package/rules/tree-sitter-queries/ruby/ruby-command-injection.yml +1 -12
  304. package/rules/tree-sitter-queries/ruby/ruby-eval.yml +1 -1
  305. package/rules/tree-sitter-queries/ruby/ruby-insecure-deserialization.yml +4 -4
  306. package/rules/tree-sitter-queries/ruby/ruby-insecure-random.yml +4 -11
  307. package/rules/tree-sitter-queries/ruby/ruby-open-struct.yml +1 -1
  308. package/rules/tree-sitter-queries/ruby/ruby-string-eval.yml +2 -3
  309. package/rules/tree-sitter-queries/ruby/ruby-weak-hash.yml +4 -4
  310. package/rules/tree-sitter-queries/rust/rust-lock-held-across-await.yml +4 -3
  311. package/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
  312. package/rules/tree-sitter-queries/typescript/console-statement.yml +11 -0
  313. package/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +4 -9
  314. package/rules/tree-sitter-queries/typescript/empty-switch-case.yml +13 -9
  315. package/rules/tree-sitter-queries/typescript/infinite-loop.yml +2 -2
  316. package/rules/tree-sitter-queries/typescript/mixed-async-styles.yml +10 -1
  317. package/rules/tree-sitter-queries/typescript/switch-case-termination.yml +5 -8
  318. package/rules/tree-sitter-queries/typescript/ts-insecure-random.yml +7 -10
  319. package/rules/tree-sitter-queries/typescript/unsafe-regex.yml +4 -2
  320. package/rules/typos/_typos.toml +17 -0
  321. package/scripts/analyze-pi-lens-logs.mjs +55 -2
  322. package/skills/pi-lens-write-tree-sitter-rule/SKILL.md +5 -0
  323. package/dist/clients/ast-grep-parser.js +0 -86
  324. package/dist/clients/config-enabled-shape.js +0 -39
  325. package/dist/clients/lsp/interactive-install.js +0 -367
  326. package/dist/clients/lsp/lsp-index.js +0 -10
  327. package/dist/clients/lsp/server-strategies.js +0 -5
  328. package/dist/clients/project-metadata.js +0 -690
  329. package/dist/clients/source-groups.js +0 -96
  330. package/dist/clients/types.js +0 -11
  331. package/docs/env_variables.md +0 -10
  332. package/rules/tree-sitter-queries/abap-disabled/delete-where.yml +0 -46
  333. package/rules/tree-sitter-queries/cpp/no-confused-move-forward.yml +0 -59
  334. package/rules/tree-sitter-queries/cpp/noexcept-functions.yml +0 -58
  335. package/rules/tree-sitter-queries/java/mockito-initialized.yml +0 -66
  336. package/rules/tree-sitter-queries/java/no-wait-notify-on-thread.yml +0 -58
  337. package/rules/tree-sitter-queries/java/short-circuit-logic.yml +0 -57
  338. package/rules/tree-sitter-queries/java/spring-session-attributes-setcomplete.yml +0 -75
  339. package/rules/tree-sitter-queries/plsql-disabled/delete-update-where.yml +0 -56
  340. package/rules/tree-sitter-queries/plsql-disabled/end-loop-semicolon.yml +0 -51
  341. package/rules/tree-sitter-queries/plsql-disabled/fetch-bulk-collect-limit.yml +0 -47
  342. package/rules/tree-sitter-queries/plsql-disabled/forallsave-exceptions.yml +0 -51
  343. package/rules/tree-sitter-queries/plsql-disabled/nchar-nvarchar2-bytes.yml +0 -54
  344. package/rules/tree-sitter-queries/plsql-disabled/not-null-initialization.yml +0 -59
  345. package/rules/tree-sitter-queries/plsql-disabled/raise-application-error-codes.yml +0 -50
  346. package/rules/tree-sitter-queries/typescript-disabled/ts-path-traversal.yml +0 -71
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Opt-in compact one-line tool-result rendering (#1327).
3
+ *
4
+ * pi renders a tool call as two rows by default: the tool-name/args row
5
+ * (`renderCall`) and the result row (`renderResult`). Most pi-lens tools
6
+ * already supply a `renderResult` via `tools/render-compact.ts`'s
7
+ * `compactRenderResult` — a collapsed one-line summary (brand-blue) plus the
8
+ * full output when expanded. This module adds a further, OPT-IN layer that
9
+ * collapses those two rows into ONE — `<status glyph> <name> — <summary>` —
10
+ * by reusing each tool's EXISTING collapsed summary text verbatim (never
11
+ * inventing new strings) and re-styling it through semantic `Theme` tokens
12
+ * instead of a fixed color.
13
+ *
14
+ * Applied generically at the registration seam (index.ts's registerTool
15
+ * loop), not as a per-tool copy: `wrapToolForCompactLine` decorates any
16
+ * `ToolDefinition` that already defines `renderResult`; tools without one are
17
+ * returned untouched. Callers gate the whole thing behind the `ui.compactToolLine`
18
+ * flag (`clients/lens-flag-registry.ts`) — when it is off, the ORIGINAL tool
19
+ * array is registered unwrapped, so behavior is byte-identical to today (no
20
+ * `renderCall`/`renderResult` are added or altered).
21
+ *
22
+ * `clients/` intentionally does NOT import `tools/render-compact.ts` (tools/
23
+ * depends on clients/, never the reverse — see AGENTS.md's seam discipline),
24
+ * so `fullTextOf` is duplicated here in miniature rather than inverting that
25
+ * dependency.
26
+ *
27
+ * API limitation vs the issue's ask: `ToolRenderResultOptions` (the type pi
28
+ * hands `renderResult`) exposes only `{ expanded, isPartial }` — no
29
+ * `outputPad`. `outputPad` exists solely on the unrelated `MessageRenderOptions`
30
+ * (custom message renderers, e.g. clients/turn-summary-render.ts), not on the
31
+ * tool-result render path. There is nothing to honor here; the collapsed line
32
+ * still respects the render width via `fitLines` so it never overflows the
33
+ * terminal (#513's crash class).
34
+ */
35
+ import { stripAnsi } from "./sanitize.js";
36
+ import { fitLines } from "./tui-fit.js";
37
+ const EMPTY_COMPONENT = {
38
+ render: () => [],
39
+ invalidate: () => { },
40
+ };
41
+ function fullTextOf(result) {
42
+ return (result.content ?? [])
43
+ .filter((c) => c.type === "text" && typeof c.text === "string")
44
+ .map((c) => c.text)
45
+ .join("\n");
46
+ }
47
+ /**
48
+ * Build the collapsed compact line from a tool's ALREADY-COMPUTED summary
49
+ * text (verbatim reuse — see module doc). `summaryText` is expected in the
50
+ * `<name/hint> — <detail>` shape every pi-lens summarizer already produces
51
+ * (e.g. `lens_diagnostics all — 3 blocking · 3 errors · 2 warnings (1 file)`);
52
+ * when the separator is absent the whole string is styled as one span
53
+ * instead of guessing a split point.
54
+ *
55
+ * Pure and theme-driven: a status glyph (success/error tokens), the name
56
+ * portion (`toolTitle`, bold), and the detail portion (`error`/`toolOutput`)
57
+ * are each styled through semantic `Theme.fg` tokens — never a fixed color.
58
+ */
59
+ export function buildCompactToolLine(summaryText, isError, theme) {
60
+ const glyph = isError ? theme.fg("error", "✗") : theme.fg("success", "✓");
61
+ const detailColor = isError ? "error" : "toolOutput";
62
+ const sepIndex = summaryText.indexOf(" — ");
63
+ if (sepIndex === -1) {
64
+ return `${glyph} ${theme.fg(detailColor, summaryText)}`;
65
+ }
66
+ const namePart = summaryText.slice(0, sepIndex);
67
+ const detailPart = summaryText.slice(sepIndex + " — ".length);
68
+ const styledName = theme.fg("toolTitle", theme.bold(namePart));
69
+ const styledDash = theme.fg("dim", "—");
70
+ const styledDetail = theme.fg(detailColor, detailPart);
71
+ return `${glyph} ${styledName} ${styledDash} ${styledDetail}`;
72
+ }
73
+ /** Render a component at a wide probe width to capture its full text,
74
+ * stripped of ANSI — the collapsed summary is re-styled through this
75
+ * module's own theme tokens rather than carrying over fixed-color escapes
76
+ * (e.g. tools/render-compact.ts's brand-blue). */
77
+ function captureComponentText(component) {
78
+ try {
79
+ const lines = component.render(4096);
80
+ return stripAnsi(lines.join(" ")).trim();
81
+ }
82
+ catch {
83
+ return "";
84
+ }
85
+ }
86
+ /**
87
+ * Decorate a single tool definition with the opt-in compact-line renderers.
88
+ * Tools with no `renderResult` are returned UNCHANGED (nothing to compact).
89
+ * Call this only when the `ui.compactToolLine` flag is already known to be
90
+ * on — the caller (index.ts) skips the wrap entirely when it is off, which
91
+ * is what keeps the off path byte-identical to today rather than this
92
+ * function branching on a live flag read per render.
93
+ */
94
+ export function wrapToolForCompactLine(tool) {
95
+ const originalRenderResult = tool.renderResult;
96
+ if (!originalRenderResult)
97
+ return tool;
98
+ const originalRenderCall = tool.renderCall;
99
+ const renderCall = (args, theme, context) => {
100
+ // Only blank the call row once a settled (non-partial) result exists —
101
+ // `renderResult` is about to paint the single combined line. While the
102
+ // tool is still running/streaming, keep showing the normal call row
103
+ // (mirrors the host's own createCallFallback) so the UI isn't blank.
104
+ if (!context.expanded && context.isPartial === false) {
105
+ return EMPTY_COMPONENT;
106
+ }
107
+ if (originalRenderCall)
108
+ return originalRenderCall(args, theme, context);
109
+ // Mirrors the host's own createCallFallback exactly: styled `tool.name`
110
+ // (the registered name, e.g. "lens_diagnostics"), not the display label.
111
+ const line = theme.fg("toolTitle", theme.bold(tool.name));
112
+ return {
113
+ render: (width) => fitLines([line], width),
114
+ invalidate: () => { },
115
+ };
116
+ };
117
+ const renderResult = (result, options, theme, context) => {
118
+ if (options.expanded) {
119
+ return originalRenderResult(result, options, theme, context);
120
+ }
121
+ let summaryText;
122
+ try {
123
+ // Probe the tool's OWN collapsed renderer to capture its already-built
124
+ // summary text; `lastComponent` is cleared so the probe never reuses
125
+ // (and thereby mutates) the real row's retained component.
126
+ const probeContext = { ...context, lastComponent: undefined };
127
+ const component = originalRenderResult(result, { ...options, expanded: false }, theme, probeContext);
128
+ summaryText = captureComponentText(component);
129
+ }
130
+ catch {
131
+ summaryText = "";
132
+ }
133
+ const resultLike = result;
134
+ if (!summaryText) {
135
+ summaryText = stripAnsi(fullTextOf(resultLike).split("\n")[0] ?? "").trim();
136
+ }
137
+ // pi invokes tool renderers with `{ content, details }` -- the failure
138
+ // bit lives on `context.isError`, not the result (#1341 review). Keep
139
+ // `result.isError` as a fallback for direct/legacy callers.
140
+ const contextIsError = context?.isError;
141
+ const isError = contextIsError ?? resultLike.isError === true;
142
+ const line = buildCompactToolLine(summaryText, isError === true, theme);
143
+ return {
144
+ render: (width) => fitLines([line], width),
145
+ invalidate: () => { },
146
+ };
147
+ };
148
+ return { ...tool, renderCall, renderResult };
149
+ }
150
+ /** Wrap every tool in an array — tools without `renderResult` pass through
151
+ * unchanged. Used by index.ts's registerTool loop, only when the
152
+ * `ui.compactToolLine` flag resolved on for this session. */
153
+ export function wrapToolsForCompactLine(tools) {
154
+ return tools.map((tool) => wrapToolForCompactLine(tool));
155
+ }
@@ -1,32 +1,75 @@
1
1
  /**
2
- * Tree-sitter Tree Cache with Incremental Parsing Support
2
+ * Tree-sitter Tree Cache
3
3
  *
4
- * Caches parsed ASTs and enables incremental updates for large files.
5
- * This provides 10-100× speedup on edits to large files (>1000 lines).
4
+ * Caches parsed ASTs so a file written once is parsed once and reused by every
5
+ * subsystem that inspects it in the same process (#675).
6
+ *
7
+ * Freshness is hash-authoritative when the caller supplies content: identical
8
+ * bytes parse to an identical tree, so a hash match is a hit even if the file's
9
+ * mtime moved (an agent re-saving unchanged bytes must not force a reparse,
10
+ * #890). Eviction is true LRU — hits re-insert their entry, so set() dropping
11
+ * the Map's first key removes the least-recently-used file, not merely the
12
+ * oldest insertion, and hot per-edit files survive scan traffic (#890).
6
13
  */
14
+ import { logTreeSitterDiagnostic } from "./tree-sitter-logger.js";
7
15
  import * as crypto from "node:crypto";
8
16
  import * as fs from "node:fs";
9
17
  import { normalizeFilePath } from "./path-utils.js";
18
+ const TREE_RETIREMENT_GRACE_MICROTASKS = 4;
19
+ export const TREE_CACHE_COUNTER_KEYS = [
20
+ "lookups",
21
+ "hits",
22
+ "coldMisses",
23
+ "capacityMisses",
24
+ "contentChangedMisses",
25
+ "mtimeMisses",
26
+ "statFailedMisses",
27
+ "sets",
28
+ "replacements",
29
+ "evictions",
30
+ "clears",
31
+ "ghostHistoryDrops",
32
+ ];
33
+ export function createTreeCacheCounters() {
34
+ return Object.fromEntries(TREE_CACHE_COUNTER_KEYS.map((key) => [key, 0]));
35
+ }
10
36
  export class TreeCache {
11
37
  cache = new Map();
38
+ recentlyEvicted = new Map();
12
39
  maxSize;
40
+ evictionHistoryMax;
13
41
  debug;
14
- constructor(maxSize = 50, debug = false) {
42
+ counters = createTreeCacheCounters();
43
+ counterObserver;
44
+ treeErrorObserver;
45
+ constructor(maxSize = 50, debug = false, evictionHistoryMax = 4096, counterObserver, treeErrorObserver) {
15
46
  this.maxSize = maxSize;
47
+ this.evictionHistoryMax = Math.max(1, Math.floor(evictionHistoryMax));
48
+ this.counterObserver = counterObserver;
49
+ this.treeErrorObserver = treeErrorObserver;
16
50
  this.debug = debug
17
- ? (msg) => console.error(`[tree-cache] ${msg}`)
51
+ ? (msg) => logTreeSitterDiagnostic({
52
+ subsystem: "tree-cache",
53
+ level: "debug",
54
+ message: msg,
55
+ })
18
56
  : () => { };
19
57
  }
58
+ recordCounter(key, amount = 1) {
59
+ this.counters[key] += amount;
60
+ this.counterObserver?.(key, amount);
61
+ }
20
62
  /**
21
63
  * Free a tree-sitter Tree's WASM-heap allocation.
22
64
  *
23
65
  * web-tree-sitter Trees live in the WASM heap; JS GC reclaims only the wrapper,
24
66
  * so the underlying memory leaks unless `tree.delete()` is called explicitly
25
67
  * (no FinalizationRegistry auto-free in 0.25). Guarded — a tree may already be
26
- * deleted, or `delete()` may throw on a corrupt/aborted runtime. Safe because
27
- * every consumer uses a returned tree transiently (parse → extract → discard
28
- * within one call); the eviction target is always the OLDEST entry, never the
29
- * just-parsed tree still in a caller's hand (#417).
68
+ * deleted, or `delete()` may throw on a corrupt/aborted runtime. Retirement is
69
+ * deferred so direct parse callers resume before deletion; consumers still
70
+ * traverse without another await, or use the cache-safe callback API (#417).
71
+ * The eviction target is the least-recently-used entry (get() re-inserts
72
+ * hits), never the just-parsed tree still in a caller's hand.
30
73
  */
31
74
  // biome-ignore lint/suspicious/noExplicitAny: web-tree-sitter Tree
32
75
  freeTree(tree) {
@@ -34,17 +77,39 @@ export class TreeCache {
34
77
  if (tree && typeof tree.delete === "function")
35
78
  tree.delete();
36
79
  }
37
- catch {
38
- // best-effort — a dead wasm runtime or double-delete must not throw
80
+ catch (error) {
81
+ this.treeErrorObserver?.(error);
39
82
  }
40
83
  }
41
- /** Remove a cache entry AND free its WASM tree. */
84
+ retireTree(tree) {
85
+ let remaining = TREE_RETIREMENT_GRACE_MICROTASKS;
86
+ const retire = () => {
87
+ if (remaining-- > 0) {
88
+ queueMicrotask(retire);
89
+ return;
90
+ }
91
+ this.freeTree(tree);
92
+ };
93
+ queueMicrotask(retire);
94
+ }
95
+ /** Remove a cache entry and retire its WASM tree after current consumers run. */
42
96
  removeEntry(key) {
43
97
  const cached = this.cache.get(key);
44
98
  if (cached)
45
- this.freeTree(cached.tree);
99
+ this.retireTree(cached.tree);
46
100
  this.cache.delete(key);
47
101
  }
102
+ rememberEviction(key, cached) {
103
+ this.recentlyEvicted.delete(key);
104
+ if (this.recentlyEvicted.size >= this.evictionHistoryMax) {
105
+ const oldestKey = this.recentlyEvicted.keys().next().value;
106
+ if (oldestKey !== undefined) {
107
+ this.recentlyEvicted.delete(oldestKey);
108
+ this.recordCounter("ghostHistoryDrops");
109
+ }
110
+ }
111
+ this.recentlyEvicted.set(key, cached.contentHash);
112
+ }
48
113
  /**
49
114
  * Generate hash for file content
50
115
  */
@@ -62,28 +127,69 @@ export class TreeCache {
62
127
  return `${languageId}:${normalizeFilePath(filePath)}`;
63
128
  }
64
129
  /**
65
- * Check if tree is cached and valid
130
+ * Check if tree is cached and valid.
131
+ *
132
+ * When `content` is provided the content hash is AUTHORITATIVE: a hash match
133
+ * is a hit regardless of mtime, and the entry's stat metadata is refreshed
134
+ * so a save-without-change (same bytes, newer mtime) does not masquerade as
135
+ * a disk modification (#890). The mtime check remains the freshness signal
136
+ * only on the content-less path, where nothing else can prove the cached
137
+ * tree current (`mtimeMisses`). A stat failure invalidates on both paths —
138
+ * a deleted file's entry is dead weight and its WASM tree should be freed.
139
+ *
140
+ * Every hit re-inserts the entry (raw Map delete+set — NOT removeEntry,
141
+ * which would retire the live tree) so eviction is true LRU (#890).
66
142
  */
67
143
  get(filePath, content, languageId) {
144
+ this.recordCounter("lookups");
68
145
  const key = this.getCacheKey(filePath, languageId);
69
146
  const cached = this.cache.get(key);
70
147
  if (!cached) {
148
+ const evictedHash = this.recentlyEvicted.get(key);
149
+ if (evictedHash !== undefined &&
150
+ content !== undefined &&
151
+ evictedHash === this.hashContent(content)) {
152
+ this.recordCounter("capacityMisses");
153
+ }
154
+ else {
155
+ this.recordCounter("coldMisses");
156
+ }
71
157
  this.debug(`Cache miss: ${filePath}`);
72
158
  return null;
73
159
  }
74
160
  // (No language-mismatch check needed: the cache key is prefixed with
75
161
  // languageId, so a key hit already implies the language matches.)
76
- // Check content hash
77
- const contentHash = this.hashContent(content);
78
- if (cached.contentHash !== contentHash) {
79
- this.debug(`Content changed: ${filePath} (${cached.lineCount} → ${content.split("\n").length} lines)`);
80
- // Keep old tree for potential incremental update, but mark as stale
81
- return null;
162
+ if (content !== undefined) {
163
+ // Check content hash — authoritative when content is provided.
164
+ const contentHash = this.hashContent(content);
165
+ if (cached.contentHash !== contentHash) {
166
+ this.recordCounter("contentChangedMisses");
167
+ this.debug(`Content changed: ${filePath} (${cached.lineCount} → ${content.split("\n").length} lines)`);
168
+ // Keep old tree for potential incremental update, but mark as stale
169
+ return null;
170
+ }
82
171
  }
83
- // Check if file was modified on disk (mtime changed)
84
172
  try {
85
173
  const stats = fs.statSync(filePath);
86
- if (stats.mtimeMs !== cached.lastModified) {
174
+ if (content !== undefined) {
175
+ // The hash already proved the tree current: an mtime delta is a
176
+ // save-without-change (or touch/clock drift). Refresh metadata
177
+ // instead of invalidating (#890). Caveat: this stamps the CURRENT
178
+ // disk mtime onto an entry validated against caller-supplied
179
+ // content — if that content lags a newer disk write, lastModified
180
+ // no longer vouches for the disk bytes. So lastModified means
181
+ // "mtime last observed on a hash-confirmed hit"; the content-less
182
+ // path below may only treat it as freshness proof while all
183
+ // callers pass content (today they all do — the undefined path is
184
+ // exercised only by tests).
185
+ if (stats.mtimeMs !== cached.lastModified) {
186
+ cached.lastModified = stats.mtimeMs;
187
+ this.debug(`Refreshed mtime on hash-matched entry: ${filePath}`);
188
+ }
189
+ }
190
+ else if (stats.mtimeMs !== cached.lastModified) {
191
+ // No content to hash — mtime is the only freshness proof.
192
+ this.recordCounter("mtimeMisses");
87
193
  this.debug(`File modified on disk: ${filePath}`);
88
194
  this.removeEntry(key);
89
195
  return null;
@@ -91,9 +197,16 @@ export class TreeCache {
91
197
  }
92
198
  catch {
93
199
  // File might be deleted, invalidate cache
200
+ this.recordCounter("statFailedMisses");
94
201
  this.removeEntry(key);
95
202
  return null;
96
203
  }
204
+ // LRU touch: re-insert so this entry becomes the newest. The SAME entry
205
+ // object is re-set — no tree replacement, so nothing is retired and a
206
+ // retired tree can never be resurrected (#890).
207
+ this.cache.delete(key);
208
+ this.cache.set(key, cached);
209
+ this.recordCounter("hits");
97
210
  this.debug(`Cache hit: ${filePath} (${cached.lineCount} lines)`);
98
211
  return cached.tree;
99
212
  }
@@ -101,16 +214,26 @@ export class TreeCache {
101
214
  * Store parsed tree in cache
102
215
  */
103
216
  set(filePath, content, languageId, tree) {
217
+ this.recordCounter("sets");
104
218
  const key = this.getCacheKey(filePath, languageId);
219
+ const contentHash = this.hashContent(content);
220
+ this.recentlyEvicted.delete(key);
105
221
  // Free the tree we're about to replace at this key (re-parse of the same
106
222
  // file) so it doesn't leak its WASM heap.
107
223
  if (this.cache.has(key)) {
224
+ this.recordCounter("replacements");
108
225
  this.removeEntry(key);
109
226
  }
110
227
  else if (this.cache.size >= this.maxSize) {
111
- // Evict + free the oldest entry when the cache is full.
228
+ // Evict + free the least-recently-used entry when the cache is full:
229
+ // get() re-inserts hits, so Map insertion order IS recency order and
230
+ // the first key is the LRU entry (#890).
112
231
  const firstKey = this.cache.keys().next().value;
113
232
  if (firstKey) {
233
+ const evicted = this.cache.get(firstKey);
234
+ if (evicted)
235
+ this.rememberEviction(firstKey, evicted);
236
+ this.recordCounter("evictions");
114
237
  this.removeEntry(firstKey);
115
238
  this.debug(`Evicted: ${firstKey}`);
116
239
  }
@@ -125,136 +248,24 @@ export class TreeCache {
125
248
  }
126
249
  this.cache.set(key, {
127
250
  tree,
128
- contentHash: this.hashContent(content),
251
+ contentHash,
129
252
  languageId,
130
- fileSize: content.length,
253
+ fileSize: Buffer.byteLength(content, "utf8"),
131
254
  lineCount: content.split("\n").length,
132
255
  lastModified: mtime,
133
256
  });
134
257
  this.debug(`Cached: ${filePath} (${content.split("\n").length} lines)`);
135
258
  }
136
- /**
137
- * Calculate the diff between old and new content
138
- * Returns edit information for incremental parsing
139
- */
140
- calculateEdit(oldContent, newContent) {
141
- // Find the first difference
142
- let startIndex = 0;
143
- while (startIndex < oldContent.length &&
144
- startIndex < newContent.length &&
145
- oldContent[startIndex] === newContent[startIndex]) {
146
- startIndex++;
147
- }
148
- // Find the last difference (working backwards)
149
- let oldEndIndex = oldContent.length;
150
- let newEndIndex = newContent.length;
151
- while (oldEndIndex > startIndex &&
152
- newEndIndex > startIndex &&
153
- oldContent[oldEndIndex - 1] === newContent[newEndIndex - 1]) {
154
- oldEndIndex--;
155
- newEndIndex--;
156
- }
157
- // No change detected
158
- if (startIndex === oldContent.length && startIndex === newContent.length) {
159
- return null;
160
- }
161
- // Calculate positions
162
- const startPosition = this.indexToPosition(oldContent, startIndex);
163
- const oldEndPosition = this.indexToPosition(oldContent, oldEndIndex);
164
- const newEndPosition = this.indexToPosition(newContent, newEndIndex);
165
- return {
166
- startIndex,
167
- oldEndIndex,
168
- newEndIndex,
169
- startPosition,
170
- oldEndPosition,
171
- newEndPosition,
172
- };
173
- }
174
- /**
175
- * Convert byte index to row/column position
176
- */
177
- indexToPosition(content, index) {
178
- const lines = content.slice(0, index).split("\n");
179
- return {
180
- row: lines.length - 1,
181
- column: lines[lines.length - 1].length,
182
- };
183
- }
184
- /**
185
- * Attempt incremental update using tree.edit()
186
- * Returns updated tree or null if incremental update failed
187
- */
188
- async incrementalUpdate(filePath, oldContent, newContent, languageId, parser) {
189
- const key = this.getCacheKey(filePath, languageId);
190
- const cached = this.cache.get(key);
191
- if (!cached) {
192
- this.debug(`No cached tree for incremental update: ${filePath}`);
193
- return null;
194
- }
195
- // Only use incremental for large files (>100 lines)
196
- const lineCount = oldContent.split("\n").length;
197
- if (lineCount < 100) {
198
- this.debug(`File too small for incremental: ${filePath} (${lineCount} lines)`);
199
- return null;
200
- }
201
- // Calculate edit
202
- const edit = this.calculateEdit(oldContent, newContent);
203
- if (!edit) {
204
- this.debug(`No edit detected for: ${filePath}`);
205
- return null;
206
- }
207
- this.debug(`Incremental update: ${filePath} (lines ${edit.startPosition.row}-${edit.oldEndPosition.row})`);
208
- try {
209
- // Apply edit to tree
210
- cached.tree.edit({
211
- startIndex: edit.startIndex,
212
- oldEndIndex: edit.oldEndIndex,
213
- newEndIndex: edit.newEndIndex,
214
- startPosition: edit.startPosition,
215
- oldEndPosition: edit.oldEndPosition,
216
- newEndPosition: edit.newEndPosition,
217
- });
218
- // Re-parse only changed region
219
- const newTree = parser.parse(newContent, cached.tree);
220
- // Update cache
221
- this.set(filePath, newContent, languageId, newTree);
222
- this.debug(`Incremental update successful: ${filePath}`);
223
- return newTree;
224
- }
225
- catch (err) {
226
- this.debug(`Incremental update failed: ${err}`);
227
- return null;
228
- }
229
- }
230
- /**
231
- * Clear cache for a specific file
232
- */
233
- invalidate(filePath, languageId) {
234
- if (languageId) {
235
- const key = this.getCacheKey(filePath, languageId);
236
- this.removeEntry(key);
237
- this.debug(`Invalidated: ${key}`);
238
- }
239
- else {
240
- // Invalidate all entries for this file
241
- for (const [key, value] of this.cache.entries()) {
242
- if (key.includes(filePath)) {
243
- this.freeTree(value.tree);
244
- this.cache.delete(key);
245
- this.debug(`Invalidated: ${key}`);
246
- }
247
- }
248
- }
249
- }
250
259
  /**
251
260
  * Clear entire cache
252
261
  */
253
262
  clear() {
263
+ this.recordCounter("clears");
254
264
  for (const entry of this.cache.values()) {
255
- this.freeTree(entry.tree);
265
+ this.retireTree(entry.tree);
256
266
  }
257
267
  this.cache.clear();
268
+ this.recentlyEvicted.clear();
258
269
  this.debug("Cache cleared");
259
270
  }
260
271
  /**
@@ -268,10 +279,12 @@ export class TreeCache {
268
279
  totalBytes += entry.fileSize;
269
280
  }
270
281
  return {
282
+ ...this.counters,
271
283
  size: this.cache.size,
272
284
  maxSize: this.maxSize,
273
285
  totalLines,
274
286
  totalBytes,
287
+ misses: this.counters.lookups - this.counters.hits,
275
288
  };
276
289
  }
277
290
  }