@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,9 +1,209 @@
1
+ import {
2
+ type CodeRequestControl,
3
+ throwIfCodeRequestInterrupted,
4
+ } from "@mrclrchtr/supi-code-runtime/api";
5
+ import type { DocumentDiagnosticReport } from "../config/types.ts";
6
+ import { raceRequestControl } from "../session/readiness.ts";
7
+
8
+ /** Evidence source used by an explicit diagnostic request. */
9
+ export type DiagnosticRequestSource = "pull" | "typescript";
10
+
11
+ /** Error used when local document or client invalidation supersedes a request. */
12
+ export class DiagnosticRequestInvalidatedError extends Error {
13
+ constructor(message = "Diagnostic request was invalidated.") {
14
+ super(message);
15
+ // biome-ignore lint/security/noSecrets: This is a stable error class name.
16
+ this.name = "DiagnosticRequestInvalidatedError";
17
+ }
18
+ }
19
+
20
+ /** Test whether a request stopped because its local evidence generation ended. */
21
+ export function isDiagnosticRequestInvalidated(error: unknown): boolean {
22
+ return error instanceof DiagnosticRequestInvalidatedError;
23
+ }
24
+
1
25
  /** Sanitized ownership and transport control for one explicit diagnostic pull. */
2
26
  export interface DiagnosticPullRequest {
3
27
  readonly uri: string;
4
28
  readonly previousResultId: string | undefined;
5
29
  readonly timeoutMs: number;
30
+ /** Adapter cancellation stops adapter work, not the owned transport. */
6
31
  readonly signal?: AbortSignal;
7
- readonly deadline?: number;
8
32
  readonly operationId?: string;
9
33
  }
34
+
35
+ /** Result returned by one request adapter. */
36
+ export interface DiagnosticRequestResult {
37
+ readonly source: DiagnosticRequestSource;
38
+ readonly report: DocumentDiagnosticReport;
39
+ }
40
+
41
+ /**
42
+ * The result and transport lifetime of one adapter request are separate.
43
+ * `result` may stop at a local deadline while `settled` waits for the
44
+ * underlying protocol request. This prevents a replacement request from
45
+ * entering the same server route too early.
46
+ */
47
+ export interface DiagnosticRequestExecution<T> {
48
+ readonly result: Promise<T>;
49
+ readonly settled: Promise<void>;
50
+ }
51
+
52
+ /** Internal seam for one explicit, file-scoped diagnostic request. */
53
+ export interface DiagnosticRequestAdapter {
54
+ /** Whether this adapter can collect the requested URI right now. */
55
+ supports(uri: string): boolean;
56
+ /** Return the source that will be used for a URI, when known. */
57
+ sourceFor(uri: string): DiagnosticRequestSource | undefined;
58
+ /** Start one request without transferring caller cancellation to transport. */
59
+ collect(request: DiagnosticPullRequest): DiagnosticRequestExecution<DiagnosticRequestResult>;
60
+ }
61
+
62
+ /** Combine adapters in priority order. The first applicable adapter wins. */
63
+ export function createPriorityDiagnosticRequestAdapter(
64
+ adapters: readonly DiagnosticRequestAdapter[],
65
+ ): DiagnosticRequestAdapter {
66
+ const find = (uri: string) => adapters.find((adapter) => adapter.supports(uri));
67
+ return {
68
+ supports: (uri) => find(uri) !== undefined,
69
+ sourceFor: (uri) => find(uri)?.sourceFor(uri),
70
+ collect: (request) => {
71
+ const adapter = find(request.uri);
72
+ if (!adapter) {
73
+ const error = new Error("No diagnostic request adapter applies to this document.");
74
+ return { result: Promise.reject(error), settled: Promise.resolve() };
75
+ }
76
+ return adapter.collect(request);
77
+ },
78
+ };
79
+ }
80
+
81
+ interface ScheduledDiagnosticRequest<T> {
82
+ readonly key: string;
83
+ readonly start: () => DiagnosticRequestExecution<T>;
84
+ readonly result: Promise<T>;
85
+ readonly resolve: (value: T | PromiseLike<T>) => void;
86
+ readonly reject: (reason?: unknown) => void;
87
+ readonly settled: Promise<void>;
88
+ readonly settle: () => void;
89
+ consumers: number;
90
+ }
91
+
92
+ /**
93
+ * Share duplicate file requests and serialize requests for one client route.
94
+ * The queue is deliberately small: callers must not create an unbounded
95
+ * server-side tsserver request backlog after a timeout.
96
+ */
97
+ export class DiagnosticRequestScheduler {
98
+ static readonly MAX_PENDING_REQUESTS = 32;
99
+
100
+ readonly #jobs = new Map<string, ScheduledDiagnosticRequest<unknown>>();
101
+ readonly #pending: Array<ScheduledDiagnosticRequest<unknown>> = [];
102
+ #active: ScheduledDiagnosticRequest<unknown> | undefined;
103
+
104
+ /** Start or join one request, while keeping the underlying job shared. */
105
+ run<T>(
106
+ key: string,
107
+ start: () => DiagnosticRequestExecution<T>,
108
+ control?: CodeRequestControl,
109
+ ): Promise<T> {
110
+ throwIfCodeRequestInterrupted(control);
111
+ let job = this.#jobs.get(key) as ScheduledDiagnosticRequest<T> | undefined;
112
+ if (!job) {
113
+ if (this.#pending.length >= DiagnosticRequestScheduler.MAX_PENDING_REQUESTS) {
114
+ return Promise.reject(new Error("Diagnostic request queue is full."));
115
+ }
116
+ let resolve!: (value: T | PromiseLike<T>) => void;
117
+ let reject!: (reason?: unknown) => void;
118
+ const result = new Promise<T>((jobResolve, jobReject) => {
119
+ resolve = jobResolve;
120
+ reject = jobReject;
121
+ });
122
+ let settle!: () => void;
123
+ const settled = new Promise<void>((resolveSettled) => {
124
+ settle = resolveSettled;
125
+ });
126
+ job = {
127
+ key,
128
+ start,
129
+ result,
130
+ resolve,
131
+ reject,
132
+ settled,
133
+ settle,
134
+ consumers: 0,
135
+ };
136
+ this.#jobs.set(key, job as ScheduledDiagnosticRequest<unknown>);
137
+ this.#pending.push(job as ScheduledDiagnosticRequest<unknown>);
138
+ this.#pump();
139
+ }
140
+ const currentJob = job;
141
+ currentJob.consumers++;
142
+ const waiting = raceRequestControl(currentJob.result, control);
143
+ void waiting
144
+ .finally(() =>
145
+ this.#releaseConsumer(currentJob as unknown as ScheduledDiagnosticRequest<unknown>),
146
+ )
147
+ .catch(() => {});
148
+ return waiting;
149
+ }
150
+
151
+ /** Drop requests that have not reached the transport. Active work is kept. */
152
+ clearPending(reason = new DiagnosticRequestInvalidatedError()): void {
153
+ this.clearPendingWhere(() => true, reason);
154
+ }
155
+
156
+ /** Drop matching queued requests without disturbing other documents. */
157
+ clearPendingWhere(
158
+ predicate: (key: string) => boolean,
159
+ reason = new DiagnosticRequestInvalidatedError(),
160
+ ): void {
161
+ const pending = this.#pending.filter((job) => predicate(job.key));
162
+ for (const job of pending) {
163
+ const index = this.#pending.indexOf(job);
164
+ if (index >= 0) this.#pending.splice(index, 1);
165
+ this.#jobs.delete(job.key);
166
+ job.reject(reason);
167
+ job.settle();
168
+ }
169
+ }
170
+
171
+ #releaseConsumer(job: ScheduledDiagnosticRequest<unknown>): void {
172
+ job.consumers = Math.max(0, job.consumers - 1);
173
+ if (job.consumers > 0 || this.#active === job) return;
174
+ const pendingIndex = this.#pending.indexOf(job);
175
+ if (pendingIndex < 0) return;
176
+ this.#pending.splice(pendingIndex, 1);
177
+ this.#jobs.delete(job.key);
178
+ job.reject(new Error("Diagnostic request had no remaining callers."));
179
+ job.settle();
180
+ }
181
+
182
+ #pump(): void {
183
+ if (this.#active !== undefined) return;
184
+ const job = this.#pending.shift();
185
+ if (!job) return;
186
+ this.#active = job;
187
+ let execution: DiagnosticRequestExecution<unknown>;
188
+ try {
189
+ execution = job.start();
190
+ } catch (error) {
191
+ job.reject(error);
192
+ job.settle();
193
+ this.#active = undefined;
194
+ this.#jobs.delete(job.key);
195
+ this.#pump();
196
+ return;
197
+ }
198
+ execution.result.then(job.resolve, job.reject).catch(() => {});
199
+ execution.settled
200
+ .catch(() => {})
201
+ .then(() => {
202
+ if (this.#active !== job) return;
203
+ this.#active = undefined;
204
+ this.#jobs.delete(job.key);
205
+ job.settle();
206
+ this.#pump();
207
+ });
208
+ }
209
+ }
@@ -5,24 +5,20 @@ import {
5
5
  } from "@mrclrchtr/supi-core/debug";
6
6
  import { boundCwd } from "../debug-telemetry.ts";
7
7
 
8
- type DiagnosticCollection = "cache" | "fallback" | "none" | "pull" | "push";
8
+ type DiagnosticCollection =
9
+ | "cache"
10
+ | "fallback"
11
+ | "mixed"
12
+ | "none"
13
+ | "pull"
14
+ | "push"
15
+ | "typescript";
9
16
  type DiagnosticFreshness = "not-observed" | "observed";
10
17
  type DiagnosticOutcome = "completed" | "incomplete" | "skipped" | "timed-out";
11
18
  type DiagnosticPullOutcome = "completed" | "failed" | "not-supported" | "not-used" | "timed-out";
12
- type DiagnosticPushOutcome =
13
- | "not-used"
14
- | "published"
15
- | "tentative"
16
- | "released"
17
- | "settled"
18
- | "timed-out";
19
- type DiagnosticSettleOutcome =
20
- | "not-used"
21
- | "published"
22
- | "tentative"
23
- | "quiet"
24
- | "released"
25
- | "timed-out";
19
+ type DiagnosticRequestSource = "pull" | "typescript" | "mixed";
20
+ type DiagnosticPushOutcome = "not-used" | "tentative" | "released" | "settled" | "timed-out";
21
+ type DiagnosticSettleOutcome = "not-used" | "quiet" | "released" | "timed-out";
26
22
  type DiagnosticTimingOperation = "refresh-open" | "sync-file";
27
23
 
28
24
  /** Result of waiting for a quiet push-diagnostic window. */
@@ -31,8 +27,11 @@ export interface DiagnosticSettleResult {
31
27
  readonly freshness: DiagnosticFreshness;
32
28
  }
33
29
 
34
- /** Result of waiting for one file's push diagnostics. */
35
- export type DiagnosticPushWaitOutcome = "published" | "tentative" | "released" | "timed-out";
30
+ /** Result of collecting one file's ambient push diagnostics. */
31
+ export type DiagnosticPushWaitOutcome = "tentative" | "released" | "timed-out";
32
+
33
+ /** Result of waiting for one ambient push observation. */
34
+ export type DiagnosticPushObservationOutcome = "observed" | "released" | "timed-out";
36
35
 
37
36
  interface DiagnosticTimingData {
38
37
  readonly collection: DiagnosticCollection;
@@ -42,21 +41,10 @@ interface DiagnosticTimingData {
42
41
  readonly outcome: DiagnosticOutcome;
43
42
  readonly pull: DiagnosticPullOutcome;
44
43
  readonly push: DiagnosticPushOutcome;
45
- readonly reopen: number;
46
44
  readonly settle: DiagnosticSettleOutcome;
47
45
  readonly timedOut: boolean;
48
46
  }
49
47
 
50
- /** Internal pull failure that retains timeout state and failed document URIs. */
51
- export class DiagnosticPullError extends Error {
52
- constructor(
53
- readonly timedOut: boolean,
54
- readonly failedUris: readonly string[] = [],
55
- ) {
56
- super("pull diagnostics incomplete");
57
- }
58
- }
59
-
60
48
  /** Bounded identity for one diagnostic timing observation. */
61
49
  export interface DiagnosticTimingIdentity {
62
50
  /** Configured server name. */
@@ -76,15 +64,16 @@ export interface DiagnosticTimingIdentity {
76
64
  export class DiagnosticObserver {
77
65
  readonly #timer = startDebugTimer();
78
66
  #pull: "failed" | "not-supported" | "timed-out";
79
- #reopened = 0;
67
+ #requestSource: DiagnosticRequestSource | undefined;
68
+ #request: "failed" | "timed-out" | undefined;
80
69
 
81
70
  constructor(
82
71
  readonly operation: DiagnosticTimingOperation,
83
- readonly supportsPull: boolean,
72
+ readonly hasDiagnosticRequestAdapter: boolean,
84
73
  readonly control?: CodeRequestControl,
85
74
  readonly identity?: DiagnosticTimingIdentity,
86
75
  ) {
87
- this.#pull = supportsPull ? "failed" : "not-supported";
76
+ this.#pull = hasDiagnosticRequestAdapter ? "failed" : "not-supported";
88
77
  }
89
78
 
90
79
  synchronized(): void {
@@ -100,7 +89,6 @@ export class DiagnosticObserver {
100
89
  outcome: "skipped",
101
90
  pull: "not-used",
102
91
  push: "not-used",
103
- reopen: 0,
104
92
  settle: "not-used",
105
93
  timedOut: false,
106
94
  });
@@ -115,7 +103,6 @@ export class DiagnosticObserver {
115
103
  outcome: "completed",
116
104
  pull: "not-used",
117
105
  push: "not-used",
118
- reopen: 0,
119
106
  settle: "not-used",
120
107
  timedOut: false,
121
108
  });
@@ -131,7 +118,6 @@ export class DiagnosticObserver {
131
118
  outcome: "completed",
132
119
  pull: "completed",
133
120
  push: "not-used",
134
- reopen: 0,
135
121
  settle: "not-used",
136
122
  timedOut: false,
137
123
  },
@@ -139,40 +125,119 @@ export class DiagnosticObserver {
139
125
  );
140
126
  }
141
127
 
142
- pullFailed(error: unknown): void {
143
- this.#pull = isDiagnosticTimeout(error) ? "timed-out" : "failed";
144
- this.#timer.mark("pull");
128
+ /** Record a completed request-based collection. */
129
+ requestCompleted(source: DiagnosticRequestSource, documentCount: number, finish = true): void {
130
+ this.#requestSource = source;
131
+ this.#request = undefined;
132
+ if (source === "pull" && finish) {
133
+ this.pullCompleted(documentCount);
134
+ return;
135
+ }
136
+ if (!finish) {
137
+ this.#timer.mark("request");
138
+ return;
139
+ }
140
+ this.#finish(
141
+ {
142
+ collection: source === "typescript" ? "typescript" : "mixed",
143
+ documentCount,
144
+ fallback: false,
145
+ freshness: "observed",
146
+ outcome: "completed",
147
+ pull: this.hasDiagnosticRequestAdapter ? "not-used" : "not-supported",
148
+ push: "not-used",
149
+ settle: "not-used",
150
+ timedOut: false,
151
+ },
152
+ "request",
153
+ );
154
+ }
155
+
156
+ /** Record a request failure without finishing the operation yet. */
157
+ requestFailed(source: DiagnosticRequestSource | undefined, error: unknown): void {
158
+ this.#requestSource = source;
159
+ this.#request = isDiagnosticTimeout(error) ? "timed-out" : "failed";
160
+ this.#timer.mark("request");
145
161
  }
146
162
 
147
- pullTimedOut(): void {
148
- this.#pull = "timed-out";
149
- this.#timer.mark("pull");
163
+ /** Finish an incomplete request collection without using push confirmation. */
164
+ requestIncomplete(
165
+ source: DiagnosticRequestSource | undefined,
166
+ timedOut = false,
167
+ documentCount = 1,
168
+ finish = true,
169
+ ): void {
170
+ const requestSource = source ?? this.#requestSource ?? "pull";
171
+ this.#requestSource = requestSource;
172
+ this.#request = timedOut || this.#request === "timed-out" ? "timed-out" : "failed";
173
+ if (!finish) {
174
+ this.#timer.mark("request");
175
+ return;
176
+ }
177
+ this.#finish(
178
+ {
179
+ collection: requestSource === "typescript" ? "typescript" : requestSource,
180
+ documentCount,
181
+ fallback: false,
182
+ freshness: "not-observed",
183
+ outcome: this.#request === "timed-out" ? "timed-out" : "incomplete",
184
+ pull: requestSource === "pull" ? this.#request : "not-supported",
185
+ push: "not-used",
186
+ settle: "not-used",
187
+ timedOut: this.#request === "timed-out",
188
+ },
189
+ "request",
190
+ );
191
+ }
192
+
193
+ /** Finish a mixed request and push observation pass. */
194
+ mixedSettled(
195
+ source: DiagnosticRequestSource,
196
+ documentCount: number,
197
+ settle: DiagnosticSettleResult,
198
+ ): void {
199
+ const timedOut = settle.outcome === "timed-out" || this.#request === "timed-out";
200
+ const requestOutcome =
201
+ this.#request === "timed-out"
202
+ ? ("timed-out" as const)
203
+ : this.#request === "failed"
204
+ ? ("failed" as const)
205
+ : ("completed" as const);
206
+ this.#finish(
207
+ {
208
+ collection: "mixed",
209
+ documentCount,
210
+ fallback: false,
211
+ freshness: settle.freshness,
212
+ outcome: timedOut ? "timed-out" : "incomplete",
213
+ pull: source === "pull" ? requestOutcome : "not-supported",
214
+ push: timedOut ? "timed-out" : settle.outcome === "released" ? "released" : "settled",
215
+ settle: settle.outcome,
216
+ timedOut,
217
+ },
218
+ "push-settle",
219
+ );
150
220
  }
151
221
 
152
- /**
153
- * Record that the reopen-resync fallback re-opened unconfirmed documents.
154
- *
155
- * The mark fires when the second settle window starts, so the measured
156
- * phase is the preceding first settle window, not the reopen work.
157
- */
158
- reopened(count: number): void {
159
- this.#reopened += count;
160
- this.#timer.mark("first-settle");
222
+ /** Record a request that could not start within its collection budget. */
223
+ requestTimedOut(source: DiagnosticRequestSource | undefined): void {
224
+ this.#requestSource = source;
225
+ this.#request = "timed-out";
226
+ this.#timer.mark("request");
227
+ this.requestIncomplete(source, true);
161
228
  }
162
229
 
163
230
  pushSettled(documentCount: number, settle: DiagnosticSettleResult): void {
164
231
  const timedOut = settle.outcome === "timed-out";
165
- const completed = settle.outcome === "quiet" && settle.freshness === "observed";
166
232
  this.#finish(
167
233
  {
168
- collection: this.supportsPull ? "fallback" : "push",
234
+ collection: this.hasDiagnosticRequestAdapter ? "fallback" : "push",
169
235
  documentCount,
170
- fallback: this.supportsPull,
236
+ fallback: this.hasDiagnosticRequestAdapter,
171
237
  freshness: settle.freshness,
172
- outcome: completed ? "completed" : timedOut ? "timed-out" : "incomplete",
238
+ outcome: timedOut ? "timed-out" : "incomplete",
173
239
  pull: this.#pull,
174
240
  push: timedOut ? "timed-out" : settle.outcome === "released" ? "released" : "settled",
175
- reopen: this.#reopened,
176
241
  settle: settle.outcome,
177
242
  timedOut: timedOut || this.#pull === "timed-out",
178
243
  },
@@ -181,30 +246,28 @@ export class DiagnosticObserver {
181
246
  }
182
247
 
183
248
  pushWaitCompleted(documentCount: number, push: DiagnosticPushWaitOutcome): void {
184
- const confirmed = push === "published";
185
- // A tentative result used the full wait budget without a confirming
186
- // republish. Keep its distinct push value and report the budget expiry.
187
249
  const timedOut = push === "tentative" || push === "timed-out";
188
- const observed = push === "published" || push === "tentative";
250
+ const observed = push === "tentative";
189
251
  this.#finish(
190
252
  {
191
- collection: this.supportsPull ? "fallback" : "push",
253
+ collection: this.hasDiagnosticRequestAdapter ? "fallback" : "push",
192
254
  documentCount,
193
- fallback: this.supportsPull,
255
+ fallback: this.hasDiagnosticRequestAdapter,
194
256
  freshness: observed ? "observed" : "not-observed",
195
- outcome: confirmed ? "completed" : timedOut ? "timed-out" : "incomplete",
257
+ outcome: timedOut ? "timed-out" : "incomplete",
196
258
  pull: this.#pull,
197
259
  push,
198
- reopen: this.#reopened,
199
- settle: push,
260
+ settle: timedOut ? "timed-out" : push,
200
261
  timedOut: timedOut || this.#pull === "timed-out",
201
262
  },
202
263
  "push-settle",
203
264
  );
204
265
  }
205
266
 
206
- #finish(data: DiagnosticTimingData, finalPhase?: "pull" | "push-settle" | "synchronize"): void {
207
- const { reopen, ...observation } = data;
267
+ #finish(
268
+ data: DiagnosticTimingData,
269
+ finalPhase?: "pull" | "push-settle" | "request" | "synchronize",
270
+ ): void {
208
271
  this.#timer.finish(
209
272
  () => ({
210
273
  operationId: this.control?.operationId,
@@ -215,10 +278,7 @@ export class DiagnosticObserver {
215
278
  cwd: boundCwd(this.identity?.cwd),
216
279
  data: {
217
280
  operation: this.operation,
218
- ...observation,
219
- // Reopen fallback usage is recorded only when it happened; the
220
- // default event shape stays stable for consumers of #322 telemetry.
221
- ...(reopen > 0 ? { reopen } : {}),
281
+ ...data,
222
282
  ...(this.identity?.server !== undefined
223
283
  ? { server: truncateIdentity(this.identity.server) }
224
284
  : {}),
@@ -234,6 +294,5 @@ export class DiagnosticObserver {
234
294
 
235
295
  /** Return whether a diagnostic failure represents a timeout without retaining its message. */
236
296
  export function isDiagnosticTimeout(error: unknown): boolean {
237
- if (error instanceof DiagnosticPullError) return error.timedOut;
238
297
  return error instanceof Error && /\btimed? ?out\b|\btimeout\b/i.test(error.message);
239
298
  }