pi-lens 4.1.2 → 4.1.4
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.
- package/CHANGELOG.md +1308 -0
- package/README.md +3 -0
- package/config/biome/core.jsonc +11 -2
- package/config/dependency-cruiser-eager-allowlist.json +91 -0
- package/dist/clients/actionable-warnings-logger.js +18 -3
- package/dist/clients/actionable-warnings.js +927 -80
- package/dist/clients/agent-behavior-client.js +13 -4
- package/dist/clients/ast-grep-client.js +136 -9
- package/dist/clients/ast-grep-rule-manager.js +60 -5
- package/dist/clients/ast-grep-tool-logger.js +2 -2
- package/dist/clients/availability-probe-flight.js +12 -0
- package/dist/clients/bash-file-access.js +1 -3
- package/dist/clients/biome-client.js +16 -2
- package/dist/clients/blocker-freshness.js +34 -0
- package/dist/clients/bootstrap.js +509 -73
- package/dist/clients/bounded-cache.js +152 -12
- package/dist/clients/bounded-telemetry.js +115 -23
- package/dist/clients/build-identity.js +95 -0
- package/dist/clients/bundled-resource-health.js +113 -0
- package/dist/clients/bus-events-logger.js +2 -2
- package/dist/clients/bus-publish.js +3 -0
- package/dist/clients/cache/rule-cache.js +33 -5
- package/dist/clients/cache-manager.js +105 -6
- package/dist/clients/cache-observability.js +46 -19
- package/dist/clients/cargo-manifest.js +422 -0
- package/dist/clients/cascade-logger.js +17 -3
- package/dist/clients/code-quality-warnings.js +13 -3
- package/dist/clients/complexity-client.js +16 -5
- package/dist/clients/config-core/deny.js +221 -0
- package/dist/clients/config-core/index.js +47 -0
- package/dist/clients/config-core/merge.js +357 -0
- package/dist/clients/config-core/normalize.js +340 -0
- package/dist/clients/config-core/process-spec.js +248 -0
- package/dist/clients/config-core/provenance.js +171 -0
- package/dist/clients/config-core/records.js +218 -0
- package/dist/clients/config-core/resolve.js +125 -0
- package/dist/clients/config-core/safe-object.js +78 -0
- package/dist/clients/config-core/schema.js +167 -0
- package/dist/clients/config-diagnostic-codes.js +281 -0
- package/dist/clients/config-locations.js +160 -0
- package/dist/clients/config-resolve.js +789 -0
- package/dist/clients/config-schema.js +197 -0
- package/dist/clients/config-warn.js +407 -0
- package/dist/clients/dead-code-client.js +33 -25
- package/dist/clients/dead-code-logger.js +2 -2
- package/dist/clients/deadline-utils.js +178 -0
- package/dist/clients/debug-handles.js +2 -2
- package/dist/clients/debug-heap.js +3 -3
- package/dist/clients/deferred-lsp-work.js +106 -0
- package/dist/clients/degradation-ledger.js +166 -8
- package/dist/clients/demoted-finding-render.js +14 -0
- package/dist/clients/dependency-checker.js +28 -22
- package/dist/clients/deps/minimatch.js +1 -1
- package/dist/clients/diagnostic-line-freshness.js +12 -9
- package/dist/clients/diagnostic-logger.js +14 -4
- package/dist/clients/dispatch/collect-later-tier.js +39 -0
- package/dist/clients/dispatch/dispatcher.js +304 -19
- package/dist/clients/dispatch/fact-store.js +328 -9
- package/dist/clients/dispatch/integration.js +214 -107
- package/dist/clients/dispatch/pending-runner-findings.js +87 -0
- package/dist/clients/dispatch/plan.js +21 -5
- package/dist/clients/dispatch/runners/actionlint.js +0 -1
- package/dist/clients/dispatch/runners/ast-grep-napi.js +550 -77
- package/dist/clients/dispatch/runners/biome-check.js +0 -1
- package/dist/clients/dispatch/runners/cpp-check.js +0 -1
- package/dist/clients/dispatch/runners/credo.js +0 -1
- package/dist/clients/dispatch/runners/cue-vet.js +0 -1
- package/dist/clients/dispatch/runners/dart-analyze.js +0 -1
- package/dist/clients/dispatch/runners/detekt.js +0 -1
- package/dist/clients/dispatch/runners/dotnet-build.js +0 -1
- package/dist/clients/dispatch/runners/elixir-check.js +0 -1
- package/dist/clients/dispatch/runners/eslint.js +5 -2
- package/dist/clients/dispatch/runners/fact-rules.js +0 -1
- package/dist/clients/dispatch/runners/fish-indent.js +0 -1
- package/dist/clients/dispatch/runners/gleam-check.js +0 -1
- package/dist/clients/dispatch/runners/go-vet.js +1 -3
- package/dist/clients/dispatch/runners/golangci-lint.js +0 -1
- package/dist/clients/dispatch/runners/hadolint.js +0 -1
- package/dist/clients/dispatch/runners/helm-lint.js +16 -4
- package/dist/clients/dispatch/runners/helm-render.js +54 -10
- package/dist/clients/dispatch/runners/htmlhint.js +0 -1
- package/dist/clients/dispatch/runners/index.js +1 -3
- package/dist/clients/dispatch/runners/javac.js +0 -1
- package/dist/clients/dispatch/runners/ktlint.js +0 -1
- package/dist/clients/dispatch/runners/lsp.js +37 -2
- package/dist/clients/dispatch/runners/markdownlint.js +4 -2
- package/dist/clients/dispatch/runners/mypy.js +0 -1
- package/dist/clients/dispatch/runners/oxlint.js +24 -22
- package/dist/clients/dispatch/runners/php-lint.js +0 -1
- package/dist/clients/dispatch/runners/phpstan.js +0 -1
- package/dist/clients/dispatch/runners/prisma-validate.js +0 -1
- package/dist/clients/dispatch/runners/psscriptanalyzer.js +4 -1
- package/dist/clients/dispatch/runners/pyright.js +7 -4
- package/dist/clients/dispatch/runners/rubocop.js +0 -1
- package/dist/clients/dispatch/runners/ruff.js +0 -1
- package/dist/clients/dispatch/runners/rust-clippy.js +9 -6
- package/dist/clients/dispatch/runners/shellcheck.js +0 -1
- package/dist/clients/dispatch/runners/shfmt.js +0 -1
- package/dist/clients/dispatch/runners/spellcheck.js +0 -1
- package/dist/clients/dispatch/runners/spotbugs.js +0 -1
- package/dist/clients/dispatch/runners/sqlfluff.js +0 -1
- package/dist/clients/dispatch/runners/stylelint.js +0 -1
- package/dist/clients/dispatch/runners/swiftlint.js +0 -1
- package/dist/clients/dispatch/runners/taplo.js +0 -1
- package/dist/clients/dispatch/runners/terragrunt.js +0 -1
- package/dist/clients/dispatch/runners/tflint.js +0 -1
- package/dist/clients/dispatch/runners/tree-sitter.js +0 -1
- package/dist/clients/dispatch/runners/trivy-config.js +0 -1
- package/dist/clients/dispatch/runners/utils/availability-policy.js +3 -0
- package/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +3 -4
- package/dist/clients/dispatch/runners/utils/runner-helpers.js +270 -105
- package/dist/clients/dispatch/runners/utils/spawn-outcome.js +4 -0
- package/dist/clients/dispatch/runners/utils/tool-failure.js +19 -7
- package/dist/clients/dispatch/runners/utils/toolchain-availability.js +89 -26
- package/dist/clients/dispatch/runners/vale.js +0 -1
- package/dist/clients/dispatch/runners/yaml-rule-parser.js +51 -17
- package/dist/clients/dispatch/runners/yamllint.js +0 -1
- package/dist/clients/dispatch/runners/zig-check.js +0 -1
- package/dist/clients/disposition-logger.js +3 -3
- package/dist/clients/effective-config.js +403 -0
- package/dist/clients/error-class.js +23 -0
- package/dist/clients/event-loop-hold.js +274 -0
- package/dist/clients/event-loop-monitor.js +66 -1
- package/dist/clients/extension-log.js +2 -2
- package/dist/clients/feature-hints.js +2 -1
- package/dist/clients/file-time.js +6 -1
- package/dist/clients/file-utils.js +266 -13
- package/dist/clients/finding-delivery-gate.js +55 -13
- package/dist/clients/format-service.js +6 -1
- package/dist/clients/formatters.js +303 -104
- package/dist/clients/freshness-cadence.js +17 -0
- package/dist/clients/generated-artifacts.js +114 -19
- package/dist/clients/generation-guard.js +4 -9
- package/dist/clients/git-guard.js +198 -48
- package/dist/clients/git-tracked-ignore.js +32 -1
- package/dist/clients/go-client.js +39 -0
- package/dist/clients/govulncheck-client.js +5 -1
- package/dist/clients/gradle-ktfmt-style.js +252 -0
- package/dist/clients/gzip-stage-write.js +7 -0
- package/dist/clients/hashline-anchor.js +424 -0
- package/dist/clients/hook-budgets.js +76 -0
- package/dist/clients/host-edit-normalize.js +5 -2
- package/dist/clients/host-ports.js +1 -1
- package/dist/clients/installer/index.js +200 -84
- package/dist/clients/installer/managed-tool-refresh.js +3 -1
- package/dist/clients/instance-reaper.js +108 -243
- package/dist/clients/instance-registry-lock.js +145 -0
- package/dist/clients/instance-registry.js +435 -122
- package/dist/clients/jscpd-client.js +6 -1
- package/dist/clients/language-profile.js +4 -2
- package/dist/clients/language-registry.js +595 -0
- package/dist/clients/latency-logger.js +179 -3
- package/dist/clients/ledger-bounds.js +13 -0
- package/dist/clients/lens-config.js +141 -30
- package/dist/clients/lens-engine.js +30 -37
- package/dist/clients/lens-flag-registry.js +67 -6
- package/dist/clients/lens-map.js +3 -1
- package/dist/clients/log-cleanup.js +2 -2
- package/dist/clients/lsp/aggregation.js +3 -1
- package/dist/clients/lsp/cascade-tier.js +4 -0
- package/dist/clients/lsp/client.js +672 -49
- package/dist/clients/lsp/config.js +504 -156
- package/dist/clients/lsp/diagnostic-binding.js +10 -5
- package/dist/clients/lsp/edits.js +15 -2
- package/dist/clients/lsp/index.js +1179 -247
- package/dist/clients/lsp/jvm-runtime.js +5 -0
- package/dist/clients/lsp/language.js +18 -169
- package/dist/clients/lsp/launch.js +38 -9
- package/dist/clients/lsp/pending-aux-coverage.js +113 -12
- package/dist/clients/lsp/server.js +426 -160
- package/dist/clients/lsp/session-roots.js +192 -0
- package/dist/clients/lsp/spawn-history.js +2 -7
- package/dist/clients/lsp/tsserver-sync.js +9 -2
- package/dist/clients/lsp/workspace-diagnostics-cache.js +17 -7
- package/dist/clients/lsp/workspace-sweep-hold.js +32 -23
- package/dist/clients/lsp-mutation.js +179 -23
- package/dist/clients/map-with-concurrency.js +36 -0
- package/dist/clients/mcp/analyze.js +45 -10
- package/dist/clients/mcp/session.js +19 -17
- package/dist/clients/memory-sampler.js +28 -1
- package/dist/clients/message-end-attribution.js +30 -0
- package/dist/clients/metrics-history.js +21 -4
- package/dist/clients/middle-man-analysis.js +2 -4
- package/dist/clients/module-report.js +20 -45
- package/dist/clients/mutating-tool.js +651 -0
- package/dist/clients/mutation-attribution.js +368 -0
- package/dist/clients/mutation-bridge.js +240 -0
- package/dist/clients/ndjson-logger.js +247 -20
- package/dist/clients/observed-mutation-sources.js +101 -0
- package/dist/clients/observed-mutation.js +1215 -0
- package/dist/clients/opaque-mutation-scan.js +300 -40
- package/dist/clients/opengrep-config.js +4 -0
- package/dist/clients/package-manager.js +201 -44
- package/dist/clients/partial-edit-apply.js +359 -80
- package/dist/clients/path-attribution-telemetry.js +23 -7
- package/dist/clients/path-keyed-map.js +35 -3
- package/dist/clients/path-utils.js +460 -13
- package/dist/clients/persist-debounce.js +8 -1
- package/dist/clients/php-cs-fixer-config.js +114 -0
- package/dist/clients/pipeline.js +77 -24
- package/dist/clients/probe-home-state.js +228 -0
- package/dist/clients/process-bridge.js +66 -0
- package/dist/clients/process-singletons.js +210 -0
- package/dist/clients/process-snapshot.js +68 -0
- package/dist/clients/project-diagnostics/fresh-fetch.js +4 -3
- package/dist/clients/project-diagnostics/runner-adapters/madge.js +2 -1
- package/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +27 -3
- package/dist/clients/project-diagnostics/scanner.js +61 -24
- package/dist/clients/project-lens-config.js +393 -122
- package/dist/clients/project-snapshot.js +85 -35
- package/dist/clients/python-environment.js +217 -0
- package/dist/clients/python-provenance.js +639 -0
- package/dist/clients/quiet-window.js +6 -1
- package/dist/clients/read-bridge.js +13 -21
- package/dist/clients/read-expansion.js +9 -46
- package/dist/clients/read-guard-logger.js +39 -6
- package/dist/clients/read-guard-tool-lines.js +364 -212
- package/dist/clients/read-guard.js +95 -7
- package/dist/clients/recent-touches.js +17 -5
- package/dist/clients/resource-sampler.js +195 -96
- package/dist/clients/review-graph/builder.js +752 -132
- package/dist/clients/review-graph/service.js +55 -3
- package/dist/clients/review-graph/shared-extraction-ir.js +7 -15
- package/dist/clients/review-graph/tsconfig-paths.js +5 -1
- package/dist/clients/review-graph/workspace-modules.js +10 -74
- package/dist/clients/review-graph-logger.js +30 -4
- package/dist/clients/runtime-agent-end.js +18 -2
- package/dist/clients/runtime-context.js +49 -16
- package/dist/clients/runtime-coordinator.js +93 -6
- package/dist/clients/runtime-session.js +169 -34
- package/dist/clients/runtime-tool-call.js +183 -35
- package/dist/clients/runtime-tool-result.js +894 -241
- package/dist/clients/runtime-turn.js +1264 -74
- package/dist/clients/rust-client.js +31 -0
- package/dist/clients/safe-spawn.js +191 -28
- package/dist/clients/sanitize.js +7 -1
- package/dist/clients/scratch-tree-policy.js +1 -3
- package/dist/clients/security-scan-client.js +51 -17
- package/dist/clients/session-lifecycle.js +238 -42
- package/dist/clients/session-start-observability.js +79 -0
- package/dist/clients/sessionstart-logger.js +12 -3
- package/dist/clients/sg-runner.js +27 -15
- package/dist/clients/sgconfig.js +6 -1
- package/dist/clients/shared-checkout-guard.js +338 -0
- package/dist/clients/skills-resolver.js +105 -0
- package/dist/clients/smells-rollup.js +2 -2
- package/dist/clients/spawn-output-cap.js +37 -0
- package/dist/clients/spawn-timeout-cooldown.js +15 -1
- package/dist/clients/startup-scan.js +6 -7
- package/dist/clients/startup-timing.js +69 -4
- package/dist/clients/string-utils.js +33 -0
- package/dist/clients/subagent-mode.js +17 -4
- package/dist/clients/test-runner-client.js +586 -118
- package/dist/clients/test-runner-delivery.js +239 -0
- package/dist/clients/tool-definition.js +41 -1
- package/dist/clients/tool-policy.js +92 -22
- package/dist/clients/tree-sitter-cache.js +40 -40
- package/dist/clients/tree-sitter-client.js +97 -53
- package/dist/clients/tree-sitter-logger.js +19 -3
- package/dist/clients/tree-sitter-query-loader.js +81 -1
- package/dist/clients/tree-sitter-shared.js +51 -46
- package/dist/clients/tree-sitter-symbol-extractor.js +28 -0
- package/dist/clients/typos-config.js +5 -0
- package/dist/clients/user-notify.js +6 -2
- package/dist/clients/warm-attach.js +8 -10
- package/dist/clients/widget-state.js +191 -26
- package/dist/clients/word-index-logger.js +15 -3
- package/dist/clients/word-index-store.js +463 -0
- package/dist/clients/word-index.js +815 -125
- package/dist/clients/workspace-topology.js +25 -1
- package/dist/clients/zizmor-config.js +29 -6
- package/dist/index.js +68577 -53950
- package/dist/mcp/analyze-cli.js +3 -1
- package/dist/mcp/build-staleness.js +8 -7
- package/dist/mcp/cli.js +3 -1
- package/dist/mcp/server.js +133 -6
- package/dist/scripts/lib/process-scan.mjs +583 -0
- package/dist/scripts/lib/skills-predicate.mjs +129 -0
- package/dist/tools/effective-config.js +89 -0
- package/dist/tools/lens-diagnostics.js +99 -24
- package/dist/tools/lsp-diagnostics.js +81 -85
- package/dist/tools/lsp-navigation.js +40 -17
- package/dist/tools/shared.js +0 -1
- package/docs/agent-guide.md +68 -3
- package/docs/ast-grep_rules_catalog.md +41 -10
- package/docs/astplayground.md +26 -17
- package/docs/configuration.md +222 -0
- package/docs/dependencies.md +3 -3
- package/docs/features.md +44 -5
- package/docs/language-coverage.md +2 -2
- package/docs/pi-lens-fixer.md +25 -0
- package/docs/pi-lens-investigator.md +25 -0
- package/docs/pi-lens-reviewer.md +27 -0
- package/docs/pi-lens-subagent.md +38 -0
- package/docs/pi-lens-warden.md +55 -0
- package/docs/public-api-stability.md +359 -0
- package/docs/release-qa-baseline.md +182 -0
- package/docs/subagent-compat.md +110 -30
- package/docs/tree-sitter_rules_catalog.md +2 -2
- package/package.json +17 -21
- package/rules/ast-grep-rules/rule-tests/c-no-malloc-free-test.yml +20 -0
- package/rules/ast-grep-rules/rule-tests/cpp-no-malloc-free-test.yml +25 -0
- package/rules/ast-grep-rules/rule-tests/cpp-no-printf-test.yml +19 -0
- package/rules/ast-grep-rules/rule-tests/go-no-fmt-println-test.yml +19 -0
- package/rules/ast-grep-rules/rule-tests/go-no-panic-in-lib-test.yml +17 -0
- package/rules/ast-grep-rules/rule-tests/go-no-underscore-func-name-test.yml +18 -0
- package/rules/ast-grep-rules/rule-tests/go-prefer-errors-is-test.yml +15 -0
- package/rules/ast-grep-rules/rule-tests/go-prefer-string-builder-test.yml +21 -0
- package/rules/ast-grep-rules/rule-tests/hyphenated-svg-attribute-test.yml +11 -0
- package/rules/ast-grep-rules/rule-tests/kotlin-no-lateinit-test.yml +17 -0
- package/rules/ast-grep-rules/rule-tests/kotlin-no-nullable-boolean-test.yml +15 -0
- package/rules/ast-grep-rules/rule-tests/kotlin-no-println-test.yml +19 -0
- package/rules/ast-grep-rules/rule-tests/kotlin-no-unnecessary-let-test.yml +7 -0
- package/rules/ast-grep-rules/rule-tests/kotlin-no-unsafe-cast-test.yml +7 -0
- package/rules/ast-grep-rules/rule-tests/kotlin-no-var-test.yml +7 -0
- package/rules/ast-grep-rules/rule-tests/kotlin-prefer-data-class-test.yml +25 -0
- package/rules/ast-grep-rules/rule-tests/kotlin-prefer-is-empty-test.yml +11 -0
- package/rules/ast-grep-rules/rule-tests/no-important-test.yml +21 -0
- package/rules/ast-grep-rules/rule-tests/no-raw-types-test.yml +30 -0
- package/rules/ast-grep-rules/rule-tests/no-string-concat-in-loop-test.yml +100 -0
- package/rules/ast-grep-rules/rule-tests/no-system-out-println-test.yml +7 -0
- package/rules/ast-grep-rules/rule-tests/prefer-string-is-empty-test.yml +7 -0
- package/rules/ast-grep-rules/rule-tests/python-optional-type-test.yml +9 -0
- package/rules/ast-grep-rules/rule-tests/reducible-list-comprehension-test.yml +11 -0
- package/rules/ast-grep-rules/rule-tests/ruby-symbol-to-proc-candidate-test.yml +10 -0
- package/rules/ast-grep-rules/rule-tests/rust-no-chars-enumerate-test.yml +7 -0
- package/rules/ast-grep-rules/rules/c-no-malloc-free.yml +39 -0
- package/rules/ast-grep-rules/rules/cpp-no-malloc-free.yml +45 -0
- package/rules/ast-grep-rules/rules/cpp-no-printf.yml +41 -0
- package/rules/ast-grep-rules/rules/go-no-fmt-println.yml +43 -0
- package/rules/ast-grep-rules/rules/go-no-panic-in-lib.yml +41 -0
- package/rules/ast-grep-rules/rules/go-no-underscore-func-name.yml +35 -0
- package/rules/ast-grep-rules/rules/go-prefer-errors-is.yml +40 -0
- package/rules/ast-grep-rules/rules/go-prefer-string-builder.yml +43 -0
- package/rules/ast-grep-rules/rules/hyphenated-svg-attribute.yml +32 -0
- package/rules/ast-grep-rules/rules/kotlin-no-lateinit.yml +37 -0
- package/rules/ast-grep-rules/rules/kotlin-no-nullable-boolean.yml +33 -0
- package/rules/ast-grep-rules/rules/kotlin-no-println.yml +42 -0
- package/rules/ast-grep-rules/rules/kotlin-no-unnecessary-let.yml +28 -0
- package/rules/ast-grep-rules/rules/kotlin-no-unsafe-cast.yml +28 -0
- package/rules/ast-grep-rules/rules/kotlin-no-var.yml +35 -0
- package/rules/ast-grep-rules/rules/kotlin-prefer-data-class.yml +66 -0
- package/rules/ast-grep-rules/rules/kotlin-prefer-is-empty.yml +35 -0
- package/rules/ast-grep-rules/rules/no-important.yml +21 -0
- package/rules/ast-grep-rules/rules/no-raw-types.yml +33 -0
- package/rules/ast-grep-rules/rules/no-string-concat-in-loop.yml +90 -0
- package/rules/ast-grep-rules/rules/no-system-out-println.yml +16 -0
- package/rules/ast-grep-rules/rules/prefer-string-is-empty.yml +11 -0
- package/rules/ast-grep-rules/rules/python-optional-type.yml +22 -0
- package/rules/ast-grep-rules/rules/reducible-list-comprehension.yml +29 -0
- package/rules/ast-grep-rules/rules/ruby-symbol-to-proc-candidate.yml +25 -0
- package/rules/ast-grep-rules/rules/rust-no-chars-enumerate.yml +19 -0
- package/rules/tree-sitter-queries/python/python-cross-language-method.yml +3 -3
- package/rules/tree-sitter-queries/python/python-hallucinated-import.yml +2 -2
- package/rules/tree-sitter-queries/python/python-sql-injection.yml +12 -2
- package/scripts/analyze-pi-lens-logs.mjs +192 -1
- package/scripts/install-selftest.mjs +58 -2
- package/scripts/lib/skills-predicate.mjs +129 -0
- package/scripts/rpc-load-check.mjs +3 -1
|
@@ -29,16 +29,17 @@
|
|
|
29
29
|
* - rust-analyzer (Rust LSP) [GitHub release]
|
|
30
30
|
* - golangci-lint (Go linting) [GitHub release]
|
|
31
31
|
*
|
|
32
|
-
*
|
|
33
|
-
* - yaml-language-server
|
|
34
|
-
* -
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* -
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
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
|
-
|
|
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
|
{
|
|
@@ -383,6 +389,12 @@ export const TOOLS = [
|
|
|
383
389
|
name: "Bash Language Server",
|
|
384
390
|
checkCommand: "bash-language-server",
|
|
385
391
|
checkArgs: ["--version"],
|
|
392
|
+
// The #2188 sweep measured a 9,667ms cold `--version` start with closed
|
|
393
|
+
// stdin — close enough to the 10s installer default that modest host
|
|
394
|
+
// contention pushes it over and emits a false verification degradation.
|
|
395
|
+
// 20s gives the same kind of headroom Vue's 30s bound gives its own
|
|
396
|
+
// slower cold start (#2176), without inventing a shared literal (#2194).
|
|
397
|
+
verificationTimeoutMs: 20_000,
|
|
386
398
|
installStrategy: "npm",
|
|
387
399
|
packageName: "bash-language-server",
|
|
388
400
|
binaryName: "bash-language-server",
|
|
@@ -419,6 +431,11 @@ export const TOOLS = [
|
|
|
419
431
|
name: "VSCode JSON Language Server",
|
|
420
432
|
checkCommand: "vscode-json-language-server",
|
|
421
433
|
checkArgs: ["--version"],
|
|
434
|
+
// The #2188 sweep measured an 11,047ms cold `--version` start with closed
|
|
435
|
+
// stdin — over the 10s installer default, so a cold or contended host can
|
|
436
|
+
// see a false verification degradation before the binary ever answers.
|
|
437
|
+
// 20s mirrors the bash-language-server bound above (#2194).
|
|
438
|
+
verificationTimeoutMs: 20_000,
|
|
422
439
|
installStrategy: "npm",
|
|
423
440
|
packageName: "vscode-langservers-extracted",
|
|
424
441
|
binaryName: "vscode-json-language-server",
|
|
@@ -429,7 +446,7 @@ export const TOOLS = [
|
|
|
429
446
|
checkCommand: "vscode-html-language-server",
|
|
430
447
|
checkArgs: ["--version"],
|
|
431
448
|
installStrategy: "npm",
|
|
432
|
-
packageName: "vscode-
|
|
449
|
+
packageName: "vscode-langservers-extracted",
|
|
433
450
|
binaryName: "vscode-html-language-server",
|
|
434
451
|
},
|
|
435
452
|
{
|
|
@@ -465,7 +482,8 @@ export const TOOLS = [
|
|
|
465
482
|
id: "helm",
|
|
466
483
|
name: "Helm",
|
|
467
484
|
checkCommand: "helm",
|
|
468
|
-
|
|
485
|
+
// intended: version --short — unverified against real binary (shape 16), do not wire until probed
|
|
486
|
+
checkArgs: ["--version"],
|
|
469
487
|
installStrategy: "github",
|
|
470
488
|
binaryName: "helm",
|
|
471
489
|
github: {
|
|
@@ -517,7 +535,7 @@ export const TOOLS = [
|
|
|
517
535
|
checkCommand: "vscode-css-language-server",
|
|
518
536
|
checkArgs: ["--version"],
|
|
519
537
|
installStrategy: "npm",
|
|
520
|
-
packageName: "vscode-
|
|
538
|
+
packageName: "vscode-langservers-extracted",
|
|
521
539
|
binaryName: "vscode-css-language-server",
|
|
522
540
|
},
|
|
523
541
|
{
|
|
@@ -543,6 +561,11 @@ export const TOOLS = [
|
|
|
543
561
|
name: "Prisma Language Server",
|
|
544
562
|
checkCommand: "prisma-language-server",
|
|
545
563
|
checkArgs: ["--version"],
|
|
564
|
+
// #2169: a real closed-stdin cold run measured 27,265ms, and a warm-cache
|
|
565
|
+
// rerun still took 9,860ms — well past the 10s installer default. 40s
|
|
566
|
+
// keeps the same margin-over-worst-observed ratio Vue's 30s bound uses
|
|
567
|
+
// (#2176) rather than trimming it for a slower binary.
|
|
568
|
+
verificationTimeoutMs: 40_000,
|
|
546
569
|
installStrategy: "npm",
|
|
547
570
|
packageName: "@prisma/language-server",
|
|
548
571
|
binaryName: "prisma-language-server",
|
|
@@ -552,6 +575,10 @@ export const TOOLS = [
|
|
|
552
575
|
name: "Vue Language Server",
|
|
553
576
|
checkCommand: "vue-language-server",
|
|
554
577
|
checkArgs: ["--version"],
|
|
578
|
+
// Vue's launcher loads the full language-service bundle before answering
|
|
579
|
+
// --version. Its cold start exceeds the dispatch probe budget on some hosts
|
|
580
|
+
// even though warm starts complete quickly (#2176).
|
|
581
|
+
verificationTimeoutMs: 30_000,
|
|
555
582
|
installStrategy: "npm",
|
|
556
583
|
packageName: "@vue/language-server",
|
|
557
584
|
binaryName: "vue-language-server",
|
|
@@ -561,6 +588,10 @@ export const TOOLS = [
|
|
|
561
588
|
name: "Svelte Language Server",
|
|
562
589
|
checkCommand: "svelteserver",
|
|
563
590
|
checkArgs: ["--version"],
|
|
591
|
+
// #2169: a real closed-stdin cold run measured 12,410ms — over the 10s
|
|
592
|
+
// installer default, matching the bash/JSON class of false verification
|
|
593
|
+
// degradation from a cold-cache host (#2194). 20s mirrors that bound.
|
|
594
|
+
verificationTimeoutMs: 20_000,
|
|
564
595
|
installStrategy: "npm",
|
|
565
596
|
packageName: "svelte-language-server",
|
|
566
597
|
binaryName: "svelteserver",
|
|
@@ -569,7 +600,9 @@ export const TOOLS = [
|
|
|
569
600
|
id: "markdownlint",
|
|
570
601
|
name: "markdownlint-cli2",
|
|
571
602
|
checkCommand: "markdownlint-cli2",
|
|
572
|
-
|
|
603
|
+
// `--version` is interpreted as a file glob by markdownlint-cli2. Use
|
|
604
|
+
// stdin with globs disabled so verification cannot scan the workspace.
|
|
605
|
+
checkArgs: ["--no-globs", "-"],
|
|
573
606
|
installStrategy: "npm",
|
|
574
607
|
packageName: "markdownlint-cli2",
|
|
575
608
|
binaryName: "markdownlint-cli2",
|
|
@@ -784,7 +817,8 @@ export const TOOLS = [
|
|
|
784
817
|
id: "spotbugs",
|
|
785
818
|
name: "SpotBugs",
|
|
786
819
|
checkCommand: "spotbugs",
|
|
787
|
-
|
|
820
|
+
// intended: -version — unverified against real binary (shape 16), do not wire until probed
|
|
821
|
+
checkArgs: ["--version"],
|
|
788
822
|
installStrategy: "archive",
|
|
789
823
|
binaryName: "spotbugs",
|
|
790
824
|
archive: {
|
|
@@ -1030,7 +1064,8 @@ export const TOOLS = [
|
|
|
1030
1064
|
id: "gitleaks",
|
|
1031
1065
|
name: "gitleaks",
|
|
1032
1066
|
checkCommand: "gitleaks",
|
|
1033
|
-
|
|
1067
|
+
// intended: version — unverified against real binary (shape 16), do not wire until probed
|
|
1068
|
+
checkArgs: ["--version"],
|
|
1034
1069
|
installStrategy: "github",
|
|
1035
1070
|
binaryName: "gitleaks",
|
|
1036
1071
|
github: {
|
|
@@ -1144,7 +1179,8 @@ export const TOOLS = [
|
|
|
1144
1179
|
id: "terraform-ls",
|
|
1145
1180
|
name: "terraform-ls",
|
|
1146
1181
|
checkCommand: "terraform-ls",
|
|
1147
|
-
|
|
1182
|
+
// intended: version — unverified against real binary (shape 16), do not wire until probed
|
|
1183
|
+
checkArgs: ["--version"],
|
|
1148
1184
|
installStrategy: "github",
|
|
1149
1185
|
binaryName: "terraform-ls",
|
|
1150
1186
|
github: {
|
|
@@ -1216,6 +1252,7 @@ export const TOOLS = [
|
|
|
1216
1252
|
id: "cue",
|
|
1217
1253
|
name: "CUE",
|
|
1218
1254
|
checkCommand: "cue",
|
|
1255
|
+
// Probed locally with CUE v0.17.1: `cue version` exits 0.
|
|
1219
1256
|
checkArgs: ["version"],
|
|
1220
1257
|
installStrategy: "github",
|
|
1221
1258
|
binaryName: "cue",
|
|
@@ -1324,6 +1361,10 @@ export const TOOLS = [
|
|
|
1324
1361
|
},
|
|
1325
1362
|
},
|
|
1326
1363
|
];
|
|
1364
|
+
/** Return the bounded managed-binary verification budget for a registered tool. */
|
|
1365
|
+
export function getToolVerificationTimeout(tool) {
|
|
1366
|
+
return tool.verificationTimeoutMs ?? 10_000;
|
|
1367
|
+
}
|
|
1327
1368
|
const ensureInFlight = new Map();
|
|
1328
1369
|
const installFailureReasons = new Map();
|
|
1329
1370
|
/** Last honest install refusal/failure reason for callers that need diagnostics. */
|
|
@@ -1621,8 +1662,13 @@ export async function checkProbeCache(toolId) {
|
|
|
1621
1662
|
try {
|
|
1622
1663
|
await fs.access(entry.path);
|
|
1623
1664
|
const stat = await fs.stat(entry.path);
|
|
1624
|
-
|
|
1625
|
-
|
|
1665
|
+
// #2300: size alongside mtime, from the same stat — a binary replaced
|
|
1666
|
+
// in-place within the same coarse-granularity mtime tick would otherwise
|
|
1667
|
+
// pass this check unnoticed. `entry.sizeBytes` absent (pre-#2300 entry)
|
|
1668
|
+
// fails open to the mtime-only check (see the field's doc comment).
|
|
1669
|
+
if (stat.mtimeMs !== entry.mtimeMs ||
|
|
1670
|
+
(entry.sizeBytes !== undefined && stat.size !== entry.sizeBytes)) {
|
|
1671
|
+
logSessionStart(`auto-install probe-cache ${toolId}: miss (mtime/size changed)`);
|
|
1626
1672
|
delete cache[toolId];
|
|
1627
1673
|
markProbeCacheChange(toolId, null);
|
|
1628
1674
|
return undefined;
|
|
@@ -1650,6 +1696,7 @@ export async function updateProbeCache(toolId, resolvedPath, transient = false)
|
|
|
1650
1696
|
const entry = {
|
|
1651
1697
|
path: resolvedPath,
|
|
1652
1698
|
mtimeMs: stat.mtimeMs,
|
|
1699
|
+
sizeBytes: stat.size,
|
|
1653
1700
|
cachedAt: Date.now(),
|
|
1654
1701
|
...(transient && { transient: true }),
|
|
1655
1702
|
};
|
|
@@ -1681,6 +1728,14 @@ export function resetProbeCacheStateForTesting() {
|
|
|
1681
1728
|
_probeCacheFlushTimer = null;
|
|
1682
1729
|
}
|
|
1683
1730
|
}
|
|
1731
|
+
/**
|
|
1732
|
+
* Exported for testing only. Read the `ensureTool` in-flight map directly
|
|
1733
|
+
* (#1968's ABA regression: a second writer replacing an entry mid-flight,
|
|
1734
|
+
* then a late-settling first run evicting it with a bare delete-by-key).
|
|
1735
|
+
*/
|
|
1736
|
+
export function _peekEnsureInFlightForTesting() {
|
|
1737
|
+
return ensureInFlight;
|
|
1738
|
+
}
|
|
1684
1739
|
// --- Check Functions ---
|
|
1685
1740
|
const pathWalkMemo = new Map();
|
|
1686
1741
|
function hashSync(value) {
|
|
@@ -1765,6 +1820,7 @@ export async function isSpawnableCommand(command) {
|
|
|
1765
1820
|
export function isLspTransportRequiredError(output) {
|
|
1766
1821
|
return /Connection (?:input|output) stream is not set|Use arguments of createConnection/i.test(output);
|
|
1767
1822
|
}
|
|
1823
|
+
const lspTransportRequiredMatcher = /Connection (?:input|output) stream is not set|Use arguments of createConnection/i;
|
|
1768
1824
|
/**
|
|
1769
1825
|
* Parse the exact version pinned in a TOOLS `packageName` spec, e.g.
|
|
1770
1826
|
* `"jscpd@3.5.10"` -> `"3.5.10"`. Scoped packages (`"@ast-grep/cli"`) have no
|
|
@@ -1773,7 +1829,7 @@ export function isLspTransportRequiredError(output) {
|
|
|
1773
1829
|
* correctly reports "no pin" rather than mistaking the scope for a version.
|
|
1774
1830
|
* Returns undefined when packageName has no explicit `@version` suffix.
|
|
1775
1831
|
*/
|
|
1776
|
-
function parsePinnedVersion(packageName) {
|
|
1832
|
+
export function parsePinnedVersion(packageName) {
|
|
1777
1833
|
const at = packageName.lastIndexOf("@");
|
|
1778
1834
|
if (at <= 0)
|
|
1779
1835
|
return undefined;
|
|
@@ -1796,7 +1852,7 @@ function extractVersionToken(output) {
|
|
|
1796
1852
|
*/
|
|
1797
1853
|
const lastManagedInstallVersion = new Map();
|
|
1798
1854
|
/**
|
|
1799
|
-
* Verify a tool binary actually works by running
|
|
1855
|
+
* Verify a tool binary actually works by running its configured checkArgs.
|
|
1800
1856
|
* This catches broken symlinks, partial installs, and corrupted binaries.
|
|
1801
1857
|
* `onVersionOutput`, when provided, receives the raw stdout on a successful
|
|
1802
1858
|
* (exit 0) probe — used to piggyback version-pin drift detection onto this
|
|
@@ -1822,7 +1878,7 @@ onTransient,
|
|
|
1822
1878
|
* sensitive caller on the dispatch hot path passes a shorter one and treats
|
|
1823
1879
|
* the expiry as transient rather than as a verdict (#1657).
|
|
1824
1880
|
*/
|
|
1825
|
-
timeoutMs = 10000) {
|
|
1881
|
+
timeoutMs = 10000, verificationArgs = ["--version"]) {
|
|
1826
1882
|
// #2015: safeSpawnAsync instead of raw spawn. Raw spawn's timeout
|
|
1827
1883
|
// SIGTERMed only cmd.exe on Windows (.cmd shims run shell:true), orphaning
|
|
1828
1884
|
// the grandchild node process - which kept scanning, held handles against
|
|
@@ -1849,16 +1905,29 @@ timeoutMs = 10000) {
|
|
|
1849
1905
|
// there), so no shell option exists or is needed here.
|
|
1850
1906
|
void useShell;
|
|
1851
1907
|
try {
|
|
1852
|
-
const result = await safeSpawnAsync(execPath,
|
|
1908
|
+
const result = await safeSpawnAsync(execPath, verificationArgs, {
|
|
1853
1909
|
timeout: timeoutMs,
|
|
1910
|
+
input: "",
|
|
1911
|
+
// A broken or version-blind language server can emit a bundled
|
|
1912
|
+
// megabytes-long diagnostic on stderr. Keep verification bounded even
|
|
1913
|
+
// when the child reaches its normal timeout first.
|
|
1914
|
+
maxOutputBytes: 64 * 1024,
|
|
1915
|
+
matchWhileStreaming: lspTransportRequiredMatcher,
|
|
1854
1916
|
});
|
|
1855
1917
|
const output = `${result.stdout}\n${result.stderr}`;
|
|
1918
|
+
if (result.outputTruncated) {
|
|
1919
|
+
recordDegradationOnce({
|
|
1920
|
+
kind: "installer-verification-output-truncated",
|
|
1921
|
+
subject: binPath,
|
|
1922
|
+
reason: `verification output exceeded 65536 bytes (${verificationArgs.join(" ")})`,
|
|
1923
|
+
});
|
|
1924
|
+
}
|
|
1856
1925
|
if (result.status === 0 && !result.error) {
|
|
1857
|
-
debugLog(`Verified: ${binPath} (
|
|
1926
|
+
debugLog(`Verified: ${binPath} (${verificationArgs.join(" ")}: ${result.stdout.trim()})`);
|
|
1858
1927
|
onVersionOutput?.(result.stdout);
|
|
1859
1928
|
return true;
|
|
1860
1929
|
}
|
|
1861
|
-
if (isLspTransportRequiredError(output)) {
|
|
1930
|
+
if (result.streamingMatch || isLspTransportRequiredError(output)) {
|
|
1862
1931
|
// Valid stdio LSP server that rejects `--version` (#208) — the
|
|
1863
1932
|
// transport-required error proves the binary works.
|
|
1864
1933
|
debugLog(`Verified (stdio LSP, transport-required): ${binPath}`);
|
|
@@ -1868,7 +1937,13 @@ timeoutMs = 10000) {
|
|
|
1868
1937
|
// verdict from the binary (#1569 transient semantics).
|
|
1869
1938
|
if (result.signal !== undefined || result.spawnFailure)
|
|
1870
1939
|
onTransient?.();
|
|
1871
|
-
|
|
1940
|
+
const kind = result.spawnFailure?.kind ??
|
|
1941
|
+
(result.signal
|
|
1942
|
+
? `killed-signal=${result.signal}`
|
|
1943
|
+
: result.error
|
|
1944
|
+
? "spawn-error"
|
|
1945
|
+
: "exit-nonzero");
|
|
1946
|
+
logSessionStart(`auto-install verify: failed for ${binPath} (check=${verificationArgs.join(" ")}, kind=${kind}${result.status !== null ? `, exit=${result.status}` : ""})`);
|
|
1872
1947
|
return false;
|
|
1873
1948
|
}
|
|
1874
1949
|
catch (err) {
|
|
@@ -1938,7 +2013,7 @@ export async function getAllToolStatuses() {
|
|
|
1938
2013
|
}
|
|
1939
2014
|
// 2. Check npm global
|
|
1940
2015
|
if (tool.installStrategy === "npm") {
|
|
1941
|
-
const npmPath = await findNpmGlobalToolPath(tool.binaryName || tool.id);
|
|
2016
|
+
const npmPath = await findNpmGlobalToolPath(tool.binaryName || tool.id, undefined, tool.checkArgs, getToolVerificationTimeout(tool));
|
|
1942
2017
|
if (npmPath) {
|
|
1943
2018
|
status.installed = true;
|
|
1944
2019
|
status.source = "npm-global";
|
|
@@ -1949,7 +2024,7 @@ export async function getAllToolStatuses() {
|
|
|
1949
2024
|
}
|
|
1950
2025
|
// 3. Check pip user install
|
|
1951
2026
|
if (tool.installStrategy === "pip") {
|
|
1952
|
-
const pipPath = await findPipUserToolPath(tool.binaryName || tool.id);
|
|
2027
|
+
const pipPath = await findPipUserToolPath(tool.binaryName || tool.id, undefined, tool.checkArgs, getToolVerificationTimeout(tool));
|
|
1953
2028
|
if (pipPath) {
|
|
1954
2029
|
status.installed = true;
|
|
1955
2030
|
status.source = "pip-user";
|
|
@@ -1981,7 +2056,7 @@ export async function getAllToolStatuses() {
|
|
|
1981
2056
|
const localPath = installerPlatform() === "win32" ? `${localBase}.cmd` : localBase;
|
|
1982
2057
|
try {
|
|
1983
2058
|
await fs.access(localPath);
|
|
1984
|
-
if (await verifyToolBinary(localPath)) {
|
|
2059
|
+
if (await verifyToolBinary(localPath, undefined, undefined, getToolVerificationTimeout(tool), tool.checkArgs)) {
|
|
1985
2060
|
status.installed = true;
|
|
1986
2061
|
status.source = "pi-lens-auto";
|
|
1987
2062
|
status.path = localPath;
|
|
@@ -2150,7 +2225,7 @@ async function getToolPathResolved(toolId, onTransient) {
|
|
|
2150
2225
|
const cmdPath = `${localBase}.cmd`;
|
|
2151
2226
|
try {
|
|
2152
2227
|
await fs.access(cmdPath);
|
|
2153
|
-
if (await verifyToolBinary(cmdPath, recordVersion, onTransient)) {
|
|
2228
|
+
if (await verifyToolBinary(cmdPath, recordVersion, onTransient, getToolVerificationTimeout(tool), tool.checkArgs)) {
|
|
2154
2229
|
return cmdPath;
|
|
2155
2230
|
}
|
|
2156
2231
|
logSessionStart(`auto-install verify: ${cmdPath} exists but is broken, will reinstall`);
|
|
@@ -2163,7 +2238,7 @@ async function getToolPathResolved(toolId, onTransient) {
|
|
|
2163
2238
|
const exePath = `${localBase}.exe`;
|
|
2164
2239
|
try {
|
|
2165
2240
|
await fs.access(exePath);
|
|
2166
|
-
if (await verifyToolBinary(exePath, recordVersion, onTransient)) {
|
|
2241
|
+
if (await verifyToolBinary(exePath, recordVersion, onTransient, getToolVerificationTimeout(tool), tool.checkArgs)) {
|
|
2167
2242
|
return exePath;
|
|
2168
2243
|
}
|
|
2169
2244
|
logSessionStart(`auto-install verify: ${exePath} exists but is broken, will reinstall`);
|
|
@@ -2175,7 +2250,7 @@ async function getToolPathResolved(toolId, onTransient) {
|
|
|
2175
2250
|
if (installerPlatform() !== "win32") {
|
|
2176
2251
|
try {
|
|
2177
2252
|
await fs.access(localBase);
|
|
2178
|
-
if (await verifyToolBinary(localBase, recordVersion, onTransient)) {
|
|
2253
|
+
if (await verifyToolBinary(localBase, recordVersion, onTransient, getToolVerificationTimeout(tool), tool.checkArgs)) {
|
|
2179
2254
|
return localBase;
|
|
2180
2255
|
}
|
|
2181
2256
|
logSessionStart(`auto-install verify: ${localBase} exists but is broken, will reinstall`);
|
|
@@ -2191,7 +2266,7 @@ async function getToolPathResolved(toolId, onTransient) {
|
|
|
2191
2266
|
if (tool.platformPackage) {
|
|
2192
2267
|
const platformBin = resolvePlatformPackageBinary(tool);
|
|
2193
2268
|
if (platformBin &&
|
|
2194
|
-
(await verifyToolBinary(platformBin, undefined, onTransient))) {
|
|
2269
|
+
(await verifyToolBinary(platformBin, undefined, onTransient, getToolVerificationTimeout(tool), tool.checkArgs))) {
|
|
2195
2270
|
logSessionStart(`auto-install ${toolId}: resolved platform-package binary at ${platformBin}`);
|
|
2196
2271
|
return platformBin;
|
|
2197
2272
|
}
|
|
@@ -2213,14 +2288,14 @@ async function getToolPathResolved(toolId, onTransient) {
|
|
|
2213
2288
|
return tool.checkCommand;
|
|
2214
2289
|
}
|
|
2215
2290
|
if (tool.installStrategy === "npm") {
|
|
2216
|
-
const npmPath = await findNpmGlobalToolPath(tool.binaryName || tool.id, onTransient);
|
|
2291
|
+
const npmPath = await findNpmGlobalToolPath(tool.binaryName || tool.id, onTransient, tool.checkArgs, getToolVerificationTimeout(tool));
|
|
2217
2292
|
if (npmPath) {
|
|
2218
2293
|
return npmPath;
|
|
2219
2294
|
}
|
|
2220
2295
|
}
|
|
2221
2296
|
// For pip tools, also probe user-level script locations
|
|
2222
2297
|
if (tool.installStrategy === "pip") {
|
|
2223
|
-
const pipPath = await findPipUserToolPath(tool.binaryName || tool.id, onTransient);
|
|
2298
|
+
const pipPath = await findPipUserToolPath(tool.binaryName || tool.id, onTransient, tool.checkArgs, getToolVerificationTimeout(tool));
|
|
2224
2299
|
if (pipPath) {
|
|
2225
2300
|
return pipPath;
|
|
2226
2301
|
}
|
|
@@ -2248,6 +2323,18 @@ async function findGitHubToolPath(binaryName) {
|
|
|
2248
2323
|
}
|
|
2249
2324
|
return undefined;
|
|
2250
2325
|
}
|
|
2326
|
+
/** Resolve a release-managed binary without probing PATH first. */
|
|
2327
|
+
export async function findManagedToolBinary(toolId) {
|
|
2328
|
+
const tool = TOOLS.find((candidate) => candidate.id === toolId);
|
|
2329
|
+
if (!tool)
|
|
2330
|
+
return undefined;
|
|
2331
|
+
if (tool.installStrategy !== "github" &&
|
|
2332
|
+
tool.installStrategy !== "maven" &&
|
|
2333
|
+
tool.installStrategy !== "archive") {
|
|
2334
|
+
return undefined;
|
|
2335
|
+
}
|
|
2336
|
+
return findGitHubToolPath(tool.binaryName || tool.id);
|
|
2337
|
+
}
|
|
2251
2338
|
function hasExecutableExtension(name) {
|
|
2252
2339
|
return /\.(exe|bat|cmd|ps1)$/i.test(name);
|
|
2253
2340
|
}
|
|
@@ -2278,7 +2365,7 @@ function getArchiveBinaryCandidates(binaryName, platform, assetName) {
|
|
|
2278
2365
|
candidates.add(`${binaryName}.cmd`);
|
|
2279
2366
|
return [...candidates];
|
|
2280
2367
|
}
|
|
2281
|
-
async function findNpmGlobalToolPath(binaryName, onTransient) {
|
|
2368
|
+
async function findNpmGlobalToolPath(binaryName, onTransient, verificationArgs = ["--version"], verificationTimeoutMs = 10_000) {
|
|
2282
2369
|
const isWindows = process.platform === "win32";
|
|
2283
2370
|
const binDirs = await getNpmGlobalBinCandidates(onTransient);
|
|
2284
2371
|
for (const dir of binDirs) {
|
|
@@ -2291,7 +2378,7 @@ async function findNpmGlobalToolPath(binaryName, onTransient) {
|
|
|
2291
2378
|
for (const candidate of candidates) {
|
|
2292
2379
|
try {
|
|
2293
2380
|
await fs.access(candidate);
|
|
2294
|
-
if (await verifyToolBinary(candidate, undefined, onTransient)) {
|
|
2381
|
+
if (await verifyToolBinary(candidate, undefined, onTransient, verificationTimeoutMs, verificationArgs)) {
|
|
2295
2382
|
return candidate;
|
|
2296
2383
|
}
|
|
2297
2384
|
}
|
|
@@ -2332,7 +2419,7 @@ async function getNpmGlobalBinCandidates(onTransient) {
|
|
|
2332
2419
|
}
|
|
2333
2420
|
return dirs;
|
|
2334
2421
|
}
|
|
2335
|
-
async function findPipUserToolPath(binaryName, onTransient) {
|
|
2422
|
+
async function findPipUserToolPath(binaryName, onTransient, verificationArgs = ["--version"], verificationTimeoutMs = 10_000) {
|
|
2336
2423
|
const isWindows = process.platform === "win32";
|
|
2337
2424
|
const userBaseCandidates = await getPythonUserBaseCandidates();
|
|
2338
2425
|
for (const userBase of userBaseCandidates) {
|
|
@@ -2365,7 +2452,7 @@ async function findPipUserToolPath(binaryName, onTransient) {
|
|
|
2365
2452
|
for (const candidate of candidates) {
|
|
2366
2453
|
try {
|
|
2367
2454
|
await fs.access(candidate);
|
|
2368
|
-
if (await verifyToolBinary(candidate, undefined, onTransient)) {
|
|
2455
|
+
if (await verifyToolBinary(candidate, undefined, onTransient, verificationTimeoutMs, verificationArgs)) {
|
|
2369
2456
|
return candidate;
|
|
2370
2457
|
}
|
|
2371
2458
|
}
|
|
@@ -2837,8 +2924,10 @@ export function getRefreshableManagedTools() {
|
|
|
2837
2924
|
refreshable.push({
|
|
2838
2925
|
toolId: tool.id,
|
|
2839
2926
|
strategy: "npm",
|
|
2927
|
+
checkArgs: tool.checkArgs,
|
|
2840
2928
|
packageName: tool.packageName,
|
|
2841
2929
|
binaryName: tool.binaryName,
|
|
2930
|
+
verificationTimeoutMs: tool.verificationTimeoutMs,
|
|
2842
2931
|
});
|
|
2843
2932
|
break;
|
|
2844
2933
|
}
|
|
@@ -2849,6 +2938,7 @@ export function getRefreshableManagedTools() {
|
|
|
2849
2938
|
refreshable.push({
|
|
2850
2939
|
toolId: tool.id,
|
|
2851
2940
|
strategy: tool.installStrategy,
|
|
2941
|
+
checkArgs: tool.checkArgs,
|
|
2852
2942
|
packageName: tool.packageName,
|
|
2853
2943
|
});
|
|
2854
2944
|
break;
|
|
@@ -2856,7 +2946,11 @@ export function getRefreshableManagedTools() {
|
|
|
2856
2946
|
case "github": {
|
|
2857
2947
|
if (!tool.github)
|
|
2858
2948
|
continue;
|
|
2859
|
-
refreshable.push({
|
|
2949
|
+
refreshable.push({
|
|
2950
|
+
toolId: tool.id,
|
|
2951
|
+
strategy: "github",
|
|
2952
|
+
checkArgs: tool.checkArgs,
|
|
2953
|
+
});
|
|
2860
2954
|
break;
|
|
2861
2955
|
}
|
|
2862
2956
|
case "maven": {
|
|
@@ -2865,6 +2959,7 @@ export function getRefreshableManagedTools() {
|
|
|
2865
2959
|
refreshable.push({
|
|
2866
2960
|
toolId: tool.id,
|
|
2867
2961
|
strategy: "maven",
|
|
2962
|
+
checkArgs: tool.checkArgs,
|
|
2868
2963
|
pinnedCoordinate: mavenCoordinate(tool.maven),
|
|
2869
2964
|
});
|
|
2870
2965
|
break;
|
|
@@ -2880,6 +2975,7 @@ export function getRefreshableManagedTools() {
|
|
|
2880
2975
|
refreshable.push({
|
|
2881
2976
|
toolId: tool.id,
|
|
2882
2977
|
strategy: "archive",
|
|
2978
|
+
checkArgs: tool.checkArgs,
|
|
2883
2979
|
pinnedCoordinate: url,
|
|
2884
2980
|
});
|
|
2885
2981
|
break;
|
|
@@ -2951,7 +3047,8 @@ async function probeManagedToolVersion(tool) {
|
|
|
2951
3047
|
return undefined;
|
|
2952
3048
|
try {
|
|
2953
3049
|
const result = await safeSpawnAsync(cached.path, tool.checkArgs, {
|
|
2954
|
-
timeout:
|
|
3050
|
+
timeout: getToolVerificationTimeout(tool),
|
|
3051
|
+
input: "",
|
|
2955
3052
|
ignoreAmbientSignal: true,
|
|
2956
3053
|
resourceLabel: `tool-refresh-version:${tool.id}`,
|
|
2957
3054
|
});
|
|
@@ -3187,8 +3284,8 @@ async function verifyRefreshedArtifact(tool, installedPath) {
|
|
|
3187
3284
|
await updateProbeCache(tool.id, installedPath);
|
|
3188
3285
|
return true;
|
|
3189
3286
|
}
|
|
3190
|
-
if (!(await verifyToolBinary(installedPath))) {
|
|
3191
|
-
logSessionStart(`managed-tool-refresh ${tool.id}: refreshed artifact at ${installedPath} failed its
|
|
3287
|
+
if (!(await verifyToolBinary(installedPath, undefined, undefined, getToolVerificationTimeout(tool), tool.checkArgs))) {
|
|
3288
|
+
logSessionStart(`managed-tool-refresh ${tool.id}: refreshed artifact at ${installedPath} failed its verification check`);
|
|
3192
3289
|
return false;
|
|
3193
3290
|
}
|
|
3194
3291
|
// Record the NEW mtime rather than leaving the persisted entry to miss its
|
|
@@ -3212,10 +3309,10 @@ async function refreshPackageManagerManagedTool(tool) {
|
|
|
3212
3309
|
const installed = tool.installStrategy === "pip"
|
|
3213
3310
|
? // `-U` is the whole fix: without it pip treats the installed copy as
|
|
3214
3311
|
// satisfying the requirement and the day-one version never moves.
|
|
3215
|
-
await installPipTool(tool.packageName, { upgrade: true })
|
|
3312
|
+
await installPipTool(tool.id, tool.packageName, { upgrade: true })
|
|
3216
3313
|
: // `gem install` always fetches the newest version that satisfies the
|
|
3217
3314
|
// requirement, so the install command IS the upgrade command.
|
|
3218
|
-
await installGemTool(tool.packageName);
|
|
3315
|
+
await installGemTool(tool.id, tool.packageName);
|
|
3219
3316
|
if (!installed) {
|
|
3220
3317
|
return {
|
|
3221
3318
|
ok: false,
|
|
@@ -3643,7 +3740,24 @@ async function installArchiveTool(tool) {
|
|
|
3643
3740
|
return undefined;
|
|
3644
3741
|
}
|
|
3645
3742
|
}
|
|
3646
|
-
|
|
3743
|
+
/**
|
|
3744
|
+
* Record a genuine package-manager install exception against `toolId` and
|
|
3745
|
+
* log it — the one place `installNpmTool`/`installPipTool`/`installGemTool`'s
|
|
3746
|
+
* catch blocks funnel through (#2661 review F3/Sonar: the three catches were
|
|
3747
|
+
* byte-identical but for the strategy label, a duplication SonarCloud's new-
|
|
3748
|
+
* code gate correctly flagged). The real error (registry E404, EBADENGINE, a
|
|
3749
|
+
* network failure, "no version satisfies…") lands in `installFailureReasons`
|
|
3750
|
+
* so a caller that distinguishes a genuine installer defect from a policy
|
|
3751
|
+
* decline (the tool-smoke lane, #2638) sees the actual message, never the
|
|
3752
|
+
* generic fallback `finishInstallAttempt` uses when nothing set it.
|
|
3753
|
+
*/
|
|
3754
|
+
function recordPackageManagerInstallException(toolId, strategyLabel, packageName, err) {
|
|
3755
|
+
const message = err.message;
|
|
3756
|
+
logSessionStart(`auto-install ${strategyLabel} ${packageName}: exception: ${message}`);
|
|
3757
|
+
installFailureReasons.set(toolId, message);
|
|
3758
|
+
return undefined;
|
|
3759
|
+
}
|
|
3760
|
+
async function installNpmTool(toolId, packageName, binaryName, verificationArgs = ["--version"], verificationTimeoutMs = 10_000) {
|
|
3647
3761
|
try {
|
|
3648
3762
|
// Ensure tools directory exists
|
|
3649
3763
|
await fs.mkdir(TOOLS_DIR, { recursive: true });
|
|
@@ -3727,7 +3841,7 @@ async function installNpmTool(packageName, binaryName) {
|
|
|
3727
3841
|
lastAttemptTransient = false;
|
|
3728
3842
|
isValid = await verifyToolBinary(binPath, undefined, () => {
|
|
3729
3843
|
lastAttemptTransient = true;
|
|
3730
|
-
});
|
|
3844
|
+
}, verificationTimeoutMs, verificationArgs);
|
|
3731
3845
|
if (isValid)
|
|
3732
3846
|
break;
|
|
3733
3847
|
if (attempt < 3) {
|
|
@@ -3767,14 +3881,26 @@ async function installNpmTool(packageName, binaryName) {
|
|
|
3767
3881
|
return binPath;
|
|
3768
3882
|
}
|
|
3769
3883
|
catch (err) {
|
|
3770
|
-
|
|
3771
|
-
return undefined;
|
|
3884
|
+
return recordPackageManagerInstallException(toolId, "npm", packageName, err);
|
|
3772
3885
|
}
|
|
3773
3886
|
}
|
|
3887
|
+
/**
|
|
3888
|
+
* The pip command ladder `installPipTool` tries, per platform, in priority
|
|
3889
|
+
* order — exported so a caller that needs to know "is ANY pip toolchain
|
|
3890
|
+
* reachable on this runner" (without running an install) probes the SAME
|
|
3891
|
+
* candidates rather than a second, narrower guess (#2661 review F4/S5: the
|
|
3892
|
+
* tool-smoke lane's own presence check tried bare `pip` only and missed a
|
|
3893
|
+
* `pip3`-only or python-module-only runner).
|
|
3894
|
+
*/
|
|
3895
|
+
export function pipCommandCandidates() {
|
|
3896
|
+
return process.platform === "win32"
|
|
3897
|
+
? ["pip", "py", "python"]
|
|
3898
|
+
: ["pip3", "pip", "python3", "python"];
|
|
3899
|
+
}
|
|
3774
3900
|
/**
|
|
3775
3901
|
* Install a pip package tool
|
|
3776
3902
|
*/
|
|
3777
|
-
async function installPipTool(packageName,
|
|
3903
|
+
async function installPipTool(toolId, packageName,
|
|
3778
3904
|
/**
|
|
3779
3905
|
* Add `-U`, turning the install into an upgrade. Without it `pip install`
|
|
3780
3906
|
* treats an already-present package as satisfied and leaves the day-one
|
|
@@ -3789,30 +3915,16 @@ options = {}) {
|
|
|
3789
3915
|
const verb = options.upgrade
|
|
3790
3916
|
? ["install", "-U", "--user"]
|
|
3791
3917
|
: ["install", "--user"];
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
},
|
|
3803
|
-
]
|
|
3804
|
-
: [
|
|
3805
|
-
{ command: "pip3", args: [...verb, packageName] },
|
|
3806
|
-
{ command: "pip", args: [...verb, packageName] },
|
|
3807
|
-
{
|
|
3808
|
-
command: "python3",
|
|
3809
|
-
args: ["-m", "pip", ...verb, packageName],
|
|
3810
|
-
},
|
|
3811
|
-
{
|
|
3812
|
-
command: "python",
|
|
3813
|
-
args: ["-m", "pip", ...verb, packageName],
|
|
3814
|
-
},
|
|
3815
|
-
];
|
|
3918
|
+
// Built from `pipCommandCandidates()` — the single source of truth this
|
|
3919
|
+
// module and any other caller (the tool-smoke lane's toolchain-presence
|
|
3920
|
+
// probe, #2661 review) share, rather than a second, independently
|
|
3921
|
+
// maintained ladder that can drift.
|
|
3922
|
+
const pipCandidates = pipCommandCandidates().map((command) => ({
|
|
3923
|
+
command,
|
|
3924
|
+
args: command === "pip" || command === "pip3"
|
|
3925
|
+
? [...verb, packageName]
|
|
3926
|
+
: ["-m", "pip", ...verb, packageName],
|
|
3927
|
+
}));
|
|
3816
3928
|
let lastError = "";
|
|
3817
3929
|
for (const candidate of pipCandidates) {
|
|
3818
3930
|
const pipResult = await safeSpawnAsync(candidate.command, candidate.args, {
|
|
@@ -3909,11 +4021,10 @@ options = {}) {
|
|
|
3909
4021
|
throw new Error(`Failed to install ${packageName}: no usable pip command found (${lastError || "unknown error"})`);
|
|
3910
4022
|
}
|
|
3911
4023
|
catch (err) {
|
|
3912
|
-
|
|
3913
|
-
return undefined;
|
|
4024
|
+
return recordPackageManagerInstallException(toolId, "pip", packageName, err);
|
|
3914
4025
|
}
|
|
3915
4026
|
}
|
|
3916
|
-
async function installGemTool(packageName) {
|
|
4027
|
+
async function installGemTool(toolId, packageName) {
|
|
3917
4028
|
try {
|
|
3918
4029
|
const gemResult = await safeSpawnAsync("gem", ["install", packageName, "--no-document"], {
|
|
3919
4030
|
timeout: 120_000,
|
|
@@ -3930,8 +4041,7 @@ async function installGemTool(packageName) {
|
|
|
3930
4041
|
return packageName;
|
|
3931
4042
|
}
|
|
3932
4043
|
catch (err) {
|
|
3933
|
-
|
|
3934
|
-
return undefined;
|
|
4044
|
+
return recordPackageManagerInstallException(toolId, "gem", packageName, err);
|
|
3935
4045
|
}
|
|
3936
4046
|
}
|
|
3937
4047
|
/**
|
|
@@ -4022,7 +4132,7 @@ export async function installTool(toolId) {
|
|
|
4022
4132
|
case "npm": {
|
|
4023
4133
|
if (!tool.packageName || !tool.binaryName)
|
|
4024
4134
|
return false;
|
|
4025
|
-
const npmPath = await installNpmTool(tool.packageName, tool.binaryName);
|
|
4135
|
+
const npmPath = await installNpmTool(tool.id, tool.packageName, tool.binaryName, tool.checkArgs, getToolVerificationTimeout(tool));
|
|
4026
4136
|
if (npmPath !== undefined) {
|
|
4027
4137
|
// #1746 review F4: an install just resolved this package's range
|
|
4028
4138
|
// against the registry, so record it as freshly checked. Otherwise a
|
|
@@ -4048,13 +4158,13 @@ export async function installTool(toolId) {
|
|
|
4048
4158
|
case "pip": {
|
|
4049
4159
|
if (!tool.packageName)
|
|
4050
4160
|
return false;
|
|
4051
|
-
const pipPath = await installPipTool(tool.packageName);
|
|
4161
|
+
const pipPath = await installPipTool(tool.id, tool.packageName);
|
|
4052
4162
|
return finishInstallAttempt(tool.id, pipPath !== undefined, startedAt);
|
|
4053
4163
|
}
|
|
4054
4164
|
case "gem": {
|
|
4055
4165
|
if (!tool.packageName)
|
|
4056
4166
|
return false;
|
|
4057
|
-
const gemPath = await installGemTool(tool.packageName);
|
|
4167
|
+
const gemPath = await installGemTool(tool.id, tool.packageName);
|
|
4058
4168
|
return finishInstallAttempt(tool.id, gemPath !== undefined, startedAt);
|
|
4059
4169
|
}
|
|
4060
4170
|
case "github": {
|
|
@@ -4316,7 +4426,13 @@ async function ensureToolResolved(toolId, opts) {
|
|
|
4316
4426
|
return await ensurePromise;
|
|
4317
4427
|
}
|
|
4318
4428
|
finally {
|
|
4319
|
-
|
|
4429
|
+
// Identity-guarded release (#1968's pattern): delete only if THIS run is
|
|
4430
|
+
// still the registered one. A bare delete-by-key lets a late-settling run
|
|
4431
|
+
// evict a live successor a second writer registered under the same key
|
|
4432
|
+
// mid-flight, after which the next caller starts a duplicate ensure/install.
|
|
4433
|
+
if (ensureInFlight.get(inFlightKey) === ensurePromise) {
|
|
4434
|
+
ensureInFlight.delete(inFlightKey);
|
|
4435
|
+
}
|
|
4320
4436
|
}
|
|
4321
4437
|
}
|
|
4322
4438
|
// --- Integration Helpers ---
|