@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
@@ -48,6 +48,7 @@
48
48
  * event handlers.
49
49
  */
50
50
  import { createHash } from "node:crypto";
51
+ import { BoundedFifoMap, BoundedLruCache } from "./bounded-cache.js";
51
52
  import { lazyEnvNumber } from "./env-utils.js";
52
53
  import { logLatency } from "./latency-logger.js";
53
54
  import { compareOrdinal } from "./string-utils.js";
@@ -161,7 +162,11 @@ function newCacheUsageSummary() {
161
162
  unknownEvidenceReasons: emptyUnknownEvidenceReasons(),
162
163
  };
163
164
  }
164
- const attributionBySession = new Map();
165
+ // Shared cap for every per-session bounded map in this module (also used by
166
+ // `prefixHashBySession` below) — a long-lived process cycling through many
167
+ // sessions cannot grow any of them without limit.
168
+ export const MAX_TRACKED_SESSIONS = 32;
169
+ const attributionBySession = new BoundedLruCache(MAX_TRACKED_SESSIONS);
165
170
  function newAttributionState() {
166
171
  return {
167
172
  prefixBrokeSinceLastUsage: false,
@@ -183,14 +188,9 @@ function newAttributionState() {
183
188
  function attributionFor(key) {
184
189
  const existing = attributionBySession.get(key);
185
190
  const state = existing ?? newAttributionState();
186
- attributionBySession.delete(key);
191
+ // `get` above already moved an existing entry to MRU; `set` below also
192
+ // refreshes recency on a create, so every fetch counts as a touch.
187
193
  attributionBySession.set(key, state);
188
- while (attributionBySession.size > MAX_TRACKED_SESSIONS) {
189
- const oldest = attributionBySession.keys().next().value;
190
- if (oldest === undefined)
191
- break;
192
- attributionBySession.delete(oldest);
193
- }
194
194
  return state;
195
195
  }
196
196
  /**
@@ -999,11 +999,13 @@ function hashFirstMessage(first) {
999
999
  * session state (read guard #1041, widget #190) which IS rehydrated from the
1000
1000
  * sidecar.
1001
1001
  *
1002
- * Bounded as an insertion-ordered LRU (evict oldest-inserted past the cap) so a
1003
- * long-lived process cycling through many sessions can't grow this unbounded.
1002
+ * Bounded as insertion-ordered FIFO (evict oldest-inserted past the cap,
1003
+ * refreshed on every WRITE by {@link recordSessionHash}'s delete+set a
1004
+ * plain read never reorders) so a long-lived process cycling through many
1005
+ * sessions can't grow this unbounded. Shares {@link MAX_TRACKED_SESSIONS}
1006
+ * with `attributionBySession` above.
1004
1007
  */
1005
- const MAX_TRACKED_SESSIONS = 32;
1006
- const prefixHashBySession = new Map();
1008
+ const prefixHashBySession = new BoundedFifoMap(MAX_TRACKED_SESSIONS);
1007
1009
  /**
1008
1010
  * Reported key used when no session id is available (undefined/empty). Internal
1009
1011
  * attribution additionally separates primary and concurrent-secondary roles;
@@ -1019,12 +1021,6 @@ const NO_SESSION_KEY = "<no-session>";
1019
1021
  function recordSessionHash(key, hash) {
1020
1022
  prefixHashBySession.delete(key);
1021
1023
  prefixHashBySession.set(key, hash);
1022
- while (prefixHashBySession.size > MAX_TRACKED_SESSIONS) {
1023
- const oldest = prefixHashBySession.keys().next().value;
1024
- if (oldest === undefined)
1025
- break;
1026
- prefixHashBySession.delete(oldest);
1027
- }
1028
1024
  }
1029
1025
  /**
1030
1026
  * Part 2 — observe `messages[0]` stability turn-over-turn, keyed by session id.
@@ -1169,3 +1165,33 @@ export function resetCachePrefixObservation() {
1169
1165
  prefixHashBySession.clear();
1170
1166
  attributionBySession.clear();
1171
1167
  }
1168
+ /** #2442 test-only membership reads, bypassing emitCacheUsageSummaryAtSessionEnd's
1169
+ * usageRecords>0 gate and log side effects. */
1170
+ export function _attributionKeyForTests(sessionId, sessionRole) {
1171
+ return attributionKey(sessionId, sessionRole);
1172
+ }
1173
+ export function _attributionBySessionHasForTests(key) {
1174
+ return attributionBySession.has(key);
1175
+ }
1176
+ export function _prefixHashBySessionHasForTests(key) {
1177
+ return prefixHashBySession.has(key);
1178
+ }
1179
+ /** #2442 test-only: drive attributionFor's fetch-or-create touch directly
1180
+ * (every call refreshes LRU recency, matching production — see
1181
+ * attributionFor's own doc comment). */
1182
+ export function _touchAttributionForTests(key) {
1183
+ attributionFor(key);
1184
+ }
1185
+ /** #2442 test-only: drive recordSessionHash's write-refresh directly. */
1186
+ export function _recordSessionHashForTests(key, hash) {
1187
+ recordSessionHash(key, hash);
1188
+ }
1189
+ /**
1190
+ * #2442 test-only: the exact `prefixHashBySession.get(key)` that
1191
+ * `observeCachePrefix` performs to fetch a session's previous hash. A
1192
+ * `.has()` reorders nothing on either bounded class, so only a real `get`
1193
+ * can pin FIFO-vs-LRU behavior (#2442 review F4).
1194
+ */
1195
+ export function _prefixHashBySessionGetForTests(key) {
1196
+ return prefixHashBySession.get(key);
1197
+ }
@@ -0,0 +1,422 @@
1
+ /**
2
+ * Minimal Cargo.toml reading — table-section slicing plus string-array and
3
+ * scalar-string parsing.
4
+ *
5
+ * Single source of truth (AGENTS.md: "a hand-maintained list that mirrors a
6
+ * registry is a defect"; the same rule applies to a second regex TOML
7
+ * reader). `extractTomlTableSection`/`parseTomlStringArray` originated in
8
+ * `clients/lsp/server.ts`'s rust-analyzer workspace-root hoisting
9
+ * (#1671/#1693) and now live here so `clients/formatters.ts`'s rustfmt
10
+ * `--edition` resolution (#2466) reuses the exact same parser instead of a
11
+ * second hand-rolled one. `clients/lsp/server.ts` re-imports them from here.
12
+ *
13
+ * The ONE Cargo.toml reader in the tree (#2473): `clients/review-graph/
14
+ * workspace-modules.ts`'s `scanCargoModules`/`detectWorkspaceType` used to
15
+ * carry an independent regex TOML reader (`extractTomlArray`/
16
+ * `extractTomlSection`/`extractTomlString`) for module-graph construction — a
17
+ * third copy, folded onto `readCargoPackageName`/`readCargoWorkspaceMembers`/
18
+ * `readCargoDependencyNames` below. That fold also fixed a latent defect:
19
+ * the old `extractTomlString` was NOT table-scoped — it scanned the whole
20
+ * file for the first `key = "value"` line regardless of which table it fell
21
+ * under, so a member manifest with a `name` key under an EARLIER non-package
22
+ * table (a `[[bin]] name = "..."` or `[package.metadata.*]` block preceding
23
+ * `[package]`) silently returned the wrong crate name. `readCargoPackageName`
24
+ * is table-scoped via `extractTomlTableSection` like every other reader here.
25
+ *
26
+ * Review round 2 (PR #2480) hardened the fold itself: `extractTomlTableSection`
27
+ * and `parseTomlStringArray` now run everything through {@link normalizeToml}
28
+ * (CRLF→LF, plus a per-line `#`-to-EOL comment strip that respects quoted
29
+ * strings) before any regex runs, and both the table heading and its
30
+ * terminator are anchored with `[ \t]*` rather than bare `^` so an indented
31
+ * heading or sub-table is read the same way the pre-fold, per-line `.trim()`d
32
+ * reader read it. `readCargoWorkspaceMembers` also gained `[workspace]`
33
+ * table-scoping and a sibling `readCargoWorkspaceExclude`, both now reused by
34
+ * `clients/lsp/server.ts` instead of that file hand-composing the same two
35
+ * primitives itself.
36
+ *
37
+ * #2480's fold missed a THIRD `[workspace]` presence check: `clients/lsp/
38
+ * server.ts`'s `RustWorkspaceRoot` walk-up carried its own
39
+ * `/^\s*\[workspace\]/m.test(...)`, four lines above the call it had already
40
+ * folded onto this file's readers. {@link hasCargoWorkspaceTable} closes that
41
+ * gap (#2498) — the one presence check every caller (that walk-up, and both
42
+ * of {@link resolveCargoPackageEdition}'s own checks below) now shares.
43
+ */
44
+ import { readFile } from "node:fs/promises";
45
+ import * as os from "node:os";
46
+ import * as path from "node:path";
47
+ import { logExtension } from "./extension-log.js";
48
+ import { findNearestMarkerRoot, isAtOrAboveHomeDir } from "./path-utils.js";
49
+ export async function readTextFileOrUndefined(filePath) {
50
+ try {
51
+ return await readFile(filePath, "utf-8");
52
+ }
53
+ catch {
54
+ return undefined;
55
+ }
56
+ }
57
+ /**
58
+ * Strip a `#`-to-end-of-line TOML comment from one line, leaving a `#`
59
+ * character INSIDE a single- or double-quoted string alone (a version spec or
60
+ * path containing a literal `#` is unusual but legal TOML). A whole-line
61
+ * comment (the line's first non-whitespace character is `#`) reduces to "".
62
+ */
63
+ function stripTomlLineComment(line) {
64
+ let quote = null;
65
+ for (let i = 0; i < line.length; i++) {
66
+ const ch = line[i];
67
+ if (quote) {
68
+ if (ch === quote)
69
+ quote = null;
70
+ continue;
71
+ }
72
+ if (ch === '"' || ch === "'") {
73
+ quote = ch;
74
+ continue;
75
+ }
76
+ if (ch === "#")
77
+ return line.slice(0, i);
78
+ }
79
+ return line;
80
+ }
81
+ /**
82
+ * Normalize CRLF to LF and strip `#`-to-EOL comments line by line. Every
83
+ * regex-based reader below runs on the result.
84
+ *
85
+ * The CRLF→LF pass is DEFENSIVE, not the fix for a real match failure —
86
+ * review round 2, F2 originally claimed a CRLF manifest's `\r` broke the
87
+ * heading/terminator `$` anchor; it does not, because ECMAScript's
88
+ * multiline `$`/`^` treat a bare `\r` as a line terminator on its own (same
89
+ * as `\n`), so the `adv-e-crlf` fixture passes this suite even with this
90
+ * `.replace(/\r\n/g, "\n")` call removed (review round 3, F2 — verified by
91
+ * mutation: dropping the line left all tests green). It stays as
92
+ * belt-and-braces normalization — a single LF-only code path for every
93
+ * downstream regex is simpler to reason about than "also correct on `\r\n`
94
+ * by an ECMAScript technicality" — and because `.split("\n")` below would
95
+ * otherwise leave a trailing `\r` on each line for {@link stripTomlLineComment}
96
+ * to walk past.
97
+ *
98
+ * The comment-strip pass IS load-bearing (review round 2, F1 — the pre-fold
99
+ * `extractTomlArray` stripped comments per line before collecting; the
100
+ * fold's single multi-line regex scan dropped that pass, so a commented-out
101
+ * `# "member",` line stayed live because nothing removed it before the
102
+ * quoted-string scan ran over the whole bracketed span). A commented-out
103
+ * entry must never survive into a captured table/array body.
104
+ *
105
+ * A leading UTF-8 BOM (`\uFEFF`) is stripped first — valid at the start of a
106
+ * Cargo.toml (rust-lang/cargo#2031) — because it sits OUTSIDE every reader's
107
+ * heading/key anchor below (`[ \t]*`, not `\s*`) and would otherwise hide a
108
+ * table or key on the file's very first line. #2498's `RustWorkspaceRoot`
109
+ * walk-up used to carry its own `/^\s*\[workspace\]/m` check, and ES's `\s`
110
+ * class DOES include `\uFEFF` — so that hand-rolled regex accepted a
111
+ * BOM-prefixed manifest by accident; folding it onto this shared reader
112
+ * without this strip would have silently regressed that shape (review round
113
+ * 2, F4).
114
+ */
115
+ function normalizeToml(content) {
116
+ return content
117
+ .replace(/^\uFEFF/, "")
118
+ .replace(/\r\n/g, "\n")
119
+ .split("\n")
120
+ .map(stripTomlLineComment)
121
+ .join("\n");
122
+ }
123
+ /**
124
+ * Slice out ONE top-level TOML table's raw body — from its `[name]` heading
125
+ * to the next top-level `[...]`/`[[...]]` heading or EOF. `members`/`exclude`
126
+ * must be read from the `[workspace]` table specifically: `[package]` has its
127
+ * OWN `exclude` key (the standard cargo-publish exclude list, conventionally
128
+ * written above `[workspace]` in a virtual-manifest-less root crate), and a
129
+ * whole-file regex would misread it as workspace membership (#1671 F4).
130
+ *
131
+ * Both the heading and the terminating next-heading are anchored with
132
+ * `[ \t]*` rather than bare `^` — TOML does not require a table heading to
133
+ * start in column 0, and the pre-fold reader (which `.trim()`ed each line
134
+ * before comparing) accepted an indented ` [package]` or ` [dependencies.
135
+ * tokio]` sub-table. The column-0-only anchor this fold shipped with missed
136
+ * both: an indented `[package]` heading never matched at all (the table read
137
+ * as absent), and an indented sub-table heading never terminated the parent
138
+ * slice (its keys leaked into the parent table's body) — review round 2, F2.
139
+ *
140
+ * Returns `undefined` when the table is ABSENT and `""` when the table IS
141
+ * present but has an empty body (its heading is immediately followed by EOF
142
+ * or the next heading, e.g. `[workspace]` as the last line of the file with
143
+ * no trailing newline, or `[workspace] `/`[workspace] # root` at EOF) —
144
+ * review round 3, F1. Both used to collapse to the same `""` sentinel, so a
145
+ * caller checking `!== ""` to mean "table present" silently misread a
146
+ * present-but-empty table as absent. Callers that only care about the table's
147
+ * CONTENT (feeding the result to {@link parseTomlStringArray}/
148
+ * {@link parseTomlScalarString}, which both already treat `undefined` as "no
149
+ * match") are unaffected; callers that check PRESENCE must compare against
150
+ * `undefined`, not `""`.
151
+ */
152
+ export function extractTomlTableSection(content, tableName) {
153
+ const normalized = normalizeToml(content);
154
+ const heading = new RegExp(`^[ \\t]*\\[${tableName}\\][ \\t]*(?:#.*)?$`, "m");
155
+ const match = heading.exec(normalized);
156
+ if (!match)
157
+ return undefined;
158
+ const rest = normalized.slice(match.index + match[0].length);
159
+ const nextHeading = rest.match(/^[ \t]*\[{1,2}[^\]]+\]{1,2}[ \t]*(?:#.*)?$/m);
160
+ return nextHeading?.index !== undefined
161
+ ? rest.slice(0, nextHeading.index)
162
+ : rest;
163
+ }
164
+ export function parseTomlStringArray(content, key) {
165
+ if (content === undefined)
166
+ return [];
167
+ const normalized = normalizeToml(content);
168
+ const match = normalized.match(new RegExp(`^[ \\t]*${key}[ \\t]*=[ \\t]*\\[([\\s\\S]*?)\\]`, "m"));
169
+ if (!match)
170
+ return [];
171
+ return [...match[1].matchAll(/"([^"]*)"|'([^']*)'/g)].map((m) => (m[1] ?? m[2] ?? "").trim());
172
+ }
173
+ /**
174
+ * Read a scalar `key = "value"` / `key = 'value'` line out of a TOML table
175
+ * section. Anchored to line-start so `swift-edition = "..."` never matches a
176
+ * `key` of `edition`, and a dotted-key inheritance line (`edition.workspace =
177
+ * true`) never matches either — the literal key is immediately followed by
178
+ * `=`, not `.`.
179
+ */
180
+ export function parseTomlScalarString(content, key) {
181
+ if (content === undefined)
182
+ return undefined;
183
+ const match = content.match(new RegExp(`^[ \\t]*${key}[ \\t]*=[ \\t]*(?:"([^"]*)"|'([^']*)')`, "m"));
184
+ if (!match)
185
+ return undefined;
186
+ return match[1] ?? match[2];
187
+ }
188
+ /**
189
+ * True when `content` declares a `[workspace]` table — table-scoped and
190
+ * comment/indentation/CRLF-tolerant like every other reader here, because it
191
+ * IS `extractTomlTableSection` underneath. Presence is `!== undefined`, not a
192
+ * truthiness check on the returned string: an empty `[workspace]` table (a
193
+ * heading with no keys) returns `""`, which reads as falsy but is a valid
194
+ * "table present" answer (`extractTomlTableSection`'s own doc comment covers
195
+ * this — review round 3, F1).
196
+ *
197
+ * Exists so a caller that only needs the yes/no answer does not hand-roll a
198
+ * `\[workspace\]` regex to get it — that regex, four lines above
199
+ * `cargoWorkspaceDeclaresMember` in `clients/lsp/server.ts`, was behaviorally
200
+ * identical to this call under every probed shape (comments, indentation,
201
+ * CRLF, trailing content) but sat outside the one Cargo.toml reader #2473
202
+ * consolidated the rest of this file's callers onto (#2498).
203
+ */
204
+ export function hasCargoWorkspaceTable(content) {
205
+ return hasTomlTable(content, "workspace");
206
+ }
207
+ /**
208
+ * True when `content` declares the table `tableName` — the yes/no half of
209
+ * {@link extractTomlTableSection}, for readers that only need presence.
210
+ * `tableName` is a regex SOURCE (it is interpolated into the heading pattern),
211
+ * so a dotted table escapes its dots: `"tool\\.uv\\.workspace"`.
212
+ *
213
+ * Exists so a second file does not re-derive the `!== undefined` comparison
214
+ * that {@link hasCargoWorkspaceTable} exists to hold: `clients/
215
+ * python-environment.ts`'s uv-workspace discovery carried a private
216
+ * `hasUvWorkspaceTable` that was this function's body with a different table
217
+ * name (review round 2, F5). The `!== undefined` (not a truthy test) is the
218
+ * load-bearing part both callers share: an empty table — a heading with no
219
+ * keys — extracts as `""`, which is falsy but IS "table present".
220
+ */
221
+ export function hasTomlTable(content, tableName) {
222
+ return extractTomlTableSection(content, tableName) !== undefined;
223
+ }
224
+ /**
225
+ * Read a crate's `[package] name`, table-scoped to the `[package]` section
226
+ * specifically (#2473) — NOT the first `name = "..."` line in the file. A
227
+ * member manifest commonly has other tables with their own `name` key
228
+ * (`[[bin]] name = "..."`, `[package.metadata.*]` blocks); reading unscoped
229
+ * silently returns whichever happens to sit first in the file.
230
+ */
231
+ export function readCargoPackageName(content) {
232
+ const packageSection = extractTomlTableSection(content, "package");
233
+ return parseTomlScalarString(packageSection, "name");
234
+ }
235
+ /**
236
+ * Read `members` off a (typically root/virtual) Cargo.toml for workspace
237
+ * expansion, table-scoped to `[workspace]` (review round 2, F4). A whole-file
238
+ * `members` regex silently also matches `[workspace.metadata.*] members =
239
+ * [...]` or any other table happening to declare a same-named key — the
240
+ * exact "unscoped read of a same-named key under an unrelated table" defect
241
+ * shape `readCargoPackageName` above was already fixed for (#2473), except
242
+ * this reader shipped the fold WITHOUT that scoping and called it out
243
+ * explicitly as intentional in the comment this replaces. It also duplicated
244
+ * `clients/lsp/server.ts`'s independent, already-correctly-scoped
245
+ * `extractTomlTableSection(content, "workspace")` + `parseTomlStringArray`
246
+ * hand-composition for its own `members`/`exclude` read — the SAME
247
+ * single-source-of-truth violation #2473 was filed to close for the OTHER
248
+ * two Cargo.toml readers. `clients/lsp/server.ts` now calls this function
249
+ * (and {@link readCargoWorkspaceExclude} below) instead of re-composing the
250
+ * two primitives itself.
251
+ */
252
+ export function readCargoWorkspaceMembers(content) {
253
+ return parseTomlStringArray(extractTomlTableSection(content, "workspace"), "members");
254
+ }
255
+ /**
256
+ * Read `exclude` off a (typically root/virtual) Cargo.toml, table-scoped to
257
+ * `[workspace]` — `[package]` has its OWN `exclude` key (the cargo-publish
258
+ * exclude list; see {@link extractTomlTableSection}'s doc comment), so an
259
+ * unscoped read would misread it as workspace exclusion (#1671 F4). Companion
260
+ * to {@link readCargoWorkspaceMembers}; both replace `clients/lsp/server.ts`'s
261
+ * former hand-composed `extractTomlTableSection` + `parseTomlStringArray`
262
+ * pair (review round 2, F4).
263
+ */
264
+ export function readCargoWorkspaceExclude(content) {
265
+ return parseTomlStringArray(extractTomlTableSection(content, "workspace"), "exclude");
266
+ }
267
+ /**
268
+ * List the dependency names declared directly under `[dependencies]` (key
269
+ * only — not the version/spec value, which may be a bare string, an inline
270
+ * table, or workspace-inherited).
271
+ */
272
+ export function readCargoDependencyNames(content) {
273
+ const section = extractTomlTableSection(content, "dependencies");
274
+ if (section === undefined)
275
+ return [];
276
+ const names = [];
277
+ // `section` is already comment-stripped by `extractTomlTableSection`'s
278
+ // `normalizeToml` pass (quote-aware). A second, non-quote-aware
279
+ // `line.split("#", 1)[0]` re-strip here was redundant AND could wrongly
280
+ // truncate a line whose value legitimately contains a `#` inside a quoted
281
+ // string — deleted (review round 3, F4).
282
+ for (const rawLine of section.split(/\r?\n/)) {
283
+ const line = rawLine.trim();
284
+ const match = line.match(/^([A-Za-z0-9_-]+)\s*=/);
285
+ if (match)
286
+ names.push(match[1]);
287
+ }
288
+ return names;
289
+ }
290
+ /**
291
+ * True when `key` is declared as workspace-inherited: the dotted-key form
292
+ * (`edition.workspace = true`) or the inline-table form
293
+ * (`edition = { workspace = true }`).
294
+ */
295
+ export function isTomlKeyWorkspaceInherited(content, key) {
296
+ if (content === undefined)
297
+ return false;
298
+ const dotted = new RegExp(`^[ \\t]*${key}\\.workspace[ \\t]*=[ \\t]*true`, "m");
299
+ const inline = new RegExp(`^[ \\t]*${key}[ \\t]*=[ \\t]*\\{[^}\\n]*\\bworkspace[ \\t]*=[ \\t]*true`, "m");
300
+ return dotted.test(content) || inline.test(content);
301
+ }
302
+ /**
303
+ * rustfmt's `--edition` is a closed enum — 2015/2018/2021/2024 as of rustfmt
304
+ * itself, NOT any four-digit string. A manifest typo (`edition = "2019"`) or
305
+ * an edition newer than the installed rustfmt understands would otherwise be
306
+ * passed straight through `--edition`, and rustfmt rejects an edition it
307
+ * doesn't recognize outright — turning EVERY `.rs` format into a hard
308
+ * `outcome: "failed"` where the pre-#2466 bare command formatted fine (#2466
309
+ * review round 2, F2). When Rust stabilizes a new edition, append it to this
310
+ * set — rustfmt's own enum is the forcing function for this list, not a
311
+ * pattern match on digit count.
312
+ */
313
+ const SUPPORTED_RUSTFMT_EDITIONS = new Set(["2015", "2018", "2021", "2024"]);
314
+ /**
315
+ * Validate a manifest-read edition value against rustfmt's actual enum
316
+ * before letting a caller pass it through `--edition`. A defined-but-invalid
317
+ * value (as opposed to "no edition found at all") is a config anomaly worth
318
+ * a debug trail, not a silent swap to `undefined` — logs the rejected value
319
+ * so a future report of "rustfmt still not carrying edition X" has the exact
320
+ * string that got refused.
321
+ */
322
+ function validatedEdition(value, filePath) {
323
+ if (value === undefined)
324
+ return undefined;
325
+ if (SUPPORTED_RUSTFMT_EDITIONS.has(value))
326
+ return value;
327
+ logExtension({
328
+ subsystem: "format",
329
+ message: "resolveCargoPackageEdition: manifest edition is not a rustfmt-supported value; falling back to the static rustfmt command",
330
+ level: "debug",
331
+ metadata: { rejectedEdition: value, filePath },
332
+ });
333
+ return undefined;
334
+ }
335
+ /** Read `[workspace.package] edition` out of a manifest that declares it. */
336
+ function readWorkspacePackageEdition(content) {
337
+ const workspaceSection = extractTomlTableSection(content, "workspace.package");
338
+ return parseTomlScalarString(workspaceSection, "edition");
339
+ }
340
+ /**
341
+ * Resolve the four-digit `edition` for the Cargo package that owns
342
+ * `filePath`, so a formatter/build tool that needs it doesn't default to an
343
+ * older edition than the source actually uses (#2466 — rustfmt silently
344
+ * rejecting valid Rust 2024 syntax under an older default edition).
345
+ *
346
+ * - Finds the nearest readable `Cargo.toml` from `filePath` via the shared
347
+ * `findNearestMarkerRoot` walker (home-ceiling guarded, depth-capped —
348
+ * AGENTS.md walk-confinement; never a private walk-up loop).
349
+ * - Reads `[package] edition` directly when present.
350
+ * - When the package declares `edition.workspace = true` (or the inline-table
351
+ * equivalent):
352
+ * - First checks whether the package's OWN manifest also declares
353
+ * `[workspace]` — a root crate can be its own workspace root (`[package]`
354
+ * + `[workspace]` + `[workspace.package]` all in one file, a documented,
355
+ * common Cargo shape) — before climbing past it (#2466 review round 2,
356
+ * F1).
357
+ * - Otherwise climbs ancestors — same home-ceiling guard — for the nearest
358
+ * one that DECLARES `[workspace]` (Cargo's own workspace-root rule, not
359
+ * merely the nearest ancestor Cargo.toml: an intermediate manifest for an
360
+ * unrelated plain package is skipped, not treated as the answer — #2466
361
+ * review round 2, F1), then reads that root's `[workspace.package]
362
+ * edition`.
363
+ * - Every edition value (direct or inherited) is checked against rustfmt's
364
+ * actual enum (`SUPPORTED_RUSTFMT_EDITIONS`), not just "looks like four
365
+ * digits" (#2466 review round 2, F2).
366
+ * - Returns `undefined` on any miss (unreadable manifest, no `[package]`
367
+ * table, unsupported/invalid value, unresolved inheritance): callers fall
368
+ * back to their pre-existing default argv rather than guessing.
369
+ *
370
+ * `homeDir` defaults to `os.homedir()` and exists as a parameter so tests can
371
+ * inject a nearer ceiling and prove the guard actually stops a climb (#2466
372
+ * review round 2, F5) — production callers never pass it.
373
+ */
374
+ export async function resolveCargoPackageEdition(filePath, homeDir = os.homedir()) {
375
+ const startDir = path.dirname(path.resolve(filePath));
376
+ const packageDir = findNearestMarkerRoot(startDir, ["Cargo.toml"], {
377
+ homeDir,
378
+ });
379
+ if (!packageDir)
380
+ return undefined;
381
+ const packageContent = await readTextFileOrUndefined(path.join(packageDir, "Cargo.toml"));
382
+ if (packageContent === undefined)
383
+ return undefined;
384
+ const packageSection = extractTomlTableSection(packageContent, "package");
385
+ const direct = parseTomlScalarString(packageSection, "edition");
386
+ if (direct !== undefined)
387
+ return validatedEdition(direct, filePath);
388
+ if (!isTomlKeyWorkspaceInherited(packageSection, "edition"))
389
+ return undefined;
390
+ // The package's own manifest may ALSO be the workspace root — check it
391
+ // before climbing so this common shape doesn't fall through to searching
392
+ // ancestors for a `[workspace.package]` that's actually right here. Table
393
+ // PRESENCE is `!== undefined`, not `!== ""` (review round 3, F1): an
394
+ // empty `[workspace]` table (heading with no keys, e.g. the last line of
395
+ // the file with no trailing newline) reads as `""`, the SAME value
396
+ // `extractTomlTableSection` returns for "table absent" — a `!== ""` check
397
+ // would misread this common non-virtual-workspace-root shape as "no
398
+ // [workspace] here" and wrongly climb past it.
399
+ if (hasCargoWorkspaceTable(packageContent)) {
400
+ return validatedEdition(readWorkspacePackageEdition(packageContent), filePath);
401
+ }
402
+ let current = path.dirname(packageDir);
403
+ for (let depth = 0; depth < 64; depth++) {
404
+ if (isAtOrAboveHomeDir(current, homeDir))
405
+ return undefined;
406
+ const ancestorContent = await readTextFileOrUndefined(path.join(current, "Cargo.toml"));
407
+ // Cargo's own rule: the workspace root is the nearest ancestor
408
+ // Cargo.toml that DECLARES `[workspace]`. An intermediate manifest for
409
+ // an unrelated package (no `[workspace]` table) is not it — keep
410
+ // climbing past it instead of stopping here. `!== undefined`, not
411
+ // `!== ""` — see the same-shaped check above (review round 3, F1).
412
+ if (ancestorContent !== undefined &&
413
+ hasCargoWorkspaceTable(ancestorContent)) {
414
+ return validatedEdition(readWorkspacePackageEdition(ancestorContent), filePath);
415
+ }
416
+ const parent = path.dirname(current);
417
+ if (parent === current)
418
+ return undefined;
419
+ current = parent;
420
+ }
421
+ return undefined;
422
+ }
@@ -1,10 +1,10 @@
1
1
  import * as path from "node:path";
2
2
  import { isTestMode } from "./env-utils.js";
3
- import { getGlobalPiLensDir } from "./file-utils.js";
3
+ import { getGlobalPiLensLogDir } from "./probe-home-state.js";
4
4
  import { createNdjsonLogger } from "./ndjson-logger.js";
5
5
  import { getMaxLogSizeMB } from "./log-cleanup.js";
6
6
  import { normalizeLoggedPath } from "./path-utils.js";
7
- const CASCADE_LOG_DIR = getGlobalPiLensDir();
7
+ const CASCADE_LOG_DIR = getGlobalPiLensLogDir();
8
8
  const CASCADE_LOG_FILE = path.join(CASCADE_LOG_DIR, "cascade.log");
9
9
  const writer = createNdjsonLogger({
10
10
  filePath: CASCADE_LOG_FILE,
@@ -1,7 +1,7 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { toRunnerDisplayPath } from "./dispatch/runner-context.js";
4
- import { getProjectDataDir } from "./file-utils.js";
4
+ import { displayProjectDataPath, getProjectDataDir } from "./file-utils.js";
5
5
  import { normalizeMessage, stableFindingId } from "./finding-identity.js";
6
6
  import { normalizeMapKey } from "./path-utils.js";
7
7
  /**
@@ -232,7 +232,14 @@ export function appendCodeQualityWarningsHistory(cwd, report) {
232
232
  // Non-fatal — history write failure should never surface to the agent.
233
233
  }
234
234
  }
235
- export function formatCodeQualityWarningsAdvisory(report) {
235
+ /**
236
+ * The turn-end advisory for this report.
237
+ *
238
+ * `cwd` is REQUIRED for the same reason as
239
+ * {@link formatActionableWarningsAdvisory} (#2521): this advisory carried the
240
+ * identical hardcoded-legacy-path defect, one cache key over.
241
+ */
242
+ export function formatCodeQualityWarningsAdvisory(report, cwd) {
236
243
  if (report.summary.warnings === 0)
237
244
  return undefined;
238
245
  const topRules = report.summary.topRules
@@ -260,8 +267,11 @@ export function formatCodeQualityWarningsAdvisory(report) {
260
267
  ? `By tier: ${tiers}. Hint and info are style opinions, not defects.`
261
268
  : undefined,
262
269
  topRules ? `Top rules: ${topRules}` : undefined,
263
- "Details written to .pi-lens/cache/code-quality-warnings.json",
270
+ // #2521: tool route first (`mode=delta` covers the code-quality cache
271
+ // as well as the actionable one), resolved path second.
272
+ "Use lens_diagnostics with mode=delta to inspect these warnings.",
264
273
  "No action required unless you are already refactoring these areas.",
274
+ `Raw report (only if you need the JSON): ${displayProjectDataPath(cwd, "cache", "code-quality-warnings.json")}`,
265
275
  ]
266
276
  .filter(Boolean)
267
277
  .join("\n");
@@ -13,7 +13,7 @@
13
13
  import { createSubsystemLogger } from "./extension-log.js";
14
14
  import * as fs from "node:fs";
15
15
  import * as path from "node:path";
16
- import { firstChildOfType, withTreeSitterRoot, resolveTreeSitterLanguage, walk, } from "./tree-sitter-shared.js";
16
+ import { firstChildOfType, withTreeSitterRoot, isComplexityLanguageId, isComplexitySupportedFile, resolveTreeSitterLanguage, walk, } from "./tree-sitter-shared.js";
17
17
  // JS/TS/JSX share one grammar shape.
18
18
  const JSTS = {
19
19
  functionLike: new Set([
@@ -164,6 +164,12 @@ const RUST = {
164
164
  tryNode: undefined, // Rust uses Result/? — no try/catch
165
165
  nameChildTypes: ["identifier"],
166
166
  };
167
+ /**
168
+ * Node configuration per grammar. Keyed by {@link ComplexityLanguageId}, whose
169
+ * list lives in `tree-sitter-shared.ts` so `isComplexitySupportedFile` can
170
+ * answer without loading this module (#2467). `Record` over a finite union
171
+ * requires EXACTLY these keys, so the two cannot drift.
172
+ */
167
173
  const LANGUAGE_NODES = {
168
174
  typescript: JSTS,
169
175
  tsx: JSTS,
@@ -373,16 +379,21 @@ export class ComplexityClient {
373
379
  constructor(verbose = false) {
374
380
  this.log = verbose ? createSubsystemLogger("complexity") : () => { };
375
381
  }
376
- /** True if the file's grammar has a complexity node mapping. */
382
+ /** True if the file's grammar has a complexity node mapping.
383
+ *
384
+ * Delegates to the module-free predicate in `tree-sitter-shared.ts`: the
385
+ * same question is asked by `handleToolCallImpl` BEFORE the analyzer
386
+ * bootstrap is loaded (#2467), and two copies of it would be two answers. */
377
387
  isSupportedFile(filePath) {
378
- const languageId = resolveTreeSitterLanguage(filePath);
379
- return Boolean(languageId && languageId in LANGUAGE_NODES);
388
+ return isComplexitySupportedFile(filePath);
380
389
  }
381
390
  /** Analyze complexity metrics for a file (null if unsupported / unparseable). */
382
391
  async analyzeFile(filePath) {
383
392
  const absolutePath = path.resolve(filePath);
384
393
  const languageId = resolveTreeSitterLanguage(absolutePath);
385
- const nodes = languageId ? LANGUAGE_NODES[languageId] : undefined;
394
+ const nodes = languageId && isComplexityLanguageId(languageId)
395
+ ? LANGUAGE_NODES[languageId]
396
+ : undefined;
386
397
  if (!nodes)
387
398
  return null;
388
399
  let content;