@herbertgao/pi-extensions 2026.8.13 → 2026.8.15
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/README.md +19 -14
- package/THIRD_PARTY_NOTICES.md +0 -1
- package/node_modules/@czottmann/pi-automode/CHANGELOG.md +19 -0
- package/node_modules/@czottmann/pi-automode/README.md +5 -1
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +126 -13
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/config.ts +7 -3
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +2 -0
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/hard-deny.ts +134 -23
- package/node_modules/@czottmann/pi-automode/package.json +1 -1
- package/node_modules/@herbertgao/{pi-stash → pi-bark}/LICENSE +4 -5
- package/node_modules/@herbertgao/pi-bark/README.md +44 -0
- package/node_modules/@herbertgao/pi-bark/assets/pi-icon.png +0 -0
- package/node_modules/@herbertgao/pi-bark/package.json +53 -0
- package/node_modules/@herbertgao/pi-bark/src/index.ts +178 -0
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +2 -1
- package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +8 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/@narumitw/pi-btw/README.md +125 -88
- package/node_modules/@narumitw/pi-btw/dist/index.ts +140 -45
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +2 -2
- package/node_modules/@narumitw/pi-btw/package.json +5 -8
- package/node_modules/@narumitw/pi-btw/src/btw.ts +31 -26
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +106 -17
- package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -2
- package/node_modules/@narumitw/pi-btw/src/settings.ts +16 -0
- package/node_modules/@tifan/pi-handoff/README.md +36 -13
- package/node_modules/@tifan/pi-handoff/package.json +2 -2
- package/node_modules/@tifan/pi-handoff/src/index.ts +160 -4
- package/node_modules/@tifan/pi-preferred-thinking/README.md +1 -1
- package/node_modules/@tifan/pi-preferred-thinking/package.json +1 -1
- package/node_modules/@tifan/pi-preferred-thinking/src/index.ts +15 -2
- package/node_modules/@tifan/pi-recap/README.md +1 -1
- package/node_modules/@tifan/pi-recap/package.json +2 -2
- package/node_modules/@tifan/pi-recap/src/index.ts +15 -0
- package/node_modules/@tifan/pi-rename/README.md +42 -21
- package/node_modules/@tifan/pi-rename/package.json +1 -1
- package/node_modules/@tifan/pi-rename/src/index.ts +62 -11
- package/node_modules/@tifan/pi-rename/src/language.ts +18 -0
- package/node_modules/@tifan/pi-rename/src/models.ts +69 -13
- package/node_modules/@tifan/pi-rename/src/naming.ts +36 -7
- package/node_modules/@tifan/pi-rename/src/sanitize.ts +22 -14
- package/node_modules/pi-lens/CHANGELOG.md +320 -0
- package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +79 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +16 -1
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +13 -6
- package/node_modules/pi-lens/dist/clients/availability-probe-flight.js +12 -0
- package/node_modules/pi-lens/dist/clients/biome-client.js +7 -0
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +20 -0
- package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +56 -19
- package/node_modules/pi-lens/dist/clients/build-identity.js +95 -0
- package/node_modules/pi-lens/dist/clients/bus-publish.js +3 -0
- package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +2 -1
- package/node_modules/pi-lens/dist/clients/cache-observability.js +2 -1
- package/node_modules/pi-lens/dist/clients/cascade-logger.js +15 -1
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +14 -11
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +56 -4
- package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +14 -0
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +20 -3
- package/node_modules/pi-lens/dist/clients/deps/minimatch.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +39 -0
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +150 -9
- package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +231 -9
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +205 -100
- package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +87 -0
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +21 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +174 -55
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +5 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +16 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +54 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +1 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +37 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +11 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +4 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +8 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +76 -25
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +4 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +19 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +7 -13
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +51 -17
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +66 -1
- package/node_modules/pi-lens/dist/clients/file-utils.js +157 -7
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +5 -0
- package/node_modules/pi-lens/dist/clients/formatters.js +28 -3
- package/node_modules/pi-lens/dist/clients/git-guard.js +197 -47
- package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +32 -1
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +5 -1
- package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +7 -0
- package/node_modules/pi-lens/dist/clients/installer/index.js +131 -34
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +3 -1
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +4 -13
- package/node_modules/pi-lens/dist/clients/instance-registry-lock.js +145 -0
- package/node_modules/pi-lens/dist/clients/instance-registry.js +432 -122
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +6 -1
- package/node_modules/pi-lens/dist/clients/language-profile.js +4 -2
- package/node_modules/pi-lens/dist/clients/latency-logger.js +19 -1
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
- package/node_modules/pi-lens/dist/clients/lens-map.js +3 -1
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +4 -0
- package/node_modules/pi-lens/dist/clients/lsp/client.js +516 -31
- package/node_modules/pi-lens/dist/clients/lsp/config.js +25 -2
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +9 -4
- package/node_modules/pi-lens/dist/clients/lsp/index.js +509 -182
- package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +5 -0
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +36 -8
- package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +116 -4
- package/node_modules/pi-lens/dist/clients/lsp/server.js +142 -13
- package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +99 -0
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +16 -6
- package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +32 -23
- package/node_modules/pi-lens/dist/clients/lsp-mutation.js +16 -2
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +12 -6
- package/node_modules/pi-lens/dist/clients/mcp/session.js +14 -1
- package/node_modules/pi-lens/dist/clients/memory-sampler.js +23 -1
- package/node_modules/pi-lens/dist/clients/message-end-attribution.js +30 -0
- package/node_modules/pi-lens/dist/clients/metrics-history.js +21 -4
- package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +232 -18
- package/node_modules/pi-lens/dist/clients/package-manager.js +6 -18
- package/node_modules/pi-lens/dist/clients/path-keyed-map.js +14 -1
- package/node_modules/pi-lens/dist/clients/path-utils.js +32 -0
- package/node_modules/pi-lens/dist/clients/pipeline.js +0 -8
- package/node_modules/pi-lens/dist/clients/process-singletons.js +210 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +2 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +10 -4
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +33 -11
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +24 -2
- package/node_modules/pi-lens/dist/clients/read-guard.js +74 -7
- package/node_modules/pi-lens/dist/clients/recent-touches.js +17 -5
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +639 -62
- package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +5 -1
- package/node_modules/pi-lens/dist/clients/review-graph-logger.js +21 -1
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +68 -0
- package/node_modules/pi-lens/dist/clients/runtime-session.js +30 -12
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +15 -0
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +106 -7
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +242 -27
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +158 -18
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +48 -17
- package/node_modules/pi-lens/dist/clients/session-lifecycle.js +238 -42
- package/node_modules/pi-lens/dist/clients/sg-runner.js +27 -15
- package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +338 -0
- package/node_modules/pi-lens/dist/clients/spawn-output-cap.js +37 -0
- package/node_modules/pi-lens/dist/clients/spawn-timeout-cooldown.js +15 -1
- package/node_modules/pi-lens/dist/clients/startup-scan.js +6 -7
- package/node_modules/pi-lens/dist/clients/startup-timing.js +69 -4
- package/node_modules/pi-lens/dist/clients/string-utils.js +20 -0
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +419 -88
- package/node_modules/pi-lens/dist/clients/tool-policy.js +1 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +11 -5
- package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +17 -1
- package/node_modules/pi-lens/dist/clients/warm-attach.js +8 -10
- package/node_modules/pi-lens/dist/clients/word-index-logger.js +13 -1
- package/node_modules/pi-lens/dist/clients/word-index-store.js +448 -0
- package/node_modules/pi-lens/dist/clients/word-index.js +582 -104
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +17 -0
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +26 -6
- package/node_modules/pi-lens/dist/index.js +11243 -6651
- package/node_modules/pi-lens/dist/mcp/build-staleness.js +8 -7
- package/node_modules/pi-lens/dist/mcp/cli.js +3 -1
- package/node_modules/pi-lens/dist/mcp/server.js +6 -1
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +4 -0
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +14 -5
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +3 -1
- package/node_modules/pi-lens/docs/agent-guide.md +4 -2
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +41 -10
- package/node_modules/pi-lens/docs/astplayground.md +26 -17
- package/node_modules/pi-lens/package.json +5 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/c-no-malloc-free-test.yml +20 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/cpp-no-malloc-free-test.yml +25 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/cpp-no-printf-test.yml +19 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-fmt-println-test.yml +19 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-panic-in-lib-test.yml +17 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-underscore-func-name-test.yml +18 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-prefer-errors-is-test.yml +15 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-prefer-string-builder-test.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/hyphenated-svg-attribute-test.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-lateinit-test.yml +17 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-nullable-boolean-test.yml +15 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-println-test.yml +19 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-unnecessary-let-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-unsafe-cast-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-var-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-prefer-data-class-test.yml +25 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-prefer-is-empty-test.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-important-test.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-types-test.yml +30 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-string-concat-in-loop-test.yml +100 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-system-out-println-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/prefer-string-is-empty-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/python-optional-type-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/reducible-list-comprehension-test.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ruby-symbol-to-proc-candidate-test.yml +10 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/rust-no-chars-enumerate-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/c-no-malloc-free.yml +39 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/cpp-no-malloc-free.yml +45 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/cpp-no-printf.yml +41 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-fmt-println.yml +43 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-panic-in-lib.yml +41 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-underscore-func-name.yml +35 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-prefer-errors-is.yml +40 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-prefer-string-builder.yml +43 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/hyphenated-svg-attribute.yml +32 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-lateinit.yml +37 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-nullable-boolean.yml +33 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-println.yml +42 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-unnecessary-let.yml +28 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-unsafe-cast.yml +28 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-var.yml +35 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-prefer-data-class.yml +66 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-prefer-is-empty.yml +35 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-important.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-types.yml +33 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-string-concat-in-loop.yml +90 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-system-out-println.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/prefer-string-is-empty.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/python-optional-type.yml +22 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/reducible-list-comprehension.yml +29 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/ruby-symbol-to-proc-candidate.yml +25 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/rust-no-chars-enumerate.yml +19 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +35 -0
- package/node_modules/pi-mcp-adapter/README.md +5 -2
- package/node_modules/pi-mcp-adapter/cli.js +4 -4
- package/node_modules/pi-mcp-adapter/commands.ts +1 -1
- package/node_modules/pi-mcp-adapter/config.ts +1 -0
- package/node_modules/pi-mcp-adapter/dist/config.js +1 -0
- package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.d.ts +24 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js +336 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/types.d.ts +3 -1
- package/node_modules/pi-mcp-adapter/dist/types.js.map +1 -1
- package/node_modules/pi-mcp-adapter/index.ts +90 -6
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +124 -32
- package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +0 -2
- package/node_modules/pi-mcp-adapter/mcp-oauth-provider.ts +89 -0
- package/node_modules/pi-mcp-adapter/mcp-references.ts +9 -1
- package/node_modules/pi-mcp-adapter/package.json +1 -1
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +62 -22
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +1 -1
- package/node_modules/pi-mcp-adapter/server-manager.ts +6 -0
- package/node_modules/pi-mcp-adapter/types.ts +3 -1
- package/node_modules/pi-web-access/CHANGELOG.md +42 -0
- package/node_modules/pi-web-access/README.md +26 -10
- package/node_modules/pi-web-access/curator-page.ts +12 -3
- package/node_modules/pi-web-access/curator-server.ts +3 -1
- package/node_modules/pi-web-access/extract.ts +88 -13
- package/node_modules/pi-web-access/gemini-search.ts +10 -5
- package/node_modules/pi-web-access/github-extract.ts +47 -3
- package/node_modules/pi-web-access/index.ts +40 -40
- package/node_modules/pi-web-access/package.json +1 -1
- package/node_modules/pi-web-access/page-query.ts +61 -8
- package/node_modules/pi-web-access/xcrawl.ts +264 -0
- package/package.json +19 -22
- package/node_modules/@herbertgao/pi-stash/README.md +0 -34
- package/node_modules/@herbertgao/pi-stash/package.json +0 -51
- package/node_modules/@herbertgao/pi-stash/src/index.ts +0 -118
- package/node_modules/@tifan/pi-titlebar-spinner/README.md +0 -24
- package/node_modules/@tifan/pi-titlebar-spinner/package.json +0 -39
- package/node_modules/@tifan/pi-titlebar-spinner/src/index.ts +0 -49
|
@@ -186,6 +186,9 @@ function probeJavaOnPath() {
|
|
|
186
186
|
latched: false,
|
|
187
187
|
retryAfterMs: Math.max(0, javaAvailabilityLatch.getRetryAtMs() - Date.now()),
|
|
188
188
|
budgetMs: JAVA_PROBE_TIMEOUT_MS,
|
|
189
|
+
// No probe ran here: the latch's own remembered cause is replayed
|
|
190
|
+
// as-is, so the call site is the one asserting it (#2209).
|
|
191
|
+
classifiedBy: "caller",
|
|
189
192
|
});
|
|
190
193
|
return Promise.resolve(undefined);
|
|
191
194
|
}
|
|
@@ -226,6 +229,7 @@ async function runJavaProbe() {
|
|
|
226
229
|
latched: true,
|
|
227
230
|
hostStallMs,
|
|
228
231
|
budgetMs: JAVA_PROBE_TIMEOUT_MS,
|
|
232
|
+
classifiedBy: "probe",
|
|
229
233
|
});
|
|
230
234
|
return true;
|
|
231
235
|
}
|
|
@@ -247,6 +251,7 @@ async function runJavaProbe() {
|
|
|
247
251
|
hostStallMs,
|
|
248
252
|
...(retryAfterMs > 0 && { retryAfterMs }),
|
|
249
253
|
budgetMs: JAVA_PROBE_TIMEOUT_MS,
|
|
254
|
+
classifiedBy: "probe",
|
|
250
255
|
});
|
|
251
256
|
return outcome === "transient" ? undefined : false;
|
|
252
257
|
}
|
|
@@ -89,22 +89,42 @@ export function combinePathValuesForPlatform(values, platform = process.platform
|
|
|
89
89
|
function resolvePathValue(env) {
|
|
90
90
|
return combinePathValuesForPlatform([env.PATH, env.Path, env.path]);
|
|
91
91
|
}
|
|
92
|
-
/**
|
|
93
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Run an `execFileSync` probe with the #2095 stderr guard applied, returning
|
|
94
|
+
* `""` on any failure. Exported as a small test seam: production always calls
|
|
95
|
+
* it with the fixed `powershell.exe` command/args below, but a test can pass
|
|
96
|
+
* a command guaranteed to fail with real stderr output (no real Windows or
|
|
97
|
+
* powershell.exe required) to prove the guard is actually reached and not
|
|
98
|
+
* vacuous.
|
|
99
|
+
*/
|
|
100
|
+
export function runStderrGuardedProbe(command, args) {
|
|
94
101
|
try {
|
|
95
102
|
const { execFileSync } = require("node:child_process");
|
|
96
|
-
const out = execFileSync(
|
|
97
|
-
|
|
98
|
-
"
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
103
|
+
const out = execFileSync(command, args, {
|
|
104
|
+
timeout: 3000,
|
|
105
|
+
encoding: "utf8",
|
|
106
|
+
// #2095 sibling: execFileSync inherits stderr to the parent by
|
|
107
|
+
// default (only stdout is captured into `out`), so a failed
|
|
108
|
+
// powershell.exe launch would print raw diagnostics into the pi
|
|
109
|
+
// TUI even though the surrounding catch already returns "" (read as
|
|
110
|
+
// "unknown" PATH by every caller). Pipe stderr instead of inheriting it.
|
|
111
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
112
|
+
});
|
|
102
113
|
return out.trim();
|
|
103
114
|
}
|
|
104
115
|
catch {
|
|
105
116
|
return "";
|
|
106
117
|
}
|
|
107
118
|
}
|
|
119
|
+
/** Read live system+user PATH from Windows registry (bypasses stale process.env.PATH). */
|
|
120
|
+
function readWindowsRegistryPath() {
|
|
121
|
+
return runStderrGuardedProbe("powershell.exe", [
|
|
122
|
+
"-NoProfile",
|
|
123
|
+
"-NonInteractive",
|
|
124
|
+
"-Command",
|
|
125
|
+
"[System.Environment]::GetEnvironmentVariable('Path','Machine') + ';' + [System.Environment]::GetEnvironmentVariable('Path','User')",
|
|
126
|
+
]);
|
|
127
|
+
}
|
|
108
128
|
let _liveWindowsPath = null;
|
|
109
129
|
function getLiveWindowsPath() {
|
|
110
130
|
if (_liveWindowsPath === null) {
|
|
@@ -234,6 +254,14 @@ function findBinaryOnPath(command, env) {
|
|
|
234
254
|
encoding: "utf-8",
|
|
235
255
|
stdio: ["ignore", "pipe", "ignore"],
|
|
236
256
|
env,
|
|
257
|
+
// #1980 population sweep: this was the one synchronous
|
|
258
|
+
// child-process site in the repo with no bound at all. It runs on
|
|
259
|
+
// the LSP server spawn path, so an unresponsive `where`/`which` — a
|
|
260
|
+
// stale network PATH entry is the classic cause — parked the event
|
|
261
|
+
// loop with no ceiling, and read as ordinary compute in
|
|
262
|
+
// `loop_block`. 5s matches `isCommandAvailable`/`findCommand` in
|
|
263
|
+
// clients/safe-spawn.ts, which run these same two binaries.
|
|
264
|
+
timeout: 5000,
|
|
237
265
|
})
|
|
238
266
|
.split(/\r?\n/)
|
|
239
267
|
.map((line) => line.trim())
|
|
@@ -36,6 +36,21 @@
|
|
|
36
36
|
* index whose keys are produced by this same process's touch path, so the
|
|
37
37
|
* cheap ephemeral normalizer is the lifetime-appropriate choice per the
|
|
38
38
|
* PathKeyedMap guidance — no realpath walk on the per-edit path.
|
|
39
|
+
*
|
|
40
|
+
* #2324 F3/R2-A: this module ALSO hands off in the other direction, for
|
|
41
|
+
* ast-grep specifically. Gate B's napi runner is a second producer of
|
|
42
|
+
* coverage for the same (file, "ast-grep") pair — when it runs as the
|
|
43
|
+
* fallback and actually evaluates rules, it calls
|
|
44
|
+
* {@link recordNapiFallbackCoverage}. The PRODUCER above reads that record
|
|
45
|
+
* (via {@link napiFallbackCoveredSince}) before deciding whether to mark a
|
|
46
|
+
* pair, because napi's Gate-B check settles synchronously while the aux-grace
|
|
47
|
+
* wait's own decision takes up to its full grace budget — by the time the
|
|
48
|
+
* wait is ready to mark, THIS touch's napi run (if any) has already finished,
|
|
49
|
+
* so the wait can reliably see it; a clear issued from napi's side cannot,
|
|
50
|
+
* because the mark it would be racing has not been written yet.
|
|
51
|
+
* {@link clearPendingAuxiliaryCoverage} is still used, but only to drop a
|
|
52
|
+
* LEFTOVER pair from an EARLIER touch once napi has fresh evaluated findings
|
|
53
|
+
* to supersede it.
|
|
39
54
|
*/
|
|
40
55
|
import { normalizeEphemeralMapKey } from "../path-utils.js";
|
|
41
56
|
/** Maximum pending (filePath, serverId) pairs; oldest evicted beyond this. */
|
|
@@ -47,6 +62,7 @@ export const MAX_PENDING_AUX_ENTRIES = 50;
|
|
|
47
62
|
* forever. Overridable via `PI_LENS_LATE_AUX_REARM_TTL_MS`.
|
|
48
63
|
*/
|
|
49
64
|
export const DEFAULT_LATE_AUX_REARM_TTL_MS = 5 * 60_000;
|
|
65
|
+
export const MAX_LATE_AUX_REARMS = 8;
|
|
50
66
|
/**
|
|
51
67
|
* Read the `PI_LENS_LATE_AUX_REARM_TTL_MS` env override at call time (not
|
|
52
68
|
* module load time) so tests and users can tune without a rebuild — same
|
|
@@ -73,6 +89,14 @@ export function isPendingAuxiliaryPastRearmTtl(pair, nowMs = Date.now()) {
|
|
|
73
89
|
return nowMs - anchor >= readLateAuxRearmTtlMs();
|
|
74
90
|
}
|
|
75
91
|
const pending = new Map();
|
|
92
|
+
/**
|
|
93
|
+
* #2168: pairs evicted at the cap have no other retirement record — the
|
|
94
|
+
* drain-time reconciliation in `runtime-turn.ts` never sees them because
|
|
95
|
+
* they are gone before a drain can observe them. Counted here so the
|
|
96
|
+
* consumer can fold them into its per-turn `pairCreated`/retirement sum
|
|
97
|
+
* instead of the pair vanishing uncounted.
|
|
98
|
+
*/
|
|
99
|
+
let capEvictedCount = 0;
|
|
76
100
|
function pairKey(filePath, serverId) {
|
|
77
101
|
return `${normalizeEphemeralMapKey(filePath)}\u0000${serverId}`;
|
|
78
102
|
}
|
|
@@ -89,7 +113,7 @@ function pairKey(filePath, serverId) {
|
|
|
89
113
|
* `rearmedAtMs`) marks a NEWER revision, so it resets any prior re-arm stamp
|
|
90
114
|
* along with the baseline — the new pair's TTL starts from its own mark.
|
|
91
115
|
*/
|
|
92
|
-
export function markPendingAuxiliaryCoverage(filePath, serverIds, markedAtMs = Date.now(), rearmedAtMs) {
|
|
116
|
+
export function markPendingAuxiliaryCoverage(filePath, serverIds, markedAtMs = Date.now(), rearmedAtMs, rearmCount) {
|
|
93
117
|
for (const serverId of serverIds) {
|
|
94
118
|
const key = pairKey(filePath, serverId);
|
|
95
119
|
const existing = pending.get(key);
|
|
@@ -107,20 +131,45 @@ export function markPendingAuxiliaryCoverage(filePath, serverIds, markedAtMs = D
|
|
|
107
131
|
serverId,
|
|
108
132
|
markedAtMs: existing.markedAtMs,
|
|
109
133
|
lastRearmedAtMs: rearmedAtMs,
|
|
134
|
+
rearmCount: rearmCount ?? (existing.rearmCount ?? 0) + 1,
|
|
110
135
|
});
|
|
111
136
|
continue;
|
|
112
137
|
}
|
|
113
138
|
pending.set(key, rearmedAtMs === undefined
|
|
114
139
|
? { filePath, serverId, markedAtMs }
|
|
115
|
-
: {
|
|
140
|
+
: {
|
|
141
|
+
filePath,
|
|
142
|
+
serverId,
|
|
143
|
+
markedAtMs,
|
|
144
|
+
lastRearmedAtMs: rearmedAtMs,
|
|
145
|
+
rearmCount: rearmCount ?? 1,
|
|
146
|
+
});
|
|
116
147
|
while (pending.size > MAX_PENDING_AUX_ENTRIES) {
|
|
117
148
|
const oldest = pending.keys().next().value;
|
|
118
149
|
if (oldest === undefined)
|
|
119
150
|
break;
|
|
120
151
|
pending.delete(oldest);
|
|
152
|
+
capEvictedCount += 1;
|
|
121
153
|
}
|
|
122
154
|
}
|
|
123
155
|
}
|
|
156
|
+
/** Re-arm a drained pair while carrying its ceiling count across the drain. */
|
|
157
|
+
export function rearmPendingAuxiliaryCoverage(pair, rearmedAtMs = Date.now(), refreshBaseline = false) {
|
|
158
|
+
const nextCount = (pair.rearmCount ?? 0) + 1;
|
|
159
|
+
markPendingAuxiliaryCoverage(pair.filePath, [pair.serverId], refreshBaseline ? rearmedAtMs : pair.markedAtMs, rearmedAtMs, nextCount);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Read-only check: is this (file, server) pair currently pending late
|
|
163
|
+
* delivery? #2324 F2: Gate B's "has this server EVER published for this
|
|
164
|
+
* file" answer goes stale the moment a later touch's aux-grace wait finds
|
|
165
|
+
* the server silent for the CURRENT content — a pending pair is exactly
|
|
166
|
+
* that signal, so a caller deciding whether to skip a fallback runner can
|
|
167
|
+
* tell "published, still current" from "published once, silent now" without
|
|
168
|
+
* draining or mutating the store.
|
|
169
|
+
*/
|
|
170
|
+
export function hasPendingAuxiliaryCoverage(filePath, serverId) {
|
|
171
|
+
return pending.has(pairKey(filePath, serverId));
|
|
172
|
+
}
|
|
124
173
|
/**
|
|
125
174
|
* Remove one pair after its findings were delivered, or whenever the caller
|
|
126
175
|
* knows it is resolved. Unknown pairs are a no-op.
|
|
@@ -139,14 +188,77 @@ export function drainPendingAuxiliaryCoverage() {
|
|
|
139
188
|
pending.clear();
|
|
140
189
|
return drained;
|
|
141
190
|
}
|
|
142
|
-
/**
|
|
143
|
-
export function
|
|
191
|
+
/** Current pending pair count for bounded reconciliation telemetry. */
|
|
192
|
+
export function pendingAuxiliaryCoverageSize() {
|
|
144
193
|
return pending.size;
|
|
145
194
|
}
|
|
195
|
+
/** Test-only alias for the store-size assertion seam. */
|
|
196
|
+
export const pendingAuxiliaryCoverageSizeForTests = pendingAuxiliaryCoverageSize;
|
|
197
|
+
/**
|
|
198
|
+
* Drain (read and reset) the cap-eviction count accumulated since the last
|
|
199
|
+
* drain. Mirrors {@link drainPendingAuxiliaryCoverage}'s consume-once shape
|
|
200
|
+
* so the turn-end consumer can fold evictions that happened between two
|
|
201
|
+
* turn ends into its reconciliation sum exactly once, never accumulating
|
|
202
|
+
* across turns.
|
|
203
|
+
*/
|
|
204
|
+
export function drainPendingAuxCapEvictedCount() {
|
|
205
|
+
const count = capEvictedCount;
|
|
206
|
+
capEvictedCount = 0;
|
|
207
|
+
return count;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* #2324 R2-A: (file) -> the timestamp of the LAST time the ast-grep napi
|
|
211
|
+
* fallback actually evaluated rules for it, as Gate B's runner
|
|
212
|
+
* (`clients/dispatch/runners/ast-grep-napi.ts`). This is the cross-module
|
|
213
|
+
* hand-off the aux-grace wait (`clients/lsp/index.ts`) needs to avoid
|
|
214
|
+
* marking a pending late-auxiliary pair for a file napi ALREADY delivered
|
|
215
|
+
* for on THIS touch.
|
|
216
|
+
*
|
|
217
|
+
* Ordering is why a synchronous clear-on-napi-run cannot do this job: the
|
|
218
|
+
* wait that decides whether to mark a pair runs for up to ~1800ms (its own
|
|
219
|
+
* grace budget) AFTER napi's Gate-B check, which is a single map lookup.
|
|
220
|
+
* Clearing at napi's run start (or end) races the mark and reliably loses —
|
|
221
|
+
* the mark, if any, has not been written yet. The wait itself is the only
|
|
222
|
+
* place that knows the outcome, so it is the one that must consult this
|
|
223
|
+
* store and skip marking, not the runner that must guess at the wait's
|
|
224
|
+
* future decision.
|
|
225
|
+
*
|
|
226
|
+
* Bounded like the pending-pair store itself; oldest evicted first.
|
|
227
|
+
*/
|
|
228
|
+
const napiFallbackCoverage = new Map();
|
|
229
|
+
export const MAX_NAPI_COVERAGE_ENTRIES = 50;
|
|
230
|
+
/** Record that napi actually evaluated rules for `filePath` at `atMs`. */
|
|
231
|
+
export function recordNapiFallbackCoverage(filePath, atMs = Date.now()) {
|
|
232
|
+
const key = normalizeEphemeralMapKey(filePath);
|
|
233
|
+
napiFallbackCoverage.delete(key);
|
|
234
|
+
napiFallbackCoverage.set(key, atMs);
|
|
235
|
+
while (napiFallbackCoverage.size > MAX_NAPI_COVERAGE_ENTRIES) {
|
|
236
|
+
const oldest = napiFallbackCoverage.keys().next().value;
|
|
237
|
+
if (oldest === undefined)
|
|
238
|
+
break;
|
|
239
|
+
napiFallbackCoverage.delete(oldest);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Did napi cover `filePath` at or after `sinceMs`? The aux-grace wait passes
|
|
244
|
+
* its own `waitStartedAt` baseline so a STALE coverage record from an
|
|
245
|
+
* earlier touch (napi ran for a PREVIOUS revision) cannot suppress marking a
|
|
246
|
+
* pair this touch's silent server genuinely needs delivered later.
|
|
247
|
+
*/
|
|
248
|
+
export function napiFallbackCoveredSince(filePath, sinceMs) {
|
|
249
|
+
const ts = napiFallbackCoverage.get(normalizeEphemeralMapKey(filePath));
|
|
250
|
+
return ts !== undefined && ts >= sinceMs;
|
|
251
|
+
}
|
|
252
|
+
/** Test-only reset for the napi-coverage hand-off. */
|
|
253
|
+
export function resetNapiFallbackCoverageForTests() {
|
|
254
|
+
napiFallbackCoverage.clear();
|
|
255
|
+
}
|
|
146
256
|
/**
|
|
147
257
|
* Session-boundary clear (#1635): pending baselines are unreachable after
|
|
148
258
|
* reset (the generation-stamped keys no longer match any active session).
|
|
149
259
|
*/
|
|
150
260
|
export function resetPendingAuxiliaryCoverage() {
|
|
151
261
|
pending.clear();
|
|
262
|
+
capEvictedCount = 0;
|
|
263
|
+
napiFallbackCoverage.clear();
|
|
152
264
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* - Auto-installation strategies
|
|
7
7
|
* - Platform-specific handling
|
|
8
8
|
*/
|
|
9
|
-
import { existsSync, mkdirSync } from "node:fs";
|
|
9
|
+
import { existsSync, mkdirSync, statSync } from "node:fs";
|
|
10
10
|
import { access, readFile, readdir, stat } from "node:fs/promises";
|
|
11
11
|
import os from "node:os";
|
|
12
12
|
import path from "node:path";
|
|
@@ -14,7 +14,7 @@ import { getGlobalPiLensDir, getProjectIgnoreGlobs, isPathIgnoredByProject, } fr
|
|
|
14
14
|
import { STAGE_TMP_PATTERN } from "../atomic-write-staging.js";
|
|
15
15
|
import { DOTNET_CSHARP_ROOT_MARKERS, DOTNET_FSHARP_ROOT_MARKERS, KIND_EXTENSIONS, } from "../file-kinds.js";
|
|
16
16
|
import { direntsHaveMarkerGlobMatch, isAtOrAboveHomeDir, isFullyQualified, isWindowsPath, pathsEqual, } from "../path-utils.js";
|
|
17
|
-
import { ensureTool, getToolEnvironment, getToolPath, } from "../installer/index.js";
|
|
17
|
+
import { ensureTool, findManagedToolBinary, getToolEnvironment, getToolPath, } from "../installer/index.js";
|
|
18
18
|
import { resolveOpengrepConfig } from "../opengrep-config.js";
|
|
19
19
|
import { isZizmorAuditTarget, resolveZizmorGitHubToken, } from "../zizmor-config.js";
|
|
20
20
|
import { logLatency } from "../latency-logger.js";
|
|
@@ -29,6 +29,7 @@ import { createLombokJdtlsArgs } from "./lombok.js";
|
|
|
29
29
|
import { resolveJavaRuntimeEnv } from "./jvm-runtime.js";
|
|
30
30
|
import { normalizeMapKey } from "./path-utils.js";
|
|
31
31
|
import { getRubyVersionDirNamesSync } from "./ruby-drive-dirs.js";
|
|
32
|
+
import { getProcessSingleton } from "../process-singletons.js";
|
|
32
33
|
const FIXTURE_ROOT_SEGMENTS = new Set(["__fixtures__", "testdata"]);
|
|
33
34
|
const FALLBACK_PROJECT_MARKERS = [
|
|
34
35
|
".git",
|
|
@@ -77,10 +78,69 @@ const PROJECT_BOUNDARY_MARKERS = [
|
|
|
77
78
|
// process (not once per session). Keep the root-boundary marker below aligned
|
|
78
79
|
// with FALLBACK_PROJECT_MARKERS, the shared fallback root-policy marker set.
|
|
79
80
|
const loggedRootCeilingClamps = new Set();
|
|
80
|
-
|
|
81
|
+
const posixCaseInsensitiveByPath = new Map();
|
|
82
|
+
function posixFilesystemIsCaseInsensitive(root) {
|
|
83
|
+
const resolved = path.resolve(root);
|
|
84
|
+
const cached = posixCaseInsensitiveByPath.get(resolved);
|
|
85
|
+
if (cached !== undefined)
|
|
86
|
+
return cached;
|
|
87
|
+
if (!existsSync(resolved)) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
const name = path.basename(resolved);
|
|
91
|
+
const alternate = name.toLowerCase() === name ? name.toUpperCase() : name.toLowerCase();
|
|
92
|
+
let insensitive = false;
|
|
93
|
+
const alternatePath = path.join(path.dirname(resolved), alternate);
|
|
94
|
+
if (alternate !== name && existsSync(alternatePath)) {
|
|
95
|
+
try {
|
|
96
|
+
const actual = statSync(resolved);
|
|
97
|
+
const alternateStat = statSync(alternatePath);
|
|
98
|
+
insensitive =
|
|
99
|
+
actual.dev === alternateStat.dev && actual.ino === alternateStat.ino;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
insensitive = false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
posixCaseInsensitiveByPath.set(resolved, insensitive);
|
|
106
|
+
return insensitive;
|
|
107
|
+
}
|
|
108
|
+
export function resetLSPCaseSensitivityState() {
|
|
109
|
+
posixCaseInsensitiveByPath.clear();
|
|
110
|
+
}
|
|
111
|
+
export function _getPosixCaseSensitivityCacheSizeForTests() {
|
|
112
|
+
return posixCaseInsensitiveByPath.size;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Path-shape-aware containment test: is `candidate` the same path as
|
|
116
|
+
* `ancestor`, or inside it?
|
|
117
|
+
*
|
|
118
|
+
* Exported so the session-cwd registry (`clients/lsp/config.ts`) and the
|
|
119
|
+
* foreign-root decline gate (`clients/lsp/index.ts`) test containment with the
|
|
120
|
+
* SAME comparator the root ceiling uses. A second hand-rolled `path.relative`
|
|
121
|
+
* helper alongside this one is how the two drift apart on Windows-shaped
|
|
122
|
+
* paths (shape 2 / #1150): `path.isAbsolute("C:\\repo")` is false on POSIX, so
|
|
123
|
+
* a host-default comparator silently mis-answers a win32 path on Linux CI.
|
|
124
|
+
*/
|
|
125
|
+
export function isSameOrWithin(ancestor, candidate, deps = {}) {
|
|
81
126
|
const windowsShaped = isWindowsPath(ancestor) || isWindowsPath(candidate);
|
|
82
127
|
const pathApi = windowsShaped ? path.win32 : path;
|
|
83
|
-
const
|
|
128
|
+
const resolvedAncestor = pathApi.resolve(ancestor);
|
|
129
|
+
const resolvedCandidate = pathApi.resolve(candidate);
|
|
130
|
+
let caseInsensitive = false;
|
|
131
|
+
if (!windowsShaped) {
|
|
132
|
+
if (deps.caseInsensitiveProbe) {
|
|
133
|
+
const cached = posixCaseInsensitiveByPath.get(resolvedAncestor);
|
|
134
|
+
caseInsensitive = cached ?? deps.caseInsensitiveProbe(resolvedAncestor);
|
|
135
|
+
if (cached === undefined) {
|
|
136
|
+
posixCaseInsensitiveByPath.set(resolvedAncestor, caseInsensitive);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
caseInsensitive = posixFilesystemIsCaseInsensitive(resolvedAncestor);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const relative = pathApi.relative(caseInsensitive ? resolvedAncestor.toLowerCase() : resolvedAncestor, caseInsensitive ? resolvedCandidate.toLowerCase() : resolvedCandidate);
|
|
84
144
|
return (relative === "" ||
|
|
85
145
|
(!relative.startsWith("..") && !pathApi.isAbsolute(relative)));
|
|
86
146
|
}
|
|
@@ -251,6 +311,24 @@ export async function resolveAndLaunch(spec, allowInstall) {
|
|
|
251
311
|
lastRuntimeFailure = err instanceof Error ? err : new Error(message);
|
|
252
312
|
}
|
|
253
313
|
};
|
|
314
|
+
// #2140: consult the release-managed binary directory (~/.pi-lens/bin)
|
|
315
|
+
// BEFORE the bare-PATH candidates below. A bare `candidates: ["opengrep"]`
|
|
316
|
+
// entry only ever resolves through the OS's own PATH lookup — never that
|
|
317
|
+
// directory — so a github/maven/archive-strategy tool installed there with
|
|
318
|
+
// no PATH entry ENOENTs every direct candidate first, only to have the
|
|
319
|
+
// managed-install step further down find the very same binary a few
|
|
320
|
+
// hundred ms later. `findManagedToolBinary` does a bare `fs.access`, no
|
|
321
|
+
// spawn, so this costs at most a few stat calls per server launch attempt
|
|
322
|
+
// (not per file/dispatch — launches are session-scoped). Mirrors
|
|
323
|
+
// `findManagedNodeToolBinary`'s npm-managed fast path (runner-helpers.ts)
|
|
324
|
+
// and `SecurityScanClient.probeVersion`'s equivalent fix for the CLI-scan
|
|
325
|
+
// half of this same issue (#2140, landed in PR #2148/#2137).
|
|
326
|
+
const managedCandidate = spec.managedToolId
|
|
327
|
+
? await findManagedToolBinary(spec.managedToolId)
|
|
328
|
+
: undefined;
|
|
329
|
+
const candidates = managedCandidate && !spec.candidates.includes(managedCandidate)
|
|
330
|
+
? [managedCandidate, ...spec.candidates]
|
|
331
|
+
: spec.candidates;
|
|
254
332
|
// A candidate that fails while a LATER candidate (or managed install)
|
|
255
333
|
// succeeds is just fallback, not a failure — logging each immediately floods
|
|
256
334
|
// the logs with scary "candidate failed / npm shim failed / Run npm install"
|
|
@@ -258,7 +336,7 @@ export async function resolveAndLaunch(spec, allowInstall) {
|
|
|
258
336
|
// surface only if ALL direct candidates fail.
|
|
259
337
|
const candidateFailures = [];
|
|
260
338
|
// Step 1 & 2 — try all explicit candidates (includes bare command = PATH lookup)
|
|
261
|
-
for (const [index, command] of
|
|
339
|
+
for (const [index, command] of candidates.entries()) {
|
|
262
340
|
logLatency({
|
|
263
341
|
type: "phase",
|
|
264
342
|
phase: "lsp_launch_candidate_attempt",
|
|
@@ -268,11 +346,11 @@ export async function resolveAndLaunch(spec, allowInstall) {
|
|
|
268
346
|
tool: toolLabel,
|
|
269
347
|
command,
|
|
270
348
|
index,
|
|
271
|
-
totalCandidates:
|
|
349
|
+
totalCandidates: candidates.length,
|
|
272
350
|
allowInstall: canInstall(allowInstall),
|
|
273
351
|
},
|
|
274
352
|
});
|
|
275
|
-
logSessionStart(`lsp launch candidate attempt tool=${toolLabel} idx=${index}/${
|
|
353
|
+
logSessionStart(`lsp launch candidate attempt tool=${toolLabel} idx=${index}/${candidates.length - 1} command=${command} cwd=${spec.cwd}`);
|
|
276
354
|
try {
|
|
277
355
|
const proc = await launchLSP(command, spec.args, {
|
|
278
356
|
cwd: spec.cwd,
|
|
@@ -896,19 +974,23 @@ async function findAncestorFileAmong(startDir, candidateSegmentLists) {
|
|
|
896
974
|
* Svelte). Without this guard an offline or partial install re-runs a 120 s
|
|
897
975
|
* forced reinstall on every spawn.
|
|
898
976
|
*
|
|
899
|
-
* The guard is
|
|
977
|
+
* The guard is process-singleton state, so without an explicit re-arm it
|
|
900
978
|
* would latch for the whole extension-host process — a repair that failed
|
|
901
979
|
* once (transient registry hiccup) would stay unrepairable for every later
|
|
902
980
|
* session in that process. `resetClassicTsRepairGuard` re-arms it; callers
|
|
903
981
|
* wire that into `session_start` alongside the other per-session resets
|
|
904
982
|
* (#1570).
|
|
905
983
|
*/
|
|
906
|
-
|
|
984
|
+
const CLASSIC_TS_REPAIR_FAMILY = "lsp.classic-ts-repair-guard";
|
|
985
|
+
const CLASSIC_TS_REPAIR_VERSION = 1;
|
|
986
|
+
function classicTsRepairState() {
|
|
987
|
+
return getProcessSingleton(CLASSIC_TS_REPAIR_FAMILY, CLASSIC_TS_REPAIR_VERSION, () => ({ attempted: false }));
|
|
988
|
+
}
|
|
907
989
|
/** Re-arm the classic-repair guard so a new session gets its own attempt. */
|
|
908
990
|
export function resetClassicTsRepairGuard() {
|
|
909
|
-
|
|
991
|
+
classicTsRepairState().attempted = false;
|
|
910
992
|
}
|
|
911
|
-
/** Test hook — clears the
|
|
993
|
+
/** Test hook — clears the process-singleton classic-repair guard. */
|
|
912
994
|
export function _resetClassicTsRepairForTests() {
|
|
913
995
|
resetClassicTsRepairGuard();
|
|
914
996
|
}
|
|
@@ -1008,7 +1090,7 @@ async function findTsserverPath(root, allowInstall) {
|
|
|
1008
1090
|
if (discoveredTsserver ||
|
|
1009
1091
|
!discoveredTsc ||
|
|
1010
1092
|
!installAllowed ||
|
|
1011
|
-
|
|
1093
|
+
classicTsRepairState().attempted) {
|
|
1012
1094
|
return discoveredTsserver;
|
|
1013
1095
|
}
|
|
1014
1096
|
// Repair only a compiler we can prove is TypeScript 7+. TypeScript 7 dropped
|
|
@@ -1021,7 +1103,7 @@ async function findTsserverPath(root, allowInstall) {
|
|
|
1021
1103
|
// An older managed tree took `latest` before the registry pinned the classic
|
|
1022
1104
|
// compiler. Reinstall the pinned version once so that tree self-heals,
|
|
1023
1105
|
// without deleting user or project-local TypeScript installations.
|
|
1024
|
-
|
|
1106
|
+
classicTsRepairState().attempted = true;
|
|
1025
1107
|
logSessionStart(`lsp typescript: managed compiler resolved to TypeScript ${discoveredVersion.version}, which ships no tsserver.js; reinstalling pinned classic fallback`);
|
|
1026
1108
|
const repairedTsc = await ensureTool("typescript", {
|
|
1027
1109
|
allowInstall: true,
|
|
@@ -2316,6 +2398,19 @@ export const BashServer = {
|
|
|
2316
2398
|
name: "Bash Language Server",
|
|
2317
2399
|
extensions: [".bash", ".sh", ".zsh"],
|
|
2318
2400
|
root: FileDirRoot,
|
|
2401
|
+
// #2194 bounded the installer's own verification at 20s; the dispatch
|
|
2402
|
+
// touch's client-wait floor stayed at the shared 5s default, so a cold
|
|
2403
|
+
// spawn could still race that budget and read as unavailable even after
|
|
2404
|
+
// installer verification cleared it. Match the installer bound (#2169).
|
|
2405
|
+
// `initializeTimeoutMs` matches it too: the measured cold start
|
|
2406
|
+
// (9,667ms, #2194) has headroom under the unraised 15s default, but
|
|
2407
|
+
// leaving the two fields mismatched is the same latent shape that made
|
|
2408
|
+
// the Prisma/Vue raise a no-op (fix-round F1, #2233) — a slower host
|
|
2409
|
+
// than the one measured would still hit the 15s inner kill despite the
|
|
2410
|
+
// caller being willing to wait 20s. Equalizing costs nothing on the
|
|
2411
|
+
// success path and removes the mismatch outright.
|
|
2412
|
+
clientWaitTimeoutMs: 20_000,
|
|
2413
|
+
initializeTimeoutMs: 20_000,
|
|
2319
2414
|
spawn(root, options) {
|
|
2320
2415
|
return resolveAndLaunch({
|
|
2321
2416
|
candidates: nodeBinCandidates(root, "bash-language-server"),
|
|
@@ -2402,6 +2497,13 @@ export const JsonServer = {
|
|
|
2402
2497
|
["package.json", "tsconfig.json", "jsconfig.json"],
|
|
2403
2498
|
[".git"],
|
|
2404
2499
|
])),
|
|
2500
|
+
// See BashServer above: the installer's 20s verification bound (#2194)
|
|
2501
|
+
// does not, on its own, raise the dispatch touch's cold-spawn wait floor.
|
|
2502
|
+
// Mirror it here so a cold spawn cannot lose that race either (#2169).
|
|
2503
|
+
// `initializeTimeoutMs` matches it for the same reason as BashServer
|
|
2504
|
+
// above (fix-round F1, #2233).
|
|
2505
|
+
clientWaitTimeoutMs: 20_000,
|
|
2506
|
+
initializeTimeoutMs: 20_000,
|
|
2405
2507
|
spawn(root, options) {
|
|
2406
2508
|
return resolveAndLaunch({
|
|
2407
2509
|
candidates: ["vscode-json-language-server"],
|
|
@@ -2444,6 +2546,17 @@ export const PrismaServer = {
|
|
|
2444
2546
|
name: "Prisma Language Server",
|
|
2445
2547
|
extensions: KIND_EXTENSIONS["prisma"],
|
|
2446
2548
|
root: RootWithFallback(createRootDetector(["prisma/schema.prisma", "schema.prisma"])),
|
|
2549
|
+
// Matches the installer's 40s verification bound above (#2169): the
|
|
2550
|
+
// dispatch touch's cold-spawn wait floor defaults to 5s and would
|
|
2551
|
+
// otherwise time the client out well before the binary answers.
|
|
2552
|
+
// `initializeTimeoutMs` MUST be set alongside it (RubyServer's own
|
|
2553
|
+
// precedent below): unset, it falls back to the 15s
|
|
2554
|
+
// `INITIALIZE_TIMEOUT_MS` default in `clients/lsp/client.ts`, which
|
|
2555
|
+
// hard-kills the child mid-handshake — a real cold Prisma run measured
|
|
2556
|
+
// up to 27.3s, so the spawn would die there before this wait ever gets
|
|
2557
|
+
// a chance to matter (fix-round F1, #2233).
|
|
2558
|
+
clientWaitTimeoutMs: 40_000,
|
|
2559
|
+
initializeTimeoutMs: 40_000,
|
|
2447
2560
|
spawn(root, options) {
|
|
2448
2561
|
return resolveAndLaunch({
|
|
2449
2562
|
candidates: nodeBinCandidates(root, "prisma-language-server"),
|
|
@@ -2466,6 +2579,15 @@ export const VueServer = {
|
|
|
2466
2579
|
"pnpm-lock.yaml",
|
|
2467
2580
|
"yarn.lock",
|
|
2468
2581
|
]))),
|
|
2582
|
+
// Vue's launcher loads the full language-service bundle before answering,
|
|
2583
|
+
// matching the installer's 30s verification bound (#2176). The dispatch
|
|
2584
|
+
// touch's cold-spawn wait floor defaults to 5s and needs the same raise so
|
|
2585
|
+
// a cold spawn cannot time out there instead. `initializeTimeoutMs` MUST
|
|
2586
|
+
// match: unset, it falls back to the 15s default in `clients/lsp/client.ts`
|
|
2587
|
+
// and hard-kills a cold spawn (measured up to 22.6s in #2188, 16.2s here)
|
|
2588
|
+
// mid-handshake before this wait can matter (fix-round F1, #2233).
|
|
2589
|
+
clientWaitTimeoutMs: 30_000,
|
|
2590
|
+
initializeTimeoutMs: 30_000,
|
|
2469
2591
|
async spawn(root, options) {
|
|
2470
2592
|
const tsserverPath = await findTsserverPath(root, options?.allowInstall);
|
|
2471
2593
|
// Vue Language Server needs Vue dependencies installed to resolve types.
|
|
@@ -2505,6 +2627,13 @@ export const SvelteServer = {
|
|
|
2505
2627
|
"pnpm-lock.yaml",
|
|
2506
2628
|
"yarn.lock",
|
|
2507
2629
|
]))),
|
|
2630
|
+
// Matches the installer's 20s verification bound above (#2169): the
|
|
2631
|
+
// dispatch touch's cold-spawn wait floor defaults to 5s and would
|
|
2632
|
+
// otherwise time the client out well before the binary answers.
|
|
2633
|
+
// `initializeTimeoutMs` matches it for the same reason as BashServer
|
|
2634
|
+
// above (fix-round F1, #2233).
|
|
2635
|
+
clientWaitTimeoutMs: 20_000,
|
|
2636
|
+
initializeTimeoutMs: 20_000,
|
|
2508
2637
|
async spawn(root, options) {
|
|
2509
2638
|
const tsserverPath = await findTsserverPath(root, options?.allowInstall);
|
|
2510
2639
|
const proc = await resolveAndLaunch({
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The session-root registry: which project roots does this process serve?
|
|
3
|
+
*
|
|
4
|
+
* #2052. A file is IN-SESSION when it lies within any initialized session cwd.
|
|
5
|
+
* `initLSPConfig` (`clients/lsp/config.ts`) is the single writer, and every
|
|
6
|
+
* entry point funnels through it — `ensureLSPConfigInitialized` (`index.ts`),
|
|
7
|
+
* `ensureReady` (`mcp/server.ts`), `clients/runtime-session.ts`, and
|
|
8
|
+
* `clients/lens-engine.ts` — so registration needs no second call site.
|
|
9
|
+
*
|
|
10
|
+
* WHY THIS IS ITS OWN MODULE, not a helper inside `config.ts`: `clients/lsp/
|
|
11
|
+
* index.ts`'s import surface from `config.js` is mirrored by an explicit
|
|
12
|
+
* `vi.mock` factory in ~58 test files. Importing a new symbol from `config.js`
|
|
13
|
+
* breaks every one of them with "No export is defined on the mock", which is a
|
|
14
|
+
* standing tax on any future export. The registry is also a different concern
|
|
15
|
+
* from `workspaceConfigs`: that map stores per-root CONFIG PAYLOAD and is
|
|
16
|
+
* LRU-capped at 32, whereas this set answers "is this root served" and must
|
|
17
|
+
* not silently evict a live root and start declining its files.
|
|
18
|
+
*
|
|
19
|
+
* WHY IT IS NOT A LATCH. The first attempt at #2052 used a single
|
|
20
|
+
* last-writer-wins session cwd that fell back to `process.cwd()`. Two bugs
|
|
21
|
+
* followed directly from that shape, and this module's contract excludes both:
|
|
22
|
+
*
|
|
23
|
+
* - MULTI-ROOT. A process that initializes projA and then projB serves BOTH.
|
|
24
|
+
* A latch silently made projA's files foreign the moment projB registered.
|
|
25
|
+
* - FAIL-OPEN ON EMPTY. An empty registry declines NOTHING. Declining is a
|
|
26
|
+
* hard refusal to answer, so it must rest on positive evidence that a
|
|
27
|
+
* session exists AND that the file sits outside it — never on the absence
|
|
28
|
+
* of evidence. Callers that never declared a session (isolated tests, API
|
|
29
|
+
* consumers, a request racing initialization) therefore keep the pre-#2052
|
|
30
|
+
* clamp behavior instead of having `process.cwd()` gate a refusal.
|
|
31
|
+
*
|
|
32
|
+
* This is also the seam #2053 needs: an explicit `analysisRoot` registers here.
|
|
33
|
+
*/
|
|
34
|
+
import { isSameOrWithin } from "./server.js";
|
|
35
|
+
import path from "node:path";
|
|
36
|
+
/**
|
|
37
|
+
* Insertion-ordered, so eviction drops the OLDEST root. Matches
|
|
38
|
+
* `LSP_CONFIG_CWD_CAP` in `index.ts`, the cap on the set of cwds that
|
|
39
|
+
* `ensureLSPConfigInitialized` will initialize at all — a root this process
|
|
40
|
+
* cannot re-initialize must not still be considered served.
|
|
41
|
+
*/
|
|
42
|
+
const sessionRoots = new Set();
|
|
43
|
+
const SESSION_ROOT_CAP = 128;
|
|
44
|
+
/** Record a session cwd as served. Idempotent; re-registering refreshes nothing. */
|
|
45
|
+
export function registerSessionRoot(cwd) {
|
|
46
|
+
sessionRoots.add(path.resolve(cwd));
|
|
47
|
+
while (sessionRoots.size > SESSION_ROOT_CAP) {
|
|
48
|
+
const oldest = sessionRoots.values().next().value;
|
|
49
|
+
if (oldest === undefined)
|
|
50
|
+
break;
|
|
51
|
+
sessionRoots.delete(oldest);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Return whether this exact root is still served after cap eviction. */
|
|
55
|
+
export function isSessionRootRegistered(cwd) {
|
|
56
|
+
return sessionRoots.has(path.resolve(cwd));
|
|
57
|
+
}
|
|
58
|
+
/** True when a readiness memo must run initialization for this root. */
|
|
59
|
+
export function shouldInitializeSessionRoot(cwd, readyRoots) {
|
|
60
|
+
const normalized = path.resolve(cwd);
|
|
61
|
+
return !readyRoots.has(normalized) || !isSessionRootRegistered(normalized);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Is `filePath` outside EVERY registered session root?
|
|
65
|
+
*
|
|
66
|
+
* Returns false (serve it) when the registry is empty — see the fail-open rule
|
|
67
|
+
* in this module's header. Containment uses `isSameOrWithin`, the same
|
|
68
|
+
* comparator the LSP root ceiling uses, which selects `path.win32` by path
|
|
69
|
+
* SHAPE rather than by `process.platform` (shape 2 / #1150).
|
|
70
|
+
*/
|
|
71
|
+
export function isOutsideAllSessionRoots(filePath) {
|
|
72
|
+
if (sessionRoots.size === 0)
|
|
73
|
+
return false;
|
|
74
|
+
const resolved = path.resolve(filePath);
|
|
75
|
+
for (const root of sessionRoots) {
|
|
76
|
+
if (isSameOrWithin(root, resolved))
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The registered roots, for the decline telemetry record. Sorted with a
|
|
83
|
+
* code-unit comparator so the emitted string is deterministic across locales
|
|
84
|
+
* (#1883), and capped because the record is BOUNDED — a process with many
|
|
85
|
+
* roots must not emit an unbounded path list.
|
|
86
|
+
*/
|
|
87
|
+
export function getSessionRootsForTelemetry(limit = 4) {
|
|
88
|
+
return [...sessionRoots]
|
|
89
|
+
.sort((a, b) => Number(a > b) - Number(a < b))
|
|
90
|
+
.slice(0, limit);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Clear the registry. Called by `resetLSPConfigStateForTests` so the registry
|
|
94
|
+
* and the config store reset together and cannot disagree about which roots
|
|
95
|
+
* exist.
|
|
96
|
+
*/
|
|
97
|
+
export function resetSessionRootsForTests() {
|
|
98
|
+
sessionRoots.clear();
|
|
99
|
+
}
|