@herbertgao/pi-extensions 2026.9.2 → 2026.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/THIRD_PARTY_NOTICES.md +24 -0
- package/node_modules/@herbertgao/sol-pi/LICENSE +19 -0
- package/node_modules/@herbertgao/sol-pi/README.md +159 -0
- package/node_modules/@herbertgao/sol-pi/SECURITY.md +26 -0
- package/node_modules/@herbertgao/sol-pi/THIRD_PARTY_NOTICES.md +19 -0
- package/node_modules/@herbertgao/sol-pi/agents-install.md +150 -0
- package/node_modules/@herbertgao/sol-pi/assets/sol-pi-hero.png +0 -0
- package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +69 -0
- package/node_modules/@herbertgao/sol-pi/docs/configuration.md +75 -0
- package/node_modules/@herbertgao/sol-pi/package.json +76 -0
- package/node_modules/@herbertgao/sol-pi/scripts/check-pi-compat.mjs +32 -0
- package/node_modules/@herbertgao/sol-pi/scripts/check-sol-pi-config.mjs +120 -0
- package/node_modules/@herbertgao/sol-pi/sol-pi.example.json +10 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/config.ts +135 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/file-queue.ts +74 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/index.ts +185 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/then-run.ts +128 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/archive.ts +53 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/candidate.ts +101 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/config.ts +71 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/index.ts +220 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/journal.ts +25 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/provider.ts +164 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/receipt.ts +177 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/index.ts +227 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/ledger.ts +20 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/observation.ts +252 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/economics.ts +237 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/extension.ts +455 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/index.ts +49 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/plan.ts +79 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/state.ts +208 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/tools.ts +100 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/index.ts +42 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/runtime-paths.ts +17 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/tui.ts +71 -0
- package/node_modules/@narumitw/pi-btw/dist/index.ts +12 -2
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +2 -2
- package/node_modules/@narumitw/pi-btw/package.json +1 -1
- package/node_modules/@narumitw/pi-btw/src/btw.ts +13 -2
- package/node_modules/pi-lens/CHANGELOG.md +176 -0
- package/node_modules/pi-lens/README.md +13 -8
- package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +3 -1
- package/node_modules/pi-lens/dist/clients/analysed-root.js +1 -0
- package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +1 -1
- package/node_modules/pi-lens/dist/clients/biome-client.js +13 -2
- package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +2 -0
- package/node_modules/pi-lens/dist/clients/cache-observability.js +122 -1
- package/node_modules/pi-lens/dist/clients/complexity-client.js +5 -0
- package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +2 -0
- package/node_modules/pi-lens/dist/clients/config-resolve.js +6 -3
- package/node_modules/pi-lens/dist/clients/config-schema.js +17 -0
- package/node_modules/pi-lens/dist/clients/config-warn.js +2 -2
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +8 -1
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +5 -1
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +38 -15
- package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +5 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +2 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +4 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +3 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +19 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +12 -12
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +2 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +12 -11
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +2 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +5 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +2 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +8 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +4 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +2 -1
- package/node_modules/pi-lens/dist/clients/effective-config.js +12 -2
- package/node_modules/pi-lens/dist/clients/extension-log.js +2 -0
- package/node_modules/pi-lens/dist/clients/file-role.js +18 -1
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +4 -0
- package/node_modules/pi-lens/dist/clients/formatters.js +54 -188
- package/node_modules/pi-lens/dist/clients/generation-guard.js +4 -0
- package/node_modules/pi-lens/dist/clients/gitleaks-client.js +42 -6
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +48 -7
- package/node_modules/pi-lens/dist/clients/installer/index.js +196 -52
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +9 -1
- package/node_modules/pi-lens/dist/clients/knip-client.js +9 -2
- package/node_modules/pi-lens/dist/clients/language-profile.js +12 -2
- package/node_modules/pi-lens/dist/clients/latency-logger.js +2 -0
- package/node_modules/pi-lens/dist/clients/lens-config.js +4 -0
- package/node_modules/pi-lens/dist/clients/lsp/config.js +36 -7
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +24 -1
- package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +78 -0
- package/node_modules/pi-lens/dist/clients/lsp/index.js +281 -45
- package/node_modules/pi-lens/dist/clients/lsp/server.js +62 -15
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +37 -4
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +3 -0
- package/node_modules/pi-lens/dist/clients/mcp/session.js +2 -0
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +2 -0
- package/node_modules/pi-lens/dist/clients/package-manager.js +2 -1
- package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +8 -3
- package/node_modules/pi-lens/dist/clients/pipeline.js +3 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +38 -10
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +10 -9
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +2 -0
- package/node_modules/pi-lens/dist/clients/review-graph-logger.js +2 -0
- package/node_modules/pi-lens/dist/clients/ruff-client.js +18 -9
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +9 -0
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +5 -0
- package/node_modules/pi-lens/dist/clients/runtime-session.js +13 -4
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +4 -1
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +48 -1
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +2 -2
- package/node_modules/pi-lens/dist/clients/session-event-guard.js +69 -1
- package/node_modules/pi-lens/dist/clients/sg-runner.js +2 -1
- package/node_modules/pi-lens/dist/clients/situational-tool-telemetry.js +108 -0
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +291 -76
- package/node_modules/pi-lens/dist/clients/tool-config.js +199 -0
- package/node_modules/pi-lens/dist/clients/tool-cwd.js +299 -0
- package/node_modules/pi-lens/dist/clients/tool-probe.js +53 -0
- package/node_modules/pi-lens/dist/clients/tool-set-policy.js +42 -0
- package/node_modules/pi-lens/dist/clients/trivy-client.js +9 -1
- package/node_modules/pi-lens/dist/clients/turn-context.js +52 -0
- package/node_modules/pi-lens/dist/clients/widget-state.js +4 -3
- package/node_modules/pi-lens/dist/index.js +21969 -20005
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +3 -2
- package/node_modules/pi-lens/dist/mcp/server.js +135 -159
- package/node_modules/pi-lens/dist/tools/activate-tools.js +10 -7
- package/node_modules/pi-lens/dist/tools/ast-grep-outline.js +2 -13
- package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +8 -13
- package/node_modules/pi-lens/dist/tools/ast-grep-search.js +83 -39
- package/node_modules/pi-lens/dist/tools/effective-config.js +4 -3
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +1 -10
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +353 -85
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +21 -110
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +20 -44
- package/node_modules/pi-lens/dist/tools/module-report.js +5 -9
- package/node_modules/pi-lens/dist/tools/project-report.js +9 -17
- package/node_modules/pi-lens/dist/tools/render-compact.js +280 -1
- package/node_modules/pi-lens/dist/tools/symbol-search.js +2 -2
- package/node_modules/pi-lens/docs/agent-guide.md +3 -4
- package/node_modules/pi-lens/docs/agent-tools.md +46 -22
- package/node_modules/pi-lens/docs/configuration.md +9 -0
- package/node_modules/pi-lens/docs/environment-variables.md +0 -5
- package/node_modules/pi-lens/docs/features.md +5 -5
- package/node_modules/pi-lens/docs/globalconfig.md +11 -1
- package/node_modules/pi-lens/docs/mcp.md +13 -1
- package/node_modules/pi-lens/docs/pi-lens-fixer.md +71 -0
- package/node_modules/pi-lens/docs/pi-lens-investigator.md +15 -0
- package/node_modules/pi-lens/docs/pi-lens-monitor.md +88 -0
- package/node_modules/pi-lens/docs/pi-lens-reviewer.md +35 -0
- package/node_modules/pi-lens/docs/pi-lens-subagent.md +12 -4
- package/node_modules/pi-lens/docs/public-api-stability.md +1 -0
- package/node_modules/pi-lens/docs/real-harness.md +46 -0
- package/node_modules/pi-lens/docs/release-qa-baseline.md +5 -1
- package/node_modules/pi-lens/docs/servercapabilities.md +1 -6
- package/node_modules/pi-lens/docs/settings.md +37 -2
- package/node_modules/pi-lens/docs/tools_improvement2.md +4 -4
- package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +1 -1
- package/node_modules/pi-lens/docs/usage.md +13 -3
- package/node_modules/pi-lens/package.json +10 -1
- package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/sql-injection.yml +7 -3
- package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +8 -6
- package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +19 -9
- package/node_modules/pi-web-access/CHANGELOG.md +29 -0
- package/node_modules/pi-web-access/README.md +33 -22
- package/node_modules/pi-web-access/abortable.ts +17 -0
- package/node_modules/pi-web-access/crawl4ai.ts +204 -0
- package/node_modules/pi-web-access/credential-source.ts +1 -0
- package/node_modules/pi-web-access/curator-page.ts +20 -2
- package/node_modules/pi-web-access/curator-run.ts +44 -0
- package/node_modules/pi-web-access/curator-server.ts +3 -1
- package/node_modules/pi-web-access/duckduckgo.ts +1 -1
- package/node_modules/pi-web-access/extract.ts +57 -12
- package/node_modules/pi-web-access/fetch-params.ts +1 -1
- package/node_modules/pi-web-access/gemini-search.ts +9 -5
- package/node_modules/pi-web-access/index.ts +25 -18
- package/node_modules/pi-web-access/openai-search.ts +35 -13
- package/node_modules/pi-web-access/package.json +2 -2
- package/node_modules/pi-web-access/page-query.ts +5 -2
- package/node_modules/pi-web-access/query-rewrite.ts +5 -2
- package/node_modules/pi-web-access/serpapi.ts +220 -0
- package/node_modules/pi-web-access/ssrf-protection.ts +5 -1
- package/node_modules/pi-web-access/storage.ts +10 -0
- package/node_modules/pi-web-access/summary-review.ts +34 -21
- package/node_modules/pi-web-access/utils.ts +6 -4
- package/package.json +7 -4
- package/node_modules/pi-lens/dist/tools/ast-dump.js +0 -103
|
@@ -34,7 +34,7 @@ import { markPendingAuxiliaryCoverage, napiFallbackCoveredSince, } from "./pendi
|
|
|
34
34
|
import { isAtOrAboveHomeDir, isWindowsPath, normalizeMapKey, uriToPath, } from "../path-utils.js";
|
|
35
35
|
import { recordLspMutation } from "../lsp-mutation.js";
|
|
36
36
|
import { createLSPClient } from "./client.js";
|
|
37
|
-
import { auxiliaryCoverageGap, bindingStateLabel, composeBoundToCurrentDisk, createDiskBindingCache, hashDiagnosticContent, resolveTouchVerdict, touchCoverageGap, } from "./diagnostic-binding.js";
|
|
37
|
+
import { auxiliaryCoverageGap, auxiliaryPublicationEvidence, bindingStateLabel, composeBoundToCurrentDisk, createDiskBindingCache, hashDiagnosticContent, resolveTouchVerdict, touchCoverageGap, } from "./diagnostic-binding.js";
|
|
38
38
|
import { getServersForFileWithConfig, getServerInitOverride, } from "./config.js";
|
|
39
39
|
// #2052: deliberately NOT taken from `config.js`. This module's import surface
|
|
40
40
|
// from `config.js` is mirrored by explicit `vi.mock` factories in ~58 test
|
|
@@ -42,9 +42,14 @@ import { getServersForFileWithConfig, getServerInitOverride, } from "./config.js
|
|
|
42
42
|
import { getSessionRootsForTelemetry, isOutsideAllSessionRoots, } from "./session-roots.js";
|
|
43
43
|
import { getProcessSingleton } from "../process-singletons.js";
|
|
44
44
|
import { getLanguageId } from "./language.js";
|
|
45
|
-
import {
|
|
45
|
+
import { getReverseDepsFromIndex, loadReverseDependencyIndexFromSnapshot, } from "../reverse-deps.js";
|
|
46
|
+
import { LSP_SERVERS, enforceLspRootCeiling, hasProjectBoundaryMarker, isDirectLspCommandTemporarilyUnavailable, resetClassicTsRepairGuard, resetLspLaunchAvailabilityGeneration, resolveLspServerCwd, } from "./server.js";
|
|
46
47
|
import { classifyCascadeWaitTier, classifyServerWaitTier, getStrategy, } from "./wait-policy/index.js";
|
|
47
48
|
const WORKSPACE_ATTRIBUTION_CLIENT_CAP = 16;
|
|
49
|
+
const AUX_WAIT_DEMOTION_THRESHOLD = 5;
|
|
50
|
+
const AUX_WAIT_DEMOTION_RATIO = 0.9;
|
|
51
|
+
const AUX_WAIT_REPROMOTION_THRESHOLD = 5;
|
|
52
|
+
const AUX_WAIT_REPROMOTION_RATIO = 0.5;
|
|
48
53
|
import { raceToCompletion } from "./aggregation.js";
|
|
49
54
|
import { applyWorkspaceEdit, mergeWorkspaceTextEditsByPriority, summarizeWorkspaceEdit, validateWorkspaceEdit, } from "./edits.js";
|
|
50
55
|
import { buildScopeKey, createWorkspaceDiagnosticsCacheContext, } from "./workspace-diagnostics-cache.js";
|
|
@@ -176,6 +181,7 @@ const NAV_CLIENT_WAIT_TIMEOUT_MS = Math.max(0, Number.parseInt(process.env.PI_LE
|
|
|
176
181
|
1500);
|
|
177
182
|
const TOUCH_DEBOUNCE_MS = Math.max(0, Number.parseInt(process.env.PI_LENS_LSP_TOUCH_DEBOUNCE_MS ?? "1500", 10) ||
|
|
178
183
|
1500);
|
|
184
|
+
const SCOPED_DEPENDENCY_TOUCH_MAX = 32;
|
|
179
185
|
// #1621: the rename-propagation notifies (`didClose` ahead of the rename,
|
|
180
186
|
// `workspace/didRenameFiles` after) share the exit-notify defect #1620 fixed —
|
|
181
187
|
// a notify write on a pipe that is not draining neither resolves nor rejects,
|
|
@@ -345,6 +351,9 @@ function mergeLspDiagnostics(diagnostics) {
|
|
|
345
351
|
}
|
|
346
352
|
return merged;
|
|
347
353
|
}
|
|
354
|
+
function attributeDiagnostics(serverId, diagnostics) {
|
|
355
|
+
return diagnostics.map((diagnostic) => ({ ...diagnostic, serverId }));
|
|
356
|
+
}
|
|
348
357
|
/**
|
|
349
358
|
* Group files by their primary language server id (#387/#631 — extracted
|
|
350
359
|
* from `runWorkspaceDiagnostics`'s inline grouping so other callers, e.g.
|
|
@@ -653,6 +662,11 @@ export class LSPService {
|
|
|
653
662
|
* the navigation-only latch, so the next touch can try again.
|
|
654
663
|
*/
|
|
655
664
|
firstContactWaits = new Map();
|
|
665
|
+
/** Session-scoped adaptive demotion for budget-hitting auxiliaries. */
|
|
666
|
+
auxWaitPressureStreak = new Map();
|
|
667
|
+
auxWaitFastAnswerStreak = new Map();
|
|
668
|
+
demotedAuxiliaryServerIds = new Set();
|
|
669
|
+
demotedAuxiliaryBudgets = new Map();
|
|
656
670
|
/**
|
|
657
671
|
* #1934 review F1: what the last COMPLETED `spawnClient` call for a
|
|
658
672
|
* (server, root) key decided, written by that call at every point it
|
|
@@ -865,13 +879,61 @@ export class LSPService {
|
|
|
865
879
|
diagnosticsUnsupported: new Set(),
|
|
866
880
|
};
|
|
867
881
|
}
|
|
882
|
+
isAuxiliaryWaitDemoted(serverId, root) {
|
|
883
|
+
return this.demotedAuxiliaryServerIds.has(`${serverId}:${normalizeMapKey(root ?? "")}`);
|
|
884
|
+
}
|
|
885
|
+
noteAuxiliaryWait(key, budgetMs, elapsedMs) {
|
|
886
|
+
if (this.demotedAuxiliaryServerIds.has(key))
|
|
887
|
+
return;
|
|
888
|
+
if (elapsedMs < AUX_WAIT_DEMOTION_RATIO * budgetMs) {
|
|
889
|
+
this.auxWaitPressureStreak.delete(key);
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
const streak = (this.auxWaitPressureStreak.get(key) ?? 0) + 1;
|
|
893
|
+
if (streak >= AUX_WAIT_DEMOTION_THRESHOLD) {
|
|
894
|
+
this.demotedAuxiliaryServerIds.add(key);
|
|
895
|
+
this.demotedAuxiliaryBudgets.set(key, budgetMs);
|
|
896
|
+
this.auxWaitPressureStreak.delete(key);
|
|
897
|
+
this.auxWaitFastAnswerStreak.delete(key);
|
|
898
|
+
recordDegradationOnce({
|
|
899
|
+
kind: "aux_wait_demoted",
|
|
900
|
+
subject: key,
|
|
901
|
+
reason: "auxiliary reached at least 90% of its declared wait budget for five consecutive dispatches",
|
|
902
|
+
});
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
this.auxWaitPressureStreak.set(key, streak);
|
|
906
|
+
}
|
|
907
|
+
noteAuxiliaryLateAnswer(key, elapsedMs) {
|
|
908
|
+
if (!this.demotedAuxiliaryServerIds.has(key))
|
|
909
|
+
return;
|
|
910
|
+
const budgetMs = this.demotedAuxiliaryBudgets.get(key);
|
|
911
|
+
if (budgetMs === undefined ||
|
|
912
|
+
elapsedMs >= AUX_WAIT_REPROMOTION_RATIO * budgetMs) {
|
|
913
|
+
this.auxWaitFastAnswerStreak.delete(key);
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
const streak = (this.auxWaitFastAnswerStreak.get(key) ?? 0) + 1;
|
|
917
|
+
if (streak < AUX_WAIT_REPROMOTION_THRESHOLD) {
|
|
918
|
+
this.auxWaitFastAnswerStreak.set(key, streak);
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
this.demotedAuxiliaryServerIds.delete(key);
|
|
922
|
+
this.demotedAuxiliaryBudgets.delete(key);
|
|
923
|
+
this.auxWaitFastAnswerStreak.delete(key);
|
|
924
|
+
recordDegradationOnce({
|
|
925
|
+
kind: "aux_wait_repromoted",
|
|
926
|
+
subject: key,
|
|
927
|
+
reason: "auxiliary produced five consecutive late answers below half its declared wait budget",
|
|
928
|
+
});
|
|
929
|
+
}
|
|
868
930
|
/**
|
|
869
931
|
* Resolve one server's client identity root. Root selection is hard-bounded
|
|
870
932
|
* by the session cwd, then config-only nested roots reuse an already-hosted
|
|
871
933
|
* same-server ancestor. Real nested projects retain independent clients.
|
|
872
934
|
*/
|
|
873
|
-
async resolveServerRoot(server, filePath) {
|
|
874
|
-
const candidate = await server.
|
|
935
|
+
async resolveServerRoot(server, filePath, onRootFailure) {
|
|
936
|
+
const candidate = await resolveLspServerCwd(server, filePath, this.sessionCwd ?? process.cwd(), onRootFailure);
|
|
875
937
|
if (!candidate)
|
|
876
938
|
return undefined;
|
|
877
939
|
// #2052: a file outside EVERY initialized session cwd gets no client at
|
|
@@ -1995,11 +2057,14 @@ export class LSPService {
|
|
|
1995
2057
|
*/
|
|
1996
2058
|
const partitionCandidates = async () => {
|
|
1997
2059
|
const resolved = await Promise.all(servers.map(async (server) => {
|
|
2060
|
+
let rootFailureReason;
|
|
1998
2061
|
try {
|
|
1999
2062
|
return {
|
|
2000
2063
|
server,
|
|
2001
|
-
root: await this.resolveServerRootOnce(server, filePath, rootMemo)
|
|
2002
|
-
|
|
2064
|
+
root: await this.resolveServerRootOnce(server, filePath, rootMemo, (reason) => {
|
|
2065
|
+
rootFailureReason = reason;
|
|
2066
|
+
}),
|
|
2067
|
+
reason: rootFailureReason,
|
|
2003
2068
|
};
|
|
2004
2069
|
}
|
|
2005
2070
|
catch (error) {
|
|
@@ -2466,6 +2531,18 @@ export class LSPService {
|
|
|
2466
2531
|
}
|
|
2467
2532
|
return out;
|
|
2468
2533
|
}
|
|
2534
|
+
/** Record a fast answer observed by the turn-end late-results path. */
|
|
2535
|
+
async observeLateAuxiliaryAnswer(filePath, serverId, elapsedMs) {
|
|
2536
|
+
for (const server of getServersForFileWithConfig(filePath)) {
|
|
2537
|
+
if (server.id !== serverId)
|
|
2538
|
+
continue;
|
|
2539
|
+
const root = await this.resolveServerRoot(server, filePath);
|
|
2540
|
+
if (root === undefined)
|
|
2541
|
+
return;
|
|
2542
|
+
this.noteAuxiliaryLateAnswer(`${serverId}:${normalizeMapKey(root)}`, elapsedMs);
|
|
2543
|
+
return;
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2469
2546
|
/**
|
|
2470
2547
|
* #1668: deliver a `workspace/didChangeWatchedFiles` event for a disk
|
|
2471
2548
|
* change the client did not author through open-document sync — a bash
|
|
@@ -2492,6 +2569,45 @@ export class LSPService {
|
|
|
2492
2569
|
}
|
|
2493
2570
|
}
|
|
2494
2571
|
}
|
|
2572
|
+
/**
|
|
2573
|
+
* Re-sync the open views affected by one recovered Git tree change. The
|
|
2574
|
+
* recovery seam already owns the changed-path set, so this deliberately
|
|
2575
|
+
* reads only the cached reverse-import index and never runs another diff.
|
|
2576
|
+
*/
|
|
2577
|
+
async resyncGitChangedFiles(changedPaths) {
|
|
2578
|
+
if (this.checkDestroyed() || changedPaths.length === 0)
|
|
2579
|
+
return;
|
|
2580
|
+
const targets = new Set();
|
|
2581
|
+
for (const changedPath of changedPaths) {
|
|
2582
|
+
const resolved = path.resolve(changedPath);
|
|
2583
|
+
for (const server of getServersForFileWithConfig(resolved)) {
|
|
2584
|
+
const root = await this.resolveServerRoot(server, resolved);
|
|
2585
|
+
if (!root)
|
|
2586
|
+
continue;
|
|
2587
|
+
const index = loadReverseDependencyIndexFromSnapshot({ cwd: root });
|
|
2588
|
+
for (const importer of index
|
|
2589
|
+
? getReverseDepsFromIndex(index, resolved)
|
|
2590
|
+
: []) {
|
|
2591
|
+
targets.add(importer);
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
targets.add(resolved);
|
|
2595
|
+
}
|
|
2596
|
+
const openTargets = [...targets].filter((filePath) => this.hasLiveClientHoldingDocument(filePath));
|
|
2597
|
+
this.documentDrift.enqueueResync(openTargets);
|
|
2598
|
+
const pass = await this.sweepDocumentDrift({ force: true });
|
|
2599
|
+
logLatency({
|
|
2600
|
+
type: "phase",
|
|
2601
|
+
phase: "lsp_external_change_resync",
|
|
2602
|
+
filePath: changedPaths[0] ?? "",
|
|
2603
|
+
durationMs: 0,
|
|
2604
|
+
metadata: {
|
|
2605
|
+
changedCount: changedPaths.length,
|
|
2606
|
+
enqueued: openTargets.length,
|
|
2607
|
+
deferred: pass?.deferred ?? this.documentDrift.pendingResyncCount,
|
|
2608
|
+
},
|
|
2609
|
+
});
|
|
2610
|
+
}
|
|
2495
2611
|
/**
|
|
2496
2612
|
* #1783: disk-drift backstop. Stat one batch of the documents a language
|
|
2497
2613
|
* server currently holds, and re-push any whose bytes on disk no longer
|
|
@@ -2680,13 +2796,13 @@ export class LSPService {
|
|
|
2680
2796
|
* instead of each taking their own. Without a `memo` this is a plain
|
|
2681
2797
|
* passthrough, for callers that have no acquisition to share with.
|
|
2682
2798
|
*/
|
|
2683
|
-
resolveServerRootOnce(server, filePath, memo) {
|
|
2799
|
+
resolveServerRootOnce(server, filePath, memo, onRootFailure) {
|
|
2684
2800
|
if (!memo)
|
|
2685
|
-
return this.resolveServerRoot(server, filePath);
|
|
2801
|
+
return this.resolveServerRoot(server, filePath, onRootFailure);
|
|
2686
2802
|
const pending = memo.get(server.id);
|
|
2687
2803
|
if (pending !== undefined)
|
|
2688
2804
|
return pending;
|
|
2689
|
-
const started = this.resolveServerRoot(server, filePath);
|
|
2805
|
+
const started = this.resolveServerRoot(server, filePath, onRootFailure);
|
|
2690
2806
|
memo.set(server.id, started);
|
|
2691
2807
|
return started;
|
|
2692
2808
|
}
|
|
@@ -3513,7 +3629,9 @@ export class LSPService {
|
|
|
3513
3629
|
if (!bindingMatchesTouchContent(binding))
|
|
3514
3630
|
return [];
|
|
3515
3631
|
const diags = entry.client.getDiagnostics(filePath);
|
|
3516
|
-
return diags.length > 0
|
|
3632
|
+
return diags.length > 0
|
|
3633
|
+
? [{ serverId: entry.info.id, diags, binding }]
|
|
3634
|
+
: [];
|
|
3517
3635
|
})
|
|
3518
3636
|
: [];
|
|
3519
3637
|
// #1493: auxiliaries whose STORED publication already covers exactly the
|
|
@@ -3822,6 +3940,15 @@ export class LSPService {
|
|
|
3822
3940
|
// confirmation; `auxCutOffServerIds` stays cut_off-only so the R8 latency
|
|
3823
3941
|
// field keeps its original meaning.
|
|
3824
3942
|
let auxUnconfirmedServerIds;
|
|
3943
|
+
// #2810: the auxiliaries this touch handed to the collect-later store —
|
|
3944
|
+
// exactly the servers whose findings a turn-end drain can still deliver.
|
|
3945
|
+
// Written by the ONE producer that marks them, from the same array it
|
|
3946
|
+
// marks, so the promise the agent is shown cannot drift from the store.
|
|
3947
|
+
// Round 3 derived the promise from the #1459 resync deferrals instead —
|
|
3948
|
+
// the one set `pending-aux-coverage.ts` never marks — so the notice
|
|
3949
|
+
// promised delivery that could not happen, while the demoted server,
|
|
3950
|
+
// which IS marked, was reported as an unexplained silence on every edit.
|
|
3951
|
+
let lateDeliveryServerIds;
|
|
3825
3952
|
// #707: tsserver sync clean-confirm state. `tsserverSyncEligible` is the
|
|
3826
3953
|
// full gate (evaluated once, before the wait); `tsserverSyncConfirmed`
|
|
3827
3954
|
// holds the sync commands' answer when the racing confirm won the wait
|
|
@@ -4098,9 +4225,11 @@ export class LSPService {
|
|
|
4098
4225
|
const answeredForThisTouch = (entry) => {
|
|
4099
4226
|
const baseline = diagnosticBaselines.get(entry.client);
|
|
4100
4227
|
const currentPathVersion = readPathVersion(entry.client);
|
|
4101
|
-
if (
|
|
4102
|
-
|
|
4103
|
-
|
|
4228
|
+
if (auxiliaryPublicationEvidence({
|
|
4229
|
+
bindingMatchesContent: false,
|
|
4230
|
+
baseline,
|
|
4231
|
+
currentPathVersion,
|
|
4232
|
+
})) {
|
|
4104
4233
|
return true;
|
|
4105
4234
|
}
|
|
4106
4235
|
try {
|
|
@@ -4116,6 +4245,11 @@ export class LSPService {
|
|
|
4116
4245
|
: perServerDeclaredTimeouts[entryIndex];
|
|
4117
4246
|
});
|
|
4118
4247
|
const perServerWaits = spawned.map((entry, entryIndex) => {
|
|
4248
|
+
if (hasTouchAuxiliaries &&
|
|
4249
|
+
entry.info.role === "auxiliary" &&
|
|
4250
|
+
this.isAuxiliaryWaitDemoted(entry.info.id, entry.client.root ?? filePath)) {
|
|
4251
|
+
return Promise.resolve(undefined);
|
|
4252
|
+
}
|
|
4119
4253
|
if (this.state.diagnosticsUnsupported.has(entry.info.id) ||
|
|
4120
4254
|
diagnosticsUnsupportedServerIds.includes(entry.info.id)) {
|
|
4121
4255
|
return Promise.resolve(undefined);
|
|
@@ -4254,6 +4388,8 @@ export class LSPService {
|
|
|
4254
4388
|
client: spawned[i].client,
|
|
4255
4389
|
baseline: diagnosticBaselines.get(spawned[i].client),
|
|
4256
4390
|
budgetMs: perServerRaceBudgets[i],
|
|
4391
|
+
demoted: this.isAuxiliaryWaitDemoted(spawned[i].info.id, spawned[i].client.root ?? filePath),
|
|
4392
|
+
root: spawned[i].client.root ?? filePath,
|
|
4257
4393
|
}
|
|
4258
4394
|
: null)
|
|
4259
4395
|
.filter((x) => x !== null);
|
|
@@ -4273,6 +4409,22 @@ export class LSPService {
|
|
|
4273
4409
|
const auxWaitStartedAt = Date.now();
|
|
4274
4410
|
const outcomes = await Promise.all(auxWaits.map(async (aux) => {
|
|
4275
4411
|
const { budgetMs } = aux;
|
|
4412
|
+
if (aux.demoted) {
|
|
4413
|
+
return {
|
|
4414
|
+
serverId: aux.serverId,
|
|
4415
|
+
outcome: deferredResyncServerIds.has(aux.serverId)
|
|
4416
|
+
? "deferred"
|
|
4417
|
+
: "demoted",
|
|
4418
|
+
publishedThisContent: auxiliaryPublicationEvidence({
|
|
4419
|
+
bindingMatchesContent: auxCoversThisContent(aux.serverId),
|
|
4420
|
+
baseline: aux.baseline,
|
|
4421
|
+
currentPathVersion: readPathVersion(aux.client),
|
|
4422
|
+
}),
|
|
4423
|
+
budgetMs,
|
|
4424
|
+
elapsedMs: 0,
|
|
4425
|
+
elapsedSinceNotifyMs: 0,
|
|
4426
|
+
};
|
|
4427
|
+
}
|
|
4276
4428
|
let timer;
|
|
4277
4429
|
const timeout = new Promise((resolve) => {
|
|
4278
4430
|
timer = setTimeout(() => resolve(false), budgetMs);
|
|
@@ -4311,10 +4463,12 @@ export class LSPService {
|
|
|
4311
4463
|
// ignoring sibling paths — and it is the SAME axis `baseline`
|
|
4312
4464
|
// was captured on above.
|
|
4313
4465
|
const currentPathVersion = readPathVersion(aux.client);
|
|
4314
|
-
const publishedEvidence =
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
currentPathVersion
|
|
4466
|
+
const publishedEvidence = auxiliaryPublicationEvidence({
|
|
4467
|
+
bindingMatchesContent: false,
|
|
4468
|
+
baseline: aux.baseline,
|
|
4469
|
+
currentPathVersion,
|
|
4470
|
+
raced,
|
|
4471
|
+
});
|
|
4318
4472
|
// #1459: a DEFERRED aux was never sent this content and is not
|
|
4319
4473
|
// waited on at all, so its instantly-resolved placeholder
|
|
4320
4474
|
// promise must not read as "silent". "Silent" is the reserved
|
|
@@ -4328,6 +4482,10 @@ export class LSPService {
|
|
|
4328
4482
|
: publishedEvidence
|
|
4329
4483
|
? "answered"
|
|
4330
4484
|
: "silent";
|
|
4485
|
+
const elapsedMs = Date.now() - auxWaitStartedAt;
|
|
4486
|
+
if (outcome !== "deferred") {
|
|
4487
|
+
this.noteAuxiliaryWait(`${aux.serverId}:${normalizeMapKey(aux.root)}`, budgetMs, elapsedMs);
|
|
4488
|
+
}
|
|
4331
4489
|
return {
|
|
4332
4490
|
serverId: aux.serverId,
|
|
4333
4491
|
outcome,
|
|
@@ -4337,9 +4495,12 @@ export class LSPService {
|
|
|
4337
4495
|
// did not narrow the touch.
|
|
4338
4496
|
// #1586: through the one predicate, so this row and the merge
|
|
4339
4497
|
// below cannot disagree about the same scanner.
|
|
4498
|
+
// #2914: binding-only by construction — the direct call
|
|
4499
|
+
// below is master's line; the stamp axis already decided
|
|
4500
|
+
// the outcome above and must not re-admit the row here.
|
|
4340
4501
|
publishedThisContent: auxCoversThisContent(aux.serverId),
|
|
4341
4502
|
budgetMs,
|
|
4342
|
-
elapsedMs
|
|
4503
|
+
elapsedMs,
|
|
4343
4504
|
// #1458 S3: elapsed measured from BEFORE the primary wait
|
|
4344
4505
|
// (waitStartedAt), not just from auxWaitStartedAt — this is
|
|
4345
4506
|
// what lets a latency row validate the ~1.3s warm-scanner
|
|
@@ -4372,7 +4533,9 @@ export class LSPService {
|
|
|
4372
4533
|
// revision and probing it would replay stale findings.
|
|
4373
4534
|
const collectLaterServerIds = outcomes
|
|
4374
4535
|
.filter((o) => !o.publishedThisContent &&
|
|
4375
|
-
(o.outcome === "cut_off" ||
|
|
4536
|
+
(o.outcome === "cut_off" ||
|
|
4537
|
+
o.outcome === "silent" ||
|
|
4538
|
+
o.outcome === "demoted") &&
|
|
4376
4539
|
// #2324 R2-A/R3-A: ast-grep's napi fallback is a
|
|
4377
4540
|
// SECOND producer of coverage for this exact pair,
|
|
4378
4541
|
// dispatched CONCURRENTLY with this whole touch
|
|
@@ -4392,7 +4555,8 @@ export class LSPService {
|
|
|
4392
4555
|
napiFallbackCoveredSince(filePath, startedAt)))
|
|
4393
4556
|
.map((o) => o.serverId);
|
|
4394
4557
|
if (collectLaterServerIds.length > 0) {
|
|
4395
|
-
|
|
4558
|
+
lateDeliveryServerIds = collectLaterServerIds;
|
|
4559
|
+
markPendingAuxiliaryCoverage(filePath, collectLaterServerIds, Date.now());
|
|
4396
4560
|
}
|
|
4397
4561
|
logLatency({
|
|
4398
4562
|
type: "phase",
|
|
@@ -4592,9 +4756,11 @@ export class LSPService {
|
|
|
4592
4756
|
const outcomes = auxEntries.map((entry) => {
|
|
4593
4757
|
const baseline = diagnosticBaselines.get(entry.client);
|
|
4594
4758
|
const currentPathVersion = readPathVersion(entry.client);
|
|
4595
|
-
const publishedEvidence =
|
|
4596
|
-
|
|
4597
|
-
|
|
4759
|
+
const publishedEvidence = auxiliaryPublicationEvidence({
|
|
4760
|
+
bindingMatchesContent: false,
|
|
4761
|
+
baseline,
|
|
4762
|
+
currentPathVersion,
|
|
4763
|
+
});
|
|
4598
4764
|
return {
|
|
4599
4765
|
serverId: entry.info.id,
|
|
4600
4766
|
outcome: deferredResyncServerIds.has(entry.info.id)
|
|
@@ -4602,6 +4768,9 @@ export class LSPService {
|
|
|
4602
4768
|
: publishedEvidence
|
|
4603
4769
|
? "answered"
|
|
4604
4770
|
: "silent",
|
|
4771
|
+
// #2914: binding-only by construction — the direct call
|
|
4772
|
+
// below is master's line; the stamp axis already decided
|
|
4773
|
+
// the outcome above and must not re-admit the row here.
|
|
4605
4774
|
publishedThisContent: auxCoversThisContent(entry.info.id),
|
|
4606
4775
|
budgetMs: timeoutFor(entry.client.serverId),
|
|
4607
4776
|
elapsedMs: waitedMs,
|
|
@@ -4866,7 +5035,7 @@ export class LSPService {
|
|
|
4866
5035
|
// diagnostics from the client cache as always.
|
|
4867
5036
|
let collected = options.collectDiagnostics
|
|
4868
5037
|
? tsserverSyncConfirmed !== undefined
|
|
4869
|
-
? mergeLspDiagnostics(tsserverSyncConfirmed)
|
|
5038
|
+
? mergeLspDiagnostics(attributeDiagnostics(spawned[0]?.info.id ?? "unknown", tsserverSyncConfirmed))
|
|
4870
5039
|
: mergeLspDiagnostics([
|
|
4871
5040
|
// #1459: a DEFERRED server's cache still holds the PREVIOUS
|
|
4872
5041
|
// content's findings — the resync that would have cleared it never
|
|
@@ -4878,8 +5047,8 @@ export class LSPService {
|
|
|
4878
5047
|
// coverage naming and the merged binding read too.
|
|
4879
5048
|
...spawned.flatMap((entry) => droppedAuxiliaryServerIds.has(entry.info.id)
|
|
4880
5049
|
? []
|
|
4881
|
-
: entry.client.getDiagnostics(filePath)),
|
|
4882
|
-
...carriedAuxiliary.flatMap((entry) => entry.diags),
|
|
5050
|
+
: attributeDiagnostics(entry.info.id, entry.client.getDiagnostics(filePath))),
|
|
5051
|
+
...carriedAuxiliary.flatMap((entry) => attributeDiagnostics(entry.serverId, entry.diags)),
|
|
4883
5052
|
])
|
|
4884
5053
|
: undefined;
|
|
4885
5054
|
// #1095 (P3-b): whether `collected` came from a tsserver sync confirm
|
|
@@ -4919,7 +5088,9 @@ export class LSPService {
|
|
|
4919
5088
|
retractPrimaryTimeoutAttribution(); // #1549
|
|
4920
5089
|
syncConfirmed = true;
|
|
4921
5090
|
collected =
|
|
4922
|
-
syncResult.length > 0
|
|
5091
|
+
syncResult.length > 0
|
|
5092
|
+
? mergeLspDiagnostics(attributeDiagnostics(spawned[0]?.info.id ?? "unknown", syncResult))
|
|
5093
|
+
: [];
|
|
4923
5094
|
logLatency({
|
|
4924
5095
|
type: "phase",
|
|
4925
5096
|
phase: "lsp_tsserver_sync_confirm",
|
|
@@ -5252,9 +5423,11 @@ export class LSPService {
|
|
|
5252
5423
|
result.inconclusiveReason = verdict.inconclusiveReason;
|
|
5253
5424
|
}
|
|
5254
5425
|
else if (collected !== undefined && primaryDiagnosticsUnsupported) {
|
|
5255
|
-
// A navigation-only primary has no diagnostic confirmation to report
|
|
5256
|
-
//
|
|
5257
|
-
|
|
5426
|
+
// A navigation-only primary has no diagnostic confirmation to report,
|
|
5427
|
+
// but an uncovered auxiliary still needs to be named for delivery.
|
|
5428
|
+
if (unconfirmedServerIds.length > 0) {
|
|
5429
|
+
result.unconfirmedServerIds = [...unconfirmedServerIds];
|
|
5430
|
+
}
|
|
5258
5431
|
}
|
|
5259
5432
|
else if (collected !== undefined && coverageGap) {
|
|
5260
5433
|
// #1470/#1493: narrowed, not collapsed. Reached for EITHER no-answer
|
|
@@ -5312,6 +5485,17 @@ export class LSPService {
|
|
|
5312
5485
|
// fully delivered. Nothing to record here: a skipped server keeps its original
|
|
5313
5486
|
// entry (and timestamp) so its window still expires naturally instead of being
|
|
5314
5487
|
// extended by every reuse.
|
|
5488
|
+
// #2810: the half of the coverage gap that has a delivery path. Derived
|
|
5489
|
+
// from `unconfirmedServerIds` so the two can never disagree about the
|
|
5490
|
+
// same scanner, and intersected with the marked set so "findings will
|
|
5491
|
+
// arrive through the late path" is said only where a pending pair
|
|
5492
|
+
// exists. Everything else in the gap — a #1459 deferral, a breaker
|
|
5493
|
+
// skip, an ast-grep already covered by its napi fallback — keeps the
|
|
5494
|
+
// honest "silent, diagnostics are incomplete" half.
|
|
5495
|
+
const lateDeliveryPending = unconfirmedServerIds.filter((serverId) => (lateDeliveryServerIds ?? []).includes(serverId));
|
|
5496
|
+
if (lateDeliveryPending.length > 0) {
|
|
5497
|
+
result.deferredServerIds = lateDeliveryPending;
|
|
5498
|
+
}
|
|
5315
5499
|
logLatency({
|
|
5316
5500
|
type: "phase",
|
|
5317
5501
|
phase: "lsp_touch_file",
|
|
@@ -5384,6 +5568,14 @@ export class LSPService {
|
|
|
5384
5568
|
...(uncoveredDeferredServerIds.length > 0 && {
|
|
5385
5569
|
deferredResyncServerIds: uncoveredDeferredServerIds,
|
|
5386
5570
|
}),
|
|
5571
|
+
// #2810: the scanners this touch promised late delivery for, so a
|
|
5572
|
+
// field query can join the promise to the pending pair that has to
|
|
5573
|
+
// honour it — and tell it apart from `deferredResyncServerIds`
|
|
5574
|
+
// above, which is the door that opened BEFORE any wait and has no
|
|
5575
|
+
// delivery path at all.
|
|
5576
|
+
...(lateDeliveryPending.length > 0 && {
|
|
5577
|
+
lateDeliveryServerIds: lateDeliveryPending,
|
|
5578
|
+
}),
|
|
5387
5579
|
// #1549: auxiliaries whose own deadline lapsed — the wait produced no
|
|
5388
5580
|
// publication, or the notify write never landed. Distinct from the fields
|
|
5389
5581
|
// above (a different door) and from `auxUnconfirmedServerIds` (derived
|
|
@@ -5620,7 +5812,7 @@ export class LSPService {
|
|
|
5620
5812
|
if (seen.has(key))
|
|
5621
5813
|
continue;
|
|
5622
5814
|
seen.add(key);
|
|
5623
|
-
merged.push(diagnostic);
|
|
5815
|
+
merged.push({ ...diagnostic, serverId: entry.serverId });
|
|
5624
5816
|
}
|
|
5625
5817
|
}
|
|
5626
5818
|
const rawCount = perServerFull.reduce((sum, entry) => sum + entry.diagnosticCount, 0);
|
|
@@ -6788,6 +6980,42 @@ export class LSPService {
|
|
|
6788
6980
|
const workspaceSweepScopeKey = buildScopeKey("all", [
|
|
6789
6981
|
...WORKSPACE_SWEEP_EXCLUDED_SERVER_IDS,
|
|
6790
6982
|
]);
|
|
6983
|
+
const touchScopedOpenDependencies = async (filePath) => {
|
|
6984
|
+
if (options.files === undefined)
|
|
6985
|
+
return true;
|
|
6986
|
+
// Import coverage is language-neutral. Undefined means this file was
|
|
6987
|
+
// not covered by the facts seam, so its cache entry is not eligible.
|
|
6988
|
+
const imports = workspaceDiagnosticsCacheCtx.importsFor(filePath);
|
|
6989
|
+
if (!imports)
|
|
6990
|
+
return false;
|
|
6991
|
+
const openImports = imports.filter((dependency) => this.hasLiveClientHoldingDocument(dependency));
|
|
6992
|
+
const capped = openImports.length > SCOPED_DEPENDENCY_TOUCH_MAX;
|
|
6993
|
+
if (capped) {
|
|
6994
|
+
recordDegradationOnce({
|
|
6995
|
+
kind: "lsp_dependency_touch_capped",
|
|
6996
|
+
subject: filePath,
|
|
6997
|
+
reason: `scoped full scan limited open import touches to ${SCOPED_DEPENDENCY_TOUCH_MAX}`,
|
|
6998
|
+
});
|
|
6999
|
+
}
|
|
7000
|
+
await Promise.all(openImports
|
|
7001
|
+
.slice(0, SCOPED_DEPENDENCY_TOUCH_MAX)
|
|
7002
|
+
.map(async (dependency) => {
|
|
7003
|
+
let content;
|
|
7004
|
+
try {
|
|
7005
|
+
content = await fs.readFile(dependency, "utf8");
|
|
7006
|
+
}
|
|
7007
|
+
catch {
|
|
7008
|
+
return;
|
|
7009
|
+
}
|
|
7010
|
+
await this.touchFile(dependency, content, {
|
|
7011
|
+
diagnostics: "none",
|
|
7012
|
+
source: "lens_diagnostics_full_dependency",
|
|
7013
|
+
clientScope: "all",
|
|
7014
|
+
excludeServerIds: await this.serverIdsNotHoldingDocument(dependency),
|
|
7015
|
+
});
|
|
7016
|
+
}));
|
|
7017
|
+
return !capped;
|
|
7018
|
+
};
|
|
6791
7019
|
const cachedResults = [];
|
|
6792
7020
|
const filesToTouch = [];
|
|
6793
7021
|
const writeIndexByPath = new Map();
|
|
@@ -6804,18 +7032,22 @@ export class LSPService {
|
|
|
6804
7032
|
// exists to detect) must NOT be replayed and reconciled as confirmed via
|
|
6805
7033
|
// mode=full. On a mismatch, fall through to a fresh touch.
|
|
6806
7034
|
if (cached && cached.binding.boundToCurrentDisk !== false) {
|
|
6807
|
-
|
|
6808
|
-
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
7035
|
+
const dependenciesConfirmed = await touchScopedOpenDependencies(filePath);
|
|
7036
|
+
if (dependenciesConfirmed)
|
|
7037
|
+
cachedResults.push({
|
|
7038
|
+
filePath,
|
|
7039
|
+
diagnostics: cached.diagnostics,
|
|
7040
|
+
count: cached.count,
|
|
7041
|
+
// #1093: a cache hit replays an older observation — carry its
|
|
7042
|
+
// scan time so mode=full's footer reconcile stamps `touchedAt`
|
|
7043
|
+
// with when the truth was seen, not now().
|
|
7044
|
+
observedAt: cached.scannedAt,
|
|
7045
|
+
contentHash: cached.binding.contentHash,
|
|
7046
|
+
boundToCurrentDisk: cached.binding.boundToCurrentDisk,
|
|
7047
|
+
writeIndex: writeIndexByPath.get(normalizeMapKey(filePath)),
|
|
7048
|
+
});
|
|
7049
|
+
else
|
|
7050
|
+
filesToTouch.push(filePath);
|
|
6819
7051
|
}
|
|
6820
7052
|
else {
|
|
6821
7053
|
filesToTouch.push(filePath);
|
|
@@ -7601,16 +7833,17 @@ export class LSPService {
|
|
|
7601
7833
|
client.pruneDiagnostics((filePath, ts) => !existingPaths.has(filePath) || now - ts > CASCADE_DIAGNOSTICS_TTL_MS);
|
|
7602
7834
|
const clientDiags = client.getAllDiagnostics();
|
|
7603
7835
|
for (const [filePath, entry] of clientDiags) {
|
|
7836
|
+
const attributed = attributeDiagnostics(client.serverId, entry.diags);
|
|
7604
7837
|
const existing = all.get(filePath);
|
|
7605
7838
|
if (existing) {
|
|
7606
7839
|
existing.diags = mergeLspDiagnostics([
|
|
7607
7840
|
...existing.diags,
|
|
7608
|
-
...
|
|
7841
|
+
...attributed,
|
|
7609
7842
|
]);
|
|
7610
7843
|
existing.ts = Math.max(existing.ts, entry.ts);
|
|
7611
7844
|
}
|
|
7612
7845
|
else {
|
|
7613
|
-
all.set(filePath, { diags:
|
|
7846
|
+
all.set(filePath, { diags: attributed, ts: entry.ts });
|
|
7614
7847
|
}
|
|
7615
7848
|
const list = bindingsByPath.get(filePath) ?? [];
|
|
7616
7849
|
list.push(entry.binding);
|
|
@@ -7970,6 +8203,9 @@ export async function hasAuxiliaryLspPublishedForRoot(serverId, filePath) {
|
|
|
7970
8203
|
export async function notifyExternalFileChange(filePath, type) {
|
|
7971
8204
|
return getLSPService().notifyExternalFileChange(filePath, type);
|
|
7972
8205
|
}
|
|
8206
|
+
export async function resyncGitChangedFiles(changedPaths) {
|
|
8207
|
+
return getLSPService().resyncGitChangedFiles(changedPaths);
|
|
8208
|
+
}
|
|
7973
8209
|
export function resetLSPService(options = {}) {
|
|
7974
8210
|
// #2504 review round 2 (F3): whatever the retiring service was still being
|
|
7975
8211
|
// asked for off-hook must stop HERE, before any teardown and before the
|