@herbertgao/pi-extensions 2026.9.0 → 2026.9.2

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 (360) hide show
  1. package/node_modules/@czottmann/pi-automode/CHANGELOG.md +13 -1
  2. package/node_modules/@czottmann/pi-automode/README.md +20 -0
  3. package/node_modules/@czottmann/pi-automode/docs/GLOSSARY.md +1 -1
  4. package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +2 -1
  5. package/node_modules/@czottmann/pi-automode/docs/defaults.md +3 -2
  6. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +38 -2
  7. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +5 -3
  8. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +11 -3
  9. package/node_modules/@czottmann/pi-automode/package.json +1 -1
  10. package/node_modules/@narumitw/pi-btw/README.md +57 -141
  11. package/node_modules/@narumitw/pi-btw/dist/index.ts +466 -41
  12. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +4 -4
  13. package/node_modules/@narumitw/pi-btw/docs/workflows.md +74 -0
  14. package/node_modules/@narumitw/pi-btw/package.json +2 -1
  15. package/node_modules/@narumitw/pi-btw/src/btw.ts +15 -1
  16. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +32 -2
  17. package/node_modules/@narumitw/pi-btw/src/keybindings.ts +330 -0
  18. package/node_modules/@narumitw/pi-btw/src/menu.ts +136 -19
  19. package/node_modules/@narumitw/pi-btw/src/settings.ts +32 -0
  20. package/node_modules/@narumitw/pi-btw/src/side-thread.ts +57 -5
  21. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +52 -30
  22. package/node_modules/@pi-plugins/fast-mode/README.md +5 -3
  23. package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts.map +1 -1
  24. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +20 -55
  25. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
  26. package/node_modules/@pi-plugins/fast-mode/package.json +1 -1
  27. package/node_modules/pi-lens/CHANGELOG.md +1072 -0
  28. package/node_modules/pi-lens/README.md +3 -0
  29. package/node_modules/pi-lens/config/biome/core.jsonc +11 -2
  30. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +13 -1
  31. package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +2 -9
  32. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +927 -80
  33. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +1 -1
  34. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +13 -4
  35. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +123 -3
  36. package/node_modules/pi-lens/dist/clients/ast-grep-rule-manager.js +60 -5
  37. package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +2 -9
  38. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +2 -0
  39. package/node_modules/pi-lens/dist/clients/bash-file-access.js +133 -3
  40. package/node_modules/pi-lens/dist/clients/biome-client.js +9 -2
  41. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +14 -6
  42. package/node_modules/pi-lens/dist/clients/bootstrap.js +509 -73
  43. package/node_modules/pi-lens/dist/clients/bounded-cache.js +152 -12
  44. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +1 -1
  45. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +59 -9
  46. package/node_modules/pi-lens/dist/clients/bundled-resource-health.js +113 -0
  47. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +3 -11
  48. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +31 -4
  49. package/node_modules/pi-lens/dist/clients/cache-manager.js +105 -6
  50. package/node_modules/pi-lens/dist/clients/cache-observability.js +40 -19
  51. package/node_modules/pi-lens/dist/clients/cargo-manifest.js +422 -0
  52. package/node_modules/pi-lens/dist/clients/cascade-logger.js +2 -9
  53. package/node_modules/pi-lens/dist/clients/child-unref.js +1 -19
  54. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +13 -3
  55. package/node_modules/pi-lens/dist/clients/complexity-client.js +16 -5
  56. package/node_modules/pi-lens/dist/clients/config-core/deny.js +221 -0
  57. package/node_modules/pi-lens/dist/clients/config-core/index.js +50 -0
  58. package/node_modules/pi-lens/dist/clients/config-core/merge.js +357 -0
  59. package/node_modules/pi-lens/dist/clients/config-core/normalize.js +340 -0
  60. package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +248 -0
  61. package/node_modules/pi-lens/dist/clients/config-core/provenance.js +164 -0
  62. package/node_modules/pi-lens/dist/clients/config-core/records.js +218 -0
  63. package/node_modules/pi-lens/dist/clients/config-core/resolve.js +125 -0
  64. package/node_modules/pi-lens/dist/clients/config-core/safe-object.js +78 -0
  65. package/node_modules/pi-lens/dist/clients/config-core/schema.js +165 -0
  66. package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +281 -0
  67. package/node_modules/pi-lens/dist/clients/config-locations.js +160 -0
  68. package/node_modules/pi-lens/dist/clients/config-resolve.js +789 -0
  69. package/node_modules/pi-lens/dist/clients/config-schema.js +234 -0
  70. package/node_modules/pi-lens/dist/clients/config-warn.js +407 -0
  71. package/node_modules/pi-lens/dist/clients/dead-code-client.js +19 -14
  72. package/node_modules/pi-lens/dist/clients/dead-code-logger.js +2 -6
  73. package/node_modules/pi-lens/dist/clients/deadline-utils.js +178 -0
  74. package/node_modules/pi-lens/dist/clients/debug-handles.js +2 -2
  75. package/node_modules/pi-lens/dist/clients/debug-heap.js +3 -3
  76. package/node_modules/pi-lens/dist/clients/deferred-lsp-work.js +106 -0
  77. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +113 -7
  78. package/node_modules/pi-lens/dist/clients/dependency-checker.js +8 -19
  79. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +1 -1
  80. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +12 -9
  81. package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +15 -5
  82. package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +0 -4
  83. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +162 -44
  84. package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +97 -0
  85. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +27 -8
  86. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +0 -6
  87. package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +1 -1
  88. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +0 -3
  89. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-complexity.js +2 -2
  90. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-fan-out.js +1 -1
  91. package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +0 -1
  92. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +383 -27
  93. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +0 -1
  94. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +4 -1
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +0 -1
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +0 -1
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +0 -1
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +0 -1
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +0 -1
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +0 -1
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +0 -1
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/fact-rules.js +0 -1
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +0 -1
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +0 -1
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +1 -3
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +0 -1
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +0 -1
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +0 -1
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +1 -2
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +0 -1
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +0 -1
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +0 -1
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +0 -1
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +0 -1
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +0 -1
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +14 -18
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +0 -1
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +0 -1
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +0 -1
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +6 -3
  121. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +7 -4
  122. package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +0 -1
  123. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +1 -2
  124. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +1 -3
  125. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +1 -2
  126. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +0 -1
  127. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +4 -3
  128. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +0 -1
  129. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +0 -1
  130. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +0 -1
  131. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +0 -1
  132. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +0 -1
  133. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +0 -1
  134. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +0 -1
  135. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +0 -1
  136. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +0 -1
  137. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +3 -0
  138. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +3 -67
  139. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +194 -80
  140. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +84 -15
  141. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils.js +0 -21
  142. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +0 -1
  143. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +2 -20
  144. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +1 -1
  145. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +0 -1
  146. package/node_modules/pi-lens/dist/clients/dispatch/utils/format-utils.js +2 -2
  147. package/node_modules/pi-lens/dist/clients/disposition-logger.js +3 -10
  148. package/node_modules/pi-lens/dist/clients/effective-config.js +403 -0
  149. package/node_modules/pi-lens/dist/clients/error-class.js +23 -0
  150. package/node_modules/pi-lens/dist/clients/event-loop-hold.js +274 -0
  151. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +2 -2
  152. package/node_modules/pi-lens/dist/clients/extension-log.js +2 -2
  153. package/node_modules/pi-lens/dist/clients/feature-hints.js +2 -1
  154. package/node_modules/pi-lens/dist/clients/file-kinds.js +1 -38
  155. package/node_modules/pi-lens/dist/clients/file-time.js +7 -2
  156. package/node_modules/pi-lens/dist/clients/file-utils.js +117 -10
  157. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +50 -13
  158. package/node_modules/pi-lens/dist/clients/format-service.js +6 -2
  159. package/node_modules/pi-lens/dist/clients/formatters.js +385 -104
  160. package/node_modules/pi-lens/dist/clients/freshness-cadence.js +17 -0
  161. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +116 -22
  162. package/node_modules/pi-lens/dist/clients/generation-guard.js +4 -9
  163. package/node_modules/pi-lens/dist/clients/git-guard.js +6 -1
  164. package/node_modules/pi-lens/dist/clients/go-client.js +39 -0
  165. package/node_modules/pi-lens/dist/clients/gradle-ktfmt-style.js +252 -0
  166. package/node_modules/pi-lens/dist/clients/grammar-source.js +3 -3
  167. package/node_modules/pi-lens/dist/clients/hashline-anchor.js +424 -0
  168. package/node_modules/pi-lens/dist/clients/hook-budgets.js +76 -0
  169. package/node_modules/pi-lens/dist/clients/host-edit-normalize.js +5 -2
  170. package/node_modules/pi-lens/dist/clients/host-ports.js +1 -1
  171. package/node_modules/pi-lens/dist/clients/installer/index.js +289 -59
  172. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +84 -18
  173. package/node_modules/pi-lens/dist/clients/instance-reaper.js +110 -236
  174. package/node_modules/pi-lens/dist/clients/instance-registry.js +6 -3
  175. package/node_modules/pi-lens/dist/clients/language-profile.js +0 -9
  176. package/node_modules/pi-lens/dist/clients/language-registry.js +599 -0
  177. package/node_modules/pi-lens/dist/clients/latency-logger.js +165 -9
  178. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +34 -1
  179. package/node_modules/pi-lens/dist/clients/lens-config.js +160 -30
  180. package/node_modules/pi-lens/dist/clients/lens-engine.js +33 -41
  181. package/node_modules/pi-lens/dist/clients/lens-events.js +1 -1
  182. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +77 -6
  183. package/node_modules/pi-lens/dist/clients/lens-map.js +1 -1
  184. package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -38
  185. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +3 -1
  186. package/node_modules/pi-lens/dist/clients/lsp/client.js +172 -33
  187. package/node_modules/pi-lens/dist/clients/lsp/config.js +484 -158
  188. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +1 -1
  189. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +1 -1
  190. package/node_modules/pi-lens/dist/clients/lsp/edits.js +18 -5
  191. package/node_modules/pi-lens/dist/clients/lsp/index.js +892 -96
  192. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +1 -1
  193. package/node_modules/pi-lens/dist/clients/lsp/language.js +18 -177
  194. package/node_modules/pi-lens/dist/clients/lsp/launch.js +2 -1
  195. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
  196. package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +5 -18
  197. package/node_modules/pi-lens/dist/clients/lsp/server.js +284 -147
  198. package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +116 -23
  199. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +2 -7
  200. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +0 -1
  201. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +14 -7
  202. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +6 -0
  203. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +2 -1
  204. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +1 -1
  205. package/node_modules/pi-lens/dist/clients/lsp-document-symbols.js +1 -1
  206. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +163 -21
  207. package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +36 -0
  208. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +33 -4
  209. package/node_modules/pi-lens/dist/clients/mcp/session.js +5 -16
  210. package/node_modules/pi-lens/dist/clients/memory-sampler.js +8 -3
  211. package/node_modules/pi-lens/dist/clients/metrics-history.js +28 -107
  212. package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +3 -5
  213. package/node_modules/pi-lens/dist/clients/module-report.js +21 -46
  214. package/node_modules/pi-lens/dist/clients/mutating-tool.js +651 -0
  215. package/node_modules/pi-lens/dist/clients/mutation-attribution.js +368 -0
  216. package/node_modules/pi-lens/dist/clients/mutation-bridge.js +240 -0
  217. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +247 -24
  218. package/node_modules/pi-lens/dist/clients/observed-mutation-sources.js +101 -0
  219. package/node_modules/pi-lens/dist/clients/observed-mutation.js +1215 -0
  220. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +70 -28
  221. package/node_modules/pi-lens/dist/clients/opengrep-config.js +5 -1
  222. package/node_modules/pi-lens/dist/clients/package-manager.js +195 -26
  223. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +359 -80
  224. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +23 -7
  225. package/node_modules/pi-lens/dist/clients/path-keyed-map.js +21 -2
  226. package/node_modules/pi-lens/dist/clients/path-utils.js +451 -15
  227. package/node_modules/pi-lens/dist/clients/performance-report.js +2 -2
  228. package/node_modules/pi-lens/dist/clients/persist-debounce.js +8 -1
  229. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +114 -0
  230. package/node_modules/pi-lens/dist/clients/pipeline.js +79 -18
  231. package/node_modules/pi-lens/dist/clients/probe-home-state.js +228 -0
  232. package/node_modules/pi-lens/dist/clients/process-bridge.js +66 -0
  233. package/node_modules/pi-lens/dist/clients/process-snapshot.js +68 -0
  234. package/node_modules/pi-lens/dist/clients/project-changes.js +1 -1
  235. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +1 -1
  236. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +4 -3
  237. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/jscpd.js +1 -1
  238. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/knip.js +1 -1
  239. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +1 -1
  240. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/opengrep.js +1 -1
  241. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +28 -4
  242. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +51 -20
  243. package/node_modules/pi-lens/dist/clients/project-lens-config.js +438 -122
  244. package/node_modules/pi-lens/dist/clients/project-scan-policy.js +3 -51
  245. package/node_modules/pi-lens/dist/clients/project-snapshot.js +52 -24
  246. package/node_modules/pi-lens/dist/clients/python-environment.js +217 -0
  247. package/node_modules/pi-lens/dist/clients/python-provenance.js +639 -0
  248. package/node_modules/pi-lens/dist/clients/quiet-window.js +6 -1
  249. package/node_modules/pi-lens/dist/clients/read-bridge.js +13 -21
  250. package/node_modules/pi-lens/dist/clients/read-expansion.js +9 -46
  251. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +16 -5
  252. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +364 -212
  253. package/node_modules/pi-lens/dist/clients/read-guard.js +26 -2
  254. package/node_modules/pi-lens/dist/clients/recent-touches.js +2 -2
  255. package/node_modules/pi-lens/dist/clients/resource-sampler.js +195 -96
  256. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +155 -114
  257. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +1 -1
  258. package/node_modules/pi-lens/dist/clients/review-graph/service.js +55 -3
  259. package/node_modules/pi-lens/dist/clients/review-graph/shared-extraction-ir.js +7 -15
  260. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +10 -74
  261. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +9 -3
  262. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +19 -3
  263. package/node_modules/pi-lens/dist/clients/runtime-context.js +49 -16
  264. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +25 -6
  265. package/node_modules/pi-lens/dist/clients/runtime-session.js +184 -32
  266. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +179 -36
  267. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +797 -243
  268. package/node_modules/pi-lens/dist/clients/runtime-turn.js +1028 -53
  269. package/node_modules/pi-lens/dist/clients/rust-client.js +31 -0
  270. package/node_modules/pi-lens/dist/clients/safe-spawn.js +33 -10
  271. package/node_modules/pi-lens/dist/clients/sanitize.js +7 -12
  272. package/node_modules/pi-lens/dist/clients/scan-utils.js +1 -56
  273. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +1 -3
  274. package/node_modules/pi-lens/dist/clients/security-scan-client.js +3 -0
  275. package/node_modules/pi-lens/dist/clients/session-lifecycle.js +1 -1
  276. package/node_modules/pi-lens/dist/clients/session-start-observability.js +79 -0
  277. package/node_modules/pi-lens/dist/clients/session-summary.js +0 -24
  278. package/node_modules/pi-lens/dist/clients/sessionstart-logger.js +12 -3
  279. package/node_modules/pi-lens/dist/clients/sgconfig.js +7 -6
  280. package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +1 -1
  281. package/node_modules/pi-lens/dist/clients/skills-resolver.js +105 -0
  282. package/node_modules/pi-lens/dist/clients/smells-rollup.js +4 -4
  283. package/node_modules/pi-lens/dist/clients/startup-scan.js +2 -10
  284. package/node_modules/pi-lens/dist/clients/string-utils.js +13 -0
  285. package/node_modules/pi-lens/dist/clients/subagent-mode.js +17 -4
  286. package/node_modules/pi-lens/dist/clients/test-runner-client.js +176 -39
  287. package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +244 -0
  288. package/node_modules/pi-lens/dist/clients/tool-definition.js +41 -1
  289. package/node_modules/pi-lens/dist/clients/tool-policy.js +92 -22
  290. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +40 -40
  291. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +87 -100
  292. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +2 -2
  293. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +81 -1
  294. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +51 -46
  295. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +28 -0
  296. package/node_modules/pi-lens/dist/clients/tui-fit.js +0 -4
  297. package/node_modules/pi-lens/dist/clients/typos-config.js +5 -0
  298. package/node_modules/pi-lens/dist/clients/user-notify.js +6 -6
  299. package/node_modules/pi-lens/dist/clients/widget-state.js +191 -26
  300. package/node_modules/pi-lens/dist/clients/word-index-logger.js +2 -9
  301. package/node_modules/pi-lens/dist/clients/word-index-store.js +23 -12
  302. package/node_modules/pi-lens/dist/clients/word-index.js +252 -47
  303. package/node_modules/pi-lens/dist/clients/workspace-topology.js +8 -1
  304. package/node_modules/pi-lens/dist/clients/zizmor-config.js +4 -1
  305. package/node_modules/pi-lens/dist/index.js +65899 -55340
  306. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +3 -1
  307. package/node_modules/pi-lens/dist/mcp/build-staleness.js +1 -1
  308. package/node_modules/pi-lens/dist/mcp/server.js +127 -5
  309. package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +583 -0
  310. package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +129 -0
  311. package/node_modules/pi-lens/dist/tools/effective-config.js +89 -0
  312. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +96 -25
  313. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +104 -86
  314. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +37 -16
  315. package/node_modules/pi-lens/dist/tools/lsp-structured-output.js +3 -3
  316. package/node_modules/pi-lens/dist/tools/render-compact.js +1 -1
  317. package/node_modules/pi-lens/dist/tools/shared.js +0 -1
  318. package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -2
  319. package/node_modules/pi-lens/docs/agent-guide.md +64 -1
  320. package/node_modules/pi-lens/docs/configuration.md +222 -0
  321. package/node_modules/pi-lens/docs/dependencies.md +3 -3
  322. package/node_modules/pi-lens/docs/features.md +44 -5
  323. package/node_modules/pi-lens/docs/globalconfig.md +20 -0
  324. package/node_modules/pi-lens/docs/language-coverage.md +2 -2
  325. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +9 -1
  326. package/node_modules/pi-lens/docs/pi-lens-fixer.md +25 -0
  327. package/node_modules/pi-lens/docs/pi-lens-investigator.md +25 -0
  328. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +33 -0
  329. package/node_modules/pi-lens/docs/pi-lens-subagent.md +49 -0
  330. package/node_modules/pi-lens/docs/pi-lens-warden.md +55 -0
  331. package/node_modules/pi-lens/docs/public-api-stability.md +359 -0
  332. package/node_modules/pi-lens/docs/release-qa-baseline.md +190 -0
  333. package/node_modules/pi-lens/docs/servercapabilities.md +7 -3
  334. package/node_modules/pi-lens/docs/subagent-compat.md +110 -30
  335. package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +2 -2
  336. package/node_modules/pi-lens/docs/word-index.md +1 -1
  337. package/node_modules/pi-lens/package.json +18 -35
  338. package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-cross-language-method.yml +3 -3
  339. package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-hallucinated-import.yml +2 -2
  340. package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-sql-injection.yml +12 -2
  341. package/node_modules/pi-lens/rules/typos/_typos.toml +4 -2
  342. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +192 -1
  343. package/node_modules/pi-lens/scripts/install-selftest.mjs +58 -2
  344. package/node_modules/pi-lens/scripts/lib/skills-predicate.mjs +129 -0
  345. package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +1 -1
  346. package/node_modules/pi-lens/scripts/rpc-load-check.mjs +3 -1
  347. package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +4 -2
  348. package/node_modules/pi-web-access/CHANGELOG.md +28 -0
  349. package/node_modules/pi-web-access/README.md +62 -15
  350. package/node_modules/pi-web-access/curator-page.ts +3 -1
  351. package/node_modules/pi-web-access/curator-server.ts +5 -1
  352. package/node_modules/pi-web-access/gemini-search.ts +8 -4
  353. package/node_modules/pi-web-access/github-extract.ts +242 -1
  354. package/node_modules/pi-web-access/index.ts +118 -64
  355. package/node_modules/pi-web-access/mistral-search.ts +281 -0
  356. package/node_modules/pi-web-access/package.json +2 -2
  357. package/node_modules/pi-web-access/perplexity.ts +14 -1
  358. package/node_modules/pi-web-access/utils.ts +23 -6
  359. package/node_modules/pi-web-access/xai-search.ts +96 -33
  360. package/package.json +6 -6
@@ -176,7 +176,10 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
176
176
  {
177
177
  formatterNames: ["prettier"],
178
178
  defaultFormatter: "prettier",
179
- defaultWhenUnconfigured: true,
179
+ // HTML commonly carries template markers ({{JS}} embeds, Handlebars,
180
+ // Helm-adjacent Go templates). Prettier reinterprets them as code and
181
+ // corrupts them. Opt in via project prettier config; do not run by default.
182
+ defaultWhenUnconfigured: false,
180
183
  gate: "smart-default",
181
184
  },
182
185
  ],
@@ -185,7 +188,7 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
185
188
  {
186
189
  formatterNames: ["prettier"],
187
190
  defaultFormatter: "prettier",
188
- defaultWhenUnconfigured: true,
191
+ defaultWhenUnconfigured: false,
189
192
  gate: "smart-default",
190
193
  },
191
194
  ],
@@ -194,7 +197,10 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
194
197
  {
195
198
  formatterNames: ["prettier"],
196
199
  defaultFormatter: "prettier",
197
- defaultWhenUnconfigured: true,
200
+ // YAML commonly carries template markers (Helm `{{ .Values.x }}`,
201
+ // Ansible `{{ var }}`). Prettier splits `{{` into `{ {` and reindents
202
+ // embedded templates. Opt in via project prettier config.
203
+ defaultWhenUnconfigured: false,
198
204
  gate: "smart-default",
199
205
  },
200
206
  ],
@@ -203,7 +209,7 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
203
209
  {
204
210
  formatterNames: ["prettier"],
205
211
  defaultFormatter: "prettier",
206
- defaultWhenUnconfigured: true,
212
+ defaultWhenUnconfigured: false,
207
213
  gate: "smart-default",
208
214
  },
209
215
  ],
@@ -750,7 +756,7 @@ const FORMATTER_POLICY_BY_FILENAME = new Map(TERRAGRUNT_FILENAMES.map((name) =>
750
756
  // lines don't fall at the head of the pre-existing lookup-table duplication and
751
757
  // get counted as new duplicated code. Bindings are live, so position is safe.
752
758
  export { AUTO_INSTALLABLE_DEFAULT_FORMATTERS, FORMATTER_POLICY_BY_EXTENSION, FORMATTER_POLICY_BY_FILENAME, };
753
- export function getFormatterPolicyForExtension(ext) {
759
+ function getFormatterPolicyForExtension(ext) {
754
760
  return FORMATTER_POLICY_BY_EXTENSION.get(ext.toLowerCase());
755
761
  }
756
762
  export function getFormatterPolicyForFile(filePath) {
@@ -2236,8 +2242,14 @@ export function _getSpotlessGradleReadCountForTests() {
2236
2242
  * executable Gradle source. This is deliberately a small lexical pre-pass,
2237
2243
  * not a Groovy/Kotlin parser. In particular, statically disabled constructs
2238
2244
  * such as `if (false) { ktlint() }` remain a documented false-positive.
2245
+ *
2246
+ * Exported (with `gradleBlockRanges` below) so `clients/gradle-ktfmt-
2247
+ * style.ts` (#2468) reuses this exact pre-pass for the `ktfmt { }` extension
2248
+ * block instead of a second hand-rolled Gradle lexer — the same
2249
+ * single-source-of-truth reuse `clients/cargo-manifest.ts` did for TOML
2250
+ * parsing (#2466).
2239
2251
  */
2240
- function stripGradleCommentsAndStrings(source) {
2252
+ export function stripGradleCommentsAndStrings(source) {
2241
2253
  let result = "";
2242
2254
  let state = "code";
2243
2255
  let quote = "";
@@ -2303,24 +2315,82 @@ function stripGradleCommentsAndStrings(source) {
2303
2315
  }
2304
2316
  return result;
2305
2317
  }
2306
- function namedGradleBlockBodies(source, name) {
2307
- const bodies = [];
2308
- const startPattern = new RegExp(`\\b${name}\\s*\\{`, "g");
2309
- for (const match of source.matchAll(startPattern)) {
2310
- const open = source.indexOf("{", match.index);
2311
- let depth = 1;
2312
- for (let index = open + 1; index < source.length; index += 1) {
2313
- if (source[index] === "{")
2314
- depth += 1;
2315
- if (source[index] === "}")
2316
- depth -= 1;
2317
- if (depth === 0) {
2318
- bodies.push(source.slice(open + 1, index));
2319
- break;
2320
- }
2318
+ const GRADLE_IDENTIFIER_CHAR = /[A-Za-z0-9_$]/;
2319
+ /**
2320
+ * The identifier immediately preceding a `{`, or `""`. Mirrors the
2321
+ * `\b<name>\s*\{` shape the named lookup used before it became a filter over
2322
+ * this scanner: whitespace (including newlines) between the name and the
2323
+ * brace is skipped, and `.` ends the identifier so `com.ncorti.ktfmt { }`
2324
+ * still reads as `ktfmt` while `something_ktfmt { }` does not.
2325
+ */
2326
+ function gradleBlockNameBefore(source, open) {
2327
+ let index = open - 1;
2328
+ while (index >= 0 && /\s/.test(source[index]))
2329
+ index -= 1;
2330
+ const end = index + 1;
2331
+ while (index >= 0 && GRADLE_IDENTIFIER_CHAR.test(source[index]))
2332
+ index -= 1;
2333
+ return source.slice(index + 1, end);
2334
+ }
2335
+ /**
2336
+ * Body ranges of EVERY brace pair in `source`, in source order, each labelled
2337
+ * with the identifier that introduced it.
2338
+ *
2339
+ * This is the repo's only Gradle brace scanner: `namedGradleBlockRanges` and
2340
+ * `namedGradleBlockBodies` are filters over it. Scanning every pair — rather
2341
+ * than only the pairs whose name a caller thought to ask about — is what lets
2342
+ * `clients/gradle-ktfmt-style.ts` (#2468 review round 3) answer "what block
2343
+ * ENCLOSES this one?" instead of only "is it inside the one name I checked?".
2344
+ * The distinction is load-bearing there: `configure(subprojects.filter { … })
2345
+ * { ktfmt { } }`, `project(":app") { ktfmt { } }` and a convention-plugin
2346
+ * wrapper all have to be recognized as *some* enclosing scope the resolver
2347
+ * cannot evaluate, so it can fail closed rather than treat the block as
2348
+ * top-level.
2349
+ *
2350
+ * Unbalanced braces are best-effort: an unmatched `}` is ignored and an
2351
+ * unclosed `{` yields no range, exactly as the previous per-name depth scan
2352
+ * behaved.
2353
+ */
2354
+ export function gradleBlockRanges(source) {
2355
+ const ranges = [];
2356
+ const opens = [];
2357
+ for (let index = 0; index < source.length; index += 1) {
2358
+ const char = source[index];
2359
+ if (char === "{") {
2360
+ opens.push(index);
2361
+ continue;
2321
2362
  }
2363
+ if (char !== "}")
2364
+ continue;
2365
+ const open = opens.pop();
2366
+ if (open === undefined)
2367
+ continue;
2368
+ ranges.push({
2369
+ name: gradleBlockNameBefore(source, open),
2370
+ start: open + 1,
2371
+ end: index,
2372
+ });
2322
2373
  }
2323
- return bodies;
2374
+ // Pairs are completed innermost-first; callers want source order.
2375
+ return ranges.sort((a, b) => a.start - b.start);
2376
+ }
2377
+ /**
2378
+ * Body ranges of every `<name> { ... }` block, in source order.
2379
+ *
2380
+ * The offsets exist so a caller can ask where a block sits RELATIVE to
2381
+ * another one — `clients/gradle-ktfmt-style.ts` (#2468) needs to know which
2382
+ * block encloses a `ktfmt { }` one, because Gradle's `subprojects { }`
2383
+ * applies it to the children and NOT to the project declaring it, while
2384
+ * `allprojects { }` applies to both. Containment of two ranges is the only
2385
+ * reliable way to answer that; matching body TEXT would collide whenever two
2386
+ * blocks happen to have identical bodies. `namedGradleBlockBodies` stays the
2387
+ * ergonomic form for callers that only need the text.
2388
+ */
2389
+ function namedGradleBlockRanges(source, name) {
2390
+ return gradleBlockRanges(source).filter((range) => range.name === name);
2391
+ }
2392
+ function namedGradleBlockBodies(source, name) {
2393
+ return namedGradleBlockRanges(source, name).map((range) => source.slice(range.start, range.end));
2324
2394
  }
2325
2395
  /**
2326
2396
  * Resolve the formatter elected by a Spotless `kotlin { ... }` block.
@@ -14,6 +14,7 @@
14
14
  import { logTreeSitterDiagnostic } from "./tree-sitter-logger.js";
15
15
  import * as crypto from "node:crypto";
16
16
  import * as fs from "node:fs";
17
+ import { BoundedFifoMap } from "./bounded-cache.js";
17
18
  import { normalizeFilePath } from "./path-utils.js";
18
19
  const TREE_RETIREMENT_GRACE_MICROTASKS = 4;
19
20
  export const TREE_CACHE_COUNTER_KEYS = [
@@ -82,17 +83,23 @@ export function deriveScanTreeCacheCapacity(fileCount, currentMaxSize) {
82
83
  return Math.max(currentMaxSize, target);
83
84
  }
84
85
  export class TreeCache {
85
- cache = new Map();
86
- recentlyEvicted = new Map();
87
- maxSize;
86
+ // BoundedFifoMap, not BoundedLruCache, even though eviction here IS true
87
+ // LRU: recency is refreshed by this class's own explicit `delete`+`set`
88
+ // touch on a VALIDATED hit (see `get`), and only there. A `get` that
89
+ // promoted on every read would also promote entries `get` goes on to
90
+ // reject (content-hash mismatch returns null without removing), silently
91
+ // changing which entry eviction targets (#2442 review F7).
92
+ cache = new BoundedFifoMap(TREE_CACHE_DEFAULT_MAX_SIZE);
93
+ recentlyEvicted;
88
94
  evictionHistoryMax;
89
95
  debug;
90
96
  counters = createTreeCacheCounters();
91
97
  counterObserver;
92
98
  treeErrorObserver;
93
99
  constructor(maxSize = TREE_CACHE_DEFAULT_MAX_SIZE, debug = false, evictionHistoryMax = 4096, counterObserver, treeErrorObserver) {
94
- this.maxSize = maxSize;
100
+ this.cache.setMaxEntries(Math.max(1, Math.floor(maxSize)));
95
101
  this.evictionHistoryMax = Math.max(1, Math.floor(evictionHistoryMax));
102
+ this.recentlyEvicted = new BoundedFifoMap(this.evictionHistoryMax);
96
103
  this.counterObserver = counterObserver;
97
104
  this.treeErrorObserver = treeErrorObserver;
98
105
  this.debug = debug
@@ -109,7 +116,7 @@ export class TreeCache {
109
116
  }
110
117
  /** Current capacity ceiling (entry count, not bytes — see class docstring). */
111
118
  getMaxSize() {
112
- return this.maxSize;
119
+ return this.cache.getMaxEntries();
113
120
  }
114
121
  /**
115
122
  * Resize the capacity ceiling (#1715). Growing only changes the bound —
@@ -120,17 +127,24 @@ export class TreeCache {
120
127
  * entries can never record a hit.
121
128
  */
122
129
  setMaxSize(n) {
123
- const next = Math.max(1, Math.floor(n));
124
- this.maxSize = next;
125
- while (this.cache.size > this.maxSize) {
126
- const firstKey = this.cache.keys().next().value;
127
- if (firstKey === undefined)
128
- break;
129
- const evicted = this.cache.get(firstKey);
130
- if (evicted)
131
- this.rememberEviction(firstKey, evicted);
130
+ this.retireEvicted(this.cache.setMaxEntries(Math.max(1, Math.floor(n))));
131
+ }
132
+ /**
133
+ * Book-keep entries the bounded map dropped: ghost history, the eviction
134
+ * counter, and the WASM-heap tree retirement (#417/#890) that made this
135
+ * cache's eviction side-effect-coupled in the first place. One
136
+ * implementation, shared by the two paths that can overflow
137
+ * ({@link setMaxSize} and {@link set}) — the shape `BoundedFifoMap.set`'s
138
+ * `[key, value]` return exists to enable (#2442 review F7).
139
+ */
140
+ retireEvicted(evicted) {
141
+ for (const [key, cached] of evicted) {
142
+ this.rememberEviction(key, cached);
132
143
  this.recordCounter("evictions");
133
- this.removeEntry(firstKey);
144
+ // The bounded map already removed the entry, so retire the tree
145
+ // directly rather than through removeEntry's re-lookup.
146
+ this.retireTree(cached.tree);
147
+ this.debug(`Evicted: ${key}`);
134
148
  }
135
149
  }
136
150
  /**
@@ -175,14 +189,9 @@ export class TreeCache {
175
189
  }
176
190
  rememberEviction(key, cached) {
177
191
  this.recentlyEvicted.delete(key);
178
- if (this.recentlyEvicted.size >= this.evictionHistoryMax) {
179
- const oldestKey = this.recentlyEvicted.keys().next().value;
180
- if (oldestKey !== undefined) {
181
- this.recentlyEvicted.delete(oldestKey);
182
- this.recordCounter("ghostHistoryDrops");
183
- }
184
- }
185
- this.recentlyEvicted.set(key, cached.contentHash);
192
+ const dropped = this.recentlyEvicted.set(key, cached.contentHash);
193
+ if (dropped.length > 0)
194
+ this.recordCounter("ghostHistoryDrops");
186
195
  }
187
196
  /**
188
197
  * Generate hash for file content
@@ -298,20 +307,11 @@ export class TreeCache {
298
307
  this.recordCounter("replacements");
299
308
  this.removeEntry(key);
300
309
  }
301
- else if (this.cache.size >= this.maxSize) {
302
- // Evict + free the least-recently-used entry when the cache is full:
303
- // get() re-inserts hits, so Map insertion order IS recency order and
304
- // the first key is the LRU entry (#890).
305
- const firstKey = this.cache.keys().next().value;
306
- if (firstKey) {
307
- const evicted = this.cache.get(firstKey);
308
- if (evicted)
309
- this.rememberEviction(firstKey, evicted);
310
- this.recordCounter("evictions");
311
- this.removeEntry(firstKey);
312
- this.debug(`Evicted: ${firstKey}`);
313
- }
314
- }
310
+ // Overflow eviction itself now happens on the `this.cache.set` below —
311
+ // the bounded map evicts the least-recently-used entry (this class's
312
+ // explicit delete+set touch on a validated hit makes insertion order
313
+ // recency order, #890) and hands back the dropped [key, tree] pair for
314
+ // `retireEvicted` to free.
315
315
  let mtime = 0;
316
316
  try {
317
317
  mtime = fs.statSync(filePath).mtimeMs;
@@ -320,14 +320,14 @@ export class TreeCache {
320
320
  // File deleted between parse and cache — cache with mtime=0;
321
321
  // next get() will miss on mtime check and re-parse
322
322
  }
323
- this.cache.set(key, {
323
+ this.retireEvicted(this.cache.set(key, {
324
324
  tree,
325
325
  contentHash,
326
326
  languageId,
327
327
  fileSize: Buffer.byteLength(content, "utf8"),
328
328
  lineCount: content.split("\n").length,
329
329
  lastModified: mtime,
330
- });
330
+ }));
331
331
  this.debug(`Cached: ${filePath} (${content.split("\n").length} lines)`);
332
332
  }
333
333
  /**
@@ -355,7 +355,7 @@ export class TreeCache {
355
355
  return {
356
356
  ...this.counters,
357
357
  size: this.cache.size,
358
- maxSize: this.maxSize,
358
+ maxSize: this.cache.getMaxEntries(),
359
359
  totalLines,
360
360
  totalBytes,
361
361
  misses: this.counters.lookups - this.counters.hits,
@@ -20,17 +20,20 @@ import * as fs from "node:fs";
20
20
  import { createRequire } from "node:module";
21
21
  import * as path from "node:path";
22
22
  import { getDegradationLedgerGeneration, incrementDegradationCount, recordDegradation, recordDegradationOnce, } from "./degradation-ledger.js";
23
+ import { BoundedFifoMap } from "./bounded-cache.js";
23
24
  import { loadWebTreeSitter } from "./deps/web-tree-sitter.js";
24
25
  import { transientRetryDelayMs } from "./dispatch/runners/utils/availability-policy.js";
25
26
  import { getProjectIgnoreMatcher, isExcludedDirName } from "./file-utils.js";
26
27
  import { downloadGrammarDetailed, fileHasWasmMagic, grammarBlockReason, grammarFileSha256, isVendoredGrammar, LANGUAGE_TO_GRAMMAR, pinnedGrammarHash, vendoredGrammarRefusal, vendoredGrammarsDir, } from "./grammar-source.js";
27
28
  import { resolvePackagePath } from "./package-root.js";
28
29
  import { assertInstallAllowed, getProjectTrustGeneration, } from "./project-trust.js";
30
+ import { escapeRegExp } from "./string-utils.js";
29
31
  import { logTreeSitterDiagnostic } from "./tree-sitter-logger.js";
30
32
  import { notifyUserDegradation } from "./user-notify.js";
31
33
  const _require = createRequire(import.meta.url);
32
34
  import { createTreeCacheCounters, deriveScanTreeCacheCapacity, TreeCache, } from "./tree-sitter-cache.js";
33
35
  import { TreeSitterNavigator } from "./tree-sitter-navigator.js";
36
+ import { isProvenSqlAlchemySessionReceiver, isSafePsycopgIdentifierComposition, isSqlAlchemyEntityQueryArgument, isSqlAlchemyStatementArgument, PYTHON_SQLALCHEMY_RECEIVER_NAMES, PYTHON_SQLALCHEMY_STATEMENT_BUILDERS, } from "./python-provenance.js";
34
37
  import { TreeSitterQueryLoader, } from "./tree-sitter-query-loader.js";
35
38
  // Hard cap on a single structural-search file walk. Bounds a misrooted scan so
36
39
  // it can't enumerate an unbounded tree synchronously before result collection
@@ -46,6 +49,14 @@ const QUERY_BATCH_MAX_LOAD_FAILURES = 3;
46
49
  // must not stall the batched query walk; the filter fails open when this cap
47
50
  // is reached so unrelated diagnostics and the current match are preserved.
48
51
  const NO_NESTED_ANCHOR_VISIT_CAP = 10_000;
52
+ // The execute-style Python DB-API/ORM methods this rule treats as SQL sinks.
53
+ // Hoisted so the post-filter does not rebuild the set per match.
54
+ const PYTHON_SQL_SINK_METHODS = new Set([
55
+ "execute",
56
+ "executemany",
57
+ "query",
58
+ "raw",
59
+ ]);
49
60
  const NOT_PARSED = { parsed: false };
50
61
  function createParserCounters() {
51
62
  return {
@@ -69,7 +80,7 @@ function grammarFileStamp(filePath) {
69
80
  return undefined;
70
81
  }
71
82
  }
72
- export function isTreeSitterWasmAbortError(error) {
83
+ function isTreeSitterWasmAbortError(error) {
73
84
  const message = error instanceof Error ? error.message : String(error);
74
85
  return message.includes("Aborted") || message.includes("abort()");
75
86
  }
@@ -269,12 +280,19 @@ export class TreeSitterClient {
269
280
  ParserClass = null;
270
281
  // biome-ignore lint/suspicious/noExplicitAny: Language loader from module
271
282
  LanguageLoader = null;
272
- // biome-ignore lint/suspicious/noExplicitAny: Compiled query cache by language+pattern hash
273
- queryCache = new Map();
274
- /** Combined multi-rule queries by language + rule-set identity (null = don't retry). */
275
- queryBatchCache = new Map();
283
+ // Declared BEFORE the two caches below: a static read by an instance field
284
+ // initializer must already be initialized (TS2729).
276
285
  static QUERY_CACHE_MAX_ENTRIES = 256;
277
286
  static QUERY_BATCH_CACHE_MAX_ENTRIES = 256;
287
+ // biome-ignore lint/suspicious/noExplicitAny: Compiled query cache by language+pattern hash
288
+ // BoundedFifoMap, not BoundedLruCache: recency here is refreshed by this
289
+ // class's own explicit delete+set on both the read and the write path (see
290
+ // cacheQuery and the two lookup sites) — exactly the raw-`Map` discipline
291
+ // the FIFO map documents. A get() that promoted on its own would change
292
+ // which entry eviction targets (#2442 review F5/F7).
293
+ queryCache = new BoundedFifoMap(TreeSitterClient.QUERY_CACHE_MAX_ENTRIES);
294
+ /** Combined multi-rule queries by language + rule-set identity (null = don't retry). */
295
+ queryBatchCache = new BoundedFifoMap(TreeSitterClient.QUERY_BATCH_CACHE_MAX_ENTRIES);
278
296
  queryCacheCap() {
279
297
  const value = Number.parseInt(process.env.PI_LENS_TREE_SITTER_QUERY_CACHE_CAP ?? "", 10);
280
298
  return Number.isSafeInteger(value) && value > 0
@@ -287,27 +305,25 @@ export class TreeSitterClient {
287
305
  ? value
288
306
  : TreeSitterClient.QUERY_BATCH_CACHE_MAX_ENTRIES;
289
307
  }
308
+ // biome-ignore lint/suspicious/noExplicitAny: compiled query objects
290
309
  cacheQuery(key, value) {
291
- this.queryCache.delete(key);
292
- this.queryCache.set(key, value);
293
- while (this.queryCache.size > this.queryCacheCap()) {
294
- const oldest = this.queryCache.entries().next().value;
295
- if (!oldest)
296
- break;
297
- this.queryCache.delete(oldest[0]);
298
- oldest[1]?.query?.delete?.();
299
- }
310
+ this.queryCache.delete(key); // write-refresh: this write is the newest
311
+ // The cap is env-readable, so re-apply it before every write — a raised
312
+ // or lowered ceiling has to take effect on the next insert, not on the
313
+ // next restart. Both calls hand back the dropped [key, value] pairs so
314
+ // the evicted compiled query's native handle is still freed (#2442
315
+ // review F5/F7 — this is why set() returns the VALUE, not just the key).
316
+ const evicted = this.queryCache.setMaxEntries(this.queryCacheCap());
317
+ evicted.push(...this.queryCache.set(key, value));
318
+ for (const [, dropped] of evicted)
319
+ dropped?.query?.delete?.();
300
320
  }
301
321
  cacheQueryBatch(key, value) {
302
322
  this.queryBatchCache.delete(key);
303
- this.queryBatchCache.set(key, value);
304
- while (this.queryBatchCache.size > this.queryBatchCacheCap()) {
305
- const oldest = this.queryBatchCache.entries().next().value;
306
- if (!oldest)
307
- break;
308
- this.queryBatchCache.delete(oldest[0]);
309
- oldest[1]?.query?.delete?.();
310
- }
323
+ const evicted = this.queryBatchCache.setMaxEntries(this.queryBatchCacheCap());
324
+ evicted.push(...this.queryBatchCache.set(key, value));
325
+ for (const [, dropped] of evicted)
326
+ dropped?.query?.delete?.();
311
327
  }
312
328
  /** Consecutive grammar-load failures per batch key — bounds load retries (#889). */
313
329
  queryBatchLoadFailures = new Map();
@@ -1448,7 +1464,8 @@ export class TreeSitterClient {
1448
1464
  const perQuery = new Map();
1449
1465
  await this.parseFileAndUse(filePath, languageId, contentOverride, (tree) => {
1450
1466
  try {
1451
- for (const match of batch.query.matches(tree.rootNode)) {
1467
+ const rootNode = tree.rootNode;
1468
+ for (const match of batch.query.matches(rootNode)) {
1452
1469
  const owner = batch.ownerOfPattern[match.patternIndex];
1453
1470
  if (owner === undefined)
1454
1471
  continue;
@@ -1465,7 +1482,7 @@ export class TreeSitterClient {
1465
1482
  if (!this.evaluatePredicates(batch.query, match))
1466
1483
  continue;
1467
1484
  if (entry.postFilter &&
1468
- !this.applyPostFilter(entry.postFilter, entry.postFilterParams, captures, tree.rootNode)) {
1485
+ !this.applyPostFilter(entry.postFilter, entry.postFilterParams, captures, rootNode)) {
1469
1486
  continue;
1470
1487
  }
1471
1488
  if (match.captures.length === 0)
@@ -1836,14 +1853,16 @@ export class TreeSitterClient {
1836
1853
  }
1837
1854
  return false;
1838
1855
  }
1856
+ /**
1857
+ * SQLAlchemy sessions are conventionally bound to one of a handful of
1858
+ * receiver names. Structural provenance (`python-provenance.ts`) proves the
1859
+ * annotated case; this name check keeps the far more common unannotated
1860
+ * `session.execute(stmt)` quiet, as it has been since the exemption was
1861
+ * added. Removing it regresses every codebase that never annotates.
1862
+ */
1839
1863
  isLikelySqlAlchemyReceiver(text) {
1840
1864
  const tail = text.split(".").pop() ?? text;
1841
- return new Set([
1842
- "session",
1843
- "db_session",
1844
- "async_session",
1845
- "sync_session",
1846
- ]).has(tail.toLowerCase());
1865
+ return PYTHON_SQLALCHEMY_RECEIVER_NAMES.has(tail.toLowerCase());
1847
1866
  }
1848
1867
  /**
1849
1868
  * The body statements of a `switch_case`, in order. A switch_case's named
@@ -2397,12 +2416,20 @@ export class TreeSitterClient {
2397
2416
  return null;
2398
2417
  return object.text;
2399
2418
  }
2419
+ /**
2420
+ * `session.execute(select(...))` and friends pass a statement OBJECT, not a
2421
+ * SQL string: parameterized by construction, and far too noisy as blockers.
2422
+ */
2400
2423
  isSafeSqlAlchemyExpressionCall(node) {
2401
2424
  if (node.type !== "call")
2402
2425
  return false;
2403
2426
  const callee = node.children?.[0]?.text ?? "";
2404
2427
  const expression = node.text;
2405
- return ["select", "insert", "update", "delete"].some((name) => callee === name || expression.startsWith(`${name}(`));
2428
+ for (const name of PYTHON_SQLALCHEMY_STATEMENT_BUILDERS) {
2429
+ if (callee === name || expression.startsWith(`${name}(`))
2430
+ return true;
2431
+ }
2432
+ return false;
2406
2433
  }
2407
2434
  /**
2408
2435
  * Post-filter predicate: returns true if the match should be kept, false to skip.
@@ -2469,15 +2496,6 @@ export class TreeSitterClient {
2469
2496
  }
2470
2497
  return slots;
2471
2498
  }
2472
- /**
2473
- * Escape a string for safe interpolation into a `RegExp` source —
2474
- * needed anywhere an identifier's raw text is combined with regex
2475
- * metacharacters like `\b` word boundaries (see
2476
- * "not_closed_or_try_with_resources" below; #1089 P2).
2477
- */
2478
- function escapeRegExp(s) {
2479
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2480
- }
2481
2499
  switch (postFilter) {
2482
2500
  case "no_nested_anchor_chain": {
2483
2501
  // Tree-sitter queries can express the opening-tag shape, but they
@@ -3546,20 +3564,39 @@ export class TreeSitterClient {
3546
3564
  /^(Open|OpenFile|ReadFile|WriteFile|Create|Remove|RemoveAll)$/.test(captures.FN?.text ?? ""));
3547
3565
  case "py_sql_injection_sink": {
3548
3566
  const fn = captures.FN?.text ?? "";
3549
- if (!new Set(["execute", "executemany", "query", "raw"]).has(fn)) {
3567
+ if (!PYTHON_SQL_SINK_METHODS.has(fn))
3550
3568
  return false;
3551
- }
3552
3569
  const sqlNode = captures.SQL;
3553
- const receiver = captures.OBJ?.text ?? "";
3554
- // SQLAlchemy ORM sessions execute expression objects, not raw SQL
3555
- // strings. `session.execute(stmt)` and `session.execute(select(...))`
3556
- // are parameterized by construction and were too noisy as blockers.
3557
- if (fn === "execute" && this.isLikelySqlAlchemyReceiver(receiver)) {
3570
+ const receiver = captures.OBJ;
3571
+ // Pre-existing exemptions — kept verbatim so no current user
3572
+ // regresses (#2577 review): an unannotated session receiver, and a
3573
+ // statement-builder call in argument position.
3574
+ if (fn === "execute" &&
3575
+ this.isLikelySqlAlchemyReceiver(receiver?.text ?? "")) {
3558
3576
  return false;
3559
3577
  }
3560
3578
  if (sqlNode && this.isSafeSqlAlchemyExpressionCall(sqlNode)) {
3561
3579
  return false;
3562
3580
  }
3581
+ // #2576: a receiver PROVEN to be a sqlalchemy Session/AsyncSession.
3582
+ // `Session.query` takes entity classes and `Session.execute` a
3583
+ // statement object (a builder call, or a name bound to one —
3584
+ // `stmt = select(User)`, which the check above cannot see). Neither
3585
+ // suppression may swallow composed SQL text (#2577 review F1/F2).
3586
+ if (isProvenSqlAlchemySessionReceiver(receiver, rootNode)) {
3587
+ if (fn === "query" &&
3588
+ isSqlAlchemyEntityQueryArgument(sqlNode, rootNode)) {
3589
+ return false;
3590
+ }
3591
+ if (fn === "execute" &&
3592
+ isSqlAlchemyStatementArgument(sqlNode, rootNode)) {
3593
+ return false;
3594
+ }
3595
+ }
3596
+ // #2576: psycopg `sql.SQL("...").format(sql.Identifier(...))`.
3597
+ if (isSafePsycopgIdentifierComposition(sqlNode, rootNode)) {
3598
+ return false;
3599
+ }
3563
3600
  return true;
3564
3601
  }
3565
3602
  case "go_sql_injection_sink":
@@ -3868,7 +3905,8 @@ export class TreeSitterClient {
3868
3905
  const matches = [];
3869
3906
  await this.parseFileAndUse(filePath, languageId, contentOverride, (tree) => {
3870
3907
  try {
3871
- const queryMatches = query.matches(tree.rootNode);
3908
+ const rootNode = tree.rootNode;
3909
+ const queryMatches = query.matches(rootNode);
3872
3910
  for (const match of queryMatches) {
3873
3911
  const captures = {};
3874
3912
  for (const capture of match.captures) {
@@ -3883,7 +3921,7 @@ export class TreeSitterClient {
3883
3921
  continue;
3884
3922
  }
3885
3923
  if (postFilter &&
3886
- !this.applyPostFilter(postFilter, postFilterParams, captures, tree.rootNode)) {
3924
+ !this.applyPostFilter(postFilter, postFilterParams, captures, rootNode)) {
3887
3925
  continue;
3888
3926
  }
3889
3927
  if (match.captures.length > 0) {
@@ -3974,54 +4012,3 @@ export class TreeSitterClient {
3974
4012
  }
3975
4013
  }
3976
4014
  // --- Simplified Pattern Search (regex fallback) ---
3977
- /**
3978
- * Fallback structural search using regex when tree-sitter unavailable
3979
- * Less accurate but works without WASM dependencies
3980
- */
3981
- export function regexStructuralSearch(pattern, files, options = {}) {
3982
- const matches = [];
3983
- const maxResults = options.maxResults ?? 50;
3984
- // Extract pattern structure for regex
3985
- // "console.log($MSG)" -> /console\.log\(([^)]+)\)/
3986
- const regexPattern = pattern
3987
- .replace(/\\/g, "\\\\")
3988
- .replace(/\./g, "\\.")
3989
- .replace(/\$\$\$[A-Z_][A-Z0-9_]*/g, "(.*?)") // variadic - non-greedy
3990
- .replace(/\$[A-Z_][A-Z0-9_]*/g, "([^,)]+)"); // single - capture group
3991
- try {
3992
- const regex = new RegExp(regexPattern, "g");
3993
- for (const file of files) {
3994
- if (matches.length >= maxResults)
3995
- break;
3996
- try {
3997
- const content = fs.readFileSync(file, "utf-8");
3998
- const lines = content.split("\n");
3999
- for (let i = 0; i < lines.length; i++) {
4000
- regex.lastIndex = 0;
4001
- const match = regex.exec(lines[i]);
4002
- if (match) {
4003
- const captures = {};
4004
- // Extract captures
4005
- for (let j = 1; j < match.length; j++) {
4006
- captures[`$${j}`] = match[j];
4007
- }
4008
- matches.push({
4009
- file,
4010
- line: i + 1,
4011
- column: match.index + 1,
4012
- matchedText: match[0],
4013
- captures,
4014
- });
4015
- if (matches.length >= maxResults)
4016
- break;
4017
- }
4018
- }
4019
- }
4020
- catch { }
4021
- }
4022
- }
4023
- catch {
4024
- // Invalid regex
4025
- }
4026
- return matches;
4027
- }
@@ -1,10 +1,10 @@
1
1
  import * as path from "node:path";
2
2
  import { isTestMode } from "./env-utils.js";
3
- import { getGlobalPiLensDir } from "./file-utils.js";
3
+ import { getGlobalPiLensLogDir } from "./probe-home-state.js";
4
4
  import { createNdjsonLogger } from "./ndjson-logger.js";
5
5
  import { getMaxLogSizeMB } from "./log-cleanup.js";
6
6
  import { normalizeLoggedPath } from "./path-utils.js";
7
- const TREE_SITTER_LOG_DIR = getGlobalPiLensDir();
7
+ const TREE_SITTER_LOG_DIR = getGlobalPiLensLogDir();
8
8
  const TREE_SITTER_LOG_FILE = path.join(TREE_SITTER_LOG_DIR, "tree-sitter.log");
9
9
  const writer = createNdjsonLogger({
10
10
  filePath: TREE_SITTER_LOG_FILE,