@mrclrchtr/supi-lsp 3.1.0 → 4.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 (52) hide show
  1. package/README.md +72 -62
  2. package/node_modules/@mrclrchtr/supi-code-runtime/README.md +8 -0
  3. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +2 -4
  4. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/api.ts +0 -4
  5. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/evidence-badge.ts +3 -2
  6. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/index.ts +0 -4
  7. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/llm.ts +0 -10
  8. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/path-utils.ts +19 -15
  9. package/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
  10. package/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +10 -1
  11. package/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +19 -29
  12. package/node_modules/@mrclrchtr/supi-code-runtime/src/index.ts +7 -1
  13. package/node_modules/@mrclrchtr/supi-code-runtime/src/query-result.ts +42 -0
  14. package/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +15 -10
  15. package/node_modules/@mrclrchtr/supi-core/package.json +2 -4
  16. package/node_modules/@mrclrchtr/supi-core/src/api.ts +0 -4
  17. package/node_modules/@mrclrchtr/supi-core/src/evidence-badge.ts +3 -2
  18. package/node_modules/@mrclrchtr/supi-core/src/index.ts +0 -4
  19. package/node_modules/@mrclrchtr/supi-core/src/llm.ts +0 -10
  20. package/node_modules/@mrclrchtr/supi-core/src/path-utils.ts +19 -15
  21. package/package.json +4 -5
  22. package/src/api.ts +9 -9
  23. package/src/client/client.ts +70 -41
  24. package/src/client/transport.ts +16 -13
  25. package/src/config/config.ts +2 -2
  26. package/src/config/lsp-settings.ts +0 -2
  27. package/src/index.ts +6 -6
  28. package/src/manager/manager-diagnostics.ts +5 -95
  29. package/src/manager/manager-workspace-recovery.ts +4 -3
  30. package/src/manager/manager-workspace-symbol.ts +44 -6
  31. package/src/manager/manager.ts +47 -65
  32. package/src/provider/lsp-semantic-provider.ts +132 -79
  33. package/src/provider/refactor-planning.ts +6 -14
  34. package/src/session/readiness.ts +38 -0
  35. package/src/session/runtime-controller.ts +87 -75
  36. package/src/session/runtime-registration.ts +5 -5
  37. package/src/session/{service-registry.ts → runtime-registry.ts} +163 -71
  38. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/abort-utils.ts +0 -31
  39. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/substrate-types.ts +0 -11
  40. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/types.ts +0 -2
  41. package/node_modules/@mrclrchtr/supi-core/src/abort-utils.ts +0 -31
  42. package/node_modules/@mrclrchtr/supi-core/src/substrate-types.ts +0 -11
  43. package/node_modules/@mrclrchtr/supi-core/src/types.ts +0 -2
  44. package/src/diagnostics/diagnostic-augmentation.ts +0 -82
  45. package/src/diagnostics/diagnostic-context.ts +0 -116
  46. package/src/diagnostics/diagnostic-display.ts +0 -69
  47. package/src/manager/capability-index.ts +0 -24
  48. package/src/manager/client-pool.ts +0 -33
  49. package/src/manager/diagnostic-store.ts +0 -51
  50. package/src/manager/manager-stale-resync.ts +0 -47
  51. package/src/manager/recovery-coordinator.ts +0 -37
  52. package/src/manager/workspace-router.ts +0 -44
@@ -0,0 +1,38 @@
1
+ const DEFAULT_SEMANTIC_READY_TIMEOUT_MS = 15_000;
2
+
3
+ export type ReadinessValueResult<T> =
4
+ | { kind: "resolved"; value: T }
5
+ | { kind: "timeout" }
6
+ | { kind: "unavailable"; reason: string };
7
+
8
+ /** Await one readiness operation while preserving its concrete success value. */
9
+ export async function raceReadinessValue<T>(
10
+ readiness: Promise<T>,
11
+ timeoutMs: number | undefined,
12
+ ): Promise<ReadinessValueResult<T>> {
13
+ const effectiveTimeoutMs = timeoutMs ?? DEFAULT_SEMANTIC_READY_TIMEOUT_MS;
14
+ let timer: ReturnType<typeof setTimeout> | null = null;
15
+
16
+ try {
17
+ const value = await Promise.race([
18
+ readiness,
19
+ new Promise<never>((_resolve, reject) => {
20
+ timer = setTimeout(
21
+ () => reject(new Error("semantic-readiness-timeout")),
22
+ effectiveTimeoutMs,
23
+ );
24
+ }),
25
+ ]);
26
+ return { kind: "resolved", value };
27
+ } catch (error) {
28
+ if (error instanceof Error && error.message === "semantic-readiness-timeout") {
29
+ return { kind: "timeout" };
30
+ }
31
+ return {
32
+ kind: "unavailable",
33
+ reason: error instanceof Error ? error.message : String(error),
34
+ };
35
+ } finally {
36
+ if (timer) clearTimeout(timer);
37
+ }
38
+ }
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // This controller owns session start/shutdown for one cwd:
4
4
  // - Creates and disposes the LspManager
5
- // - Publishes SessionLspService states through the existing registry
5
+ // - Publishes WorkspaceLspRuntime states through the existing registry
6
6
  // - Exposes the data the umbrella adapter will need later
7
7
  //
8
8
  // It does NOT import pi event types or ExtensionAPI.
@@ -19,12 +19,13 @@ import {
19
19
  registerPendingLspCapabilities,
20
20
  unregisterLspCapabilities,
21
21
  } from "./runtime-registration.ts";
22
- import { scanMissingServers, scanProjectCapabilities, startDetectedServers } from "./scanner.ts";
23
22
  import {
24
- clearSessionLspService,
25
- SessionLspService,
26
- setSessionLspServiceState,
27
- } from "./service-registry.ts";
23
+ clearWorkspaceLspRuntime,
24
+ createWorkspaceLspRuntimeOwner,
25
+ setWorkspaceLspRuntimeState,
26
+ type WorkspaceLspRuntime,
27
+ } from "./runtime-registry.ts";
28
+ import { scanMissingServers, scanProjectCapabilities, startDetectedServers } from "./scanner.ts";
28
29
 
29
30
  // ── Types ─────────────────────────────────────────────────────────────
30
31
 
@@ -44,10 +45,12 @@ interface LspControllerPending {
44
45
  kind: "pending";
45
46
  }
46
47
 
48
+ type WorkspaceLspRuntimeOwner = ReturnType<typeof createWorkspaceLspRuntimeOwner>;
49
+
47
50
  interface LspControllerReady {
48
51
  kind: "ready";
49
- manager: LspManager;
50
- service: SessionLspService;
52
+ runtimeOwner: WorkspaceLspRuntimeOwner;
53
+ workspaceRuntime: WorkspaceLspRuntime;
51
54
  projectServers: ProjectServerInfo[];
52
55
  detectedServers: DetectedProjectServer[];
53
56
  settings: LspSettings;
@@ -65,10 +68,14 @@ interface LspControllerUnavailable {
65
68
 
66
69
  /** Result type from {@link LspRuntimeController.start}. */
67
70
  export type LspStartResult =
68
- | { kind: "ready"; manager: LspManager; service: SessionLspService }
71
+ | { kind: "ready"; runtime: WorkspaceLspRuntime }
69
72
  | { kind: "disabled"; message: string }
70
73
  | { kind: "unavailable"; reason: string };
71
74
 
75
+ function supersededStartResult(): LspStartResult {
76
+ return { kind: "unavailable", reason: "LSP startup was superseded by a newer lifecycle event." };
77
+ }
78
+
72
79
  // ── Controller ────────────────────────────────────────────────────────
73
80
 
74
81
  /**
@@ -82,7 +89,7 @@ export type LspStartResult =
82
89
  * const controller = new LspRuntimeController(cwd);
83
90
  * const result = await controller.start();
84
91
  * if (result.kind === "ready") {
85
- * // use controller.manager, controller.service
92
+ * // use result.runtime for workspace LSP operations
86
93
  * }
87
94
  * // later
88
95
  * await controller.shutdown();
@@ -91,13 +98,14 @@ export type LspStartResult =
91
98
  export class LspRuntimeController {
92
99
  readonly #cwd: string;
93
100
  #state: LspControllerState;
94
- #runtime: WorkspaceRuntime | null;
101
+ #capabilityRuntime: WorkspaceRuntime | null;
102
+ /** Monotonic ownership token for starts, shutdowns, and async warm-up. */
95
103
  #readinessGeneration = 0;
96
104
 
97
105
  constructor(cwd: string, runtime?: WorkspaceRuntime) {
98
106
  this.#cwd = cwd;
99
107
  this.#state = { kind: "initial" };
100
- this.#runtime = runtime ?? null;
108
+ this.#capabilityRuntime = runtime ?? null;
101
109
  }
102
110
 
103
111
  /** The workspace cwd this controller was created for. */
@@ -110,14 +118,9 @@ export class LspRuntimeController {
110
118
  return this.#state.kind;
111
119
  }
112
120
 
113
- /** The LspManager, only available when state is "ready". */
114
- get manager(): LspManager | null {
115
- return this.#state.kind === "ready" ? this.#state.manager : null;
116
- }
117
-
118
- /** The SessionLspService, only available when state is "ready". */
119
- get service(): SessionLspService | null {
120
- return this.#state.kind === "ready" ? this.#state.service : null;
121
+ /** Workspace LSP operations, only available when state is "ready". */
122
+ get workspaceRuntime(): WorkspaceLspRuntime | null {
123
+ return this.#state.kind === "ready" ? this.#state.workspaceRuntime : null;
121
124
  }
122
125
 
123
126
  /** Project server info, only available when state is "ready". */
@@ -139,13 +142,13 @@ export class LspRuntimeController {
139
142
  }
140
143
 
141
144
  /** The WorkspaceRuntime registered for this session's cwd. */
142
- get runtime(): WorkspaceRuntime | null {
143
- return this.#runtime;
145
+ get capabilityRuntime(): WorkspaceRuntime | null {
146
+ return this.#capabilityRuntime;
144
147
  }
145
148
 
146
- /** Attach a WorkspaceRuntime for capability registration. */
149
+ /** Attach the capability broker used for semantic registration. */
147
150
  setRuntime(runtime: WorkspaceRuntime): void {
148
- this.#runtime = runtime;
151
+ this.#capabilityRuntime = runtime;
149
152
  }
150
153
 
151
154
  /**
@@ -161,10 +164,12 @@ export class LspRuntimeController {
161
164
  * Returns the start result and updates the controller's state.
162
165
  */
163
166
  async start(): Promise<LspStartResult> {
167
+ const generation = ++this.#readinessGeneration;
164
168
  clearTsconfigCache();
165
169
 
166
170
  // Restart safety: shut down any existing session before creating a new one
167
171
  await this.cleanupExistingSession();
172
+ if (generation !== this.#readinessGeneration) return supersededStartResult();
168
173
 
169
174
  const lspSettings = loadLspSettings(this.#cwd);
170
175
  // Note: lspSettings.enabled is ignored — the global switch is deprecated.
@@ -175,9 +180,9 @@ export class LspRuntimeController {
175
180
  const config = loadConfig(this.#cwd);
176
181
 
177
182
  try {
178
- return await this.initializeLspSession(config, lspSettings);
183
+ return await this.initializeLspSession(config, lspSettings, generation);
179
184
  } catch (error: unknown) {
180
- return this.setUnavailable(error);
185
+ return this.setUnavailable(error, generation);
181
186
  }
182
187
  }
183
188
 
@@ -185,18 +190,28 @@ export class LspRuntimeController {
185
190
  * Shut down any existing LSP session before starting a new one.
186
191
  */
187
192
  private async cleanupExistingSession(): Promise<void> {
188
- this.#readinessGeneration++;
189
193
  if (this.#state.kind !== "ready") return;
190
- await this.#state.manager.shutdownAll();
191
- if (this.#runtime) unregisterLspCapabilities(this.#runtime, this.#cwd);
192
- clearSessionLspService(this.#cwd);
194
+ await this.#state.runtimeOwner.shutdown();
195
+ if (this.#capabilityRuntime) unregisterLspCapabilities(this.#capabilityRuntime, this.#cwd);
196
+ clearWorkspaceLspRuntime(this.#cwd);
197
+ }
198
+
199
+ /** Publish an explicit disabled state when no language-server routes remain enabled. */
200
+ private setDisabled(generation: number): LspStartResult {
201
+ if (generation !== this.#readinessGeneration) return supersededStartResult();
202
+ const message = "All language servers are disabled by configuration.";
203
+ if (this.#capabilityRuntime) unregisterLspCapabilities(this.#capabilityRuntime, this.#cwd);
204
+ this.#state = { kind: "disabled", message };
205
+ setWorkspaceLspRuntimeState(this.#cwd, { kind: "disabled" });
206
+ return { kind: "disabled", message };
193
207
  }
194
208
 
195
209
  /** Set controller state to unavailable with the given error. */
196
- private setUnavailable(error: unknown): LspStartResult {
210
+ private setUnavailable(error: unknown, generation: number): LspStartResult {
211
+ if (generation !== this.#readinessGeneration) return supersededStartResult();
197
212
  const reason = error instanceof Error ? error.message : String(error);
198
213
  this.#state = { kind: "unavailable", reason };
199
- setSessionLspServiceState(this.#cwd, { kind: "unavailable", reason });
214
+ setWorkspaceLspRuntimeState(this.#cwd, { kind: "unavailable", reason });
200
215
  return { kind: "unavailable", reason };
201
216
  }
202
217
 
@@ -207,83 +222,80 @@ export class LspRuntimeController {
207
222
  private async initializeLspSession(
208
223
  config: LspConfig,
209
224
  settings: LspSettings,
225
+ generation: number,
210
226
  ): Promise<LspStartResult> {
211
- clearSessionLspService(this.#cwd);
227
+ if (generation !== this.#readinessGeneration) return supersededStartResult();
228
+ clearWorkspaceLspRuntime(this.#cwd);
229
+ if (Object.keys(config.servers).length === 0) return this.setDisabled(generation);
212
230
  this.#state = { kind: "pending" };
213
231
 
214
232
  const manager = new LspManager(config, this.#cwd);
215
233
  manager.setExcludePatterns(settings.exclude);
216
- setSessionLspServiceState(this.#cwd, { kind: "pending" });
234
+ setWorkspaceLspRuntimeState(this.#cwd, { kind: "pending" });
217
235
 
218
236
  const detectedServers = scanProjectCapabilities(config, this.#cwd);
219
237
  manager.registerDetectedServers(detectedServers);
220
238
  await startDetectedServers(manager, detectedServers);
239
+ if (generation !== this.#readinessGeneration) {
240
+ await manager.shutdownAll();
241
+ return supersededStartResult();
242
+ }
221
243
 
222
244
  scanWorkspaceSentinels(this.#cwd);
223
245
 
224
- const service = new SessionLspService(manager);
225
- setSessionLspServiceState(this.#cwd, { kind: "ready", service });
246
+ const runtimeOwner = createWorkspaceLspRuntimeOwner(manager);
247
+ const workspaceRuntime = runtimeOwner.runtime;
248
+ setWorkspaceLspRuntimeState(this.#cwd, { kind: "ready", runtime: workspaceRuntime });
226
249
 
227
- if (this.#runtime) {
228
- registerPendingLspCapabilities(this.#runtime, this.#cwd, service);
250
+ if (this.#capabilityRuntime) {
251
+ registerPendingLspCapabilities(this.#capabilityRuntime, this.#cwd, workspaceRuntime);
229
252
  }
230
253
 
231
- const projectServers = manager.getKnownProjectServers(detectedServers);
254
+ const projectServers = workspaceRuntime.getProjectServers();
232
255
 
233
256
  this.#state = {
234
257
  kind: "ready",
235
- manager,
236
- service,
258
+ runtimeOwner,
259
+ workspaceRuntime,
237
260
  projectServers,
238
261
  detectedServers,
239
262
  settings,
240
263
  };
241
264
 
242
- const readinessGeneration = ++this.#readinessGeneration;
243
- void this.promoteSemanticReadiness(manager, detectedServers, readinessGeneration);
265
+ void this.promoteSemanticReadiness(workspaceRuntime, generation);
244
266
 
245
- return { kind: "ready", manager, service };
267
+ return { kind: "ready", runtime: workspaceRuntime };
246
268
  }
247
269
 
248
270
  private async promoteSemanticReadiness(
249
- manager: LspManager,
250
- detectedServers: DetectedProjectServer[],
271
+ workspaceRuntime: WorkspaceLspRuntime,
251
272
  readinessGeneration: number,
252
273
  ): Promise<void> {
253
274
  try {
254
- await manager.waitUntilWorkspaceReady();
275
+ const readiness = await workspaceRuntime.waitUntilReadyForWorkspace();
276
+ if (readiness.kind !== "ready") return;
255
277
  } catch {
256
- this.retractPendingCapabilities();
257
278
  return;
258
279
  }
259
280
 
260
- if (
261
- readinessGeneration !== this.#readinessGeneration ||
262
- this.#state.kind !== "ready" ||
263
- this.#state.manager !== manager
264
- ) {
265
- return;
266
- }
281
+ if (!this.isCurrentRuntime(workspaceRuntime, readinessGeneration)) return;
282
+ if (this.#state.kind !== "ready") return;
267
283
 
268
- this.#state.projectServers = manager.getKnownProjectServers(detectedServers);
269
- if (this.#runtime) {
270
- markLspCapabilitiesReady(this.#runtime, this.#cwd);
284
+ this.#state.projectServers = workspaceRuntime.getProjectServers();
285
+ if (this.#capabilityRuntime) {
286
+ markLspCapabilitiesReady(this.#capabilityRuntime, this.#cwd);
271
287
  }
272
288
  }
273
289
 
274
- /**
275
- * Retract the pending semantic registration when warm-up fails.
276
- * Leaves the workspace in unavailable state so callers see a definitive
277
- * failure rather than an orphaned pending capability.
278
- */
279
- private retractPendingCapabilities(): void {
280
- if (this.#runtime) {
281
- unregisterLspCapabilities(this.#runtime, this.#cwd);
282
- }
283
- setSessionLspServiceState(this.#cwd, {
284
- kind: "unavailable",
285
- reason: "LSP warm-up failed. Check code_health for server status.",
286
- });
290
+ private isCurrentRuntime(
291
+ workspaceRuntime: WorkspaceLspRuntime,
292
+ readinessGeneration: number,
293
+ ): boolean {
294
+ return (
295
+ readinessGeneration === this.#readinessGeneration &&
296
+ this.#state.kind === "ready" &&
297
+ this.#state.workspaceRuntime === workspaceRuntime
298
+ );
287
299
  }
288
300
 
289
301
  /**
@@ -296,16 +308,16 @@ export class LspRuntimeController {
296
308
  this.#readinessGeneration++;
297
309
  clearTsconfigCache();
298
310
 
299
- if (this.#runtime) {
300
- unregisterLspCapabilities(this.#runtime, this.#cwd);
311
+ if (this.#capabilityRuntime) {
312
+ unregisterLspCapabilities(this.#capabilityRuntime, this.#cwd);
301
313
  }
302
314
 
303
315
  if (this.#cwd) {
304
- clearSessionLspService(this.#cwd);
316
+ clearWorkspaceLspRuntime(this.#cwd);
305
317
  }
306
318
 
307
319
  if (this.#state.kind === "ready") {
308
- await this.#state.manager.shutdownAll();
320
+ await this.#state.runtimeOwner.shutdown();
309
321
  }
310
322
 
311
323
  this.#state = { kind: "initial" };
@@ -1,14 +1,14 @@
1
1
  /**
2
2
  * LSP-to-runtime capability registration.
3
3
  *
4
- * Adapts a SessionLspService into the runtime's SemanticProvider interface
4
+ * Adapts a WorkspaceLspRuntime into the runtime's SemanticProvider interface
5
5
  * and registers/unregisters it with the shared WorkspaceRuntime at session
6
6
  * lifecycle boundaries.
7
7
  */
8
8
 
9
9
  import type { WorkspaceRuntime } from "@mrclrchtr/supi-code-runtime/api";
10
10
  import { createLspSemanticProvider } from "../provider/lsp-semantic-provider.ts";
11
- import type { SessionLspService } from "./service-registry.ts";
11
+ import type { WorkspaceLspRuntime } from "./runtime-registry.ts";
12
12
 
13
13
  /**
14
14
  * Register LSP capabilities for a workspace cwd in pending state.
@@ -20,7 +20,7 @@ import type { SessionLspService } from "./service-registry.ts";
20
20
  export function registerPendingLspCapabilities(
21
21
  runtime: WorkspaceRuntime,
22
22
  cwd: string,
23
- service: SessionLspService,
23
+ service: WorkspaceLspRuntime,
24
24
  ): void {
25
25
  const provider = createLspSemanticProvider(service);
26
26
  runtime.registerSemanticPending(cwd, provider);
@@ -29,7 +29,7 @@ export function registerPendingLspCapabilities(
29
29
  /**
30
30
  * Register LSP capabilities for a workspace cwd as ready.
31
31
  *
32
- * Wraps SessionLspService into a SemanticProvider via the existing semantic
32
+ * Wraps WorkspaceLspRuntime into a SemanticProvider via the existing semantic
33
33
  * adapter and publishes it into the shared workspace runtime so that
34
34
  * code-intelligence and other consumers can discover semantic analysis
35
35
  * availability.
@@ -37,7 +37,7 @@ export function registerPendingLspCapabilities(
37
37
  export function registerLspCapabilities(
38
38
  runtime: WorkspaceRuntime,
39
39
  cwd: string,
40
- service: SessionLspService,
40
+ service: WorkspaceLspRuntime,
41
41
  ): void {
42
42
  const provider = createLspSemanticProvider(service);
43
43
  runtime.registerSemantic(cwd, provider);