@herbertgao/pi-extensions 2026.8.5 → 2026.8.7

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 (389) hide show
  1. package/README.md +5 -5
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -2
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +19 -7
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +30 -9
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +272 -22
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +74 -5
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +94 -77
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +48 -6
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +1 -1
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +14 -0
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +55 -17
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +41 -2
  14. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +75 -6
  15. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +7 -3
  16. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +64 -1
  17. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  18. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +16 -0
  19. package/node_modules/@herbertgao/pi-subagents/README.md +30 -22
  20. package/node_modules/@herbertgao/pi-subagents/examples/agent-tool-description.md +5 -5
  21. package/node_modules/@herbertgao/pi-subagents/package.json +7 -7
  22. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +7 -1
  23. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +48 -19
  24. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +22 -15
  25. package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +26 -2
  26. package/node_modules/@herbertgao/pi-subagents/src/index.ts +219 -40
  27. package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +101 -3
  28. package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +18 -4
  29. package/node_modules/@herbertgao/pi-subagents/src/settings.ts +99 -0
  30. package/node_modules/@herbertgao/pi-subagents/src/types.ts +22 -3
  31. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +51 -3
  32. package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +20 -2
  33. package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +34 -10
  34. package/node_modules/@herbertgao/pi-subagents/src/usage.ts +129 -1
  35. package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +29 -5
  36. package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +3 -0
  37. package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +46 -10
  38. package/node_modules/@juicesharp/rpiv-ask-user-question/config.ts +25 -0
  39. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/configuration.md +13 -4
  40. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +8 -1
  41. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +3 -1
  42. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +2 -2
  43. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/zh.json +2 -2
  44. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +3 -2
  45. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +10 -0
  46. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +3 -1
  47. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +30 -7
  48. package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
  49. package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +30 -4
  50. package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +26 -5
  51. package/node_modules/@narumitw/pi-btw/README.md +119 -103
  52. package/node_modules/@narumitw/pi-btw/dist/index.ts +2898 -0
  53. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +7 -0
  54. package/node_modules/@narumitw/pi-btw/package.json +13 -9
  55. package/node_modules/@narumitw/pi-btw/src/btw.ts +40 -42
  56. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +55 -10
  57. package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +390 -0
  58. package/node_modules/@narumitw/pi-btw/src/menu.ts +78 -15
  59. package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
  60. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
  61. package/node_modules/pi-lens/CHANGELOG.md +1004 -0
  62. package/node_modules/pi-lens/README.md +11 -3
  63. package/node_modules/pi-lens/config/ruff/core.toml +42 -0
  64. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +130 -27
  65. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +427 -0
  66. package/node_modules/pi-lens/dist/clients/agent-nudge.js +79 -16
  67. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +9 -3
  68. package/node_modules/pi-lens/dist/clients/atomic-write.js +9 -3
  69. package/node_modules/pi-lens/dist/clients/bash-file-access.js +285 -8
  70. package/node_modules/pi-lens/dist/clients/biome-client.js +176 -37
  71. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +428 -0
  72. package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +119 -0
  73. package/node_modules/pi-lens/dist/clients/bootstrap.js +12 -1
  74. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +12 -3
  75. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +192 -0
  76. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +62 -6
  77. package/node_modules/pi-lens/dist/clients/bus-publish.js +11 -3
  78. package/node_modules/pi-lens/dist/clients/cascade-budget.js +196 -0
  79. package/node_modules/pi-lens/dist/clients/cascade-format.js +76 -2
  80. package/node_modules/pi-lens/dist/clients/cascade-types.js +9 -1
  81. package/node_modules/pi-lens/dist/clients/child-unref.js +54 -6
  82. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +76 -24
  83. package/node_modules/pi-lens/dist/clients/console-guard-install.js +16 -4
  84. package/node_modules/pi-lens/dist/clients/dead-code-client.js +249 -52
  85. package/node_modules/pi-lens/dist/clients/deadline-utils.js +8 -1
  86. package/node_modules/pi-lens/dist/clients/debug-handles.js +5 -0
  87. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +62 -13
  88. package/node_modules/pi-lens/dist/clients/dependency-checker.js +43 -9
  89. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +153 -37
  90. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
  91. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
  92. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +165 -11
  93. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +325 -72
  94. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +7 -4
  95. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +5 -0
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +234 -18
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +187 -19
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +6 -3
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +11 -9
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +398 -0
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +11 -3
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +12 -7
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +18 -0
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +4 -0
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +16 -3
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +81 -41
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +16 -0
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +23 -0
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +331 -55
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +38 -5
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +97 -19
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +14 -0
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +20 -0
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +22 -0
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +10 -0
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +18 -6
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +73 -12
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +488 -0
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +84 -0
  121. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +260 -27
  122. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +943 -93
  123. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +86 -11
  124. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +72 -0
  125. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +106 -0
  126. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +9 -0
  127. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -1
  128. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +4 -2
  129. package/node_modules/pi-lens/dist/clients/disposition-publish.js +10 -3
  130. package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
  131. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +28 -5
  132. package/node_modules/pi-lens/dist/clients/extension-log.js +317 -3
  133. package/node_modules/pi-lens/dist/clients/file-kinds.js +8 -2
  134. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +294 -0
  135. package/node_modules/pi-lens/dist/clients/finding-identity.js +88 -0
  136. package/node_modules/pi-lens/dist/clients/fix-worklog.js +5 -1
  137. package/node_modules/pi-lens/dist/clients/format-events-publish.js +39 -8
  138. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +3 -2
  139. package/node_modules/pi-lens/dist/clients/formatters.js +500 -115
  140. package/node_modules/pi-lens/dist/clients/generation-guard.js +216 -0
  141. package/node_modules/pi-lens/dist/clients/git-guard.js +49 -21
  142. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
  143. package/node_modules/pi-lens/dist/clients/go-client.js +21 -39
  144. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +219 -10
  145. package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
  146. package/node_modules/pi-lens/dist/clients/host-ports.js +9 -1
  147. package/node_modules/pi-lens/dist/clients/installer/index.js +1119 -101
  148. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +58 -0
  149. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +715 -0
  150. package/node_modules/pi-lens/dist/clients/instance-reaper.js +643 -97
  151. package/node_modules/pi-lens/dist/clients/instance-registry.js +87 -17
  152. package/node_modules/pi-lens/dist/clients/jscpd-client.js +86 -15
  153. package/node_modules/pi-lens/dist/clients/knip-client.js +361 -24
  154. package/node_modules/pi-lens/dist/clients/language-policy.js +8 -2
  155. package/node_modules/pi-lens/dist/clients/language-profile.js +2 -0
  156. package/node_modules/pi-lens/dist/clients/latency-logger.js +487 -9
  157. package/node_modules/pi-lens/dist/clients/lazy-import.js +21 -0
  158. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -0
  159. package/node_modules/pi-lens/dist/clients/lens-events.js +56 -25
  160. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +16 -0
  161. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +45 -2
  162. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
  163. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +160 -20
  164. package/node_modules/pi-lens/dist/clients/lsp/client.js +1666 -148
  165. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +111 -0
  166. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
  167. package/node_modules/pi-lens/dist/clients/lsp/index.js +2496 -171
  168. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +215 -0
  169. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +146 -14
  170. package/node_modules/pi-lens/dist/clients/lsp/language.js +48 -4
  171. package/node_modules/pi-lens/dist/clients/lsp/server.js +517 -69
  172. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +22 -0
  173. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +43 -0
  174. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +172 -0
  175. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +21 -5
  176. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +61 -3
  177. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +422 -28
  178. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +30 -0
  179. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +175 -0
  180. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +3 -2
  181. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +4 -0
  182. package/node_modules/pi-lens/dist/clients/mcp/session.js +39 -18
  183. package/node_modules/pi-lens/dist/clients/model-provider.js +53 -0
  184. package/node_modules/pi-lens/dist/clients/module-report.js +8 -0
  185. package/node_modules/pi-lens/dist/clients/opengrep-client.js +15 -1
  186. package/node_modules/pi-lens/dist/clients/package-manager.js +170 -14
  187. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +27 -0
  188. package/node_modules/pi-lens/dist/clients/path-utils.js +160 -0
  189. package/node_modules/pi-lens/dist/clients/pipeline.js +178 -30
  190. package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +37 -0
  191. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +17 -3
  192. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +89 -22
  193. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +85 -15
  194. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +15 -3
  195. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +23 -2
  196. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +50 -2
  197. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +5 -0
  198. package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
  199. package/node_modules/pi-lens/dist/clients/project-snapshot.js +299 -0
  200. package/node_modules/pi-lens/dist/clients/quiet-window-config.js +47 -0
  201. package/node_modules/pi-lens/dist/clients/quiet-window.js +11 -25
  202. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +1 -1
  203. package/node_modules/pi-lens/dist/clients/read-guard.js +184 -10
  204. package/node_modules/pi-lens/dist/clients/resource-sampler.js +49 -17
  205. package/node_modules/pi-lens/dist/clients/reverse-deps.js +50 -11
  206. package/node_modules/pi-lens/dist/clients/review-graph/query.js +76 -4
  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 +248 -13
  209. package/node_modules/pi-lens/dist/clients/runtime-context.js +112 -12
  210. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +472 -61
  211. package/node_modules/pi-lens/dist/clients/runtime-session.js +359 -13
  212. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
  213. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +269 -22
  214. package/node_modules/pi-lens/dist/clients/runtime-turn.js +948 -87
  215. package/node_modules/pi-lens/dist/clients/rust-client.js +21 -37
  216. package/node_modules/pi-lens/dist/clients/safe-spawn.js +220 -7
  217. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +161 -0
  218. package/node_modules/pi-lens/dist/clients/search-read-registration.js +34 -6
  219. package/node_modules/pi-lens/dist/clients/security-scan-client.js +224 -8
  220. package/node_modules/pi-lens/dist/clients/sg-runner.js +249 -37
  221. package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
  222. package/node_modules/pi-lens/dist/clients/smells-rollup.js +18 -11
  223. package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
  224. package/node_modules/pi-lens/dist/clients/startup-timing.js +26 -2
  225. package/node_modules/pi-lens/dist/clients/test-runner-client.js +574 -32
  226. package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
  227. package/node_modules/pi-lens/dist/clients/tool-policy.js +127 -20
  228. package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -0
  229. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +75 -1
  230. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +769 -64
  231. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +11 -0
  232. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +90 -0
  233. package/node_modules/pi-lens/dist/clients/trivy-client.js +19 -1
  234. package/node_modules/pi-lens/dist/clients/warm-attach.js +17 -0
  235. package/node_modules/pi-lens/dist/clients/widget-state.js +336 -36
  236. package/node_modules/pi-lens/dist/clients/word-index.js +305 -33
  237. package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
  238. package/node_modules/pi-lens/dist/index.js +64677 -49484
  239. package/node_modules/pi-lens/dist/mcp/server.js +8 -2
  240. package/node_modules/pi-lens/dist/tools/activate-tools.js +17 -5
  241. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +9 -4
  242. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
  243. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +469 -56
  244. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +190 -46
  245. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
  246. package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -1
  247. package/node_modules/pi-lens/docs/agent-guide.md +44 -14
  248. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +518 -501
  249. package/node_modules/pi-lens/docs/dispositions.md +21 -0
  250. package/node_modules/pi-lens/docs/features.md +33 -6
  251. package/node_modules/pi-lens/docs/globalconfig.md +14 -0
  252. package/node_modules/pi-lens/docs/language-coverage.md +71 -5
  253. package/node_modules/pi-lens/docs/mcp.md +2 -2
  254. package/node_modules/pi-lens/docs/servercapabilities.md +4 -1
  255. package/node_modules/pi-lens/docs/settings.md +25 -1
  256. package/node_modules/pi-lens/docs/usage.md +24 -5
  257. package/node_modules/pi-lens/docs/word-index.md +35 -0
  258. package/node_modules/pi-lens/package.json +10 -6
  259. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-js-snapshot.yml +23 -0
  260. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-snapshot.yml +23 -0
  261. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/ts-void-zero-snapshot.yml +23 -0
  262. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-object-param-test.yml +16 -0
  263. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +22 -0
  264. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-compile-call-test.yml +29 -1
  265. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-js-test.yml +9 -0
  266. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-test.yml +9 -0
  267. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-dupe-class-members-test.yml +33 -0
  268. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-js-test.yml +7 -1
  269. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-test.yml +7 -1
  270. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-known-value-widening-test.yml +9 -0
  271. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-non-null-assertion-test.yml +216 -0
  272. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +9 -0
  273. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +9 -0
  274. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +9 -0
  275. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +9 -0
  276. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-return-value-in-generator-test.yml +12 -0
  277. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-runtime-typeof-test.yml +33 -0
  278. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-server-bind-wildcard-test.yml +9 -0
  279. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-shape-in-symbol-names-test.yml +13 -0
  280. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-js-test.yml +5 -1
  281. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-test.yml +5 -1
  282. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-laundering-test.yml +11 -0
  283. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-parameters-test.yml +12 -0
  284. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-returns-test.yml +16 -0
  285. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-any-test.yml +10 -0
  286. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-unknown-test.yml +8 -0
  287. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/redundant-unsafe-function-test.yml +35 -0
  288. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/require-safety-comment-for-as-unknown-as-test.yml +177 -0
  289. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ts-void-zero-test.yml +1 -0
  290. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member-js.yml +13 -2
  291. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member.yml +13 -2
  292. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +2 -0
  293. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-object-param.yml +60 -0
  294. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +66 -0
  295. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-compile-call.yml +63 -5
  296. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread-js.yml +21 -0
  297. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread.yml +29 -0
  298. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-dupe-class-members.yml +52 -8
  299. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html-js.yml +27 -2
  300. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html.yml +27 -2
  301. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-known-value-widening.yml +71 -0
  302. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-non-null-assertion.yml +316 -0
  303. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +7 -0
  304. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +29 -0
  305. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +30 -0
  306. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +41 -0
  307. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +42 -0
  308. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-return-value-in-generator.yml +22 -5
  309. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-runtime-typeof.yml +121 -0
  310. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-server-bind-wildcard.yml +23 -7
  311. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-shape-in-symbol-names.yml +68 -0
  312. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +41 -2
  313. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code.yml +41 -2
  314. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +32 -0
  315. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-parameters.yml +67 -0
  316. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-returns.yml +106 -0
  317. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-any.yml +50 -0
  318. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-unknown.yml +56 -0
  319. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +2 -0
  320. package/node_modules/pi-lens/rules/ast-grep-rules/rules/redundant-unsafe-function.yml +32 -0
  321. package/node_modules/pi-lens/rules/ast-grep-rules/rules/require-safety-comment-for-as-unknown-as.yml +184 -0
  322. package/node_modules/pi-lens/rules/ast-grep-rules/rules/ts-void-zero.yml +5 -0
  323. package/node_modules/pi-lens/rules/ast-grep-rules/self-scan-baseline.json +5 -0
  324. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +132 -1
  325. package/node_modules/pi-lens/scripts/download-grammars.js +25 -0
  326. package/node_modules/pi-lens/scripts/grammars.lock.json +9 -0
  327. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +12 -11
  328. package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +22 -22
  329. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +87 -116
  330. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +170 -0
  331. package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
  332. package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
  333. package/node_modules/pi-mcp-adapter/CHANGELOG.md +49 -0
  334. package/node_modules/pi-mcp-adapter/README.md +51 -6
  335. package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
  336. package/node_modules/pi-mcp-adapter/cli.js +188 -5
  337. package/node_modules/pi-mcp-adapter/commands.ts +128 -22
  338. package/node_modules/pi-mcp-adapter/config.ts +28 -10
  339. package/node_modules/pi-mcp-adapter/direct-tools.ts +23 -19
  340. package/node_modules/pi-mcp-adapter/index.ts +219 -32
  341. package/node_modules/pi-mcp-adapter/init.ts +36 -4
  342. package/node_modules/pi-mcp-adapter/lifecycle.ts +315 -24
  343. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +39 -6
  344. package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +387 -0
  345. package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +27 -3
  346. package/node_modules/pi-mcp-adapter/mcp-code.ts +4 -0
  347. package/node_modules/pi-mcp-adapter/mcp-panel.ts +42 -13
  348. package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +2 -1
  349. package/node_modules/pi-mcp-adapter/metadata-cache.ts +46 -25
  350. package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
  351. package/node_modules/pi-mcp-adapter/package.json +4 -1
  352. package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
  353. package/node_modules/pi-mcp-adapter/request-headers-command.ts +330 -0
  354. package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
  355. package/node_modules/pi-mcp-adapter/server-manager.ts +145 -2
  356. package/node_modules/pi-mcp-adapter/session-recovery.ts +7 -0
  357. package/node_modules/pi-mcp-adapter/tool-approval.ts +15 -2
  358. package/node_modules/pi-mcp-adapter/tool-metadata.ts +38 -18
  359. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +44 -4
  360. package/node_modules/pi-mcp-adapter/types.ts +95 -10
  361. package/node_modules/pi-mcp-adapter/ui-server.ts +18 -16
  362. package/node_modules/pi-mcp-adapter/utils.ts +70 -0
  363. package/node_modules/pi-web-access/CHANGELOG.md +37 -0
  364. package/node_modules/pi-web-access/README.md +38 -14
  365. package/node_modules/pi-web-access/anysearch.ts +4 -2
  366. package/node_modules/pi-web-access/auth-fetch.ts +148 -0
  367. package/node_modules/pi-web-access/brave.ts +16 -4
  368. package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
  369. package/node_modules/pi-web-access/curator-page.ts +8 -3
  370. package/node_modules/pi-web-access/curator-server.ts +5 -1
  371. package/node_modules/pi-web-access/exa.ts +18 -7
  372. package/node_modules/pi-web-access/extract.ts +134 -37
  373. package/node_modules/pi-web-access/fetch-params.ts +17 -3
  374. package/node_modules/pi-web-access/firecrawl.ts +172 -12
  375. package/node_modules/pi-web-access/gemini-search.ts +33 -7
  376. package/node_modules/pi-web-access/index.ts +160 -104
  377. package/node_modules/pi-web-access/package.json +2 -2
  378. package/node_modules/pi-web-access/page-query.ts +4 -2
  379. package/node_modules/pi-web-access/parallel-mcp.ts +249 -0
  380. package/node_modules/pi-web-access/parallel.ts +2 -2
  381. package/node_modules/pi-web-access/query-rewrite.ts +51 -0
  382. package/node_modules/pi-web-access/serper.ts +211 -0
  383. package/node_modules/pi-web-access/summary-model-scope.ts +12 -4
  384. package/node_modules/pi-web-access/summary-review.ts +51 -23
  385. package/node_modules/pi-web-access/tavily.ts +16 -4
  386. package/node_modules/pi-web-access/utils.ts +87 -0
  387. package/node_modules/pi-web-access/valyu.ts +199 -0
  388. package/node_modules/pi-web-access/youtube-extract.ts +2 -2
  389. package/package.json +13 -13
@@ -15,12 +15,13 @@
15
15
  */
16
16
  import * as fs from "node:fs";
17
17
  import * as path from "node:path";
18
+ import { createDeadline, forEachCooperatively, yieldIfOverBudget, } from "./cooperative-budget.js";
19
+ import { KIND_EXTENSIONS } from "./file-kinds.js";
18
20
  import { PathKeyedMap } from "./path-keyed-map.js";
19
21
  import { isAtOrAboveHomeDir, normalizeEphemeralMapKey } from "./path-utils.js";
20
22
  import { createDebounceScheduler, } from "./persist-debounce.js";
21
23
  import { getWordIndexMaxFilesDerived } from "./project-scale.js";
22
24
  import { logWordIndex } from "./word-index-logger.js";
23
- import { createDeadline, forEachCooperatively, yieldIfOverBudget, } from "./cooperative-budget.js";
24
25
  /**
25
26
  * The single normalizer every word-index path key folds through (#1025). The
26
27
  * CHEAP form — slash-fold + win32-lowercase, NO filesystem I/O — because the
@@ -38,11 +39,50 @@ export const wordIndexKey = normalizeEphemeralMapKey;
38
39
  // postings without improving relevance. Kept deliberately small and
39
40
  // language-agnostic.
40
41
  const STOPWORDS = new Set([
41
- "the", "and", "for", "let", "var", "const", "function", "return", "if",
42
- "else", "import", "export", "from", "class", "interface", "type", "enum",
43
- "new", "this", "self", "void", "null", "true", "false", "async", "await",
44
- "public", "private", "protected", "static", "def", "fn", "func", "struct",
45
- "impl", "pub", "use", "mod", "in", "of", "as", "is", "not", "with",
42
+ "the",
43
+ "and",
44
+ "for",
45
+ "let",
46
+ "var",
47
+ "const",
48
+ "function",
49
+ "return",
50
+ "if",
51
+ "else",
52
+ "import",
53
+ "export",
54
+ "from",
55
+ "class",
56
+ "interface",
57
+ "type",
58
+ "enum",
59
+ "new",
60
+ "this",
61
+ "self",
62
+ "void",
63
+ "null",
64
+ "true",
65
+ "false",
66
+ "async",
67
+ "await",
68
+ "public",
69
+ "private",
70
+ "protected",
71
+ "static",
72
+ "def",
73
+ "fn",
74
+ "func",
75
+ "struct",
76
+ "impl",
77
+ "pub",
78
+ "use",
79
+ "mod",
80
+ "in",
81
+ "of",
82
+ "as",
83
+ "is",
84
+ "not",
85
+ "with",
46
86
  ]);
47
87
  const TEST_VENDOR_RE = /(?:(^|[\\/])(?:tests?|__tests__|spec|specs|__mocks__|vendor|node_modules|examples?|fixtures?|\.git|dist|build|coverage)([\\/]|$))|(?:\.(?:test|spec)\.[a-z]+$)/i;
48
88
  const DOC_FILE_RE = /\.(?:md|mdx|markdown|json|json5|jsonc|txt|rst|lock|ya?ml|toml|csv)$/i;
@@ -498,6 +538,10 @@ export async function collectWordIndexDocs(root, shouldContinue = () => true, pr
498
538
  }
499
539
  return docs;
500
540
  }
541
+ // Node's fs.promises.stat runs on libuv's threadpool (default 4 slots), so
542
+ // real parallelism tops out there; the surplus workers are queue depth that
543
+ // keeps the pool saturated without starving other threadpool consumers.
544
+ export const WORD_INDEX_STAT_CONCURRENCY = 8;
501
545
  const WORD_INDEX_INCREMENTAL_CHURN_THRESHOLD = 0.3;
502
546
  // A ratio alone misclassifies one stale file in a three-file project as dense.
503
547
  // Below this absolute floor, per-document replacement is bounded and cheaper
@@ -531,19 +575,22 @@ const WORD_INDEX_POSTING_SCAN_TOKEN_COST = 0.2;
531
575
  * tokenized. Expected dense/legacy states return `full-required` BEFORE any
532
576
  * mutation; unexpected corruption or supersession still throws.
533
577
  */
534
- export async function refreshWordIndexIncrementally(index, root, shouldContinue = () => true) {
578
+ export async function refreshWordIndexIncrementally(index, root, shouldContinue = () => true, options = {}) {
535
579
  if (!index.forward || !index.fileMtimes || !index.fileSizes) {
536
580
  return {
537
581
  mode: "full-required",
538
582
  reason: "missing-incremental-metadata",
583
+ timings: { sourceWalkMs: 0, statWalkMs: 0, refreshReadsMs: 0 },
539
584
  };
540
585
  }
541
586
  const { collectSourceFilesAsync } = await import("./source-filter.js");
542
587
  const maxFiles = getWordIndexMaxFilesDerived(root);
588
+ const sourceWalkStartMs = Date.now();
543
589
  const walked = await collectSourceFilesAsync(root, {
544
590
  maxFiles,
545
591
  prioritizeCodeKinds: true,
546
592
  });
593
+ const sourceWalkMs = Date.now() - sourceWalkStartMs;
547
594
  if (!shouldContinue())
548
595
  throw new Error("word index refresh superseded");
549
596
  // This set-difference must run in the SAME normalized key space the path
@@ -559,30 +606,50 @@ export async function refreshWordIndexIncrementally(index, root, shouldContinue
559
606
  // `wordIndexKey` keeps build/edit/refresh convergent; `current`'s value
560
607
  // retains the raw walk path for the stat/read and for the display key.
561
608
  const current = new Map();
562
- // Check both supersession and the monotonic budget at every stat unit. Unlike
563
- // the former modulus cadence, this remains bounded as per-file work changes.
564
- const deadline = createDeadline(WORD_INDEX_BUILD_YIELD_BUDGET_MS);
565
- for (const file of walked) {
566
- if (!shouldContinue())
567
- throw new Error("word index refresh superseded");
568
- try {
569
- const stat = fs.statSync(file);
570
- if (stat.size <= WORD_INDEX_MAX_BYTES) {
571
- current.set(wordIndexKey(file), {
572
- path: file,
573
- mtimeMs: stat.mtimeMs,
574
- size: stat.size,
575
- });
609
+ const statWalkStartMs = Date.now();
610
+ const statFile = options.statFile ?? ((file) => fs.promises.stat(file));
611
+ const requestedConcurrency = options.statConcurrency ?? WORD_INDEX_STAT_CONCURRENCY;
612
+ const statConcurrency = Number.isFinite(requestedConcurrency) && requestedConcurrency > 0
613
+ ? Math.max(1, Math.floor(requestedConcurrency))
614
+ : WORD_INDEX_STAT_CONCURRENCY;
615
+ const statResults = new Array(walked.length);
616
+ let cursor = 0;
617
+ let superseded = false;
618
+ const worker = async () => {
619
+ while (true) {
620
+ if (!shouldContinue()) {
621
+ superseded = true;
622
+ return;
623
+ }
624
+ const slot = cursor++;
625
+ if (slot >= walked.length)
626
+ return;
627
+ const file = walked[slot];
628
+ try {
629
+ const stat = await statFile(file);
630
+ if (stat.size <= WORD_INDEX_MAX_BYTES) {
631
+ statResults[slot] = {
632
+ path: file,
633
+ mtimeMs: stat.mtimeMs,
634
+ size: stat.size,
635
+ };
636
+ }
637
+ }
638
+ catch {
639
+ // A rejection has statSync parity: the file is simply absent.
576
640
  }
577
641
  }
578
- catch {
579
- // A file vanishing between walk and stat is simply absent.
580
- }
581
- if (deadline.expired() && (await yieldIfOverBudget(deadline))) {
582
- if (!shouldContinue())
583
- throw new Error("word index refresh superseded");
584
- }
642
+ };
643
+ await Promise.all(Array.from({ length: Math.min(statConcurrency, walked.length) }, () => worker()));
644
+ if (superseded || !shouldContinue()) {
645
+ throw new Error("word index refresh superseded");
646
+ }
647
+ const statWalkMs = Date.now() - statWalkStartMs;
648
+ for (const result of statResults) {
649
+ if (result)
650
+ current.set(wordIndexKey(result.path), result);
585
651
  }
652
+ const timings = { sourceWalkMs, statWalkMs, refreshReadsMs: 0 };
586
653
  const preflightFiles = Object.assign([...current.values()], {
587
654
  truncated: walked.length === maxFiles,
588
655
  });
@@ -596,7 +663,12 @@ export async function refreshWordIndexIncrementally(index, root, shouldContinue
596
663
  changedSet++;
597
664
  const denominator = Math.max(oldSet.size, current.size, 1);
598
665
  if (changedSet / denominator > WORD_INDEX_INCREMENTAL_CHURN_THRESHOLD) {
599
- return { mode: "full-required", reason: "file-set-churn", preflightFiles };
666
+ return {
667
+ mode: "full-required",
668
+ reason: "file-set-churn",
669
+ preflightFiles,
670
+ timings,
671
+ };
600
672
  }
601
673
  // Replacing one document filters every shared posting array for that
602
674
  // document's tokens. That is excellent for a sparse edit, but repeating it
@@ -667,8 +739,10 @@ export async function refreshWordIndexIncrementally(index, root, shouldContinue
667
739
  mode: "full-required",
668
740
  reason: "stale-document-churn",
669
741
  preflightFiles,
742
+ timings,
670
743
  };
671
744
  }
745
+ const deadline = createDeadline(WORD_INDEX_BUILD_YIELD_BUDGET_MS);
672
746
  let dropped = 0;
673
747
  for (const key of oldSet) {
674
748
  if (!current.has(key)) {
@@ -682,6 +756,7 @@ export async function refreshWordIndexIncrementally(index, root, shouldContinue
682
756
  }
683
757
  let refreshed = 0;
684
758
  let skipped = 0;
759
+ const refreshReadsStartMs = Date.now();
685
760
  for (const { path: file, mtimeMs, size } of current.values()) {
686
761
  // #1105: mtime-first, size-second freshness — mtime alone is a stale
687
762
  // signal (mtime-preserving content changes serve stale identifiers to
@@ -726,6 +801,7 @@ export async function refreshWordIndexIncrementally(index, root, shouldContinue
726
801
  throw new Error("word index refresh superseded");
727
802
  }
728
803
  }
804
+ timings.refreshReadsMs = Date.now() - refreshReadsStartMs;
729
805
  index.truncated = walked.length === maxFiles;
730
806
  return {
731
807
  mode: "incremental",
@@ -733,16 +809,212 @@ export async function refreshWordIndexIncrementally(index, root, shouldContinue
733
809
  dropped,
734
810
  skipped,
735
811
  reused: current.size - refreshed - skipped,
812
+ timings,
813
+ };
814
+ }
815
+ export const WORD_INDEX_QUERY_FILTER_KEYS = ["lang", "file", "ext"];
816
+ /** Thrown by {@link parseWordIndexQuery}/{@link buildWordIndexQueryFilter} for
817
+ * an unrecognized `key:` prefix or an unrecognized `lang:` value — a query
818
+ * typo fails loudly with the supported list instead of silently degrading
819
+ * into a literal search term (#1450 acceptance criterion). */
820
+ export class WordIndexQueryError extends Error {
821
+ constructor(message) {
822
+ super(message);
823
+ this.name = "WordIndexQueryError";
824
+ }
825
+ }
826
+ const WORD_INDEX_FILTER_TOKEN_RE = /^(-)?([A-Za-z][A-Za-z0-9_-]*):(.+)$/;
827
+ /**
828
+ * Split a word-index query string into plain search terms and `key:value`
829
+ * prefix filters (#1450). Whitespace-delimited, one regex per token — no
830
+ * quoting scheme invented here, matching the pre-existing query path (which
831
+ * never supported quoting either). A `-` immediately before a recognized
832
+ * `key:` negates that filter; a bare `-term` (no colon) is left as an
833
+ * ordinary token, unchanged from prior behavior — `tokenizeLine` already
834
+ * strips the leading `-` when it extracts identifiers.
835
+ *
836
+ * An unrecognized `key:` (e.g. `type:foo`) throws {@link WordIndexQueryError}
837
+ * naming the supported prefixes, rather than falling through as a literal
838
+ * term — a typo must fail loudly, never silently rank as if the filter never
839
+ * existed. An empty value (`lang:`) is not treated as a filter token at all
840
+ * (kept as a term) since there is nothing to filter by.
841
+ */
842
+ export function parseWordIndexQuery(query) {
843
+ const filters = [];
844
+ const termParts = [];
845
+ for (const raw of query.split(/\s+/)) {
846
+ if (!raw)
847
+ continue;
848
+ const match = raw.match(WORD_INDEX_FILTER_TOKEN_RE);
849
+ if (!match) {
850
+ termParts.push(raw);
851
+ continue;
852
+ }
853
+ const [, negation, keyRaw, value] = match;
854
+ const key = keyRaw.toLowerCase();
855
+ if (!value) {
856
+ termParts.push(raw);
857
+ continue;
858
+ }
859
+ if (!WORD_INDEX_QUERY_FILTER_KEYS.includes(key)) {
860
+ // Not a recognized filter key. Ordinary search terms legitimately
861
+ // contain colons (std::vector, error:foo, http://…, C:\ paths, a
862
+ // TODO:tag), and the old tokenizer searched them fine — a hard
863
+ // error here is a usability regression, not typo protection. The
864
+ // token passes through as a plain term; the loud-failure contract
865
+ // survives where it is unambiguous: a KNOWN key with a bad value
866
+ // (lang:notalang) still throws.
867
+ termParts.push(raw);
868
+ continue;
869
+ }
870
+ filters.push({
871
+ key: key,
872
+ value,
873
+ negated: negation === "-",
874
+ });
875
+ }
876
+ return { terms: termParts.join(" "), filters };
877
+ }
878
+ /** `lang:X` → the extension set for FileKind X (case-insensitive), drawn
879
+ * exclusively from `KIND_EXTENSIONS` (clients/file-kinds.ts) — the single
880
+ * source of truth (#894 invariant: no second, hand-maintained language list).
881
+ * An unrecognized kind throws {@link WordIndexQueryError} listing the accepted
882
+ * kinds (KIND_EXTENSIONS' own keys), the same loud-failure contract as an
883
+ * unsupported `key:` prefix. */
884
+ function resolveLangExtensions(value) {
885
+ const kind = value.toLowerCase();
886
+ const extensions = KIND_EXTENSIONS[kind];
887
+ if (!extensions) {
888
+ const known = Object.keys(KIND_EXTENSIONS).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)).join(", ");
889
+ throw new WordIndexQueryError(`Unknown lang: "${value}" in word-index query — supported languages: ${known}.`);
890
+ }
891
+ return extensions;
892
+ }
893
+ /** `ext:ts` / `ext:.ts` → normalized to a single leading-dot, lowercased form for extname comparison. */
894
+ function normalizeExtFilterValue(value) {
895
+ return `.${value.replace(/^\.+/, "").toLowerCase()}`;
896
+ }
897
+ function resolveWordIndexFilter(filter) {
898
+ if (filter.key === "lang") {
899
+ const extensions = resolveLangExtensions(filter.value);
900
+ return {
901
+ key: filter.key,
902
+ negated: filter.negated,
903
+ test: (file) => extensions.includes(path.extname(file).toLowerCase()),
904
+ };
905
+ }
906
+ if (filter.key === "ext") {
907
+ const normalized = normalizeExtFilterValue(filter.value);
908
+ return {
909
+ key: filter.key,
910
+ negated: filter.negated,
911
+ test: (file) => path.extname(file).toLowerCase() === normalized,
912
+ };
913
+ }
914
+ // "file": substring match against the index's own normalized display path
915
+ // (#1450 — see the doc comment on {@link buildWordIndexQueryFilter} for the
916
+ // case-behavior choice). Both sides fold through `wordIndexKey` so the
917
+ // comparison matches the SAME normalization the index's own path keys use.
918
+ const needle = wordIndexKey(filter.value);
919
+ return {
920
+ key: filter.key,
921
+ negated: filter.negated,
922
+ test: (_file, displayPath) => displayPath.includes(needle),
923
+ };
924
+ }
925
+ /**
926
+ * Build a pre-ranking predicate from parsed query filters (#1450). Composes
927
+ * (AND) with `searchWordIndex`'s pre-existing `fileFilter` option (#771) —
928
+ * `symbol_search`'s structured `paths`/`lang` params and this query's inline
929
+ * filters both apply when both are present.
930
+ *
931
+ * Semantics: multiple positive filters of the SAME key OR together
932
+ * (`lang:ts lang:go` matches either); filters of DIFFERENT keys AND
933
+ * (`lang:ts file:clients/` requires both); a negated filter always subtracts,
934
+ * regardless of any positive filter sharing its key.
935
+ *
936
+ * All filters are RESOLVED EAGERLY (extension lookups, `WordIndexQueryError`
937
+ * thrown) here at build time — before any candidate file is tested — so a bad
938
+ * `lang:` value fails once, loudly, rather than per-file during the scoring
939
+ * loop.
940
+ *
941
+ * `file:` case behavior: matched via `wordIndexKey` (this module's single
942
+ * path-key normalizer, `normalizeEphemeralMapKey`) on BOTH the candidate path
943
+ * and the filter value — slash-folded everywhere, and case-folded ONLY on
944
+ * win32 (Windows' case-insensitive filesystem), preserved on POSIX. This
945
+ * mirrors the SAME normalization the word index's own path-keyed maps already
946
+ * apply (#1025), rather than inventing a second, divergent case rule for this
947
+ * one filter. The candidate path matched is the RAW index-stored path (not
948
+ * cwd-relativized) — the index already stores paths in the form the caller
949
+ * built it with (relative in tests, absolute in the real project walk), and
950
+ * an absolute path's tail always contains its project-relative suffix, so
951
+ * `file:clients/word-index.ts` still matches naturally either way.
952
+ */
953
+ export function buildWordIndexQueryFilter(filters) {
954
+ if (filters.length === 0)
955
+ return undefined;
956
+ const resolved = filters.map(resolveWordIndexFilter);
957
+ const positivesByKey = new Map();
958
+ const negatives = [];
959
+ for (const r of resolved) {
960
+ if (r.negated) {
961
+ negatives.push(r);
962
+ continue;
963
+ }
964
+ const group = positivesByKey.get(r.key) ?? [];
965
+ group.push(r);
966
+ positivesByKey.set(r.key, group);
967
+ }
968
+ return (file) => {
969
+ const displayPath = wordIndexKey(file);
970
+ for (const negative of negatives) {
971
+ if (negative.test(file, displayPath))
972
+ return false;
973
+ }
974
+ for (const group of positivesByKey.values()) {
975
+ if (!group.some((r) => r.test(file, displayPath)))
976
+ return false;
977
+ }
978
+ return true;
736
979
  };
737
980
  }
981
+ function combineFileFilters(a, b) {
982
+ if (!a)
983
+ return b;
984
+ if (!b)
985
+ return a;
986
+ return (file) => a(file) && b(file);
987
+ }
738
988
  /**
739
989
  * Rank files for a query by BM25 over the query's identifier tokens, then apply
740
990
  * priors: demote test/vendor and doc/data files, and boost by graph centrality
741
991
  * when supplied. Returns the top {@link RankOptions.limit} files, highest first.
992
+ *
993
+ * The query string may mix plain terms with `lang:`/`file:`/`ext:` prefix
994
+ * filters (+ `-` negation, #1450) — parsed by {@link parseWordIndexQuery} and
995
+ * applied as a `fileFilter` (composed, AND, with any caller-supplied one)
996
+ * BEFORE scoring, same as the pre-existing `paths`/`lang` options (#771): a
997
+ * surviving file's score is unaffected by filtering. A query with no filter
998
+ * tokens reproduces prior output byte-for-byte.
999
+ *
1000
+ * DF-normalization note: BM25's per-token `idf` is computed from `docFrequency`
1001
+ * over the FULL (unfiltered) postings for that token — `fileFilter` (whether
1002
+ * from `options.fileFilter` or from this query's inline filters) is applied
1003
+ * AFTER `idf` is computed, per file, inside the same loop. This means a
1004
+ * filtered query reuses the GLOBAL, corpus-wide document frequency rather than
1005
+ * recomputing it over just the filtered subset. That is the pre-existing #771
1006
+ * behavior this change does not alter; it is an acceptable approximation
1007
+ * (idf reflects true corpus rarity, not an artifact of the filter) and keeps
1008
+ * filtered/unfiltered scores for the SAME file directly comparable, which is
1009
+ * the property #1450 asks for ("BM25 and centrality stay comparable within
1010
+ * the filtered set").
742
1011
  */
743
1012
  export function searchWordIndex(index, query, options = {}) {
744
1013
  const { demoteTestVendor = true, demoteDocs = true, centrality, limit = 20, fileFilter, } = options;
745
- const queryTokens = [...new Set(tokenizeLine(query))];
1014
+ const parsedQuery = parseWordIndexQuery(query);
1015
+ const queryFilter = buildWordIndexQueryFilter(parsedQuery.filters);
1016
+ const combinedFilter = combineFileFilters(fileFilter, queryFilter);
1017
+ const queryTokens = [...new Set(tokenizeLine(parsedQuery.terms))];
746
1018
  if (queryTokens.length === 0)
747
1019
  return [];
748
1020
  const docCount = index.docCount || 1;
@@ -763,7 +1035,7 @@ export function searchWordIndex(index, query, options = {}) {
763
1035
  const docFrequency = linesByFile.size;
764
1036
  const idf = Math.log(1 + (docCount - docFrequency + 0.5) / (docFrequency + 0.5));
765
1037
  for (const [file, lines] of linesByFile) {
766
- if (fileFilter && !fileFilter(file))
1038
+ if (combinedFilter && !combinedFilter(file))
767
1039
  continue;
768
1040
  const termFrequency = lines.length;
769
1041
  const docLength = index.docLengths.get(file) ?? avgDocLength;
@@ -977,7 +1249,7 @@ export function triggerBackgroundWordIndexBuild(cwd, dbg, options = {}) {
977
1249
  void (async () => {
978
1250
  const startMs = Date.now();
979
1251
  try {
980
- const { loadProjectSnapshot, saveProjectSnapshot, PROJECT_SNAPSHOT_VERSION } = await import("./project-snapshot.js");
1252
+ const { loadProjectSnapshot, saveProjectSnapshot, PROJECT_SNAPSHOT_VERSION, } = await import("./project-snapshot.js");
981
1253
  const docs = await collectWordIndexDocs(key);
982
1254
  const index = await buildWordIndexAsync(docs);
983
1255
  const existing = loadProjectSnapshot(key);
@@ -1059,7 +1331,7 @@ function getWordIndexPersistScheduler() {
1059
1331
  }
1060
1332
  async function writeWordIndexSnapshot(cwd, index, dbg) {
1061
1333
  try {
1062
- const { loadProjectSnapshot, saveProjectSnapshot, PROJECT_SNAPSHOT_VERSION } = await import("./project-snapshot.js");
1334
+ const { loadProjectSnapshot, saveProjectSnapshot, PROJECT_SNAPSHOT_VERSION, } = await import("./project-snapshot.js");
1063
1335
  const existing = loadProjectSnapshot(cwd);
1064
1336
  const snapshot = existing ?? {
1065
1337
  version: PROJECT_SNAPSHOT_VERSION,