@herbertgao/pi-extensions 2026.9.0 → 2026.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (360) hide show
  1. package/node_modules/@czottmann/pi-automode/CHANGELOG.md +13 -1
  2. package/node_modules/@czottmann/pi-automode/README.md +20 -0
  3. package/node_modules/@czottmann/pi-automode/docs/GLOSSARY.md +1 -1
  4. package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +2 -1
  5. package/node_modules/@czottmann/pi-automode/docs/defaults.md +3 -2
  6. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +38 -2
  7. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +5 -3
  8. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +11 -3
  9. package/node_modules/@czottmann/pi-automode/package.json +1 -1
  10. package/node_modules/@narumitw/pi-btw/README.md +57 -141
  11. package/node_modules/@narumitw/pi-btw/dist/index.ts +466 -41
  12. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +4 -4
  13. package/node_modules/@narumitw/pi-btw/docs/workflows.md +74 -0
  14. package/node_modules/@narumitw/pi-btw/package.json +2 -1
  15. package/node_modules/@narumitw/pi-btw/src/btw.ts +15 -1
  16. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +32 -2
  17. package/node_modules/@narumitw/pi-btw/src/keybindings.ts +330 -0
  18. package/node_modules/@narumitw/pi-btw/src/menu.ts +136 -19
  19. package/node_modules/@narumitw/pi-btw/src/settings.ts +32 -0
  20. package/node_modules/@narumitw/pi-btw/src/side-thread.ts +57 -5
  21. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +52 -30
  22. package/node_modules/@pi-plugins/fast-mode/README.md +5 -3
  23. package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts.map +1 -1
  24. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +20 -55
  25. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
  26. package/node_modules/@pi-plugins/fast-mode/package.json +1 -1
  27. package/node_modules/pi-lens/CHANGELOG.md +1072 -0
  28. package/node_modules/pi-lens/README.md +3 -0
  29. package/node_modules/pi-lens/config/biome/core.jsonc +11 -2
  30. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +13 -1
  31. package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +2 -9
  32. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +927 -80
  33. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +1 -1
  34. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +13 -4
  35. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +123 -3
  36. package/node_modules/pi-lens/dist/clients/ast-grep-rule-manager.js +60 -5
  37. package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +2 -9
  38. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +2 -0
  39. package/node_modules/pi-lens/dist/clients/bash-file-access.js +133 -3
  40. package/node_modules/pi-lens/dist/clients/biome-client.js +9 -2
  41. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +14 -6
  42. package/node_modules/pi-lens/dist/clients/bootstrap.js +509 -73
  43. package/node_modules/pi-lens/dist/clients/bounded-cache.js +152 -12
  44. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +1 -1
  45. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +59 -9
  46. package/node_modules/pi-lens/dist/clients/bundled-resource-health.js +113 -0
  47. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +3 -11
  48. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +31 -4
  49. package/node_modules/pi-lens/dist/clients/cache-manager.js +105 -6
  50. package/node_modules/pi-lens/dist/clients/cache-observability.js +40 -19
  51. package/node_modules/pi-lens/dist/clients/cargo-manifest.js +422 -0
  52. package/node_modules/pi-lens/dist/clients/cascade-logger.js +2 -9
  53. package/node_modules/pi-lens/dist/clients/child-unref.js +1 -19
  54. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +13 -3
  55. package/node_modules/pi-lens/dist/clients/complexity-client.js +16 -5
  56. package/node_modules/pi-lens/dist/clients/config-core/deny.js +221 -0
  57. package/node_modules/pi-lens/dist/clients/config-core/index.js +50 -0
  58. package/node_modules/pi-lens/dist/clients/config-core/merge.js +357 -0
  59. package/node_modules/pi-lens/dist/clients/config-core/normalize.js +340 -0
  60. package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +248 -0
  61. package/node_modules/pi-lens/dist/clients/config-core/provenance.js +164 -0
  62. package/node_modules/pi-lens/dist/clients/config-core/records.js +218 -0
  63. package/node_modules/pi-lens/dist/clients/config-core/resolve.js +125 -0
  64. package/node_modules/pi-lens/dist/clients/config-core/safe-object.js +78 -0
  65. package/node_modules/pi-lens/dist/clients/config-core/schema.js +165 -0
  66. package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +281 -0
  67. package/node_modules/pi-lens/dist/clients/config-locations.js +160 -0
  68. package/node_modules/pi-lens/dist/clients/config-resolve.js +789 -0
  69. package/node_modules/pi-lens/dist/clients/config-schema.js +234 -0
  70. package/node_modules/pi-lens/dist/clients/config-warn.js +407 -0
  71. package/node_modules/pi-lens/dist/clients/dead-code-client.js +19 -14
  72. package/node_modules/pi-lens/dist/clients/dead-code-logger.js +2 -6
  73. package/node_modules/pi-lens/dist/clients/deadline-utils.js +178 -0
  74. package/node_modules/pi-lens/dist/clients/debug-handles.js +2 -2
  75. package/node_modules/pi-lens/dist/clients/debug-heap.js +3 -3
  76. package/node_modules/pi-lens/dist/clients/deferred-lsp-work.js +106 -0
  77. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +113 -7
  78. package/node_modules/pi-lens/dist/clients/dependency-checker.js +8 -19
  79. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +1 -1
  80. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +12 -9
  81. package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +15 -5
  82. package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +0 -4
  83. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +162 -44
  84. package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +97 -0
  85. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +27 -8
  86. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +0 -6
  87. package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +1 -1
  88. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +0 -3
  89. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-complexity.js +2 -2
  90. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-fan-out.js +1 -1
  91. package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +0 -1
  92. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +383 -27
  93. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +0 -1
  94. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +4 -1
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +0 -1
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +0 -1
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +0 -1
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +0 -1
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +0 -1
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +0 -1
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +0 -1
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/fact-rules.js +0 -1
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +0 -1
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +0 -1
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +1 -3
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +0 -1
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +0 -1
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +0 -1
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +1 -2
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +0 -1
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +0 -1
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +0 -1
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +0 -1
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +0 -1
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +0 -1
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +14 -18
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +0 -1
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +0 -1
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +0 -1
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +6 -3
  121. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +7 -4
  122. package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +0 -1
  123. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +1 -2
  124. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +1 -3
  125. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +1 -2
  126. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +0 -1
  127. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +4 -3
  128. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +0 -1
  129. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +0 -1
  130. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +0 -1
  131. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +0 -1
  132. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +0 -1
  133. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +0 -1
  134. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +0 -1
  135. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +0 -1
  136. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +0 -1
  137. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +3 -0
  138. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +3 -67
  139. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +194 -80
  140. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +84 -15
  141. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils.js +0 -21
  142. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +0 -1
  143. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +2 -20
  144. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +1 -1
  145. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +0 -1
  146. package/node_modules/pi-lens/dist/clients/dispatch/utils/format-utils.js +2 -2
  147. package/node_modules/pi-lens/dist/clients/disposition-logger.js +3 -10
  148. package/node_modules/pi-lens/dist/clients/effective-config.js +403 -0
  149. package/node_modules/pi-lens/dist/clients/error-class.js +23 -0
  150. package/node_modules/pi-lens/dist/clients/event-loop-hold.js +274 -0
  151. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +2 -2
  152. package/node_modules/pi-lens/dist/clients/extension-log.js +2 -2
  153. package/node_modules/pi-lens/dist/clients/feature-hints.js +2 -1
  154. package/node_modules/pi-lens/dist/clients/file-kinds.js +1 -38
  155. package/node_modules/pi-lens/dist/clients/file-time.js +7 -2
  156. package/node_modules/pi-lens/dist/clients/file-utils.js +117 -10
  157. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +50 -13
  158. package/node_modules/pi-lens/dist/clients/format-service.js +6 -2
  159. package/node_modules/pi-lens/dist/clients/formatters.js +385 -104
  160. package/node_modules/pi-lens/dist/clients/freshness-cadence.js +17 -0
  161. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +116 -22
  162. package/node_modules/pi-lens/dist/clients/generation-guard.js +4 -9
  163. package/node_modules/pi-lens/dist/clients/git-guard.js +6 -1
  164. package/node_modules/pi-lens/dist/clients/go-client.js +39 -0
  165. package/node_modules/pi-lens/dist/clients/gradle-ktfmt-style.js +252 -0
  166. package/node_modules/pi-lens/dist/clients/grammar-source.js +3 -3
  167. package/node_modules/pi-lens/dist/clients/hashline-anchor.js +424 -0
  168. package/node_modules/pi-lens/dist/clients/hook-budgets.js +76 -0
  169. package/node_modules/pi-lens/dist/clients/host-edit-normalize.js +5 -2
  170. package/node_modules/pi-lens/dist/clients/host-ports.js +1 -1
  171. package/node_modules/pi-lens/dist/clients/installer/index.js +289 -59
  172. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +84 -18
  173. package/node_modules/pi-lens/dist/clients/instance-reaper.js +110 -236
  174. package/node_modules/pi-lens/dist/clients/instance-registry.js +6 -3
  175. package/node_modules/pi-lens/dist/clients/language-profile.js +0 -9
  176. package/node_modules/pi-lens/dist/clients/language-registry.js +599 -0
  177. package/node_modules/pi-lens/dist/clients/latency-logger.js +165 -9
  178. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +34 -1
  179. package/node_modules/pi-lens/dist/clients/lens-config.js +160 -30
  180. package/node_modules/pi-lens/dist/clients/lens-engine.js +33 -41
  181. package/node_modules/pi-lens/dist/clients/lens-events.js +1 -1
  182. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +77 -6
  183. package/node_modules/pi-lens/dist/clients/lens-map.js +1 -1
  184. package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -38
  185. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +3 -1
  186. package/node_modules/pi-lens/dist/clients/lsp/client.js +172 -33
  187. package/node_modules/pi-lens/dist/clients/lsp/config.js +484 -158
  188. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +1 -1
  189. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +1 -1
  190. package/node_modules/pi-lens/dist/clients/lsp/edits.js +18 -5
  191. package/node_modules/pi-lens/dist/clients/lsp/index.js +892 -96
  192. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +1 -1
  193. package/node_modules/pi-lens/dist/clients/lsp/language.js +18 -177
  194. package/node_modules/pi-lens/dist/clients/lsp/launch.js +2 -1
  195. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
  196. package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +5 -18
  197. package/node_modules/pi-lens/dist/clients/lsp/server.js +284 -147
  198. package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +116 -23
  199. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +2 -7
  200. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +0 -1
  201. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +14 -7
  202. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +6 -0
  203. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +2 -1
  204. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +1 -1
  205. package/node_modules/pi-lens/dist/clients/lsp-document-symbols.js +1 -1
  206. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +163 -21
  207. package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +36 -0
  208. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +33 -4
  209. package/node_modules/pi-lens/dist/clients/mcp/session.js +5 -16
  210. package/node_modules/pi-lens/dist/clients/memory-sampler.js +8 -3
  211. package/node_modules/pi-lens/dist/clients/metrics-history.js +28 -107
  212. package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +3 -5
  213. package/node_modules/pi-lens/dist/clients/module-report.js +21 -46
  214. package/node_modules/pi-lens/dist/clients/mutating-tool.js +651 -0
  215. package/node_modules/pi-lens/dist/clients/mutation-attribution.js +368 -0
  216. package/node_modules/pi-lens/dist/clients/mutation-bridge.js +240 -0
  217. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +247 -24
  218. package/node_modules/pi-lens/dist/clients/observed-mutation-sources.js +101 -0
  219. package/node_modules/pi-lens/dist/clients/observed-mutation.js +1215 -0
  220. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +70 -28
  221. package/node_modules/pi-lens/dist/clients/opengrep-config.js +5 -1
  222. package/node_modules/pi-lens/dist/clients/package-manager.js +195 -26
  223. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +359 -80
  224. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +23 -7
  225. package/node_modules/pi-lens/dist/clients/path-keyed-map.js +21 -2
  226. package/node_modules/pi-lens/dist/clients/path-utils.js +451 -15
  227. package/node_modules/pi-lens/dist/clients/performance-report.js +2 -2
  228. package/node_modules/pi-lens/dist/clients/persist-debounce.js +8 -1
  229. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +114 -0
  230. package/node_modules/pi-lens/dist/clients/pipeline.js +79 -18
  231. package/node_modules/pi-lens/dist/clients/probe-home-state.js +228 -0
  232. package/node_modules/pi-lens/dist/clients/process-bridge.js +66 -0
  233. package/node_modules/pi-lens/dist/clients/process-snapshot.js +68 -0
  234. package/node_modules/pi-lens/dist/clients/project-changes.js +1 -1
  235. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +1 -1
  236. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +4 -3
  237. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/jscpd.js +1 -1
  238. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/knip.js +1 -1
  239. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +1 -1
  240. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/opengrep.js +1 -1
  241. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +28 -4
  242. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +51 -20
  243. package/node_modules/pi-lens/dist/clients/project-lens-config.js +438 -122
  244. package/node_modules/pi-lens/dist/clients/project-scan-policy.js +3 -51
  245. package/node_modules/pi-lens/dist/clients/project-snapshot.js +52 -24
  246. package/node_modules/pi-lens/dist/clients/python-environment.js +217 -0
  247. package/node_modules/pi-lens/dist/clients/python-provenance.js +639 -0
  248. package/node_modules/pi-lens/dist/clients/quiet-window.js +6 -1
  249. package/node_modules/pi-lens/dist/clients/read-bridge.js +13 -21
  250. package/node_modules/pi-lens/dist/clients/read-expansion.js +9 -46
  251. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +16 -5
  252. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +364 -212
  253. package/node_modules/pi-lens/dist/clients/read-guard.js +26 -2
  254. package/node_modules/pi-lens/dist/clients/recent-touches.js +2 -2
  255. package/node_modules/pi-lens/dist/clients/resource-sampler.js +195 -96
  256. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +155 -114
  257. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +1 -1
  258. package/node_modules/pi-lens/dist/clients/review-graph/service.js +55 -3
  259. package/node_modules/pi-lens/dist/clients/review-graph/shared-extraction-ir.js +7 -15
  260. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +10 -74
  261. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +9 -3
  262. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +19 -3
  263. package/node_modules/pi-lens/dist/clients/runtime-context.js +49 -16
  264. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +25 -6
  265. package/node_modules/pi-lens/dist/clients/runtime-session.js +184 -32
  266. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +179 -36
  267. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +797 -243
  268. package/node_modules/pi-lens/dist/clients/runtime-turn.js +1028 -53
  269. package/node_modules/pi-lens/dist/clients/rust-client.js +31 -0
  270. package/node_modules/pi-lens/dist/clients/safe-spawn.js +33 -10
  271. package/node_modules/pi-lens/dist/clients/sanitize.js +7 -12
  272. package/node_modules/pi-lens/dist/clients/scan-utils.js +1 -56
  273. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +1 -3
  274. package/node_modules/pi-lens/dist/clients/security-scan-client.js +3 -0
  275. package/node_modules/pi-lens/dist/clients/session-lifecycle.js +1 -1
  276. package/node_modules/pi-lens/dist/clients/session-start-observability.js +79 -0
  277. package/node_modules/pi-lens/dist/clients/session-summary.js +0 -24
  278. package/node_modules/pi-lens/dist/clients/sessionstart-logger.js +12 -3
  279. package/node_modules/pi-lens/dist/clients/sgconfig.js +7 -6
  280. package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +1 -1
  281. package/node_modules/pi-lens/dist/clients/skills-resolver.js +105 -0
  282. package/node_modules/pi-lens/dist/clients/smells-rollup.js +4 -4
  283. package/node_modules/pi-lens/dist/clients/startup-scan.js +2 -10
  284. package/node_modules/pi-lens/dist/clients/string-utils.js +13 -0
  285. package/node_modules/pi-lens/dist/clients/subagent-mode.js +17 -4
  286. package/node_modules/pi-lens/dist/clients/test-runner-client.js +176 -39
  287. package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +244 -0
  288. package/node_modules/pi-lens/dist/clients/tool-definition.js +41 -1
  289. package/node_modules/pi-lens/dist/clients/tool-policy.js +92 -22
  290. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +40 -40
  291. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +87 -100
  292. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +2 -2
  293. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +81 -1
  294. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +51 -46
  295. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +28 -0
  296. package/node_modules/pi-lens/dist/clients/tui-fit.js +0 -4
  297. package/node_modules/pi-lens/dist/clients/typos-config.js +5 -0
  298. package/node_modules/pi-lens/dist/clients/user-notify.js +6 -6
  299. package/node_modules/pi-lens/dist/clients/widget-state.js +191 -26
  300. package/node_modules/pi-lens/dist/clients/word-index-logger.js +2 -9
  301. package/node_modules/pi-lens/dist/clients/word-index-store.js +23 -12
  302. package/node_modules/pi-lens/dist/clients/word-index.js +252 -47
  303. package/node_modules/pi-lens/dist/clients/workspace-topology.js +8 -1
  304. package/node_modules/pi-lens/dist/clients/zizmor-config.js +4 -1
  305. package/node_modules/pi-lens/dist/index.js +65899 -55340
  306. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +3 -1
  307. package/node_modules/pi-lens/dist/mcp/build-staleness.js +1 -1
  308. package/node_modules/pi-lens/dist/mcp/server.js +127 -5
  309. package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +583 -0
  310. package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +129 -0
  311. package/node_modules/pi-lens/dist/tools/effective-config.js +89 -0
  312. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +96 -25
  313. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +104 -86
  314. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +37 -16
  315. package/node_modules/pi-lens/dist/tools/lsp-structured-output.js +3 -3
  316. package/node_modules/pi-lens/dist/tools/render-compact.js +1 -1
  317. package/node_modules/pi-lens/dist/tools/shared.js +0 -1
  318. package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -2
  319. package/node_modules/pi-lens/docs/agent-guide.md +64 -1
  320. package/node_modules/pi-lens/docs/configuration.md +222 -0
  321. package/node_modules/pi-lens/docs/dependencies.md +3 -3
  322. package/node_modules/pi-lens/docs/features.md +44 -5
  323. package/node_modules/pi-lens/docs/globalconfig.md +20 -0
  324. package/node_modules/pi-lens/docs/language-coverage.md +2 -2
  325. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +9 -1
  326. package/node_modules/pi-lens/docs/pi-lens-fixer.md +25 -0
  327. package/node_modules/pi-lens/docs/pi-lens-investigator.md +25 -0
  328. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +33 -0
  329. package/node_modules/pi-lens/docs/pi-lens-subagent.md +49 -0
  330. package/node_modules/pi-lens/docs/pi-lens-warden.md +55 -0
  331. package/node_modules/pi-lens/docs/public-api-stability.md +359 -0
  332. package/node_modules/pi-lens/docs/release-qa-baseline.md +190 -0
  333. package/node_modules/pi-lens/docs/servercapabilities.md +7 -3
  334. package/node_modules/pi-lens/docs/subagent-compat.md +110 -30
  335. package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +2 -2
  336. package/node_modules/pi-lens/docs/word-index.md +1 -1
  337. package/node_modules/pi-lens/package.json +18 -35
  338. package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-cross-language-method.yml +3 -3
  339. package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-hallucinated-import.yml +2 -2
  340. package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-sql-injection.yml +12 -2
  341. package/node_modules/pi-lens/rules/typos/_typos.toml +4 -2
  342. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +192 -1
  343. package/node_modules/pi-lens/scripts/install-selftest.mjs +58 -2
  344. package/node_modules/pi-lens/scripts/lib/skills-predicate.mjs +129 -0
  345. package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +1 -1
  346. package/node_modules/pi-lens/scripts/rpc-load-check.mjs +3 -1
  347. package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +4 -2
  348. package/node_modules/pi-web-access/CHANGELOG.md +28 -0
  349. package/node_modules/pi-web-access/README.md +62 -15
  350. package/node_modules/pi-web-access/curator-page.ts +3 -1
  351. package/node_modules/pi-web-access/curator-server.ts +5 -1
  352. package/node_modules/pi-web-access/gemini-search.ts +8 -4
  353. package/node_modules/pi-web-access/github-extract.ts +242 -1
  354. package/node_modules/pi-web-access/index.ts +118 -64
  355. package/node_modules/pi-web-access/mistral-search.ts +281 -0
  356. package/node_modules/pi-web-access/package.json +2 -2
  357. package/node_modules/pi-web-access/perplexity.ts +14 -1
  358. package/node_modules/pi-web-access/utils.ts +23 -6
  359. package/node_modules/pi-web-access/xai-search.ts +96 -33
  360. package/package.json +6 -6
@@ -18,7 +18,7 @@ import { containerNameChain, getOpenDocumentSymbols, lspSymbolKindName, } from "
18
18
  import { isAtOrAboveHomeDir, normalizeFilePath, normalizeMapKey, toProjectRelativePath, } from "../path-utils.js";
19
19
  import { collectProjectSourceFilesWithBudgetAsync } from "../project-scan-policy.js";
20
20
  import { getReviewGraphMaxFilesDerived } from "../project-scale.js";
21
- import { compareOrdinal } from "../string-utils.js";
21
+ import { compareOrdinal, escapeRegExp } from "../string-utils.js";
22
22
  import { BoundedLruCache } from "../bounded-cache.js";
23
23
  import { jsTsCandidatePaths, resolveAliasedImport, resolveImportToFiles, resolveProjectReferenceImport, resolveWorkspacePackageImport, } from "./import-resolvers.js";
24
24
  import { RUNTIME_CONFIG } from "../runtime-config.js";
@@ -27,7 +27,7 @@ import { buildQualifiedName, findOwnerName } from "../symbol-containment.js";
27
27
  import { logTreeSitterCacheStats } from "../tree-sitter-logger.js";
28
28
  import { flushReviewGraphLogSync, logReviewGraph, makeReviewGraphBuildMetadata, } from "../review-graph-logger.js";
29
29
  import { getSharedTreeSitterClient } from "../tree-sitter-shared.js";
30
- import { TreeSitterSymbolExtractor, } from "../tree-sitter-symbol-extractor.js";
30
+ import { symbolExtractionGrammar, TreeSitterSymbolExtractor, } from "../tree-sitter-symbol-extractor.js";
31
31
  import { withTreeSitterRoot } from "../tree-sitter-shared.js";
32
32
  import { incrementDegradationCount } from "../degradation-ledger.js";
33
33
  import { resolveGitIdentity } from "./git-identity.js";
@@ -92,8 +92,6 @@ const MAIN_KINDS = new Set([
92
92
  // so a repo heavy in JSON/YAML/Markdown doesn't trip the cap on files the graph
93
93
  // would have filtered out anyway (the cap is on the walk, not on noise). #250.
94
94
  const MAIN_KIND_EXTENSIONS = Array.from(MAIN_KINDS).flatMap((kind) => KIND_EXTENSIONS[kind] ?? []);
95
- /** The bounded, source-filtered extension set shared by graph cache readers. */
96
- export const REVIEW_GRAPH_SOURCE_EXTENSIONS = MAIN_KIND_EXTENSIONS;
97
95
  const CHANGED_SYMBOLS_PREFIX = "session.reviewGraph.changedSymbols:";
98
96
  const extractorCache = new Map();
99
97
  const REVIEW_GRAPH_MAX_WARM_WORKSPACES = 8;
@@ -103,27 +101,20 @@ const REVIEW_GRAPH_MAX_WARM_WORKSPACES = 8;
103
101
  // spelling is resolved once; repeated builds reuse the result. The project
104
102
  // memo is bounded and workspace-cache clears provide a freshness boundary.
105
103
  const REVIEW_GRAPH_SOURCE_PATH_MEMO_ENTRIES = 16_384;
106
- const _sourcePathMemos = new Map();
104
+ // BoundedLruCache, not BoundedFifoMap: a hit here must refresh the workspace's
105
+ // recency (the hand-rolled version did its own delete+set for exactly that),
106
+ // so a workspace still being built is never the one evicted (#2442).
107
+ const _sourcePathMemos = new BoundedLruCache(REVIEW_GRAPH_MAX_WARM_WORKSPACES);
107
108
  function sourcePathMemo(cwd) {
108
109
  const key = normalizeMapKey(path.resolve(cwd));
109
110
  const existing = _sourcePathMemos.get(key);
110
- if (existing) {
111
- // The workspace map is also bounded LRU; a hit must refresh its recency.
112
- _sourcePathMemos.delete(key);
113
- _sourcePathMemos.set(key, existing);
111
+ if (existing)
114
112
  return existing;
115
- }
116
113
  const memo = {
117
114
  cache: new BoundedLruCache(REVIEW_GRAPH_SOURCE_PATH_MEMO_ENTRIES),
118
115
  normalizeCalls: { value: 0 },
119
116
  };
120
117
  _sourcePathMemos.set(key, memo);
121
- while (_sourcePathMemos.size > REVIEW_GRAPH_MAX_WARM_WORKSPACES) {
122
- const oldest = _sourcePathMemos.keys().next().value;
123
- if (oldest === undefined)
124
- break;
125
- _sourcePathMemos.delete(oldest);
126
- }
127
118
  return memo;
128
119
  }
129
120
  function normalizeGraphSourcePath(memo, raw) {
@@ -414,7 +405,7 @@ export function estimateReviewGraphStoreBytes(totalNodes, totalEdges) {
414
405
  // cleared). Bounding only the cache left the full graph resident on the fact, so
415
406
  // both sites go through `retainedGraph` below, memoized per graph instance so the
416
407
  // two sites share ONE bounded object instead of trimming twice (#2255 review F2).
417
- export const GRAPH_MAX_IN_MEMORY_BYTES_DEFAULT = 512 * 1024 * 1024;
408
+ const GRAPH_MAX_IN_MEMORY_BYTES_DEFAULT = 512 * 1024 * 1024;
418
409
  function graphMaxInMemoryBytes() {
419
410
  const raw = Number(process.env.PI_LENS_GRAPH_MAX_IN_MEMORY_BYTES);
420
411
  return Number.isFinite(raw) && raw > 0
@@ -528,12 +519,15 @@ function headSliceGraph(graph, cap) {
528
519
  * This bounds the VALUE, not the store, and deliberately adds no second bounding
529
520
  * mechanism to `FactStore` (#2243 owns the store-level discipline).
530
521
  *
531
- * `sessionFacts` entry COUNT is not fully bounded — `session.baseline.${path}`
532
- * (`dispatch/dispatcher.ts`) and `session.baseline.cascade.${path}`
533
- * (`dispatch/integration.ts`) mint one key per file touched and clear only on
534
- * `clearAll`. That is #2282, a separate #2240 sibling; it is
535
- * per-file `Diagnostic[]`, not a whole project graph. What this seam fixes is the
536
- * one fact whose single VALUE is unbounded in project size.
522
+ * `sessionFacts` entry COUNT (a separate #2240 sibling, #2282) is now bounded
523
+ * too: `session.baseline.${path}` (`dispatch/dispatcher.ts`),
524
+ * `session.baseline.cascade.${path}` (`dispatch/integration.ts`), and this
525
+ * module's own `changedSymbols`/`entitySnapshot` per-file keys all go through
526
+ * `FactStore.setBoundedSessionFact`/`getBoundedSessionFact`, an LRU-count-cap
527
+ * sibling of the per-file `fileFacts` bound reusing the SAME discipline
528
+ * (#2243), not a second mechanism. `session.reviewGraph` itself stays on the
529
+ * plain, unbounded `sessionFacts` map — its key is a fixed singleton, so the
530
+ * VALUE bound below is what keeps IT small, not an entry-count cap.
537
531
  */
538
532
  function setSessionReviewGraphFact(cwd, facts, graph) {
539
533
  const retained = retainedGraph(cwd, graph);
@@ -826,9 +820,6 @@ function makeCtx(filePath, cwd, facts) {
826
820
  log: () => { },
827
821
  };
828
822
  }
829
- function escapeRegExp(string) {
830
- return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
831
- }
832
823
  function createEmptyGraph() {
833
824
  return {
834
825
  version: REVIEW_GRAPH_VERSION,
@@ -974,7 +965,7 @@ function diffSignatureMaps(previous, next) {
974
965
  // `RUNTIME_CONFIG.reviewGraph.maxFiles` constant as the fallback — the ratio
975
966
  // table reproduces that same 1,000-file default at the default base, so this
976
967
  // is behavior-neutral when nothing is configured.
977
- export function getReviewGraphMaxFiles(cwd) {
968
+ function getReviewGraphMaxFiles(cwd) {
978
969
  const override = Number.parseInt(process.env.PI_LENS_REVIEW_GRAPH_MAX_FILES ?? "", 10);
979
970
  return Number.isFinite(override) && override > 0
980
971
  ? override
@@ -1226,13 +1217,15 @@ function indexEdge(graph, edge) {
1226
1217
  graph.edgesByTo.set(edge.to, to);
1227
1218
  }
1228
1219
  /**
1229
- * Drop `edge` from both adjacency buckets (#2074). Costs one scan of the two
1230
- * buckets the edge belongs to, never a scan of `graph.edges`, so removing a
1231
- * changed file's edges stays proportional to that file's fan-in/fan-out.
1220
+ * Drop `edge` from both adjacency buckets. This is kept for the single-edge
1221
+ * dedupe path; multi-file removal uses `unindexEdges` below so a shared hub
1222
+ * bucket is scanned once rather than once per removed edge (#2074).
1232
1223
  */
1233
1224
  function unindexEdge(graph, edge) {
1234
1225
  const from = graph.edgesByFrom.get(edge.from);
1235
1226
  if (from) {
1227
+ // Count the full bucket as the linear `indexOf` scan's bounded work.
1228
+ _rebuildCounters.removeOwnedEdgePositions += from.length;
1236
1229
  const at = from.indexOf(edge);
1237
1230
  if (at >= 0)
1238
1231
  from.splice(at, 1);
@@ -1241,6 +1234,7 @@ function unindexEdge(graph, edge) {
1241
1234
  }
1242
1235
  const to = graph.edgesByTo.get(edge.to);
1243
1236
  if (to) {
1237
+ _rebuildCounters.removeOwnedEdgePositions += to.length;
1244
1238
  const at = to.indexOf(edge);
1245
1239
  if (at >= 0)
1246
1240
  to.splice(at, 1);
@@ -1248,6 +1242,49 @@ function unindexEdge(graph, edge) {
1248
1242
  graph.edgesByTo.delete(edge.to);
1249
1243
  }
1250
1244
  }
1245
+ /**
1246
+ * Remove a batch of edges from both live adjacency indexes. Each touched
1247
+ * bucket is filtered once, so the work is proportional to the bucket lengths,
1248
+ * not to the product of a hub's fan-in and its removed-edge count (#2074).
1249
+ */
1250
+ function unindexEdges(graph, removedEdges) {
1251
+ const fromIds = new Set();
1252
+ const toIds = new Set();
1253
+ for (const edge of removedEdges) {
1254
+ fromIds.add(edge.from);
1255
+ toIds.add(edge.to);
1256
+ }
1257
+ for (const fromId of fromIds) {
1258
+ const bucket = graph.edgesByFrom.get(fromId);
1259
+ if (!bucket)
1260
+ continue;
1261
+ const kept = [];
1262
+ for (const edge of bucket) {
1263
+ _rebuildCounters.removeOwnedEdgePositions++;
1264
+ if (!removedEdges.has(edge))
1265
+ kept.push(edge);
1266
+ }
1267
+ if (kept.length === 0)
1268
+ graph.edgesByFrom.delete(fromId);
1269
+ else
1270
+ graph.edgesByFrom.set(fromId, kept);
1271
+ }
1272
+ for (const toId of toIds) {
1273
+ const bucket = graph.edgesByTo.get(toId);
1274
+ if (!bucket)
1275
+ continue;
1276
+ const kept = [];
1277
+ for (const edge of bucket) {
1278
+ _rebuildCounters.removeOwnedEdgePositions++;
1279
+ if (!removedEdges.has(edge))
1280
+ kept.push(edge);
1281
+ }
1282
+ if (kept.length === 0)
1283
+ graph.edgesByTo.delete(toId);
1284
+ else
1285
+ graph.edgesByTo.set(toId, kept);
1286
+ }
1287
+ }
1251
1288
  function addEdge(graph, edge) {
1252
1289
  graph.edges.push(edge);
1253
1290
  indexEdge(graph, edge);
@@ -2621,9 +2658,16 @@ async function tryResumeFromCheckpoint(cwd, filesToBuild, ignoredIds) {
2621
2658
  }
2622
2659
  const graph = loaded.graph;
2623
2660
  // Evict every stale (content-changed) processed file so its outdated
2624
- // contribution is replaced by a fresh walk below.
2661
+ // contribution is replaced by a fresh walk below. One shared drop set,
2662
+ // compacted once (#2074) — pruneOrphanNonFileNodes reads graph.edges next,
2663
+ // so the compaction must land before it runs.
2664
+ const removedEdges = new Set();
2625
2665
  for (const file of stale)
2626
- removeFileOwnedGraphData(graph, file);
2666
+ removeFileOwnedGraphData(graph, file, removedEdges);
2667
+ if (removedEdges.size > 0) {
2668
+ unindexEdges(graph, removedEdges);
2669
+ graph.edges = graph.edges.filter((edge) => !removedEdges.has(edge));
2670
+ }
2627
2671
  pruneOrphanNonFileNodes(graph);
2628
2672
  graph.changedSymbolsByFile = new Map();
2629
2673
  const remaining = filesToBuild.filter((file) => !reusableHashes.has(file));
@@ -2654,7 +2698,7 @@ export function _readReviewGraphCheckpointForTests(cwd) {
2654
2698
  }
2655
2699
  /** Test hook: force any pending debounced persist to write immediately. */
2656
2700
  export function flushReviewGraphPersistsForTests() {
2657
- for (const key of [..._pendingPersist.keys()]) {
2701
+ for (const key of _pendingPersist.keys()) {
2658
2702
  const pending = _pendingPersist.get(key);
2659
2703
  if (!pending)
2660
2704
  continue;
@@ -2718,7 +2762,7 @@ export function flushReviewGraphPersist(cwd, source = "cli") {
2718
2762
  let pending = _pendingPersist.get(key);
2719
2763
  const removedWorkerRequests = [];
2720
2764
  let selectedWorkerRequest;
2721
- for (const [id, request] of [..._workerRequests]) {
2765
+ for (const [id, request] of _workerRequests) {
2722
2766
  if (request.key !== key)
2723
2767
  continue;
2724
2768
  _workerRequests.delete(id);
@@ -2883,7 +2927,7 @@ function upsertChangedSymbols(graph, facts, filePath) {
2883
2927
  if (detectFileRole(filePath) === "test")
2884
2928
  return;
2885
2929
  const normalized = normalizeMapKey(filePath);
2886
- const changed = facts.getSessionFact(`${CHANGED_SYMBOLS_PREFIX}${normalized}`);
2930
+ const changed = facts.getBoundedSessionFact(`${CHANGED_SYMBOLS_PREFIX}${normalized}`);
2887
2931
  if (changed && changed.length > 0) {
2888
2932
  graph.changedSymbolsByFile.set(normalized, [...changed]);
2889
2933
  }
@@ -3158,45 +3202,13 @@ function addJsTsFile(graph, cwd, filePath, facts, ignoredIds) {
3158
3202
  }
3159
3203
  }
3160
3204
  }
3161
- function mapKindToTreeSitterLanguage(kind, filePath) {
3162
- switch (kind) {
3163
- case "python":
3164
- return "python";
3165
- case "go":
3166
- return "go";
3167
- case "rust":
3168
- return "rust";
3169
- case "ruby":
3170
- return "ruby";
3171
- case "cxx": {
3172
- const ext = filePath ? path.extname(filePath).toLowerCase() : "";
3173
- return ext === ".c" || ext === ".h" ? "c" : "cpp";
3174
- }
3175
- case "java":
3176
- return "java";
3177
- case "kotlin":
3178
- return "kotlin";
3179
- case "dart":
3180
- return "dart";
3181
- case "elixir":
3182
- return "elixir";
3183
- case "csharp":
3184
- return "csharp";
3185
- case "php":
3186
- return "php";
3187
- case "swift":
3188
- return "swift";
3189
- case "lua":
3190
- return "lua";
3191
- case "ocaml":
3192
- return "ocaml";
3193
- case "zig":
3194
- return "zig";
3195
- case "shell":
3196
- return "bash";
3197
- default:
3198
- return undefined;
3199
- }
3205
+ // The review graph's kind -> tree-sitter grammar answer. Delegates to the
3206
+ // registry-derived resolver (#2424): this used to be a hand-written switch that
3207
+ // re-derived the `.c`/`.h` split inline and drifted from the ext -> grammar
3208
+ // authority. Exported for the language-identity golden snapshot
3209
+ // (scripts/gen-language-snapshot.mjs).
3210
+ export function mapKindToTreeSitterLanguage(kind, filePath) {
3211
+ return symbolExtractionGrammar(kind, filePath);
3200
3212
  }
3201
3213
  async function getExtractor(languageId) {
3202
3214
  if (extractorCache.has(languageId))
@@ -3467,7 +3479,7 @@ function addTreeSitterFile(graph, cwd, filePath, languageId, extracted, ignoredI
3467
3479
  * SymbolInformation results (including native TypeScript 7) recover the same
3468
3480
  * containment through `containerName` when the owner is present in the result.
3469
3481
  */
3470
- export function addLspFallbackSymbols(graph, filePath, languageId, symbols) {
3482
+ function addLspFallbackSymbols(graph, filePath, languageId, symbols) {
3471
3483
  const normalized = normalizeMapKey(filePath);
3472
3484
  const fileNodeId = `file:${normalized}`;
3473
3485
  let added = 0;
@@ -3622,46 +3634,55 @@ function addCxxIncludeEdges(graph, cwd, filePath, ignoredIds, contentOverride) {
3622
3634
  });
3623
3635
  }
3624
3636
  }
3625
- function removeFileOwnedGraphData(graph, filePath) {
3637
+ /**
3638
+ * Drop the nodes/edges a changed file owns, and index-maintain as it goes.
3639
+ * `removedEdges` accumulates edges to drop from `graph.edges` — the CALLER
3640
+ * compacts the array once for the whole batch (#2074): before this, each call
3641
+ * ran its own `graph.edges.filter()`, so a batch of N changed files scanned
3642
+ * the whole edge array N times (changedFiles x graph, not changedFiles x
3643
+ * fan-in/out).
3644
+ *
3645
+ * `removedIds` is exactly `fileNodes`/`symbolNodesByFile`'s entries for this
3646
+ * file — `file` and `symbol` are the only node kinds that ever set `filePath`
3647
+ * (`module`/`external` placeholders never do) — so no `graph.nodes` scan is
3648
+ * needed either, and the owned edges are read straight off `edgesByFrom` /
3649
+ * `edgesByTo` instead of scanning `graph.edges`.
3650
+ */
3651
+ function removeFileOwnedGraphData(graph, filePath, removedEdges) {
3626
3652
  const normalized = normalizeMapKey(filePath);
3627
- const fileNodeId = `file:${normalized}`;
3628
- const removedIds = new Set();
3629
- const removedSymbolIds = new Set();
3630
- for (const [id, node] of graph.nodes) {
3631
- if (node.filePath !== normalized)
3632
- continue;
3633
- removedIds.add(id);
3634
- if (node.kind === "symbol")
3635
- removedSymbolIds.add(id);
3636
- }
3653
+ const fileNodeId = graph.fileNodes.get(normalized) ?? `file:${normalized}`;
3654
+ const removedSymbolIds = new Set(graph.symbolNodesByFile.get(normalized));
3655
+ const removedIds = new Set(removedSymbolIds);
3637
3656
  if (graph.nodes.has(fileNodeId))
3638
3657
  removedIds.add(fileNodeId);
3658
+ const candidates = new Set();
3659
+ for (const id of removedIds) {
3660
+ for (const edge of graph.edgesByFrom.get(id) ?? []) {
3661
+ _rebuildCounters.removeOwnedEdgeVisits++;
3662
+ candidates.add(edge);
3663
+ }
3664
+ for (const edge of graph.edgesByTo.get(id) ?? []) {
3665
+ _rebuildCounters.removeOwnedEdgeVisits++;
3666
+ candidates.add(edge);
3667
+ }
3668
+ }
3639
3669
  const preservedIncomingSymbolEdges = [];
3640
- const droppedEdges = [];
3641
- graph.edges = graph.edges.filter((edge) => {
3670
+ for (const edge of candidates) {
3671
+ // A cross-edge can be discovered from both changed files' adjacency
3672
+ // buckets. Preserve and remove it only once, matching eager unindexing
3673
+ // while the batch indexes remain live until the end.
3674
+ if (removedEdges.has(edge))
3675
+ continue;
3642
3676
  const fromRemoved = removedIds.has(edge.from);
3643
- const toRemoved = removedIds.has(edge.to);
3644
- if (fromRemoved) {
3645
- droppedEdges.push(edge);
3646
- return false;
3647
- }
3648
- if (removedSymbolIds.has(edge.to)) {
3677
+ // Preserve importer edges to the stable file node id; the node is
3678
+ // re-added below.
3679
+ if (!fromRemoved && edge.to === fileNodeId)
3680
+ continue;
3681
+ if (!fromRemoved && removedSymbolIds.has(edge.to)) {
3649
3682
  preservedIncomingSymbolEdges.push({ ...edge });
3650
- droppedEdges.push(edge);
3651
- return false;
3652
3683
  }
3653
- // Preserve importer edges to the stable file node id; the node is re-added below.
3654
- if (toRemoved && edge.to === fileNodeId)
3655
- return true;
3656
- if (toRemoved)
3657
- droppedEdges.push(edge);
3658
- return !toRemoved;
3659
- });
3660
- // #2074: keep the adjacency indexes live instead of rebuilding them over the
3661
- // whole graph afterwards. Only the dropped edges' own buckets are touched.
3662
- // Unconditional: on an unindexed graph every bucket lookup simply misses.
3663
- for (const edge of droppedEdges)
3664
- unindexEdge(graph, edge);
3684
+ removedEdges.add(edge);
3685
+ }
3665
3686
  for (const id of removedIds) {
3666
3687
  const node = graph.nodes.get(id);
3667
3688
  graph.nodes.delete(id);
@@ -3769,18 +3790,30 @@ async function addFileToGraph(graph, cwd, file, facts, ignoredIds, contentOverri
3769
3790
  /**
3770
3791
  * #2074 acceptance instrumentation. `restoreComparisons` counts edge-metadata
3771
3792
  * stringifications inside `restoreValidIncomingEdges`; `importTargetEdgeScans`
3772
- * counts edges visited by `importTargetsForFile`. Before this change both grew
3773
- * with the whole graph on every one-file rebuild. They are the count-based
3774
- * signal the issue asks for, because wall time on this hardware is IO-noisy
3775
- * (#1920).
3793
+ * counts edges visited by `importTargetsForFile`; `removeOwnedEdgeVisits`
3794
+ * counts edges visited while collecting a changed file's owned edges in
3795
+ * `removeFileOwnedGraphData`. Before this change all three grew with the whole
3796
+ * graph on every one-file rebuild, and `removeOwnedEdgeVisits` grew with
3797
+ * changedFiles x graph on a multi-file batch (one full `graph.edges` scan per
3798
+ * file). `removeOwnedEdgePositions` counts adjacency positions examined while
3799
+ * removing edges; batching keeps a high-fan-in bucket linear instead of
3800
+ * rescanning its prefix for every edge. They are the count-based signal the
3801
+ * issue asks for, because wall time on this hardware is IO-noisy (#1920).
3776
3802
  */
3777
- const _rebuildCounters = { restoreComparisons: 0, importTargetEdgeScans: 0 };
3803
+ const _rebuildCounters = {
3804
+ restoreComparisons: 0,
3805
+ importTargetEdgeScans: 0,
3806
+ removeOwnedEdgeVisits: 0,
3807
+ removeOwnedEdgePositions: 0,
3808
+ };
3778
3809
  export function _getReviewGraphRebuildCountersForTests() {
3779
3810
  return { ..._rebuildCounters };
3780
3811
  }
3781
3812
  export function _resetReviewGraphRebuildCountersForTests() {
3782
3813
  _rebuildCounters.restoreComparisons = 0;
3783
3814
  _rebuildCounters.importTargetEdgeScans = 0;
3815
+ _rebuildCounters.removeOwnedEdgeVisits = 0;
3816
+ _rebuildCounters.removeOwnedEdgePositions = 0;
3784
3817
  }
3785
3818
  /**
3786
3819
  * Identity of an edge for dedupe purposes, WITHIN one target bucket: the array
@@ -3893,10 +3926,18 @@ async function updateGraphFiles(graph, cwd, files, facts, ignoredIds) {
3893
3926
  priorTargets: importTargetsForFile(graph, file),
3894
3927
  }));
3895
3928
  const preservedIncoming = [];
3929
+ // #2074: one shared drop set for the whole batch, compacted into graph.edges
3930
+ // exactly once below — not once per file, which made a multi-file rebuild
3931
+ // scan the edge array changedFiles times over.
3932
+ const removedEdges = new Set();
3896
3933
  for (const file of files) {
3897
- preservedIncoming.push(...removeFileOwnedGraphData(graph, file));
3934
+ preservedIncoming.push(...removeFileOwnedGraphData(graph, file, removedEdges));
3898
3935
  await addFileToGraph(graph, cwd, file, facts, ignoredIds);
3899
3936
  }
3937
+ if (removedEdges.size > 0) {
3938
+ unindexEdges(graph, removedEdges);
3939
+ graph.edges = graph.edges.filter((edge) => !removedEdges.has(edge));
3940
+ }
3900
3941
  restoreValidIncomingEdges(graph, preservedIncoming);
3901
3942
  resolveDeferredSymbolEdges(graph, false);
3902
3943
  graph.changedSymbolsByFile.clear();
@@ -90,7 +90,7 @@ function resolveDart(cwd, filePath, source) {
90
90
  // JS/TS extension from the specifier before re-appending candidate extensions
91
91
  // lets that universal pattern resolve to the real source file. Exported so
92
92
  // builder.ts's warm `localImportToFile` shares the exact same regex (#694).
93
- export const JS_TS_EXT_RE = /\.(mjs|cjs|mts|cts|jsx?|tsx?)$/i;
93
+ const JS_TS_EXT_RE = /\.(mjs|cjs|mts|cts|jsx?|tsx?)$/i;
94
94
  /**
95
95
  * Ordered candidate list for resolving a relative JS/TS import specifier
96
96
  * (already resolved to an absolute base path) to an on-disk file, with
@@ -1,3 +1,5 @@
1
+ import { recordDegradationOnce } from "../degradation-ledger.js";
2
+ import { isAbsoluteFilePath, normalizeMapKey } from "../path-utils.js";
1
3
  import { computeImpactCascade as computeImpactCascadeImpl, computeTransitiveImpact as computeTransitiveImpactImpl, } from "./query.js";
2
4
  import { buildOrUpdateGraph as buildOrUpdateGraphImpl, } from "./builder.js";
3
5
  import { formatImpactCascade as formatImpactCascadeImpl } from "./format.js";
@@ -19,7 +21,57 @@ export function computeTransitiveImpact(graph, seedFile, options) {
19
21
  return computeTransitiveImpactImpl(graph, seedFile, options);
20
22
  }
21
23
  export function recordEntitySnapshotDiff(facts, filePath, nextSnapshot) {
22
- const prev = facts.getSessionFact(`${ENTITY_SNAPSHOT_PREFIX}${filePath}`) ?? new Map();
24
+ // #2477 round 2 (reviewer F1/F2): folding `cwd` into this key was REVERTED.
25
+ // The writer (`clients/dispatch/runners/tree-sitter.ts`) passes `ctx.cwd`,
26
+ // which `createDispatchContext` (`clients/dispatch/dispatcher.ts:324-326`)
27
+ // sets to the file's NEAREST LANGUAGE-ROOT (`resolveLanguageRootForFile`),
28
+ // while the reader (`upsertChangedSymbols`, `clients/review-graph/builder.ts`,
29
+ // reached via `computeCascadeForFile` → `buildOrUpdateGraph`) keys off the
30
+ // WORKSPACE cwd. In a monorepo/cargo-workspace/go-module layout those two
31
+ // cwd values diverge, so a folded `(cwd, path)` key the builder reader can
32
+ // never hit zeroed `graphChangedSymbolCount` on every build — reproduced:
33
+ // master returns `["alpha","beta"]`, the folded-key version returns `[]`
34
+ // — and made the cascade over-fan to every symbol (`query.ts:230-238`
35
+ // treats an empty `changedSymbols` as "no narrowing").
36
+ //
37
+ // The #2477 cross-project collision this was meant to close cannot reach
38
+ // this function in production: the sole writer always passes `ctx.filePath`,
39
+ // which `createDispatchContext` sets via `resolveRunnerPath` to an
40
+ // ALWAYS-ABSOLUTE path (the #2016 invariant, `dispatcher.ts:320,327,346`).
41
+ // A relative `filePath` here can only happen through a caller regression.
42
+ // Enforce that instead of re-keying: reject a non-absolute `filePath` with
43
+ // a visible, bounded ledger record (never silently, per AGENTS.md) and
44
+ // skip the diff rather than compute one under a key the real reader could
45
+ // never reach anyway. `recordDegradationOnce` (not a thrown error) is used
46
+ // because the sole call site's surrounding try/catch
47
+ // (`tree-sitter.ts` — "entity snapshot / blast-radius enrichment is
48
+ // best-effort") is a BARE, UNLOGGED catch: a thrown error there vanishes
49
+ // with no trace, which is strictly worse than a ledger record.
50
+ if (!isAbsoluteFilePath(filePath)) {
51
+ recordDegradationOnce({
52
+ kind: "review-graph-non-absolute-entity-path",
53
+ subject: filePath,
54
+ reason: "recordEntitySnapshotDiff requires an absolute filePath (refs #2477)",
55
+ });
56
+ return { added: [], removed: [], modified: [] };
57
+ }
58
+ // Normalize once at this boundary, then reuse the folded path for both
59
+ // per-file facts. An unnormalized write is a key the builder reader can never
60
+ // hit, and an unnormalized snapshot forks a second empty diff (#2355).
61
+ const normalizedFilePath = normalizeMapKey(filePath);
62
+ const snapshotKey = `${ENTITY_SNAPSHOT_PREFIX}${normalizedFilePath}`;
63
+ const changedSymbolsKey = `${CHANGED_SYMBOLS_PREFIX}${normalizedFilePath}`;
64
+ const stored = facts.getBoundedSessionFact(snapshotKey);
65
+ // An evicted snapshot is unknown, not empty. Diffing against an empty Map
66
+ // puts every entity in `added`, which reads downstream as "the whole file
67
+ // changed" and schedules a blast-radius run for a file that did not change.
68
+ // Re-seed the snapshot and report no diff instead (#2282 review F1).
69
+ if (stored === undefined && facts.wasBoundedSessionFactEvicted(snapshotKey)) {
70
+ facts.setBoundedSessionFact(snapshotKey, new Map(nextSnapshot));
71
+ facts.setBoundedSessionFact(changedSymbolsKey, []);
72
+ return { added: [], removed: [], modified: [] };
73
+ }
74
+ const prev = stored ?? new Map();
23
75
  const added = [];
24
76
  const removed = [];
25
77
  const modified = [];
@@ -38,7 +90,7 @@ export function recordEntitySnapshotDiff(facts, filePath, nextSnapshot) {
38
90
  .map((key) => key.split(":")[1])
39
91
  .filter(Boolean)),
40
92
  ];
41
- facts.setSessionFact(`${ENTITY_SNAPSHOT_PREFIX}${filePath}`, new Map(nextSnapshot));
42
- facts.setSessionFact(`${CHANGED_SYMBOLS_PREFIX}${filePath}`, changedSymbols);
93
+ facts.setBoundedSessionFact(snapshotKey, new Map(nextSnapshot));
94
+ facts.setBoundedSessionFact(changedSymbolsKey, changedSymbols);
43
95
  return { added, removed, modified };
44
96
  }
@@ -1,4 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
+ import { BoundedFifoMap } from "../bounded-cache.js";
2
3
  import { normalizeMapKey } from "../path-utils.js";
3
4
  /**
4
5
  * THE producer/consumer hash contract, in one place (#955 review): scanner
@@ -17,7 +18,10 @@ export function reviewGraphIrContentHash(content) {
17
18
  // registry's lifetime is the scan-to-build window, not the process lifetime.
18
19
  const MAX_ROOTS = 8;
19
20
  const MAX_FILES_PER_ROOT = 20_000;
20
- const roots = new Map();
21
+ // Both levels are FIFO, matching the hand-rolled blocks these replaced: no
22
+ // read on either map refreshed recency before #2442, and the consume-once
23
+ // contract above means a read is normally a DELETE anyway.
24
+ const roots = new BoundedFifoMap(MAX_ROOTS);
21
25
  let accepted = 0;
22
26
  let rejected = 0;
23
27
  function rootKey(cwd) {
@@ -33,23 +37,11 @@ export function publishReviewGraphFileIr(cwd, entry) {
33
37
  const key = rootKey(cwd);
34
38
  let files = roots.get(key);
35
39
  if (!files) {
36
- files = new Map();
40
+ files = new BoundedFifoMap(MAX_FILES_PER_ROOT);
37
41
  roots.set(key, files);
38
- while (roots.size > MAX_ROOTS) {
39
- const oldest = roots.keys().next().value;
40
- if (oldest === undefined)
41
- break;
42
- roots.delete(oldest);
43
- }
44
42
  }
45
43
  const fileKey = normalizeMapKey(entry.filePath);
46
- files.delete(fileKey);
47
- while (files.size >= MAX_FILES_PER_ROOT) {
48
- const oldest = files.keys().next().value;
49
- if (oldest === undefined)
50
- break;
51
- files.delete(oldest);
52
- }
44
+ files.delete(fileKey); // republish is a refresh: it becomes the newest
53
45
  files.set(fileKey, { ...entry, filePath: fileKey });
54
46
  }
55
47
  /**