@herbertgao/pi-extensions 2026.8.4 → 2026.8.6

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/README.md +5 -5
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +11 -3
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +11 -3
  4. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +24 -0
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +91 -1
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -3
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +74 -5
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +7 -6
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +21 -12
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +4 -5
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +53 -14
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +22 -12
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +2 -4
  14. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  15. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +18 -0
  16. package/node_modules/@herbertgao/pi-subagents/package.json +8 -4
  17. package/node_modules/@herbertgao/pi-subagents/src/agent-color.ts +72 -67
  18. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +255 -0
  19. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +191 -3
  20. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +20 -4
  21. package/node_modules/@herbertgao/pi-subagents/src/index.ts +289 -113
  22. package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +24 -1
  23. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +24 -2
  24. package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +15 -6
  25. package/node_modules/@herbertgao/pi-subagents/src/ui/schedule-menu.ts +9 -8
  26. package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -0
  27. package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +9 -5
  28. package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +2 -0
  29. package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +20 -0
  30. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +6 -0
  31. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +7 -0
  32. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  33. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +7 -0
  34. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +16 -5
  35. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +4 -2
  36. package/node_modules/@narumitw/pi-btw/README.md +32 -24
  37. package/node_modules/@narumitw/pi-btw/package.json +6 -6
  38. package/node_modules/@narumitw/pi-btw/src/btw.ts +4 -2
  39. package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -13
  40. package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
  41. package/node_modules/pi-lens/CHANGELOG.md +2535 -954
  42. package/node_modules/pi-lens/README.md +13 -0
  43. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +73 -18
  44. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +314 -0
  45. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
  46. package/node_modules/pi-lens/dist/clients/agent-nudge.js +14 -7
  47. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +68 -16
  48. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -1
  49. package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +37 -10
  50. package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +63 -0
  51. package/node_modules/pi-lens/dist/clients/atomic-write.js +114 -14
  52. package/node_modules/pi-lens/dist/clients/bash-file-access.js +193 -23
  53. package/node_modules/pi-lens/dist/clients/biome-client.js +136 -24
  54. package/node_modules/pi-lens/dist/clients/bootstrap.js +11 -2
  55. package/node_modules/pi-lens/dist/clients/bounded-cache.js +34 -0
  56. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +192 -0
  57. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +62 -6
  58. package/node_modules/pi-lens/dist/clients/bus-publish.js +19 -4
  59. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +47 -5
  60. package/node_modules/pi-lens/dist/clients/cache-manager.js +97 -9
  61. package/node_modules/pi-lens/dist/clients/cache-observability.js +301 -14
  62. package/node_modules/pi-lens/dist/clients/call-graph.js +449 -117
  63. package/node_modules/pi-lens/dist/clients/cascade-format.js +57 -2
  64. package/node_modules/pi-lens/dist/clients/child-unref.js +85 -0
  65. package/node_modules/pi-lens/dist/clients/codebase-model.js +52 -23
  66. package/node_modules/pi-lens/dist/clients/collateral-test-role.js +40 -0
  67. package/node_modules/pi-lens/dist/clients/complexity-client.js +2 -1
  68. package/node_modules/pi-lens/dist/clients/console-guard-install.js +26 -0
  69. package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
  70. package/node_modules/pi-lens/dist/clients/dead-code-client.js +137 -31
  71. package/node_modules/pi-lens/dist/clients/debug-handles.js +206 -0
  72. package/node_modules/pi-lens/dist/clients/debug-heap.js +167 -0
  73. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +153 -0
  74. package/node_modules/pi-lens/dist/clients/dependency-checker.js +361 -77
  75. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +65 -13
  76. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +18 -4
  77. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +176 -29
  78. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +23 -7
  79. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +8 -18
  80. package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +8 -5
  81. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +35 -0
  82. package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +17 -8
  83. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +1172 -683
  84. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +14 -0
  85. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +11 -1
  86. package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +50 -0
  87. package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +135 -0
  88. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +23 -5
  89. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +5 -9
  90. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +3 -2
  91. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
  92. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +154 -0
  93. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +3 -2
  94. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +5 -1
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +47 -6
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -9
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +9 -5
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +4 -8
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -3
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -3
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +163 -0
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +37 -5
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +3 -1
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +13 -7
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +226 -0
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +69 -0
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +473 -42
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +97 -0
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
  114. package/node_modules/pi-lens/dist/clients/disposition-publish.js +18 -4
  115. package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
  116. package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
  117. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
  118. package/node_modules/pi-lens/dist/clients/extension-log.js +432 -0
  119. package/node_modules/pi-lens/dist/clients/extension-mode.js +73 -0
  120. package/node_modules/pi-lens/dist/clients/file-kinds.js +57 -0
  121. package/node_modules/pi-lens/dist/clients/file-role.js +18 -3
  122. package/node_modules/pi-lens/dist/clients/file-utils.js +56 -34
  123. package/node_modules/pi-lens/dist/clients/fix-worklog.js +5 -1
  124. package/node_modules/pi-lens/dist/clients/format-events-publish.js +51 -7
  125. package/node_modules/pi-lens/dist/clients/format-service.js +24 -8
  126. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +8 -0
  127. package/node_modules/pi-lens/dist/clients/formatters.js +358 -61
  128. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +148 -15
  129. package/node_modules/pi-lens/dist/clients/git-guard.js +678 -11
  130. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
  131. package/node_modules/pi-lens/dist/clients/go-client.js +23 -40
  132. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +124 -7
  133. package/node_modules/pi-lens/dist/clients/grammar-source.js +12 -4
  134. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +21 -3
  135. package/node_modules/pi-lens/dist/clients/host-ports.js +31 -0
  136. package/node_modules/pi-lens/dist/clients/installer/index.js +395 -100
  137. package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
  138. package/node_modules/pi-lens/dist/clients/jscpd-client.js +27 -55
  139. package/node_modules/pi-lens/dist/clients/knip-client.js +58 -42
  140. package/node_modules/pi-lens/dist/clients/language-policy.js +14 -2
  141. package/node_modules/pi-lens/dist/clients/language-profile.js +13 -7
  142. package/node_modules/pi-lens/dist/clients/latency-logger.js +72 -1
  143. package/node_modules/pi-lens/dist/clients/lens-config.js +12 -1
  144. package/node_modules/pi-lens/dist/clients/lens-engine.js +86 -17
  145. package/node_modules/pi-lens/dist/clients/lens-events.js +63 -9
  146. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +16 -0
  147. package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
  148. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +73 -0
  149. package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
  150. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
  151. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +59 -14
  152. package/node_modules/pi-lens/dist/clients/lsp/client.js +662 -56
  153. package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
  154. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +115 -0
  155. package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
  156. package/node_modules/pi-lens/dist/clients/lsp/index.js +2025 -800
  157. package/node_modules/pi-lens/dist/clients/lsp/launch.js +32 -26
  158. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
  159. package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +83 -0
  160. package/node_modules/pi-lens/dist/clients/lsp/server.js +437 -86
  161. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +96 -0
  162. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +21 -5
  163. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +18 -3
  164. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
  165. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +8 -0
  166. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +248 -0
  167. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +127 -32
  168. package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
  169. package/node_modules/pi-lens/dist/clients/mcp/session.js +235 -14
  170. package/node_modules/pi-lens/dist/clients/memory-sampler.js +115 -0
  171. package/node_modules/pi-lens/dist/clients/metrics-client.js +2 -1
  172. package/node_modules/pi-lens/dist/clients/metrics-history.js +2 -1
  173. package/node_modules/pi-lens/dist/clients/model-provider.js +53 -0
  174. package/node_modules/pi-lens/dist/clients/module-report-lsp.js +5 -0
  175. package/node_modules/pi-lens/dist/clients/module-report.js +168 -3
  176. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +319 -118
  177. package/node_modules/pi-lens/dist/clients/opengrep-client.js +1 -1
  178. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +120 -5
  179. package/node_modules/pi-lens/dist/clients/path-utils.js +110 -9
  180. package/node_modules/pi-lens/dist/clients/pipeline.js +76 -15
  181. package/node_modules/pi-lens/dist/clients/print-mode.js +21 -0
  182. package/node_modules/pi-lens/dist/clients/project-changes.js +40 -0
  183. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +3 -2
  184. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +1 -1
  185. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +8 -0
  186. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +23 -2
  187. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +21 -1
  188. package/node_modules/pi-lens/dist/clients/project-lens-config.js +86 -9
  189. package/node_modules/pi-lens/dist/clients/project-report.js +27 -13
  190. package/node_modules/pi-lens/dist/clients/project-snapshot.js +175 -20
  191. package/node_modules/pi-lens/dist/clients/project-trust.js +181 -0
  192. package/node_modules/pi-lens/dist/clients/quiet-window.js +18 -4
  193. package/node_modules/pi-lens/dist/clients/read-bridge.js +147 -0
  194. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +159 -1
  195. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +150 -29
  196. package/node_modules/pi-lens/dist/clients/read-guard.js +185 -4
  197. package/node_modules/pi-lens/dist/clients/resource-sampler.js +70 -84
  198. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +1122 -221
  199. package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +2 -1
  200. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +21 -10
  201. package/node_modules/pi-lens/dist/clients/review-graph/query.js +24 -0
  202. package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +46 -0
  203. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +57 -6
  204. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +51 -3
  205. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +30 -1
  206. package/node_modules/pi-lens/dist/clients/ruff-client.js +27 -44
  207. package/node_modules/pi-lens/dist/clients/run-duration.js +55 -0
  208. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +257 -77
  209. package/node_modules/pi-lens/dist/clients/runtime-context.js +129 -12
  210. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +242 -26
  211. package/node_modules/pi-lens/dist/clients/runtime-session.js +428 -117
  212. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +71 -48
  213. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +342 -33
  214. package/node_modules/pi-lens/dist/clients/runtime-turn.js +619 -93
  215. package/node_modules/pi-lens/dist/clients/rust-client.js +23 -38
  216. package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
  217. package/node_modules/pi-lens/dist/clients/security-scan-client.js +90 -6
  218. package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
  219. package/node_modules/pi-lens/dist/clients/sg-runner.js +424 -196
  220. package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
  221. package/node_modules/pi-lens/dist/clients/smells-rollup.js +231 -0
  222. package/node_modules/pi-lens/dist/clients/source-filter.js +184 -20
  223. package/node_modules/pi-lens/dist/clients/source-walker.js +66 -10
  224. package/node_modules/pi-lens/dist/clients/startup-scan.js +15 -16
  225. package/node_modules/pi-lens/dist/clients/startup-timing.js +7 -1
  226. package/node_modules/pi-lens/dist/clients/test-runner-client.js +469 -32
  227. package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
  228. package/node_modules/pi-lens/dist/clients/tool-policy.js +390 -39
  229. package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
  230. package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -0
  231. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +6 -1
  232. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +419 -50
  233. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +21 -0
  234. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +7 -1
  235. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +7 -2
  236. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +44 -7
  237. package/node_modules/pi-lens/dist/clients/user-notify.js +51 -0
  238. package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +57 -0
  239. package/node_modules/pi-lens/dist/clients/warm-attach.js +50 -13
  240. package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
  241. package/node_modules/pi-lens/dist/clients/word-index.js +680 -81
  242. package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
  243. package/node_modules/pi-lens/dist/index.js +51785 -36118
  244. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
  245. package/node_modules/pi-lens/dist/mcp/server.js +219 -46
  246. package/node_modules/pi-lens/dist/tools/activate-tools.js +17 -5
  247. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +9 -4
  248. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +93 -27
  249. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
  250. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +203 -38
  251. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +210 -42
  252. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -3
  253. package/node_modules/pi-lens/dist/tools/module-report.js +9 -0
  254. package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -1
  255. package/node_modules/pi-lens/docs/agent-guide.md +40 -15
  256. package/node_modules/pi-lens/docs/agent-tools.md +21 -10
  257. package/node_modules/pi-lens/docs/analysisall.md +192 -0
  258. package/node_modules/pi-lens/docs/api-ports-inventory.md +165 -0
  259. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +14 -3
  260. package/node_modules/pi-lens/docs/durable-store-audit-1202.md +32 -0
  261. package/node_modules/pi-lens/docs/environment-variables.md +44 -0
  262. package/node_modules/pi-lens/docs/fable.md +27 -0
  263. package/node_modules/pi-lens/docs/features.md +45 -3
  264. package/node_modules/pi-lens/docs/globalconfig.md +56 -1
  265. package/node_modules/pi-lens/docs/language-coverage.md +4 -3
  266. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +2 -2
  267. package/node_modules/pi-lens/docs/mcp.md +37 -4
  268. package/node_modules/pi-lens/docs/servercapabilities.md +3 -3
  269. package/node_modules/pi-lens/docs/settings.md +18 -1
  270. package/node_modules/pi-lens/docs/tools.md +4 -1
  271. package/node_modules/pi-lens/docs/usage.md +41 -5
  272. package/node_modules/pi-lens/docs/word-index.md +35 -0
  273. package/node_modules/pi-lens/package.json +10 -10
  274. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
  275. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
  276. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +8 -0
  277. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-js-test.yml +9 -0
  278. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-test.yml +9 -0
  279. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
  280. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
  281. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +7 -0
  282. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +7 -0
  283. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +8 -0
  284. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +8 -0
  285. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-laundering-test.yml +11 -0
  286. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
  287. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
  288. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
  289. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +21 -0
  290. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread-js.yml +21 -0
  291. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread.yml +29 -0
  292. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
  293. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
  294. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +9 -0
  295. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +9 -0
  296. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +13 -0
  297. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +16 -0
  298. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +27 -0
  299. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
  300. package/node_modules/pi-lens/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
  301. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +2 -2
  302. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +134 -2
  303. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +10 -11
  304. package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +22 -22
  305. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +8 -114
  306. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +129 -0
  307. package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
  308. package/node_modules/pi-mcp-adapter/CHANGELOG.md +38 -0
  309. package/node_modules/pi-mcp-adapter/README.md +38 -4
  310. package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
  311. package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
  312. package/node_modules/pi-mcp-adapter/cli.js +25 -4
  313. package/node_modules/pi-mcp-adapter/commands.ts +15 -7
  314. package/node_modules/pi-mcp-adapter/config.ts +4 -4
  315. package/node_modules/pi-mcp-adapter/direct-tools.ts +58 -8
  316. package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -8
  317. package/node_modules/pi-mcp-adapter/index.ts +8 -3
  318. package/node_modules/pi-mcp-adapter/init.ts +29 -5
  319. package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -4
  320. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
  321. package/node_modules/pi-mcp-adapter/mcp-auth.ts +60 -2
  322. package/node_modules/pi-mcp-adapter/mcp-panel.ts +45 -9
  323. package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +22 -20
  324. package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +2 -1
  325. package/node_modules/pi-mcp-adapter/metadata-cache.ts +37 -4
  326. package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
  327. package/node_modules/pi-mcp-adapter/package.json +4 -3
  328. package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
  329. package/node_modules/pi-mcp-adapter/request-headers-command.ts +336 -0
  330. package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
  331. package/node_modules/pi-mcp-adapter/server-manager.ts +41 -7
  332. package/node_modules/pi-mcp-adapter/session-recovery.ts +8 -2
  333. package/node_modules/pi-mcp-adapter/tool-approval.ts +48 -7
  334. package/node_modules/pi-mcp-adapter/tool-metadata.ts +62 -4
  335. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
  336. package/node_modules/pi-mcp-adapter/types.ts +176 -27
  337. package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
  338. package/node_modules/pi-web-access/CHANGELOG.md +14 -0
  339. package/node_modules/pi-web-access/README.md +18 -12
  340. package/node_modules/pi-web-access/auth-fetch.ts +148 -0
  341. package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
  342. package/node_modules/pi-web-access/curator-page.ts +5 -3
  343. package/node_modules/pi-web-access/curator-server.ts +2 -1
  344. package/node_modules/pi-web-access/extract.ts +106 -34
  345. package/node_modules/pi-web-access/fetch-params.ts +17 -3
  346. package/node_modules/pi-web-access/firecrawl.ts +172 -12
  347. package/node_modules/pi-web-access/gemini-search.ts +18 -4
  348. package/node_modules/pi-web-access/index.ts +120 -48
  349. package/node_modules/pi-web-access/package.json +2 -2
  350. package/node_modules/pi-web-access/summary-review.ts +11 -5
  351. package/node_modules/pi-web-access/youtube-extract.ts +2 -2
  352. package/package.json +12 -13
  353. package/node_modules/pi-lens/dist/clients/ast-grep-parser.js +0 -86
  354. package/node_modules/pi-lens/dist/clients/lsp/interactive-install.js +0 -367
  355. package/node_modules/pi-lens/dist/clients/lsp/lsp-index.js +0 -10
  356. package/node_modules/pi-lens/dist/clients/lsp/server-strategies.js +0 -5
  357. package/node_modules/pi-lens/dist/clients/project-metadata.js +0 -690
  358. package/node_modules/pi-lens/dist/clients/source-groups.js +0 -96
  359. package/node_modules/pi-lens/dist/clients/startup-marker.js +0 -6
  360. package/node_modules/pi-lens/dist/clients/types.js +0 -11
@@ -23,6 +23,10 @@ import { loadWebTreeSitter } from "./deps/web-tree-sitter.js";
23
23
  import { getProjectIgnoreMatcher, isExcludedDirName } from "./file-utils.js";
24
24
  import { downloadGrammar, grammarBlockReason, LANGUAGE_TO_GRAMMAR, } from "./grammar-source.js";
25
25
  import { resolvePackagePath } from "./package-root.js";
26
+ import { recordDegradation } from "./degradation-ledger.js";
27
+ import { assertInstallAllowed, getProjectTrustGeneration, } from "./project-trust.js";
28
+ import { logTreeSitterDiagnostic } from "./tree-sitter-logger.js";
29
+ import { notifyUserDegradation } from "./user-notify.js";
26
30
  const _require = createRequire(import.meta.url);
27
31
  import { createTreeCacheCounters, TreeCache, } from "./tree-sitter-cache.js";
28
32
  import { TreeSitterNavigator } from "./tree-sitter-navigator.js";
@@ -37,6 +41,10 @@ const TREE_SITTER_MAX_SCAN_FILES = 20_000;
37
41
  // failure (offline lazy fetch, mid-scan load error) recover on a later scan
38
42
  // instead of paying the 3.3x per-rule fallback for the process lifetime (#889).
39
43
  const QUERY_BATCH_MAX_LOAD_FAILURES = 3;
44
+ // Keep post-filter tree walks bounded. A malformed or unexpectedly huge tree
45
+ // must not stall the batched query walk; the filter fails open when this cap
46
+ // is reached so unrelated diagnostics and the current match are preserved.
47
+ const NO_NESTED_ANCHOR_VISIT_CAP = 10_000;
40
48
  const NOT_PARSED = { parsed: false };
41
49
  function createParserCounters() {
42
50
  return {
@@ -60,6 +68,8 @@ export class TreeSitterClient {
60
68
  grammarsDir;
61
69
  /** In-flight/settled lazy grammar fetches, keyed by wasm filename. */
62
70
  grammarEnsurePromises = new Map();
71
+ trustBlockedGrammarNotifications = new Set();
72
+ trustNotificationsGeneration = getProjectTrustGeneration();
63
73
  // biome-ignore lint/suspicious/noExplicitAny: Optional dependency loaded dynamically
64
74
  ParserClass = null;
65
75
  // biome-ignore lint/suspicious/noExplicitAny: Language loader from module
@@ -68,6 +78,38 @@ export class TreeSitterClient {
68
78
  queryCache = new Map();
69
79
  /** Combined multi-rule queries by language + rule-set identity (null = don't retry). */
70
80
  queryBatchCache = new Map();
81
+ static QUERY_CACHE_MAX_ENTRIES = 256;
82
+ static QUERY_BATCH_CACHE_MAX_ENTRIES = 256;
83
+ queryCacheCap() {
84
+ const value = Number.parseInt(process.env.PI_LENS_TREE_SITTER_QUERY_CACHE_CAP ?? "", 10);
85
+ return Number.isSafeInteger(value) && value > 0 ? value : TreeSitterClient.QUERY_CACHE_MAX_ENTRIES;
86
+ }
87
+ queryBatchCacheCap() {
88
+ const value = Number.parseInt(process.env.PI_LENS_TREE_SITTER_QUERY_BATCH_CACHE_CAP ?? "", 10);
89
+ return Number.isSafeInteger(value) && value > 0 ? value : TreeSitterClient.QUERY_BATCH_CACHE_MAX_ENTRIES;
90
+ }
91
+ cacheQuery(key, value) {
92
+ this.queryCache.delete(key);
93
+ this.queryCache.set(key, value);
94
+ while (this.queryCache.size > this.queryCacheCap()) {
95
+ const oldest = this.queryCache.entries().next().value;
96
+ if (!oldest)
97
+ break;
98
+ this.queryCache.delete(oldest[0]);
99
+ oldest[1]?.query?.delete?.();
100
+ }
101
+ }
102
+ cacheQueryBatch(key, value) {
103
+ this.queryBatchCache.delete(key);
104
+ this.queryBatchCache.set(key, value);
105
+ while (this.queryBatchCache.size > this.queryBatchCacheCap()) {
106
+ const oldest = this.queryBatchCache.entries().next().value;
107
+ if (!oldest)
108
+ break;
109
+ this.queryBatchCache.delete(oldest[0]);
110
+ oldest[1]?.query?.delete?.();
111
+ }
112
+ }
71
113
  /** Consecutive grammar-load failures per batch key — bounds load retries (#889). */
72
114
  queryBatchLoadFailures = new Map();
73
115
  queryLoader = new TreeSitterQueryLoader();
@@ -98,10 +140,40 @@ export class TreeSitterClient {
98
140
  return false;
99
141
  if (!this.wasmAborted) {
100
142
  this.wasmAborted = true;
143
+ recordDegradation({
144
+ kind: "wasm-abort",
145
+ subject: "web-tree-sitter",
146
+ reason: error instanceof Error ? error.message : String(error),
147
+ });
101
148
  this.onWasmAbort?.();
102
149
  }
103
150
  return true;
104
151
  }
152
+ /**
153
+ * O(1) runtime footprint counters (#1123 item 2 memory attribution): every
154
+ * field is a `Map.size` read, never an iteration over the maps' contents.
155
+ * `wasmMemoryBytes` is deliberately NOT included here — web-tree-sitter
156
+ * 0.25.10's Emscripten `Module` (which owns the WASM linear memory /
157
+ * `HEAPU8.buffer`) is a private closure variable in the package's
158
+ * `bindings.ts` and is not exposed through any public export (`Parser`,
159
+ * `Language`, `Query`, ...); reaching it would require either reflecting
160
+ * into the package's internal module state (brittle across web-tree-sitter
161
+ * versions/bundling) or overriding Emscripten's `wasmMemory` module option
162
+ * at `Parser.init()` time with a hand-constructed `WebAssembly.Memory`
163
+ * matching the library's own default page-count math (risks a memory-import
164
+ * mismatch that would break ALL structural analysis, for an
165
+ * observability-only feature). `process.memoryUsage().arrayBuffers` is the
166
+ * process-wide proxy used instead (WASM linear memory backs an ArrayBuffer,
167
+ * so it is included there) — see clients/memory-sampler.ts.
168
+ */
169
+ getRuntimeStats() {
170
+ return {
171
+ languagesLoaded: this.languages.size,
172
+ parsersLoaded: this.parsers.size,
173
+ queryCacheSize: this.queryCache.size,
174
+ queryBatchCacheSize: this.queryBatchCache.size,
175
+ };
176
+ }
105
177
  getParseCacheStats() {
106
178
  return {
107
179
  ...this.treeCache.getStats(),
@@ -145,7 +217,13 @@ export class TreeSitterClient {
145
217
  /** Debug logging helper */
146
218
  dbg(msg) {
147
219
  if (this.verbose) {
148
- console.error(`[tree-sitter] ${msg}`); // pi-lens-ignore: console-statement intentional verbose logger
220
+ // #1333: verbose gating is preserved, but the SINK is tree-sitter.log —
221
+ // a raw write would corrupt pi's frame the moment verbose is enabled.
222
+ logTreeSitterDiagnostic({
223
+ subsystem: "tree-sitter-client",
224
+ level: "debug",
225
+ message: msg,
226
+ });
149
227
  }
150
228
  }
151
229
  /**
@@ -285,6 +363,32 @@ export class TreeSitterClient {
285
363
  if (this.resolveGrammarFile(grammarFile)) {
286
364
  return true;
287
365
  }
366
+ if (!assertInstallAllowed(`tree-sitter grammar fetch: ${grammarFile}`)) {
367
+ const unavailable = `tree-sitter grammar '${grammarFile}' is unavailable because the project is not trusted; ` +
368
+ `runtime grammar downloads are disabled until trust is granted.`;
369
+ logTreeSitterDiagnostic({
370
+ subsystem: "tree-sitter-client",
371
+ message: unavailable,
372
+ metadata: { grammarFile, outcome: "trust-gated" },
373
+ });
374
+ recordDegradation({
375
+ kind: "grammar-blocked",
376
+ subject: grammarFile,
377
+ reason: "runtime grammar download blocked because project is untrusted",
378
+ });
379
+ // Lazy clear-on-transition (#1363 review): compare the trust
380
+ // generation at use time -- no listener registration, no retention.
381
+ const generation = getProjectTrustGeneration();
382
+ if (generation !== this.trustNotificationsGeneration) {
383
+ this.trustNotificationsGeneration = generation;
384
+ this.trustBlockedGrammarNotifications.clear();
385
+ }
386
+ if (!this.trustBlockedGrammarNotifications.has(grammarFile)) {
387
+ this.trustBlockedGrammarNotifications.add(grammarFile);
388
+ notifyUserDegradation(`pi-lens: ${unavailable}`);
389
+ }
390
+ return false;
391
+ }
288
392
  const inflight = this.grammarEnsurePromises.get(grammarFile);
289
393
  if (inflight)
290
394
  return inflight;
@@ -300,17 +404,36 @@ export class TreeSitterClient {
300
404
  if (ok) {
301
405
  if (!this.grammarsDir)
302
406
  this.grammarsDir = dir;
303
- console.error(`[pi-lens] fetched missing tree-sitter grammar ${grammarFile} at runtime (install scripts were skipped by the package manager)`);
407
+ logTreeSitterDiagnostic({
408
+ subsystem: "tree-sitter-client",
409
+ level: "warn",
410
+ message: `fetched missing tree-sitter grammar ${grammarFile} at runtime (install scripts were skipped by the package manager)`,
411
+ metadata: { grammarFile, outcome: "fetched" },
412
+ });
304
413
  }
305
414
  else {
306
415
  // Surface the degradation once per grammar (the promise cache dedupes)
307
416
  // instead of failing silently — otherwise pnpm/bun users offline get
308
417
  // no signal that a language's tree-sitter features are unavailable.
309
- console.error(`[pi-lens] tree-sitter grammar '${grammarFile}' is unavailable — ` +
418
+ const unavailable = `tree-sitter grammar '${grammarFile}' is unavailable — ` +
310
419
  `symbol search, module reports and structural rules for this language will be degraded. ` +
311
420
  `The package manager skipped install scripts and the runtime download failed (offline or CDN unreachable). ` +
312
421
  `Fix: reinstall with a manager that runs postinstall, allow its build scripts ` +
313
- `(pnpm approve-builds / bun trustedDependencies), or restore network access.`);
422
+ `(pnpm approve-builds / bun trustedDependencies), or restore network access.`;
423
+ logTreeSitterDiagnostic({
424
+ subsystem: "tree-sitter-client",
425
+ message: unavailable,
426
+ metadata: { grammarFile, outcome: "unavailable" },
427
+ });
428
+ recordDegradation({
429
+ kind: "grammar-blocked",
430
+ subject: grammarFile,
431
+ reason: "runtime grammar download failed",
432
+ });
433
+ // HUMAN-audience: an offline grammar fetch silently degrades this
434
+ // language's features, so it reaches the user through the HOST's
435
+ // render path (#1333) rather than a raw terminal write.
436
+ notifyUserDegradation(`pi-lens: ${unavailable}`);
314
437
  }
315
438
  return ok;
316
439
  })();
@@ -393,6 +516,11 @@ export class TreeSitterClient {
393
516
  const blockReason = grammarBlockReason(grammarFile);
394
517
  if (blockReason) {
395
518
  this.dbg(`Grammar ${grammarFile} blocked on this runtime — ${blockReason}`);
519
+ recordDegradation({
520
+ kind: "grammar-blocked",
521
+ subject: grammarFile,
522
+ reason: blockReason,
523
+ });
396
524
  return null;
397
525
  }
398
526
  // Look across the bundled core `grammars/` dir and the postinstall/lazy
@@ -744,8 +872,11 @@ export class TreeSitterClient {
744
872
  .digest("hex");
745
873
  const cacheKey = this.getQueryCacheKey(`batch:${identity}`, languageId);
746
874
  const cached = this.queryBatchCache.get(cacheKey);
747
- if (cached !== undefined)
875
+ if (cached !== undefined) {
876
+ this.queryBatchCache.delete(cacheKey);
877
+ this.queryBatchCache.set(cacheKey, cached);
748
878
  return cached;
879
+ }
749
880
  // A loadLanguage() failure is transient (offline lazy grammar fetch,
750
881
  // transient mid-scan load error) — do NOT cache null for it, or every
751
882
  // later scan pays the ~3.3x per-rule fallback for the process lifetime
@@ -757,7 +888,7 @@ export class TreeSitterClient {
757
888
  if (failures >= QUERY_BATCH_MAX_LOAD_FAILURES) {
758
889
  this.dbg(`Batch: grammar for ${languageId} failed to load ${failures} times — caching miss`);
759
890
  this.queryBatchLoadFailures.delete(cacheKey);
760
- this.queryBatchCache.set(cacheKey, null);
891
+ this.cacheQueryBatch(cacheKey, null);
761
892
  }
762
893
  else {
763
894
  this.queryBatchLoadFailures.set(cacheKey, failures);
@@ -817,7 +948,7 @@ export class TreeSitterClient {
817
948
  }
818
949
  };
819
950
  const batch = await build();
820
- this.queryBatchCache.set(cacheKey, batch);
951
+ this.cacheQueryBatch(cacheKey, batch);
821
952
  return batch;
822
953
  }
823
954
  /**
@@ -928,7 +1059,10 @@ export class TreeSitterClient {
928
1059
  // Check cache first
929
1060
  if (this.queryCache.has(cacheKey)) {
930
1061
  this.dbg(`Query cache hit: ${cacheKey}`);
931
- return this.queryCache.get(cacheKey);
1062
+ const cached = this.queryCache.get(cacheKey);
1063
+ this.queryCache.delete(cacheKey);
1064
+ this.queryCache.set(cacheKey, cached);
1065
+ return cached;
932
1066
  }
933
1067
  const language = await this.loadLanguage(languageId);
934
1068
  if (!language) {
@@ -945,7 +1079,7 @@ export class TreeSitterClient {
945
1079
  this.dbg(`Query compiled with ${query.patternCount()} patterns`);
946
1080
  const result = { query, metavars, postFilter, postFilterParams };
947
1081
  // Cache the compiled query
948
- this.queryCache.set(cacheKey, result);
1082
+ this.cacheQuery(cacheKey, result);
949
1083
  return result;
950
1084
  }
951
1085
  catch (err) {
@@ -958,7 +1092,10 @@ export class TreeSitterClient {
958
1092
  async compileRawQuery(queryId, queryStr, metavars, languageId, postFilter, postFilterParams) {
959
1093
  const cacheKey = this.getQueryCacheKey(`raw:${queryId}:${queryStr}`, languageId);
960
1094
  if (this.queryCache.has(cacheKey)) {
961
- return this.queryCache.get(cacheKey);
1095
+ const cached = this.queryCache.get(cacheKey);
1096
+ this.queryCache.delete(cacheKey);
1097
+ this.queryCache.set(cacheKey, cached);
1098
+ return cached;
962
1099
  }
963
1100
  const language = await this.loadLanguage(languageId);
964
1101
  if (!language)
@@ -969,7 +1106,7 @@ export class TreeSitterClient {
969
1106
  // biome-ignore lint/suspicious/noExplicitAny: Language type compatibility
970
1107
  const query = new Query(language, queryStr);
971
1108
  const result = { query, metavars, postFilter, postFilterParams };
972
- this.queryCache.set(cacheKey, result);
1109
+ this.cacheQuery(cacheKey, result);
973
1110
  return result;
974
1111
  }
975
1112
  catch (err) {
@@ -989,13 +1126,59 @@ export class TreeSitterClient {
989
1126
  if (this.reportedCompileFailures.has(key))
990
1127
  return;
991
1128
  this.reportedCompileFailures.add(key);
992
- console.error(`[pi-lens] tree-sitter rule '${ruleId}' failed to compile against '${languageId}' — ` +
993
- `matches for this rule are silently dropped rather than reported. ` +
994
- `Fix the query in the rule definition to re-enable it. (${err})`);
1129
+ logTreeSitterDiagnostic({
1130
+ subsystem: "tree-sitter-client",
1131
+ languageId,
1132
+ message: `tree-sitter rule '${ruleId}' failed to compile against '${languageId}' — ` +
1133
+ `matches for this rule are silently dropped rather than reported. ` +
1134
+ `Fix the query in the rule definition to re-enable it. (${err})`,
1135
+ metadata: { ruleId },
1136
+ });
995
1137
  }
996
1138
  hasChildToken(node, token) {
997
1139
  return node.children?.some((child) => child.type === token || child.text === token);
998
1140
  }
1141
+ /**
1142
+ * Collect names introduced by a parameter binding pattern.
1143
+ *
1144
+ * Binding-aware: only identifiers in a *binding* position are counted.
1145
+ * Two node shapes hold reference expressions rather than bindings and
1146
+ * must not be descended into:
1147
+ * - `assignment_pattern` / `object_assignment_pattern` (`pattern = default`,
1148
+ * e.g. `[a = b]` or `{a = b}`) — the `value`/`right` side is a default
1149
+ * *expression*, not a new name (`b` in `{a = b}` is a reference).
1150
+ * - `computed_property_name` (`[expr]:` in a destructuring pattern,
1151
+ * e.g. `{[key]: a}`) — `expr` is a reference, not a binding.
1152
+ * Nested binding positions (destructured sub-patterns) are still walked
1153
+ * so renamed/duplicate collisions inside them are found.
1154
+ */
1155
+ bindingNames(node) {
1156
+ const names = new Set();
1157
+ if (!node)
1158
+ return names;
1159
+ const stack = [node];
1160
+ while (stack.length > 0) {
1161
+ const current = stack.pop();
1162
+ if (current.type === "identifier")
1163
+ names.add(current.text);
1164
+ else if (current.type === "shorthand_property_identifier_pattern") {
1165
+ names.add(current.text);
1166
+ }
1167
+ if (current.type === "property_identifier" ||
1168
+ current.type === "type_identifier" ||
1169
+ current.type === "computed_property_name")
1170
+ continue;
1171
+ if (current.type === "assignment_pattern" ||
1172
+ current.type === "object_assignment_pattern") {
1173
+ const left = current.childForFieldName?.("left");
1174
+ if (left)
1175
+ stack.push(left);
1176
+ continue;
1177
+ }
1178
+ stack.push(...(current.children ?? []));
1179
+ }
1180
+ return names;
1181
+ }
999
1182
  containsYieldInFunctionBody(node, root = node) {
1000
1183
  for (const child of node.children ?? []) {
1001
1184
  if (child.type === "yield")
@@ -1030,6 +1213,122 @@ export class TreeSitterClient {
1030
1213
  // First named child is the case's value expression (`case <value>`).
1031
1214
  return named.slice(1);
1032
1215
  }
1216
+ /**
1217
+ * Whether a case body carries an intentional fallthrough marker comment
1218
+ * (`// fallthrough`, `// falls through`, …). Only a *trailing* marker is
1219
+ * honored: a comment attached to the case node after its body statements
1220
+ * (TypeScript), the case's next sibling in the switch body (JavaScript), or
1221
+ * one inside the trailing body statement's block. A comment in a nested
1222
+ * function or an earlier statement is not a marker for this case and must
1223
+ * not suppress a genuine fall-through. Only comment nodes are checked, never
1224
+ * the case value or statement text.
1225
+ */
1226
+ hasFallthroughMarker(caseNode) {
1227
+ const comments = [];
1228
+ const body = this.switchCaseBodyStatements(caseNode);
1229
+ const last = body[body.length - 1];
1230
+ const lastEnd = last?.endIndex ?? caseNode.startIndex;
1231
+ // TypeScript attaches a trailing `// fallthrough` as a direct child of the
1232
+ // case node, after the body statements.
1233
+ for (const c of caseNode.children ?? []) {
1234
+ if (c.type.includes("comment") && c.startIndex >= lastEnd) {
1235
+ comments.push(c);
1236
+ }
1237
+ }
1238
+ // JavaScript attaches it to the switch body as the case's next sibling.
1239
+ const siblings = (caseNode.parent?.children ?? []).filter((c) => c.isNamed);
1240
+ const index = siblings.findIndex((c) => c.startIndex === caseNode.startIndex);
1241
+ const next = index >= 0 ? siblings[index + 1] : undefined;
1242
+ if (next?.type.includes("comment"))
1243
+ comments.push(next);
1244
+ // A marker inside the trailing body statement's block (e.g. `{ work();
1245
+ // /* fallthrough */ }`). Only the trailing statement is walked, and nested
1246
+ // functions are not descended into, so a comment in a nested function or
1247
+ // an earlier statement can't suppress a genuine fall-through.
1248
+ if (last) {
1249
+ const NESTED_FN = new Set([
1250
+ "function_declaration",
1251
+ "function_expression",
1252
+ "arrow_function",
1253
+ "method_definition",
1254
+ "generator_function",
1255
+ "generator_function_declaration",
1256
+ "class_declaration",
1257
+ ]);
1258
+ const stack = [last];
1259
+ for (let visited = 0; stack.length > 0 && visited < 500; visited++) {
1260
+ const node = stack.pop();
1261
+ if (!node)
1262
+ break;
1263
+ if (node.type.includes("comment"))
1264
+ comments.push(node);
1265
+ if (!NESTED_FN.has(node.type)) {
1266
+ stack.push(...(node.children ?? []));
1267
+ }
1268
+ }
1269
+ }
1270
+ return comments.some((c) => /falls?\s?through/i.test(c.text));
1271
+ }
1272
+ /**
1273
+ * Whether a statement terminates control flow (does not fall through to the
1274
+ * next statement). Handles terminators, trailing blocks, try/catch/finally,
1275
+ * and exhaustive if/else. Fail-safe: any unrecognized shape returns false
1276
+ * (falls through), so a blocking rule never under-reports. Depth-bounded so a
1277
+ * pathological nesting cannot blow the stack.
1278
+ */
1279
+ statementTerminates(node, depth = 0) {
1280
+ if (depth > 20)
1281
+ return false;
1282
+ const t = node.type;
1283
+ const TERMINATORS = new Set([
1284
+ "break_statement",
1285
+ "return_statement",
1286
+ "throw_statement",
1287
+ "continue_statement",
1288
+ ]);
1289
+ if (TERMINATORS.has(t))
1290
+ return true;
1291
+ // Wrapper nodes whose trailing statement decides termination: a block, an
1292
+ // `else` clause, a `catch` clause, or a `finally` clause.
1293
+ if (t === "statement_block" ||
1294
+ t === "else_clause" ||
1295
+ t === "catch_clause" ||
1296
+ t === "finally_clause") {
1297
+ const inner = (node.children ?? []).filter((c) => c.isNamed && !c.type.includes("comment"));
1298
+ const last = inner[inner.length - 1];
1299
+ if (!last)
1300
+ return false; // empty wrapper falls through
1301
+ return this.statementTerminates(last, depth + 1);
1302
+ }
1303
+ if (t === "try_statement") {
1304
+ // The try body is the first statement_block child. A returning/throwing
1305
+ // try completes after finally, so only a catch that handles a thrown try
1306
+ // and falls through can still reach the end. A try body that completes
1307
+ // normally reaches the end unless a finally terminates it.
1308
+ const tryBody = (node.children ?? []).find((c) => c.type === "statement_block");
1309
+ const tryTerminates = tryBody
1310
+ ? this.statementTerminates(tryBody, depth + 1)
1311
+ : false;
1312
+ const catches = (node.children ?? []).filter((c) => c.type === "catch_clause");
1313
+ const fin = (node.children ?? []).find((c) => c.type === "finally_clause");
1314
+ if (tryTerminates) {
1315
+ return catches.every((c) => this.statementTerminates(c, depth + 1));
1316
+ }
1317
+ return !!fin && this.statementTerminates(fin, depth + 1);
1318
+ }
1319
+ if (t === "if_statement") {
1320
+ // An if only terminates when it has an else and both branches do.
1321
+ const named = (node.children ?? []).filter((c) => c.isNamed && !c.type.includes("comment"));
1322
+ // named = [condition, consequence, (else_clause)]
1323
+ const consequence = named[1];
1324
+ const alternative = named[2];
1325
+ if (!consequence || !alternative)
1326
+ return false;
1327
+ return (this.statementTerminates(consequence, depth + 1) &&
1328
+ this.statementTerminates(alternative, depth + 1));
1329
+ }
1330
+ return false;
1331
+ }
1033
1332
  /**
1034
1333
  * Whether a `switch_case`'s next sibling is another label — `case "a": case
1035
1334
  * "b": handle()` groups two labels onto one body, which is idiomatic, not a
@@ -1524,7 +1823,71 @@ export class TreeSitterClient {
1524
1823
  }
1525
1824
  return slots;
1526
1825
  }
1826
+ /**
1827
+ * Escape a string for safe interpolation into a `RegExp` source —
1828
+ * needed anywhere an identifier's raw text is combined with regex
1829
+ * metacharacters like `\b` word boundaries (see
1830
+ * "not_closed_or_try_with_resources" below; #1089 P2).
1831
+ */
1832
+ function escapeRegExp(s) {
1833
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1834
+ }
1527
1835
  switch (postFilter) {
1836
+ case "no_nested_anchor_chain": {
1837
+ // Tree-sitter queries can express the opening-tag shape, but they
1838
+ // cannot express both an arbitrary-depth descendant relationship and
1839
+ // an ancestor exclusion. Walk the captured JSX element here so the
1840
+ // TSX path agrees with the ast-grep rule: keep exactly the outermost
1841
+ // anchor that contains another anchor, including through wrappers.
1842
+ // This post-filter runs inside a batched query walk. It must never
1843
+ // abort that walk or turn a malformed tree into a false negative.
1844
+ try {
1845
+ const outer = captures.OUTER_ELEMENT;
1846
+ if (!outer) {
1847
+ this.reportPostFilterFailure(postFilter, "missing OUTER_ELEMENT capture");
1848
+ return true;
1849
+ }
1850
+ const isAnchorElement = (node) => {
1851
+ if (node.type !== "jsx_element")
1852
+ return false;
1853
+ const opening = node.childForFieldName?.("open_tag") ??
1854
+ node.children?.find((child) => child.type === "jsx_opening_element");
1855
+ const name = opening?.childForFieldName?.("name") ??
1856
+ opening?.children?.find((child) => child.type === "identifier");
1857
+ return name?.text === "a";
1858
+ };
1859
+ let visited = 0;
1860
+ let ancestor = outer.parent;
1861
+ while (ancestor) {
1862
+ if (++visited > NO_NESTED_ANCHOR_VISIT_CAP) {
1863
+ this.reportPostFilterFailure(postFilter, `ancestor walk exceeded ${NO_NESTED_ANCHOR_VISIT_CAP} nodes`);
1864
+ return true;
1865
+ }
1866
+ if (isAnchorElement(ancestor))
1867
+ return false;
1868
+ ancestor = ancestor.parent;
1869
+ }
1870
+ const pending = [...(outer.children ?? [])];
1871
+ while (pending.length > 0) {
1872
+ const node = pending.pop();
1873
+ if (!node)
1874
+ continue;
1875
+ if (++visited > NO_NESTED_ANCHOR_VISIT_CAP) {
1876
+ this.reportPostFilterFailure(postFilter, `descendant walk exceeded ${NO_NESTED_ANCHOR_VISIT_CAP} nodes`);
1877
+ return true;
1878
+ }
1879
+ if (isAnchorElement(node))
1880
+ return true;
1881
+ pending.push(...(node.children ?? []));
1882
+ }
1883
+ return false;
1884
+ }
1885
+ catch (error) {
1886
+ const reason = error instanceof Error ? error.message : String(error);
1887
+ this.reportPostFilterFailure(postFilter, `tree walk failed${reason ? `: ${reason}` : ""}`);
1888
+ return true;
1889
+ }
1890
+ }
1528
1891
  case "differs_only_by_case": {
1529
1892
  try {
1530
1893
  const field = captures.FIELD?.text ?? "";
@@ -1637,7 +2000,11 @@ export class TreeSitterClient {
1637
2000
  ]) ?? rootNode;
1638
2001
  if (!scope)
1639
2002
  return true;
1640
- const resourceWord = new RegExp(`\b${resource}\b`);
2003
+ // `\b` in a template literal is the BACKSPACE control char
2004
+ // (U+0008), not a regex word-boundary escape — that requires
2005
+ // the literal two-character sequence `\\b`. The identifier
2006
+ // itself must also be regex-escaped (#1089 P2).
2007
+ const resourceWord = new RegExp(`\\b${escapeRegExp(resource)}\\b`);
1641
2008
  const stack = [scope];
1642
2009
  for (let visited = 0; stack.length > 0 && visited < 10_000; visited++) {
1643
2010
  const node = stack.pop();
@@ -1846,41 +2213,27 @@ export class TreeSitterClient {
1846
2213
  return !this.bodyHasLoopExit(bodyNode, false);
1847
2214
  }
1848
2215
  case "same_param_name": {
1849
- // duplicate-function-arg: keep the pair only when the two captured
1850
- // parameter identifiers share the same name.
1851
- const first = captures.PARAM1?.text;
1852
- const second = captures.NAME?.text;
1853
- return !!first && first === second;
2216
+ // duplicate-function-arg: keep the pair when the captured binding
2217
+ // patterns share any name.
2218
+ const first = this.bindingNames(captures.PARAM1);
2219
+ const second = this.bindingNames(captures.NAME);
2220
+ return [...first].some((name) => second.has(name));
1854
2221
  }
1855
2222
  case "no_terminating_statement": {
1856
2223
  // switch-case-termination: keep a non-empty case (already known to be
1857
- // followed by another case via the query) whose last body statement is
1858
- // not a terminator, i.e. it falls through. Empty cases are handled by
1859
- // empty-switch-case, so require at least one body statement here.
2224
+ // followed by another case via the query) whose last body statement
2225
+ // does not terminate, i.e. it falls through. Empty cases are handled
2226
+ // by empty-switch-case, so require at least one body statement here.
2227
+ // An intentional `// fallthrough` marker suppresses the finding.
1860
2228
  const caseNode = captures.CASE;
1861
2229
  if (!caseNode)
1862
2230
  return false;
2231
+ if (this.hasFallthroughMarker(caseNode))
2232
+ return false;
1863
2233
  const body = this.switchCaseBodyStatements(caseNode);
1864
2234
  if (body.length === 0)
1865
2235
  return false;
1866
- let last = body[body.length - 1];
1867
- while (last.type === "statement_block") {
1868
- // A block-scoped case body terminates when its trailing statement
1869
- // terminates. Keep an empty block as the last node so it still
1870
- // reports fall-through.
1871
- const inner = (last.children ?? []).filter((c) => c.isNamed && !c.type.includes("comment"));
1872
- const next = inner[inner.length - 1];
1873
- if (!next)
1874
- break;
1875
- last = next;
1876
- }
1877
- const TERMINATORS = new Set([
1878
- "break_statement",
1879
- "return_statement",
1880
- "throw_statement",
1881
- "continue_statement",
1882
- ]);
1883
- return !TERMINATORS.has(last.type);
2236
+ return !this.statementTerminates(body[body.length - 1]);
1884
2237
  }
1885
2238
  case "no_break_or_return": {
1886
2239
  // infinite-loop-java: keep a `while(true)`/`for(;;)` whose body has no
@@ -1981,18 +2334,17 @@ export class TreeSitterClient {
1981
2334
  // A typed `except` clause has a named child for the exception
1982
2335
  // spec — one of: identifier (e.g. `except ValueError`),
1983
2336
  // attribute (e.g. `except asyncio.TimeoutError` — dotted name),
1984
- // tuple (e.g. `except (E, F)`), or as_pattern (e.g. `except E as e`).
2337
+ // tuple (e.g. `except (E, F)`), as_pattern (e.g. `except E as e`),
2338
+ // parenthesized_expression (e.g. `except (E)`), or subscript
2339
+ // (e.g. `except dict[str, int]` — #1244, mirrors the ast-grep
2340
+ // rule's shape space).
1985
2341
  // Bare `except:` has NO named children (just the `except` keyword,
1986
2342
  // the `:` colon, and the body block).
1987
2343
  // biome-ignore lint/suspicious/noExplicitAny: AST iteration
1988
2344
  const hasExceptionSpec = clauseNode.children.some((c) => {
1989
2345
  if (!c.isNamed)
1990
2346
  return false;
1991
- return (c.type === "identifier" ||
1992
- c.type === "attribute" ||
1993
- c.type === "tuple" ||
1994
- c.type === "as_pattern" ||
1995
- c.type === "parenthesized_expression");
2347
+ return (c.type !== "block");
1996
2348
  });
1997
2349
  // Fire ONLY when bare (no exception spec)
1998
2350
  return !hasExceptionSpec;
@@ -2756,14 +3108,31 @@ export class TreeSitterClient {
2756
3108
  }
2757
3109
  }
2758
3110
  reportedMissingPostFilters = new Set();
3111
+ reportedPostFilterFailures = new Set();
3112
+ /** Keep a failed post-filter match and leave a bounded diagnostic trail. */
3113
+ reportPostFilterFailure(postFilter, reason) {
3114
+ if (this.reportedPostFilterFailures.has(postFilter))
3115
+ return;
3116
+ this.reportedPostFilterFailures.add(postFilter);
3117
+ logTreeSitterDiagnostic({
3118
+ subsystem: "tree-sitter-client",
3119
+ message: `tree-sitter rule post_filter '${postFilter}' failed — ` +
3120
+ `keeping the diagnostic (fail-open): ${reason}.`,
3121
+ metadata: { postFilter },
3122
+ });
3123
+ }
2759
3124
  /** Warn once per unimplemented post_filter — a silent rule needs a trail. */
2760
3125
  reportMissingPostFilter(postFilter) {
2761
3126
  if (this.reportedMissingPostFilters.has(postFilter))
2762
3127
  return;
2763
3128
  this.reportedMissingPostFilters.add(postFilter);
2764
- console.error(`[pi-lens] tree-sitter rule post_filter '${postFilter}' is not implemented — ` +
2765
- `matches for the rules using it are suppressed rather than reported unfiltered. ` +
2766
- `Implement it in applyPostFilter (clients/tree-sitter-client.ts) to re-enable them.`);
3129
+ logTreeSitterDiagnostic({
3130
+ subsystem: "tree-sitter-client",
3131
+ message: `tree-sitter rule post_filter '${postFilter}' is not implemented — ` +
3132
+ `matches for the rules using it are suppressed rather than reported unfiltered. ` +
3133
+ `Implement it in applyPostFilter (clients/tree-sitter-client.ts) to re-enable them.`,
3134
+ metadata: { postFilter },
3135
+ });
2767
3136
  }
2768
3137
  /**
2769
3138
  * Evaluate text predicates (#match?, #eq?) for a query match.