@herbertgao/pi-extensions 2026.9.0 → 2026.9.1

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 (293) 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/@pi-plugins/fast-mode/README.md +5 -3
  11. package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts.map +1 -1
  12. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +20 -55
  13. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
  14. package/node_modules/@pi-plugins/fast-mode/package.json +1 -1
  15. package/node_modules/pi-lens/CHANGELOG.md +988 -0
  16. package/node_modules/pi-lens/README.md +3 -0
  17. package/node_modules/pi-lens/config/biome/core.jsonc +11 -2
  18. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +13 -1
  19. package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +2 -2
  20. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +927 -80
  21. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +13 -4
  22. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +123 -3
  23. package/node_modules/pi-lens/dist/clients/ast-grep-rule-manager.js +60 -5
  24. package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +2 -2
  25. package/node_modules/pi-lens/dist/clients/bash-file-access.js +1 -3
  26. package/node_modules/pi-lens/dist/clients/biome-client.js +9 -2
  27. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +14 -0
  28. package/node_modules/pi-lens/dist/clients/bootstrap.js +509 -73
  29. package/node_modules/pi-lens/dist/clients/bounded-cache.js +152 -12
  30. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +59 -4
  31. package/node_modules/pi-lens/dist/clients/bundled-resource-health.js +113 -0
  32. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +2 -2
  33. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +31 -4
  34. package/node_modules/pi-lens/dist/clients/cache-manager.js +105 -6
  35. package/node_modules/pi-lens/dist/clients/cache-observability.js +44 -18
  36. package/node_modules/pi-lens/dist/clients/cargo-manifest.js +422 -0
  37. package/node_modules/pi-lens/dist/clients/cascade-logger.js +2 -2
  38. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +13 -3
  39. package/node_modules/pi-lens/dist/clients/complexity-client.js +16 -5
  40. package/node_modules/pi-lens/dist/clients/config-core/deny.js +221 -0
  41. package/node_modules/pi-lens/dist/clients/config-core/index.js +47 -0
  42. package/node_modules/pi-lens/dist/clients/config-core/merge.js +357 -0
  43. package/node_modules/pi-lens/dist/clients/config-core/normalize.js +340 -0
  44. package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +248 -0
  45. package/node_modules/pi-lens/dist/clients/config-core/provenance.js +171 -0
  46. package/node_modules/pi-lens/dist/clients/config-core/records.js +218 -0
  47. package/node_modules/pi-lens/dist/clients/config-core/resolve.js +125 -0
  48. package/node_modules/pi-lens/dist/clients/config-core/safe-object.js +78 -0
  49. package/node_modules/pi-lens/dist/clients/config-core/schema.js +167 -0
  50. package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +281 -0
  51. package/node_modules/pi-lens/dist/clients/config-locations.js +160 -0
  52. package/node_modules/pi-lens/dist/clients/config-resolve.js +789 -0
  53. package/node_modules/pi-lens/dist/clients/config-schema.js +197 -0
  54. package/node_modules/pi-lens/dist/clients/config-warn.js +407 -0
  55. package/node_modules/pi-lens/dist/clients/dead-code-client.js +19 -14
  56. package/node_modules/pi-lens/dist/clients/dead-code-logger.js +2 -2
  57. package/node_modules/pi-lens/dist/clients/deadline-utils.js +178 -0
  58. package/node_modules/pi-lens/dist/clients/debug-handles.js +2 -2
  59. package/node_modules/pi-lens/dist/clients/debug-heap.js +3 -3
  60. package/node_modules/pi-lens/dist/clients/deferred-lsp-work.js +106 -0
  61. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +113 -7
  62. package/node_modules/pi-lens/dist/clients/dependency-checker.js +8 -19
  63. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +12 -9
  64. package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +14 -4
  65. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +162 -18
  66. package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +97 -0
  67. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +9 -7
  68. package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +0 -1
  69. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +380 -26
  70. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +0 -1
  71. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +0 -1
  72. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +0 -1
  73. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +0 -1
  74. package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +0 -1
  75. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +0 -1
  76. package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +0 -1
  77. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +0 -1
  78. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +0 -1
  79. package/node_modules/pi-lens/dist/clients/dispatch/runners/fact-rules.js +0 -1
  80. package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +0 -1
  81. package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +0 -1
  82. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +1 -3
  83. package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +0 -1
  84. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +0 -1
  85. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +0 -1
  86. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +0 -1
  87. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +0 -1
  88. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +0 -1
  89. package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +0 -1
  90. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +0 -1
  91. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +0 -1
  92. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +0 -1
  93. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +13 -18
  94. package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +0 -1
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +0 -1
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +0 -1
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +0 -1
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +7 -4
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +0 -1
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +0 -1
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +1 -3
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +0 -1
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +0 -1
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +0 -1
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +0 -1
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +0 -1
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +0 -1
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +0 -1
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +0 -1
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +0 -1
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +0 -1
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +0 -1
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +0 -1
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +3 -0
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +3 -4
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +194 -80
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +84 -15
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +0 -1
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +0 -1
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +0 -1
  121. package/node_modules/pi-lens/dist/clients/disposition-logger.js +3 -3
  122. package/node_modules/pi-lens/dist/clients/effective-config.js +403 -0
  123. package/node_modules/pi-lens/dist/clients/error-class.js +23 -0
  124. package/node_modules/pi-lens/dist/clients/event-loop-hold.js +274 -0
  125. package/node_modules/pi-lens/dist/clients/extension-log.js +2 -2
  126. package/node_modules/pi-lens/dist/clients/feature-hints.js +2 -1
  127. package/node_modules/pi-lens/dist/clients/file-time.js +6 -1
  128. package/node_modules/pi-lens/dist/clients/file-utils.js +112 -9
  129. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +50 -13
  130. package/node_modules/pi-lens/dist/clients/format-service.js +6 -1
  131. package/node_modules/pi-lens/dist/clients/formatters.js +276 -102
  132. package/node_modules/pi-lens/dist/clients/freshness-cadence.js +17 -0
  133. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +114 -19
  134. package/node_modules/pi-lens/dist/clients/generation-guard.js +4 -9
  135. package/node_modules/pi-lens/dist/clients/git-guard.js +1 -1
  136. package/node_modules/pi-lens/dist/clients/go-client.js +39 -0
  137. package/node_modules/pi-lens/dist/clients/gradle-ktfmt-style.js +252 -0
  138. package/node_modules/pi-lens/dist/clients/hashline-anchor.js +424 -0
  139. package/node_modules/pi-lens/dist/clients/hook-budgets.js +76 -0
  140. package/node_modules/pi-lens/dist/clients/host-edit-normalize.js +5 -2
  141. package/node_modules/pi-lens/dist/clients/host-ports.js +1 -1
  142. package/node_modules/pi-lens/dist/clients/installer/index.js +71 -52
  143. package/node_modules/pi-lens/dist/clients/instance-reaper.js +104 -230
  144. package/node_modules/pi-lens/dist/clients/instance-registry.js +6 -3
  145. package/node_modules/pi-lens/dist/clients/language-registry.js +595 -0
  146. package/node_modules/pi-lens/dist/clients/latency-logger.js +160 -2
  147. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +13 -0
  148. package/node_modules/pi-lens/dist/clients/lens-config.js +141 -30
  149. package/node_modules/pi-lens/dist/clients/lens-engine.js +30 -37
  150. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +59 -6
  151. package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -2
  152. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +3 -1
  153. package/node_modules/pi-lens/dist/clients/lsp/client.js +168 -30
  154. package/node_modules/pi-lens/dist/clients/lsp/config.js +483 -158
  155. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +1 -1
  156. package/node_modules/pi-lens/dist/clients/lsp/edits.js +15 -2
  157. package/node_modules/pi-lens/dist/clients/lsp/index.js +676 -71
  158. package/node_modules/pi-lens/dist/clients/lsp/language.js +18 -169
  159. package/node_modules/pi-lens/dist/clients/lsp/launch.js +2 -1
  160. package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +5 -16
  161. package/node_modules/pi-lens/dist/clients/lsp/server.js +284 -147
  162. package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +116 -23
  163. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +2 -7
  164. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +9 -2
  165. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +1 -1
  166. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +163 -21
  167. package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +36 -0
  168. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +33 -4
  169. package/node_modules/pi-lens/dist/clients/mcp/session.js +5 -16
  170. package/node_modules/pi-lens/dist/clients/memory-sampler.js +8 -3
  171. package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +2 -4
  172. package/node_modules/pi-lens/dist/clients/module-report.js +20 -45
  173. package/node_modules/pi-lens/dist/clients/mutating-tool.js +651 -0
  174. package/node_modules/pi-lens/dist/clients/mutation-attribution.js +368 -0
  175. package/node_modules/pi-lens/dist/clients/mutation-bridge.js +240 -0
  176. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +247 -20
  177. package/node_modules/pi-lens/dist/clients/observed-mutation-sources.js +101 -0
  178. package/node_modules/pi-lens/dist/clients/observed-mutation.js +1215 -0
  179. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +68 -22
  180. package/node_modules/pi-lens/dist/clients/opengrep-config.js +4 -0
  181. package/node_modules/pi-lens/dist/clients/package-manager.js +195 -26
  182. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +359 -80
  183. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +23 -7
  184. package/node_modules/pi-lens/dist/clients/path-keyed-map.js +21 -2
  185. package/node_modules/pi-lens/dist/clients/path-utils.js +428 -13
  186. package/node_modules/pi-lens/dist/clients/persist-debounce.js +8 -1
  187. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +114 -0
  188. package/node_modules/pi-lens/dist/clients/pipeline.js +77 -16
  189. package/node_modules/pi-lens/dist/clients/probe-home-state.js +228 -0
  190. package/node_modules/pi-lens/dist/clients/process-bridge.js +66 -0
  191. package/node_modules/pi-lens/dist/clients/process-snapshot.js +68 -0
  192. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +4 -3
  193. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +27 -3
  194. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +51 -20
  195. package/node_modules/pi-lens/dist/clients/project-lens-config.js +393 -122
  196. package/node_modules/pi-lens/dist/clients/project-snapshot.js +52 -24
  197. package/node_modules/pi-lens/dist/clients/python-environment.js +217 -0
  198. package/node_modules/pi-lens/dist/clients/python-provenance.js +639 -0
  199. package/node_modules/pi-lens/dist/clients/quiet-window.js +6 -1
  200. package/node_modules/pi-lens/dist/clients/read-bridge.js +13 -21
  201. package/node_modules/pi-lens/dist/clients/read-expansion.js +9 -46
  202. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +15 -4
  203. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +364 -212
  204. package/node_modules/pi-lens/dist/clients/read-guard.js +21 -0
  205. package/node_modules/pi-lens/dist/clients/resource-sampler.js +195 -96
  206. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +152 -109
  207. package/node_modules/pi-lens/dist/clients/review-graph/service.js +55 -3
  208. package/node_modules/pi-lens/dist/clients/review-graph/shared-extraction-ir.js +7 -15
  209. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +10 -74
  210. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +9 -3
  211. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +18 -2
  212. package/node_modules/pi-lens/dist/clients/runtime-context.js +49 -16
  213. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +25 -6
  214. package/node_modules/pi-lens/dist/clients/runtime-session.js +139 -22
  215. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +168 -35
  216. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +797 -243
  217. package/node_modules/pi-lens/dist/clients/runtime-turn.js +1028 -53
  218. package/node_modules/pi-lens/dist/clients/rust-client.js +31 -0
  219. package/node_modules/pi-lens/dist/clients/safe-spawn.js +33 -10
  220. package/node_modules/pi-lens/dist/clients/sanitize.js +7 -1
  221. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +1 -3
  222. package/node_modules/pi-lens/dist/clients/security-scan-client.js +3 -0
  223. package/node_modules/pi-lens/dist/clients/session-lifecycle.js +1 -1
  224. package/node_modules/pi-lens/dist/clients/session-start-observability.js +79 -0
  225. package/node_modules/pi-lens/dist/clients/sessionstart-logger.js +12 -3
  226. package/node_modules/pi-lens/dist/clients/sgconfig.js +6 -1
  227. package/node_modules/pi-lens/dist/clients/skills-resolver.js +105 -0
  228. package/node_modules/pi-lens/dist/clients/smells-rollup.js +2 -2
  229. package/node_modules/pi-lens/dist/clients/string-utils.js +13 -0
  230. package/node_modules/pi-lens/dist/clients/subagent-mode.js +17 -4
  231. package/node_modules/pi-lens/dist/clients/test-runner-client.js +176 -39
  232. package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +239 -0
  233. package/node_modules/pi-lens/dist/clients/tool-definition.js +41 -1
  234. package/node_modules/pi-lens/dist/clients/tool-policy.js +91 -21
  235. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +40 -40
  236. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +86 -48
  237. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +2 -2
  238. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +81 -1
  239. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +51 -46
  240. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +28 -0
  241. package/node_modules/pi-lens/dist/clients/typos-config.js +5 -0
  242. package/node_modules/pi-lens/dist/clients/user-notify.js +6 -2
  243. package/node_modules/pi-lens/dist/clients/widget-state.js +191 -26
  244. package/node_modules/pi-lens/dist/clients/word-index-logger.js +2 -2
  245. package/node_modules/pi-lens/dist/clients/word-index-store.js +20 -5
  246. package/node_modules/pi-lens/dist/clients/word-index.js +243 -31
  247. package/node_modules/pi-lens/dist/clients/workspace-topology.js +8 -1
  248. package/node_modules/pi-lens/dist/clients/zizmor-config.js +3 -0
  249. package/node_modules/pi-lens/dist/index.js +64335 -54300
  250. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +3 -1
  251. package/node_modules/pi-lens/dist/mcp/server.js +127 -5
  252. package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +583 -0
  253. package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +129 -0
  254. package/node_modules/pi-lens/dist/tools/effective-config.js +89 -0
  255. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +95 -24
  256. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +67 -80
  257. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +37 -16
  258. package/node_modules/pi-lens/dist/tools/shared.js +0 -1
  259. package/node_modules/pi-lens/docs/agent-guide.md +64 -1
  260. package/node_modules/pi-lens/docs/configuration.md +222 -0
  261. package/node_modules/pi-lens/docs/dependencies.md +3 -3
  262. package/node_modules/pi-lens/docs/features.md +44 -5
  263. package/node_modules/pi-lens/docs/language-coverage.md +2 -2
  264. package/node_modules/pi-lens/docs/pi-lens-fixer.md +25 -0
  265. package/node_modules/pi-lens/docs/pi-lens-investigator.md +25 -0
  266. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +27 -0
  267. package/node_modules/pi-lens/docs/pi-lens-subagent.md +38 -0
  268. package/node_modules/pi-lens/docs/pi-lens-warden.md +55 -0
  269. package/node_modules/pi-lens/docs/public-api-stability.md +359 -0
  270. package/node_modules/pi-lens/docs/release-qa-baseline.md +182 -0
  271. package/node_modules/pi-lens/docs/subagent-compat.md +110 -30
  272. package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +2 -2
  273. package/node_modules/pi-lens/package.json +15 -23
  274. package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-cross-language-method.yml +3 -3
  275. package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-hallucinated-import.yml +2 -2
  276. package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-sql-injection.yml +12 -2
  277. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +192 -1
  278. package/node_modules/pi-lens/scripts/install-selftest.mjs +58 -2
  279. package/node_modules/pi-lens/scripts/lib/skills-predicate.mjs +129 -0
  280. package/node_modules/pi-lens/scripts/rpc-load-check.mjs +3 -1
  281. package/node_modules/pi-web-access/CHANGELOG.md +28 -0
  282. package/node_modules/pi-web-access/README.md +62 -15
  283. package/node_modules/pi-web-access/curator-page.ts +3 -1
  284. package/node_modules/pi-web-access/curator-server.ts +5 -1
  285. package/node_modules/pi-web-access/gemini-search.ts +8 -4
  286. package/node_modules/pi-web-access/github-extract.ts +242 -1
  287. package/node_modules/pi-web-access/index.ts +118 -64
  288. package/node_modules/pi-web-access/mistral-search.ts +281 -0
  289. package/node_modules/pi-web-access/package.json +2 -2
  290. package/node_modules/pi-web-access/perplexity.ts +14 -1
  291. package/node_modules/pi-web-access/utils.ts +23 -6
  292. package/node_modules/pi-web-access/xai-search.ts +96 -33
  293. package/package.json +5 -5
@@ -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";
@@ -103,27 +103,20 @@ const REVIEW_GRAPH_MAX_WARM_WORKSPACES = 8;
103
103
  // spelling is resolved once; repeated builds reuse the result. The project
104
104
  // memo is bounded and workspace-cache clears provide a freshness boundary.
105
105
  const REVIEW_GRAPH_SOURCE_PATH_MEMO_ENTRIES = 16_384;
106
- const _sourcePathMemos = new Map();
106
+ // BoundedLruCache, not BoundedFifoMap: a hit here must refresh the workspace's
107
+ // recency (the hand-rolled version did its own delete+set for exactly that),
108
+ // so a workspace still being built is never the one evicted (#2442).
109
+ const _sourcePathMemos = new BoundedLruCache(REVIEW_GRAPH_MAX_WARM_WORKSPACES);
107
110
  function sourcePathMemo(cwd) {
108
111
  const key = normalizeMapKey(path.resolve(cwd));
109
112
  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);
113
+ if (existing)
114
114
  return existing;
115
- }
116
115
  const memo = {
117
116
  cache: new BoundedLruCache(REVIEW_GRAPH_SOURCE_PATH_MEMO_ENTRIES),
118
117
  normalizeCalls: { value: 0 },
119
118
  };
120
119
  _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
120
  return memo;
128
121
  }
129
122
  function normalizeGraphSourcePath(memo, raw) {
@@ -528,12 +521,15 @@ function headSliceGraph(graph, cap) {
528
521
  * This bounds the VALUE, not the store, and deliberately adds no second bounding
529
522
  * mechanism to `FactStore` (#2243 owns the store-level discipline).
530
523
  *
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.
524
+ * `sessionFacts` entry COUNT (a separate #2240 sibling, #2282) is now bounded
525
+ * too: `session.baseline.${path}` (`dispatch/dispatcher.ts`),
526
+ * `session.baseline.cascade.${path}` (`dispatch/integration.ts`), and this
527
+ * module's own `changedSymbols`/`entitySnapshot` per-file keys all go through
528
+ * `FactStore.setBoundedSessionFact`/`getBoundedSessionFact`, an LRU-count-cap
529
+ * sibling of the per-file `fileFacts` bound reusing the SAME discipline
530
+ * (#2243), not a second mechanism. `session.reviewGraph` itself stays on the
531
+ * plain, unbounded `sessionFacts` map — its key is a fixed singleton, so the
532
+ * VALUE bound below is what keeps IT small, not an entry-count cap.
537
533
  */
538
534
  function setSessionReviewGraphFact(cwd, facts, graph) {
539
535
  const retained = retainedGraph(cwd, graph);
@@ -826,9 +822,6 @@ function makeCtx(filePath, cwd, facts) {
826
822
  log: () => { },
827
823
  };
828
824
  }
829
- function escapeRegExp(string) {
830
- return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
831
- }
832
825
  function createEmptyGraph() {
833
826
  return {
834
827
  version: REVIEW_GRAPH_VERSION,
@@ -1226,13 +1219,15 @@ function indexEdge(graph, edge) {
1226
1219
  graph.edgesByTo.set(edge.to, to);
1227
1220
  }
1228
1221
  /**
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.
1222
+ * Drop `edge` from both adjacency buckets. This is kept for the single-edge
1223
+ * dedupe path; multi-file removal uses `unindexEdges` below so a shared hub
1224
+ * bucket is scanned once rather than once per removed edge (#2074).
1232
1225
  */
1233
1226
  function unindexEdge(graph, edge) {
1234
1227
  const from = graph.edgesByFrom.get(edge.from);
1235
1228
  if (from) {
1229
+ // Count the full bucket as the linear `indexOf` scan's bounded work.
1230
+ _rebuildCounters.removeOwnedEdgePositions += from.length;
1236
1231
  const at = from.indexOf(edge);
1237
1232
  if (at >= 0)
1238
1233
  from.splice(at, 1);
@@ -1241,6 +1236,7 @@ function unindexEdge(graph, edge) {
1241
1236
  }
1242
1237
  const to = graph.edgesByTo.get(edge.to);
1243
1238
  if (to) {
1239
+ _rebuildCounters.removeOwnedEdgePositions += to.length;
1244
1240
  const at = to.indexOf(edge);
1245
1241
  if (at >= 0)
1246
1242
  to.splice(at, 1);
@@ -1248,6 +1244,49 @@ function unindexEdge(graph, edge) {
1248
1244
  graph.edgesByTo.delete(edge.to);
1249
1245
  }
1250
1246
  }
1247
+ /**
1248
+ * Remove a batch of edges from both live adjacency indexes. Each touched
1249
+ * bucket is filtered once, so the work is proportional to the bucket lengths,
1250
+ * not to the product of a hub's fan-in and its removed-edge count (#2074).
1251
+ */
1252
+ function unindexEdges(graph, removedEdges) {
1253
+ const fromIds = new Set();
1254
+ const toIds = new Set();
1255
+ for (const edge of removedEdges) {
1256
+ fromIds.add(edge.from);
1257
+ toIds.add(edge.to);
1258
+ }
1259
+ for (const fromId of fromIds) {
1260
+ const bucket = graph.edgesByFrom.get(fromId);
1261
+ if (!bucket)
1262
+ continue;
1263
+ const kept = [];
1264
+ for (const edge of bucket) {
1265
+ _rebuildCounters.removeOwnedEdgePositions++;
1266
+ if (!removedEdges.has(edge))
1267
+ kept.push(edge);
1268
+ }
1269
+ if (kept.length === 0)
1270
+ graph.edgesByFrom.delete(fromId);
1271
+ else
1272
+ graph.edgesByFrom.set(fromId, kept);
1273
+ }
1274
+ for (const toId of toIds) {
1275
+ const bucket = graph.edgesByTo.get(toId);
1276
+ if (!bucket)
1277
+ continue;
1278
+ const kept = [];
1279
+ for (const edge of bucket) {
1280
+ _rebuildCounters.removeOwnedEdgePositions++;
1281
+ if (!removedEdges.has(edge))
1282
+ kept.push(edge);
1283
+ }
1284
+ if (kept.length === 0)
1285
+ graph.edgesByTo.delete(toId);
1286
+ else
1287
+ graph.edgesByTo.set(toId, kept);
1288
+ }
1289
+ }
1251
1290
  function addEdge(graph, edge) {
1252
1291
  graph.edges.push(edge);
1253
1292
  indexEdge(graph, edge);
@@ -2621,9 +2660,16 @@ async function tryResumeFromCheckpoint(cwd, filesToBuild, ignoredIds) {
2621
2660
  }
2622
2661
  const graph = loaded.graph;
2623
2662
  // Evict every stale (content-changed) processed file so its outdated
2624
- // contribution is replaced by a fresh walk below.
2663
+ // contribution is replaced by a fresh walk below. One shared drop set,
2664
+ // compacted once (#2074) — pruneOrphanNonFileNodes reads graph.edges next,
2665
+ // so the compaction must land before it runs.
2666
+ const removedEdges = new Set();
2625
2667
  for (const file of stale)
2626
- removeFileOwnedGraphData(graph, file);
2668
+ removeFileOwnedGraphData(graph, file, removedEdges);
2669
+ if (removedEdges.size > 0) {
2670
+ unindexEdges(graph, removedEdges);
2671
+ graph.edges = graph.edges.filter((edge) => !removedEdges.has(edge));
2672
+ }
2627
2673
  pruneOrphanNonFileNodes(graph);
2628
2674
  graph.changedSymbolsByFile = new Map();
2629
2675
  const remaining = filesToBuild.filter((file) => !reusableHashes.has(file));
@@ -2654,7 +2700,7 @@ export function _readReviewGraphCheckpointForTests(cwd) {
2654
2700
  }
2655
2701
  /** Test hook: force any pending debounced persist to write immediately. */
2656
2702
  export function flushReviewGraphPersistsForTests() {
2657
- for (const key of [..._pendingPersist.keys()]) {
2703
+ for (const key of _pendingPersist.keys()) {
2658
2704
  const pending = _pendingPersist.get(key);
2659
2705
  if (!pending)
2660
2706
  continue;
@@ -2718,7 +2764,7 @@ export function flushReviewGraphPersist(cwd, source = "cli") {
2718
2764
  let pending = _pendingPersist.get(key);
2719
2765
  const removedWorkerRequests = [];
2720
2766
  let selectedWorkerRequest;
2721
- for (const [id, request] of [..._workerRequests]) {
2767
+ for (const [id, request] of _workerRequests) {
2722
2768
  if (request.key !== key)
2723
2769
  continue;
2724
2770
  _workerRequests.delete(id);
@@ -2883,7 +2929,7 @@ function upsertChangedSymbols(graph, facts, filePath) {
2883
2929
  if (detectFileRole(filePath) === "test")
2884
2930
  return;
2885
2931
  const normalized = normalizeMapKey(filePath);
2886
- const changed = facts.getSessionFact(`${CHANGED_SYMBOLS_PREFIX}${normalized}`);
2932
+ const changed = facts.getBoundedSessionFact(`${CHANGED_SYMBOLS_PREFIX}${normalized}`);
2887
2933
  if (changed && changed.length > 0) {
2888
2934
  graph.changedSymbolsByFile.set(normalized, [...changed]);
2889
2935
  }
@@ -3158,45 +3204,13 @@ function addJsTsFile(graph, cwd, filePath, facts, ignoredIds) {
3158
3204
  }
3159
3205
  }
3160
3206
  }
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
- }
3207
+ // The review graph's kind -> tree-sitter grammar answer. Delegates to the
3208
+ // registry-derived resolver (#2424): this used to be a hand-written switch that
3209
+ // re-derived the `.c`/`.h` split inline and drifted from the ext -> grammar
3210
+ // authority. Exported for the language-identity golden snapshot
3211
+ // (scripts/gen-language-snapshot.mjs).
3212
+ export function mapKindToTreeSitterLanguage(kind, filePath) {
3213
+ return symbolExtractionGrammar(kind, filePath);
3200
3214
  }
3201
3215
  async function getExtractor(languageId) {
3202
3216
  if (extractorCache.has(languageId))
@@ -3622,46 +3636,55 @@ function addCxxIncludeEdges(graph, cwd, filePath, ignoredIds, contentOverride) {
3622
3636
  });
3623
3637
  }
3624
3638
  }
3625
- function removeFileOwnedGraphData(graph, filePath) {
3639
+ /**
3640
+ * Drop the nodes/edges a changed file owns, and index-maintain as it goes.
3641
+ * `removedEdges` accumulates edges to drop from `graph.edges` — the CALLER
3642
+ * compacts the array once for the whole batch (#2074): before this, each call
3643
+ * ran its own `graph.edges.filter()`, so a batch of N changed files scanned
3644
+ * the whole edge array N times (changedFiles x graph, not changedFiles x
3645
+ * fan-in/out).
3646
+ *
3647
+ * `removedIds` is exactly `fileNodes`/`symbolNodesByFile`'s entries for this
3648
+ * file — `file` and `symbol` are the only node kinds that ever set `filePath`
3649
+ * (`module`/`external` placeholders never do) — so no `graph.nodes` scan is
3650
+ * needed either, and the owned edges are read straight off `edgesByFrom` /
3651
+ * `edgesByTo` instead of scanning `graph.edges`.
3652
+ */
3653
+ function removeFileOwnedGraphData(graph, filePath, removedEdges) {
3626
3654
  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
- }
3655
+ const fileNodeId = graph.fileNodes.get(normalized) ?? `file:${normalized}`;
3656
+ const removedSymbolIds = new Set(graph.symbolNodesByFile.get(normalized));
3657
+ const removedIds = new Set(removedSymbolIds);
3637
3658
  if (graph.nodes.has(fileNodeId))
3638
3659
  removedIds.add(fileNodeId);
3660
+ const candidates = new Set();
3661
+ for (const id of removedIds) {
3662
+ for (const edge of graph.edgesByFrom.get(id) ?? []) {
3663
+ _rebuildCounters.removeOwnedEdgeVisits++;
3664
+ candidates.add(edge);
3665
+ }
3666
+ for (const edge of graph.edgesByTo.get(id) ?? []) {
3667
+ _rebuildCounters.removeOwnedEdgeVisits++;
3668
+ candidates.add(edge);
3669
+ }
3670
+ }
3639
3671
  const preservedIncomingSymbolEdges = [];
3640
- const droppedEdges = [];
3641
- graph.edges = graph.edges.filter((edge) => {
3672
+ for (const edge of candidates) {
3673
+ // A cross-edge can be discovered from both changed files' adjacency
3674
+ // buckets. Preserve and remove it only once, matching eager unindexing
3675
+ // while the batch indexes remain live until the end.
3676
+ if (removedEdges.has(edge))
3677
+ continue;
3642
3678
  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)) {
3679
+ // Preserve importer edges to the stable file node id; the node is
3680
+ // re-added below.
3681
+ if (!fromRemoved && edge.to === fileNodeId)
3682
+ continue;
3683
+ if (!fromRemoved && removedSymbolIds.has(edge.to)) {
3649
3684
  preservedIncomingSymbolEdges.push({ ...edge });
3650
- droppedEdges.push(edge);
3651
- return false;
3652
3685
  }
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);
3686
+ removedEdges.add(edge);
3687
+ }
3665
3688
  for (const id of removedIds) {
3666
3689
  const node = graph.nodes.get(id);
3667
3690
  graph.nodes.delete(id);
@@ -3769,18 +3792,30 @@ async function addFileToGraph(graph, cwd, file, facts, ignoredIds, contentOverri
3769
3792
  /**
3770
3793
  * #2074 acceptance instrumentation. `restoreComparisons` counts edge-metadata
3771
3794
  * 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).
3795
+ * counts edges visited by `importTargetsForFile`; `removeOwnedEdgeVisits`
3796
+ * counts edges visited while collecting a changed file's owned edges in
3797
+ * `removeFileOwnedGraphData`. Before this change all three grew with the whole
3798
+ * graph on every one-file rebuild, and `removeOwnedEdgeVisits` grew with
3799
+ * changedFiles x graph on a multi-file batch (one full `graph.edges` scan per
3800
+ * file). `removeOwnedEdgePositions` counts adjacency positions examined while
3801
+ * removing edges; batching keeps a high-fan-in bucket linear instead of
3802
+ * rescanning its prefix for every edge. They are the count-based signal the
3803
+ * issue asks for, because wall time on this hardware is IO-noisy (#1920).
3776
3804
  */
3777
- const _rebuildCounters = { restoreComparisons: 0, importTargetEdgeScans: 0 };
3805
+ const _rebuildCounters = {
3806
+ restoreComparisons: 0,
3807
+ importTargetEdgeScans: 0,
3808
+ removeOwnedEdgeVisits: 0,
3809
+ removeOwnedEdgePositions: 0,
3810
+ };
3778
3811
  export function _getReviewGraphRebuildCountersForTests() {
3779
3812
  return { ..._rebuildCounters };
3780
3813
  }
3781
3814
  export function _resetReviewGraphRebuildCountersForTests() {
3782
3815
  _rebuildCounters.restoreComparisons = 0;
3783
3816
  _rebuildCounters.importTargetEdgeScans = 0;
3817
+ _rebuildCounters.removeOwnedEdgeVisits = 0;
3818
+ _rebuildCounters.removeOwnedEdgePositions = 0;
3784
3819
  }
3785
3820
  /**
3786
3821
  * Identity of an edge for dedupe purposes, WITHIN one target bucket: the array
@@ -3893,10 +3928,18 @@ async function updateGraphFiles(graph, cwd, files, facts, ignoredIds) {
3893
3928
  priorTargets: importTargetsForFile(graph, file),
3894
3929
  }));
3895
3930
  const preservedIncoming = [];
3931
+ // #2074: one shared drop set for the whole batch, compacted into graph.edges
3932
+ // exactly once below — not once per file, which made a multi-file rebuild
3933
+ // scan the edge array changedFiles times over.
3934
+ const removedEdges = new Set();
3896
3935
  for (const file of files) {
3897
- preservedIncoming.push(...removeFileOwnedGraphData(graph, file));
3936
+ preservedIncoming.push(...removeFileOwnedGraphData(graph, file, removedEdges));
3898
3937
  await addFileToGraph(graph, cwd, file, facts, ignoredIds);
3899
3938
  }
3939
+ if (removedEdges.size > 0) {
3940
+ unindexEdges(graph, removedEdges);
3941
+ graph.edges = graph.edges.filter((edge) => !removedEdges.has(edge));
3942
+ }
3900
3943
  restoreValidIncomingEdges(graph, preservedIncoming);
3901
3944
  resolveDeferredSymbolEdges(graph, false);
3902
3945
  graph.changedSymbolsByFile.clear();
@@ -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
  /**
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import * as fs from "node:fs";
8
8
  import * as path from "node:path";
9
+ import { hasCargoWorkspaceTable, readCargoDependencyNames, readCargoPackageName, readCargoWorkspaceMembers, } from "../cargo-manifest.js";
9
10
  import { getProjectIgnoreMatcher, isExcludedDirName, } from "../file-utils.js";
10
11
  import { normalizeMapKey } from "../path-utils.js";
11
12
  import { getWorkspaceManifestMarkers } from "../workspace-topology.js";
@@ -52,8 +53,12 @@ function detectWorkspaceType(cwd) {
52
53
  if (markers.hasCargoToml) {
53
54
  try {
54
55
  const content = fs.readFileSync(path.join(cwd, "Cargo.toml"), "utf-8");
55
- if (content.includes("[workspace]"))
56
+ // The shared presence check (#2498) — see its own doc comment in
57
+ // cargo-manifest.ts for why a bare `.includes`/regex reimplementation
58
+ // of this test is wrong (comment/sentinel handling).
59
+ if (hasCargoWorkspaceTable(content)) {
56
60
  return "cargo";
61
+ }
57
62
  }
58
63
  catch { }
59
64
  }
@@ -146,71 +151,6 @@ function extractYamlList(content, key) {
146
151
  }
147
152
  return values;
148
153
  }
149
- function extractTomlArray(content, key) {
150
- const prefix = `${key}`;
151
- let collecting = false;
152
- let buffer = "";
153
- for (const rawLine of content.split(/\r?\n/)) {
154
- const line = rawLine.split("#", 1)[0].trim();
155
- if (!line)
156
- continue;
157
- if (!collecting) {
158
- if (!line.startsWith(prefix))
159
- continue;
160
- const equalsIndex = line.indexOf("=");
161
- if (equalsIndex === -1 || line.slice(0, equalsIndex).trim() !== key)
162
- continue;
163
- const afterEquals = line.slice(equalsIndex + 1).trim();
164
- if (!afterEquals.startsWith("["))
165
- continue;
166
- collecting = true;
167
- buffer += afterEquals.slice(1);
168
- }
169
- else {
170
- buffer += `,${line}`;
171
- }
172
- const closeIndex = buffer.indexOf("]");
173
- if (closeIndex !== -1) {
174
- buffer = buffer.slice(0, closeIndex);
175
- break;
176
- }
177
- }
178
- return buffer
179
- .split(",")
180
- .map((s) => stripQuotes(s.trim()))
181
- .filter(Boolean);
182
- }
183
- function extractTomlSection(content, section) {
184
- const lines = [];
185
- let inSection = false;
186
- for (const rawLine of content.split(/\r?\n/)) {
187
- const trimmed = rawLine.trim();
188
- if (trimmed === `[${section}]`) {
189
- inSection = true;
190
- continue;
191
- }
192
- if (inSection && trimmed.startsWith("[") && trimmed.endsWith("]"))
193
- break;
194
- if (inSection)
195
- lines.push(rawLine);
196
- }
197
- return lines;
198
- }
199
- function extractTomlString(content, key) {
200
- for (const rawLine of content.split(/\r?\n/)) {
201
- const line = rawLine.split("#", 1)[0].trim();
202
- const equalsIndex = line.indexOf("=");
203
- if (equalsIndex === -1 || line.slice(0, equalsIndex).trim() !== key)
204
- continue;
205
- const value = line.slice(equalsIndex + 1).trim();
206
- const quote = value[0];
207
- if ((quote !== '"' && quote !== "'") || value.length < 2)
208
- return undefined;
209
- const endIndex = value.indexOf(quote, 1);
210
- return endIndex === -1 ? undefined : value.slice(1, endIndex);
211
- }
212
- return undefined;
213
- }
214
154
  function moduleFromPackageJson(cwd, pkgRoot) {
215
155
  const pkgJson = readJsonSafe(path.join(pkgRoot, "package.json"));
216
156
  if (!pkgJson?.name)
@@ -250,7 +190,7 @@ function scanCargoModules(cwd) {
250
190
  let members = [];
251
191
  try {
252
192
  const content = fs.readFileSync(path.join(cwd, "Cargo.toml"), "utf-8");
253
- members = extractTomlArray(content, "members");
193
+ members = readCargoWorkspaceMembers(content);
254
194
  }
255
195
  catch {
256
196
  return [];
@@ -260,15 +200,11 @@ function scanCargoModules(cwd) {
260
200
  const pkgRoot = path.resolve(cwd, member);
261
201
  const memberToml = path.join(pkgRoot, "Cargo.toml");
262
202
  let name = "";
263
- const deps = [];
203
+ let deps = [];
264
204
  try {
265
205
  const content = fs.readFileSync(memberToml, "utf-8");
266
- name = extractTomlString(content, "name") ?? "";
267
- for (const line of extractTomlSection(content, "dependencies")) {
268
- const depName = line.trim().match(/^([A-Za-z0-9_-]+)\s*=/);
269
- if (depName)
270
- deps.push(depName[1]);
271
- }
206
+ name = readCargoPackageName(content) ?? "";
207
+ deps = readCargoDependencyNames(content);
272
208
  }
273
209
  catch {
274
210
  continue;
@@ -1,11 +1,17 @@
1
1
  import * as path from "node:path";
2
2
  import { isTestMode } from "./env-utils.js";
3
- import { getGlobalPiLensDir } from "./file-utils.js";
3
+ import { getGlobalPiLensLogDir } from "./probe-home-state.js";
4
+ import { getMaxLogSizeMB } from "./log-cleanup.js";
4
5
  import { createNdjsonLogger } from "./ndjson-logger.js";
5
6
  import { normalizeFilePath } from "./path-utils.js";
6
- const REVIEW_GRAPH_LOG_DIR = getGlobalPiLensDir();
7
+ const REVIEW_GRAPH_LOG_DIR = getGlobalPiLensLogDir();
7
8
  const REVIEW_GRAPH_LOG_FILE = path.join(REVIEW_GRAPH_LOG_DIR, "review-graph.log");
8
- const writer = createNdjsonLogger({ filePath: REVIEW_GRAPH_LOG_FILE });
9
+ // Bounded like every other global sink (#2505) — see sessionstart-logger.
10
+ const writer = createNdjsonLogger({
11
+ filePath: REVIEW_GRAPH_LOG_FILE,
12
+ maxBytes: getMaxLogSizeMB() * 1024 * 1024,
13
+ backupPath: `${REVIEW_GRAPH_LOG_FILE}.1`,
14
+ });
9
15
  /**
10
16
  * Build the one canonical graph metadata shape used by build and persist logs.
11
17
  * Counts come from the graph instance; source-file count may use the exact