@mrclrchtr/supi-lsp 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 (47) hide show
  1. package/README.md +35 -13
  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/ignore/index.js +3 -1
  10. package/node_modules/ignore/legacy.js +3 -1
  11. package/node_modules/ignore/package.json +1 -1
  12. package/package.json +3 -3
  13. package/src/api.ts +16 -0
  14. package/src/client/client-diagnostic-cache.ts +43 -35
  15. package/src/client/client-diagnostic-capabilities.ts +83 -17
  16. package/src/client/client-diagnostic-collection.ts +108 -92
  17. package/src/client/client-diagnostic-evidence.ts +44 -82
  18. package/src/client/client-diagnostic-host.ts +3 -4
  19. package/src/client/client-diagnostic-publication.ts +9 -68
  20. package/src/client/client-diagnostic-pull.ts +45 -58
  21. package/src/client/client-diagnostic-refresh.ts +230 -331
  22. package/src/client/client-diagnostic-request.ts +201 -1
  23. package/src/client/client-diagnostic-timing.ts +129 -70
  24. package/src/client/client-diagnostic-typescript.ts +395 -0
  25. package/src/client/client-diagnostic-waiters.ts +8 -12
  26. package/src/client/client-diagnostics.ts +685 -183
  27. package/src/client/client-document-state.ts +2 -2
  28. package/src/client/client-document-sync.ts +60 -60
  29. package/src/client/client-request-enrollment.ts +75 -0
  30. package/src/client/client-semantic-input-barrier.ts +443 -0
  31. package/src/client/client-semantic-input-errors.ts +30 -0
  32. package/src/client/client-semantic-input-file.ts +15 -0
  33. package/src/client/client.ts +290 -37
  34. package/src/client/transport.ts +72 -16
  35. package/src/config/capabilities.ts +1 -0
  36. package/src/config/tsconfig-scope.ts +30 -4
  37. package/src/diagnostics/evidence.ts +1 -1
  38. package/src/diagnostics/workspace-sentinels.ts +4 -32
  39. package/src/diagnostics/workspace-sources.ts +108 -0
  40. package/src/manager/manager-diagnostics.ts +10 -4
  41. package/src/manager/manager-process-crash-report.ts +50 -1
  42. package/src/manager/manager-workspace-recovery.ts +11 -7
  43. package/src/manager/manager.ts +770 -56
  44. package/src/session/runtime-diagnostic-surface.ts +3 -1
  45. package/src/session/runtime-diagnostics.ts +37 -4
  46. package/src/session/runtime-registry.ts +48 -12
  47. package/src/session/workspace-lsp-runtime.ts +50 -4
@@ -14,6 +14,8 @@ import type {
14
14
  */
15
15
  export interface ProcessCrashDiagnosticDemand {
16
16
  readonly scopes?: readonly string[];
17
+ /** Allow one bounded retry for failed routes during explicit health refresh. */
18
+ readonly explicit?: boolean;
17
19
  }
18
20
 
19
21
  /** Diagnostic and recovery operations owned by a workspace LSP runtime. */
@@ -45,7 +47,7 @@ export interface WorkspaceLspDiagnosticSurface {
45
47
  quietMs?: number;
46
48
  /** Evidence from a refresh the caller already completed; skips this pass's own refresh when no watched-file changes apply. */
47
49
  initialEvidence?: DiagnosticEvidenceSummary;
48
- /** Explicit demand to recover crashed routes with tracked files in scope. */
50
+ /** Explicit demand to retry crashed and failed routes in scope. */
49
51
  processCrashDemand?: ProcessCrashDiagnosticDemand;
50
52
  control?: CodeRequestControl;
51
53
  }): Promise<RecoverDiagnosticsResult>;
@@ -52,8 +52,8 @@ export type ProcessCrashRecoveryOutcome =
52
52
  | "recovery-failed"
53
53
  | "recovery-exhausted";
54
54
 
55
- /** Typed next action for a non-recovered process-crash route. */
56
- export type ProcessCrashRecoveryNextAction = "use-exact-file" | "reload-workspace";
55
+ /** Typed next action for a route that needs another explicit health refresh. */
56
+ export type ProcessCrashRecoveryNextAction = "use-exact-file" | "refresh";
57
57
 
58
58
  /** One bounded route entry in a process-crash recovery report. */
59
59
  export interface ProcessCrashRecoveryEntry {
@@ -84,6 +84,29 @@ export interface ProcessCrashRecoveryReport {
84
84
  readonly omittedEntries: number;
85
85
  }
86
86
 
87
+ /** Stable outcome for one initial-start route handled by explicit refresh. */
88
+ export type StartupRetryOutcome = "recovered" | "retry-failed";
89
+
90
+ /** Typed next action for a failed initial-start retry. */
91
+ export type StartupRetryNextAction = "refresh";
92
+
93
+ /** One bounded route entry in an initial-start retry report. */
94
+ export interface StartupRetryEntry {
95
+ readonly name: string;
96
+ readonly root: string;
97
+ readonly outcome: StartupRetryOutcome;
98
+ readonly nextAction?: StartupRetryNextAction;
99
+ readonly failureMessage?: string;
100
+ }
101
+
102
+ /** Bounded result of explicit retries for initial-start routes. */
103
+ export interface StartupRetryReport {
104
+ readonly recoveredRoutes: number;
105
+ readonly failedRoutes: number;
106
+ readonly entries: readonly StartupRetryEntry[];
107
+ readonly omittedEntries: number;
108
+ }
109
+
87
110
  /** Create the empty outcome for a pass with no process-crash demand. */
88
111
  export function emptyProcessCrashRecoveryReport(): ProcessCrashRecoveryReport {
89
112
  return {
@@ -96,6 +119,11 @@ export function emptyProcessCrashRecoveryReport(): ProcessCrashRecoveryReport {
96
119
  };
97
120
  }
98
121
 
122
+ /** Create the empty outcome for a pass with no initial-start retry. */
123
+ export function emptyStartupRetryReport(): StartupRetryReport {
124
+ return { recoveredRoutes: 0, failedRoutes: 0, entries: [], omittedEntries: 0 };
125
+ }
126
+
99
127
  /** Result from a workspace diagnostic recovery pass. */
100
128
  export interface RecoverDiagnosticsResult {
101
129
  /** Active clients targeted by the best-effort refresh, not confirmed successful refreshes. */
@@ -104,6 +132,8 @@ export interface RecoverDiagnosticsResult {
104
132
  restartedClients: number;
105
133
  /** Separate outcome for process-crash route recovery selected by this pass. */
106
134
  processCrashRecovery: ProcessCrashRecoveryReport;
135
+ /** Separate outcome for explicit retries of failed initial-start routes. */
136
+ startupRetry: StartupRetryReport;
107
137
  /** Evidence collected by the refresh and recovery operations. */
108
138
  diagnosticEvidence: DiagnosticEvidenceSummary;
109
139
  /** Final diagnostic report captured after all refresh and recovery work. */
@@ -128,7 +158,10 @@ export interface ScopeDecisionEntry {
128
158
  basis: string | null;
129
159
  }
130
160
 
131
- /** Aggregate tsconfig scope decisions for all tracked files, for debug telemetry. */
161
+ /**
162
+ * Aggregate tsconfig scope decisions for automatic-scope TypeScript/JavaScript-family
163
+ * tracked files, for debug telemetry.
164
+ */
132
165
  export interface ScopeDecisionSummary {
133
166
  caseSensitiveFileNames: boolean;
134
167
  counts: {
@@ -141,6 +174,6 @@ export interface ScopeDecisionSummary {
141
174
  basisCounts: Record<string, number>;
142
175
  /** Bounded workspace-relative entries (oldest first), not the full set. */
143
176
  entries: ScopeDecisionEntry[];
144
- /** Total number of tracked files the summary was computed over. */
177
+ /** Total number of automatic-scope TypeScript/JavaScript-family tracked files covered. */
145
178
  totalFiles: number;
146
179
  }
@@ -41,8 +41,10 @@ import type {
41
41
  DiagnosticEvidenceSummary,
42
42
  ProcessCrashRecoveryReport,
43
43
  RecoverDiagnosticsResult,
44
+ StartupRetryReport,
44
45
  } from "./runtime-diagnostics.ts";
45
46
  import type {
47
+ BulkTrackFilesResult,
46
48
  RoutedMutationResponse,
47
49
  SemanticReadinessResult,
48
50
  WorkspaceLspRuntime,
@@ -58,6 +60,7 @@ export {
58
60
  type DiagnosticEvidenceStatus,
59
61
  type DiagnosticEvidenceSummary,
60
62
  emptyProcessCrashRecoveryReport,
63
+ emptyStartupRetryReport,
61
64
  MAX_PROCESS_CRASH_RECOVERY_ENTRIES,
62
65
  type OutstandingDiagnosticSummaryEntry,
63
66
  type ProcessCrashRecoveryEntry,
@@ -65,11 +68,17 @@ export {
65
68
  type ProcessCrashRecoveryOutcome,
66
69
  type ProcessCrashRecoveryReport,
67
70
  type RecoverDiagnosticsResult,
71
+ type StartupRetryEntry,
72
+ type StartupRetryNextAction,
73
+ type StartupRetryOutcome,
74
+ type StartupRetryReport,
68
75
  type WorkspaceDiagnosticReport,
69
76
  type WorkspaceDiagnosticSnapshot,
70
77
  type WorkspaceDiagnosticSummaryEntry,
71
78
  } from "./runtime-diagnostics.ts";
72
79
  export type {
80
+ BulkTrackFileOutcome,
81
+ BulkTrackFilesResult,
73
82
  RoutedMutationResponse,
74
83
  SemanticReadinessResult,
75
84
  WorkspaceLspRuntime,
@@ -267,11 +276,11 @@ class DefaultWorkspaceLspRuntime implements WorkspaceLspRuntime {
267
276
  */
268
277
  async waitUntilReadyForFile(
269
278
  filePath: string,
270
- options: { timeoutMs?: number } = {},
279
+ options: { timeoutMs?: number; retryFailedRoute?: boolean } = {},
271
280
  control?: CodeRequestControl,
272
281
  ): Promise<SemanticReadinessResult> {
273
282
  const resolvedPath = this.resolveFilePath(filePath);
274
- if (!this.manager.canServeFile(resolvedPath)) {
283
+ if (!options.retryFailedRoute && !this.manager.canServeFile(resolvedPath)) {
275
284
  const processCrashRecovery = this.manager.getProcessCrashRecoveryReportForFile(resolvedPath);
276
285
  return {
277
286
  kind: "unavailable",
@@ -281,31 +290,44 @@ class DefaultWorkspaceLspRuntime implements WorkspaceLspRuntime {
281
290
  }
282
291
 
283
292
  let eagerProcessCrashRecovery: ProcessCrashRecoveryReport | undefined;
293
+ let eagerStartupRetry: StartupRetryReport | undefined;
284
294
  const readiness = await raceReadinessValue(
285
- this.manager.waitUntilFileReady(resolvedPath, control, (report) => {
286
- eagerProcessCrashRecovery = report;
295
+ this.manager.waitUntilFileReady(resolvedPath, control, {
296
+ retryFailedRoute: options.retryFailedRoute === true,
297
+ onProcessCrashRecovery: (report) => {
298
+ eagerProcessCrashRecovery = report;
299
+ },
300
+ onStartupRetry: (report) => {
301
+ eagerStartupRetry = report;
302
+ },
287
303
  }),
288
304
  options.timeoutMs,
289
305
  control,
290
306
  );
291
307
  if (readiness.kind !== "resolved") {
292
- // A timeout can happen before shared recovery settles. Do not create a
293
- // route outcome for work that has no final result yet.
308
+ // A timeout can happen before shared recovery settles. Keep only route
309
+ // outcomes that completed before the timeout.
294
310
  const currentProcessCrashRecovery =
295
311
  this.manager.getProcessCrashRecoveryReportForFile(resolvedPath);
296
312
  const processCrashRecovery = currentProcessCrashRecovery ?? eagerProcessCrashRecovery;
297
- return processCrashRecovery ? { ...readiness, processCrashRecovery } : readiness;
313
+ return {
314
+ ...readiness,
315
+ ...(eagerStartupRetry ? { startupRetry: eagerStartupRetry } : {}),
316
+ ...(processCrashRecovery ? { processCrashRecovery } : {}),
317
+ };
298
318
  }
299
- const { client, processCrashRecovery } = readiness.value;
319
+ const { client, processCrashRecovery, startupRetry } = readiness.value;
300
320
  const recovery = hasProcessCrashRecovery(processCrashRecovery) ? { processCrashRecovery } : {};
321
+ const startup = startupRetry && startupRetry.entries.length > 0 ? { startupRetry } : {};
301
322
  if (!client) {
302
323
  return {
303
324
  kind: "unavailable",
304
325
  reason: "The routed LSP client could not be started for this file",
326
+ ...startup,
305
327
  ...recovery,
306
328
  };
307
329
  }
308
- return { kind: "ready", ...recovery };
330
+ return { kind: "ready", ...startup, ...recovery };
309
331
  }
310
332
 
311
333
  /**
@@ -345,7 +367,12 @@ class DefaultWorkspaceLspRuntime implements WorkspaceLspRuntime {
345
367
  return this.manager.scanWorkspaceSentinels(options);
346
368
  }
347
369
 
348
- /** Refresh the automatic workspace sentinel and source inventory. */
370
+ /** Scan configured source extensions under the automatic path policy. */
371
+ scanWorkspaceSources(control?: CodeRequestControl) {
372
+ return this.manager.scanWorkspaceSources(control);
373
+ }
374
+
375
+ /** Refresh the automatic workspace sentinel. */
349
376
  syncWorkspaceSentinelSnapshot(
350
377
  previous: Map<string, number>,
351
378
  options: WorkspaceSentinelScanOptions = {},
@@ -353,6 +380,14 @@ class DefaultWorkspaceLspRuntime implements WorkspaceLspRuntime {
353
380
  return this.manager.syncWorkspaceSentinelSnapshot(previous, options);
354
381
  }
355
382
 
383
+ /** Track a bounded set of source files without exposing clients. */
384
+ bulkTrackFiles(
385
+ filePaths: readonly string[],
386
+ control?: CodeRequestControl,
387
+ ): Promise<BulkTrackFilesResult> {
388
+ return this.manager.bulkTrackFiles(filePaths, control);
389
+ }
390
+
356
391
  /** Track a file in its routed client without exposing that client. */
357
392
  async trackFile(filePath: string): Promise<boolean> {
358
393
  const resolvedPath = this.resolveFilePath(filePath);
@@ -417,14 +452,14 @@ class DefaultWorkspaceLspRuntime implements WorkspaceLspRuntime {
417
452
  return this.manager.getOutstandingDiagnosticSummarySnapshot(maxSeverity);
418
453
  }
419
454
 
420
- /** Trigger a workspace-wide diagnostics refresh and stale-state recovery pass. */
455
+ /** Retry failed routes and run a workspace-wide diagnostic recovery pass. */
421
456
  async recoverDiagnostics(options?: {
422
457
  restartIfStillStale?: boolean;
423
458
  maxWaitMs?: number;
424
459
  quietMs?: number;
425
460
  /** Evidence from a refresh the caller already completed; skips this pass's own refresh when no watched-file changes apply. */
426
461
  initialEvidence?: DiagnosticEvidenceSummary;
427
- /** Explicit demand to recover crashed routes with tracked files in scope. */
462
+ /** Explicit demand to retry failed startup and crashed routes in scope. */
428
463
  processCrashDemand?: import("./runtime-diagnostic-surface.ts").ProcessCrashDiagnosticDemand;
429
464
  control?: CodeRequestControl;
430
465
  }): Promise<RecoverDiagnosticsResult> {
@@ -444,6 +479,7 @@ class DefaultWorkspaceLspRuntime implements WorkspaceLspRuntime {
444
479
  attemptedClients: result.attemptedClients,
445
480
  restartedClients: result.restartedClients,
446
481
  processCrashRecovery: result.processCrashRecovery,
482
+ startupRetry: result.startupRetry,
447
483
  attemptedServers: boundServerNames(result.attemptedServers ?? []),
448
484
  restartedServers: boundServerNames(result.restartedServers ?? []),
449
485
  ...(result.restartReason ? { reason: result.restartReason } : {}),
@@ -17,8 +17,12 @@ import type {
17
17
  WorkspaceSentinelScanOptions,
18
18
  WorkspaceSentinelSyncResult,
19
19
  } from "../diagnostics/workspace-sentinels.ts";
20
+ import type { WorkspaceSourceInventory } from "../diagnostics/workspace-sources.ts";
20
21
  import type { WorkspaceLspDiagnosticSurface } from "./runtime-diagnostic-surface.ts";
21
- import type { ProcessCrashRecoveryReport } from "./runtime-diagnostics.ts";
22
+ import type { ProcessCrashRecoveryReport, StartupRetryReport } from "./runtime-diagnostics.ts";
23
+
24
+ /** Maximum number of files processed by one automatic bulk-tracking call. */
25
+ export const MAX_BULK_TRACK_FILES = 256;
22
26
 
23
27
  export type WorkspaceLspRuntimeState =
24
28
  | { kind: "ready"; runtime: WorkspaceLspRuntime }
@@ -30,17 +34,23 @@ export type WorkspaceLspRuntimeState =
30
34
  export type SemanticReadinessResult =
31
35
  | {
32
36
  kind: "ready";
37
+ /** Initial-start retry observed while establishing file readiness. */
38
+ startupRetry?: StartupRetryReport;
33
39
  /** Process-crash route recovery observed while establishing file readiness. */
34
40
  processCrashRecovery?: ProcessCrashRecoveryReport;
35
41
  }
36
42
  | {
37
43
  kind: "timeout";
44
+ /** Initial-start retry observed before the readiness timeout. */
45
+ startupRetry?: StartupRetryReport;
38
46
  /** Process-crash route recovery observed before the readiness timeout. */
39
47
  processCrashRecovery?: ProcessCrashRecoveryReport;
40
48
  }
41
49
  | {
42
50
  kind: "unavailable";
43
51
  reason: string;
52
+ /** Initial-start retry observed before readiness became unavailable. */
53
+ startupRetry?: StartupRetryReport;
44
54
  /** Process-crash route recovery observed before readiness became unavailable. */
45
55
  processCrashRecovery?: ProcessCrashRecoveryReport;
46
56
  };
@@ -53,6 +63,28 @@ export interface RoutedMutationResponse<T> {
53
63
  readonly authorizedMutationRoots: readonly string[];
54
64
  }
55
65
 
66
+ /** Outcome for one path selected by a bounded automatic tracking batch. */
67
+ export type BulkTrackFileOutcome =
68
+ | { readonly file: string; readonly kind: "tracked" }
69
+ | { readonly file: string; readonly kind: "already-tracked" }
70
+ /**
71
+ * Automatic tracking skipped this candidate. This includes policy-excluded
72
+ * paths, unsupported automatic routes, missing paths, and paths that are no
73
+ * longer regular. The coarse reason does not prove one cause or that an
74
+ * explicit request cannot be served.
75
+ */
76
+ | {
77
+ readonly file: string;
78
+ readonly kind: "skipped";
79
+ readonly reason: "missing" | "not-automatic-source";
80
+ }
81
+ | { readonly file: string; readonly kind: "unavailable"; readonly reason: string };
82
+
83
+ /** Result of one bounded automatic tracking batch. */
84
+ export interface BulkTrackFilesResult {
85
+ readonly outcomes: readonly BulkTrackFileOutcome[];
86
+ }
87
+
56
88
  /**
57
89
  * Workspace-scoped LSP interface that owns routing, readiness, semantic operations,
58
90
  * diagnostics, and recovery without exposing clients or the mutable manager.
@@ -104,9 +136,13 @@ export interface WorkspaceLspRuntime extends WorkspaceLspDiagnosticSurface {
104
136
  control?: CodeRequestControl,
105
137
  ): Promise<RoutedMutationResponse<CodeAction[] | null> | null>;
106
138
  getOpenDocumentVersion(filePath: string): number | null;
139
+ /**
140
+ * Wait for file readiness; an explicit refresh may make one shared route
141
+ * retry for this call without changing ordinary request recovery limits.
142
+ */
107
143
  waitUntilReadyForFile(
108
144
  filePath: string,
109
- options?: { timeoutMs?: number },
145
+ options?: { timeoutMs?: number; retryFailedRoute?: boolean },
110
146
  control?: CodeRequestControl,
111
147
  ): Promise<SemanticReadinessResult>;
112
148
  waitUntilReadyForWorkspace(
@@ -116,13 +152,23 @@ export interface WorkspaceLspRuntime extends WorkspaceLspDiagnosticSurface {
116
152
  getProjectServers(): ProjectServerInfo[];
117
153
  /** Check whether automatic LSP work can use and serve the source file. */
118
154
  isSupportedSourceFile(filePath: string): boolean;
119
- /** Inventory policy-eligible workspace sentinels and optional source files. */
155
+ /** Inventory policy-eligible workspace sentinels. */
120
156
  scanWorkspaceSentinels(options?: WorkspaceSentinelScanOptions): Map<string, number>;
121
- /** Refresh one policy-eligible workspace sentinel and source inventory. */
157
+ /** Scan configured source extensions under the automatic path policy. */
158
+ scanWorkspaceSources(control?: CodeRequestControl): Promise<WorkspaceSourceInventory>;
159
+ /** Refresh one policy-eligible workspace sentinel. */
122
160
  syncWorkspaceSentinelSnapshot(
123
161
  previous: Map<string, number>,
124
162
  options?: WorkspaceSentinelScanOptions,
125
163
  ): WorkspaceSentinelSyncResult;
164
+ /**
165
+ * Track a bounded, deduplicated set of automatic source files. Inputs after
166
+ * the first 256 unique paths are ignored; callers must retain those paths.
167
+ */
168
+ bulkTrackFiles(
169
+ filePaths: readonly string[],
170
+ control?: CodeRequestControl,
171
+ ): Promise<BulkTrackFilesResult>;
126
172
  trackFile(filePath: string): Promise<boolean>;
127
173
  closeFile(filePath: string): void;
128
174
  pruneMissingFiles(): readonly string[];