@mrclrchtr/supi-code-intelligence 6.0.1 → 6.2.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 (147) hide show
  1. package/README.md +2 -2
  2. package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  3. package/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +5 -0
  4. package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  5. package/node_modules/@mrclrchtr/supi-core/src/debug-identity.ts +11 -0
  6. package/node_modules/@mrclrchtr/supi-core/src/debug.ts +5 -0
  7. package/node_modules/@mrclrchtr/supi-lsp/README.md +26 -4
  8. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  9. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +5 -0
  10. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  11. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-identity.ts +11 -0
  12. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug.ts +5 -0
  13. package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-jsonrpc/package.json +1 -1
  14. package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-languageserver-protocol/lib/common/protocol.colorProvider.d.ts +1 -1
  15. package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-languageserver-protocol/lib/common/protocol.d.ts +2 -2
  16. package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-languageserver-protocol/lib/common/protocol.fileOperations.d.ts +9 -9
  17. package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-languageserver-protocol/package.json +3 -3
  18. package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-languageserver-types/lib/esm/main.d.ts +2 -2
  19. package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-languageserver-types/lib/esm/main.js +1 -1
  20. package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-languageserver-types/lib/umd/main.d.ts +2 -2
  21. package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-languageserver-types/lib/umd/main.js +1 -1
  22. package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-languageserver-types/package.json +1 -1
  23. package/node_modules/@mrclrchtr/supi-lsp/package.json +4 -5
  24. package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +14 -7
  25. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-cache.ts +1 -1
  26. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-capabilities.ts +0 -5
  27. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-publication.ts +6 -3
  28. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-refresh.ts +2 -1
  29. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-timing.ts +5 -2
  30. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostics.ts +2 -1
  31. package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +23 -5
  32. package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +40 -18
  33. package/node_modules/@mrclrchtr/supi-lsp/src/config/config.ts +164 -30
  34. package/node_modules/@mrclrchtr/supi-lsp/src/config/lsp-settings.ts +9 -87
  35. package/node_modules/@mrclrchtr/supi-lsp/src/config/server-config.ts +8 -1
  36. package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-scope.ts +4 -5
  37. package/node_modules/@mrclrchtr/supi-lsp/src/config/types.ts +1 -29
  38. package/node_modules/@mrclrchtr/supi-lsp/src/debug-telemetry.ts +7 -21
  39. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-severity.ts +6 -0
  40. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-summary.ts +11 -14
  41. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/evidence.ts +0 -5
  42. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/stale-diagnostics.ts +0 -1
  43. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/workspace-sentinels.ts +29 -49
  44. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-client-state.ts +1 -1
  45. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +4 -8
  46. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-helpers.ts +1 -9
  47. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-project-info.ts +12 -6
  48. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +9 -1
  49. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-symbol.ts +26 -41
  50. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +849 -117
  51. package/node_modules/@mrclrchtr/supi-lsp/src/provider/lsp-semantic-provider.ts +6 -4
  52. package/node_modules/@mrclrchtr/supi-lsp/src/provider/semantic-symbol-mapper.ts +59 -28
  53. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-controller.ts +27 -19
  54. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostic-surface.ts +10 -0
  55. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +26 -0
  56. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registration.ts +0 -17
  57. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +118 -27
  58. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-transition-debug.ts +7 -2
  59. package/node_modules/@mrclrchtr/supi-lsp/src/session/scanner.ts +24 -21
  60. package/node_modules/@mrclrchtr/supi-lsp/src/session/workspace-lsp-runtime.ts +33 -5
  61. package/node_modules/@mrclrchtr/supi-lsp/src/summary.ts +19 -40
  62. package/node_modules/@mrclrchtr/supi-lsp/src/utils.ts +13 -19
  63. package/node_modules/@mrclrchtr/supi-lsp/src/workspace-path-policy.ts +287 -0
  64. package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +6 -18
  65. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  66. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +5 -0
  67. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  68. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug-identity.ts +11 -0
  69. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug.ts +5 -0
  70. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -5
  71. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/kotlin/tree-sitter-kotlin.wasm.json +1 -1
  72. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/sql/tree-sitter-sql.wasm.json +1 -1
  73. package/node_modules/@mrclrchtr/supi-tree-sitter/src/api.ts +1 -11
  74. package/node_modules/@mrclrchtr/supi-tree-sitter/src/coordinates.ts +19 -0
  75. package/node_modules/@mrclrchtr/supi-tree-sitter/src/language.ts +5 -23
  76. package/node_modules/@mrclrchtr/supi-tree-sitter/src/operation-support.ts +3 -21
  77. package/node_modules/@mrclrchtr/supi-tree-sitter/src/provider/tree-sitter-provider.ts +5 -9
  78. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime-controller.ts +10 -20
  79. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/session.ts +7 -1
  80. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-client.ts +9 -5
  81. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-protocol.ts +134 -9
  82. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/call-name.ts +96 -0
  83. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/call-sites.ts +11 -12
  84. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callees.ts +39 -56
  85. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/exports.ts +9 -7
  86. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/js-binding-pattern.ts +26 -0
  87. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/node-at.ts +2 -21
  88. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline-html-sql.ts +144 -12
  89. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline.ts +50 -37
  90. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/scope.ts +138 -0
  91. package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +0 -7
  92. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/parsed-file-store.ts +1 -14
  93. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/runtime.ts +0 -10
  94. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/service.ts +6 -8
  95. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/worker-main.ts +34 -10
  96. package/package.json +5 -5
  97. package/src/analysis/capability/capability-warnings.ts +4 -22
  98. package/src/analysis/health/recovery.ts +4 -0
  99. package/src/analysis/health/server-status.ts +64 -0
  100. package/src/analysis/health/signals.ts +1 -0
  101. package/src/analysis/provider.ts +2 -1
  102. package/src/analysis/search/ast-scan-timing.ts +4 -2
  103. package/src/analysis/search/ast-scan.ts +1 -1
  104. package/src/analysis/target/symbol.ts +28 -16
  105. package/src/analysis/target/types.ts +1 -0
  106. package/src/api.ts +1 -0
  107. package/src/extension.ts +4 -2
  108. package/src/session/find-workflow.ts +5 -12
  109. package/src/session/health-refresh.ts +30 -4
  110. package/src/session/health-types.ts +27 -2
  111. package/src/session/health-workflow.ts +5 -1
  112. package/src/session/input/health-refactor.ts +16 -14
  113. package/src/session/refactor-types.ts +14 -1
  114. package/src/session/refactor-workflow.ts +21 -16
  115. package/src/session/semantic-demand.ts +36 -0
  116. package/src/session/target-workflow.ts +9 -12
  117. package/src/substrate/lsp/lifecycle.ts +0 -3
  118. package/src/substrate/lsp/maintenance.ts +4 -5
  119. package/src/substrate/lsp/settings.ts +6 -9
  120. package/src/substrate/lsp/state.ts +0 -2
  121. package/src/substrate/workspace-provider-host.ts +2 -2
  122. package/src/tool/code_health/file-scope-markdown.ts +43 -0
  123. package/src/tool/code_health/guidance.ts +2 -2
  124. package/src/tool/code_health/markdown.ts +81 -69
  125. package/src/tool/code_health/refresh-outcome.ts +46 -0
  126. package/src/tool/code_health/refresh-status.ts +166 -23
  127. package/src/tool/code_health/result.ts +15 -5
  128. package/src/tool/code_health/spec.ts +2 -1
  129. package/src/tool/code_health/tui.ts +42 -8
  130. package/src/tool/code_refactor_apply/guidance.ts +3 -2
  131. package/src/tool/code_refactor_plan/guidance.ts +1 -1
  132. package/src/tool/register.ts +5 -2
  133. package/src/tool/result/errors.ts +1 -0
  134. package/src/tool/result/types.ts +6 -0
  135. package/src/tool/schemas.ts +21 -11
  136. package/src/ui/footer.ts +7 -5
  137. package/src/ui/status-command.ts +31 -0
  138. package/src/ui/status-overlay.ts +69 -23
  139. package/node_modules/@mrclrchtr/supi-lsp/src/config/server-actions.ts +0 -59
  140. package/node_modules/@mrclrchtr/supi-lsp/src/pattern-matcher.ts +0 -24
  141. package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/generate-kotlin-wasm.mjs +0 -132
  142. package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/generate-sql-wasm.mjs +0 -158
  143. package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/vendor-wasm.mjs +0 -167
  144. package/node_modules/@mrclrchtr/supi-tree-sitter/src/index.ts +0 -42
  145. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime-registration.ts +0 -37
  146. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/service-registry.ts +0 -30
  147. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/structure.ts +0 -8
@@ -92,12 +92,14 @@ export function createLspSemanticProvider(lsp: WorkspaceLspRuntime): SemanticPro
92
92
  async workspaceSymbols(
93
93
  query: string,
94
94
  control?: CodeRequestControl,
95
+ scopes?: readonly string[],
95
96
  ): Promise<CodeQueryResult<CodeSymbol[]>> {
96
- const result = control
97
- ? await lsp.workspaceSymbol(query, control)
98
- : await lsp.workspaceSymbol(query);
97
+ const result = await lsp.workspaceSymbol(query, control, scopes);
99
98
  return mapCodeQueryResult(result, (results) =>
100
- results.map((symbol) => toCodeSymbol(symbol as SymbolInformation)),
99
+ results.flatMap((symbol) => {
100
+ const mapped = toCodeSymbol(symbol as SymbolInformation);
101
+ return mapped ? [mapped] : [];
102
+ }),
101
103
  );
102
104
  },
103
105
 
@@ -30,20 +30,34 @@ function toCodeLocation(item: Location | LocationLink): CodeLocation | null {
30
30
  const loc = item as Record<string, unknown>;
31
31
  const uri = loc.uri ?? loc.targetUri;
32
32
  if (typeof uri !== "string") return null;
33
- const range = loc.targetSelectionRange ?? loc.targetRange ?? loc.range;
34
- if (!range || typeof range !== "object") return null;
35
- const r = range as { start: Record<string, unknown>; end: Record<string, unknown> };
36
- if (!r.start || !r.end) return null;
37
- return {
38
- uri,
39
- range: {
40
- start: {
41
- line: (r.start.line as number) ?? 0,
42
- character: (r.start.character as number) ?? 0,
43
- },
44
- end: { line: (r.end.line as number) ?? 0, character: (r.end.character as number) ?? 0 },
45
- },
46
- };
33
+ const range = toValidRange(loc.targetSelectionRange ?? loc.targetRange ?? loc.range);
34
+ return range ? { uri, range } : null;
35
+ }
36
+
37
+ function toValidRange(value: unknown): SourceRange | null {
38
+ if (!value || typeof value !== "object") return null;
39
+ const range = value as { start?: unknown; end?: unknown };
40
+ const start = toValidPosition(range.start);
41
+ const end = toValidPosition(range.end);
42
+ return start && end && !isPositionBefore(end, start) ? { start, end } : null;
43
+ }
44
+
45
+ function toValidPosition(value: unknown): { line: number; character: number } | null {
46
+ if (!value || typeof value !== "object") return null;
47
+ const position = value as Record<string, unknown>;
48
+ return Number.isInteger(position.line) &&
49
+ (position.line as number) >= 0 &&
50
+ Number.isInteger(position.character) &&
51
+ (position.character as number) >= 0
52
+ ? { line: position.line as number, character: position.character as number }
53
+ : null;
54
+ }
55
+
56
+ function isPositionBefore(
57
+ left: { line: number; character: number },
58
+ right: { line: number; character: number },
59
+ ): boolean {
60
+ return left.line < right.line || (left.line === right.line && left.character < right.character);
47
61
  }
48
62
 
49
63
  const SYMBOL_KIND_NAMES: Record<number, string> = {
@@ -102,8 +116,19 @@ function flattenDocumentSymbols(
102
116
  let document: DocumentSymbol | null = null;
103
117
  if (isSymbolInformation(sym)) information = sym;
104
118
  else document = sym;
105
- const declStart = document?.range.start ?? information?.location.range.start;
106
- if (!declStart) continue;
119
+ const declarationRange = toValidRange(document?.range ?? information?.location.range);
120
+ if (!declarationRange) {
121
+ if (document?.children?.length) {
122
+ result.push(
123
+ ...flattenDocumentSymbols(document.children, filePath, sym.name, {
124
+ sourceLines: context.sourceLines,
125
+ nesting: "nested",
126
+ }),
127
+ );
128
+ }
129
+ continue;
130
+ }
131
+ const declStart = declarationRange.start;
107
132
  const nameStart = document
108
133
  ? resolveDocumentSymbolNameStart(document, context.sourceLines)
109
134
  : null;
@@ -149,8 +174,9 @@ function resolveDocumentSymbolNameStart(
149
174
  symbol: DocumentSymbol,
150
175
  sourceLines: readonly string[] | null,
151
176
  ): { line: number; character: number } | null {
152
- const selectionStart = symbol.selectionRange?.start;
153
- if (!selectionStart) return null;
177
+ const selectionRange = toValidRange(symbol.selectionRange);
178
+ if (!selectionRange) return null;
179
+ const selectionStart = selectionRange.start;
154
180
  if (!sourceLines) return selectionStart;
155
181
  const sourceLine = sourceLines[selectionStart.line];
156
182
  if (sourceLine === undefined) return null;
@@ -160,25 +186,30 @@ function resolveDocumentSymbolNameStart(
160
186
  ) {
161
187
  return selectionStart;
162
188
  }
163
- const rangeStart = symbol.range?.start;
164
- const rangeEnd = symbol.range?.end;
165
- const from = rangeStart?.line === selectionStart.line ? rangeStart.character : 0;
166
- const until = rangeEnd?.line === selectionStart.line ? rangeEnd.character : sourceLine.length;
189
+ const declarationRange = toValidRange(symbol.range);
190
+ if (!declarationRange) return null;
191
+ const from =
192
+ declarationRange.start.line === selectionStart.line ? declarationRange.start.character : 0;
193
+ const until =
194
+ declarationRange.end.line === selectionStart.line
195
+ ? declarationRange.end.character
196
+ : sourceLine.length;
167
197
  const character = sourceLine.indexOf(symbol.name, from);
168
198
  if (character < 0 || character + symbol.name.length > until) return null;
169
199
  return { line: selectionStart.line, character };
170
200
  }
171
201
 
172
- /** Map one workspace symbol to the shared representation. */
173
- export function toCodeSymbol(sym: SymbolInformation): CodeSymbol {
174
- const start = sym.location?.range?.start;
202
+ /** Map one valid workspace symbol to the shared representation. */
203
+ export function toCodeSymbol(sym: SymbolInformation): CodeSymbol | null {
204
+ const location = toCodeLocation(sym.location);
205
+ if (!location) return null;
175
206
  return {
176
207
  name: sym.name,
177
208
  kind: symbolKindName(sym.kind),
178
- file: uriToFile(sym.location?.uri ?? ""),
209
+ file: uriToFile(location.uri),
179
210
  declarationAnchor: {
180
- line: start ? start.line + 1 : 0,
181
- character: start ? start.character + 1 : 0,
211
+ line: location.range.start.line + 1,
212
+ character: location.range.start.character + 1,
182
213
  },
183
214
  container: sym.containerName ?? null,
184
215
  };
@@ -9,14 +9,19 @@
9
9
 
10
10
  // biome-ignore lint/style/noExcessiveLinesPerFile: session lifecycle, capability projection, and telemetry stay in one controller.
11
11
  import type { WorkspaceRuntime } from "@mrclrchtr/supi-code-runtime/api";
12
- import { recordDebugEvent } from "@mrclrchtr/supi-core/debug";
12
+ import {
13
+ recordDebugEvent,
14
+ truncateDebugIdentity as truncateIdentity,
15
+ } from "@mrclrchtr/supi-core/debug";
13
16
  import { loadConfig } from "../config/config.ts";
14
17
  import { type LspSettings, loadLspSettings } from "../config/lsp-settings.ts";
15
18
  import { clearTsconfigCache } from "../config/tsconfig-scope.ts";
16
19
  import type { DetectedProjectServer, LspConfig, ProjectServerInfo } from "../config/types.ts";
17
- import { truncateIdentity } from "../debug-telemetry.ts";
18
- import { scanWorkspaceSentinels } from "../diagnostics/workspace-sentinels.ts";
19
20
  import { LspManager, type ManagerLifecycleTransition } from "../manager/manager.ts";
21
+ import {
22
+ type AutomaticLspPathPolicy,
23
+ createAutomaticLspPathPolicy,
24
+ } from "../workspace-path-policy.ts";
20
25
  import {
21
26
  markLspCapabilitiesReady,
22
27
  registerPendingLspCapabilities,
@@ -58,6 +63,7 @@ interface LspControllerReady {
58
63
  projectServers: ProjectServerInfo[];
59
64
  detectedServers: DetectedProjectServer[];
60
65
  settings: LspSettings;
66
+ automaticPathPolicy: AutomaticLspPathPolicy;
61
67
  }
62
68
 
63
69
  interface LspControllerDisabled {
@@ -207,7 +213,6 @@ export class LspRuntimeController {
207
213
  *
208
214
  * Always attempts detected servers unless they were explicitly disabled
209
215
  * per language via `lsp.servers.<language>.enabled: false`.
210
- * The global `lsp.enabled` and `lsp.active` keys are deprecated and ignored.
211
216
  *
212
217
  * Returns the start result and updates the controller's state.
213
218
  */
@@ -220,11 +225,6 @@ export class LspRuntimeController {
220
225
  if (generation !== this.#readinessGeneration) return supersededStartResult();
221
226
 
222
227
  const lspSettings = loadLspSettings(this.#cwd);
223
- // Note: lspSettings.enabled is ignored — the global switch is deprecated.
224
- // Per-language `lsp.servers.<language>.enabled: false` is the supported
225
- // way to opt out and is already handled by loadConfig.
226
- // lspSettings.active is also ignored — the allowlist is deprecated.
227
-
228
228
  const config = loadConfig(this.#cwd);
229
229
 
230
230
  try {
@@ -287,27 +287,35 @@ export class LspRuntimeController {
287
287
  if (Object.keys(config.servers).length === 0) return this.setDisabled(generation);
288
288
  this.#state = { kind: "pending" };
289
289
 
290
+ const automaticPathPolicy = createAutomaticLspPathPolicy(this.#cwd, settings.exclude);
290
291
  let manager: LspManager;
291
- manager = new LspManager(config, this.#cwd, (transition) => {
292
- this.handleManagerLifecycle(manager, generation, transition);
293
- });
292
+ manager = new LspManager(
293
+ config,
294
+ this.#cwd,
295
+ (transition) => {
296
+ this.handleManagerLifecycle(manager, generation, transition);
297
+ },
298
+ automaticPathPolicy,
299
+ );
294
300
  this.#activeManager = manager;
295
301
  this.#latestManagerTransition = null;
296
302
  this.publishLifecycle("startup", false, []);
297
- manager.setExcludePatterns(settings.exclude);
298
303
  setWorkspaceLspRuntimeState(this.#cwd, { kind: "pending" });
299
304
 
300
- const detectedServers = scanProjectCapabilities(config, this.#cwd);
305
+ const detectedServers = scanProjectCapabilities(
306
+ config,
307
+ this.#cwd,
308
+ undefined,
309
+ automaticPathPolicy,
310
+ );
301
311
  manager.registerDetectedServers(detectedServers);
302
- await startDetectedServers(manager, detectedServers);
312
+ await startDetectedServers(manager, detectedServers, automaticPathPolicy);
303
313
  if (generation !== this.#readinessGeneration) {
304
314
  if (this.#activeManager === manager) this.#activeManager = null;
305
315
  await manager.shutdownAll();
306
316
  return supersededStartResult();
307
317
  }
308
318
 
309
- scanWorkspaceSentinels(this.#cwd);
310
-
311
319
  const runtimeOwner = createWorkspaceLspRuntimeOwner(manager);
312
320
  const workspaceRuntime = runtimeOwner.runtime;
313
321
  setWorkspaceLspRuntimeState(this.#cwd, { kind: "ready", runtime: workspaceRuntime });
@@ -333,6 +341,7 @@ export class LspRuntimeController {
333
341
  projectServers,
334
342
  detectedServers,
335
343
  settings,
344
+ automaticPathPolicy,
336
345
  };
337
346
 
338
347
  this.projectSemanticReadiness(workspaceRuntime, semanticReady);
@@ -397,7 +406,6 @@ export class LspRuntimeController {
397
406
  projectServers: projectServers.map((server) => ({
398
407
  ...server,
399
408
  fileTypes: [...server.fileTypes],
400
- supportedActions: [...server.supportedActions],
401
409
  openFiles: [...server.openFiles],
402
410
  })),
403
411
  };
@@ -452,6 +460,6 @@ export class LspRuntimeController {
452
460
  getMissingServers(): Array<{ name: string; command: string }> {
453
461
  if (this.#state.kind !== "ready") return [];
454
462
  const config = loadConfig(this.#cwd);
455
- return scanMissingServers(config, this.#cwd);
463
+ return scanMissingServers(config, this.#cwd, undefined, this.#state.automaticPathPolicy);
456
464
  }
457
465
  }
@@ -8,6 +8,14 @@ import type {
8
8
  WorkspaceDiagnosticSummaryEntry,
9
9
  } from "./runtime-diagnostics.ts";
10
10
 
11
+ /**
12
+ * Explicit diagnostic demand that can recover previously running crashed
13
+ * routes. Omitted scopes include every route with retained tracked files.
14
+ */
15
+ export interface ProcessCrashDiagnosticDemand {
16
+ readonly scopes?: readonly string[];
17
+ }
18
+
11
19
  /** Diagnostic and recovery operations owned by a workspace LSP runtime. */
12
20
  export interface WorkspaceLspDiagnosticSurface {
13
21
  fileDiagnostics(
@@ -37,6 +45,8 @@ export interface WorkspaceLspDiagnosticSurface {
37
45
  quietMs?: number;
38
46
  /** Evidence from a refresh the caller already completed; skips this pass's own refresh when no watched-file changes apply. */
39
47
  initialEvidence?: DiagnosticEvidenceSummary;
48
+ /** Explicit demand to recover crashed routes with tracked files in scope. */
49
+ processCrashDemand?: ProcessCrashDiagnosticDemand;
40
50
  control?: CodeRequestControl;
41
51
  }): Promise<RecoverDiagnosticsResult>;
42
52
  }
@@ -45,11 +45,37 @@ export interface OutstandingDiagnosticSummaryEntry {
45
45
  hints: number;
46
46
  }
47
47
 
48
+ /**
49
+ * Outcome of explicit process-crash recovery demand in one diagnostic pass.
50
+ *
51
+ * Routes are counted rather than client generations because process-crash
52
+ * recovery owns one budget per LSP route. `attemptedRoutes` is the number of
53
+ * crashed routes selected by the demand; a route can be failed already or
54
+ * have a shared replacement in progress when the demand observes it.
55
+ */
56
+ export interface ProcessCrashRecoverySummary {
57
+ attemptedRoutes: number;
58
+ recoveredRoutes: number;
59
+ failedRoutes: number;
60
+ }
61
+
62
+ /** Create the empty outcome for a pass with no process-crash demand. */
63
+ export function emptyProcessCrashRecoverySummary(): ProcessCrashRecoverySummary {
64
+ return {
65
+ attemptedRoutes: 0,
66
+ recoveredRoutes: 0,
67
+ failedRoutes: 0,
68
+ };
69
+ }
70
+
48
71
  /** Result from a workspace diagnostic recovery pass. */
49
72
  export interface RecoverDiagnosticsResult {
50
73
  /** Active clients targeted by the best-effort refresh, not confirmed successful refreshes. */
51
74
  attemptedClients: number;
75
+ /** Clients restarted by stale-diagnostic recovery, not process-crash recovery. */
52
76
  restartedClients: number;
77
+ /** Separate outcome for process-crash route recovery selected by this pass. */
78
+ processCrashRecovery: ProcessCrashRecoverySummary;
53
79
  /** Evidence collected by the refresh and recovery operations. */
54
80
  diagnosticEvidence: DiagnosticEvidenceSummary;
55
81
  /** Final diagnostic report captured after all refresh and recovery work. */
@@ -26,23 +26,6 @@ export function registerPendingLspCapabilities(
26
26
  runtime.registerSemanticPending(cwd, provider);
27
27
  }
28
28
 
29
- /**
30
- * Register LSP capabilities for a workspace cwd as ready.
31
- *
32
- * Wraps WorkspaceLspRuntime into a SemanticProvider via the existing semantic
33
- * adapter and publishes it into the shared workspace runtime so that
34
- * code-intelligence and other consumers can discover semantic analysis
35
- * availability.
36
- */
37
- export function registerLspCapabilities(
38
- runtime: WorkspaceRuntime,
39
- cwd: string,
40
- service: WorkspaceLspRuntime,
41
- ): void {
42
- const provider = createLspSemanticProvider(service);
43
- runtime.registerSemantic(cwd, provider);
44
- }
45
-
46
29
  /** Promote an already-registered pending semantic provider to ready. */
47
30
  export function markLspCapabilitiesReady(runtime: WorkspaceRuntime, cwd: string): void {
48
31
  runtime.markSemanticReady(cwd);
@@ -9,7 +9,11 @@ import {
9
9
  throwIfCodeRequestInterrupted,
10
10
  unavailableCodeQuery,
11
11
  } from "@mrclrchtr/supi-code-runtime/api";
12
- import { recordDebugEvent } from "@mrclrchtr/supi-core/debug";
12
+ import {
13
+ recordDebugEvent,
14
+ truncateDebugIdentity as truncateIdentity,
15
+ } from "@mrclrchtr/supi-core/debug";
16
+ import { resolveToolPath as resolveSessionPath } from "@mrclrchtr/supi-core/path";
13
17
  import { createSessionStateRegistry } from "@mrclrchtr/supi-core/session";
14
18
  import type {
15
19
  CodeAction,
@@ -26,11 +30,18 @@ import type {
26
30
  WorkspaceEdit,
27
31
  WorkspaceSymbol,
28
32
  } from "../config/types.ts";
29
- import { boundServerNames, truncateIdentity } from "../debug-telemetry.ts";
33
+ import { boundServerNames } from "../debug-telemetry.ts";
34
+ import type {
35
+ WorkspaceSentinelScanOptions,
36
+ WorkspaceSentinelSyncResult,
37
+ } from "../diagnostics/workspace-sentinels.ts";
30
38
  import type { LspManager } from "../manager/manager.ts";
31
- import { resolveSessionPath } from "../utils.ts";
32
39
  import { raceReadinessValue } from "./readiness.ts";
33
- import type { DiagnosticEvidenceSummary, RecoverDiagnosticsResult } from "./runtime-diagnostics.ts";
40
+ import type {
41
+ DiagnosticEvidenceSummary,
42
+ ProcessCrashRecoverySummary,
43
+ RecoverDiagnosticsResult,
44
+ } from "./runtime-diagnostics.ts";
34
45
  import type {
35
46
  RoutedMutationResponse,
36
47
  SemanticReadinessResult,
@@ -38,16 +49,21 @@ import type {
38
49
  WorkspaceLspRuntimeState,
39
50
  } from "./workspace-lsp-runtime.ts";
40
51
 
41
- export type { WorkspaceLspDiagnosticSurface } from "./runtime-diagnostic-surface.ts";
42
52
  export type {
43
- DiagnosticEvidenceDocument,
44
- DiagnosticEvidenceStatus,
45
- DiagnosticEvidenceSummary,
46
- OutstandingDiagnosticSummaryEntry,
47
- RecoverDiagnosticsResult,
48
- WorkspaceDiagnosticReport,
49
- WorkspaceDiagnosticSnapshot,
50
- WorkspaceDiagnosticSummaryEntry,
53
+ ProcessCrashDiagnosticDemand,
54
+ WorkspaceLspDiagnosticSurface,
55
+ } from "./runtime-diagnostic-surface.ts";
56
+ export {
57
+ type DiagnosticEvidenceDocument,
58
+ type DiagnosticEvidenceStatus,
59
+ type DiagnosticEvidenceSummary,
60
+ emptyProcessCrashRecoverySummary,
61
+ type OutstandingDiagnosticSummaryEntry,
62
+ type ProcessCrashRecoverySummary,
63
+ type RecoverDiagnosticsResult,
64
+ type WorkspaceDiagnosticReport,
65
+ type WorkspaceDiagnosticSnapshot,
66
+ type WorkspaceDiagnosticSummaryEntry,
51
67
  } from "./runtime-diagnostics.ts";
52
68
  export type {
53
69
  RoutedMutationResponse,
@@ -64,6 +80,29 @@ function unavailableFileQuery<T>(operation: string, file: string): CodeQueryResu
64
80
  return unavailableCodeQuery(`No routed LSP client could complete ${operation} for ${file}.`);
65
81
  }
66
82
 
83
+ function hasProcessCrashRecovery(summary: ProcessCrashRecoverySummary): boolean {
84
+ return summary.attemptedRoutes > 0 || summary.recoveredRoutes > 0 || summary.failedRoutes > 0;
85
+ }
86
+
87
+ /**
88
+ * Combine the eager and current state for one readiness wait.
89
+ * A current failure replaces the older pending view; max avoids double-counting
90
+ * one route when both views describe the same recovery attempt.
91
+ */
92
+ function mergeProcessCrashRecoverySummaries(
93
+ eager: ProcessCrashRecoverySummary | undefined,
94
+ current: ProcessCrashRecoverySummary | null,
95
+ ): ProcessCrashRecoverySummary | undefined {
96
+ if (!eager) return current ?? undefined;
97
+ if (!current) return eager;
98
+ if (current.failedRoutes > 0) return current;
99
+ return {
100
+ attemptedRoutes: Math.max(eager.attemptedRoutes, current.attemptedRoutes),
101
+ recoveredRoutes: Math.max(eager.recoveredRoutes, current.recoveredRoutes),
102
+ failedRoutes: Math.max(eager.failedRoutes, current.failedRoutes),
103
+ };
104
+ }
105
+
67
106
  /** Emit one aggregate tsconfig scope-decision event after a recovery pass. */
68
107
  function recordScopeDecisionEvent(manager: LspManager): void {
69
108
  try {
@@ -107,7 +146,10 @@ class DefaultWorkspaceLspRuntime implements WorkspaceLspRuntime {
107
146
  control?: CodeRequestControl,
108
147
  ): Promise<CodeQueryResult<Hover | null>> {
109
148
  const resolvedPath = this.resolveFilePath(filePath);
110
- const client = await this.manager.ensureFileOpen(resolvedPath);
149
+ const client = await this.manager.ensureFileOpen(resolvedPath, {
150
+ recoverProcessCrash: true,
151
+ control,
152
+ });
111
153
  if (!client) return unavailableFileQuery("hover", resolvedPath);
112
154
  return client.hover(resolvedPath, position, control);
113
155
  }
@@ -118,7 +160,10 @@ class DefaultWorkspaceLspRuntime implements WorkspaceLspRuntime {
118
160
  control?: CodeRequestControl,
119
161
  ): Promise<CodeQueryResult<Location | Location[] | LocationLink[] | null>> {
120
162
  const resolvedPath = this.resolveFilePath(filePath);
121
- const client = await this.manager.ensureFileOpen(resolvedPath);
163
+ const client = await this.manager.ensureFileOpen(resolvedPath, {
164
+ recoverProcessCrash: true,
165
+ control,
166
+ });
122
167
  if (!client) return unavailableFileQuery("definition", resolvedPath);
123
168
  return client.definition(resolvedPath, position, control);
124
169
  }
@@ -129,7 +174,10 @@ class DefaultWorkspaceLspRuntime implements WorkspaceLspRuntime {
129
174
  control?: CodeRequestControl,
130
175
  ): Promise<CodeQueryResult<Location[]>> {
131
176
  const resolvedPath = this.resolveFilePath(filePath);
132
- const client = await this.manager.ensureFileOpen(resolvedPath);
177
+ const client = await this.manager.ensureFileOpen(resolvedPath, {
178
+ recoverProcessCrash: true,
179
+ control,
180
+ });
133
181
  if (!client) return unavailableFileQuery("references", resolvedPath);
134
182
  return mapCodeQueryResult(
135
183
  await client.references(resolvedPath, position, control),
@@ -143,7 +191,10 @@ class DefaultWorkspaceLspRuntime implements WorkspaceLspRuntime {
143
191
  control?: CodeRequestControl,
144
192
  ): Promise<CodeQueryResult<Location | Location[] | LocationLink[] | null>> {
145
193
  const resolvedPath = this.resolveFilePath(filePath);
146
- const client = await this.manager.ensureFileOpen(resolvedPath);
194
+ const client = await this.manager.ensureFileOpen(resolvedPath, {
195
+ recoverProcessCrash: true,
196
+ control,
197
+ });
147
198
  if (!client) return unavailableFileQuery("implementation", resolvedPath);
148
199
  return client.implementation(resolvedPath, position, control);
149
200
  }
@@ -153,7 +204,10 @@ class DefaultWorkspaceLspRuntime implements WorkspaceLspRuntime {
153
204
  control?: CodeRequestControl,
154
205
  ): Promise<CodeQueryResult<DocumentSymbol[] | SymbolInformation[]>> {
155
206
  const resolvedPath = this.resolveFilePath(filePath);
156
- const client = await this.manager.ensureFileOpen(resolvedPath);
207
+ const client = await this.manager.ensureFileOpen(resolvedPath, {
208
+ recoverProcessCrash: true,
209
+ control,
210
+ });
157
211
  if (!client) return unavailableFileQuery("document symbols", resolvedPath);
158
212
  return mapCodeQueryResult(
159
213
  await client.documentSymbols(resolvedPath, control),
@@ -164,8 +218,9 @@ class DefaultWorkspaceLspRuntime implements WorkspaceLspRuntime {
164
218
  async workspaceSymbol(
165
219
  query: string,
166
220
  control?: CodeRequestControl,
221
+ scopes?: readonly string[],
167
222
  ): Promise<CodeQueryResult<SymbolInformation[] | WorkspaceSymbol[]>> {
168
- return this.manager.workspaceSymbol(query, control);
223
+ return this.manager.workspaceSymbol(query, control, scopes);
169
224
  }
170
225
 
171
226
  async rename(
@@ -175,7 +230,10 @@ class DefaultWorkspaceLspRuntime implements WorkspaceLspRuntime {
175
230
  control?: CodeRequestControl,
176
231
  ): Promise<RoutedMutationResponse<WorkspaceEdit | null> | null> {
177
232
  const resolvedPath = this.resolveFilePath(filePath);
178
- const client = await this.manager.ensureFileOpen(resolvedPath);
233
+ const client = await this.manager.ensureFileOpen(resolvedPath, {
234
+ recoverProcessCrash: true,
235
+ control,
236
+ });
179
237
  if (!client) return null;
180
238
  return {
181
239
  value: await client.rename(resolvedPath, position, newName, control),
@@ -193,7 +251,10 @@ class DefaultWorkspaceLspRuntime implements WorkspaceLspRuntime {
193
251
  control?: CodeRequestControl,
194
252
  ): Promise<RoutedMutationResponse<CodeAction[] | null> | null> {
195
253
  const resolvedPath = this.resolveFilePath(filePath);
196
- const client = await this.manager.ensureFileOpen(resolvedPath);
254
+ const client = await this.manager.ensureFileOpen(resolvedPath, {
255
+ recoverProcessCrash: true,
256
+ control,
257
+ });
197
258
  if (!client) return null;
198
259
 
199
260
  const range = isRange(positionOrRange)
@@ -221,25 +282,39 @@ class DefaultWorkspaceLspRuntime implements WorkspaceLspRuntime {
221
282
  ): Promise<SemanticReadinessResult> {
222
283
  const resolvedPath = this.resolveFilePath(filePath);
223
284
  if (!this.manager.canServeFile(resolvedPath)) {
285
+ const processCrashRecovery = this.manager.getProcessCrashRecoverySummaryForFile(resolvedPath);
224
286
  return {
225
287
  kind: "unavailable",
226
288
  reason: "No LSP client can serve this file",
289
+ ...(processCrashRecovery ? { processCrashRecovery } : {}),
227
290
  };
228
291
  }
229
292
 
293
+ let eagerProcessCrashRecovery: ProcessCrashRecoverySummary | undefined;
230
294
  const readiness = await raceReadinessValue(
231
- this.manager.waitUntilFileReady(resolvedPath, control),
295
+ this.manager.waitUntilFileReady(resolvedPath, control, (summary) => {
296
+ eagerProcessCrashRecovery = summary;
297
+ }),
232
298
  options.timeoutMs,
233
299
  control,
234
300
  );
235
- if (readiness.kind !== "resolved") return readiness;
236
- if (readiness.value === null) {
301
+ if (readiness.kind !== "resolved") {
302
+ const processCrashRecovery = mergeProcessCrashRecoverySummaries(
303
+ eagerProcessCrashRecovery,
304
+ this.manager.getProcessCrashRecoverySummaryForFile(resolvedPath),
305
+ );
306
+ return processCrashRecovery ? { ...readiness, processCrashRecovery } : readiness;
307
+ }
308
+ const { client, processCrashRecovery } = readiness.value;
309
+ const recovery = hasProcessCrashRecovery(processCrashRecovery) ? { processCrashRecovery } : {};
310
+ if (!client) {
237
311
  return {
238
312
  kind: "unavailable",
239
313
  reason: "The routed LSP client could not be started for this file",
314
+ ...recovery,
240
315
  };
241
316
  }
242
- return { kind: "ready" };
317
+ return { kind: "ready", ...recovery };
243
318
  }
244
319
 
245
320
  /**
@@ -269,9 +344,22 @@ class DefaultWorkspaceLspRuntime implements WorkspaceLspRuntime {
269
344
  return this.manager.getKnownProjectServers([]);
270
345
  }
271
346
 
272
- /** Check whether the file can be served semantically for explicit LSP operations. */
347
+ /** Check whether automatic LSP work can use and serve the source file. */
273
348
  isSupportedSourceFile(filePath: string): boolean {
274
- return this.manager.canServeFile(this.resolveFilePath(filePath));
349
+ return this.manager.isSupportedSourceFile(this.resolveFilePath(filePath));
350
+ }
351
+
352
+ /** Inventory automatic workspace sentinel and source paths. */
353
+ scanWorkspaceSentinels(options: WorkspaceSentinelScanOptions = {}): Map<string, number> {
354
+ return this.manager.scanWorkspaceSentinels(options);
355
+ }
356
+
357
+ /** Refresh the automatic workspace sentinel and source inventory. */
358
+ syncWorkspaceSentinelSnapshot(
359
+ previous: Map<string, number>,
360
+ options: WorkspaceSentinelScanOptions = {},
361
+ ): WorkspaceSentinelSyncResult {
362
+ return this.manager.syncWorkspaceSentinelSnapshot(previous, options);
275
363
  }
276
364
 
277
365
  /** Track a file in its routed client without exposing that client. */
@@ -345,6 +433,8 @@ class DefaultWorkspaceLspRuntime implements WorkspaceLspRuntime {
345
433
  quietMs?: number;
346
434
  /** Evidence from a refresh the caller already completed; skips this pass's own refresh when no watched-file changes apply. */
347
435
  initialEvidence?: DiagnosticEvidenceSummary;
436
+ /** Explicit demand to recover crashed routes with tracked files in scope. */
437
+ processCrashDemand?: import("./runtime-diagnostic-surface.ts").ProcessCrashDiagnosticDemand;
348
438
  control?: CodeRequestControl;
349
439
  }): Promise<RecoverDiagnosticsResult> {
350
440
  const recoveryStartedAt = Date.now();
@@ -362,6 +452,7 @@ class DefaultWorkspaceLspRuntime implements WorkspaceLspRuntime {
362
452
  elapsedMs: result.elapsedMs,
363
453
  attemptedClients: result.attemptedClients,
364
454
  restartedClients: result.restartedClients,
455
+ processCrashRecovery: result.processCrashRecovery,
365
456
  attemptedServers: boundServerNames(result.attemptedServers ?? []),
366
457
  restartedServers: boundServerNames(result.restartedServers ?? []),
367
458
  ...(result.restartReason ? { reason: result.restartReason } : {}),
@@ -1,6 +1,9 @@
1
- import { recordDebugEvent } from "@mrclrchtr/supi-core/debug";
1
+ import {
2
+ recordDebugEvent,
3
+ truncateDebugIdentity as truncateIdentity,
4
+ } from "@mrclrchtr/supi-core/debug";
2
5
  import type { ProjectServerInfo } from "../config/types.ts";
3
- import { MAX_SERVERS, truncateIdentity } from "../debug-telemetry.ts";
6
+ import { MAX_SERVERS } from "../debug-telemetry.ts";
4
7
  import type { LspRuntimeTransition } from "./runtime-controller.ts";
5
8
 
6
9
  /** One bounded server entry in a runtime-transition payload. */
@@ -8,6 +11,7 @@ export interface TransitionServerEntry {
8
11
  readonly name: string;
9
12
  readonly status: ProjectServerInfo["status"];
10
13
  readonly ready: boolean;
14
+ readonly statusReason?: NonNullable<ProjectServerInfo["statusReason"]>;
11
15
  }
12
16
 
13
17
  /** Bound a project-server snapshot to MAX_SERVERS name/status/ready entries. */
@@ -18,6 +22,7 @@ function boundedServerEntries(
18
22
  name: truncateIdentity(server.name),
19
23
  status: server.status,
20
24
  ready: server.ready,
25
+ ...(server.statusReason ? { statusReason: server.statusReason } : {}),
21
26
  }));
22
27
  }
23
28