@mrclrchtr/supi-code-intelligence 6.3.0 → 7.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.
Files changed (136) hide show
  1. package/README.md +164 -82
  2. package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  3. package/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +8 -1
  4. package/node_modules/@mrclrchtr/supi-core/README.md +8 -0
  5. package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  6. package/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +10 -3
  7. package/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
  8. package/node_modules/@mrclrchtr/supi-core/src/llm.ts +141 -16
  9. package/node_modules/@mrclrchtr/supi-lsp/README.md +35 -13
  10. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  11. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +8 -1
  12. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +8 -0
  13. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  14. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +10 -3
  15. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
  16. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/llm.ts +141 -16
  17. package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/index.js +3 -1
  18. package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/legacy.js +3 -1
  19. package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/package.json +1 -1
  20. package/node_modules/@mrclrchtr/supi-lsp/package.json +3 -3
  21. package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +16 -0
  22. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-cache.ts +43 -35
  23. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-capabilities.ts +83 -17
  24. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-collection.ts +108 -92
  25. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-evidence.ts +44 -82
  26. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-host.ts +3 -4
  27. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-publication.ts +9 -68
  28. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-pull.ts +45 -58
  29. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-refresh.ts +230 -331
  30. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-request.ts +201 -1
  31. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-timing.ts +129 -70
  32. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-typescript.ts +395 -0
  33. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-waiters.ts +8 -12
  34. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostics.ts +685 -183
  35. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-state.ts +2 -2
  36. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-sync.ts +60 -60
  37. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-request-enrollment.ts +75 -0
  38. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-semantic-input-barrier.ts +443 -0
  39. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-semantic-input-errors.ts +30 -0
  40. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-semantic-input-file.ts +15 -0
  41. package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +290 -37
  42. package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +72 -16
  43. package/node_modules/@mrclrchtr/supi-lsp/src/config/capabilities.ts +1 -0
  44. package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-scope.ts +30 -4
  45. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/evidence.ts +1 -1
  46. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/workspace-sentinels.ts +4 -32
  47. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/workspace-sources.ts +108 -0
  48. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +10 -4
  49. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-process-crash-report.ts +50 -1
  50. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +11 -7
  51. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +770 -56
  52. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostic-surface.ts +3 -1
  53. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +37 -4
  54. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +48 -12
  55. package/node_modules/@mrclrchtr/supi-lsp/src/session/workspace-lsp-runtime.ts +50 -4
  56. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  57. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +8 -1
  58. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/README.md +8 -0
  59. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  60. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +10 -3
  61. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
  62. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/llm.ts +141 -16
  63. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -3
  64. package/node_modules/@mrclrchtr/supi-tree-sitter/src/provider/tree-sitter-provider.ts +2 -0
  65. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callee-display-name.ts +165 -0
  66. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callees.ts +67 -9
  67. package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +2 -0
  68. package/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +19 -18
  69. package/node_modules/yaml/browser/dist/nodes/Alias.js +25 -23
  70. package/node_modules/yaml/dist/compose/resolve-flow-scalar.js +19 -18
  71. package/node_modules/yaml/dist/nodes/Alias.js +25 -23
  72. package/node_modules/yaml/package.json +1 -1
  73. package/package.json +5 -5
  74. package/src/analysis/health/diagnostics.ts +1 -2
  75. package/src/analysis/health/file-scope.ts +4 -1
  76. package/src/analysis/health/server-status.ts +1 -1
  77. package/src/analysis/relations/callees.ts +17 -8
  78. package/src/analysis/relations/implementations.ts +1 -2
  79. package/src/analysis/relations/references.ts +8 -4
  80. package/src/analysis/relations/types.ts +6 -30
  81. package/src/analysis/target/anchored.ts +9 -1
  82. package/src/analysis/target/symbol.ts +29 -11
  83. package/src/analysis/target/types.ts +14 -6
  84. package/src/api.ts +2 -0
  85. package/src/session/graph/collect.ts +18 -3
  86. package/src/session/health-refresh.ts +27 -11
  87. package/src/session/health-types.ts +21 -4
  88. package/src/session/health-workflow.ts +28 -22
  89. package/src/session/orientation-types.ts +5 -6
  90. package/src/session/orientation-workflow.ts +10 -2
  91. package/src/session/session.ts +12 -5
  92. package/src/session/target-workflow.ts +9 -6
  93. package/src/substrate/lsp/maintenance.ts +53 -47
  94. package/src/substrate/lsp/source-tracking.ts +252 -0
  95. package/src/tool/code_find/guidance.ts +3 -3
  96. package/src/tool/code_find/spec.ts +5 -4
  97. package/src/tool/code_find/tui.ts +7 -1
  98. package/src/tool/code_graph/details.ts +32 -0
  99. package/src/tool/code_graph/display.ts +54 -0
  100. package/src/tool/code_graph/format.ts +60 -0
  101. package/src/tool/code_graph/guidance.ts +2 -2
  102. package/src/tool/code_graph/markdown.ts +76 -198
  103. package/src/tool/code_graph/read-next.ts +65 -0
  104. package/src/tool/code_graph/result.ts +118 -83
  105. package/src/tool/code_graph/tui.ts +216 -54
  106. package/src/tool/code_health/guidance.ts +3 -5
  107. package/src/tool/code_health/markdown.ts +90 -57
  108. package/src/tool/code_health/refresh-outcome.ts +51 -0
  109. package/src/tool/code_health/refresh-status.ts +130 -43
  110. package/src/tool/code_health/scope.ts +78 -0
  111. package/src/tool/code_health/spec.ts +1 -1
  112. package/src/tool/code_health/tui.ts +29 -38
  113. package/src/tool/code_inspect/guidance.ts +5 -3
  114. package/src/tool/code_inspect/tui.ts +7 -1
  115. package/src/tool/code_orientation/guidance.ts +3 -4
  116. package/src/tool/code_orientation/result.ts +31 -8
  117. package/src/tool/code_orientation/tui.ts +26 -5
  118. package/src/tool/code_refactor_apply/guidance.ts +1 -1
  119. package/src/tool/code_refactor_apply/tui.ts +7 -3
  120. package/src/tool/code_refactor_plan/guidance.ts +3 -5
  121. package/src/tool/code_refactor_plan/tui.ts +7 -1
  122. package/src/tool/code_resolve/guidance.ts +3 -5
  123. package/src/tool/code_resolve/result.ts +13 -10
  124. package/src/tool/code_resolve/tui.ts +18 -1
  125. package/src/tool/guidance.ts +11 -9
  126. package/src/tool/infra/truncate.ts +39 -13
  127. package/src/tool/result/candidate-row.ts +27 -0
  128. package/src/tool/result/errors.ts +21 -6
  129. package/src/tool/result/types.ts +7 -3
  130. package/src/tool/schemas.ts +5 -0
  131. package/src/types/execution.ts +2 -0
  132. package/src/types/index.ts +2 -0
  133. package/src/ui/tui/candidate-legacy-body.ts +69 -0
  134. package/src/ui/tui/candidate-selection.ts +366 -0
  135. package/src/ui/tui/common.ts +26 -31
  136. package/src/ui/tui/execution-error.ts +92 -0
@@ -1,3 +1,4 @@
1
+ // biome-ignore-all lint/style/noExcessiveLinesPerFile: Diagnostic validation and evidence application stay together.
1
2
  import {
2
3
  type CodeQueryResult,
3
4
  partialCodeQuery,
@@ -8,7 +9,6 @@ import type {
8
9
  DocumentDiagnosticReport,
9
10
  PublishDiagnosticsParams,
10
11
  } from "../config/types.ts";
11
- import type { DiagnosticStateWait } from "./client-diagnostic-waiters.ts";
12
12
 
13
13
  /** Validate one untrusted LSP diagnostic publication before it enters the cache. */
14
14
  export function isValidPublishDiagnosticsParams(value: unknown): value is PublishDiagnosticsParams {
@@ -154,34 +154,33 @@ function isRecord(value: unknown): value is Record<string, unknown> {
154
154
  return typeof value === "object" && value !== null && !Array.isArray(value);
155
155
  }
156
156
 
157
+ /** Evidence source that established or observed one cached diagnostic set. */
158
+ export type DiagnosticEvidenceSource = "pull" | "typescript" | "push";
159
+
157
160
  /** Stored diagnostic data and the protocol evidence that established it. */
158
161
  export interface DiagnosticCacheEntry {
159
162
  diagnostics: Diagnostic[];
160
163
  receivedAt: number;
161
- source: "pull" | "push";
164
+ source: DiagnosticEvidenceSource;
162
165
  synchronizationId?: number;
163
166
  evidenceRevision?: number;
164
167
  version?: number;
165
168
  resultId?: string;
166
169
  /**
167
- * Valid publications observed for the entry's synchronization. The first
168
- * valid publication for a synchronization is tentative; a later valid
169
- * publication promotes the entry to confirmed (ADR 0021). A confirmed
170
- * pull entry counts as the first publication when a push continues the
171
- * same synchronization, so that push keeps the confirmation. Pull
172
- * entries as such carry no count and are confirmed on arrival.
170
+ * Number of ambient publications retained for compatibility with existing
171
+ * telemetry. Publication count never establishes confirmation.
173
172
  */
174
173
  publications?: number;
175
174
  }
176
175
 
177
176
  /**
178
- * Test whether one push entry is still tentative for its synchronization.
177
+ * Test whether one entry came from ambient push observation.
179
178
  *
180
- * A pull entry confirms on arrival; a push entry confirms only after a
181
- * later valid publication for the same synchronization (ADR 0021).
179
+ * Push publication count is not a completion contract. Every push stays
180
+ * tentative until a request-based evidence source replaces it.
182
181
  */
183
182
  export function isTentativePushEntry(entry: DiagnosticCacheEntry | undefined): boolean {
184
- return Boolean(entry && entry.source === "push" && (entry.publications ?? 1) < 2);
183
+ return entry?.source === "push";
185
184
  }
186
185
 
187
186
  /** One document synchronization that needs current diagnostic evidence. */
@@ -214,23 +213,6 @@ export function isCurrentSynchronization(
214
213
  );
215
214
  }
216
215
 
217
- /** Test whether a fresh push confirms the supplied synchronization. */
218
- export function hasFreshPush(
219
- store: ReadonlyMap<string, DiagnosticCacheEntry>,
220
- synchronization: DiagnosticSynchronization,
221
- currentEvidenceRevision?: number,
222
- ): boolean {
223
- const entry = store.get(synchronization.uri);
224
- return Boolean(
225
- entry?.source === "push" &&
226
- !isTentativePushEntry(entry) &&
227
- entry.synchronizationId === synchronization.synchronizationId &&
228
- (synchronization.evidenceRevision === undefined ||
229
- entry.evidenceRevision === synchronization.evidenceRevision) &&
230
- (currentEvidenceRevision === undefined || entry.evidenceRevision === currentEvidenceRevision),
231
- );
232
- }
233
-
234
216
  /**
235
217
  * Test whether stored evidence matches a synchronization without a
236
218
  * confirmation gate. A tentative push matches here but cannot confirm.
@@ -262,16 +244,10 @@ export function hasFreshEvidence(
262
244
  );
263
245
  }
264
246
 
265
- /**
266
- * Return the latest cache update that matches one of the synchronizations.
267
- *
268
- * Every accepted publication restarts the settle quiet period, tentative
269
- * publications included, so this helper does not apply the confirmation
270
- * gate (ADR 0021).
271
- */
247
+ /** Return the latest ambient update for one of the synchronizations. */
272
248
  export function latestCurrentEvidenceReceivedAt(
273
249
  store: ReadonlyMap<string, DiagnosticCacheEntry>,
274
- synchronizations: DiagnosticSynchronization[],
250
+ synchronizations: readonly DiagnosticSynchronization[],
275
251
  currentEvidenceRevision?: number,
276
252
  ): number {
277
253
  let latest = 0;
@@ -284,34 +260,6 @@ export function latestCurrentEvidenceReceivedAt(
284
260
  return latest;
285
261
  }
286
262
 
287
- interface PullRaceOptions {
288
- readonly pull: Promise<boolean>;
289
- readonly waitForChange: () => DiagnosticStateWait;
290
- readonly freshPush: () => boolean;
291
- readonly current: () => boolean;
292
- }
293
-
294
- /** Race a pull against fresh push evidence and lifecycle release. */
295
- export async function raceDiagnosticPull(
296
- options: PullRaceOptions,
297
- ): Promise<"pull" | "push" | "released"> {
298
- const pull = options.pull.then((confirmed) =>
299
- confirmed ? ("pull" as const) : ("released" as const),
300
- );
301
- while (options.current()) {
302
- if (options.freshPush()) return "push";
303
- const change = options.waitForChange();
304
- let outcome: "changed" | "pull" | "released";
305
- try {
306
- outcome = await Promise.race([pull, change.promise.then(() => "changed" as const)]);
307
- } finally {
308
- change.cancel();
309
- }
310
- if (outcome !== "changed") return outcome;
311
- }
312
- return "released";
313
- }
314
-
315
263
  /** Return explicit partial or unavailable evidence after fresh collection fails. */
316
264
  export function incompleteDiagnosticResult(
317
265
  cachedDiagnostics: Diagnostic[] | null,
@@ -342,6 +290,7 @@ interface ApplyPullReportOptions {
342
290
  readonly synchronizationId: number | undefined;
343
291
  readonly evidenceRevision: number;
344
292
  readonly isRelatedUriTracked: (uri: string) => boolean;
293
+ readonly source?: Exclude<DiagnosticEvidenceSource, "push">;
345
294
  }
346
295
 
347
296
  /** Apply one valid full or linked unchanged pull report. */
@@ -365,12 +314,18 @@ export function applyPullReport(options: ApplyPullReportOptions): boolean {
365
314
  store.set(uri, {
366
315
  diagnostics: report.items,
367
316
  receivedAt: Date.now(),
368
- source: "pull",
317
+ source: options.source ?? "pull",
369
318
  synchronizationId,
370
319
  evidenceRevision,
371
320
  resultId: report.resultId,
372
321
  });
373
- applyRelatedPullReports(store, report, evidenceRevision, isRelatedUriTracked);
322
+ applyRelatedPullReports({
323
+ store,
324
+ report,
325
+ evidenceRevision,
326
+ isRelatedUriTracked,
327
+ source: options.source ?? "pull",
328
+ });
374
329
  return true;
375
330
  }
376
331
  if (!previous || previousResultId === undefined || typeof report.resultId !== "string") {
@@ -379,34 +334,41 @@ export function applyPullReport(options: ApplyPullReportOptions): boolean {
379
334
  store.set(uri, {
380
335
  ...previous,
381
336
  receivedAt: Date.now(),
382
- source: "pull",
337
+ source: options.source ?? "pull",
383
338
  synchronizationId,
384
339
  evidenceRevision,
385
340
  resultId: report.resultId,
386
341
  });
387
- applyRelatedPullReports(store, report, evidenceRevision, isRelatedUriTracked);
342
+ applyRelatedPullReports({
343
+ store,
344
+ report,
345
+ evidenceRevision,
346
+ isRelatedUriTracked,
347
+ source: options.source ?? "pull",
348
+ });
388
349
  return true;
389
350
  }
390
351
 
391
- function applyRelatedPullReports(
392
- store: Map<string, DiagnosticCacheEntry>,
393
- report: DocumentDiagnosticReport,
394
- evidenceRevision: number,
395
- isRelatedUriTracked: (uri: string) => boolean,
396
- ): void {
397
- for (const [relatedUri, relatedReport] of Object.entries(report.relatedDocuments ?? {})) {
352
+ function applyRelatedPullReports(options: {
353
+ store: Map<string, DiagnosticCacheEntry>;
354
+ report: DocumentDiagnosticReport;
355
+ evidenceRevision: number;
356
+ isRelatedUriTracked: (uri: string) => boolean;
357
+ source: Exclude<DiagnosticEvidenceSource, "push">;
358
+ }): void {
359
+ for (const [relatedUri, relatedReport] of Object.entries(options.report.relatedDocuments ?? {})) {
398
360
  if (!isValidDocumentDiagnosticReport(relatedReport, false)) continue;
399
361
  // Skip explicit unchanged reports; full reports (kind "full", "", or
400
362
  // absent) may enter the related-document store.
401
363
  if (relatedReport.kind === "unchanged") continue;
402
- if (isRelatedUriTracked(relatedUri)) continue;
403
- const existing = store.get(relatedUri);
404
- if (existing?.evidenceRevision === evidenceRevision) continue;
405
- store.set(relatedUri, {
364
+ if (options.isRelatedUriTracked(relatedUri)) continue;
365
+ const existing = options.store.get(relatedUri);
366
+ if (existing?.evidenceRevision === options.evidenceRevision) continue;
367
+ options.store.set(relatedUri, {
406
368
  diagnostics: relatedReport.items,
407
369
  receivedAt: Date.now(),
408
- source: "pull",
409
- evidenceRevision,
370
+ source: options.source,
371
+ evidenceRevision: options.evidenceRevision,
410
372
  resultId: relatedReport.resultId,
411
373
  });
412
374
  }
@@ -1,5 +1,4 @@
1
- import type { DocumentDiagnosticReport } from "../config/types.ts";
2
- import type { DiagnosticPullRequest } from "./client-diagnostic-request.ts";
1
+ import type { DiagnosticRequestAdapter } from "./client-diagnostic-request.ts";
3
2
 
4
3
  /** Transport and capability operations required by diagnostic state. */
5
4
  export interface ClientDiagnosticsHost {
@@ -8,8 +7,8 @@ export interface ClientDiagnosticsHost {
8
7
  /** Absolute workspace root, for debug-telemetry identity. */
9
8
  cwd?: string;
10
9
  isOperational(): boolean;
11
- supportsPullDiagnostics(): boolean;
10
+ /** Native pull and server-specific request evidence share this priority adapter. */
11
+ diagnosticRequestAdapter: DiagnosticRequestAdapter;
12
12
  usesIncrementalDocumentSync(): boolean;
13
13
  sendNotification(method: string, params: unknown): void;
14
- pullDocumentDiagnostics(request: DiagnosticPullRequest): Promise<DocumentDiagnosticReport | null>;
15
14
  }
@@ -1,19 +1,16 @@
1
1
  // Bounded push-publication telemetry for one client's diagnostic state.
2
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.
3
+ // Push-only servers can publish one or more results for one document
4
+ // synchronization. This tracker records one bounded per-synchronization
5
+ // publication summary per diagnostic operation. Events
6
+ // carry only bounded server, workspace, relative-file, synchronization
7
+ // identity, count, and timing data. They never carry diagnostic payloads or
8
+ // source text. Publication counts remain telemetry only.
10
9
 
11
- import * as path from "node:path";
12
10
  import {
13
11
  recordDebugEvent,
14
12
  truncateDebugIdentity as truncateIdentity,
15
13
  } from "@mrclrchtr/supi-core/debug";
16
- import { uriToFile } from "@mrclrchtr/supi-core/path";
17
14
  import { boundCwd } from "../debug-telemetry.ts";
18
15
 
19
16
  /** Maximum tracked synchronizations before the oldest entry is evicted. */
@@ -59,8 +56,6 @@ interface SynchronizationPublicationState {
59
56
  publications: number;
60
57
  firstReceivedAt: number;
61
58
  lastReceivedAt: number;
62
- /** When a finished operation first reported this synchronization unconfirmed. */
63
- unconfirmedAt?: number;
64
59
  }
65
60
 
66
61
  function synchronizationKey(
@@ -75,10 +70,7 @@ function synchronizationKey(
75
70
  export class DiagnosticPublicationTracker {
76
71
  readonly #states = new Map<string, SynchronizationPublicationState>();
77
72
 
78
- constructor(
79
- private readonly identity: { server?: string; cwd?: string },
80
- private readonly fileFor: (uri: string) => string | undefined,
81
- ) {}
73
+ constructor(private readonly identity: { server?: string; cwd?: string }) {}
82
74
 
83
75
  /**
84
76
  * Record one accepted push publication for a synchronization.
@@ -116,9 +108,8 @@ export class DiagnosticPublicationTracker {
116
108
  /**
117
109
  * Emit one bounded per-synchronization publication summary.
118
110
  *
119
- * Synchronizations with no observed push publication are omitted. A
120
- * synchronization that ends unconfirmed is marked so a later promotion can
121
- * emit the ambient late-republish event.
111
+ * Synchronizations with no observed push publication are omitted. The
112
+ * caller supplies the confirmation flag; publication counts do not change it.
122
113
  */
123
114
  emitSummary(options: {
124
115
  readonly operation: "refresh-open" | "sync-file";
@@ -127,7 +118,6 @@ export class DiagnosticPublicationTracker {
127
118
  readonly operationId?: string;
128
119
  }): void {
129
120
  const entries: DiagnosticPublicationSummaryEntry[] = [];
130
- const now = Date.now();
131
121
  for (const synchronization of options.synchronizations) {
132
122
  const state = this.#states.get(
133
123
  synchronizationKey(
@@ -144,8 +134,6 @@ export class DiagnosticPublicationTracker {
144
134
  lastReceivedAt: state.lastReceivedAt,
145
135
  confirmed: synchronization.confirmed,
146
136
  });
147
- if (!synchronization.confirmed) state.unconfirmedAt ??= now;
148
- else state.unconfirmedAt = undefined;
149
137
  }
150
138
  if (entries.length === 0) return;
151
139
  recordDebugEvent({
@@ -167,51 +155,4 @@ export class DiagnosticPublicationTracker {
167
155
  },
168
156
  });
169
157
  }
170
-
171
- /**
172
- * Observe one promotion of a synchronization by a later publication.
173
- *
174
- * The ambient late-republish event fires only when a finished operation
175
- * previously reported the synchronization unconfirmed. The mark is cleared
176
- * so one promotion emits at most one event.
177
- */
178
- promoted(
179
- uri: string,
180
- synchronizationId: number,
181
- evidenceRevision: number,
182
- receivedAt: number = Date.now(),
183
- ): void {
184
- const state = this.#states.get(synchronizationKey(uri, synchronizationId, evidenceRevision));
185
- const unconfirmedAt = state?.unconfirmedAt;
186
- if (!state || state.publications < 2 || unconfirmedAt === undefined) return;
187
- state.unconfirmedAt = undefined;
188
- const file = this.fileFor(uri);
189
- recordDebugEvent({
190
- source: "lsp",
191
- level: "debug",
192
- category: "diagnostics.publication",
193
- message: "LSP diagnostic late republish",
194
- cwd: boundCwd(this.identity.cwd),
195
- data: {
196
- synchronizationId,
197
- publications: state.publications,
198
- receivedAt,
199
- delayMs: Math.max(0, receivedAt - unconfirmedAt),
200
- ...(this.identity.server !== undefined
201
- ? { server: truncateIdentity(this.identity.server) }
202
- : {}),
203
- ...(file !== undefined ? { file: truncateIdentity(file) } : {}),
204
- },
205
- });
206
- }
207
- }
208
-
209
- /** Return a workspace-relative diagnostic file path for telemetry identity. */
210
- export function trackerFileIdentityFor(
211
- cwd: string | undefined,
212
- ): (uri: string) => string | undefined {
213
- return (uri) => {
214
- if (cwd === undefined) return undefined;
215
- return path.relative(cwd, path.resolve(cwd, uriToFile(uri)));
216
- };
217
158
  }
@@ -1,62 +1,49 @@
1
- import { CodeRequestDeadlineError } from "@mrclrchtr/supi-code-runtime/api";
2
- import type { DocumentDiagnosticReport } from "../config/types.ts";
3
1
  import { applyPullReport, type DiagnosticCacheEntry } from "./client-diagnostic-evidence.ts";
4
- import type { DiagnosticPullRequest } from "./client-diagnostic-request.ts";
2
+ import type {
3
+ DiagnosticPullRequest,
4
+ DiagnosticRequestAdapter,
5
+ DiagnosticRequestExecution,
6
+ } from "./client-diagnostic-request.ts";
5
7
 
6
- interface PullDiagnosticEvidenceOptions {
7
- store: Map<string, DiagnosticCacheEntry>;
8
- uri: string;
9
- timeoutMs: number;
10
- synchronizationId?: number;
11
- evidenceRevision: number;
12
- currentRevision(): number;
13
- isCurrentSynchronization(): boolean;
14
- isRelatedUriTracked(uri: string): boolean;
15
- signal?: AbortSignal;
16
- deadline?: number;
17
- operationId?: string;
18
- pull(request: DiagnosticPullRequest): Promise<DocumentDiagnosticReport | null>;
19
- }
20
-
21
- /** Collect one pull report only if its document and workspace generations remain current. */
22
- export async function pullDiagnosticEvidence(
23
- options: PullDiagnosticEvidenceOptions,
24
- ): Promise<boolean> {
25
- const previous = options.store.get(options.uri);
26
- const previousResultId = previous?.resultId;
27
- const report = await options.pull({
28
- uri: options.uri,
29
- previousResultId,
30
- timeoutMs: options.timeoutMs,
31
- signal: options.signal,
32
- deadline: options.deadline,
33
- operationId: options.operationId,
34
- });
35
- // Final gate: a report that lands after cancellation or deadline expiry
36
- // must not be applied as evidence.
37
- if (options.signal?.aborted) throw options.signal.reason;
38
- if (options.deadline !== undefined && Date.now() >= options.deadline) {
39
- throw new CodeRequestDeadlineError();
40
- }
41
- if (report === null || report === undefined) {
42
- throw new Error("Invalid LSP diagnostic report.");
43
- }
44
- if (
45
- options.evidenceRevision !== options.currentRevision() ||
46
- (options.synchronizationId !== undefined && !options.isCurrentSynchronization())
47
- ) {
48
- return false;
49
- }
50
- const applied = applyPullReport({
51
- store: options.store,
52
- uri: options.uri,
53
- report,
54
- previous,
55
- previousResultId,
56
- synchronizationId: options.synchronizationId,
57
- evidenceRevision: options.evidenceRevision,
58
- isRelatedUriTracked: options.isRelatedUriTracked,
8
+ /** Start one adapter request and apply it only while its evidence is current. */
9
+ export function startDiagnosticEvidenceFromAdapter(options: {
10
+ readonly adapter: DiagnosticRequestAdapter;
11
+ readonly request: DiagnosticPullRequest;
12
+ readonly store: Map<string, DiagnosticCacheEntry>;
13
+ readonly synchronizationId?: number;
14
+ readonly evidenceRevision: number;
15
+ readonly currentRevision: () => number;
16
+ readonly isCurrentSynchronization: () => boolean;
17
+ readonly markEvidenceCurrent?: (uri: string, synchronizationId: number | undefined) => void;
18
+ readonly isRelatedUriTracked: (uri: string) => boolean;
19
+ }): DiagnosticRequestExecution<boolean> {
20
+ const previous = options.store.get(options.request.uri);
21
+ const execution = options.adapter.collect(options.request);
22
+ const result = execution.result.then((requestResult) => {
23
+ if (options.request.signal?.aborted) {
24
+ throw options.request.signal.reason ?? new Error("Diagnostic request cancelled.");
25
+ }
26
+ if (
27
+ options.evidenceRevision !== options.currentRevision() ||
28
+ (options.synchronizationId !== undefined && !options.isCurrentSynchronization())
29
+ ) {
30
+ return false;
31
+ }
32
+ const applied = applyPullReport({
33
+ store: options.store,
34
+ uri: options.request.uri,
35
+ report: requestResult.report,
36
+ previous,
37
+ previousResultId: options.request.previousResultId,
38
+ synchronizationId: options.synchronizationId,
39
+ evidenceRevision: options.evidenceRevision,
40
+ isRelatedUriTracked: options.isRelatedUriTracked,
41
+ source: requestResult.source,
42
+ });
43
+ if (!applied) throw new Error("Invalid diagnostic request report.");
44
+ options.markEvidenceCurrent?.(options.request.uri, options.synchronizationId);
45
+ return true;
59
46
  });
60
- if (!applied) throw new Error("Invalid LSP diagnostic report.");
61
- return true;
47
+ result.catch(() => {});
48
+ return { result, settled: execution.settled };
62
49
  }