@herbertgao/pi-extensions 2026.9.1 → 2026.9.3

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 (341) hide show
  1. package/README.md +3 -2
  2. package/THIRD_PARTY_NOTICES.md +24 -0
  3. package/node_modules/@herbertgao/sol-pi/LICENSE +19 -0
  4. package/node_modules/@herbertgao/sol-pi/README.md +159 -0
  5. package/node_modules/@herbertgao/sol-pi/SECURITY.md +26 -0
  6. package/node_modules/@herbertgao/sol-pi/THIRD_PARTY_NOTICES.md +19 -0
  7. package/node_modules/@herbertgao/sol-pi/agents-install.md +150 -0
  8. package/node_modules/@herbertgao/sol-pi/assets/sol-pi-hero.png +0 -0
  9. package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +69 -0
  10. package/node_modules/@herbertgao/sol-pi/docs/configuration.md +75 -0
  11. package/node_modules/@herbertgao/sol-pi/package.json +76 -0
  12. package/node_modules/@herbertgao/sol-pi/scripts/check-pi-compat.mjs +32 -0
  13. package/node_modules/@herbertgao/sol-pi/scripts/check-sol-pi-config.mjs +120 -0
  14. package/node_modules/@herbertgao/sol-pi/sol-pi.example.json +10 -0
  15. package/node_modules/@herbertgao/sol-pi/src/sol-pi/config.ts +135 -0
  16. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/file-queue.ts +74 -0
  17. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/index.ts +185 -0
  18. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/then-run.ts +128 -0
  19. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/archive.ts +53 -0
  20. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/candidate.ts +101 -0
  21. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/config.ts +71 -0
  22. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/index.ts +220 -0
  23. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/journal.ts +25 -0
  24. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/provider.ts +164 -0
  25. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/receipt.ts +177 -0
  26. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/index.ts +227 -0
  27. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/ledger.ts +20 -0
  28. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/observation.ts +252 -0
  29. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/economics.ts +237 -0
  30. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/extension.ts +455 -0
  31. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/index.ts +49 -0
  32. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/plan.ts +79 -0
  33. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/state.ts +208 -0
  34. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/tools.ts +100 -0
  35. package/node_modules/@herbertgao/sol-pi/src/sol-pi/index.ts +42 -0
  36. package/node_modules/@herbertgao/sol-pi/src/sol-pi/runtime-paths.ts +17 -0
  37. package/node_modules/@herbertgao/sol-pi/src/sol-pi/tui.ts +71 -0
  38. package/node_modules/@narumitw/pi-btw/README.md +57 -141
  39. package/node_modules/@narumitw/pi-btw/dist/index.ts +478 -43
  40. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +4 -4
  41. package/node_modules/@narumitw/pi-btw/docs/workflows.md +74 -0
  42. package/node_modules/@narumitw/pi-btw/package.json +2 -1
  43. package/node_modules/@narumitw/pi-btw/src/btw.ts +28 -3
  44. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +32 -2
  45. package/node_modules/@narumitw/pi-btw/src/keybindings.ts +330 -0
  46. package/node_modules/@narumitw/pi-btw/src/menu.ts +136 -19
  47. package/node_modules/@narumitw/pi-btw/src/settings.ts +32 -0
  48. package/node_modules/@narumitw/pi-btw/src/side-thread.ts +57 -5
  49. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +52 -30
  50. package/node_modules/pi-lens/CHANGELOG.md +260 -0
  51. package/node_modules/pi-lens/README.md +13 -8
  52. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +3 -1
  53. package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +0 -7
  54. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +1 -1
  55. package/node_modules/pi-lens/dist/clients/analysed-root.js +1 -0
  56. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +1 -1
  57. package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +1 -8
  58. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +2 -0
  59. package/node_modules/pi-lens/dist/clients/bash-file-access.js +133 -1
  60. package/node_modules/pi-lens/dist/clients/biome-client.js +13 -2
  61. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +0 -6
  62. package/node_modules/pi-lens/dist/clients/bootstrap.js +4 -4
  63. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +1 -1
  64. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +2 -5
  65. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +1 -9
  66. package/node_modules/pi-lens/dist/clients/cache-observability.js +122 -6
  67. package/node_modules/pi-lens/dist/clients/cargo-manifest.js +2 -2
  68. package/node_modules/pi-lens/dist/clients/cascade-logger.js +0 -7
  69. package/node_modules/pi-lens/dist/clients/child-unref.js +1 -19
  70. package/node_modules/pi-lens/dist/clients/complexity-client.js +5 -0
  71. package/node_modules/pi-lens/dist/clients/config-core/index.js +11 -8
  72. package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +1 -1
  73. package/node_modules/pi-lens/dist/clients/config-core/provenance.js +2 -9
  74. package/node_modules/pi-lens/dist/clients/config-core/records.js +1 -1
  75. package/node_modules/pi-lens/dist/clients/config-core/schema.js +3 -5
  76. package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +3 -1
  77. package/node_modules/pi-lens/dist/clients/config-resolve.js +10 -7
  78. package/node_modules/pi-lens/dist/clients/config-schema.js +56 -2
  79. package/node_modules/pi-lens/dist/clients/config-warn.js +2 -2
  80. package/node_modules/pi-lens/dist/clients/dead-code-client.js +8 -1
  81. package/node_modules/pi-lens/dist/clients/dead-code-logger.js +0 -4
  82. package/node_modules/pi-lens/dist/clients/dependency-checker.js +5 -1
  83. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +1 -1
  84. package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +1 -1
  85. package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +0 -4
  86. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +38 -41
  87. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +1 -1
  88. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +5 -1
  89. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +18 -1
  90. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +0 -6
  91. package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +1 -1
  92. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +0 -3
  93. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-complexity.js +2 -2
  94. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-fan-out.js +1 -1
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +2 -1
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +3 -1
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +2 -3
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +7 -2
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +2 -1
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +2 -1
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +2 -1
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +2 -1
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +2 -1
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +2 -1
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +2 -1
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +2 -1
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +2 -1
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +2 -1
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +2 -1
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +3 -1
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +3 -2
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +2 -1
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +2 -1
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +2 -1
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +19 -1
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +3 -2
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +2 -1
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +3 -1
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +2 -1
  121. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +2 -1
  122. package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +2 -1
  123. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +15 -11
  124. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +2 -1
  125. package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +2 -1
  126. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +3 -3
  127. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +12 -11
  128. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +3 -2
  129. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -1
  130. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +5 -3
  131. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
  132. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +3 -2
  133. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +3 -2
  134. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +2 -1
  135. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
  136. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +2 -1
  137. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +2 -1
  138. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +2 -1
  139. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +5 -0
  140. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +2 -2
  141. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +1 -64
  142. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +8 -2
  143. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils.js +0 -21
  144. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +2 -1
  145. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +2 -20
  146. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +5 -3
  147. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +2 -1
  148. package/node_modules/pi-lens/dist/clients/dispatch/utils/format-utils.js +2 -2
  149. package/node_modules/pi-lens/dist/clients/disposition-logger.js +0 -7
  150. package/node_modules/pi-lens/dist/clients/effective-config.js +12 -2
  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 -0
  153. package/node_modules/pi-lens/dist/clients/file-kinds.js +1 -38
  154. package/node_modules/pi-lens/dist/clients/file-role.js +18 -1
  155. package/node_modules/pi-lens/dist/clients/file-time.js +1 -1
  156. package/node_modules/pi-lens/dist/clients/file-utils.js +5 -1
  157. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +4 -0
  158. package/node_modules/pi-lens/dist/clients/format-service.js +0 -1
  159. package/node_modules/pi-lens/dist/clients/formatters.js +66 -93
  160. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +3 -4
  161. package/node_modules/pi-lens/dist/clients/generation-guard.js +4 -0
  162. package/node_modules/pi-lens/dist/clients/git-guard.js +5 -0
  163. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +42 -6
  164. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +48 -7
  165. package/node_modules/pi-lens/dist/clients/grammar-source.js +3 -3
  166. package/node_modules/pi-lens/dist/clients/installer/index.js +416 -61
  167. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +84 -18
  168. package/node_modules/pi-lens/dist/clients/instance-reaper.js +6 -6
  169. package/node_modules/pi-lens/dist/clients/jscpd-client.js +9 -1
  170. package/node_modules/pi-lens/dist/clients/knip-client.js +9 -2
  171. package/node_modules/pi-lens/dist/clients/language-profile.js +12 -11
  172. package/node_modules/pi-lens/dist/clients/language-registry.js +5 -1
  173. package/node_modules/pi-lens/dist/clients/latency-logger.js +7 -7
  174. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -1
  175. package/node_modules/pi-lens/dist/clients/lens-config.js +23 -0
  176. package/node_modules/pi-lens/dist/clients/lens-engine.js +3 -4
  177. package/node_modules/pi-lens/dist/clients/lens-events.js +1 -1
  178. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +18 -0
  179. package/node_modules/pi-lens/dist/clients/lens-map.js +1 -1
  180. package/node_modules/pi-lens/dist/clients/log-cleanup.js +0 -36
  181. package/node_modules/pi-lens/dist/clients/lsp/client.js +4 -3
  182. package/node_modules/pi-lens/dist/clients/lsp/config.js +37 -7
  183. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +24 -1
  184. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +79 -1
  185. package/node_modules/pi-lens/dist/clients/lsp/edits.js +3 -3
  186. package/node_modules/pi-lens/dist/clients/lsp/index.js +491 -64
  187. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +1 -1
  188. package/node_modules/pi-lens/dist/clients/lsp/language.js +0 -8
  189. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
  190. package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +0 -2
  191. package/node_modules/pi-lens/dist/clients/lsp/server.js +62 -15
  192. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +0 -1
  193. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +5 -5
  194. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +6 -0
  195. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +2 -1
  196. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +37 -4
  197. package/node_modules/pi-lens/dist/clients/lsp-document-symbols.js +1 -1
  198. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +3 -0
  199. package/node_modules/pi-lens/dist/clients/mcp/session.js +2 -0
  200. package/node_modules/pi-lens/dist/clients/metrics-history.js +28 -107
  201. package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +1 -1
  202. package/node_modules/pi-lens/dist/clients/module-report.js +1 -1
  203. package/node_modules/pi-lens/dist/clients/mutating-tool.js +3 -3
  204. package/node_modules/pi-lens/dist/clients/mutation-attribution.js +3 -3
  205. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +0 -4
  206. package/node_modules/pi-lens/dist/clients/observed-mutation.js +9 -9
  207. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +2 -6
  208. package/node_modules/pi-lens/dist/clients/opengrep-client.js +2 -0
  209. package/node_modules/pi-lens/dist/clients/opengrep-config.js +1 -1
  210. package/node_modules/pi-lens/dist/clients/package-manager.js +2 -1
  211. package/node_modules/pi-lens/dist/clients/path-utils.js +23 -2
  212. package/node_modules/pi-lens/dist/clients/performance-report.js +2 -2
  213. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +9 -4
  214. package/node_modules/pi-lens/dist/clients/pipeline.js +5 -4
  215. package/node_modules/pi-lens/dist/clients/project-changes.js +1 -1
  216. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +1 -1
  217. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +38 -10
  218. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +10 -9
  219. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/jscpd.js +1 -1
  220. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/knip.js +1 -1
  221. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +1 -1
  222. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/opengrep.js +1 -1
  223. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +1 -1
  224. package/node_modules/pi-lens/dist/clients/project-lens-config.js +49 -1
  225. package/node_modules/pi-lens/dist/clients/project-scan-policy.js +3 -51
  226. package/node_modules/pi-lens/dist/clients/python-provenance.js +1 -1
  227. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +3 -1
  228. package/node_modules/pi-lens/dist/clients/read-guard.js +5 -2
  229. package/node_modules/pi-lens/dist/clients/recent-touches.js +2 -2
  230. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +3 -5
  231. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +1 -1
  232. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +2 -0
  233. package/node_modules/pi-lens/dist/clients/ruff-client.js +18 -9
  234. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +10 -1
  235. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +5 -0
  236. package/node_modules/pi-lens/dist/clients/runtime-session.js +58 -14
  237. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +11 -1
  238. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +4 -1
  239. package/node_modules/pi-lens/dist/clients/runtime-turn.js +48 -1
  240. package/node_modules/pi-lens/dist/clients/sanitize.js +0 -11
  241. package/node_modules/pi-lens/dist/clients/scan-utils.js +1 -56
  242. package/node_modules/pi-lens/dist/clients/security-scan-client.js +2 -2
  243. package/node_modules/pi-lens/dist/clients/session-event-guard.js +69 -1
  244. package/node_modules/pi-lens/dist/clients/session-summary.js +0 -24
  245. package/node_modules/pi-lens/dist/clients/sg-runner.js +2 -1
  246. package/node_modules/pi-lens/dist/clients/sgconfig.js +1 -5
  247. package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +1 -1
  248. package/node_modules/pi-lens/dist/clients/situational-tool-telemetry.js +108 -0
  249. package/node_modules/pi-lens/dist/clients/smells-rollup.js +2 -2
  250. package/node_modules/pi-lens/dist/clients/startup-scan.js +2 -10
  251. package/node_modules/pi-lens/dist/clients/test-runner-client.js +291 -76
  252. package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +77 -72
  253. package/node_modules/pi-lens/dist/clients/tool-config.js +199 -0
  254. package/node_modules/pi-lens/dist/clients/tool-cwd.js +299 -0
  255. package/node_modules/pi-lens/dist/clients/tool-policy.js +1 -1
  256. package/node_modules/pi-lens/dist/clients/tool-probe.js +53 -0
  257. package/node_modules/pi-lens/dist/clients/tool-set-policy.js +42 -0
  258. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +1 -52
  259. package/node_modules/pi-lens/dist/clients/trivy-client.js +9 -1
  260. package/node_modules/pi-lens/dist/clients/tui-fit.js +0 -4
  261. package/node_modules/pi-lens/dist/clients/turn-context.js +52 -0
  262. package/node_modules/pi-lens/dist/clients/user-notify.js +0 -4
  263. package/node_modules/pi-lens/dist/clients/widget-state.js +4 -3
  264. package/node_modules/pi-lens/dist/clients/word-index-logger.js +0 -7
  265. package/node_modules/pi-lens/dist/clients/word-index-store.js +3 -7
  266. package/node_modules/pi-lens/dist/clients/word-index.js +9 -16
  267. package/node_modules/pi-lens/dist/clients/zizmor-config.js +1 -1
  268. package/node_modules/pi-lens/dist/index.js +19969 -17481
  269. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +3 -2
  270. package/node_modules/pi-lens/dist/mcp/build-staleness.js +1 -1
  271. package/node_modules/pi-lens/dist/mcp/server.js +135 -159
  272. package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +1 -1
  273. package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +1 -1
  274. package/node_modules/pi-lens/dist/tools/activate-tools.js +10 -7
  275. package/node_modules/pi-lens/dist/tools/ast-grep-outline.js +2 -13
  276. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +8 -13
  277. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +83 -39
  278. package/node_modules/pi-lens/dist/tools/effective-config.js +4 -3
  279. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +1 -10
  280. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +354 -86
  281. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +54 -112
  282. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +20 -44
  283. package/node_modules/pi-lens/dist/tools/lsp-structured-output.js +3 -3
  284. package/node_modules/pi-lens/dist/tools/module-report.js +5 -9
  285. package/node_modules/pi-lens/dist/tools/project-report.js +9 -17
  286. package/node_modules/pi-lens/dist/tools/render-compact.js +281 -2
  287. package/node_modules/pi-lens/dist/tools/symbol-search.js +3 -4
  288. package/node_modules/pi-lens/docs/agent-guide.md +3 -4
  289. package/node_modules/pi-lens/docs/agent-tools.md +46 -22
  290. package/node_modules/pi-lens/docs/configuration.md +9 -0
  291. package/node_modules/pi-lens/docs/environment-variables.md +0 -5
  292. package/node_modules/pi-lens/docs/features.md +5 -5
  293. package/node_modules/pi-lens/docs/globalconfig.md +31 -1
  294. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +9 -1
  295. package/node_modules/pi-lens/docs/mcp.md +13 -1
  296. package/node_modules/pi-lens/docs/pi-lens-fixer.md +71 -0
  297. package/node_modules/pi-lens/docs/pi-lens-investigator.md +15 -0
  298. package/node_modules/pi-lens/docs/pi-lens-monitor.md +88 -0
  299. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +41 -0
  300. package/node_modules/pi-lens/docs/pi-lens-subagent.md +21 -2
  301. package/node_modules/pi-lens/docs/public-api-stability.md +1 -0
  302. package/node_modules/pi-lens/docs/real-harness.md +46 -0
  303. package/node_modules/pi-lens/docs/release-qa-baseline.md +17 -5
  304. package/node_modules/pi-lens/docs/servercapabilities.md +2 -3
  305. package/node_modules/pi-lens/docs/settings.md +37 -2
  306. package/node_modules/pi-lens/docs/tools_improvement2.md +4 -4
  307. package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +1 -1
  308. package/node_modules/pi-lens/docs/usage.md +13 -3
  309. package/node_modules/pi-lens/docs/word-index.md +1 -1
  310. package/node_modules/pi-lens/package.json +15 -15
  311. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/sql-injection.yml +7 -3
  312. package/node_modules/pi-lens/rules/typos/_typos.toml +4 -2
  313. package/node_modules/pi-lens/scripts/lib/skills-predicate.mjs +1 -1
  314. package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +1 -1
  315. package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +4 -2
  316. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +8 -6
  317. package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +19 -9
  318. package/node_modules/pi-web-access/CHANGELOG.md +29 -0
  319. package/node_modules/pi-web-access/README.md +33 -22
  320. package/node_modules/pi-web-access/abortable.ts +17 -0
  321. package/node_modules/pi-web-access/crawl4ai.ts +204 -0
  322. package/node_modules/pi-web-access/credential-source.ts +1 -0
  323. package/node_modules/pi-web-access/curator-page.ts +20 -2
  324. package/node_modules/pi-web-access/curator-run.ts +44 -0
  325. package/node_modules/pi-web-access/curator-server.ts +3 -1
  326. package/node_modules/pi-web-access/duckduckgo.ts +1 -1
  327. package/node_modules/pi-web-access/extract.ts +57 -12
  328. package/node_modules/pi-web-access/fetch-params.ts +1 -1
  329. package/node_modules/pi-web-access/gemini-search.ts +9 -5
  330. package/node_modules/pi-web-access/index.ts +25 -18
  331. package/node_modules/pi-web-access/openai-search.ts +35 -13
  332. package/node_modules/pi-web-access/package.json +2 -2
  333. package/node_modules/pi-web-access/page-query.ts +5 -2
  334. package/node_modules/pi-web-access/query-rewrite.ts +5 -2
  335. package/node_modules/pi-web-access/serpapi.ts +220 -0
  336. package/node_modules/pi-web-access/ssrf-protection.ts +5 -1
  337. package/node_modules/pi-web-access/storage.ts +10 -0
  338. package/node_modules/pi-web-access/summary-review.ts +34 -21
  339. package/node_modules/pi-web-access/utils.ts +6 -4
  340. package/package.json +7 -4
  341. package/node_modules/pi-lens/dist/tools/ast-dump.js +0 -103
@@ -66,6 +66,7 @@ import { commitDurableStoreAsync } from "../durable-store.js";
66
66
  import { getGlobalPiLensDir } from "../file-utils.js";
67
67
  import { allAvailableGlobalBinDirs, installArgs, pmBinary, resolveNodePackageManager, } from "../package-manager.js";
68
68
  import { resetSafeSpawnWindowsCommandCache, safeSpawnAsync, } from "../safe-spawn.js";
69
+ import { probeToolAsync } from "../tool-probe.js";
69
70
  import { logSessionStart } from "../sessionstart-logger.js";
70
71
  // Global installation directory for pi-lens tools
71
72
  const TOOLS_DIR = path.join(getGlobalPiLensDir(), "tools");
@@ -227,6 +228,114 @@ const OS_ARCH_ZIP_ASSETS = {
227
228
  darwin: { x64: "darwin_amd64.zip", arm64: "darwin_arm64.zip" },
228
229
  win32: { x64: "windows_amd64.zip", arm64: "windows_arm64.zip" },
229
230
  };
231
+ function managedPackageFormatterTool(spec) {
232
+ return {
233
+ id: spec.id,
234
+ name: spec.name,
235
+ checkCommand: spec.id,
236
+ checkArgs: ["--version"],
237
+ installStrategy: spec.installStrategy,
238
+ packageName: spec.packageName,
239
+ binaryName: spec.id,
240
+ };
241
+ }
242
+ const MANAGED_PACKAGE_FORMATTERS = [
243
+ { id: "black", name: "Black", installStrategy: "pip", packageName: "black" },
244
+ {
245
+ id: "cmake-format",
246
+ name: "cmake-format",
247
+ installStrategy: "pip",
248
+ packageName: "cmakelang",
249
+ },
250
+ { id: "oxfmt", name: "oxfmt", installStrategy: "npm", packageName: "oxfmt" },
251
+ ];
252
+ function managedGitHubFormatterTool(spec) {
253
+ return {
254
+ id: spec.id,
255
+ name: spec.name,
256
+ checkCommand: spec.id,
257
+ checkArgs: ["--version"],
258
+ installStrategy: "github",
259
+ binaryName: spec.id,
260
+ github: {
261
+ repo: `${spec.owner}/${spec.repo}`,
262
+ assetMatch: spec.assetPattern,
263
+ ...(spec.binaryInArchive && { binaryInArchive: spec.binaryInArchive }),
264
+ ...(spec.kind === "phar" && { launcher: "php" }),
265
+ ...(spec.kind === "jar" && { launcher: "java" }),
266
+ },
267
+ };
268
+ }
269
+ const MANAGED_GITHUB_FORMATTERS = [
270
+ {
271
+ id: "stylua",
272
+ name: "StyLua",
273
+ owner: "JohnnyMorganz",
274
+ repo: "StyLua",
275
+ assetPattern: archAssetMatch({
276
+ linux: { x64: "linux-x86_64.zip", arm64: "linux-aarch64.zip" },
277
+ darwin: { x64: "macos-x86_64.zip", arm64: "macos-aarch64.zip" },
278
+ win32: { x64: "windows-x86_64.zip" },
279
+ }),
280
+ kind: "binary",
281
+ binaryInArchive: "stylua",
282
+ },
283
+ {
284
+ id: "php-cs-fixer",
285
+ name: "PHP CS Fixer",
286
+ owner: "PHP-CS-Fixer",
287
+ repo: "PHP-CS-Fixer",
288
+ assetPattern: (platform) => platform === "linux" || platform === "darwin" || platform === "win32"
289
+ ? "php-cs-fixer.phar"
290
+ : undefined,
291
+ kind: "phar",
292
+ },
293
+ {
294
+ id: "cljfmt",
295
+ name: "cljfmt",
296
+ owner: "weavejester",
297
+ repo: "cljfmt",
298
+ assetPattern: (platform, arch) => {
299
+ if (platform === "linux")
300
+ return arch === "arm64"
301
+ ? "standalone.jar"
302
+ : "linux-amd64-static.tar.gz";
303
+ if (platform === "darwin")
304
+ return "standalone.jar";
305
+ if (platform === "win32")
306
+ return "win-amd64.zip";
307
+ return undefined;
308
+ },
309
+ kind: "jar",
310
+ binaryInArchive: "cljfmt",
311
+ },
312
+ ];
313
+ const MANAGED_MAVEN_FORMATTERS = [
314
+ {
315
+ id: "google-java-format",
316
+ name: "google-java-format",
317
+ groupId: "com.google.googlejavaformat",
318
+ artifactId: "google-java-format",
319
+ version: "1.27.0",
320
+ classifier: "all-deps",
321
+ },
322
+ ];
323
+ function managedMavenFormatterTool(spec) {
324
+ return {
325
+ id: spec.id,
326
+ name: spec.name,
327
+ checkCommand: spec.id,
328
+ checkArgs: ["--version"],
329
+ installStrategy: "maven",
330
+ binaryName: spec.id,
331
+ maven: {
332
+ groupId: spec.groupId,
333
+ artifactId: spec.artifactId,
334
+ version: spec.version,
335
+ classifier: spec.classifier,
336
+ },
337
+ };
338
+ }
230
339
  export const TOOLS = [
231
340
  // Core LSP servers
232
341
  {
@@ -275,6 +384,7 @@ export const TOOLS = [
275
384
  packageName: "prettier",
276
385
  binaryName: "prettier",
277
386
  },
387
+ ...MANAGED_PACKAGE_FORMATTERS.map(managedPackageFormatterTool),
278
388
  {
279
389
  id: "ruff",
280
390
  name: "Ruff",
@@ -548,10 +658,27 @@ export const TOOLS = [
548
658
  binaryName: "docker-langserver",
549
659
  },
550
660
  {
661
+ // #2722: intelephense has no CLI — its entry calls createConnection()
662
+ // unconditionally — and Node prints the offending source line before the
663
+ // error. The bundle is one ~4 MB minified line, so the transport-required
664
+ // marker #208 rescues on lands at byte 4,154,741 of a 4,423,356-byte
665
+ // stderr while Node truncates piped stderr at 1 MiB on exit. Measured on
666
+ // intelephense@1.18.5, linux, Node v22.22.1:
667
+ // $ intelephense --version 2>&1 | wc -c -> 1048576
668
+ // $ intelephense --version 2>&1 | grep -c "Connection input stream is not set" -> 0
669
+ // $ intelephense --version 2>err.txt; wc -c < err.txt -> 4423356
670
+ // Same run over the alternatives, each with stdin closed the way
671
+ // verifyToolBinary closes it (`input: ""`):
672
+ // --help, -v, --socket=0 -> identical dump (exit 1, 4423356 bytes,
673
+ // marker at 4154741, 1048576 through a pipe, marker absent)
674
+ // --stdio -> exit 1 with ZERO bytes on either stream,
675
+ // so it carries no marker to rescue on either.
676
+ // No checkArgs value produces a verdict. Verified spawn-free instead.
551
677
  id: "intelephense",
552
678
  name: "Intelephense",
553
679
  checkCommand: "intelephense",
554
680
  checkArgs: ["--version"],
681
+ verification: "package-entry",
555
682
  installStrategy: "npm",
556
683
  packageName: "intelephense",
557
684
  binaryName: "intelephense",
@@ -690,6 +817,8 @@ export const TOOLS = [
690
817
  // bare binary, no archive
691
818
  },
692
819
  },
820
+ ...MANAGED_GITHUB_FORMATTERS.map(managedGitHubFormatterTool),
821
+ ...MANAGED_MAVEN_FORMATTERS.map(managedMavenFormatterTool),
693
822
  {
694
823
  id: "rust-analyzer",
695
824
  name: "rust-analyzer",
@@ -1616,29 +1745,16 @@ function isAstGrepVersionOutput(output) {
1616
1745
  return /\bast[- ]grep\b/i.test(output);
1617
1746
  }
1618
1747
  async function verifyAstGrepProbePath(binPath) {
1619
- return new Promise((resolve) => {
1620
- let proc;
1621
- try {
1622
- proc = spawn(binPath, ["--version"], {
1623
- stdio: ["ignore", "pipe", "pipe"],
1624
- shell: process.platform === "win32" && /\.(cmd|bat)$/i.test(binPath),
1625
- timeout: 5000,
1626
- });
1627
- }
1628
- catch {
1629
- // SYNCHRONOUS spawn throw (Windows `spawn UNKNOWN`/EINVAL, the pidusage
1630
- // bug class, #533) — best-effort probe, resolve rather than reject.
1631
- resolve(false);
1632
- return;
1633
- }
1634
- let output = "";
1635
- proc.stdout?.on("data", (data) => (output += data));
1636
- proc.stderr?.on("data", (data) => (output += data));
1637
- proc.on("exit", (code) => {
1638
- resolve(code === 0 && isAstGrepVersionOutput(output));
1639
- });
1640
- proc.on("error", () => resolve(false));
1748
+ // #2894: through the probe seam rather than a hand-rolled `spawn` promise.
1749
+ // `probeToolAsync` already owns the synchronous-throw case (#533), the
1750
+ // Windows `.cmd`/`.bat` resolution this used `shell: true` for, and the
1751
+ // timeout tree-kill a bare `spawn({ timeout })` does not do.
1752
+ const result = await probeToolAsync(binPath, ["--version"], {
1753
+ timeout: 5000,
1641
1754
  });
1755
+ return (!result.error &&
1756
+ result.status === 0 &&
1757
+ isAstGrepVersionOutput(`${result.stdout}${result.stderr}`));
1642
1758
  }
1643
1759
  // Exported for testing only.
1644
1760
  export async function checkProbeCache(toolId) {
@@ -1851,6 +1967,111 @@ function extractVersionToken(output) {
1851
1967
  * maven/archive) never populate or read this map.
1852
1968
  */
1853
1969
  const lastManagedInstallVersion = new Map();
1970
+ /**
1971
+ * The package whose entry module verifies `tool`'s managed install spawn-free,
1972
+ * or undefined when the tool is verified by spawning `checkArgs` (#2722).
1973
+ * Registry-declared per tool — never inferred from the strategy: a spawn probe
1974
+ * is the stronger check everywhere it can actually return a verdict.
1975
+ */
1976
+ export function packageEntryVerification(tool) {
1977
+ return tool.verification === "package-entry" ? tool.packageName : undefined;
1978
+ }
1979
+ /**
1980
+ * Spawn-free verification of a managed npm install (#2722).
1981
+ *
1982
+ * For an stdio LSP server with no CLI surface, every `checkArgs` value is a
1983
+ * throw, and #208's transport-required rescue is output-ORDER dependent: Node
1984
+ * prints the offending source line ahead of the error, so a server whose bundle
1985
+ * is megabytes of minified JS pushes the marker past both our retained window
1986
+ * and Node's own 1 MiB pipe truncation. The bytes never leave the child, so no
1987
+ * spawn can answer "is this install intact".
1988
+ *
1989
+ * The evidence used instead is the same class the `archive` strategy already
1990
+ * accepts through `treeMarker`: the installed tree is inspected on disk. The
1991
+ * install verifies when the package directory beside the `.bin` shim carries a
1992
+ * readable `package.json` with a `version`, and the entry module that
1993
+ * package.json itself names — `bin[<shim name>]` or a bare `bin` string —
1994
+ * exists as a non-empty file. `main` is deliberately NOT a fallback: npm
1995
+ * creates the `node_modules/.bin/<shim>` this function is given only from a
1996
+ * `bin` field, so a manifest without one cannot be the manifest behind this
1997
+ * shim. That is exactly what `verifyToolBinary` was documented to catch here —
1998
+ * broken symlinks and partial installs — and it is strictly more than the
1999
+ * `--version` spawn can report for this class.
2000
+ */
2001
+ export async function verifyNpmPackageEntry(binPath, packageName) {
2002
+ // `<...>/node_modules/.bin/<shim>` → `<...>/node_modules`.
2003
+ const nodeModulesDir = path.dirname(path.dirname(binPath));
2004
+ const pinned = parsePinnedVersion(packageName);
2005
+ const bareName = pinned
2006
+ ? packageName.slice(0, -(pinned.length + 1))
2007
+ : packageName;
2008
+ const packageDir = path.join(nodeModulesDir, ...bareName.split("/"));
2009
+ const shimName = path
2010
+ .basename(binPath)
2011
+ .replace(/\.(cmd|exe|ps1|bat)$/i, "")
2012
+ .toLowerCase();
2013
+ const fail = (reason) => {
2014
+ logSessionStart(`auto-install verify: failed for ${binPath} (check=package-entry, kind=${reason})`);
2015
+ return false;
2016
+ };
2017
+ // R2-F1: the shim ITSELF, before anything else. Every path below is DERIVED
2018
+ // from `binPath`, so without this a partial install — npm never wrote the
2019
+ // `.bin` entry, or wrote one that is empty, a directory, or a symlink
2020
+ // dangling at a deleted target — verified `true` on the strength of a
2021
+ // package tree that nothing can execute, and `installNpmTool` then recorded
2022
+ // the install as SUCCEEDED. `classifyInstallOutcome` grades any non-"failed"
2023
+ // outcome as `⚠ unavailable (succeeded)`, so that answer re-hid exactly the
2024
+ // nightly row #2722 exists to expose. `statSync` FOLLOWS the link, which is
2025
+ // what makes missing and dangling one branch.
2026
+ try {
2027
+ const shim = statSync(binPath);
2028
+ if (!shim.isFile() || shim.size === 0)
2029
+ return fail("shim-not-a-file");
2030
+ }
2031
+ catch {
2032
+ return fail("shim-missing");
2033
+ }
2034
+ let manifest;
2035
+ try {
2036
+ manifest = JSON.parse(await fs.readFile(path.join(packageDir, "package.json"), "utf8"));
2037
+ }
2038
+ catch {
2039
+ return fail("package-json-unreadable");
2040
+ }
2041
+ if (typeof manifest.version !== "string" || !manifest.version) {
2042
+ return fail("package-json-no-version");
2043
+ }
2044
+ const bin = manifest.bin;
2045
+ const entry = typeof bin === "string"
2046
+ ? bin
2047
+ : bin && typeof bin === "object"
2048
+ ? Object.entries(bin).find(
2049
+ // Case-folded on purpose: on a case-insensitive filesystem the
2050
+ // shim on disk can differ in case from the manifest key npm
2051
+ // wrote it from, and `path.basename` reads the disk name.
2052
+ ([name]) => name.toLowerCase() === shimName)?.[1]
2053
+ : undefined;
2054
+ if (typeof entry !== "string" || !entry) {
2055
+ return fail("package-json-no-entry");
2056
+ }
2057
+ try {
2058
+ const stat = statSync(path.join(packageDir, entry));
2059
+ if (!stat.isFile() || stat.size === 0)
2060
+ return fail("entry-not-a-file");
2061
+ }
2062
+ catch {
2063
+ return fail("entry-missing");
2064
+ }
2065
+ // R2-F3 (catalog shape 31): "did the new verifier run at all, and on what"
2066
+ // has to be answerable from sessionstart.log, not only from a debug build —
2067
+ // the failure branches above already log there. Measured volume on a real
2068
+ // intelephense install, scratch PI_LENS_HOME: TWO rows on the session that
2069
+ // installs (installNpmTool's verify, then the post-install resolution), and
2070
+ // ZERO on every later session — the probe cache answers before any verify
2071
+ // runs (`auto-install ensure intelephense: probe cache hit`).
2072
+ logSessionStart(`auto-install verify: succeeded for ${binPath} (check=package-entry, version=${manifest.version}, entry=${entry})`);
2073
+ return true;
2074
+ }
1854
2075
  /**
1855
2076
  * Verify a tool binary actually works by running its configured checkArgs.
1856
2077
  * This catches broken symlinks, partial installs, and corrupted binaries.
@@ -1878,7 +2099,26 @@ onTransient,
1878
2099
  * sensitive caller on the dispatch hot path passes a shorter one and treats
1879
2100
  * the expiry as transient rather than as a verdict (#1657).
1880
2101
  */
1881
- timeoutMs = 10000, verificationArgs = ["--version"]) {
2102
+ timeoutMs = 10000, verificationArgs = ["--version"],
2103
+ /**
2104
+ * Package name from {@link packageEntryVerification} — present only for a
2105
+ * registry entry declaring `verification: "package-entry"`. When present the
2106
+ * spawn is skipped entirely and {@link verifyNpmPackageEntry} answers (#2722).
2107
+ */
2108
+ packageEntryOf,
2109
+ /**
2110
+ * Called when a `false` verdict is INCONCLUSIVE rather than a verdict: the
2111
+ * transport-required matcher was armed, never matched, and the child's
2112
+ * output was cut off before the tail arrived, so the #208 rescue could not
2113
+ * be evaluated at all (#2722). Distinct from `onTransient` on purpose — the
2114
+ * prober DID run to completion and re-probing reproduces it exactly, so this
2115
+ * is a durable "cannot be verified", not a stall (catalog shape 13). A
2116
+ * caller must not treat it as "broken" and delete the installation.
2117
+ */
2118
+ onInconclusive) {
2119
+ if (packageEntryOf !== undefined) {
2120
+ return verifyNpmPackageEntry(binPath, packageEntryOf);
2121
+ }
1882
2122
  // #2015: safeSpawnAsync instead of raw spawn. Raw spawn's timeout
1883
2123
  // SIGTERMed only cmd.exe on Windows (.cmd shims run shell:true), orphaning
1884
2124
  // the grandchild node process - which kept scanning, held handles against
@@ -1937,6 +2177,31 @@ timeoutMs = 10000, verificationArgs = ["--version"]) {
1937
2177
  // verdict from the binary (#1569 transient semantics).
1938
2178
  if (result.signal !== undefined || result.spawnFailure)
1939
2179
  onTransient?.();
2180
+ if (result.outputTruncated &&
2181
+ result.signal === undefined &&
2182
+ !result.spawnFailure) {
2183
+ // #2722: the transport-required matcher above is armed on EVERY probe,
2184
+ // and it never matched — but the output we kept is a prefix, so the
2185
+ // marker may simply sit past it (intelephense emits ~4 MB of bundle
2186
+ // ahead of its own diagnostic). "Unmatched within a truncated prefix"
2187
+ // is not "this binary is broken"; callers must not delete the install
2188
+ // on it. Recorded as its own kind so a monitor can tell an unreadable
2189
+ // probe apart from a rejected binary.
2190
+ //
2191
+ // R2-F4: gated on the probe having actually FINISHED. A verbose child
2192
+ // that is SIGTERMed at the timeout also arrives here truncated, and
2193
+ // that is the #1569 transient class one line above, not this one —
2194
+ // ungated, the two overlapped and `installNpmTool` (which tests
2195
+ // inconclusive first) replaced the #2015 transient message with this
2196
+ // one. The doc comment's "the prober DID run to completion" is now
2197
+ // true rather than aspirational.
2198
+ onInconclusive?.();
2199
+ recordDegradationOnce({
2200
+ kind: "installer-verification-inconclusive",
2201
+ subject: binPath,
2202
+ reason: `transport-required marker unresolved in truncated output (${verificationArgs.join(" ")})`,
2203
+ });
2204
+ }
1940
2205
  const kind = result.spawnFailure?.kind ??
1941
2206
  (result.signal
1942
2207
  ? `killed-signal=${result.signal}`
@@ -1985,29 +2250,15 @@ export async function getAllToolStatuses() {
1985
2250
  status.installed = true;
1986
2251
  status.source = "global-path";
1987
2252
  status.path = tool.checkCommand;
1988
- // Try to get version
1989
- const versionResult = await new Promise((resolve) => {
1990
- let proc;
1991
- try {
1992
- proc = spawn(tool.checkCommand, ["--version"], {
1993
- stdio: ["ignore", "pipe", "pipe"],
1994
- shell: process.platform === "win32",
1995
- timeout: 5000,
1996
- });
1997
- }
1998
- catch {
1999
- // SYNCHRONOUS spawn throw (Windows `spawn UNKNOWN`/EINVAL, the
2000
- // pidusage bug class, #533) — best-effort, resolve empty version.
2001
- resolve("");
2002
- return;
2003
- }
2004
- let out = "";
2005
- proc.stdout?.on("data", (d) => (out += d));
2006
- proc.stderr?.on("data", (d) => (out += d));
2007
- proc.on("exit", () => resolve(out.trim().split("\n")[0]?.slice(0, 30) || ""));
2008
- proc.on("error", () => resolve(""));
2253
+ // Try to get version — through the probe seam (#2894), which owns the
2254
+ // synchronous-throw case (#533) and the timeout tree-kill that a bare
2255
+ // `spawn({ timeout })` promise did not do.
2256
+ const probe = await probeToolAsync(tool.checkCommand, ["--version"], {
2257
+ timeout: 5000,
2009
2258
  });
2010
- status.version = versionResult || undefined;
2259
+ status.version =
2260
+ `${probe.stdout}${probe.stderr}`.trim().split("\n")[0]?.slice(0, 30) ||
2261
+ undefined;
2011
2262
  statuses.push(status);
2012
2263
  continue;
2013
2264
  }
@@ -2056,7 +2307,7 @@ export async function getAllToolStatuses() {
2056
2307
  const localPath = installerPlatform() === "win32" ? `${localBase}.cmd` : localBase;
2057
2308
  try {
2058
2309
  await fs.access(localPath);
2059
- if (await verifyToolBinary(localPath, undefined, undefined, getToolVerificationTimeout(tool), tool.checkArgs)) {
2310
+ if (await verifyToolBinary(localPath, undefined, undefined, getToolVerificationTimeout(tool), tool.checkArgs, packageEntryVerification(tool))) {
2060
2311
  status.installed = true;
2061
2312
  status.source = "pi-lens-auto";
2062
2313
  status.path = localPath;
@@ -2225,7 +2476,7 @@ async function getToolPathResolved(toolId, onTransient) {
2225
2476
  const cmdPath = `${localBase}.cmd`;
2226
2477
  try {
2227
2478
  await fs.access(cmdPath);
2228
- if (await verifyToolBinary(cmdPath, recordVersion, onTransient, getToolVerificationTimeout(tool), tool.checkArgs)) {
2479
+ if (await verifyToolBinary(cmdPath, recordVersion, onTransient, getToolVerificationTimeout(tool), tool.checkArgs, packageEntryVerification(tool))) {
2229
2480
  return cmdPath;
2230
2481
  }
2231
2482
  logSessionStart(`auto-install verify: ${cmdPath} exists but is broken, will reinstall`);
@@ -2238,7 +2489,7 @@ async function getToolPathResolved(toolId, onTransient) {
2238
2489
  const exePath = `${localBase}.exe`;
2239
2490
  try {
2240
2491
  await fs.access(exePath);
2241
- if (await verifyToolBinary(exePath, recordVersion, onTransient, getToolVerificationTimeout(tool), tool.checkArgs)) {
2492
+ if (await verifyToolBinary(exePath, recordVersion, onTransient, getToolVerificationTimeout(tool), tool.checkArgs, packageEntryVerification(tool))) {
2242
2493
  return exePath;
2243
2494
  }
2244
2495
  logSessionStart(`auto-install verify: ${exePath} exists but is broken, will reinstall`);
@@ -2250,7 +2501,7 @@ async function getToolPathResolved(toolId, onTransient) {
2250
2501
  if (installerPlatform() !== "win32") {
2251
2502
  try {
2252
2503
  await fs.access(localBase);
2253
- if (await verifyToolBinary(localBase, recordVersion, onTransient, getToolVerificationTimeout(tool), tool.checkArgs)) {
2504
+ if (await verifyToolBinary(localBase, recordVersion, onTransient, getToolVerificationTimeout(tool), tool.checkArgs, packageEntryVerification(tool))) {
2254
2505
  return localBase;
2255
2506
  }
2256
2507
  logSessionStart(`auto-install verify: ${localBase} exists but is broken, will reinstall`);
@@ -2349,6 +2600,13 @@ function getGitHubInstalledBinaryName(binaryName, platform, assetName) {
2349
2600
  return `${binaryName}.cmd`;
2350
2601
  return `${binaryName}.exe`;
2351
2602
  }
2603
+ function launcherForGitHubAsset(spec, assetName) {
2604
+ if (spec.launcher === "php" && assetName.endsWith(".phar"))
2605
+ return "php";
2606
+ if (spec.launcher === "java" && assetName.endsWith(".jar"))
2607
+ return "java";
2608
+ return undefined;
2609
+ }
2352
2610
  function getArchiveBinaryCandidates(binaryName, platform, assetName) {
2353
2611
  if (platform !== "win32")
2354
2612
  return [binaryName];
@@ -2701,10 +2959,35 @@ prefetchedRelease) {
2701
2959
  const binaryName = tool.binaryName ?? tool.id;
2702
2960
  const isWindows = platform === "win32";
2703
2961
  const finalBinaryName = getGitHubInstalledBinaryName(binaryName, platform, asset.name);
2704
- const destPath = path.join(GITHUB_BIN_DIR, finalBinaryName);
2962
+ let destPath = path.join(GITHUB_BIN_DIR, finalBinaryName);
2705
2963
  const assetName = asset.name;
2964
+ const launcherRuntime = launcherForGitHubAsset(spec, assetName);
2965
+ if (launcherRuntime &&
2966
+ !(await isCommandAvailable(launcherRuntime, ["--version"]))) {
2967
+ logSessionStart(`github-install ${tool.id}: ${launcherRuntime} not found — asset requires a runtime launcher`);
2968
+ return undefined;
2969
+ }
2706
2970
  try {
2707
- if (assetName.endsWith(".gz") && !assetName.endsWith(".tar.gz")) {
2971
+ if (launcherRuntime) {
2972
+ const assetPath = path.join(GITHUB_BIN_DIR, `${tool.id}${assetName.endsWith(".phar") ? ".phar" : ".jar"}`);
2973
+ await writeFileAtomicAsync(assetPath, assetBuffer, {
2974
+ bestEffort: false,
2975
+ mode: 0o750,
2976
+ });
2977
+ const launcherName = isWindows ? `${binaryName}.bat` : binaryName;
2978
+ destPath = path.join(GITHUB_BIN_DIR, launcherName);
2979
+ const runtimeTarget = assetName.endsWith(".phar")
2980
+ ? `${tool.id}.phar`
2981
+ : `${tool.id}.jar`;
2982
+ const command = isWindows
2983
+ ? `@echo off\r\n${launcherRuntime} "%~dp0${runtimeTarget}" %*\r\n`
2984
+ : `#!/bin/sh\nexec ${launcherRuntime} "$(dirname "$0")/${runtimeTarget}" "$@"\n`;
2985
+ await writeFileAtomicAsync(destPath, command, {
2986
+ bestEffort: false,
2987
+ mode: isWindows ? undefined : 0o750,
2988
+ });
2989
+ }
2990
+ else if (assetName.endsWith(".gz") && !assetName.endsWith(".tar.gz")) {
2708
2991
  // Bare gzip (e.g. rust-analyzer-x86_64-unknown-linux-gnu.gz) — decompress directly
2709
2992
  const decompressed = await new Promise((resolve, reject) => {
2710
2993
  const gunzip = createGunzip();
@@ -2873,6 +3156,7 @@ export function npmToolNeedsPostinstall(packageName) {
2873
3156
  * that wanders off it.
2874
3157
  */
2875
3158
  export function getRefreshableManagedNpmTools() {
3159
+ const seenPackages = new Set();
2876
3160
  return getRefreshableManagedTools()
2877
3161
  .filter((candidate) => candidate.strategy === "npm")
2878
3162
  .map((candidate) => ({
@@ -2881,7 +3165,13 @@ export function getRefreshableManagedNpmTools() {
2881
3165
  // have both, so these are total.
2882
3166
  packageName: candidate.packageName,
2883
3167
  binaryName: candidate.binaryName,
2884
- }));
3168
+ }))
3169
+ .filter((candidate) => {
3170
+ if (seenPackages.has(candidate.packageName))
3171
+ return false;
3172
+ seenPackages.add(candidate.packageName);
3173
+ return true;
3174
+ });
2885
3175
  }
2886
3176
  /** The Maven GAV a `maven` entry pins, as a stable identity string. */
2887
3177
  function mavenCoordinate(spec) {
@@ -2928,6 +3218,7 @@ export function getRefreshableManagedTools() {
2928
3218
  packageName: tool.packageName,
2929
3219
  binaryName: tool.binaryName,
2930
3220
  verificationTimeoutMs: tool.verificationTimeoutMs,
3221
+ packageEntryOf: packageEntryVerification(tool),
2931
3222
  });
2932
3223
  break;
2933
3224
  }
@@ -3046,7 +3337,7 @@ async function probeManagedToolVersion(tool) {
3046
3337
  if (!cached?.path || !existsSync(cached.path))
3047
3338
  return undefined;
3048
3339
  try {
3049
- const result = await safeSpawnAsync(cached.path, tool.checkArgs, {
3340
+ const result = await probeToolAsync(cached.path, tool.checkArgs, {
3050
3341
  timeout: getToolVerificationTimeout(tool),
3051
3342
  input: "",
3052
3343
  ignoreAmbientSignal: true,
@@ -3752,12 +4043,20 @@ async function installArchiveTool(tool) {
3752
4043
  * generic fallback `finishInstallAttempt` uses when nothing set it.
3753
4044
  */
3754
4045
  function recordPackageManagerInstallException(toolId, strategyLabel, packageName, err) {
3755
- const message = err.message;
4046
+ const message = boundInstallError(err.message);
3756
4047
  logSessionStart(`auto-install ${strategyLabel} ${packageName}: exception: ${message}`);
3757
4048
  installFailureReasons.set(toolId, message);
3758
4049
  return undefined;
3759
4050
  }
3760
- async function installNpmTool(toolId, packageName, binaryName, verificationArgs = ["--version"], verificationTimeoutMs = 10_000) {
4051
+ const INSTALL_ERROR_LINE_LIMIT = 1000;
4052
+ const INSTALL_CANDIDATE_ERROR_LIMIT = 200;
4053
+ function boundInstallError(value, limit = INSTALL_ERROR_LINE_LIMIT) {
4054
+ const line = value.replace(/[\r\n]+/g, " ").trim();
4055
+ return line.length > limit ? `${line.slice(0, limit - 3)}...` : line;
4056
+ }
4057
+ async function installNpmTool(toolId, packageName, binaryName, verificationArgs = ["--version"], verificationTimeoutMs = 10_000,
4058
+ /** See {@link packageEntryVerification} — spawn-free verification (#2722). */
4059
+ packageEntryOf) {
3761
4060
  try {
3762
4061
  // Ensure tools directory exists
3763
4062
  await fs.mkdir(TOOLS_DIR, { recursive: true });
@@ -3837,11 +4136,15 @@ async function installNpmTool(toolId, packageName, binaryName, verificationArgs
3837
4136
  debugLog(`Verifying ${binaryName}...`);
3838
4137
  let isValid = false;
3839
4138
  let lastAttemptTransient = false;
4139
+ let lastAttemptInconclusive = false;
3840
4140
  for (let attempt = 1; attempt <= 3; attempt++) {
3841
4141
  lastAttemptTransient = false;
4142
+ lastAttemptInconclusive = false;
3842
4143
  isValid = await verifyToolBinary(binPath, undefined, () => {
3843
4144
  lastAttemptTransient = true;
3844
- }, verificationTimeoutMs, verificationArgs);
4145
+ }, verificationTimeoutMs, verificationArgs, packageEntryOf, () => {
4146
+ lastAttemptInconclusive = true;
4147
+ });
3845
4148
  if (isValid)
3846
4149
  break;
3847
4150
  if (attempt < 3) {
@@ -3849,6 +4152,37 @@ async function installNpmTool(toolId, packageName, binaryName, verificationArgs
3849
4152
  await new Promise((r) => setTimeout(r, 1000 * attempt));
3850
4153
  }
3851
4154
  }
4155
+ if (!isValid && lastAttemptInconclusive) {
4156
+ // #2722: the probe ran to completion but could not decide — the #208
4157
+ // transport-required matcher was armed, never matched, and the kept
4158
+ // output is a truncated prefix, so the marker may sit past it (the
4159
+ // class intelephense fell into: ~4 MB of bundle ahead of the marker,
4160
+ // unreachable through any pipe). Same rule as the transient branch
4161
+ // below (#2015): a non-verdict must not delete a freshly installed
4162
+ // package. Deliberately NOT folded into `lastAttemptTransient` —
4163
+ // re-probing reproduces this exactly, so it is durable, and a
4164
+ // transient verdict would keep re-arming the reinstall path
4165
+ // (catalog shape 13).
4166
+ // R2-F2: a non-verdict is a reason to keep ONLY while the tree on disk
4167
+ // still looks like a complete install. With intelephense verified
4168
+ // spawn-free, no registry entry reaches this branch healthy (measured:
4169
+ // no other npm-strategy LSP server emits more than 1,527 bytes), so
4170
+ // its live population is BROKEN servers that spew past the retained
4171
+ // window and die — and for those the delete was the only repair that
4172
+ // existed. Measured on npm 9.2.0 against a real managed prefix: a
4173
+ // package file corrupted IN PLACE is not repaired by re-installing
4174
+ // (`up to date`, the zeroed file stays zeroed), while a package
4175
+ // directory, a `.bin` shim or a nested dependency that is GONE is
4176
+ // reinstalled. So the same on-disk evidence `verification:
4177
+ // "package-entry"` uses decides keep-vs-delete here — strictly as a
4178
+ // gate, never as a verdict: the install is still recorded as failed,
4179
+ // so the tool still reads `✗` and nothing is re-hidden.
4180
+ if (await verifyNpmPackageEntry(binPath, packageName)) {
4181
+ logSessionStart(`auto-install ${packageName}: verification inconclusive (output truncated before the transport-required marker) but the installed tree is intact; keeping installation for re-probe`);
4182
+ return undefined;
4183
+ }
4184
+ logSessionStart(`auto-install ${packageName}: verification inconclusive AND the installed tree is incomplete; cleaning up so the next install can repair it`);
4185
+ }
3852
4186
  if (!isValid && lastAttemptTransient) {
3853
4187
  // #2015: a killed/spawn-failed prober is NOT a verdict about the
3854
4188
  // binary (#1569 semantics). Cold-start verifies on fresh installs
@@ -3925,7 +4259,7 @@ options = {}) {
3925
4259
  ? [...verb, packageName]
3926
4260
  : ["-m", "pip", ...verb, packageName],
3927
4261
  }));
3928
- let lastError = "";
4262
+ const errors = [];
3929
4263
  for (const candidate of pipCandidates) {
3930
4264
  const pipResult = await safeSpawnAsync(candidate.command, candidate.args, {
3931
4265
  timeout: 120_000,
@@ -4015,10 +4349,11 @@ options = {}) {
4015
4349
  }
4016
4350
  return packageName;
4017
4351
  }
4018
- lastError = `${candidate.command} ${candidate.args.join(" ")}: ${outcome.error}`;
4019
- debugLog(`[pip-fallback] ${lastError}`);
4352
+ const candidateError = `${candidate.command} ${candidate.args.join(" ")}: ${boundInstallError(outcome.error, INSTALL_CANDIDATE_ERROR_LIMIT)}`;
4353
+ errors.push(candidateError);
4354
+ debugLog(`[pip-fallback] ${candidateError}`);
4020
4355
  }
4021
- throw new Error(`Failed to install ${packageName}: no usable pip command found (${lastError || "unknown error"})`);
4356
+ throw new Error(`Failed to install ${packageName}: no usable pip command found (${errors.join(" | ") || "unknown error"})`);
4022
4357
  }
4023
4358
  catch (err) {
4024
4359
  return recordPackageManagerInstallException(toolId, "pip", packageName, err);
@@ -4132,7 +4467,7 @@ export async function installTool(toolId) {
4132
4467
  case "npm": {
4133
4468
  if (!tool.packageName || !tool.binaryName)
4134
4469
  return false;
4135
- const npmPath = await installNpmTool(tool.id, tool.packageName, tool.binaryName, tool.checkArgs, getToolVerificationTimeout(tool));
4470
+ const npmPath = await installNpmTool(tool.id, tool.packageName, tool.binaryName, tool.checkArgs, getToolVerificationTimeout(tool), packageEntryVerification(tool));
4136
4471
  if (npmPath !== undefined) {
4137
4472
  // #1746 review F4: an install just resolved this package's range
4138
4473
  // against the registry, so record it as freshly checked. Otherwise a
@@ -4170,6 +4505,12 @@ export async function installTool(toolId) {
4170
4505
  case "github": {
4171
4506
  if (!tool.github)
4172
4507
  return false;
4508
+ if (!tool.github.assetMatch(process.platform, process.arch)) {
4509
+ const reason = `unsupported platform=${process.platform} arch=${process.arch}`;
4510
+ noteInstallAttempt(tool.id, "unavailable", reason);
4511
+ logSessionStart(`auto-install ${tool.id}: ${reason}`);
4512
+ return false;
4513
+ }
4173
4514
  const ghPath = await installGitHubTool(tool);
4174
4515
  return finishInstallAttempt(tool.id, ghPath !== undefined, startedAt);
4175
4516
  }
@@ -4182,6 +4523,12 @@ export async function installTool(toolId) {
4182
4523
  case "archive": {
4183
4524
  if (!tool.archive)
4184
4525
  return false;
4526
+ if (!resolveArchiveUrl(tool.archive)) {
4527
+ const reason = `unsupported platform=${process.platform} arch=${process.arch}`;
4528
+ noteInstallAttempt(tool.id, "unavailable", reason);
4529
+ logSessionStart(`auto-install ${tool.id}: ${reason}`);
4530
+ return false;
4531
+ }
4185
4532
  const archivePath = await installArchiveTool(tool);
4186
4533
  return finishInstallAttempt(tool.id, archivePath !== undefined, startedAt);
4187
4534
  }
@@ -4500,6 +4847,8 @@ export function getToolInstallStrategy(toolId) {
4500
4847
  * "at least one platform" guard instead.
4501
4848
  */
4502
4849
  export const GITHUB_TOOLS = [
4850
+ "cljfmt",
4851
+ "php-cs-fixer",
4503
4852
  "shellcheck",
4504
4853
  "shfmt",
4505
4854
  "rust-analyzer",
@@ -4534,6 +4883,12 @@ export function resolveGitHubAsset(toolId, platform, arch) {
4534
4883
  const tool = TOOLS.find((t) => t.id === toolId);
4535
4884
  return tool?.github?.assetMatch(platform, arch);
4536
4885
  }
4886
+ export function resolveGitHubAssetLauncher(toolId, _platform, assetName) {
4887
+ const tool = TOOLS.find((t) => t.id === toolId);
4888
+ return tool?.github
4889
+ ? launcherForGitHubAsset(tool.github, assetName)
4890
+ : undefined;
4891
+ }
4537
4892
  export function resolveGitHubInstalledBinaryName(toolId, platform, assetName) {
4538
4893
  const tool = TOOLS.find((t) => t.id === toolId);
4539
4894
  if (!tool)