@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
@@ -32,7 +32,6 @@ export function assessStaleDiagnostics(
32
32
  }
33
33
 
34
34
  export function isLikelyStaleDiagnostic(diagnostic: Diagnostic): boolean {
35
- if (diagnostic.severity === undefined) return false;
36
35
  if (diagnostic.code !== undefined) {
37
36
  if (typeof diagnostic.code === "number" && MODULE_RESOLUTION_CODES.has(diagnostic.code)) {
38
37
  return true;
@@ -1,9 +1,13 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
+ import { fileToUri, uriToFile } from "@mrclrchtr/supi-core/path";
3
4
  import { FileChangeType, type FileEvent } from "../config/types.ts";
4
- import { fileToUri, uriToFile } from "../utils.ts";
5
+ import {
6
+ type AutomaticLspPathPolicy,
7
+ createDefaultAutomaticLspPathPolicy,
8
+ walkAutomaticLspTree,
9
+ } from "../workspace-path-policy.ts";
5
10
 
6
- const IGNORED_DIRECTORIES = new Set(["node_modules", ".pnpm", ".git", "dist", "coverage"]);
7
11
  const ROOT_LOCKFILES = ["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lockb"];
8
12
 
9
13
  export interface WorkspaceSentinelScanOptions {
@@ -13,6 +17,8 @@ export interface WorkspaceSentinelScanOptions {
13
17
  * the diff then reports them as `sourceChanges` so the caller can pull them.
14
18
  */
15
19
  includeSourceFiles?: boolean;
20
+ /** Runtime-owned automatic path policy. */
21
+ policy?: AutomaticLspPathPolicy;
16
22
  }
17
23
 
18
24
  /** Build a fresh snapshot of workspace sentinel files and their mtimes. */
@@ -25,11 +31,19 @@ export function scanWorkspaceSentinels(
25
31
 
26
32
  if (!fs.existsSync(resolvedCwd)) return snapshot;
27
33
 
28
- try {
29
- walkWorkspace(resolvedCwd, resolvedCwd, snapshot, options.includeSourceFiles ?? false);
30
- } catch {
31
- return snapshot;
32
- }
34
+ const policy = options.policy ?? createDefaultAutomaticLspPathPolicy(resolvedCwd);
35
+ walkAutomaticLspTree(policy, resolvedCwd, Number.MAX_SAFE_INTEGER, (directory, entries) => {
36
+ for (const entry of entries) {
37
+ if (!entry.isFile() && !entry.isSymbolicLink()) continue;
38
+ const filePath = path.join(directory, entry.name);
39
+ if (!options.includeSourceFiles && !isWorkspaceSentinelPath(filePath, resolvedCwd)) continue;
40
+ try {
41
+ snapshot.set(filePath, fs.statSync(filePath).mtimeMs);
42
+ } catch {
43
+ // File deleted between readdir and stat — skip.
44
+ }
45
+ }
46
+ });
33
47
 
34
48
  return snapshot;
35
49
  }
@@ -60,6 +74,13 @@ export function diffWorkspaceSentinelSnapshot(
60
74
  return changes.sort((a, b) => a.uri.localeCompare(b.uri));
61
75
  }
62
76
 
77
+ /** One policy-filtered workspace inventory refresh. */
78
+ export interface WorkspaceSentinelSyncResult {
79
+ snapshot: Map<string, number>;
80
+ changes: FileEvent[];
81
+ sourceChanges: FileEvent[];
82
+ }
83
+
63
84
  /**
64
85
  * Refresh a previous snapshot and return the new snapshot plus change events.
65
86
  *
@@ -72,7 +93,7 @@ export function syncWorkspaceSentinelSnapshot(
72
93
  cwd: string,
73
94
  previous: Map<string, number>,
74
95
  options: WorkspaceSentinelScanOptions = {},
75
- ): { snapshot: Map<string, number>; changes: FileEvent[]; sourceChanges: FileEvent[] } {
96
+ ): WorkspaceSentinelSyncResult {
76
97
  const snapshot = scanWorkspaceSentinels(cwd, options);
77
98
  const changes = diffWorkspaceSentinelSnapshot(previous, snapshot);
78
99
  if (!options.includeSourceFiles) return { snapshot, changes, sourceChanges: [] };
@@ -91,47 +112,6 @@ export function syncWorkspaceSentinelSnapshot(
91
112
  };
92
113
  }
93
114
 
94
- /** Determine whether a file path should trigger workspace recovery. */
95
- export function isWorkspaceRecoveryTrigger(filePath: string, cwd: string): boolean {
96
- const root = path.resolve(cwd);
97
- return isWorkspaceSentinelPath(path.resolve(root, filePath), root);
98
- }
99
-
100
- function walkWorkspace(
101
- root: string,
102
- directory: string,
103
- snapshot: Map<string, number>,
104
- includeSourceFiles: boolean,
105
- ): void {
106
- let entries: fs.Dirent[];
107
- try {
108
- entries = fs.readdirSync(directory, { withFileTypes: true });
109
- } catch {
110
- // Permission error or deleted directory — skip it rather than
111
- // failing the entire scan. A partial snapshot still detects
112
- // changes in accessible subtrees.
113
- return;
114
- }
115
-
116
- for (const entry of entries) {
117
- if (entry.isDirectory()) {
118
- if (IGNORED_DIRECTORIES.has(entry.name)) continue;
119
- walkWorkspace(root, path.join(directory, entry.name), snapshot, includeSourceFiles);
120
- continue;
121
- }
122
-
123
- if (!entry.isFile()) continue;
124
-
125
- const filePath = path.join(directory, entry.name);
126
- if (!includeSourceFiles && !isWorkspaceSentinelPath(filePath, root)) continue;
127
- try {
128
- snapshot.set(filePath, fs.statSync(filePath).mtimeMs);
129
- } catch {
130
- // File deleted between readdir and stat — skip.
131
- }
132
- }
133
- }
134
-
135
115
  function isWorkspaceSentinelPath(filePath: string, root: string): boolean {
136
116
  const name = path.basename(filePath);
137
117
 
@@ -3,6 +3,7 @@ import {
3
3
  type CodeRequestControl,
4
4
  isCodeRequestInterruption,
5
5
  } from "@mrclrchtr/supi-code-runtime/api";
6
+ import { uriToFile } from "@mrclrchtr/supi-core/path";
6
7
  import type { LspClient } from "../client/client.ts";
7
8
  import { getDiagnosticFileState } from "../client/client-file-state.ts";
8
9
  import {
@@ -10,7 +11,6 @@ import {
10
11
  type DiagnosticEvidenceSummary,
11
12
  summarizeDiagnosticEvidence,
12
13
  } from "../diagnostics/evidence.ts";
13
- import { uriToFile } from "../utils.ts";
14
14
 
15
15
  export function closeFileAcrossClients(clients: Iterable<LspClient>, filePath: string): void {
16
16
  const resolvedPath = path.resolve(filePath);
@@ -7,9 +7,8 @@ import {
7
7
  import type { LspClient } from "../client/client.ts";
8
8
  import type { DiagnosticEntry } from "../client/client-document-state.ts";
9
9
  import type { Diagnostic } from "../config/types.ts";
10
+ import { effectiveDiagnosticSeverity } from "../diagnostics/diagnostic-severity.ts";
10
11
  import { relativeFilePathFromUri } from "../diagnostics/diagnostic-summary.ts";
11
- import { shouldIgnoreLspPath } from "../summary.ts";
12
- import { isExcludedByPattern } from "./manager-helpers.ts";
13
12
 
14
13
  /** Sync one file and return diagnostics up to the supplied severity threshold. */
15
14
  export async function syncClientFileAndGetDiagnostics(
@@ -30,7 +29,7 @@ export async function syncClientFileAndGetDiagnostics(
30
29
  export function collectOutstandingDiagnosticsDetailed(
31
30
  clientEntries: Iterable<ReadonlyArray<DiagnosticEntry>>,
32
31
  cwd: string,
33
- excludePatterns: string[],
32
+ includeFile: (file: string) => boolean,
34
33
  maxSeverity: number,
35
34
  ): Array<{ file: string; diagnostics: Diagnostic[] }> {
36
35
  const fileDiags = new Map<string, Diagnostic[]>();
@@ -38,8 +37,7 @@ export function collectOutstandingDiagnosticsDetailed(
38
37
  for (const entries of clientEntries) {
39
38
  for (const entry of entries) {
40
39
  const file = relativeFilePathFromUri(entry.uri, cwd);
41
- if (shouldIgnoreLspPath(file, cwd)) continue;
42
- if (isExcludedByPattern(file, excludePatterns)) continue;
40
+ if (!includeFile(file)) continue;
43
41
  const filtered = filterDiagnosticsBySeverity(entry.diagnostics, maxSeverity);
44
42
  if (filtered.length === 0) continue;
45
43
  const existing = fileDiags.get(file) ?? [];
@@ -53,7 +51,5 @@ export function collectOutstandingDiagnosticsDetailed(
53
51
  }
54
52
 
55
53
  function filterDiagnosticsBySeverity(diagnostics: Diagnostic[], maxSeverity: number): Diagnostic[] {
56
- return diagnostics.filter(
57
- (diagnostic) => diagnostic.severity !== undefined && diagnostic.severity <= maxSeverity,
58
- );
54
+ return diagnostics.filter((diagnostic) => effectiveDiagnosticSeverity(diagnostic) <= maxSeverity);
59
55
  }
@@ -1,7 +1,6 @@
1
1
  import * as path from "node:path";
2
+ import { resolveToolPath as resolveSessionPath } from "@mrclrchtr/supi-core/path";
2
3
  import * as projectRoots from "@mrclrchtr/supi-core/project";
3
- import { isGlobMatch } from "../pattern-matcher.ts";
4
- import { resolveSessionPath } from "../utils.ts";
5
4
 
6
5
  /** Unique key for a client identified by server name and root. */
7
6
  export function clientKey(serverName: string, root: string): string {
@@ -32,10 +31,3 @@ export function rememberKnownRoot(
32
31
  const roots = knownRoots.get(serverName) ?? [];
33
32
  knownRoots.set(serverName, projectRoots.mergeKnownRoots(roots, root));
34
33
  }
35
-
36
- /** Check if a file path matches any user-configured exclude pattern. */
37
- export function isExcludedByPattern(file: string, excludePatterns: string[]): boolean {
38
- return (
39
- excludePatterns.length > 0 && excludePatterns.some((pattern) => isGlobMatch(file, pattern))
40
- );
41
- }
@@ -1,6 +1,5 @@
1
1
  import type { LspClient } from "../client/client.ts";
2
- import { getSupportedLspServerActions } from "../config/server-actions.ts";
3
- import type { ProjectServerInfo } from "../config/server-config.ts";
2
+ import type { ProjectServerInfo, ProjectServerStatusReason } from "../config/server-config.ts";
4
3
  import { displayRelativeFilePath } from "../summary.ts";
5
4
 
6
5
  interface ProjectServerInfoInput {
@@ -9,14 +8,18 @@ interface ProjectServerInfoInput {
9
8
  fileTypes: string[];
10
9
  client: LspClient | undefined;
11
10
  unavailableReason?: "missing-command" | "start-failed" | "runtime-error";
11
+ statusReason?: ProjectServerStatusReason;
12
+ /** Filter files from ambient project status without changing explicit routing. */
13
+ includeOpenFile?: (file: string) => boolean;
12
14
  }
13
15
 
14
16
  export function buildProjectServerInfo(
15
17
  input: ProjectServerInfoInput,
16
18
  cwd: string,
17
19
  ): ProjectServerInfo {
18
- const status =
19
- input.client?.status === "running"
20
+ const status = input.statusReason
21
+ ? "error"
22
+ : input.client?.status === "running"
20
23
  ? "running"
21
24
  : input.client?.status === "error" || input.unavailableReason === "start-failed"
22
25
  ? "error"
@@ -29,8 +32,11 @@ export function buildProjectServerInfo(
29
32
  root: input.root,
30
33
  fileTypes: input.fileTypes,
31
34
  status,
32
- supportedActions: getSupportedLspServerActions(input.client?.serverCapabilities),
33
- openFiles: input.client?.openFiles.map((file) => displayRelativeFilePath(file, cwd)) ?? [],
35
+ ...(input.statusReason ? { statusReason: input.statusReason } : {}),
36
+ openFiles:
37
+ input.client?.openFiles
38
+ .filter((file) => input.includeOpenFile?.(file) ?? true)
39
+ .map((file) => displayRelativeFilePath(file, cwd)) ?? [],
34
40
  ready: input.client?.ready ?? false,
35
41
  };
36
42
  }
@@ -16,12 +16,19 @@ import {
16
16
  assessStaleDiagnostics,
17
17
  type StaleDiagnosticAssessment,
18
18
  } from "../diagnostics/stale-diagnostics.ts";
19
- import type { WorkspaceDiagnosticReport } from "../session/runtime-diagnostics.ts";
19
+ import {
20
+ emptyProcessCrashRecoverySummary,
21
+ type ProcessCrashRecoverySummary,
22
+ type WorkspaceDiagnosticReport,
23
+ } from "../session/runtime-diagnostics.ts";
20
24
 
21
25
  export interface WorkspaceRecoveryResult {
22
26
  /** Active clients targeted by the best-effort refresh, not confirmed successful refreshes. */
23
27
  attemptedClients: number;
28
+ /** Clients restarted by stale-diagnostic recovery, not process-crash recovery. */
24
29
  restartedClients: number;
30
+ /** No process-crash demand is handled by this generic recovery pass. */
31
+ processCrashRecovery: ProcessCrashRecoverySummary;
25
32
  /** Server names of the active clients targeted by this pass, for telemetry identity. */
26
33
  attemptedServers: string[];
27
34
  /** Server names of the clients restarted during this pass, for telemetry identity. */
@@ -157,6 +164,7 @@ export async function recoverWorkspaceDiagnostics(
157
164
  return {
158
165
  attemptedClients,
159
166
  restartedClients,
167
+ processCrashRecovery: emptyProcessCrashRecoverySummary(),
160
168
  attemptedServers,
161
169
  restartedServers: restartServerNames,
162
170
  ...(restartReason ? { restartReason } : {}),
@@ -1,4 +1,3 @@
1
- import * as fs from "node:fs";
2
1
  import * as path from "node:path";
3
2
  import {
4
3
  type CodeQueryResult,
@@ -7,7 +6,6 @@ import {
7
6
  partialCodeQuery,
8
7
  unavailableCodeQuery,
9
8
  } from "@mrclrchtr/supi-code-runtime/api";
10
- import { walkProject } from "@mrclrchtr/supi-core/project";
11
9
  import type { LspClient } from "../client/client.ts";
12
10
  import type {
13
11
  DocumentSymbol,
@@ -15,10 +13,14 @@ import type {
15
13
  SymbolInformation,
16
14
  WorkspaceSymbol,
17
15
  } from "../config/types.ts";
16
+ import {
17
+ type AutomaticLspPathPolicy,
18
+ createDefaultAutomaticLspPathPolicy,
19
+ walkAutomaticLspTree,
20
+ } from "../workspace-path-policy.ts";
18
21
 
19
22
  type WorkspaceSymbolLike = SymbolInformation | WorkspaceSymbol;
20
23
 
21
- const SKIP_DIRS = new Set(["node_modules", ".git", ".pnpm", "dist", "build", "coverage"]);
22
24
  const DEFAULT_WARM_FILE_DEPTH = 4;
23
25
  const DEFAULT_MARKER_SCAN_DEPTH = 6;
24
26
 
@@ -30,6 +32,8 @@ export interface WorkspaceSymbolWarmTarget {
30
32
  export interface WorkspaceSymbolWarmOptions {
31
33
  maxFileDepth?: number;
32
34
  maxMarkerDepth?: number;
35
+ /** Runtime-owned automatic path policy. */
36
+ policy?: AutomaticLspPathPolicy;
33
37
  }
34
38
 
35
39
  export function getWorkspaceSymbolWarmPosition(
@@ -98,14 +102,6 @@ export function workspaceSymbolCollectionResult(
98
102
  return completedCodeQuery(collection.results);
99
103
  }
100
104
 
101
- export async function managerWorkspaceSymbol(
102
- clients: Iterable<LspClient>,
103
- query: string,
104
- control?: CodeRequestControl,
105
- ): Promise<CodeQueryResult<WorkspaceSymbolLike[]>> {
106
- return workspaceSymbolCollectionResult(await collectWorkspaceSymbols(clients, query, control));
107
- }
108
-
109
105
  export function findWorkspaceSymbolWarmTargets(
110
106
  root: string,
111
107
  rootMarkers: string[],
@@ -116,12 +112,13 @@ export function findWorkspaceSymbolWarmTargets(
116
112
  const allowed = new Set(fileTypes.map((fileType) => fileType.toLowerCase()));
117
113
  const maxFileDepth = options.maxFileDepth ?? DEFAULT_WARM_FILE_DEPTH;
118
114
  const maxMarkerDepth = options.maxMarkerDepth ?? DEFAULT_MARKER_SCAN_DEPTH;
115
+ const policy = options.policy ?? createDefaultAutomaticLspPathPolicy(resolvedRoot);
119
116
  if (allowed.size === 0) return [];
120
117
 
121
- const markerRoots = collectMarkerRoots(resolvedRoot, rootMarkers, maxMarkerDepth);
118
+ const markerRoots = collectMarkerRoots(resolvedRoot, rootMarkers, maxMarkerDepth, policy);
122
119
  const targets = markerRoots
123
120
  .map((entry) => {
124
- const file = findWarmFileRecursive(entry.root, allowed, maxFileDepth);
121
+ const file = findWarmFileRecursive(entry.root, allowed, maxFileDepth, policy);
125
122
  return file ? { projectRoot: entry.root, file, priority: entry.priority } : null;
126
123
  })
127
124
  .filter((entry): entry is { projectRoot: string; file: string; priority: number } =>
@@ -137,7 +134,7 @@ export function findWorkspaceSymbolWarmTargets(
137
134
 
138
135
  if (targets.length > 0) return targets;
139
136
 
140
- const fallback = findWarmFileRecursive(resolvedRoot, allowed, maxFileDepth);
137
+ const fallback = findWarmFileRecursive(resolvedRoot, allowed, maxFileDepth, policy);
141
138
  return fallback ? [{ projectRoot: resolvedRoot, file: fallback }] : [];
142
139
  }
143
140
 
@@ -150,13 +147,15 @@ function collectMarkerRoots(
150
147
  root: string,
151
148
  rootMarkers: string[],
152
149
  maxDepth: number,
150
+ policy: AutomaticLspPathPolicy,
153
151
  ): MarkerRootEntry[] {
154
152
  if (rootMarkers.length === 0) return [];
155
153
 
156
154
  const markerIndex = new Map(rootMarkers.map((marker, index) => [marker, index]));
157
155
  const matches = new Map<string, number>();
158
156
 
159
- walkProject(root, maxDepth, (directory, entryNames) => {
157
+ walkAutomaticLspTree(policy, root, maxDepth, (directory, entries) => {
158
+ const entryNames = new Set(entries.map((entry) => entry.name));
160
159
  const matchedPriority = rootMarkers.reduce<number | null>((best, marker) => {
161
160
  if (!entryNames.has(marker)) return best;
162
161
  const next = markerIndex.get(marker) ?? Number.MAX_SAFE_INTEGER;
@@ -181,31 +180,17 @@ function findWarmFileRecursive(
181
180
  directory: string,
182
181
  allowed: Set<string>,
183
182
  depth: number,
183
+ policy: AutomaticLspPathPolicy,
184
184
  ): string | null {
185
- let entries: fs.Dirent[];
186
- try {
187
- entries = fs.readdirSync(directory, { withFileTypes: true });
188
- } catch {
189
- return null;
190
- }
191
-
192
- const sortedEntries = [...entries].sort((a, b) => a.name.localeCompare(b.name));
193
-
194
- for (const entry of sortedEntries) {
195
- if (!entry.isFile()) continue;
196
- const ext = path.extname(entry.name).slice(1).toLowerCase();
197
- if (!allowed.has(ext)) continue;
198
- return path.join(directory, entry.name);
199
- }
200
-
201
- if (depth <= 0) return null;
202
-
203
- for (const entry of sortedEntries) {
204
- if (!entry.isDirectory()) continue;
205
- if (SKIP_DIRS.has(entry.name)) continue;
206
- const nested = findWarmFileRecursive(path.join(directory, entry.name), allowed, depth - 1);
207
- if (nested) return nested;
208
- }
209
-
210
- return null;
185
+ let warmFile: string | null = null;
186
+ walkAutomaticLspTree(policy, directory, depth, (currentDirectory, entries) => {
187
+ for (const entry of entries) {
188
+ if (!entry.isFile() && !entry.isSymbolicLink()) continue;
189
+ if (!allowed.has(path.extname(entry.name).slice(1).toLowerCase())) continue;
190
+ warmFile = path.join(currentDirectory, entry.name);
191
+ return true;
192
+ }
193
+ return false;
194
+ });
195
+ return warmFile;
211
196
  }