@mrclrchtr/supi-code-intelligence 5.0.0 → 6.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 -2
- package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- 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 +34 -3
- 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/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/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- 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/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
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
// Bounded push-publication telemetry for one client's diagnostic state.
|
|
2
|
+
//
|
|
3
|
+
// Push-only servers can publish an early result and a later semantic result
|
|
4
|
+
// for one document synchronization (ADR 0021). This tracker records one
|
|
5
|
+
// bounded per-synchronization publication summary per diagnostic operation
|
|
6
|
+
// and one ambient event when a later publication promotes a synchronization
|
|
7
|
+
// that an earlier operation already reported unconfirmed. Events carry only
|
|
8
|
+
// bounded server, workspace, relative-file, synchronization identity, count,
|
|
9
|
+
// and timing data. They never carry diagnostic payloads or source text.
|
|
10
|
+
|
|
11
|
+
import * as path from "node:path";
|
|
12
|
+
import { recordDebugEvent } from "@mrclrchtr/supi-core/debug";
|
|
13
|
+
import { boundCwd, truncateIdentity } from "../debug-telemetry.ts";
|
|
14
|
+
import { uriToFile } from "../utils.ts";
|
|
15
|
+
|
|
16
|
+
/** Maximum tracked synchronizations before the oldest entry is evicted. */
|
|
17
|
+
export const MAX_TRACKED_SYNCHRONIZATIONS = 64;
|
|
18
|
+
|
|
19
|
+
/** Maximum synchronization entries in one bounded publication summary. */
|
|
20
|
+
export const MAX_SUMMARY_SYNCHRONIZATIONS = 16;
|
|
21
|
+
|
|
22
|
+
/** Maximum publication count retained for telemetry; 2 means two or more. */
|
|
23
|
+
const MAX_PUBLICATIONS = 2;
|
|
24
|
+
|
|
25
|
+
/** Bounded identity for one publication telemetry observation. */
|
|
26
|
+
export interface DiagnosticPublicationIdentity {
|
|
27
|
+
/** Configured server name. */
|
|
28
|
+
readonly server?: string;
|
|
29
|
+
/** Absolute workspace root. */
|
|
30
|
+
readonly cwd?: string;
|
|
31
|
+
/** Workspace-relative file path; sync-file operations only. */
|
|
32
|
+
readonly file?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** One awaited synchronization of a completed diagnostic operation. */
|
|
36
|
+
export interface DiagnosticPublicationSynchronization {
|
|
37
|
+
readonly uri: string;
|
|
38
|
+
readonly synchronizationId: number;
|
|
39
|
+
readonly evidenceRevision: number;
|
|
40
|
+
readonly confirmed: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** One bounded per-synchronization publication summary entry. */
|
|
44
|
+
export interface DiagnosticPublicationSummaryEntry {
|
|
45
|
+
readonly synchronizationId: number;
|
|
46
|
+
readonly publications: number;
|
|
47
|
+
readonly firstReceivedAt: number;
|
|
48
|
+
readonly lastReceivedAt: number;
|
|
49
|
+
readonly confirmed: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface SynchronizationPublicationState {
|
|
53
|
+
readonly uri: string;
|
|
54
|
+
readonly synchronizationId: number;
|
|
55
|
+
readonly evidenceRevision: number;
|
|
56
|
+
publications: number;
|
|
57
|
+
firstReceivedAt: number;
|
|
58
|
+
lastReceivedAt: number;
|
|
59
|
+
/** When a finished operation first reported this synchronization unconfirmed. */
|
|
60
|
+
unconfirmedAt?: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function synchronizationKey(
|
|
64
|
+
uri: string,
|
|
65
|
+
synchronizationId: number,
|
|
66
|
+
evidenceRevision: number,
|
|
67
|
+
): string {
|
|
68
|
+
return `${uri}|${synchronizationId}|${evidenceRevision}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Track bounded push-publication counts for one LSP client. */
|
|
72
|
+
export class DiagnosticPublicationTracker {
|
|
73
|
+
readonly #states = new Map<string, SynchronizationPublicationState>();
|
|
74
|
+
|
|
75
|
+
constructor(
|
|
76
|
+
private readonly identity: { server?: string; cwd?: string },
|
|
77
|
+
private readonly fileFor: (uri: string) => string | undefined,
|
|
78
|
+
) {}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Record one accepted push publication for a synchronization.
|
|
82
|
+
*
|
|
83
|
+
* The state map is bounded to {@link MAX_TRACKED_SYNCHRONIZATIONS}; the
|
|
84
|
+
* oldest tracked synchronization is evicted when the bound is exceeded.
|
|
85
|
+
*/
|
|
86
|
+
record(
|
|
87
|
+
uri: string,
|
|
88
|
+
synchronizationId: number,
|
|
89
|
+
evidenceRevision: number,
|
|
90
|
+
receivedAt: number = Date.now(),
|
|
91
|
+
): void {
|
|
92
|
+
const key = synchronizationKey(uri, synchronizationId, evidenceRevision);
|
|
93
|
+
const existing = this.#states.get(key);
|
|
94
|
+
if (existing) {
|
|
95
|
+
existing.publications = Math.min(existing.publications + 1, MAX_PUBLICATIONS);
|
|
96
|
+
existing.lastReceivedAt = receivedAt;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
this.#states.set(key, {
|
|
100
|
+
uri,
|
|
101
|
+
synchronizationId,
|
|
102
|
+
evidenceRevision,
|
|
103
|
+
publications: 1,
|
|
104
|
+
firstReceivedAt: receivedAt,
|
|
105
|
+
lastReceivedAt: receivedAt,
|
|
106
|
+
});
|
|
107
|
+
if (this.#states.size > MAX_TRACKED_SYNCHRONIZATIONS) {
|
|
108
|
+
const oldest = this.#states.keys().next().value;
|
|
109
|
+
if (oldest !== undefined) this.#states.delete(oldest);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Emit one bounded per-synchronization publication summary.
|
|
115
|
+
*
|
|
116
|
+
* Synchronizations with no observed push publication are omitted. A
|
|
117
|
+
* synchronization that ends unconfirmed is marked so a later promotion can
|
|
118
|
+
* emit the ambient late-republish event.
|
|
119
|
+
*/
|
|
120
|
+
emitSummary(options: {
|
|
121
|
+
readonly operation: "refresh-open" | "sync-file";
|
|
122
|
+
readonly identity: DiagnosticPublicationIdentity;
|
|
123
|
+
readonly synchronizations: readonly DiagnosticPublicationSynchronization[];
|
|
124
|
+
readonly operationId?: string;
|
|
125
|
+
}): void {
|
|
126
|
+
const entries: DiagnosticPublicationSummaryEntry[] = [];
|
|
127
|
+
const now = Date.now();
|
|
128
|
+
for (const synchronization of options.synchronizations) {
|
|
129
|
+
const state = this.#states.get(
|
|
130
|
+
synchronizationKey(
|
|
131
|
+
synchronization.uri,
|
|
132
|
+
synchronization.synchronizationId,
|
|
133
|
+
synchronization.evidenceRevision,
|
|
134
|
+
),
|
|
135
|
+
);
|
|
136
|
+
if (!state) continue;
|
|
137
|
+
entries.push({
|
|
138
|
+
synchronizationId: state.synchronizationId,
|
|
139
|
+
publications: state.publications,
|
|
140
|
+
firstReceivedAt: state.firstReceivedAt,
|
|
141
|
+
lastReceivedAt: state.lastReceivedAt,
|
|
142
|
+
confirmed: synchronization.confirmed,
|
|
143
|
+
});
|
|
144
|
+
if (!synchronization.confirmed) state.unconfirmedAt ??= now;
|
|
145
|
+
else state.unconfirmedAt = undefined;
|
|
146
|
+
}
|
|
147
|
+
if (entries.length === 0) return;
|
|
148
|
+
recordDebugEvent({
|
|
149
|
+
operationId: options.operationId,
|
|
150
|
+
source: "lsp",
|
|
151
|
+
level: "debug",
|
|
152
|
+
category: "diagnostics.publication",
|
|
153
|
+
message: "LSP diagnostic publication summary",
|
|
154
|
+
cwd: boundCwd(options.identity.cwd),
|
|
155
|
+
data: {
|
|
156
|
+
operation: options.operation,
|
|
157
|
+
synchronizations: entries.slice(0, MAX_SUMMARY_SYNCHRONIZATIONS),
|
|
158
|
+
...(options.identity.server !== undefined
|
|
159
|
+
? { server: truncateIdentity(options.identity.server) }
|
|
160
|
+
: {}),
|
|
161
|
+
...(options.identity.file !== undefined
|
|
162
|
+
? { file: truncateIdentity(options.identity.file) }
|
|
163
|
+
: {}),
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Observe one promotion of a synchronization by a later publication.
|
|
170
|
+
*
|
|
171
|
+
* The ambient late-republish event fires only when a finished operation
|
|
172
|
+
* previously reported the synchronization unconfirmed. The mark is cleared
|
|
173
|
+
* so one promotion emits at most one event.
|
|
174
|
+
*/
|
|
175
|
+
promoted(
|
|
176
|
+
uri: string,
|
|
177
|
+
synchronizationId: number,
|
|
178
|
+
evidenceRevision: number,
|
|
179
|
+
receivedAt: number = Date.now(),
|
|
180
|
+
): void {
|
|
181
|
+
const state = this.#states.get(synchronizationKey(uri, synchronizationId, evidenceRevision));
|
|
182
|
+
const unconfirmedAt = state?.unconfirmedAt;
|
|
183
|
+
if (!state || state.publications < 2 || unconfirmedAt === undefined) return;
|
|
184
|
+
state.unconfirmedAt = undefined;
|
|
185
|
+
const file = this.fileFor(uri);
|
|
186
|
+
recordDebugEvent({
|
|
187
|
+
source: "lsp",
|
|
188
|
+
level: "debug",
|
|
189
|
+
category: "diagnostics.publication",
|
|
190
|
+
message: "LSP diagnostic late republish",
|
|
191
|
+
cwd: boundCwd(this.identity.cwd),
|
|
192
|
+
data: {
|
|
193
|
+
synchronizationId,
|
|
194
|
+
publications: state.publications,
|
|
195
|
+
receivedAt,
|
|
196
|
+
delayMs: Math.max(0, receivedAt - unconfirmedAt),
|
|
197
|
+
...(this.identity.server !== undefined
|
|
198
|
+
? { server: truncateIdentity(this.identity.server) }
|
|
199
|
+
: {}),
|
|
200
|
+
...(file !== undefined ? { file: truncateIdentity(file) } : {}),
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Return a workspace-relative diagnostic file path for telemetry identity. */
|
|
207
|
+
export function trackerFileIdentityFor(
|
|
208
|
+
cwd: string | undefined,
|
|
209
|
+
): (uri: string) => string | undefined {
|
|
210
|
+
return (uri) => {
|
|
211
|
+
if (cwd === undefined) return undefined;
|
|
212
|
+
return path.relative(cwd, path.resolve(cwd, uriToFile(uri)));
|
|
213
|
+
};
|
|
214
|
+
}
|
|
@@ -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
|
}
|