@mrclrchtr/supi-code-intelligence 6.1.0 → 6.3.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 (141) hide show
  1. package/README.md +3 -1
  2. package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  3. package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  4. package/node_modules/@mrclrchtr/supi-core/src/debug-identity.ts +11 -0
  5. package/node_modules/@mrclrchtr/supi-core/src/debug.ts +5 -0
  6. package/node_modules/@mrclrchtr/supi-lsp/README.md +23 -3
  7. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  8. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  9. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-identity.ts +11 -0
  10. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug.ts +5 -0
  11. package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/README.md +52 -0
  12. package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/index.js +785 -129
  13. package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/legacy.js +660 -98
  14. package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/package.json +10 -4
  15. package/node_modules/@mrclrchtr/supi-lsp/package.json +4 -5
  16. package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +16 -7
  17. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-cache.ts +1 -1
  18. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-capabilities.ts +0 -5
  19. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-publication.ts +6 -3
  20. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-refresh.ts +2 -1
  21. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-timing.ts +5 -2
  22. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostics.ts +2 -1
  23. package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +17 -5
  24. package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +40 -18
  25. package/node_modules/@mrclrchtr/supi-lsp/src/config/config.ts +164 -30
  26. package/node_modules/@mrclrchtr/supi-lsp/src/config/lsp-settings.ts +9 -87
  27. package/node_modules/@mrclrchtr/supi-lsp/src/config/server-config.ts +0 -1
  28. package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-scope.ts +4 -5
  29. package/node_modules/@mrclrchtr/supi-lsp/src/config/types.ts +0 -29
  30. package/node_modules/@mrclrchtr/supi-lsp/src/debug-telemetry.ts +7 -21
  31. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-severity.ts +6 -0
  32. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-summary.ts +11 -14
  33. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/evidence.ts +0 -5
  34. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/stale-diagnostics.ts +0 -1
  35. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/workspace-sentinels.ts +29 -49
  36. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-client-state.ts +1 -1
  37. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +4 -8
  38. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-helpers.ts +1 -9
  39. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-process-crash-report.ts +106 -0
  40. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-project-info.ts +6 -3
  41. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +9 -1
  42. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-symbol.ts +26 -41
  43. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +577 -152
  44. package/node_modules/@mrclrchtr/supi-lsp/src/provider/lsp-semantic-provider.ts +4 -1
  45. package/node_modules/@mrclrchtr/supi-lsp/src/provider/semantic-symbol-mapper.ts +59 -28
  46. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-controller.ts +27 -19
  47. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +54 -0
  48. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registration.ts +0 -17
  49. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +74 -19
  50. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-transition-debug.ts +5 -2
  51. package/node_modules/@mrclrchtr/supi-lsp/src/session/scanner.ts +24 -21
  52. package/node_modules/@mrclrchtr/supi-lsp/src/session/workspace-lsp-runtime.ts +29 -3
  53. package/node_modules/@mrclrchtr/supi-lsp/src/summary.ts +19 -40
  54. package/node_modules/@mrclrchtr/supi-lsp/src/utils.ts +13 -19
  55. package/node_modules/@mrclrchtr/supi-lsp/src/workspace-path-policy.ts +296 -0
  56. package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +6 -18
  57. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  58. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  59. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug-identity.ts +11 -0
  60. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug.ts +5 -0
  61. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/README.md +29 -1
  62. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.cjs +99 -19
  63. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.cjs.map +4 -4
  64. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.js +94 -18
  65. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.js.map +4 -4
  66. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm +0 -0
  67. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm.map +14 -14
  68. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/package.json +13 -8
  69. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.cjs +97 -19
  70. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.cjs.map +4 -4
  71. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.d.cts +28 -8
  72. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.d.cts.map +1 -1
  73. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.d.ts +28 -8
  74. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.d.ts.map +1 -1
  75. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.js +92 -18
  76. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.js.map +4 -4
  77. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.wasm +0 -0
  78. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.wasm.map +17 -17
  79. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +7 -6
  80. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/kotlin/tree-sitter-kotlin.wasm +0 -0
  81. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/kotlin/tree-sitter-kotlin.wasm.json +2 -2
  82. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/sql/tree-sitter-sql.wasm +0 -0
  83. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/sql/tree-sitter-sql.wasm.json +2 -2
  84. package/node_modules/@mrclrchtr/supi-tree-sitter/src/api.ts +1 -11
  85. package/node_modules/@mrclrchtr/supi-tree-sitter/src/coordinates.ts +19 -0
  86. package/node_modules/@mrclrchtr/supi-tree-sitter/src/language.ts +5 -23
  87. package/node_modules/@mrclrchtr/supi-tree-sitter/src/operation-support.ts +3 -21
  88. package/node_modules/@mrclrchtr/supi-tree-sitter/src/provider/tree-sitter-provider.ts +5 -9
  89. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime-controller.ts +10 -20
  90. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/session.ts +7 -1
  91. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-client.ts +9 -5
  92. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-protocol.ts +134 -9
  93. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/call-name.ts +96 -0
  94. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/call-sites.ts +11 -12
  95. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callees.ts +39 -56
  96. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/exports.ts +9 -7
  97. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/js-binding-pattern.ts +26 -0
  98. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/node-at.ts +2 -21
  99. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline-html-sql.ts +144 -12
  100. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline.ts +50 -37
  101. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/scope.ts +138 -0
  102. package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +0 -7
  103. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/parsed-file-store.ts +1 -14
  104. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/runtime.ts +0 -10
  105. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/service.ts +6 -8
  106. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/worker-main.ts +34 -10
  107. package/package.json +5 -5
  108. package/src/analysis/capability/capability-warnings.ts +4 -22
  109. package/src/analysis/search/ast-scan-timing.ts +4 -2
  110. package/src/analysis/search/ast-scan.ts +1 -1
  111. package/src/api.ts +1 -0
  112. package/src/extension.ts +4 -2
  113. package/src/session/health-refresh.ts +26 -2
  114. package/src/session/health-types.ts +19 -0
  115. package/src/session/input/health-refactor.ts +16 -14
  116. package/src/session/refactor-types.ts +14 -1
  117. package/src/session/refactor-workflow.ts +21 -16
  118. package/src/substrate/lsp/lifecycle.ts +0 -3
  119. package/src/substrate/lsp/maintenance.ts +4 -5
  120. package/src/substrate/lsp/settings.ts +7 -9
  121. package/src/substrate/lsp/state.ts +0 -2
  122. package/src/substrate/workspace-provider-host.ts +2 -2
  123. package/src/tool/code_health/file-scope-markdown.ts +43 -0
  124. package/src/tool/code_health/markdown.ts +66 -66
  125. package/src/tool/code_health/refresh-outcome.ts +76 -0
  126. package/src/tool/code_health/refresh-status.ts +212 -23
  127. package/src/tool/code_health/result.ts +1 -0
  128. package/src/tool/code_health/tui.ts +18 -5
  129. package/src/tool/code_refactor_apply/guidance.ts +3 -2
  130. package/src/tool/code_refactor_plan/guidance.ts +1 -1
  131. package/src/tool/register.ts +5 -2
  132. package/src/tool/schemas.ts +21 -11
  133. package/node_modules/@mrclrchtr/supi-lsp/src/config/server-actions.ts +0 -59
  134. package/node_modules/@mrclrchtr/supi-lsp/src/pattern-matcher.ts +0 -24
  135. package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/generate-kotlin-wasm.mjs +0 -132
  136. package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/generate-sql-wasm.mjs +0 -158
  137. package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/vendor-wasm.mjs +0 -167
  138. package/node_modules/@mrclrchtr/supi-tree-sitter/src/index.ts +0 -42
  139. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime-registration.ts +0 -37
  140. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/service-registry.ts +0 -30
  141. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/structure.ts +0 -8
@@ -96,7 +96,10 @@ export function createLspSemanticProvider(lsp: WorkspaceLspRuntime): SemanticPro
96
96
  ): Promise<CodeQueryResult<CodeSymbol[]>> {
97
97
  const result = await lsp.workspaceSymbol(query, control, scopes);
98
98
  return mapCodeQueryResult(result, (results) =>
99
- results.map((symbol) => toCodeSymbol(symbol as SymbolInformation)),
99
+ results.flatMap((symbol) => {
100
+ const mapped = toCodeSymbol(symbol as SymbolInformation);
101
+ return mapped ? [mapped] : [];
102
+ }),
100
103
  );
101
104
  },
102
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
  }
@@ -45,11 +45,65 @@ export interface OutstandingDiagnosticSummaryEntry {
45
45
  hints: number;
46
46
  }
47
47
 
48
+ /** Stable outcome for one route in an explicit process-crash report. */
49
+ export type ProcessCrashRecoveryOutcome =
50
+ | "recovered"
51
+ | "skipped-no-retained-file"
52
+ | "recovery-failed"
53
+ | "recovery-exhausted";
54
+
55
+ /** Typed next action for a non-recovered process-crash route. */
56
+ export type ProcessCrashRecoveryNextAction = "use-exact-file" | "reload-workspace";
57
+
58
+ /** One bounded route entry in a process-crash recovery report. */
59
+ export interface ProcessCrashRecoveryEntry {
60
+ /** Configured server name. */
61
+ readonly name: string;
62
+ /** Workspace-relative route root. */
63
+ readonly root: string;
64
+ readonly outcome: ProcessCrashRecoveryOutcome;
65
+ /** Available action when the route was not recovered. */
66
+ readonly nextAction?: ProcessCrashRecoveryNextAction;
67
+ /** Bounded caught Error.message for a failed recovery, when available. */
68
+ readonly failureMessage?: string;
69
+ }
70
+
71
+ /** Maximum number of route entries retained in a process-crash report. */
72
+ export const MAX_PROCESS_CRASH_RECOVERY_ENTRIES = 16;
73
+
74
+ /** Bounded, route-specific result of one explicit process-crash demand. */
75
+ export interface ProcessCrashRecoveryReport {
76
+ /** Exact counts include entries omitted from the visible route list. */
77
+ readonly recoveredRoutes: number;
78
+ readonly skippedRoutes: number;
79
+ readonly failedRoutes: number;
80
+ readonly exhaustedRoutes: number;
81
+ /** At most 16 entries, ordered by action priority, root, and server name. */
82
+ readonly entries: readonly ProcessCrashRecoveryEntry[];
83
+ /** Exact number of route entries not included in {@link entries}. */
84
+ readonly omittedEntries: number;
85
+ }
86
+
87
+ /** Create the empty outcome for a pass with no process-crash demand. */
88
+ export function emptyProcessCrashRecoveryReport(): ProcessCrashRecoveryReport {
89
+ return {
90
+ recoveredRoutes: 0,
91
+ skippedRoutes: 0,
92
+ failedRoutes: 0,
93
+ exhaustedRoutes: 0,
94
+ entries: [],
95
+ omittedEntries: 0,
96
+ };
97
+ }
98
+
48
99
  /** Result from a workspace diagnostic recovery pass. */
49
100
  export interface RecoverDiagnosticsResult {
50
101
  /** Active clients targeted by the best-effort refresh, not confirmed successful refreshes. */
51
102
  attemptedClients: number;
103
+ /** Clients restarted by stale-diagnostic recovery, not process-crash recovery. */
52
104
  restartedClients: number;
105
+ /** Separate outcome for process-crash route recovery selected by this pass. */
106
+ processCrashRecovery: ProcessCrashRecoveryReport;
53
107
  /** Evidence collected by the refresh and recovery operations. */
54
108
  diagnosticEvidence: DiagnosticEvidenceSummary;
55
109
  /** 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
+ ProcessCrashRecoveryReport,
43
+ RecoverDiagnosticsResult,
44
+ } from "./runtime-diagnostics.ts";
34
45
  import type {
35
46
  RoutedMutationResponse,
36
47
  SemanticReadinessResult,
@@ -42,15 +53,21 @@ export type {
42
53
  ProcessCrashDiagnosticDemand,
43
54
  WorkspaceLspDiagnosticSurface,
44
55
  } from "./runtime-diagnostic-surface.ts";
45
- export type {
46
- DiagnosticEvidenceDocument,
47
- DiagnosticEvidenceStatus,
48
- DiagnosticEvidenceSummary,
49
- OutstandingDiagnosticSummaryEntry,
50
- RecoverDiagnosticsResult,
51
- WorkspaceDiagnosticReport,
52
- WorkspaceDiagnosticSnapshot,
53
- WorkspaceDiagnosticSummaryEntry,
56
+ export {
57
+ type DiagnosticEvidenceDocument,
58
+ type DiagnosticEvidenceStatus,
59
+ type DiagnosticEvidenceSummary,
60
+ emptyProcessCrashRecoveryReport,
61
+ MAX_PROCESS_CRASH_RECOVERY_ENTRIES,
62
+ type OutstandingDiagnosticSummaryEntry,
63
+ type ProcessCrashRecoveryEntry,
64
+ type ProcessCrashRecoveryNextAction,
65
+ type ProcessCrashRecoveryOutcome,
66
+ type ProcessCrashRecoveryReport,
67
+ type RecoverDiagnosticsResult,
68
+ type WorkspaceDiagnosticReport,
69
+ type WorkspaceDiagnosticSnapshot,
70
+ type WorkspaceDiagnosticSummaryEntry,
54
71
  } from "./runtime-diagnostics.ts";
55
72
  export type {
56
73
  RoutedMutationResponse,
@@ -67,6 +84,15 @@ function unavailableFileQuery<T>(operation: string, file: string): CodeQueryResu
67
84
  return unavailableCodeQuery(`No routed LSP client could complete ${operation} for ${file}.`);
68
85
  }
69
86
 
87
+ function hasProcessCrashRecovery(report: ProcessCrashRecoveryReport): boolean {
88
+ return (
89
+ report.recoveredRoutes > 0 ||
90
+ report.skippedRoutes > 0 ||
91
+ report.failedRoutes > 0 ||
92
+ report.exhaustedRoutes > 0
93
+ );
94
+ }
95
+
70
96
  /** Emit one aggregate tsconfig scope-decision event after a recovery pass. */
71
97
  function recordScopeDecisionEvent(manager: LspManager): void {
72
98
  try {
@@ -246,25 +272,40 @@ class DefaultWorkspaceLspRuntime implements WorkspaceLspRuntime {
246
272
  ): Promise<SemanticReadinessResult> {
247
273
  const resolvedPath = this.resolveFilePath(filePath);
248
274
  if (!this.manager.canServeFile(resolvedPath)) {
275
+ const processCrashRecovery = this.manager.getProcessCrashRecoveryReportForFile(resolvedPath);
249
276
  return {
250
277
  kind: "unavailable",
251
278
  reason: "No LSP client can serve this file",
279
+ ...(processCrashRecovery ? { processCrashRecovery } : {}),
252
280
  };
253
281
  }
254
282
 
283
+ let eagerProcessCrashRecovery: ProcessCrashRecoveryReport | undefined;
255
284
  const readiness = await raceReadinessValue(
256
- this.manager.waitUntilFileReady(resolvedPath, control),
285
+ this.manager.waitUntilFileReady(resolvedPath, control, (report) => {
286
+ eagerProcessCrashRecovery = report;
287
+ }),
257
288
  options.timeoutMs,
258
289
  control,
259
290
  );
260
- if (readiness.kind !== "resolved") return readiness;
261
- if (readiness.value === null) {
291
+ 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.
294
+ const currentProcessCrashRecovery =
295
+ this.manager.getProcessCrashRecoveryReportForFile(resolvedPath);
296
+ const processCrashRecovery = currentProcessCrashRecovery ?? eagerProcessCrashRecovery;
297
+ return processCrashRecovery ? { ...readiness, processCrashRecovery } : readiness;
298
+ }
299
+ const { client, processCrashRecovery } = readiness.value;
300
+ const recovery = hasProcessCrashRecovery(processCrashRecovery) ? { processCrashRecovery } : {};
301
+ if (!client) {
262
302
  return {
263
303
  kind: "unavailable",
264
304
  reason: "The routed LSP client could not be started for this file",
305
+ ...recovery,
265
306
  };
266
307
  }
267
- return { kind: "ready" };
308
+ return { kind: "ready", ...recovery };
268
309
  }
269
310
 
270
311
  /**
@@ -294,9 +335,22 @@ class DefaultWorkspaceLspRuntime implements WorkspaceLspRuntime {
294
335
  return this.manager.getKnownProjectServers([]);
295
336
  }
296
337
 
297
- /** Check whether the file can be served semantically for explicit LSP operations. */
338
+ /** Check whether automatic LSP work can use and serve the source file. */
298
339
  isSupportedSourceFile(filePath: string): boolean {
299
- return this.manager.canServeFile(this.resolveFilePath(filePath));
340
+ return this.manager.isSupportedSourceFile(this.resolveFilePath(filePath));
341
+ }
342
+
343
+ /** Inventory automatic workspace sentinel and source paths. */
344
+ scanWorkspaceSentinels(options: WorkspaceSentinelScanOptions = {}): Map<string, number> {
345
+ return this.manager.scanWorkspaceSentinels(options);
346
+ }
347
+
348
+ /** Refresh the automatic workspace sentinel and source inventory. */
349
+ syncWorkspaceSentinelSnapshot(
350
+ previous: Map<string, number>,
351
+ options: WorkspaceSentinelScanOptions = {},
352
+ ): WorkspaceSentinelSyncResult {
353
+ return this.manager.syncWorkspaceSentinelSnapshot(previous, options);
300
354
  }
301
355
 
302
356
  /** Track a file in its routed client without exposing that client. */
@@ -389,6 +443,7 @@ class DefaultWorkspaceLspRuntime implements WorkspaceLspRuntime {
389
443
  elapsedMs: result.elapsedMs,
390
444
  attemptedClients: result.attemptedClients,
391
445
  restartedClients: result.restartedClients,
446
+ processCrashRecovery: result.processCrashRecovery,
392
447
  attemptedServers: boundServerNames(result.attemptedServers ?? []),
393
448
  restartedServers: boundServerNames(result.restartedServers ?? []),
394
449
  ...(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. */
@@ -1,15 +1,19 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import { dedupeTopmostRoots, walkProject } from "@mrclrchtr/supi-core/project";
3
+ import { dedupeTopmostRoots } from "@mrclrchtr/supi-core/project";
4
4
  import type {
5
5
  DetectedProjectServer,
6
6
  LspConfig,
7
7
  MissingServer,
8
- ProjectServerInfo,
9
8
  ServerConfig,
10
9
  } from "../config/types.ts";
11
10
  import type { LspManager } from "../manager/manager.ts";
12
11
  import { commandExists } from "../utils.ts";
12
+ import {
13
+ type AutomaticLspPathPolicy,
14
+ createDefaultAutomaticLspPathPolicy,
15
+ walkAutomaticLspTree,
16
+ } from "../workspace-path-policy.ts";
13
17
 
14
18
  const DEFAULT_MAX_DEPTH = 3;
15
19
 
@@ -17,8 +21,14 @@ export function scanProjectCapabilities(
17
21
  config: LspConfig,
18
22
  cwd: string,
19
23
  maxDepth: number = DEFAULT_MAX_DEPTH,
24
+ policy: AutomaticLspPathPolicy = createDefaultAutomaticLspPathPolicy(cwd),
20
25
  ): DetectedProjectServer[] {
21
- const { markerMatches, extensionBasedServers } = collectServerHints(config, cwd, maxDepth);
26
+ const { markerMatches, extensionBasedServers } = collectServerHints(
27
+ config,
28
+ cwd,
29
+ maxDepth,
30
+ policy,
31
+ );
22
32
 
23
33
  return Object.entries(config.servers)
24
34
  .flatMap(([serverName, server]) => {
@@ -45,11 +55,12 @@ function collectServerHints(
45
55
  config: LspConfig,
46
56
  cwd: string,
47
57
  maxDepth: number,
58
+ policy: AutomaticLspPathPolicy,
48
59
  ): { markerMatches: Map<string, Set<string>>; extensionBasedServers: Set<string> } {
49
60
  const ctx: HintContext = { markerMatches: new Map(), extensionBasedServers: new Set() };
50
61
 
51
- walkProject(cwd, maxDepth, (directory, entryNames) => {
52
- inspectDirectory(directory, entryNames, config.servers, ctx);
62
+ walkAutomaticLspTree(policy, cwd, maxDepth, (directory, entries) => {
63
+ inspectDirectory(directory, new Set(entries.map((entry) => entry.name)), config.servers, ctx);
53
64
  });
54
65
 
55
66
  return { markerMatches: ctx.markerMatches, extensionBasedServers: ctx.extensionBasedServers };
@@ -103,15 +114,10 @@ function hasMatchingFile(directory: string, entryNames: Set<string>, fileTypes:
103
114
  export async function startDetectedServers(
104
115
  manager: LspManager,
105
116
  detected: DetectedProjectServer[],
117
+ policy: AutomaticLspPathPolicy = createDefaultAutomaticLspPathPolicy(manager.getCwd()),
106
118
  ): Promise<void> {
107
- await Promise.all(detected.map((entry) => manager.startServerForRoot(entry.name, entry.root)));
108
- }
109
-
110
- export function introspectCapabilities(
111
- manager: LspManager,
112
- detected: DetectedProjectServer[],
113
- ): ProjectServerInfo[] {
114
- return manager.getKnownProjectServers(detected);
119
+ const eligible = detected.filter((entry) => policy.isEligible(entry.root, "directory"));
120
+ await Promise.all(eligible.map((entry) => manager.startServerForRoot(entry.name, entry.root)));
115
121
  }
116
122
 
117
123
  /**
@@ -123,17 +129,14 @@ export function scanMissingServers(
123
129
  config: LspConfig,
124
130
  cwd: string,
125
131
  maxDepth: number = DEFAULT_MAX_DEPTH,
132
+ policy: AutomaticLspPathPolicy = createDefaultAutomaticLspPathPolicy(cwd),
126
133
  ): MissingServer[] {
127
134
  const foundExtensions = new Set<string>();
128
135
 
129
- walkProject(cwd, maxDepth, (directory, entryNames) => {
130
- for (const name of entryNames) {
131
- try {
132
- if (!fs.statSync(path.join(directory, name)).isFile()) continue;
133
- } catch {
134
- continue;
135
- }
136
- const ext = path.extname(name).slice(1).toLowerCase();
136
+ walkAutomaticLspTree(policy, cwd, maxDepth, (_directory, entries) => {
137
+ for (const entry of entries) {
138
+ if (!entry.isFile() && !entry.isSymbolicLink()) continue;
139
+ const ext = path.extname(entry.name).slice(1).toLowerCase();
137
140
  if (ext) foundExtensions.add(ext);
138
141
  }
139
142
  });
@@ -13,7 +13,12 @@ import type {
13
13
  WorkspaceEdit,
14
14
  WorkspaceSymbol,
15
15
  } from "../config/types.ts";
16
+ import type {
17
+ WorkspaceSentinelScanOptions,
18
+ WorkspaceSentinelSyncResult,
19
+ } from "../diagnostics/workspace-sentinels.ts";
16
20
  import type { WorkspaceLspDiagnosticSurface } from "./runtime-diagnostic-surface.ts";
21
+ import type { ProcessCrashRecoveryReport } from "./runtime-diagnostics.ts";
17
22
 
18
23
  export type WorkspaceLspRuntimeState =
19
24
  | { kind: "ready"; runtime: WorkspaceLspRuntime }
@@ -23,9 +28,22 @@ export type WorkspaceLspRuntimeState =
23
28
  | { kind: "unavailable"; reason: string };
24
29
 
25
30
  export type SemanticReadinessResult =
26
- | { kind: "ready" }
27
- | { kind: "timeout" }
28
- | { kind: "unavailable"; reason: string };
31
+ | {
32
+ kind: "ready";
33
+ /** Process-crash route recovery observed while establishing file readiness. */
34
+ processCrashRecovery?: ProcessCrashRecoveryReport;
35
+ }
36
+ | {
37
+ kind: "timeout";
38
+ /** Process-crash route recovery observed before the readiness timeout. */
39
+ processCrashRecovery?: ProcessCrashRecoveryReport;
40
+ }
41
+ | {
42
+ kind: "unavailable";
43
+ reason: string;
44
+ /** Process-crash route recovery observed before readiness became unavailable. */
45
+ processCrashRecovery?: ProcessCrashRecoveryReport;
46
+ };
29
47
 
30
48
  /** One mutation response and the exact provider roots from its semantic route. */
31
49
  export interface RoutedMutationResponse<T> {
@@ -96,7 +114,15 @@ export interface WorkspaceLspRuntime extends WorkspaceLspDiagnosticSurface {
96
114
  control?: CodeRequestControl,
97
115
  ): Promise<SemanticReadinessResult>;
98
116
  getProjectServers(): ProjectServerInfo[];
117
+ /** Check whether automatic LSP work can use and serve the source file. */
99
118
  isSupportedSourceFile(filePath: string): boolean;
119
+ /** Inventory policy-eligible workspace sentinels and optional source files. */
120
+ scanWorkspaceSentinels(options?: WorkspaceSentinelScanOptions): Map<string, number>;
121
+ /** Refresh one policy-eligible workspace sentinel and source inventory. */
122
+ syncWorkspaceSentinelSnapshot(
123
+ previous: Map<string, number>,
124
+ options?: WorkspaceSentinelScanOptions,
125
+ ): WorkspaceSentinelSyncResult;
100
126
  trackFile(filePath: string): Promise<boolean>;
101
127
  closeFile(filePath: string): void;
102
128
  pruneMissingFiles(): readonly string[];