@mrclrchtr/supi-code-intelligence 4.10.0 → 5.0.0
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 +2 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +9 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +53 -13
- package/node_modules/@mrclrchtr/supi-code-runtime/src/request-control.ts +47 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +13 -1
- package/node_modules/@mrclrchtr/supi-core/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/config/config.ts +31 -0
- package/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -0
- package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +19 -3
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +3 -0
- package/node_modules/@mrclrchtr/supi-lsp/README.md +80 -5
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +9 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +53 -13
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/request-control.ts +47 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +13 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config/config.ts +31 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +19 -3
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +3 -0
- package/node_modules/@mrclrchtr/supi-lsp/package.json +7 -6
- package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +10 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-cache.ts +206 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-capabilities.ts +78 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-collection.ts +114 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-evidence.ts +372 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-host.ts +14 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-pull.ts +62 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-refresh.ts +491 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-request.ts +9 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-timing.ts +71 -11
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-waiters.ts +206 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostics.ts +365 -297
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-state.ts +52 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-sync.ts +185 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-file-state.ts +25 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +467 -115
- package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +161 -33
- package/node_modules/@mrclrchtr/supi-lsp/src/config/capabilities.ts +10 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/config/defaults.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/debug-telemetry.ts +45 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/evidence.ts +38 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-client-state.ts +79 -9
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +13 -4
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +300 -24
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-symbol.ts +5 -2
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +464 -96
- package/node_modules/@mrclrchtr/supi-lsp/src/provider/lsp-refactor-provider.ts +130 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/provider/lsp-semantic-provider.ts +45 -329
- package/node_modules/@mrclrchtr/supi-lsp/src/provider/refactor-planning.ts +57 -120
- package/node_modules/@mrclrchtr/supi-lsp/src/provider/semantic-edit-normalizer.ts +320 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/provider/semantic-symbol-mapper.ts +207 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/session/readiness.ts +72 -2
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-controller.ts +151 -26
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostic-surface.ts +40 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +50 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +136 -120
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-transition-debug.ts +49 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/session/workspace-lsp-runtime.ts +102 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +17 -7
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +9 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +53 -13
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/request-control.ts +47 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +13 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config/config.ts +31 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +19 -3
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +3 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/LICENSE +21 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/README.md +258 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/dist/babel.cjs +257 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/dist/jiti.cjs +1 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-cli.mjs +34 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-hooks.mjs +124 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-native.mjs +121 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-register.d.mts +1 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-register.mjs +4 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-static.mjs +23 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti.cjs +30 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti.d.cts +8 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti.d.mts +8 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti.mjs +29 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/types.d.ts +420 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/package.json +146 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +6 -3
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/api.ts +0 -9
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/index.ts +0 -8
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/provider/tree-sitter-provider.ts +16 -12
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime-controller.ts +65 -87
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/session.ts +47 -95
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-timing.ts +147 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-client-lifecycle.ts +39 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-client.ts +561 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-message-size.ts +24 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-protocol.ts +452 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/call-sites.ts +4 -2
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callees.ts +12 -4
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/exports.ts +4 -2
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/imports.ts +4 -2
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/node-at.ts +5 -3
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +33 -12
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/blocking-test-bootstrap.mjs +34 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/bootstrap.mjs +8 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/parsed-file-store-helpers.ts +23 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/parsed-file-store.ts +400 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/request-control.ts +48 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/runtime-parser-helpers.ts +24 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/runtime-query-helpers.ts +43 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/runtime.ts +421 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/service.ts +116 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/worker-main.ts +201 -0
- package/package.json +5 -5
- package/src/analysis/health/diagnostics.ts +236 -24
- package/src/analysis/health/recovery.ts +6 -2
- package/src/analysis/provider.ts +58 -0
- package/src/analysis/readiness.ts +30 -9
- package/src/analysis/refactor/apply.ts +45 -1
- package/src/analysis/refactor/mutation-authority.ts +143 -0
- package/src/analysis/relations/callees.ts +9 -1
- package/src/analysis/relations/types.ts +5 -2
- package/src/analysis/search/ast-scan-timing.ts +9 -2
- package/src/analysis/search/ast-scan.ts +1 -1
- package/src/analysis/search/deadline.ts +2 -2
- package/src/analysis/search/pattern-analysis.ts +46 -32
- package/src/analysis/search/pattern.ts +12 -2
- package/src/analysis/target/anchored.ts +20 -11
- package/src/analysis/target/file.ts +18 -11
- package/src/analysis/target/identity.ts +19 -3
- package/src/analysis/target/symbol.ts +36 -15
- package/src/api.ts +4 -0
- package/src/diagnostics/evidence.ts +51 -0
- package/src/session/capability-adapter.ts +12 -3
- package/src/session/find-workflow.ts +17 -7
- package/src/session/graph/collect.ts +18 -2
- package/src/session/graph-workflow.ts +19 -12
- package/src/session/health-refresh.ts +148 -0
- package/src/session/health-types.ts +37 -15
- package/src/session/health-workflow.ts +37 -39
- package/src/session/inspect/collect.ts +20 -3
- package/src/session/inspect-workflow.ts +10 -5
- package/src/session/orientation/collect.ts +10 -1
- package/src/session/orientation/context-facts.ts +3 -1
- package/src/session/orientation/context-sections.ts +20 -7
- package/src/session/orientation/gather.ts +17 -11
- package/src/session/orientation-types.ts +2 -1
- package/src/session/orientation-workflow.ts +15 -8
- package/src/session/refactor-plans.ts +2 -0
- package/src/session/refactor-workflow.ts +59 -14
- package/src/session/session.ts +4 -1
- package/src/session/target-workflow.ts +54 -23
- package/src/session/workflow-control.ts +7 -3
- package/src/substrate/lsp/lifecycle.ts +26 -46
- package/src/substrate/lsp/maintenance.ts +199 -25
- package/src/substrate/lsp/state.ts +2 -2
- package/src/substrate/workspace-provider-host.ts +25 -3
- package/src/tool/find/execute.ts +7 -1
- package/src/tool/find/render.ts +20 -3
- package/src/tool/find/tui.ts +23 -21
- package/src/tool/graph/execute.ts +23 -3
- package/src/tool/graph/tui.ts +59 -46
- package/src/tool/health/execute.ts +7 -2
- package/src/tool/health/markdown.ts +56 -14
- package/src/tool/health/refresh-status.ts +105 -0
- package/src/tool/health/tui.ts +95 -98
- package/src/tool/infra/workflow-control.ts +8 -0
- package/src/tool/inspect/execute.ts +7 -1
- package/src/tool/inspect/tui.ts +26 -18
- package/src/tool/orientation/execute.ts +21 -3
- package/src/tool/orientation/tui.ts +51 -35
- package/src/tool/refactor-apply/execute.ts +3 -0
- package/src/tool/refactor-apply/tui.ts +43 -25
- package/src/tool/refactor-plan/execute.ts +4 -0
- package/src/tool/refactor-plan/tui.ts +33 -20
- package/src/tool/register.ts +127 -22
- package/src/tool/resolve/execute.ts +18 -1
- package/src/tool/resolve/tui.ts +42 -24
- package/src/tool/result/display.ts +107 -0
- package/src/tool/result/errors.ts +20 -17
- package/src/tool/result/find.ts +52 -8
- package/src/tool/result/graph.ts +44 -1
- package/src/tool/result/health.ts +53 -1
- package/src/tool/result/inspect.ts +113 -1
- package/src/tool/result/orientation.ts +66 -14
- package/src/tool/result/refactor.ts +36 -4
- package/src/tool/result/resolve.ts +44 -7
- package/src/tool/result/types.ts +20 -0
- package/src/types/execution.ts +18 -5
- package/src/types/index.ts +8 -1
- package/src/ui/status-command.ts +5 -3
- package/src/ui/status-overlay.ts +4 -0
- package/src/ui/tui/common.ts +108 -10
- package/src/ui/tui/display.ts +48 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime.ts +0 -351
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
// biome-ignore-all lint/style/noExcessiveLinesPerFile: refresh orchestration, pull collection, and the reopen fallback stay in one cohesive module.
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import {
|
|
4
|
+
type CodeRequestControl,
|
|
5
|
+
isCodeRequestInterruption,
|
|
6
|
+
throwIfCodeRequestInterrupted,
|
|
7
|
+
} from "@mrclrchtr/supi-code-runtime/api";
|
|
8
|
+
import type { TextDocumentIdentifier } from "../config/types.ts";
|
|
9
|
+
import {
|
|
10
|
+
type DiagnosticEvidenceSummary,
|
|
11
|
+
summarizeDiagnosticEvidence,
|
|
12
|
+
} from "../diagnostics/evidence.ts";
|
|
13
|
+
import { detectLanguageId, fileToUri, uriToFile } from "../utils.ts";
|
|
14
|
+
import {
|
|
15
|
+
type DiagnosticCacheEntry,
|
|
16
|
+
type DiagnosticSynchronization,
|
|
17
|
+
hasFreshEvidence,
|
|
18
|
+
hasFreshPush,
|
|
19
|
+
isCurrentSynchronization,
|
|
20
|
+
latestFreshEvidenceReceivedAt,
|
|
21
|
+
nextDocumentVersion,
|
|
22
|
+
raceDiagnosticPull,
|
|
23
|
+
} from "./client-diagnostic-evidence.ts";
|
|
24
|
+
import type { ClientDiagnosticsHost } from "./client-diagnostic-host.ts";
|
|
25
|
+
import { pullDiagnosticEvidence } from "./client-diagnostic-pull.ts";
|
|
26
|
+
import type { DiagnosticPullRequest } from "./client-diagnostic-request.ts";
|
|
27
|
+
import {
|
|
28
|
+
DiagnosticObserver,
|
|
29
|
+
DiagnosticPullError,
|
|
30
|
+
isDiagnosticTimeout,
|
|
31
|
+
} from "./client-diagnostic-timing.ts";
|
|
32
|
+
import type { DiagnosticStateWait, DiagnosticWaitRegistry } from "./client-diagnostic-waiters.ts";
|
|
33
|
+
import type { OpenDocumentState } from "./client-document-state.ts";
|
|
34
|
+
import {
|
|
35
|
+
type ResynchronizeDocumentsResult,
|
|
36
|
+
reopenDocument,
|
|
37
|
+
resynchronizeOpenDocuments,
|
|
38
|
+
} from "./client-document-sync.ts";
|
|
39
|
+
import { getDiagnosticFileState } from "./client-file-state.ts";
|
|
40
|
+
|
|
41
|
+
/** Send the protocol close notification for one removed document. */
|
|
42
|
+
export function sendDidCloseNotification(
|
|
43
|
+
host: Pick<ClientDiagnosticsHost, "sendNotification">,
|
|
44
|
+
uri: string,
|
|
45
|
+
): void {
|
|
46
|
+
host.sendNotification("textDocument/didClose", {
|
|
47
|
+
textDocument: { uri } satisfies TextDocumentIdentifier,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Build final document coverage after one client refresh attempt. */
|
|
52
|
+
export function buildDiagnosticRefreshEvidence(options: {
|
|
53
|
+
requestedFiles: readonly string[];
|
|
54
|
+
resynchronization: ResynchronizeDocumentsResult;
|
|
55
|
+
/** Synchronizations that prove evidence, after reopen-resync updates. */
|
|
56
|
+
synchronizations: readonly DiagnosticSynchronization[];
|
|
57
|
+
failedPullUris: ReadonlySet<string>;
|
|
58
|
+
failedFiles: ReadonlySet<string>;
|
|
59
|
+
failedResynchronizations: ReadonlySet<string>;
|
|
60
|
+
currentEvidenceRevision: number;
|
|
61
|
+
openDocuments: ReadonlyMap<string, unknown>;
|
|
62
|
+
diagnosticStore: ReadonlyMap<string, DiagnosticCacheEntry>;
|
|
63
|
+
}): DiagnosticEvidenceSummary {
|
|
64
|
+
const synchronizationByFile = new Map(
|
|
65
|
+
options.synchronizations.map((item) => [uriToFile(item.uri), item]),
|
|
66
|
+
);
|
|
67
|
+
const removedFiles = new Set(options.resynchronization.removedFiles);
|
|
68
|
+
const failedFiles = new Set(options.resynchronization.failedFiles);
|
|
69
|
+
const documents = options.requestedFiles.map((file) => {
|
|
70
|
+
const uri = fileToUri(file);
|
|
71
|
+
const synchronization = synchronizationByFile.get(file);
|
|
72
|
+
if (removedFiles.has(file) || getDiagnosticFileState(file) === "removed") {
|
|
73
|
+
return { file, status: "removed" as const };
|
|
74
|
+
}
|
|
75
|
+
if (
|
|
76
|
+
failedFiles.has(file) ||
|
|
77
|
+
options.failedFiles.has(file) ||
|
|
78
|
+
options.failedResynchronizations.has(file)
|
|
79
|
+
) {
|
|
80
|
+
return { file, status: "failed" as const };
|
|
81
|
+
}
|
|
82
|
+
if (!options.openDocuments.has(uri)) {
|
|
83
|
+
return { file, status: "unconfirmed" as const };
|
|
84
|
+
}
|
|
85
|
+
if (
|
|
86
|
+
synchronization &&
|
|
87
|
+
hasFreshEvidence(options.diagnosticStore, synchronization, options.currentEvidenceRevision)
|
|
88
|
+
) {
|
|
89
|
+
return { file, status: "confirmed" as const };
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
file,
|
|
93
|
+
status: options.failedPullUris.has(uri) ? ("failed" as const) : ("unconfirmed" as const),
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
return summarizeDiagnosticEvidence(documents);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Pull one document and reject evidence from a stale client generation. */
|
|
100
|
+
export function pullClientDiagnosticEvidence(
|
|
101
|
+
options: Omit<DiagnosticPullRequest, "previousResultId"> & {
|
|
102
|
+
store: Map<string, DiagnosticCacheEntry>;
|
|
103
|
+
synchronizationId?: number;
|
|
104
|
+
evidenceRevision: number;
|
|
105
|
+
currentRevision(): number;
|
|
106
|
+
isCurrentSynchronization(): boolean;
|
|
107
|
+
isRelatedUriTracked(uri: string): boolean;
|
|
108
|
+
pull(
|
|
109
|
+
request: DiagnosticPullRequest,
|
|
110
|
+
): Promise<import("../config/types.ts").DocumentDiagnosticReport | null>;
|
|
111
|
+
},
|
|
112
|
+
): Promise<boolean> {
|
|
113
|
+
return pullDiagnosticEvidence(options);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Pull one document through a client's host and preserve generation checks. */
|
|
117
|
+
export function pullClientDiagnosticEvidenceFromHost(options: {
|
|
118
|
+
host: Pick<ClientDiagnosticsHost, "pullDocumentDiagnostics">;
|
|
119
|
+
store: Map<string, DiagnosticCacheEntry>;
|
|
120
|
+
openDocuments: ReadonlyMap<string, { synchronizationId: number; evidenceRevision?: number }>;
|
|
121
|
+
currentEvidenceRevision(): number;
|
|
122
|
+
isRelatedUriTracked(uri: string): boolean;
|
|
123
|
+
request: Omit<DiagnosticPullRequest, "previousResultId"> & {
|
|
124
|
+
synchronizationId?: number;
|
|
125
|
+
evidenceRevision?: number;
|
|
126
|
+
};
|
|
127
|
+
}): Promise<boolean> {
|
|
128
|
+
return pullClientDiagnosticEvidence({
|
|
129
|
+
store: options.store,
|
|
130
|
+
...options.request,
|
|
131
|
+
evidenceRevision: options.request.evidenceRevision ?? options.currentEvidenceRevision(),
|
|
132
|
+
currentRevision: options.currentEvidenceRevision,
|
|
133
|
+
isCurrentSynchronization: () =>
|
|
134
|
+
options.request.synchronizationId === undefined ||
|
|
135
|
+
isCurrentSynchronization(options.openDocuments, {
|
|
136
|
+
uri: options.request.uri,
|
|
137
|
+
synchronizationId: options.request.synchronizationId,
|
|
138
|
+
evidenceRevision: options.request.evidenceRevision,
|
|
139
|
+
}),
|
|
140
|
+
isRelatedUriTracked: options.isRelatedUriTracked,
|
|
141
|
+
pull: (request) => options.host.pullDocumentDiagnostics(request),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Collect pull evidence for every synchronized document in one refresh. */
|
|
146
|
+
export async function pullDiagnosticsForOpenDocuments(options: {
|
|
147
|
+
requests: readonly DiagnosticSynchronization[];
|
|
148
|
+
syncStart: number;
|
|
149
|
+
maxWaitMs: number;
|
|
150
|
+
signal?: AbortSignal;
|
|
151
|
+
deadline?: number;
|
|
152
|
+
operationId?: string;
|
|
153
|
+
currentEvidenceRevision: () => number;
|
|
154
|
+
openDocuments: ReadonlyMap<string, { evidenceRevision: number; synchronizationId: number }>;
|
|
155
|
+
diagnosticStore: ReadonlyMap<string, DiagnosticCacheEntry>;
|
|
156
|
+
waitForChange: () => DiagnosticStateWait;
|
|
157
|
+
pullDiagnostics: (options: {
|
|
158
|
+
request: DiagnosticSynchronization;
|
|
159
|
+
timeoutMs: number;
|
|
160
|
+
signal: AbortSignal;
|
|
161
|
+
operationId?: string;
|
|
162
|
+
deadline?: number;
|
|
163
|
+
}) => Promise<boolean>;
|
|
164
|
+
}): Promise<void> {
|
|
165
|
+
const deadline = options.syncStart + options.maxWaitMs;
|
|
166
|
+
const results = await Promise.allSettled(
|
|
167
|
+
options.requests.map(async (request) => {
|
|
168
|
+
const remaining = deadline - Date.now();
|
|
169
|
+
if (remaining <= 0) throw new Error("pull diagnostic timeout");
|
|
170
|
+
const pullController = new AbortController();
|
|
171
|
+
// Link the caller's cancellation to this document's pull controller so
|
|
172
|
+
// an in-flight pull receives protocol cancellation and stops promptly.
|
|
173
|
+
const onAbort = () => pullController.abort(options.signal?.reason);
|
|
174
|
+
if (options.signal?.aborted) onAbort();
|
|
175
|
+
else options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
176
|
+
try {
|
|
177
|
+
const outcome = await raceDiagnosticPull({
|
|
178
|
+
pull: options.pullDiagnostics({
|
|
179
|
+
request,
|
|
180
|
+
timeoutMs: remaining,
|
|
181
|
+
signal: pullController.signal,
|
|
182
|
+
operationId: options.operationId,
|
|
183
|
+
deadline: options.deadline,
|
|
184
|
+
}),
|
|
185
|
+
waitForChange: options.waitForChange,
|
|
186
|
+
freshPush: () =>
|
|
187
|
+
hasFreshPush(options.diagnosticStore, request, options.currentEvidenceRevision()),
|
|
188
|
+
current: () => isCurrentSynchronization(options.openDocuments, request),
|
|
189
|
+
});
|
|
190
|
+
if (outcome !== "pull") pullController.abort();
|
|
191
|
+
return outcome === "pull";
|
|
192
|
+
} finally {
|
|
193
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
194
|
+
}
|
|
195
|
+
}),
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
// A cancelled refresh must not degrade into failed coverage evidence:
|
|
199
|
+
// the caller no longer awaits a result.
|
|
200
|
+
throwIfCodeRequestInterrupted({ signal: options.signal, deadline: options.deadline });
|
|
201
|
+
|
|
202
|
+
const incomplete = results.some((result) => result.status === "rejected" || !result.value);
|
|
203
|
+
if (incomplete && options.requests.length > 0) {
|
|
204
|
+
const failedUris = options.requests.flatMap((request, index) => {
|
|
205
|
+
const result = results[index];
|
|
206
|
+
return result?.status === "rejected" && !isDiagnosticTimeout(result.reason)
|
|
207
|
+
? [request.uri]
|
|
208
|
+
: [];
|
|
209
|
+
});
|
|
210
|
+
throw new DiagnosticPullError(
|
|
211
|
+
results.some((result) => result.status === "rejected" && isDiagnosticTimeout(result.reason)),
|
|
212
|
+
failedUris,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
interface ClientDiagnosticRefreshOptions {
|
|
218
|
+
readonly host: ClientDiagnosticsHost;
|
|
219
|
+
readonly openDocuments: Map<string, OpenDocumentState>;
|
|
220
|
+
readonly diagnosticStore: Map<string, DiagnosticCacheEntry>;
|
|
221
|
+
readonly waiters: DiagnosticWaitRegistry;
|
|
222
|
+
readonly versionHistory: Map<string, number>;
|
|
223
|
+
readonly requestedFiles: readonly string[];
|
|
224
|
+
readonly evidenceRevision: () => number;
|
|
225
|
+
readonly failedFiles: () => ReadonlySet<string>;
|
|
226
|
+
readonly isRelatedUriTracked: (uri: string) => boolean;
|
|
227
|
+
readonly nextSynchronizationId: () => number;
|
|
228
|
+
readonly clearFile: (uri: string) => void;
|
|
229
|
+
readonly invalidateEvidence: (uri: string) => void;
|
|
230
|
+
readonly markUnversionedSyncMoment: (uri: string) => void;
|
|
231
|
+
readonly clearFailedFile: (uri: string) => void;
|
|
232
|
+
readonly options: { maxWaitMs?: number; quietMs?: number } & CodeRequestControl;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Refresh one client and return exact document evidence for that attempt. */
|
|
236
|
+
export async function refreshClientOpenDiagnostics(
|
|
237
|
+
options: ClientDiagnosticRefreshOptions,
|
|
238
|
+
): Promise<DiagnosticEvidenceSummary> {
|
|
239
|
+
// Reject immediately when the request was already cancelled: no document
|
|
240
|
+
// resynchronization or protocol traffic may start for a pass the caller
|
|
241
|
+
// no longer awaits.
|
|
242
|
+
throwIfCodeRequestInterrupted(options.options);
|
|
243
|
+
const supportsPull = options.host.supportsPullDiagnostics();
|
|
244
|
+
const observer = new DiagnosticObserver("refresh-open", supportsPull, options.options, {
|
|
245
|
+
server: options.host.server,
|
|
246
|
+
cwd: options.host.cwd,
|
|
247
|
+
});
|
|
248
|
+
if (!options.host.isOperational()) {
|
|
249
|
+
observer.skipped(options.requestedFiles.length);
|
|
250
|
+
return summarizeDiagnosticEvidence(
|
|
251
|
+
options.requestedFiles.map((file) => ({
|
|
252
|
+
file,
|
|
253
|
+
status:
|
|
254
|
+
getDiagnosticFileState(file) === "removed" ? ("removed" as const) : ("failed" as const),
|
|
255
|
+
})),
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const maxWaitMs = options.options.maxWaitMs ?? 3_000;
|
|
260
|
+
const quietMs = options.options.quietMs ?? 200;
|
|
261
|
+
const syncStart = Date.now();
|
|
262
|
+
const resynchronization = resynchronizeOpenDocuments({
|
|
263
|
+
openDocuments: options.openDocuments,
|
|
264
|
+
waiters: options.waiters,
|
|
265
|
+
nextVersion: (uri) => nextDocumentVersion(options.versionHistory, uri),
|
|
266
|
+
nextSynchronizationId: options.nextSynchronizationId,
|
|
267
|
+
evidenceRevision: options.evidenceRevision(),
|
|
268
|
+
sendNotification: (method, params) => options.host.sendNotification(method, params),
|
|
269
|
+
uriToFile,
|
|
270
|
+
clearFile: options.clearFile,
|
|
271
|
+
invalidateEvidence: options.invalidateEvidence,
|
|
272
|
+
markUnversionedSyncMoment: options.markUnversionedSyncMoment,
|
|
273
|
+
clearFailedFile: options.clearFailedFile,
|
|
274
|
+
});
|
|
275
|
+
let synchronizations = resynchronization.synchronizations;
|
|
276
|
+
const settleEpoch = options.waiters.settleEpoch;
|
|
277
|
+
observer.synchronized();
|
|
278
|
+
const documentCount = synchronizations.length;
|
|
279
|
+
let failedPullUris: ReadonlySet<string> = new Set();
|
|
280
|
+
const buildEvidence = () =>
|
|
281
|
+
buildDiagnosticRefreshEvidence({
|
|
282
|
+
requestedFiles: options.requestedFiles,
|
|
283
|
+
resynchronization,
|
|
284
|
+
synchronizations,
|
|
285
|
+
failedPullUris,
|
|
286
|
+
failedFiles: options.failedFiles(),
|
|
287
|
+
failedResynchronizations: new Set(resynchronization.failedFiles),
|
|
288
|
+
currentEvidenceRevision: options.evidenceRevision(),
|
|
289
|
+
openDocuments: options.openDocuments,
|
|
290
|
+
diagnosticStore: options.diagnosticStore,
|
|
291
|
+
});
|
|
292
|
+
if (documentCount === 0) {
|
|
293
|
+
observer.skipped(0);
|
|
294
|
+
return buildEvidence();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (supportsPull) {
|
|
298
|
+
const pull = await collectPullEvidenceForRefresh({
|
|
299
|
+
options,
|
|
300
|
+
synchronizations,
|
|
301
|
+
syncStart,
|
|
302
|
+
maxWaitMs,
|
|
303
|
+
observer,
|
|
304
|
+
});
|
|
305
|
+
failedPullUris = new Set(pull.failedPullUris);
|
|
306
|
+
if (pull.completed) return buildEvidence();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
let finalSettle = await options.waiters.waitForSettle(
|
|
310
|
+
{
|
|
311
|
+
syncStart,
|
|
312
|
+
maxWaitMs,
|
|
313
|
+
quietMs,
|
|
314
|
+
settleEpoch,
|
|
315
|
+
isComplete: () =>
|
|
316
|
+
synchronizations.every((item) =>
|
|
317
|
+
hasFreshEvidence(options.diagnosticStore, item, options.evidenceRevision()),
|
|
318
|
+
),
|
|
319
|
+
latestReceived: () =>
|
|
320
|
+
latestFreshEvidenceReceivedAt(
|
|
321
|
+
options.diagnosticStore,
|
|
322
|
+
synchronizations,
|
|
323
|
+
options.evidenceRevision(),
|
|
324
|
+
),
|
|
325
|
+
},
|
|
326
|
+
options.options,
|
|
327
|
+
);
|
|
328
|
+
if (!supportsPull && finalSettle.outcome === "timed-out") {
|
|
329
|
+
const reopen = await reopenUnconfirmedDocuments({
|
|
330
|
+
options,
|
|
331
|
+
synchronizations,
|
|
332
|
+
maxWaitMs,
|
|
333
|
+
quietMs,
|
|
334
|
+
observer,
|
|
335
|
+
});
|
|
336
|
+
if (reopen.performed) {
|
|
337
|
+
synchronizations = reopen.synchronizations;
|
|
338
|
+
finalSettle = await options.waiters.waitForSettle(
|
|
339
|
+
{
|
|
340
|
+
syncStart: reopen.startedAt,
|
|
341
|
+
maxWaitMs: reopen.budgetMs,
|
|
342
|
+
quietMs,
|
|
343
|
+
settleEpoch: options.waiters.settleEpoch,
|
|
344
|
+
isComplete: () =>
|
|
345
|
+
synchronizations.every((item) =>
|
|
346
|
+
hasFreshEvidence(options.diagnosticStore, item, options.evidenceRevision()),
|
|
347
|
+
),
|
|
348
|
+
latestReceived: () =>
|
|
349
|
+
latestFreshEvidenceReceivedAt(
|
|
350
|
+
options.diagnosticStore,
|
|
351
|
+
synchronizations,
|
|
352
|
+
options.evidenceRevision(),
|
|
353
|
+
),
|
|
354
|
+
},
|
|
355
|
+
options.options,
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
observer.pushSettled(documentCount, finalSettle);
|
|
360
|
+
// A cancelled settle must not publish evidence the caller no longer awaits.
|
|
361
|
+
throwIfCodeRequestInterrupted(options.options);
|
|
362
|
+
return buildEvidence();
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Pull diagnostic evidence for every synchronized document, or fall through
|
|
367
|
+
* to the push settle path when any pull fails. An interruption during the
|
|
368
|
+
* pull phase stops the refresh instead of degrading into failed coverage.
|
|
369
|
+
*/
|
|
370
|
+
async function collectPullEvidenceForRefresh(options: {
|
|
371
|
+
options: ClientDiagnosticRefreshOptions;
|
|
372
|
+
synchronizations: readonly DiagnosticSynchronization[];
|
|
373
|
+
syncStart: number;
|
|
374
|
+
maxWaitMs: number;
|
|
375
|
+
observer: DiagnosticObserver;
|
|
376
|
+
}): Promise<{ completed: boolean; failedPullUris: ReadonlySet<string> }> {
|
|
377
|
+
const { options: refresh, synchronizations, syncStart, maxWaitMs, observer } = options;
|
|
378
|
+
try {
|
|
379
|
+
await pullDiagnosticsForOpenDocuments({
|
|
380
|
+
requests: synchronizations,
|
|
381
|
+
syncStart,
|
|
382
|
+
maxWaitMs,
|
|
383
|
+
signal: refresh.options.signal,
|
|
384
|
+
deadline: refresh.options.deadline,
|
|
385
|
+
operationId: refresh.options.operationId,
|
|
386
|
+
currentEvidenceRevision: refresh.evidenceRevision,
|
|
387
|
+
openDocuments: refresh.openDocuments,
|
|
388
|
+
diagnosticStore: refresh.diagnosticStore,
|
|
389
|
+
waitForChange: () => refresh.waiters.waitForChange(),
|
|
390
|
+
pullDiagnostics: (pullOptions) =>
|
|
391
|
+
pullClientDiagnosticEvidenceFromHost({
|
|
392
|
+
host: refresh.host,
|
|
393
|
+
store: refresh.diagnosticStore,
|
|
394
|
+
openDocuments: refresh.openDocuments,
|
|
395
|
+
currentEvidenceRevision: refresh.evidenceRevision,
|
|
396
|
+
isRelatedUriTracked: refresh.isRelatedUriTracked,
|
|
397
|
+
request: {
|
|
398
|
+
uri: pullOptions.request.uri,
|
|
399
|
+
timeoutMs: pullOptions.timeoutMs,
|
|
400
|
+
synchronizationId: pullOptions.request.synchronizationId,
|
|
401
|
+
evidenceRevision:
|
|
402
|
+
refresh.openDocuments.get(pullOptions.request.uri)?.evidenceRevision ??
|
|
403
|
+
refresh.evidenceRevision(),
|
|
404
|
+
signal: pullOptions.signal,
|
|
405
|
+
deadline: pullOptions.deadline,
|
|
406
|
+
operationId: pullOptions.operationId,
|
|
407
|
+
},
|
|
408
|
+
}),
|
|
409
|
+
});
|
|
410
|
+
observer.pullCompleted(synchronizations.length);
|
|
411
|
+
return { completed: true, failedPullUris: new Set() };
|
|
412
|
+
} catch (error) {
|
|
413
|
+
observer.pullFailed(error);
|
|
414
|
+
if (error instanceof DiagnosticPullError) {
|
|
415
|
+
return { completed: false, failedPullUris: new Set(error.failedUris) };
|
|
416
|
+
}
|
|
417
|
+
if (isCodeRequestInterruption(error, refresh.options)) throw error;
|
|
418
|
+
return { completed: false, failedPullUris: new Set() };
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Reopen-resync fallback (R2): on push-only routes an open document that
|
|
424
|
+
|
|
425
|
+
* stays unconfirmed after the settle window receives no further push — a
|
|
426
|
+
* clean file gets no push on didChange at all. Close and reopen each
|
|
427
|
+
* unconfirmed document so the server publishes on didOpen, then settle
|
|
428
|
+
* again within a bounded second window. The cache entry and version
|
|
429
|
+
* history survive the reopen; other documents keep their server state.
|
|
430
|
+
*/
|
|
431
|
+
async function reopenUnconfirmedDocuments(options: {
|
|
432
|
+
options: ClientDiagnosticRefreshOptions;
|
|
433
|
+
synchronizations: readonly DiagnosticSynchronization[];
|
|
434
|
+
maxWaitMs: number;
|
|
435
|
+
quietMs: number;
|
|
436
|
+
observer: DiagnosticObserver;
|
|
437
|
+
}): Promise<{
|
|
438
|
+
performed: boolean;
|
|
439
|
+
startedAt: number;
|
|
440
|
+
budgetMs: number;
|
|
441
|
+
synchronizations: DiagnosticSynchronization[];
|
|
442
|
+
}> {
|
|
443
|
+
const { options: refresh, synchronizations, maxWaitMs, quietMs, observer } = options;
|
|
444
|
+
const startedAt = Date.now();
|
|
445
|
+
const budgetMs = Math.min(maxWaitMs, quietMs * 4);
|
|
446
|
+
const unconfirmed = synchronizations.filter(
|
|
447
|
+
(item) => !hasFreshEvidence(refresh.diagnosticStore, item, refresh.evidenceRevision()),
|
|
448
|
+
);
|
|
449
|
+
const reopenedSynchronizations: DiagnosticSynchronization[] = [];
|
|
450
|
+
for (const item of unconfirmed) {
|
|
451
|
+
const document = refresh.openDocuments.get(item.uri);
|
|
452
|
+
if (!document) continue;
|
|
453
|
+
const filePath = uriToFile(item.uri);
|
|
454
|
+
let content: string;
|
|
455
|
+
try {
|
|
456
|
+
content = readFileSync(filePath, "utf-8");
|
|
457
|
+
} catch {
|
|
458
|
+
// The file disappeared mid-refresh; keep the document as-is and
|
|
459
|
+
// report its current unconfirmed coverage.
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
reopenDocument({
|
|
463
|
+
uri: item.uri,
|
|
464
|
+
content,
|
|
465
|
+
document,
|
|
466
|
+
languageId: detectLanguageId(filePath),
|
|
467
|
+
nextVersion: () => nextDocumentVersion(refresh.versionHistory, item.uri),
|
|
468
|
+
nextSynchronizationId: refresh.nextSynchronizationId,
|
|
469
|
+
evidenceRevision: refresh.evidenceRevision(),
|
|
470
|
+
waiters: refresh.waiters,
|
|
471
|
+
sendNotification: (method, params) => refresh.host.sendNotification(method, params),
|
|
472
|
+
markUnversionedSyncMoment: () => refresh.markUnversionedSyncMoment(item.uri),
|
|
473
|
+
});
|
|
474
|
+
reopenedSynchronizations.push({
|
|
475
|
+
uri: item.uri,
|
|
476
|
+
synchronizationId: document.synchronizationId,
|
|
477
|
+
evidenceRevision: document.evidenceRevision,
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
if (reopenedSynchronizations.length === 0) {
|
|
481
|
+
return { performed: false, startedAt, budgetMs, synchronizations: [...synchronizations] };
|
|
482
|
+
}
|
|
483
|
+
observer.reopened(reopenedSynchronizations.length);
|
|
484
|
+
const reopenedByUri = new Map(reopenedSynchronizations.map((item) => [item.uri, item]));
|
|
485
|
+
return {
|
|
486
|
+
performed: true,
|
|
487
|
+
startedAt,
|
|
488
|
+
budgetMs,
|
|
489
|
+
synchronizations: synchronizations.map((item) => reopenedByUri.get(item.uri) ?? item),
|
|
490
|
+
};
|
|
491
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Sanitized ownership and transport control for one explicit diagnostic pull. */
|
|
2
|
+
export interface DiagnosticPullRequest {
|
|
3
|
+
readonly uri: string;
|
|
4
|
+
readonly previousResultId: string | undefined;
|
|
5
|
+
readonly timeoutMs: number;
|
|
6
|
+
readonly signal?: AbortSignal;
|
|
7
|
+
readonly deadline?: number;
|
|
8
|
+
readonly operationId?: string;
|
|
9
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import type { CodeRequestControl } from "@mrclrchtr/supi-code-runtime/api";
|
|
1
2
|
import { startDebugTimer } from "@mrclrchtr/supi-core/debug";
|
|
3
|
+
import { boundCwd, truncateIdentity } from "../debug-telemetry.ts";
|
|
2
4
|
|
|
3
|
-
type DiagnosticCollection = "fallback" | "none" | "pull" | "push";
|
|
5
|
+
type DiagnosticCollection = "cache" | "fallback" | "none" | "pull" | "push";
|
|
4
6
|
type DiagnosticFreshness = "not-observed" | "observed";
|
|
5
|
-
type DiagnosticOutcome = "completed" | "skipped" | "timed-out";
|
|
7
|
+
type DiagnosticOutcome = "completed" | "incomplete" | "skipped" | "timed-out";
|
|
6
8
|
type DiagnosticPullOutcome = "completed" | "failed" | "not-supported" | "not-used" | "timed-out";
|
|
7
9
|
type DiagnosticPushOutcome = "not-used" | "published" | "released" | "settled" | "timed-out";
|
|
8
10
|
type DiagnosticSettleOutcome = "not-used" | "published" | "quiet" | "released" | "timed-out";
|
|
@@ -10,7 +12,7 @@ type DiagnosticTimingOperation = "refresh-open" | "sync-file";
|
|
|
10
12
|
|
|
11
13
|
/** Result of waiting for a quiet push-diagnostic window. */
|
|
12
14
|
export interface DiagnosticSettleResult {
|
|
13
|
-
readonly outcome: "quiet" | "timed-out";
|
|
15
|
+
readonly outcome: "quiet" | "released" | "timed-out";
|
|
14
16
|
readonly freshness: DiagnosticFreshness;
|
|
15
17
|
}
|
|
16
18
|
|
|
@@ -25,30 +27,47 @@ interface DiagnosticTimingData {
|
|
|
25
27
|
readonly outcome: DiagnosticOutcome;
|
|
26
28
|
readonly pull: DiagnosticPullOutcome;
|
|
27
29
|
readonly push: DiagnosticPushOutcome;
|
|
30
|
+
readonly reopen: number;
|
|
28
31
|
readonly settle: DiagnosticSettleOutcome;
|
|
29
32
|
readonly timedOut: boolean;
|
|
30
33
|
}
|
|
31
34
|
|
|
32
|
-
/** Internal pull failure that retains
|
|
35
|
+
/** Internal pull failure that retains timeout state and failed document URIs. */
|
|
33
36
|
export class DiagnosticPullError extends Error {
|
|
34
|
-
constructor(
|
|
37
|
+
constructor(
|
|
38
|
+
readonly timedOut: boolean,
|
|
39
|
+
readonly failedUris: readonly string[] = [],
|
|
40
|
+
) {
|
|
35
41
|
super("pull diagnostics incomplete");
|
|
36
42
|
}
|
|
37
43
|
}
|
|
38
44
|
|
|
45
|
+
/** Bounded identity for one diagnostic timing observation. */
|
|
46
|
+
export interface DiagnosticTimingIdentity {
|
|
47
|
+
/** Configured server name. */
|
|
48
|
+
readonly server?: string;
|
|
49
|
+
/** Absolute workspace root. */
|
|
50
|
+
readonly cwd?: string;
|
|
51
|
+
/** Workspace-relative file path; sync-file operations only. */
|
|
52
|
+
readonly file?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
39
55
|
/**
|
|
40
|
-
* Record one diagnostic operation without
|
|
56
|
+
* Record one diagnostic operation without diagnostic text or document content.
|
|
41
57
|
*
|
|
42
58
|
* The observer owns result classification so diagnostic control flow does not
|
|
43
|
-
* duplicate the event shape.
|
|
59
|
+
* duplicate the event shape. Identity is bounded and workspace-relative.
|
|
44
60
|
*/
|
|
45
61
|
export class DiagnosticObserver {
|
|
46
62
|
readonly #timer = startDebugTimer();
|
|
47
63
|
#pull: "failed" | "not-supported" | "timed-out";
|
|
64
|
+
#reopened = 0;
|
|
48
65
|
|
|
49
66
|
constructor(
|
|
50
67
|
readonly operation: DiagnosticTimingOperation,
|
|
51
68
|
readonly supportsPull: boolean,
|
|
69
|
+
readonly control?: CodeRequestControl,
|
|
70
|
+
readonly identity?: DiagnosticTimingIdentity,
|
|
52
71
|
) {
|
|
53
72
|
this.#pull = supportsPull ? "failed" : "not-supported";
|
|
54
73
|
}
|
|
@@ -66,6 +85,22 @@ export class DiagnosticObserver {
|
|
|
66
85
|
outcome: "skipped",
|
|
67
86
|
pull: "not-used",
|
|
68
87
|
push: "not-used",
|
|
88
|
+
reopen: 0,
|
|
89
|
+
settle: "not-used",
|
|
90
|
+
timedOut: false,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
cacheReused(documentCount: number): void {
|
|
95
|
+
this.#finish({
|
|
96
|
+
collection: "cache",
|
|
97
|
+
documentCount,
|
|
98
|
+
fallback: false,
|
|
99
|
+
freshness: "observed",
|
|
100
|
+
outcome: "completed",
|
|
101
|
+
pull: "not-used",
|
|
102
|
+
push: "not-used",
|
|
103
|
+
reopen: 0,
|
|
69
104
|
settle: "not-used",
|
|
70
105
|
timedOut: false,
|
|
71
106
|
});
|
|
@@ -81,6 +116,7 @@ export class DiagnosticObserver {
|
|
|
81
116
|
outcome: "completed",
|
|
82
117
|
pull: "completed",
|
|
83
118
|
push: "not-used",
|
|
119
|
+
reopen: 0,
|
|
84
120
|
settle: "not-used",
|
|
85
121
|
timedOut: false,
|
|
86
122
|
},
|
|
@@ -98,17 +134,25 @@ export class DiagnosticObserver {
|
|
|
98
134
|
this.#timer.mark("pull");
|
|
99
135
|
}
|
|
100
136
|
|
|
137
|
+
/** Record that the reopen-resync fallback re-opened unconfirmed documents. */
|
|
138
|
+
reopened(count: number): void {
|
|
139
|
+
this.#reopened += count;
|
|
140
|
+
this.#timer.mark("reopen");
|
|
141
|
+
}
|
|
142
|
+
|
|
101
143
|
pushSettled(documentCount: number, settle: DiagnosticSettleResult): void {
|
|
102
144
|
const timedOut = settle.outcome === "timed-out";
|
|
145
|
+
const completed = settle.outcome === "quiet" && settle.freshness === "observed";
|
|
103
146
|
this.#finish(
|
|
104
147
|
{
|
|
105
148
|
collection: this.supportsPull ? "fallback" : "push",
|
|
106
149
|
documentCount,
|
|
107
150
|
fallback: this.supportsPull,
|
|
108
151
|
freshness: settle.freshness,
|
|
109
|
-
outcome: timedOut ? "timed-out" : "
|
|
152
|
+
outcome: completed ? "completed" : timedOut ? "timed-out" : "incomplete",
|
|
110
153
|
pull: this.#pull,
|
|
111
|
-
push: timedOut ? "timed-out" : "settled",
|
|
154
|
+
push: timedOut ? "timed-out" : settle.outcome === "released" ? "released" : "settled",
|
|
155
|
+
reopen: this.#reopened,
|
|
112
156
|
settle: settle.outcome,
|
|
113
157
|
timedOut: timedOut || this.#pull === "timed-out",
|
|
114
158
|
},
|
|
@@ -124,9 +168,10 @@ export class DiagnosticObserver {
|
|
|
124
168
|
documentCount,
|
|
125
169
|
fallback: this.supportsPull,
|
|
126
170
|
freshness: push === "published" ? "observed" : "not-observed",
|
|
127
|
-
outcome: timedOut ? "timed-out" : "
|
|
171
|
+
outcome: push === "published" ? "completed" : timedOut ? "timed-out" : "incomplete",
|
|
128
172
|
pull: this.#pull,
|
|
129
173
|
push,
|
|
174
|
+
reopen: this.#reopened,
|
|
130
175
|
settle: push,
|
|
131
176
|
timedOut: timedOut || this.#pull === "timed-out",
|
|
132
177
|
},
|
|
@@ -135,13 +180,28 @@ export class DiagnosticObserver {
|
|
|
135
180
|
}
|
|
136
181
|
|
|
137
182
|
#finish(data: DiagnosticTimingData, finalPhase?: "pull" | "push-settle" | "synchronize"): void {
|
|
183
|
+
const { reopen, ...observation } = data;
|
|
138
184
|
this.#timer.finish(
|
|
139
185
|
() => ({
|
|
186
|
+
operationId: this.control?.operationId,
|
|
140
187
|
source: "lsp",
|
|
141
188
|
level: "debug",
|
|
142
189
|
category: "diagnostics.timing",
|
|
143
190
|
message: `LSP diagnostic ${this.operation} ${data.outcome}`,
|
|
144
|
-
|
|
191
|
+
cwd: boundCwd(this.identity?.cwd),
|
|
192
|
+
data: {
|
|
193
|
+
operation: this.operation,
|
|
194
|
+
...observation,
|
|
195
|
+
// Reopen fallback usage is recorded only when it happened; the
|
|
196
|
+
// default event shape stays stable for consumers of #322 telemetry.
|
|
197
|
+
...(reopen > 0 ? { reopen } : {}),
|
|
198
|
+
...(this.identity?.server !== undefined
|
|
199
|
+
? { server: truncateIdentity(this.identity.server) }
|
|
200
|
+
: {}),
|
|
201
|
+
...(this.identity?.file !== undefined
|
|
202
|
+
? { file: truncateIdentity(this.identity.file) }
|
|
203
|
+
: {}),
|
|
204
|
+
},
|
|
145
205
|
}),
|
|
146
206
|
finalPhase,
|
|
147
207
|
);
|