@herbertgao/pi-extensions 2026.9.0 → 2026.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (360) hide show
  1. package/node_modules/@czottmann/pi-automode/CHANGELOG.md +13 -1
  2. package/node_modules/@czottmann/pi-automode/README.md +20 -0
  3. package/node_modules/@czottmann/pi-automode/docs/GLOSSARY.md +1 -1
  4. package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +2 -1
  5. package/node_modules/@czottmann/pi-automode/docs/defaults.md +3 -2
  6. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +38 -2
  7. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +5 -3
  8. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +11 -3
  9. package/node_modules/@czottmann/pi-automode/package.json +1 -1
  10. package/node_modules/@narumitw/pi-btw/README.md +57 -141
  11. package/node_modules/@narumitw/pi-btw/dist/index.ts +466 -41
  12. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +4 -4
  13. package/node_modules/@narumitw/pi-btw/docs/workflows.md +74 -0
  14. package/node_modules/@narumitw/pi-btw/package.json +2 -1
  15. package/node_modules/@narumitw/pi-btw/src/btw.ts +15 -1
  16. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +32 -2
  17. package/node_modules/@narumitw/pi-btw/src/keybindings.ts +330 -0
  18. package/node_modules/@narumitw/pi-btw/src/menu.ts +136 -19
  19. package/node_modules/@narumitw/pi-btw/src/settings.ts +32 -0
  20. package/node_modules/@narumitw/pi-btw/src/side-thread.ts +57 -5
  21. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +52 -30
  22. package/node_modules/@pi-plugins/fast-mode/README.md +5 -3
  23. package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts.map +1 -1
  24. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +20 -55
  25. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
  26. package/node_modules/@pi-plugins/fast-mode/package.json +1 -1
  27. package/node_modules/pi-lens/CHANGELOG.md +1072 -0
  28. package/node_modules/pi-lens/README.md +3 -0
  29. package/node_modules/pi-lens/config/biome/core.jsonc +11 -2
  30. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +13 -1
  31. package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +2 -9
  32. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +927 -80
  33. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +1 -1
  34. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +13 -4
  35. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +123 -3
  36. package/node_modules/pi-lens/dist/clients/ast-grep-rule-manager.js +60 -5
  37. package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +2 -9
  38. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +2 -0
  39. package/node_modules/pi-lens/dist/clients/bash-file-access.js +133 -3
  40. package/node_modules/pi-lens/dist/clients/biome-client.js +9 -2
  41. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +14 -6
  42. package/node_modules/pi-lens/dist/clients/bootstrap.js +509 -73
  43. package/node_modules/pi-lens/dist/clients/bounded-cache.js +152 -12
  44. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +1 -1
  45. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +59 -9
  46. package/node_modules/pi-lens/dist/clients/bundled-resource-health.js +113 -0
  47. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +3 -11
  48. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +31 -4
  49. package/node_modules/pi-lens/dist/clients/cache-manager.js +105 -6
  50. package/node_modules/pi-lens/dist/clients/cache-observability.js +40 -19
  51. package/node_modules/pi-lens/dist/clients/cargo-manifest.js +422 -0
  52. package/node_modules/pi-lens/dist/clients/cascade-logger.js +2 -9
  53. package/node_modules/pi-lens/dist/clients/child-unref.js +1 -19
  54. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +13 -3
  55. package/node_modules/pi-lens/dist/clients/complexity-client.js +16 -5
  56. package/node_modules/pi-lens/dist/clients/config-core/deny.js +221 -0
  57. package/node_modules/pi-lens/dist/clients/config-core/index.js +50 -0
  58. package/node_modules/pi-lens/dist/clients/config-core/merge.js +357 -0
  59. package/node_modules/pi-lens/dist/clients/config-core/normalize.js +340 -0
  60. package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +248 -0
  61. package/node_modules/pi-lens/dist/clients/config-core/provenance.js +164 -0
  62. package/node_modules/pi-lens/dist/clients/config-core/records.js +218 -0
  63. package/node_modules/pi-lens/dist/clients/config-core/resolve.js +125 -0
  64. package/node_modules/pi-lens/dist/clients/config-core/safe-object.js +78 -0
  65. package/node_modules/pi-lens/dist/clients/config-core/schema.js +165 -0
  66. package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +281 -0
  67. package/node_modules/pi-lens/dist/clients/config-locations.js +160 -0
  68. package/node_modules/pi-lens/dist/clients/config-resolve.js +789 -0
  69. package/node_modules/pi-lens/dist/clients/config-schema.js +234 -0
  70. package/node_modules/pi-lens/dist/clients/config-warn.js +407 -0
  71. package/node_modules/pi-lens/dist/clients/dead-code-client.js +19 -14
  72. package/node_modules/pi-lens/dist/clients/dead-code-logger.js +2 -6
  73. package/node_modules/pi-lens/dist/clients/deadline-utils.js +178 -0
  74. package/node_modules/pi-lens/dist/clients/debug-handles.js +2 -2
  75. package/node_modules/pi-lens/dist/clients/debug-heap.js +3 -3
  76. package/node_modules/pi-lens/dist/clients/deferred-lsp-work.js +106 -0
  77. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +113 -7
  78. package/node_modules/pi-lens/dist/clients/dependency-checker.js +8 -19
  79. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +1 -1
  80. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +12 -9
  81. package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +15 -5
  82. package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +0 -4
  83. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +162 -44
  84. package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +97 -0
  85. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +27 -8
  86. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +0 -6
  87. package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +1 -1
  88. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +0 -3
  89. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-complexity.js +2 -2
  90. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-fan-out.js +1 -1
  91. package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +0 -1
  92. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +383 -27
  93. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +0 -1
  94. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +4 -1
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +0 -1
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +0 -1
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +0 -1
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +0 -1
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +0 -1
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +0 -1
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +0 -1
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/fact-rules.js +0 -1
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +0 -1
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +0 -1
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +1 -3
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +0 -1
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +0 -1
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +0 -1
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +1 -2
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +0 -1
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +0 -1
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +0 -1
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +0 -1
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +0 -1
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +0 -1
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +14 -18
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +0 -1
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +0 -1
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +0 -1
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +6 -3
  121. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +7 -4
  122. package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +0 -1
  123. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +1 -2
  124. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +1 -3
  125. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +1 -2
  126. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +0 -1
  127. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +4 -3
  128. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +0 -1
  129. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +0 -1
  130. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +0 -1
  131. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +0 -1
  132. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +0 -1
  133. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +0 -1
  134. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +0 -1
  135. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +0 -1
  136. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +0 -1
  137. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +3 -0
  138. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +3 -67
  139. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +194 -80
  140. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +84 -15
  141. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils.js +0 -21
  142. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +0 -1
  143. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +2 -20
  144. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +1 -1
  145. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +0 -1
  146. package/node_modules/pi-lens/dist/clients/dispatch/utils/format-utils.js +2 -2
  147. package/node_modules/pi-lens/dist/clients/disposition-logger.js +3 -10
  148. package/node_modules/pi-lens/dist/clients/effective-config.js +403 -0
  149. package/node_modules/pi-lens/dist/clients/error-class.js +23 -0
  150. package/node_modules/pi-lens/dist/clients/event-loop-hold.js +274 -0
  151. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +2 -2
  152. package/node_modules/pi-lens/dist/clients/extension-log.js +2 -2
  153. package/node_modules/pi-lens/dist/clients/feature-hints.js +2 -1
  154. package/node_modules/pi-lens/dist/clients/file-kinds.js +1 -38
  155. package/node_modules/pi-lens/dist/clients/file-time.js +7 -2
  156. package/node_modules/pi-lens/dist/clients/file-utils.js +117 -10
  157. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +50 -13
  158. package/node_modules/pi-lens/dist/clients/format-service.js +6 -2
  159. package/node_modules/pi-lens/dist/clients/formatters.js +385 -104
  160. package/node_modules/pi-lens/dist/clients/freshness-cadence.js +17 -0
  161. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +116 -22
  162. package/node_modules/pi-lens/dist/clients/generation-guard.js +4 -9
  163. package/node_modules/pi-lens/dist/clients/git-guard.js +6 -1
  164. package/node_modules/pi-lens/dist/clients/go-client.js +39 -0
  165. package/node_modules/pi-lens/dist/clients/gradle-ktfmt-style.js +252 -0
  166. package/node_modules/pi-lens/dist/clients/grammar-source.js +3 -3
  167. package/node_modules/pi-lens/dist/clients/hashline-anchor.js +424 -0
  168. package/node_modules/pi-lens/dist/clients/hook-budgets.js +76 -0
  169. package/node_modules/pi-lens/dist/clients/host-edit-normalize.js +5 -2
  170. package/node_modules/pi-lens/dist/clients/host-ports.js +1 -1
  171. package/node_modules/pi-lens/dist/clients/installer/index.js +289 -59
  172. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +84 -18
  173. package/node_modules/pi-lens/dist/clients/instance-reaper.js +110 -236
  174. package/node_modules/pi-lens/dist/clients/instance-registry.js +6 -3
  175. package/node_modules/pi-lens/dist/clients/language-profile.js +0 -9
  176. package/node_modules/pi-lens/dist/clients/language-registry.js +599 -0
  177. package/node_modules/pi-lens/dist/clients/latency-logger.js +165 -9
  178. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +34 -1
  179. package/node_modules/pi-lens/dist/clients/lens-config.js +160 -30
  180. package/node_modules/pi-lens/dist/clients/lens-engine.js +33 -41
  181. package/node_modules/pi-lens/dist/clients/lens-events.js +1 -1
  182. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +77 -6
  183. package/node_modules/pi-lens/dist/clients/lens-map.js +1 -1
  184. package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -38
  185. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +3 -1
  186. package/node_modules/pi-lens/dist/clients/lsp/client.js +172 -33
  187. package/node_modules/pi-lens/dist/clients/lsp/config.js +484 -158
  188. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +1 -1
  189. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +1 -1
  190. package/node_modules/pi-lens/dist/clients/lsp/edits.js +18 -5
  191. package/node_modules/pi-lens/dist/clients/lsp/index.js +892 -96
  192. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +1 -1
  193. package/node_modules/pi-lens/dist/clients/lsp/language.js +18 -177
  194. package/node_modules/pi-lens/dist/clients/lsp/launch.js +2 -1
  195. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
  196. package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +5 -18
  197. package/node_modules/pi-lens/dist/clients/lsp/server.js +284 -147
  198. package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +116 -23
  199. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +2 -7
  200. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +0 -1
  201. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +14 -7
  202. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +6 -0
  203. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +2 -1
  204. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +1 -1
  205. package/node_modules/pi-lens/dist/clients/lsp-document-symbols.js +1 -1
  206. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +163 -21
  207. package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +36 -0
  208. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +33 -4
  209. package/node_modules/pi-lens/dist/clients/mcp/session.js +5 -16
  210. package/node_modules/pi-lens/dist/clients/memory-sampler.js +8 -3
  211. package/node_modules/pi-lens/dist/clients/metrics-history.js +28 -107
  212. package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +3 -5
  213. package/node_modules/pi-lens/dist/clients/module-report.js +21 -46
  214. package/node_modules/pi-lens/dist/clients/mutating-tool.js +651 -0
  215. package/node_modules/pi-lens/dist/clients/mutation-attribution.js +368 -0
  216. package/node_modules/pi-lens/dist/clients/mutation-bridge.js +240 -0
  217. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +247 -24
  218. package/node_modules/pi-lens/dist/clients/observed-mutation-sources.js +101 -0
  219. package/node_modules/pi-lens/dist/clients/observed-mutation.js +1215 -0
  220. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +70 -28
  221. package/node_modules/pi-lens/dist/clients/opengrep-config.js +5 -1
  222. package/node_modules/pi-lens/dist/clients/package-manager.js +195 -26
  223. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +359 -80
  224. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +23 -7
  225. package/node_modules/pi-lens/dist/clients/path-keyed-map.js +21 -2
  226. package/node_modules/pi-lens/dist/clients/path-utils.js +451 -15
  227. package/node_modules/pi-lens/dist/clients/performance-report.js +2 -2
  228. package/node_modules/pi-lens/dist/clients/persist-debounce.js +8 -1
  229. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +114 -0
  230. package/node_modules/pi-lens/dist/clients/pipeline.js +79 -18
  231. package/node_modules/pi-lens/dist/clients/probe-home-state.js +228 -0
  232. package/node_modules/pi-lens/dist/clients/process-bridge.js +66 -0
  233. package/node_modules/pi-lens/dist/clients/process-snapshot.js +68 -0
  234. package/node_modules/pi-lens/dist/clients/project-changes.js +1 -1
  235. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +1 -1
  236. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +4 -3
  237. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/jscpd.js +1 -1
  238. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/knip.js +1 -1
  239. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +1 -1
  240. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/opengrep.js +1 -1
  241. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +28 -4
  242. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +51 -20
  243. package/node_modules/pi-lens/dist/clients/project-lens-config.js +438 -122
  244. package/node_modules/pi-lens/dist/clients/project-scan-policy.js +3 -51
  245. package/node_modules/pi-lens/dist/clients/project-snapshot.js +52 -24
  246. package/node_modules/pi-lens/dist/clients/python-environment.js +217 -0
  247. package/node_modules/pi-lens/dist/clients/python-provenance.js +639 -0
  248. package/node_modules/pi-lens/dist/clients/quiet-window.js +6 -1
  249. package/node_modules/pi-lens/dist/clients/read-bridge.js +13 -21
  250. package/node_modules/pi-lens/dist/clients/read-expansion.js +9 -46
  251. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +16 -5
  252. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +364 -212
  253. package/node_modules/pi-lens/dist/clients/read-guard.js +26 -2
  254. package/node_modules/pi-lens/dist/clients/recent-touches.js +2 -2
  255. package/node_modules/pi-lens/dist/clients/resource-sampler.js +195 -96
  256. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +155 -114
  257. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +1 -1
  258. package/node_modules/pi-lens/dist/clients/review-graph/service.js +55 -3
  259. package/node_modules/pi-lens/dist/clients/review-graph/shared-extraction-ir.js +7 -15
  260. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +10 -74
  261. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +9 -3
  262. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +19 -3
  263. package/node_modules/pi-lens/dist/clients/runtime-context.js +49 -16
  264. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +25 -6
  265. package/node_modules/pi-lens/dist/clients/runtime-session.js +184 -32
  266. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +179 -36
  267. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +797 -243
  268. package/node_modules/pi-lens/dist/clients/runtime-turn.js +1028 -53
  269. package/node_modules/pi-lens/dist/clients/rust-client.js +31 -0
  270. package/node_modules/pi-lens/dist/clients/safe-spawn.js +33 -10
  271. package/node_modules/pi-lens/dist/clients/sanitize.js +7 -12
  272. package/node_modules/pi-lens/dist/clients/scan-utils.js +1 -56
  273. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +1 -3
  274. package/node_modules/pi-lens/dist/clients/security-scan-client.js +3 -0
  275. package/node_modules/pi-lens/dist/clients/session-lifecycle.js +1 -1
  276. package/node_modules/pi-lens/dist/clients/session-start-observability.js +79 -0
  277. package/node_modules/pi-lens/dist/clients/session-summary.js +0 -24
  278. package/node_modules/pi-lens/dist/clients/sessionstart-logger.js +12 -3
  279. package/node_modules/pi-lens/dist/clients/sgconfig.js +7 -6
  280. package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +1 -1
  281. package/node_modules/pi-lens/dist/clients/skills-resolver.js +105 -0
  282. package/node_modules/pi-lens/dist/clients/smells-rollup.js +4 -4
  283. package/node_modules/pi-lens/dist/clients/startup-scan.js +2 -10
  284. package/node_modules/pi-lens/dist/clients/string-utils.js +13 -0
  285. package/node_modules/pi-lens/dist/clients/subagent-mode.js +17 -4
  286. package/node_modules/pi-lens/dist/clients/test-runner-client.js +176 -39
  287. package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +244 -0
  288. package/node_modules/pi-lens/dist/clients/tool-definition.js +41 -1
  289. package/node_modules/pi-lens/dist/clients/tool-policy.js +92 -22
  290. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +40 -40
  291. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +87 -100
  292. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +2 -2
  293. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +81 -1
  294. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +51 -46
  295. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +28 -0
  296. package/node_modules/pi-lens/dist/clients/tui-fit.js +0 -4
  297. package/node_modules/pi-lens/dist/clients/typos-config.js +5 -0
  298. package/node_modules/pi-lens/dist/clients/user-notify.js +6 -6
  299. package/node_modules/pi-lens/dist/clients/widget-state.js +191 -26
  300. package/node_modules/pi-lens/dist/clients/word-index-logger.js +2 -9
  301. package/node_modules/pi-lens/dist/clients/word-index-store.js +23 -12
  302. package/node_modules/pi-lens/dist/clients/word-index.js +252 -47
  303. package/node_modules/pi-lens/dist/clients/workspace-topology.js +8 -1
  304. package/node_modules/pi-lens/dist/clients/zizmor-config.js +4 -1
  305. package/node_modules/pi-lens/dist/index.js +65899 -55340
  306. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +3 -1
  307. package/node_modules/pi-lens/dist/mcp/build-staleness.js +1 -1
  308. package/node_modules/pi-lens/dist/mcp/server.js +127 -5
  309. package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +583 -0
  310. package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +129 -0
  311. package/node_modules/pi-lens/dist/tools/effective-config.js +89 -0
  312. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +96 -25
  313. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +104 -86
  314. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +37 -16
  315. package/node_modules/pi-lens/dist/tools/lsp-structured-output.js +3 -3
  316. package/node_modules/pi-lens/dist/tools/render-compact.js +1 -1
  317. package/node_modules/pi-lens/dist/tools/shared.js +0 -1
  318. package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -2
  319. package/node_modules/pi-lens/docs/agent-guide.md +64 -1
  320. package/node_modules/pi-lens/docs/configuration.md +222 -0
  321. package/node_modules/pi-lens/docs/dependencies.md +3 -3
  322. package/node_modules/pi-lens/docs/features.md +44 -5
  323. package/node_modules/pi-lens/docs/globalconfig.md +20 -0
  324. package/node_modules/pi-lens/docs/language-coverage.md +2 -2
  325. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +9 -1
  326. package/node_modules/pi-lens/docs/pi-lens-fixer.md +25 -0
  327. package/node_modules/pi-lens/docs/pi-lens-investigator.md +25 -0
  328. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +33 -0
  329. package/node_modules/pi-lens/docs/pi-lens-subagent.md +49 -0
  330. package/node_modules/pi-lens/docs/pi-lens-warden.md +55 -0
  331. package/node_modules/pi-lens/docs/public-api-stability.md +359 -0
  332. package/node_modules/pi-lens/docs/release-qa-baseline.md +190 -0
  333. package/node_modules/pi-lens/docs/servercapabilities.md +7 -3
  334. package/node_modules/pi-lens/docs/subagent-compat.md +110 -30
  335. package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +2 -2
  336. package/node_modules/pi-lens/docs/word-index.md +1 -1
  337. package/node_modules/pi-lens/package.json +18 -35
  338. package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-cross-language-method.yml +3 -3
  339. package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-hallucinated-import.yml +2 -2
  340. package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-sql-injection.yml +12 -2
  341. package/node_modules/pi-lens/rules/typos/_typos.toml +4 -2
  342. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +192 -1
  343. package/node_modules/pi-lens/scripts/install-selftest.mjs +58 -2
  344. package/node_modules/pi-lens/scripts/lib/skills-predicate.mjs +129 -0
  345. package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +1 -1
  346. package/node_modules/pi-lens/scripts/rpc-load-check.mjs +3 -1
  347. package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +4 -2
  348. package/node_modules/pi-web-access/CHANGELOG.md +28 -0
  349. package/node_modules/pi-web-access/README.md +62 -15
  350. package/node_modules/pi-web-access/curator-page.ts +3 -1
  351. package/node_modules/pi-web-access/curator-server.ts +5 -1
  352. package/node_modules/pi-web-access/gemini-search.ts +8 -4
  353. package/node_modules/pi-web-access/github-extract.ts +242 -1
  354. package/node_modules/pi-web-access/index.ts +118 -64
  355. package/node_modules/pi-web-access/mistral-search.ts +281 -0
  356. package/node_modules/pi-web-access/package.json +2 -2
  357. package/node_modules/pi-web-access/perplexity.ts +14 -1
  358. package/node_modules/pi-web-access/utils.ts +23 -6
  359. package/node_modules/pi-web-access/xai-search.ts +96 -33
  360. package/package.json +6 -6
@@ -29,16 +29,17 @@
29
29
  * - rust-analyzer (Rust LSP) [GitHub release]
30
30
  * - golangci-lint (Go linting) [GitHub release]
31
31
  *
32
- * Manual install required (25+ tools):
33
- * - yaml-language-server: npm install -g yaml-language-server
34
- * - vscode-json-languageserver: npm install -g vscode-langservers-extracted
35
- * - bash-language-server: npm install -g bash-language-server
36
- * - svelte-language-server: npm install -g svelte-language-server
37
- * - vscode-css-languageserver: npm install -g vscode-langservers-extracted
38
- * - @prisma/language-server: npm install -g @prisma/language-server
39
- * - dockerfile-language-server: npm install -g dockerfile-language-server-nodejs
40
- * - @vue/language-server: npm install -g @vue/language-server
41
- * - And all language-specific servers (gopls, rust-analyzer, etc.)
32
+ * Every other managed tool (including several the list above omits, e.g.
33
+ * bash-language-server, yaml-language-server, svelte-language-server,
34
+ * @prisma/language-server, @vue/language-server, dockerfile-language-server-nodejs,
35
+ * and vscode-css-languageserver — all of which ARE auto-installed) is
36
+ * documented by the `TOOLS` array below, not restated here. #2638: a
37
+ * hand-kept "manual install" list naming specific package specs used to live
38
+ * in this comment, drifted from `TOOLS` (it named a since-unpublished npm
39
+ * package for the CSS entry, and several tools it called "manual" were
40
+ * already auto-install above) and nothing caught it. `TOOLS` is the single
41
+ * source of truth for every id, package, and strategy; deleted rather than
42
+ * re-derived, since a comment cannot be generated from code at write time.
42
43
  *
43
44
  * Strategies:
44
45
  * - npm packages via npx/bun
@@ -234,7 +235,12 @@ export const TOOLS = [
234
235
  checkCommand: "typescript-language-server",
235
236
  checkArgs: ["--version"],
236
237
  installStrategy: "npm",
237
- packageName: "typescript-language-server",
238
+ // Pinned below the package's own floor: 6.0.0 declares
239
+ // engines.node >=22.22.2, above the pi host's own floor (pi-lens
240
+ // must never require more than pi does — #2633) — an unpinned
241
+ // install here resolves latest and prints EBADENGINE on any pi
242
+ // host's supported Node. 5.3.0's own floor is engines.node >=20.
243
+ packageName: "typescript-language-server@5.3.0",
238
244
  binaryName: "typescript-language-server",
239
245
  },
240
246
  {
@@ -440,7 +446,7 @@ export const TOOLS = [
440
446
  checkCommand: "vscode-html-language-server",
441
447
  checkArgs: ["--version"],
442
448
  installStrategy: "npm",
443
- packageName: "vscode-html-languageserver-bin",
449
+ packageName: "vscode-langservers-extracted",
444
450
  binaryName: "vscode-html-language-server",
445
451
  },
446
452
  {
@@ -529,7 +535,7 @@ export const TOOLS = [
529
535
  checkCommand: "vscode-css-language-server",
530
536
  checkArgs: ["--version"],
531
537
  installStrategy: "npm",
532
- packageName: "vscode-css-languageserver",
538
+ packageName: "vscode-langservers-extracted",
533
539
  binaryName: "vscode-css-language-server",
534
540
  },
535
541
  {
@@ -542,10 +548,27 @@ export const TOOLS = [
542
548
  binaryName: "docker-langserver",
543
549
  },
544
550
  {
551
+ // #2722: intelephense has no CLI — its entry calls createConnection()
552
+ // unconditionally — and Node prints the offending source line before the
553
+ // error. The bundle is one ~4 MB minified line, so the transport-required
554
+ // marker #208 rescues on lands at byte 4,154,741 of a 4,423,356-byte
555
+ // stderr while Node truncates piped stderr at 1 MiB on exit. Measured on
556
+ // intelephense@1.18.5, linux, Node v22.22.1:
557
+ // $ intelephense --version 2>&1 | wc -c -> 1048576
558
+ // $ intelephense --version 2>&1 | grep -c "Connection input stream is not set" -> 0
559
+ // $ intelephense --version 2>err.txt; wc -c < err.txt -> 4423356
560
+ // Same run over the alternatives, each with stdin closed the way
561
+ // verifyToolBinary closes it (`input: ""`):
562
+ // --help, -v, --socket=0 -> identical dump (exit 1, 4423356 bytes,
563
+ // marker at 4154741, 1048576 through a pipe, marker absent)
564
+ // --stdio -> exit 1 with ZERO bytes on either stream,
565
+ // so it carries no marker to rescue on either.
566
+ // No checkArgs value produces a verdict. Verified spawn-free instead.
545
567
  id: "intelephense",
546
568
  name: "Intelephense",
547
569
  checkCommand: "intelephense",
548
570
  checkArgs: ["--version"],
571
+ verification: "package-entry",
549
572
  installStrategy: "npm",
550
573
  packageName: "intelephense",
551
574
  binaryName: "intelephense",
@@ -1823,7 +1846,7 @@ const lspTransportRequiredMatcher = /Connection (?:input|output) stream is not s
1823
1846
  * correctly reports "no pin" rather than mistaking the scope for a version.
1824
1847
  * Returns undefined when packageName has no explicit `@version` suffix.
1825
1848
  */
1826
- function parsePinnedVersion(packageName) {
1849
+ export function parsePinnedVersion(packageName) {
1827
1850
  const at = packageName.lastIndexOf("@");
1828
1851
  if (at <= 0)
1829
1852
  return undefined;
@@ -1845,6 +1868,111 @@ function extractVersionToken(output) {
1845
1868
  * maven/archive) never populate or read this map.
1846
1869
  */
1847
1870
  const lastManagedInstallVersion = new Map();
1871
+ /**
1872
+ * The package whose entry module verifies `tool`'s managed install spawn-free,
1873
+ * or undefined when the tool is verified by spawning `checkArgs` (#2722).
1874
+ * Registry-declared per tool — never inferred from the strategy: a spawn probe
1875
+ * is the stronger check everywhere it can actually return a verdict.
1876
+ */
1877
+ export function packageEntryVerification(tool) {
1878
+ return tool.verification === "package-entry" ? tool.packageName : undefined;
1879
+ }
1880
+ /**
1881
+ * Spawn-free verification of a managed npm install (#2722).
1882
+ *
1883
+ * For an stdio LSP server with no CLI surface, every `checkArgs` value is a
1884
+ * throw, and #208's transport-required rescue is output-ORDER dependent: Node
1885
+ * prints the offending source line ahead of the error, so a server whose bundle
1886
+ * is megabytes of minified JS pushes the marker past both our retained window
1887
+ * and Node's own 1 MiB pipe truncation. The bytes never leave the child, so no
1888
+ * spawn can answer "is this install intact".
1889
+ *
1890
+ * The evidence used instead is the same class the `archive` strategy already
1891
+ * accepts through `treeMarker`: the installed tree is inspected on disk. The
1892
+ * install verifies when the package directory beside the `.bin` shim carries a
1893
+ * readable `package.json` with a `version`, and the entry module that
1894
+ * package.json itself names — `bin[<shim name>]` or a bare `bin` string —
1895
+ * exists as a non-empty file. `main` is deliberately NOT a fallback: npm
1896
+ * creates the `node_modules/.bin/<shim>` this function is given only from a
1897
+ * `bin` field, so a manifest without one cannot be the manifest behind this
1898
+ * shim. That is exactly what `verifyToolBinary` was documented to catch here —
1899
+ * broken symlinks and partial installs — and it is strictly more than the
1900
+ * `--version` spawn can report for this class.
1901
+ */
1902
+ export async function verifyNpmPackageEntry(binPath, packageName) {
1903
+ // `<...>/node_modules/.bin/<shim>` → `<...>/node_modules`.
1904
+ const nodeModulesDir = path.dirname(path.dirname(binPath));
1905
+ const pinned = parsePinnedVersion(packageName);
1906
+ const bareName = pinned
1907
+ ? packageName.slice(0, -(pinned.length + 1))
1908
+ : packageName;
1909
+ const packageDir = path.join(nodeModulesDir, ...bareName.split("/"));
1910
+ const shimName = path
1911
+ .basename(binPath)
1912
+ .replace(/\.(cmd|exe|ps1|bat)$/i, "")
1913
+ .toLowerCase();
1914
+ const fail = (reason) => {
1915
+ logSessionStart(`auto-install verify: failed for ${binPath} (check=package-entry, kind=${reason})`);
1916
+ return false;
1917
+ };
1918
+ // R2-F1: the shim ITSELF, before anything else. Every path below is DERIVED
1919
+ // from `binPath`, so without this a partial install — npm never wrote the
1920
+ // `.bin` entry, or wrote one that is empty, a directory, or a symlink
1921
+ // dangling at a deleted target — verified `true` on the strength of a
1922
+ // package tree that nothing can execute, and `installNpmTool` then recorded
1923
+ // the install as SUCCEEDED. `classifyInstallOutcome` grades any non-"failed"
1924
+ // outcome as `⚠ unavailable (succeeded)`, so that answer re-hid exactly the
1925
+ // nightly row #2722 exists to expose. `statSync` FOLLOWS the link, which is
1926
+ // what makes missing and dangling one branch.
1927
+ try {
1928
+ const shim = statSync(binPath);
1929
+ if (!shim.isFile() || shim.size === 0)
1930
+ return fail("shim-not-a-file");
1931
+ }
1932
+ catch {
1933
+ return fail("shim-missing");
1934
+ }
1935
+ let manifest;
1936
+ try {
1937
+ manifest = JSON.parse(await fs.readFile(path.join(packageDir, "package.json"), "utf8"));
1938
+ }
1939
+ catch {
1940
+ return fail("package-json-unreadable");
1941
+ }
1942
+ if (typeof manifest.version !== "string" || !manifest.version) {
1943
+ return fail("package-json-no-version");
1944
+ }
1945
+ const bin = manifest.bin;
1946
+ const entry = typeof bin === "string"
1947
+ ? bin
1948
+ : bin && typeof bin === "object"
1949
+ ? Object.entries(bin).find(
1950
+ // Case-folded on purpose: on a case-insensitive filesystem the
1951
+ // shim on disk can differ in case from the manifest key npm
1952
+ // wrote it from, and `path.basename` reads the disk name.
1953
+ ([name]) => name.toLowerCase() === shimName)?.[1]
1954
+ : undefined;
1955
+ if (typeof entry !== "string" || !entry) {
1956
+ return fail("package-json-no-entry");
1957
+ }
1958
+ try {
1959
+ const stat = statSync(path.join(packageDir, entry));
1960
+ if (!stat.isFile() || stat.size === 0)
1961
+ return fail("entry-not-a-file");
1962
+ }
1963
+ catch {
1964
+ return fail("entry-missing");
1965
+ }
1966
+ // R2-F3 (catalog shape 31): "did the new verifier run at all, and on what"
1967
+ // has to be answerable from sessionstart.log, not only from a debug build —
1968
+ // the failure branches above already log there. Measured volume on a real
1969
+ // intelephense install, scratch PI_LENS_HOME: TWO rows on the session that
1970
+ // installs (installNpmTool's verify, then the post-install resolution), and
1971
+ // ZERO on every later session — the probe cache answers before any verify
1972
+ // runs (`auto-install ensure intelephense: probe cache hit`).
1973
+ logSessionStart(`auto-install verify: succeeded for ${binPath} (check=package-entry, version=${manifest.version}, entry=${entry})`);
1974
+ return true;
1975
+ }
1848
1976
  /**
1849
1977
  * Verify a tool binary actually works by running its configured checkArgs.
1850
1978
  * This catches broken symlinks, partial installs, and corrupted binaries.
@@ -1872,7 +2000,26 @@ onTransient,
1872
2000
  * sensitive caller on the dispatch hot path passes a shorter one and treats
1873
2001
  * the expiry as transient rather than as a verdict (#1657).
1874
2002
  */
1875
- timeoutMs = 10000, verificationArgs = ["--version"]) {
2003
+ timeoutMs = 10000, verificationArgs = ["--version"],
2004
+ /**
2005
+ * Package name from {@link packageEntryVerification} — present only for a
2006
+ * registry entry declaring `verification: "package-entry"`. When present the
2007
+ * spawn is skipped entirely and {@link verifyNpmPackageEntry} answers (#2722).
2008
+ */
2009
+ packageEntryOf,
2010
+ /**
2011
+ * Called when a `false` verdict is INCONCLUSIVE rather than a verdict: the
2012
+ * transport-required matcher was armed, never matched, and the child's
2013
+ * output was cut off before the tail arrived, so the #208 rescue could not
2014
+ * be evaluated at all (#2722). Distinct from `onTransient` on purpose — the
2015
+ * prober DID run to completion and re-probing reproduces it exactly, so this
2016
+ * is a durable "cannot be verified", not a stall (catalog shape 13). A
2017
+ * caller must not treat it as "broken" and delete the installation.
2018
+ */
2019
+ onInconclusive) {
2020
+ if (packageEntryOf !== undefined) {
2021
+ return verifyNpmPackageEntry(binPath, packageEntryOf);
2022
+ }
1876
2023
  // #2015: safeSpawnAsync instead of raw spawn. Raw spawn's timeout
1877
2024
  // SIGTERMed only cmd.exe on Windows (.cmd shims run shell:true), orphaning
1878
2025
  // the grandchild node process - which kept scanning, held handles against
@@ -1931,6 +2078,31 @@ timeoutMs = 10000, verificationArgs = ["--version"]) {
1931
2078
  // verdict from the binary (#1569 transient semantics).
1932
2079
  if (result.signal !== undefined || result.spawnFailure)
1933
2080
  onTransient?.();
2081
+ if (result.outputTruncated &&
2082
+ result.signal === undefined &&
2083
+ !result.spawnFailure) {
2084
+ // #2722: the transport-required matcher above is armed on EVERY probe,
2085
+ // and it never matched — but the output we kept is a prefix, so the
2086
+ // marker may simply sit past it (intelephense emits ~4 MB of bundle
2087
+ // ahead of its own diagnostic). "Unmatched within a truncated prefix"
2088
+ // is not "this binary is broken"; callers must not delete the install
2089
+ // on it. Recorded as its own kind so a monitor can tell an unreadable
2090
+ // probe apart from a rejected binary.
2091
+ //
2092
+ // R2-F4: gated on the probe having actually FINISHED. A verbose child
2093
+ // that is SIGTERMed at the timeout also arrives here truncated, and
2094
+ // that is the #1569 transient class one line above, not this one —
2095
+ // ungated, the two overlapped and `installNpmTool` (which tests
2096
+ // inconclusive first) replaced the #2015 transient message with this
2097
+ // one. The doc comment's "the prober DID run to completion" is now
2098
+ // true rather than aspirational.
2099
+ onInconclusive?.();
2100
+ recordDegradationOnce({
2101
+ kind: "installer-verification-inconclusive",
2102
+ subject: binPath,
2103
+ reason: `transport-required marker unresolved in truncated output (${verificationArgs.join(" ")})`,
2104
+ });
2105
+ }
1934
2106
  const kind = result.spawnFailure?.kind ??
1935
2107
  (result.signal
1936
2108
  ? `killed-signal=${result.signal}`
@@ -2050,7 +2222,7 @@ export async function getAllToolStatuses() {
2050
2222
  const localPath = installerPlatform() === "win32" ? `${localBase}.cmd` : localBase;
2051
2223
  try {
2052
2224
  await fs.access(localPath);
2053
- if (await verifyToolBinary(localPath, undefined, undefined, getToolVerificationTimeout(tool), tool.checkArgs)) {
2225
+ if (await verifyToolBinary(localPath, undefined, undefined, getToolVerificationTimeout(tool), tool.checkArgs, packageEntryVerification(tool))) {
2054
2226
  status.installed = true;
2055
2227
  status.source = "pi-lens-auto";
2056
2228
  status.path = localPath;
@@ -2219,7 +2391,7 @@ async function getToolPathResolved(toolId, onTransient) {
2219
2391
  const cmdPath = `${localBase}.cmd`;
2220
2392
  try {
2221
2393
  await fs.access(cmdPath);
2222
- if (await verifyToolBinary(cmdPath, recordVersion, onTransient, getToolVerificationTimeout(tool), tool.checkArgs)) {
2394
+ if (await verifyToolBinary(cmdPath, recordVersion, onTransient, getToolVerificationTimeout(tool), tool.checkArgs, packageEntryVerification(tool))) {
2223
2395
  return cmdPath;
2224
2396
  }
2225
2397
  logSessionStart(`auto-install verify: ${cmdPath} exists but is broken, will reinstall`);
@@ -2232,7 +2404,7 @@ async function getToolPathResolved(toolId, onTransient) {
2232
2404
  const exePath = `${localBase}.exe`;
2233
2405
  try {
2234
2406
  await fs.access(exePath);
2235
- if (await verifyToolBinary(exePath, recordVersion, onTransient, getToolVerificationTimeout(tool), tool.checkArgs)) {
2407
+ if (await verifyToolBinary(exePath, recordVersion, onTransient, getToolVerificationTimeout(tool), tool.checkArgs, packageEntryVerification(tool))) {
2236
2408
  return exePath;
2237
2409
  }
2238
2410
  logSessionStart(`auto-install verify: ${exePath} exists but is broken, will reinstall`);
@@ -2244,7 +2416,7 @@ async function getToolPathResolved(toolId, onTransient) {
2244
2416
  if (installerPlatform() !== "win32") {
2245
2417
  try {
2246
2418
  await fs.access(localBase);
2247
- if (await verifyToolBinary(localBase, recordVersion, onTransient, getToolVerificationTimeout(tool), tool.checkArgs)) {
2419
+ if (await verifyToolBinary(localBase, recordVersion, onTransient, getToolVerificationTimeout(tool), tool.checkArgs, packageEntryVerification(tool))) {
2248
2420
  return localBase;
2249
2421
  }
2250
2422
  logSessionStart(`auto-install verify: ${localBase} exists but is broken, will reinstall`);
@@ -2867,6 +3039,7 @@ export function npmToolNeedsPostinstall(packageName) {
2867
3039
  * that wanders off it.
2868
3040
  */
2869
3041
  export function getRefreshableManagedNpmTools() {
3042
+ const seenPackages = new Set();
2870
3043
  return getRefreshableManagedTools()
2871
3044
  .filter((candidate) => candidate.strategy === "npm")
2872
3045
  .map((candidate) => ({
@@ -2875,7 +3048,13 @@ export function getRefreshableManagedNpmTools() {
2875
3048
  // have both, so these are total.
2876
3049
  packageName: candidate.packageName,
2877
3050
  binaryName: candidate.binaryName,
2878
- }));
3051
+ }))
3052
+ .filter((candidate) => {
3053
+ if (seenPackages.has(candidate.packageName))
3054
+ return false;
3055
+ seenPackages.add(candidate.packageName);
3056
+ return true;
3057
+ });
2879
3058
  }
2880
3059
  /** The Maven GAV a `maven` entry pins, as a stable identity string. */
2881
3060
  function mavenCoordinate(spec) {
@@ -2922,6 +3101,7 @@ export function getRefreshableManagedTools() {
2922
3101
  packageName: tool.packageName,
2923
3102
  binaryName: tool.binaryName,
2924
3103
  verificationTimeoutMs: tool.verificationTimeoutMs,
3104
+ packageEntryOf: packageEntryVerification(tool),
2925
3105
  });
2926
3106
  break;
2927
3107
  }
@@ -3303,10 +3483,10 @@ async function refreshPackageManagerManagedTool(tool) {
3303
3483
  const installed = tool.installStrategy === "pip"
3304
3484
  ? // `-U` is the whole fix: without it pip treats the installed copy as
3305
3485
  // satisfying the requirement and the day-one version never moves.
3306
- await installPipTool(tool.packageName, { upgrade: true })
3486
+ await installPipTool(tool.id, tool.packageName, { upgrade: true })
3307
3487
  : // `gem install` always fetches the newest version that satisfies the
3308
3488
  // requirement, so the install command IS the upgrade command.
3309
- await installGemTool(tool.packageName);
3489
+ await installGemTool(tool.id, tool.packageName);
3310
3490
  if (!installed) {
3311
3491
  return {
3312
3492
  ok: false,
@@ -3734,7 +3914,26 @@ async function installArchiveTool(tool) {
3734
3914
  return undefined;
3735
3915
  }
3736
3916
  }
3737
- async function installNpmTool(packageName, binaryName, verificationArgs = ["--version"], verificationTimeoutMs = 10_000) {
3917
+ /**
3918
+ * Record a genuine package-manager install exception against `toolId` and
3919
+ * log it — the one place `installNpmTool`/`installPipTool`/`installGemTool`'s
3920
+ * catch blocks funnel through (#2661 review F3/Sonar: the three catches were
3921
+ * byte-identical but for the strategy label, a duplication SonarCloud's new-
3922
+ * code gate correctly flagged). The real error (registry E404, EBADENGINE, a
3923
+ * network failure, "no version satisfies…") lands in `installFailureReasons`
3924
+ * so a caller that distinguishes a genuine installer defect from a policy
3925
+ * decline (the tool-smoke lane, #2638) sees the actual message, never the
3926
+ * generic fallback `finishInstallAttempt` uses when nothing set it.
3927
+ */
3928
+ function recordPackageManagerInstallException(toolId, strategyLabel, packageName, err) {
3929
+ const message = err.message;
3930
+ logSessionStart(`auto-install ${strategyLabel} ${packageName}: exception: ${message}`);
3931
+ installFailureReasons.set(toolId, message);
3932
+ return undefined;
3933
+ }
3934
+ async function installNpmTool(toolId, packageName, binaryName, verificationArgs = ["--version"], verificationTimeoutMs = 10_000,
3935
+ /** See {@link packageEntryVerification} — spawn-free verification (#2722). */
3936
+ packageEntryOf) {
3738
3937
  try {
3739
3938
  // Ensure tools directory exists
3740
3939
  await fs.mkdir(TOOLS_DIR, { recursive: true });
@@ -3814,11 +4013,15 @@ async function installNpmTool(packageName, binaryName, verificationArgs = ["--ve
3814
4013
  debugLog(`Verifying ${binaryName}...`);
3815
4014
  let isValid = false;
3816
4015
  let lastAttemptTransient = false;
4016
+ let lastAttemptInconclusive = false;
3817
4017
  for (let attempt = 1; attempt <= 3; attempt++) {
3818
4018
  lastAttemptTransient = false;
4019
+ lastAttemptInconclusive = false;
3819
4020
  isValid = await verifyToolBinary(binPath, undefined, () => {
3820
4021
  lastAttemptTransient = true;
3821
- }, verificationTimeoutMs, verificationArgs);
4022
+ }, verificationTimeoutMs, verificationArgs, packageEntryOf, () => {
4023
+ lastAttemptInconclusive = true;
4024
+ });
3822
4025
  if (isValid)
3823
4026
  break;
3824
4027
  if (attempt < 3) {
@@ -3826,6 +4029,37 @@ async function installNpmTool(packageName, binaryName, verificationArgs = ["--ve
3826
4029
  await new Promise((r) => setTimeout(r, 1000 * attempt));
3827
4030
  }
3828
4031
  }
4032
+ if (!isValid && lastAttemptInconclusive) {
4033
+ // #2722: the probe ran to completion but could not decide — the #208
4034
+ // transport-required matcher was armed, never matched, and the kept
4035
+ // output is a truncated prefix, so the marker may sit past it (the
4036
+ // class intelephense fell into: ~4 MB of bundle ahead of the marker,
4037
+ // unreachable through any pipe). Same rule as the transient branch
4038
+ // below (#2015): a non-verdict must not delete a freshly installed
4039
+ // package. Deliberately NOT folded into `lastAttemptTransient` —
4040
+ // re-probing reproduces this exactly, so it is durable, and a
4041
+ // transient verdict would keep re-arming the reinstall path
4042
+ // (catalog shape 13).
4043
+ // R2-F2: a non-verdict is a reason to keep ONLY while the tree on disk
4044
+ // still looks like a complete install. With intelephense verified
4045
+ // spawn-free, no registry entry reaches this branch healthy (measured:
4046
+ // no other npm-strategy LSP server emits more than 1,527 bytes), so
4047
+ // its live population is BROKEN servers that spew past the retained
4048
+ // window and die — and for those the delete was the only repair that
4049
+ // existed. Measured on npm 9.2.0 against a real managed prefix: a
4050
+ // package file corrupted IN PLACE is not repaired by re-installing
4051
+ // (`up to date`, the zeroed file stays zeroed), while a package
4052
+ // directory, a `.bin` shim or a nested dependency that is GONE is
4053
+ // reinstalled. So the same on-disk evidence `verification:
4054
+ // "package-entry"` uses decides keep-vs-delete here — strictly as a
4055
+ // gate, never as a verdict: the install is still recorded as failed,
4056
+ // so the tool still reads `✗` and nothing is re-hidden.
4057
+ if (await verifyNpmPackageEntry(binPath, packageName)) {
4058
+ logSessionStart(`auto-install ${packageName}: verification inconclusive (output truncated before the transport-required marker) but the installed tree is intact; keeping installation for re-probe`);
4059
+ return undefined;
4060
+ }
4061
+ logSessionStart(`auto-install ${packageName}: verification inconclusive AND the installed tree is incomplete; cleaning up so the next install can repair it`);
4062
+ }
3829
4063
  if (!isValid && lastAttemptTransient) {
3830
4064
  // #2015: a killed/spawn-failed prober is NOT a verdict about the
3831
4065
  // binary (#1569 semantics). Cold-start verifies on fresh installs
@@ -3858,14 +4092,26 @@ async function installNpmTool(packageName, binaryName, verificationArgs = ["--ve
3858
4092
  return binPath;
3859
4093
  }
3860
4094
  catch (err) {
3861
- logSessionStart(`auto-install npm ${packageName}: exception: ${err.message}`);
3862
- return undefined;
4095
+ return recordPackageManagerInstallException(toolId, "npm", packageName, err);
3863
4096
  }
3864
4097
  }
4098
+ /**
4099
+ * The pip command ladder `installPipTool` tries, per platform, in priority
4100
+ * order — exported so a caller that needs to know "is ANY pip toolchain
4101
+ * reachable on this runner" (without running an install) probes the SAME
4102
+ * candidates rather than a second, narrower guess (#2661 review F4/S5: the
4103
+ * tool-smoke lane's own presence check tried bare `pip` only and missed a
4104
+ * `pip3`-only or python-module-only runner).
4105
+ */
4106
+ export function pipCommandCandidates() {
4107
+ return process.platform === "win32"
4108
+ ? ["pip", "py", "python"]
4109
+ : ["pip3", "pip", "python3", "python"];
4110
+ }
3865
4111
  /**
3866
4112
  * Install a pip package tool
3867
4113
  */
3868
- async function installPipTool(packageName,
4114
+ async function installPipTool(toolId, packageName,
3869
4115
  /**
3870
4116
  * Add `-U`, turning the install into an upgrade. Without it `pip install`
3871
4117
  * treats an already-present package as satisfied and leaves the day-one
@@ -3880,30 +4126,16 @@ options = {}) {
3880
4126
  const verb = options.upgrade
3881
4127
  ? ["install", "-U", "--user"]
3882
4128
  : ["install", "--user"];
3883
- const pipCandidates = isWindows
3884
- ? [
3885
- { command: "pip", args: [...verb, packageName] },
3886
- {
3887
- command: "py",
3888
- args: ["-m", "pip", ...verb, packageName],
3889
- },
3890
- {
3891
- command: "python",
3892
- args: ["-m", "pip", ...verb, packageName],
3893
- },
3894
- ]
3895
- : [
3896
- { command: "pip3", args: [...verb, packageName] },
3897
- { command: "pip", args: [...verb, packageName] },
3898
- {
3899
- command: "python3",
3900
- args: ["-m", "pip", ...verb, packageName],
3901
- },
3902
- {
3903
- command: "python",
3904
- args: ["-m", "pip", ...verb, packageName],
3905
- },
3906
- ];
4129
+ // Built from `pipCommandCandidates()` — the single source of truth this
4130
+ // module and any other caller (the tool-smoke lane's toolchain-presence
4131
+ // probe, #2661 review) share, rather than a second, independently
4132
+ // maintained ladder that can drift.
4133
+ const pipCandidates = pipCommandCandidates().map((command) => ({
4134
+ command,
4135
+ args: command === "pip" || command === "pip3"
4136
+ ? [...verb, packageName]
4137
+ : ["-m", "pip", ...verb, packageName],
4138
+ }));
3907
4139
  let lastError = "";
3908
4140
  for (const candidate of pipCandidates) {
3909
4141
  const pipResult = await safeSpawnAsync(candidate.command, candidate.args, {
@@ -4000,11 +4232,10 @@ options = {}) {
4000
4232
  throw new Error(`Failed to install ${packageName}: no usable pip command found (${lastError || "unknown error"})`);
4001
4233
  }
4002
4234
  catch (err) {
4003
- logSessionStart(`auto-install pip ${packageName}: exception: ${err.message}`);
4004
- return undefined;
4235
+ return recordPackageManagerInstallException(toolId, "pip", packageName, err);
4005
4236
  }
4006
4237
  }
4007
- async function installGemTool(packageName) {
4238
+ async function installGemTool(toolId, packageName) {
4008
4239
  try {
4009
4240
  const gemResult = await safeSpawnAsync("gem", ["install", packageName, "--no-document"], {
4010
4241
  timeout: 120_000,
@@ -4021,8 +4252,7 @@ async function installGemTool(packageName) {
4021
4252
  return packageName;
4022
4253
  }
4023
4254
  catch (err) {
4024
- logSessionStart(`auto-install gem ${packageName}: exception: ${err.message}`);
4025
- return undefined;
4255
+ return recordPackageManagerInstallException(toolId, "gem", packageName, err);
4026
4256
  }
4027
4257
  }
4028
4258
  /**
@@ -4113,7 +4343,7 @@ export async function installTool(toolId) {
4113
4343
  case "npm": {
4114
4344
  if (!tool.packageName || !tool.binaryName)
4115
4345
  return false;
4116
- const npmPath = await installNpmTool(tool.packageName, tool.binaryName, tool.checkArgs, getToolVerificationTimeout(tool));
4346
+ const npmPath = await installNpmTool(tool.id, tool.packageName, tool.binaryName, tool.checkArgs, getToolVerificationTimeout(tool), packageEntryVerification(tool));
4117
4347
  if (npmPath !== undefined) {
4118
4348
  // #1746 review F4: an install just resolved this package's range
4119
4349
  // against the registry, so record it as freshly checked. Otherwise a
@@ -4139,13 +4369,13 @@ export async function installTool(toolId) {
4139
4369
  case "pip": {
4140
4370
  if (!tool.packageName)
4141
4371
  return false;
4142
- const pipPath = await installPipTool(tool.packageName);
4372
+ const pipPath = await installPipTool(tool.id, tool.packageName);
4143
4373
  return finishInstallAttempt(tool.id, pipPath !== undefined, startedAt);
4144
4374
  }
4145
4375
  case "gem": {
4146
4376
  if (!tool.packageName)
4147
4377
  return false;
4148
- const gemPath = await installGemTool(tool.packageName);
4378
+ const gemPath = await installGemTool(tool.id, tool.packageName);
4149
4379
  return finishInstallAttempt(tool.id, gemPath !== undefined, startedAt);
4150
4380
  }
4151
4381
  case "github": {