@mrclrchtr/supi-code-intelligence 5.0.0 → 6.0.1
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 +4 -2
- 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-core/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-core/package.json +2 -2
- package/node_modules/@mrclrchtr/supi-core/src/api.ts +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +0 -3
- package/node_modules/@mrclrchtr/supi-core/src/debug.ts +9 -0
- package/node_modules/@mrclrchtr/supi-core/src/index.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/README.md +36 -3
- 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-core/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +2 -2
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/api.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +0 -3
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug.ts +9 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/index.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/package.json +3 -3
- package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +12 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-cache.ts +67 -28
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-collection.ts +63 -6
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-evidence.ts +46 -5
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-host.ts +1 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-publication.ts +214 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-refresh.ts +278 -79
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-timing.ts +29 -8
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostics.ts +101 -19
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-state.ts +19 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-sync.ts +49 -3
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +73 -15
- package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/config/capabilities.ts +2 -2
- package/node_modules/@mrclrchtr/supi-lsp/src/config/config.ts +5 -5
- package/node_modules/@mrclrchtr/supi-lsp/src/config/server-config.ts +3 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-extends.ts +68 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-path.ts +8 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-scope.ts +176 -24
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/evidence.ts +4 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/workspace-sentinels.ts +46 -10
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +4 -3
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +65 -17
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +143 -21
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostic-surface.ts +2 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +43 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +31 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +2 -0
- 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-core/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +2 -2
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/api.ts +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +0 -3
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug.ts +9 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/index.ts +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm.map +4 -4
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.wasm.map +4 -4
- package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -3
- package/package.json +5 -5
- package/src/analysis/health/diagnostics.ts +58 -126
- package/src/analysis/health/file-scope.ts +184 -0
- package/src/analysis/health/recovery.ts +8 -1
- package/src/analysis/readiness.ts +4 -0
- package/src/analysis/search/ast-scan.ts +4 -1
- package/src/analysis/search/pattern-analysis.ts +10 -3
- package/src/analysis/search/pattern.ts +6 -1
- package/src/config.ts +35 -1
- package/src/extension.ts +57 -14
- package/src/overview/overview-data.ts +9 -2
- package/src/overview/overview.ts +49 -30
- package/src/overview/types.ts +2 -0
- package/src/session/find-types.ts +2 -2
- package/src/session/find-workflow.ts +3 -2
- package/src/session/health-refresh.ts +76 -49
- package/src/session/health-types.ts +7 -1
- package/src/session/health-workflow.ts +35 -18
- package/src/session/input/workflows.ts +2 -2
- package/src/session/inspect-workflow.ts +2 -1
- package/src/session/orientation/collect.ts +2 -2
- package/src/session/orientation-types.ts +2 -0
- package/src/session/orientation-workflow.ts +8 -2
- package/src/session/refactor-workflow.ts +2 -1
- package/src/session/session.ts +25 -1
- package/src/substrate/lsp/maintenance.ts +105 -23
- package/src/substrate/lsp/recovery.ts +11 -5
- package/src/tool/code_find/execute.ts +23 -0
- package/src/tool/code_find/guidance.ts +8 -0
- package/src/tool/{find → code_find}/render.ts +1 -1
- package/src/tool/{result/find.ts → code_find/result.ts} +36 -11
- package/src/tool/code_find/spec.ts +37 -0
- package/src/tool/code_graph/execute.ts +24 -0
- package/src/tool/code_graph/guidance.ts +6 -0
- package/src/tool/{graph → code_graph}/markdown.ts +1 -1
- package/src/tool/{result/graph.ts → code_graph/result.ts} +69 -4
- package/src/tool/code_graph/spec.ts +39 -0
- package/src/tool/{health → code_health}/execute.ts +2 -18
- package/src/tool/code_health/guidance.ts +8 -0
- package/src/tool/{health → code_health}/markdown.ts +68 -18
- package/src/tool/{result/health.ts → code_health/result.ts} +29 -4
- package/src/tool/code_health/spec.ts +41 -0
- package/src/tool/code_inspect/execute.ts +20 -0
- package/src/tool/code_inspect/guidance.ts +6 -0
- package/src/tool/{inspect → code_inspect}/markdown.ts +1 -1
- package/src/tool/{result/inspect.ts → code_inspect/result.ts} +32 -3
- package/src/tool/code_inspect/spec.ts +25 -0
- package/src/tool/code_orientation/execute.ts +25 -0
- package/src/tool/code_orientation/guidance.ts +9 -0
- package/src/tool/{orientation → code_orientation}/markdown.ts +1 -1
- package/src/tool/{result/orientation.ts → code_orientation/result.ts} +67 -3
- package/src/tool/code_orientation/spec.ts +25 -0
- package/src/tool/code_refactor_apply/execute.ts +17 -0
- package/src/tool/code_refactor_apply/guidance.ts +5 -0
- package/src/tool/{refactor-apply/execute.ts → code_refactor_apply/result.ts} +5 -11
- package/src/tool/code_refactor_apply/spec.ts +26 -0
- package/src/tool/code_refactor_plan/execute.ts +27 -0
- package/src/tool/code_refactor_plan/guidance.ts +8 -0
- package/src/tool/{refactor-plan/execute.ts → code_refactor_plan/result.ts} +9 -22
- package/src/tool/code_refactor_plan/spec.ts +25 -0
- package/src/tool/code_resolve/execute.ts +19 -0
- package/src/tool/code_resolve/guidance.ts +8 -0
- package/src/tool/{resolve → code_resolve}/markdown.ts +1 -1
- package/src/tool/{result/resolve.ts → code_resolve/result.ts} +37 -24
- package/src/tool/code_resolve/spec.ts +25 -0
- package/src/tool/guidance.ts +71 -52
- package/src/tool/infra/truncate.ts +40 -0
- package/src/tool/{refactor-plan/markdown.ts → refactor-markdown.ts} +4 -7
- package/src/tool/register.ts +9 -97
- package/src/tool/result/refactor.ts +1 -4
- package/src/tool/schemas.ts +26 -157
- package/src/tool/specs.ts +40 -126
- package/src/tool/find/execute.ts +0 -43
- package/src/tool/graph/execute.ts +0 -82
- package/src/tool/inspect/execute.ts +0 -40
- package/src/tool/orientation/execute.ts +0 -80
- package/src/tool/resolve/execute.ts +0 -53
- /package/src/tool/{find → code_find}/ast-kinds.ts +0 -0
- /package/src/tool/{find → code_find}/markdown.ts +0 -0
- /package/src/tool/{find → code_find}/modes.ts +0 -0
- /package/src/tool/{find → code_find}/tui.ts +0 -0
- /package/src/tool/{graph → code_graph}/tui.ts +0 -0
- /package/src/tool/{health → code_health}/refresh-status.ts +0 -0
- /package/src/tool/{health → code_health}/semantic-state.ts +0 -0
- /package/src/tool/{health → code_health}/tui.ts +0 -0
- /package/src/tool/{inspect → code_inspect}/tui.ts +0 -0
- /package/src/tool/{orientation → code_orientation}/tui.ts +0 -0
- /package/src/tool/{refactor-apply → code_refactor_apply}/tui.ts +0 -0
- /package/src/tool/{refactor-plan → code_refactor_plan}/tui.ts +0 -0
- /package/src/tool/{resolve → code_resolve}/tui.ts +0 -0
|
@@ -14,14 +14,19 @@ import { detectLanguageId, fileToUri, uriToFile } from "../utils.ts";
|
|
|
14
14
|
import {
|
|
15
15
|
type DiagnosticCacheEntry,
|
|
16
16
|
type DiagnosticSynchronization,
|
|
17
|
+
hasCurrentEvidence,
|
|
17
18
|
hasFreshEvidence,
|
|
18
19
|
hasFreshPush,
|
|
19
20
|
isCurrentSynchronization,
|
|
20
|
-
|
|
21
|
+
latestCurrentEvidenceReceivedAt,
|
|
21
22
|
nextDocumentVersion,
|
|
22
23
|
raceDiagnosticPull,
|
|
23
24
|
} from "./client-diagnostic-evidence.ts";
|
|
24
25
|
import type { ClientDiagnosticsHost } from "./client-diagnostic-host.ts";
|
|
26
|
+
import type {
|
|
27
|
+
DiagnosticPublicationIdentity,
|
|
28
|
+
DiagnosticPublicationSynchronization,
|
|
29
|
+
} from "./client-diagnostic-publication.ts";
|
|
25
30
|
import { pullDiagnosticEvidence } from "./client-diagnostic-pull.ts";
|
|
26
31
|
import type { DiagnosticPullRequest } from "./client-diagnostic-request.ts";
|
|
27
32
|
import {
|
|
@@ -30,7 +35,11 @@ import {
|
|
|
30
35
|
isDiagnosticTimeout,
|
|
31
36
|
} from "./client-diagnostic-timing.ts";
|
|
32
37
|
import type { DiagnosticStateWait, DiagnosticWaitRegistry } from "./client-diagnostic-waiters.ts";
|
|
33
|
-
import
|
|
38
|
+
import {
|
|
39
|
+
fingerprintDocumentContent,
|
|
40
|
+
hasConfirmedDiagnosticEvidence,
|
|
41
|
+
type OpenDocumentState,
|
|
42
|
+
} from "./client-document-state.ts";
|
|
34
43
|
import {
|
|
35
44
|
type ResynchronizeDocumentsResult,
|
|
36
45
|
reopenDocument,
|
|
@@ -60,12 +69,15 @@ export function buildDiagnosticRefreshEvidence(options: {
|
|
|
60
69
|
currentEvidenceRevision: number;
|
|
61
70
|
openDocuments: ReadonlyMap<string, unknown>;
|
|
62
71
|
diagnosticStore: ReadonlyMap<string, DiagnosticCacheEntry>;
|
|
72
|
+
/** Required quiet time after the latest push for this refresh result. */
|
|
73
|
+
pushQuietMs?: number;
|
|
63
74
|
}): DiagnosticEvidenceSummary {
|
|
64
75
|
const synchronizationByFile = new Map(
|
|
65
76
|
options.synchronizations.map((item) => [uriToFile(item.uri), item]),
|
|
66
77
|
);
|
|
67
78
|
const removedFiles = new Set(options.resynchronization.removedFiles);
|
|
68
79
|
const failedFiles = new Set(options.resynchronization.failedFiles);
|
|
80
|
+
const observedAt = Date.now();
|
|
69
81
|
const documents = options.requestedFiles.map((file) => {
|
|
70
82
|
const uri = fileToUri(file);
|
|
71
83
|
const synchronization = synchronizationByFile.get(file);
|
|
@@ -84,7 +96,13 @@ export function buildDiagnosticRefreshEvidence(options: {
|
|
|
84
96
|
}
|
|
85
97
|
if (
|
|
86
98
|
synchronization &&
|
|
87
|
-
|
|
99
|
+
hasSettledRefreshEvidence({
|
|
100
|
+
store: options.diagnosticStore,
|
|
101
|
+
synchronization,
|
|
102
|
+
currentEvidenceRevision: options.currentEvidenceRevision,
|
|
103
|
+
pushQuietMs: options.pushQuietMs,
|
|
104
|
+
observedAt,
|
|
105
|
+
})
|
|
88
106
|
) {
|
|
89
107
|
return { file, status: "confirmed" as const };
|
|
90
108
|
}
|
|
@@ -96,6 +114,25 @@ export function buildDiagnosticRefreshEvidence(options: {
|
|
|
96
114
|
return summarizeDiagnosticEvidence(documents);
|
|
97
115
|
}
|
|
98
116
|
|
|
117
|
+
/** Test whether fresh evidence can confirm this refresh result. */
|
|
118
|
+
function hasSettledRefreshEvidence(options: {
|
|
119
|
+
store: ReadonlyMap<string, DiagnosticCacheEntry>;
|
|
120
|
+
synchronization: DiagnosticSynchronization;
|
|
121
|
+
currentEvidenceRevision: number;
|
|
122
|
+
pushQuietMs: number | undefined;
|
|
123
|
+
observedAt: number;
|
|
124
|
+
}): boolean {
|
|
125
|
+
if (!hasFreshEvidence(options.store, options.synchronization, options.currentEvidenceRevision)) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
const entry = options.store.get(options.synchronization.uri);
|
|
129
|
+
return (
|
|
130
|
+
options.pushQuietMs === undefined ||
|
|
131
|
+
entry?.source !== "push" ||
|
|
132
|
+
options.observedAt - entry.receivedAt >= options.pushQuietMs
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
99
136
|
/** Pull one document and reject evidence from a stale client generation. */
|
|
100
137
|
export function pullClientDiagnosticEvidence(
|
|
101
138
|
options: Omit<DiagnosticPullRequest, "previousResultId"> & {
|
|
@@ -214,6 +251,70 @@ export async function pullDiagnosticsForOpenDocuments(options: {
|
|
|
214
251
|
}
|
|
215
252
|
}
|
|
216
253
|
|
|
254
|
+
/**
|
|
255
|
+
* Classify open documents for a refresh by disk content.
|
|
256
|
+
*
|
|
257
|
+
* A document whose disk content still matches its open fingerprint stays in
|
|
258
|
+
* the server's current state:
|
|
259
|
+
* - with current evidence it is reusable without document synchronization;
|
|
260
|
+
* - without current evidence it is retained: it keeps its synchronization and
|
|
261
|
+
* collection waits for the server's existing pipeline instead of forcing a
|
|
262
|
+
* no-op didChange. Large push-only servers (typescript-language-server)
|
|
263
|
+
* skip empty-to-empty publishes, so a no-op didChange of a clean file can
|
|
264
|
+
* never confirm — it only invalidates in-progress evidence and restarts
|
|
265
|
+
* full-program checks (#344).
|
|
266
|
+
*
|
|
267
|
+
* Documents outside the current evidence revision were invalidated by a
|
|
268
|
+
* workspace change and must resynchronize: their stale revision cannot
|
|
269
|
+
* produce fresh evidence without an explicit sync (ADR 0020).
|
|
270
|
+
*/
|
|
271
|
+
function classifyReusableDocuments(options: {
|
|
272
|
+
openDocuments: ReadonlyMap<string, OpenDocumentState>;
|
|
273
|
+
diagnosticStore: ReadonlyMap<string, DiagnosticCacheEntry>;
|
|
274
|
+
evidenceRevision: number;
|
|
275
|
+
failedFiles: ReadonlySet<string>;
|
|
276
|
+
}): {
|
|
277
|
+
reusableUris: Set<string>;
|
|
278
|
+
retainedUris: Set<string>;
|
|
279
|
+
preloadedContent: Map<string, string>;
|
|
280
|
+
} {
|
|
281
|
+
const reusableUris = new Set<string>();
|
|
282
|
+
const retainedUris = new Set<string>();
|
|
283
|
+
const preloadedContent = new Map<string, string>();
|
|
284
|
+
for (const [uri, document] of options.openDocuments) {
|
|
285
|
+
const filePath = uriToFile(uri);
|
|
286
|
+
if (options.failedFiles.has(filePath)) continue;
|
|
287
|
+
let content: string;
|
|
288
|
+
try {
|
|
289
|
+
content = readFileSync(filePath, "utf-8");
|
|
290
|
+
} catch {
|
|
291
|
+
// The resynchronization path classifies removed and unreadable files.
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
if (document.evidenceRevision !== options.evidenceRevision) {
|
|
295
|
+
// Invalidated generation: the resync didChange re-establishes proof.
|
|
296
|
+
preloadedContent.set(uri, content);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (fingerprintDocumentContent(content) !== document.contentFingerprint) {
|
|
300
|
+
preloadedContent.set(uri, content);
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
if (
|
|
304
|
+
hasConfirmedDiagnosticEvidence(
|
|
305
|
+
document,
|
|
306
|
+
options.diagnosticStore.get(uri),
|
|
307
|
+
options.evidenceRevision,
|
|
308
|
+
)
|
|
309
|
+
) {
|
|
310
|
+
reusableUris.add(uri);
|
|
311
|
+
} else {
|
|
312
|
+
retainedUris.add(uri);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return { reusableUris, retainedUris, preloadedContent };
|
|
316
|
+
}
|
|
317
|
+
|
|
217
318
|
interface ClientDiagnosticRefreshOptions {
|
|
218
319
|
readonly host: ClientDiagnosticsHost;
|
|
219
320
|
readonly openDocuments: Map<string, OpenDocumentState>;
|
|
@@ -229,7 +330,98 @@ interface ClientDiagnosticRefreshOptions {
|
|
|
229
330
|
readonly invalidateEvidence: (uri: string) => void;
|
|
230
331
|
readonly markUnversionedSyncMoment: (uri: string) => void;
|
|
231
332
|
readonly clearFailedFile: (uri: string) => void;
|
|
333
|
+
/** Server-requested refreshes bypass normal push-only evidence reuse. */
|
|
334
|
+
readonly forceResynchronize?: boolean;
|
|
232
335
|
readonly options: { maxWaitMs?: number; quietMs?: number } & CodeRequestControl;
|
|
336
|
+
/** Push-publication telemetry surface for this client. */
|
|
337
|
+
readonly publications: {
|
|
338
|
+
emitSummary(options: {
|
|
339
|
+
readonly operation: "refresh-open";
|
|
340
|
+
readonly identity: DiagnosticPublicationIdentity;
|
|
341
|
+
readonly synchronizations: readonly DiagnosticPublicationSynchronization[];
|
|
342
|
+
readonly operationId?: string;
|
|
343
|
+
}): void;
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
interface PreparedRefreshDocuments {
|
|
348
|
+
readonly resynchronization: ResynchronizeDocumentsResult;
|
|
349
|
+
readonly synchronizations: DiagnosticSynchronization[];
|
|
350
|
+
readonly fullyReusable: boolean;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** Classify reusable documents, then resynchronize only the remaining set. */
|
|
354
|
+
function prepareRefreshDocuments(
|
|
355
|
+
options: ClientDiagnosticRefreshOptions,
|
|
356
|
+
supportsPull: boolean,
|
|
357
|
+
evidenceRevision: number,
|
|
358
|
+
): PreparedRefreshDocuments {
|
|
359
|
+
const reuseEnabled = !options.forceResynchronize;
|
|
360
|
+
const classification = reuseEnabled
|
|
361
|
+
? classifyReusableDocuments({
|
|
362
|
+
openDocuments: options.openDocuments,
|
|
363
|
+
diagnosticStore: options.diagnosticStore,
|
|
364
|
+
evidenceRevision,
|
|
365
|
+
failedFiles: options.failedFiles(),
|
|
366
|
+
})
|
|
367
|
+
: undefined;
|
|
368
|
+
const reusableUris = classification?.reusableUris ?? new Set<string>();
|
|
369
|
+
const retainedUris = classification?.retainedUris ?? new Set<string>();
|
|
370
|
+
const documentsToResynchronize = new Map(
|
|
371
|
+
Array.from(options.openDocuments).filter(
|
|
372
|
+
([uri]) => !reusableUris.has(uri) && !retainedUris.has(uri),
|
|
373
|
+
),
|
|
374
|
+
);
|
|
375
|
+
const resynchronization = resynchronizeOpenDocuments({
|
|
376
|
+
openDocuments: documentsToResynchronize,
|
|
377
|
+
waiters: options.waiters,
|
|
378
|
+
nextVersion: (uri) => nextDocumentVersion(options.versionHistory, uri),
|
|
379
|
+
nextSynchronizationId: options.nextSynchronizationId,
|
|
380
|
+
evidenceRevision,
|
|
381
|
+
incrementalSync: options.host.usesIncrementalDocumentSync(),
|
|
382
|
+
sendNotification: (method, params) => options.host.sendNotification(method, params),
|
|
383
|
+
uriToFile,
|
|
384
|
+
preloadedContent: classification?.preloadedContent,
|
|
385
|
+
clearFile: options.clearFile,
|
|
386
|
+
invalidateEvidence: options.invalidateEvidence,
|
|
387
|
+
markUnversionedSyncMoment: options.markUnversionedSyncMoment,
|
|
388
|
+
clearFailedFile: options.clearFailedFile,
|
|
389
|
+
});
|
|
390
|
+
const reusableSynchronizations: DiagnosticSynchronization[] = [];
|
|
391
|
+
for (const uri of reusableUris) {
|
|
392
|
+
const document = options.openDocuments.get(uri);
|
|
393
|
+
if (!document) continue;
|
|
394
|
+
reusableSynchronizations.push({
|
|
395
|
+
uri,
|
|
396
|
+
synchronizationId: document.synchronizationId,
|
|
397
|
+
evidenceRevision: document.evidenceRevision,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
// Retained documents keep their current synchronization: settle waits for
|
|
401
|
+
// the server's existing pipeline without any protocol work.
|
|
402
|
+
const retainedSynchronizations: DiagnosticSynchronization[] = [];
|
|
403
|
+
for (const uri of retainedUris) {
|
|
404
|
+
const document = options.openDocuments.get(uri);
|
|
405
|
+
if (!document) continue;
|
|
406
|
+
retainedSynchronizations.push({
|
|
407
|
+
uri,
|
|
408
|
+
synchronizationId: document.synchronizationId,
|
|
409
|
+
evidenceRevision: document.evidenceRevision,
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
const synchronizations = [
|
|
413
|
+
...reusableSynchronizations,
|
|
414
|
+
...retainedSynchronizations,
|
|
415
|
+
...resynchronization.synchronizations,
|
|
416
|
+
];
|
|
417
|
+
// Pull-capable routes still request current diagnostics even when every
|
|
418
|
+
// document synchronization is reusable.
|
|
419
|
+
const fullyReusable =
|
|
420
|
+
reuseEnabled &&
|
|
421
|
+
!supportsPull &&
|
|
422
|
+
options.openDocuments.size > 0 &&
|
|
423
|
+
reusableUris.size === options.openDocuments.size;
|
|
424
|
+
return { resynchronization, synchronizations, fullyReusable };
|
|
233
425
|
}
|
|
234
426
|
|
|
235
427
|
/** Refresh one client and return exact document evidence for that attempt. */
|
|
@@ -259,25 +451,11 @@ export async function refreshClientOpenDiagnostics(
|
|
|
259
451
|
const maxWaitMs = options.options.maxWaitMs ?? 3_000;
|
|
260
452
|
const quietMs = options.options.quietMs ?? 200;
|
|
261
453
|
const syncStart = Date.now();
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
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;
|
|
454
|
+
const prepared = prepareRefreshDocuments(options, supportsPull, options.evidenceRevision());
|
|
455
|
+
const resynchronization = prepared.resynchronization;
|
|
456
|
+
let synchronizations = prepared.synchronizations;
|
|
279
457
|
let failedPullUris: ReadonlySet<string> = new Set();
|
|
280
|
-
const buildEvidence = () =>
|
|
458
|
+
const buildEvidence = (pushQuietMs?: number) =>
|
|
281
459
|
buildDiagnosticRefreshEvidence({
|
|
282
460
|
requestedFiles: options.requestedFiles,
|
|
283
461
|
resynchronization,
|
|
@@ -288,8 +466,19 @@ export async function refreshClientOpenDiagnostics(
|
|
|
288
466
|
currentEvidenceRevision: options.evidenceRevision(),
|
|
289
467
|
openDocuments: options.openDocuments,
|
|
290
468
|
diagnosticStore: options.diagnosticStore,
|
|
469
|
+
pushQuietMs,
|
|
291
470
|
});
|
|
292
|
-
|
|
471
|
+
|
|
472
|
+
// A fully reusable push-only refresh has no protocol work to collect.
|
|
473
|
+
// Preserve the existing cache timing event format and return current evidence.
|
|
474
|
+
if (prepared.fullyReusable) {
|
|
475
|
+
observer.cacheReused(synchronizations.length);
|
|
476
|
+
return buildEvidence();
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const settleEpoch = options.waiters.settleEpoch;
|
|
480
|
+
observer.synchronized();
|
|
481
|
+
if (synchronizations.length === 0) {
|
|
293
482
|
observer.skipped(0);
|
|
294
483
|
return buildEvidence();
|
|
295
484
|
}
|
|
@@ -306,60 +495,64 @@ export async function refreshClientOpenDiagnostics(
|
|
|
306
495
|
if (pull.completed) return buildEvidence();
|
|
307
496
|
}
|
|
308
497
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
498
|
+
const waitForDiagnosticSettle = (settleStart: number, settleGeneration: number) =>
|
|
499
|
+
options.waiters.waitForSettle(
|
|
500
|
+
{
|
|
501
|
+
syncStart: settleStart,
|
|
502
|
+
maxWaitMs,
|
|
503
|
+
quietMs,
|
|
504
|
+
settleEpoch: settleGeneration,
|
|
505
|
+
isComplete: () =>
|
|
506
|
+
synchronizations.every((item) =>
|
|
507
|
+
hasFreshEvidence(options.diagnosticStore, item, options.evidenceRevision()),
|
|
508
|
+
),
|
|
509
|
+
latestReceived: () =>
|
|
510
|
+
latestCurrentEvidenceReceivedAt(
|
|
511
|
+
options.diagnosticStore,
|
|
512
|
+
synchronizations,
|
|
513
|
+
options.evidenceRevision(),
|
|
514
|
+
),
|
|
515
|
+
},
|
|
516
|
+
options.options,
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
let finalSettle = await waitForDiagnosticSettle(syncStart, settleEpoch);
|
|
328
520
|
if (!supportsPull && finalSettle.outcome === "timed-out") {
|
|
329
521
|
const reopen = await reopenUnconfirmedDocuments({
|
|
330
522
|
options,
|
|
331
523
|
synchronizations,
|
|
332
|
-
|
|
333
|
-
quietMs,
|
|
524
|
+
reopenCandidates: resynchronization.resynchronizedUris,
|
|
334
525
|
observer,
|
|
335
526
|
});
|
|
336
527
|
if (reopen.performed) {
|
|
337
528
|
synchronizations = reopen.synchronizations;
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
);
|
|
529
|
+
// A large push-only project may still be processing the reopen batch.
|
|
530
|
+
// The replacement pass uses the same collection budget as the initial pass.
|
|
531
|
+
finalSettle = await waitForDiagnosticSettle(reopen.startedAt, options.waiters.settleEpoch);
|
|
357
532
|
}
|
|
358
533
|
}
|
|
359
|
-
observer.pushSettled(
|
|
534
|
+
observer.pushSettled(synchronizations.length, finalSettle);
|
|
360
535
|
// A cancelled settle must not publish evidence the caller no longer awaits.
|
|
361
536
|
throwIfCodeRequestInterrupted(options.options);
|
|
362
|
-
|
|
537
|
+
options.publications.emitSummary({
|
|
538
|
+
operation: "refresh-open",
|
|
539
|
+
identity: {
|
|
540
|
+
server: options.host.server,
|
|
541
|
+
cwd: options.host.cwd,
|
|
542
|
+
},
|
|
543
|
+
synchronizations: synchronizations.map((synchronization) => ({
|
|
544
|
+
uri: synchronization.uri,
|
|
545
|
+
synchronizationId: synchronization.synchronizationId,
|
|
546
|
+
evidenceRevision: synchronization.evidenceRevision ?? options.evidenceRevision(),
|
|
547
|
+
confirmed: hasFreshEvidence(
|
|
548
|
+
options.diagnosticStore,
|
|
549
|
+
synchronization,
|
|
550
|
+
options.evidenceRevision(),
|
|
551
|
+
),
|
|
552
|
+
})),
|
|
553
|
+
operationId: options.options.operationId,
|
|
554
|
+
});
|
|
555
|
+
return buildEvidence(quietMs);
|
|
363
556
|
}
|
|
364
557
|
|
|
365
558
|
/**
|
|
@@ -420,31 +613,38 @@ async function collectPullEvidenceForRefresh(options: {
|
|
|
420
613
|
}
|
|
421
614
|
|
|
422
615
|
/**
|
|
423
|
-
* Reopen-resync fallback (R2): on push-only routes
|
|
424
|
-
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
*
|
|
616
|
+
* Reopen-resync fallback (R2): on push-only routes a document that was
|
|
617
|
+
* didChange-synchronized and stays unconfirmed after the settle window may
|
|
618
|
+
* have been skipped by the server — a clean file gets no push on didChange
|
|
619
|
+
* at all, but the server publishes on didOpen. Close and reopen each such
|
|
620
|
+
* document so the server publishes, then settle again within a bounded
|
|
621
|
+
* second window. The cache entry and version history survive the reopen;
|
|
622
|
+
* other documents keep their server state.
|
|
623
|
+
*
|
|
624
|
+
* Only documents this pass resynchronized are candidates: retained documents
|
|
625
|
+
* wait for the server's existing pipeline, and reopening them would cancel
|
|
626
|
+
* in-progress server work without fixing any publish gap (#344).
|
|
430
627
|
*/
|
|
431
628
|
async function reopenUnconfirmedDocuments(options: {
|
|
432
629
|
options: ClientDiagnosticRefreshOptions;
|
|
433
630
|
synchronizations: readonly DiagnosticSynchronization[];
|
|
434
|
-
|
|
435
|
-
|
|
631
|
+
/** URIs that received a didChange in this pass and may need a reopen push. */
|
|
632
|
+
reopenCandidates: ReadonlySet<string>;
|
|
436
633
|
observer: DiagnosticObserver;
|
|
437
634
|
}): Promise<{
|
|
438
635
|
performed: boolean;
|
|
439
636
|
startedAt: number;
|
|
440
|
-
budgetMs: number;
|
|
441
637
|
synchronizations: DiagnosticSynchronization[];
|
|
442
638
|
}> {
|
|
443
|
-
const { options: refresh, synchronizations,
|
|
639
|
+
const { options: refresh, synchronizations, reopenCandidates, observer } = options;
|
|
444
640
|
const startedAt = Date.now();
|
|
445
|
-
const budgetMs = Math.min(maxWaitMs, quietMs * 4);
|
|
446
641
|
const unconfirmed = synchronizations.filter(
|
|
447
|
-
(item) =>
|
|
642
|
+
(item) =>
|
|
643
|
+
reopenCandidates.has(item.uri) &&
|
|
644
|
+
// A document with any current publication — tentative included — is
|
|
645
|
+
// not a reopen candidate: its server pipeline is alive and a republish
|
|
646
|
+
// can still promote the retained cache (ADR 0021).
|
|
647
|
+
!hasCurrentEvidence(refresh.diagnosticStore, item, refresh.evidenceRevision()),
|
|
448
648
|
);
|
|
449
649
|
const reopenedSynchronizations: DiagnosticSynchronization[] = [];
|
|
450
650
|
for (const item of unconfirmed) {
|
|
@@ -478,14 +678,13 @@ async function reopenUnconfirmedDocuments(options: {
|
|
|
478
678
|
});
|
|
479
679
|
}
|
|
480
680
|
if (reopenedSynchronizations.length === 0) {
|
|
481
|
-
return { performed: false, startedAt,
|
|
681
|
+
return { performed: false, startedAt, synchronizations: [...synchronizations] };
|
|
482
682
|
}
|
|
483
683
|
observer.reopened(reopenedSynchronizations.length);
|
|
484
684
|
const reopenedByUri = new Map(reopenedSynchronizations.map((item) => [item.uri, item]));
|
|
485
685
|
return {
|
|
486
686
|
performed: true,
|
|
487
687
|
startedAt,
|
|
488
|
-
budgetMs,
|
|
489
688
|
synchronizations: synchronizations.map((item) => reopenedByUri.get(item.uri) ?? item),
|
|
490
689
|
};
|
|
491
690
|
}
|
|
@@ -6,8 +6,20 @@ type DiagnosticCollection = "cache" | "fallback" | "none" | "pull" | "push";
|
|
|
6
6
|
type DiagnosticFreshness = "not-observed" | "observed";
|
|
7
7
|
type DiagnosticOutcome = "completed" | "incomplete" | "skipped" | "timed-out";
|
|
8
8
|
type DiagnosticPullOutcome = "completed" | "failed" | "not-supported" | "not-used" | "timed-out";
|
|
9
|
-
type DiagnosticPushOutcome =
|
|
10
|
-
|
|
9
|
+
type DiagnosticPushOutcome =
|
|
10
|
+
| "not-used"
|
|
11
|
+
| "published"
|
|
12
|
+
| "tentative"
|
|
13
|
+
| "released"
|
|
14
|
+
| "settled"
|
|
15
|
+
| "timed-out";
|
|
16
|
+
type DiagnosticSettleOutcome =
|
|
17
|
+
| "not-used"
|
|
18
|
+
| "published"
|
|
19
|
+
| "tentative"
|
|
20
|
+
| "quiet"
|
|
21
|
+
| "released"
|
|
22
|
+
| "timed-out";
|
|
11
23
|
type DiagnosticTimingOperation = "refresh-open" | "sync-file";
|
|
12
24
|
|
|
13
25
|
/** Result of waiting for a quiet push-diagnostic window. */
|
|
@@ -17,7 +29,7 @@ export interface DiagnosticSettleResult {
|
|
|
17
29
|
}
|
|
18
30
|
|
|
19
31
|
/** Result of waiting for one file's push diagnostics. */
|
|
20
|
-
export type DiagnosticPushWaitOutcome = "published" | "released" | "timed-out";
|
|
32
|
+
export type DiagnosticPushWaitOutcome = "published" | "tentative" | "released" | "timed-out";
|
|
21
33
|
|
|
22
34
|
interface DiagnosticTimingData {
|
|
23
35
|
readonly collection: DiagnosticCollection;
|
|
@@ -134,10 +146,15 @@ export class DiagnosticObserver {
|
|
|
134
146
|
this.#timer.mark("pull");
|
|
135
147
|
}
|
|
136
148
|
|
|
137
|
-
/**
|
|
149
|
+
/**
|
|
150
|
+
* Record that the reopen-resync fallback re-opened unconfirmed documents.
|
|
151
|
+
*
|
|
152
|
+
* The mark fires when the second settle window starts, so the measured
|
|
153
|
+
* phase is the preceding first settle window, not the reopen work.
|
|
154
|
+
*/
|
|
138
155
|
reopened(count: number): void {
|
|
139
156
|
this.#reopened += count;
|
|
140
|
-
this.#timer.mark("
|
|
157
|
+
this.#timer.mark("first-settle");
|
|
141
158
|
}
|
|
142
159
|
|
|
143
160
|
pushSettled(documentCount: number, settle: DiagnosticSettleResult): void {
|
|
@@ -161,14 +178,18 @@ export class DiagnosticObserver {
|
|
|
161
178
|
}
|
|
162
179
|
|
|
163
180
|
pushWaitCompleted(documentCount: number, push: DiagnosticPushWaitOutcome): void {
|
|
164
|
-
const
|
|
181
|
+
const confirmed = push === "published";
|
|
182
|
+
// A tentative result used the full wait budget without a confirming
|
|
183
|
+
// republish. Keep its distinct push value and report the budget expiry.
|
|
184
|
+
const timedOut = push === "tentative" || push === "timed-out";
|
|
185
|
+
const observed = push === "published" || push === "tentative";
|
|
165
186
|
this.#finish(
|
|
166
187
|
{
|
|
167
188
|
collection: this.supportsPull ? "fallback" : "push",
|
|
168
189
|
documentCount,
|
|
169
190
|
fallback: this.supportsPull,
|
|
170
|
-
freshness:
|
|
171
|
-
outcome:
|
|
191
|
+
freshness: observed ? "observed" : "not-observed",
|
|
192
|
+
outcome: confirmed ? "completed" : timedOut ? "timed-out" : "incomplete",
|
|
172
193
|
pull: this.#pull,
|
|
173
194
|
push,
|
|
174
195
|
reopen: this.#reopened,
|