@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
@@ -1,100 +1,22 @@
1
- // LSP settings helpers config loading for LSP session lifecycle.
2
- //
3
- // These are the non-UI config helpers extracted from settings-registration.ts
4
- // so they can be consumed by the library-only runtime controller without
5
- // importing pi-specific UI modules.
1
+ // LSP settings used by workspace lifecycle and automatic path policy.
6
2
 
7
- import { loadSupiConfig, loadSupiConfigForScope } from "@mrclrchtr/supi-core/config";
3
+ import { loadSupiConfig } from "@mrclrchtr/supi-core/config";
8
4
 
9
- // ── Types ────────────────────────────────────────────────────
10
-
11
- export interface LspSettings extends Record<string, unknown> {
12
- enabled: boolean;
13
- active: string[];
5
+ /** Effective non-server LSP settings. */
6
+ export interface LspSettings {
14
7
  exclude: string[];
15
8
  }
16
9
 
17
10
  export const LSP_DEFAULTS: LspSettings = {
18
- enabled: true,
19
- active: [],
20
11
  exclude: [],
21
12
  };
22
13
 
23
- // ── Config helpers ───────────────────────────────────────────
24
-
25
- /**
26
- * Load LSP settings from supi config for the given cwd.
27
- *
28
- * Merges project and global scopes with defaults, returning the
29
- * effective LspSettings for session startup.
30
- */
14
+ /** Load effective LSP settings for one workspace. */
31
15
  export function loadLspSettings(cwd: string, homeDir?: string): LspSettings {
32
- return loadSupiConfig("lsp", cwd, LSP_DEFAULTS, { homeDir });
33
- }
34
-
35
- /**
36
- * Return a user-facing message explaining why LSP may appear disabled.
37
- *
38
- * **Since the always-on policy:** `lsp.enabled: false` is deprecated and
39
- * ignored. This function is kept for backward compatibility with the
40
- * settings UI and reports that LSP is always attempted.
41
- */
42
- export function getLspDisabledMessage(cwd: string, homeDir?: string): string {
43
- const global = loadSupiConfigForScope("lsp", cwd, LSP_DEFAULTS, { scope: "global", homeDir });
44
- const project = loadSupiConfigForScope("lsp", cwd, LSP_DEFAULTS, { scope: "project", homeDir });
45
-
46
- if (project.enabled === false) {
47
- return "LSP is always attempted; `lsp.enabled: false` in project settings (.pi/supi/config.json) is deprecated and ignored";
48
- }
49
- if (global.enabled === false) {
50
- return "LSP is always attempted; `lsp.enabled: false` in global settings (~/.pi/agent/supi/config.json) is deprecated and ignored";
51
- }
52
- return "LSP is always attempted";
53
- }
54
-
55
- // ── Deprecated-key detection (always-on policy) ────────────────
56
-
57
- /**
58
- * Result of checking for deprecated LSP configuration keys.
59
- */
60
- export interface DeprecatedLspKeys {
61
- /** `lsp.enabled` presence in project config. */
62
- projectEnabled: boolean;
63
- /** `lsp.enabled` presence in global config. */
64
- globalEnabled: boolean;
65
- /** `lsp.active` presence in project config. */
66
- projectActive: boolean;
67
- /** `lsp.active` presence in global config. */
68
- globalActive: boolean;
69
- }
70
-
71
- /**
72
- * Check whether deprecated `lsp.enabled` or `lsp.active` keys exist
73
- * in project or global config. These keys are ignored by the always-on
74
- * LSP policy but may be present in user config.
75
- */
76
- export function getDeprecatedLspKeys(cwd: string, homeDir?: string): DeprecatedLspKeys {
77
- const global = loadSupiConfigForScope("lsp", cwd, LSP_DEFAULTS, { scope: "global", homeDir });
78
- const project = loadSupiConfigForScope("lsp", cwd, LSP_DEFAULTS, { scope: "project", homeDir });
79
-
16
+ const loaded = loadSupiConfig("lsp", cwd, LSP_DEFAULTS, { homeDir });
80
17
  return {
81
- projectEnabled: hasExplicitEnabledKey(project),
82
- globalEnabled: hasExplicitEnabledKey(global),
83
- projectActive: hasExplicitActiveKey(project),
84
- globalActive: hasExplicitActiveKey(global),
18
+ exclude: Array.isArray(loaded.exclude)
19
+ ? loaded.exclude.filter((entry): entry is string => typeof entry === "string")
20
+ : [],
85
21
  };
86
22
  }
87
-
88
- /** Whether the loaded settings had an explicit `enabled` key (different from default). */
89
- function hasExplicitEnabledKey(settings: Partial<{ enabled: boolean }>): boolean {
90
- return settings.enabled === false;
91
- }
92
-
93
- /** Whether the loaded settings had an explicit `active` key. */
94
- function hasExplicitActiveKey(settings: Partial<{ active: string[] }>): boolean {
95
- return Array.isArray(settings.active) && settings.active.length > 0;
96
- }
97
-
98
- // Alias for backwards compatibility with the RED test name
99
- /** @deprecated Use {@link getDeprecatedLspKeys} instead. */
100
- export const hasDeprecatedLspKeys: typeof getDeprecatedLspKeys = getDeprecatedLspKeys;
@@ -36,7 +36,6 @@ export interface ProjectServerInfo extends DetectedProjectServer {
36
36
  status: "running" | "error" | "unavailable";
37
37
  /** Structured lifecycle reason when process-crash recovery is active. */
38
38
  statusReason?: ProjectServerStatusReason;
39
- supportedActions: string[];
40
39
  openFiles: string[];
41
40
  /** Whether the LSP server is currently not indexing and ready to serve queries. */
42
41
  ready: boolean;
@@ -379,15 +379,14 @@ export function invalidateTsconfigCacheForConfig(configPath: string): void {
379
379
  }
380
380
 
381
381
  /**
382
- * Invalidate nearest-config lookups that found nothing under `dir`.
382
+ * Invalidate nearest-config lookups under a directory that gained a config.
383
383
  *
384
- * Used when a config is created: directories below the new config that
385
- * previously resolved to no config may now resolve to it.
384
+ * A new config can replace either a no-config result or a cached lower-priority
385
+ * config, such as `jsconfig.json` when `tsconfig.json` is created.
386
386
  */
387
387
  export function invalidateTsconfigCacheForConfigDir(dir: string): void {
388
388
  const normalizedDir = normalizePath(dir);
389
- for (const [key, value] of nearestConfigCache) {
390
- if (value !== null) continue;
389
+ for (const key of nearestConfigCache.keys()) {
391
390
  const keyDir = key.split("::", 1)[0];
392
391
  if (keyDir === normalizedDir || keyDir.startsWith(`${normalizedDir}/`)) {
393
392
  nearestConfigCache.delete(key);
@@ -49,35 +49,6 @@ export {
49
49
  WorkspaceSymbol,
50
50
  } from "vscode-languageserver-types";
51
51
 
52
- // Alias for backward compatibility — our code uses ClientDiagnosticCapabilities
53
- import type { DiagnosticClientCapabilities } from "vscode-languageserver-protocol";
54
- export type ClientDiagnosticCapabilities = DiagnosticClientCapabilities;
55
-
56
- // ── JSON-RPC types (local — replaced by vscode-jsonrpc in transport task) ──
57
- export type JsonRpcId = number | string;
58
-
59
- export interface JsonRpcRequest {
60
- jsonrpc: "2.0";
61
- id: JsonRpcId;
62
- method: string;
63
- params?: unknown;
64
- }
65
-
66
- export interface JsonRpcResponse {
67
- jsonrpc: "2.0";
68
- id: JsonRpcId | null;
69
- result?: unknown;
70
- error?: { code: number; message: string; data?: unknown };
71
- }
72
-
73
- export interface JsonRpcNotification {
74
- jsonrpc: "2.0";
75
- method: string;
76
- params?: unknown;
77
- }
78
-
79
- export type JsonRpcMessage = JsonRpcRequest | JsonRpcResponse | JsonRpcNotification;
80
-
81
52
  // ── SuPi-specific server config ──────────────────────────────────────
82
53
  export type {
83
54
  DetectedProjectServer,
@@ -1,19 +1,14 @@
1
1
  // Bounded identity vocabulary for LSP debug telemetry.
2
2
  //
3
- // LSP debug events may carry server, workspace, file, method, and root
4
- // identity for local protocol diagnosis. Every emitted identity string is
5
- // bounded to MAX_IDENTITY_STRING UTF-16 code units (truncation marker
6
- // included) and every identity list is bounded to MAX_SERVERS entries.
3
+ // LSP debug events can include server, workspace, file, method, and root
4
+ // identity for local protocol diagnosis. Each identity string and list uses
5
+ // the shared bounds.
6
+
7
+ import { truncateDebugIdentity } from "@mrclrchtr/supi-core/debug";
7
8
 
8
9
  /** Maximum number of server entries in one bounded LSP debug payload. */
9
10
  export const MAX_SERVERS = 16;
10
11
 
11
- /** Maximum UTF-16 code-unit length of one emitted identity string in an LSP debug payload. */
12
- export const MAX_IDENTITY_STRING = 512;
13
-
14
- /** Marker appended to an identity string truncated to MAX_IDENTITY_STRING. */
15
- export const IDENTITY_TRUNCATION_MARKER = "…";
16
-
17
12
  /**
18
13
  * JSON-RPC error code recorded for client-local request timeouts.
19
14
  *
@@ -24,22 +19,13 @@ export const IDENTITY_TRUNCATION_MARKER = "…";
24
19
  */
25
20
  export const LSP_REQUEST_TIMEOUT_ERROR_CODE = -32_095;
26
21
 
27
- /**
28
- * Truncate one identity string so the emitted value (marker included) never
29
- * exceeds MAX_IDENTITY_STRING UTF-16 code units.
30
- */
31
- export function truncateIdentity(value: string): string {
32
- if (value.length <= MAX_IDENTITY_STRING) return value;
33
- return `${value.slice(0, MAX_IDENTITY_STRING - 1)}${IDENTITY_TRUNCATION_MARKER}`;
34
- }
35
-
36
22
  /** Bound an optional workspace root for event-level identity; undefined stays absent. */
37
23
  export function boundCwd(cwd: string | undefined): string | undefined {
38
24
  if (cwd === undefined) return undefined;
39
- return truncateIdentity(cwd);
25
+ return truncateDebugIdentity(cwd);
40
26
  }
41
27
 
42
28
  /** Bound a server-name list to MAX_SERVERS entries, truncating each name. */
43
29
  export function boundServerNames(names: readonly string[]): string[] {
44
- return names.slice(0, MAX_SERVERS).map(truncateIdentity);
30
+ return names.slice(0, MAX_SERVERS).map(truncateDebugIdentity);
45
31
  }
@@ -0,0 +1,6 @@
1
+ import { type Diagnostic, DiagnosticSeverity } from "../config/types.ts";
2
+
3
+ /** Return Error when a diagnostic omits the optional LSP severity. */
4
+ export function effectiveDiagnosticSeverity(diagnostic: Diagnostic): DiagnosticSeverity {
5
+ return diagnostic.severity ?? DiagnosticSeverity.Error;
6
+ }
@@ -1,23 +1,23 @@
1
+ import { uriToFile } from "@mrclrchtr/supi-core/path";
1
2
  import { type Diagnostic, DiagnosticSeverity } from "../config/types.ts";
2
3
  import type { OutstandingDiagnosticSummaryEntry } from "../manager/manager-types.ts";
3
- import { isGlobMatch } from "../pattern-matcher.ts";
4
- import { displayRelativeFilePath, shouldIgnoreLspPath } from "../summary.ts";
5
- import { uriToFile } from "../utils.ts";
4
+ import { displayRelativeFilePath } from "../summary.ts";
5
+ import { effectiveDiagnosticSeverity } from "./diagnostic-severity.ts";
6
6
 
7
7
  export function collectDiagnosticSummaryCounts(
8
8
  fileDiags: Map<string, { errors: number; warnings: number }>,
9
9
  entry: { uri: string; diagnostics: Diagnostic[] },
10
10
  cwd: string,
11
- excludePatterns?: string[],
11
+ includeFile: (file: string) => boolean,
12
12
  ): void {
13
13
  const file = relativeFilePathFromUri(entry.uri, cwd);
14
- if (shouldIgnoreLspPath(file, cwd)) return;
15
- if (excludePatterns?.some((p) => isGlobMatch(file, p))) return;
14
+ if (!includeFile(file)) return;
16
15
 
17
16
  const current = fileDiags.get(file) ?? { errors: 0, warnings: 0 };
18
17
  for (const diagnostic of entry.diagnostics) {
19
- if (diagnostic.severity === DiagnosticSeverity.Error) current.errors++;
20
- else if (diagnostic.severity === DiagnosticSeverity.Warning) current.warnings++;
18
+ const severity = effectiveDiagnosticSeverity(diagnostic);
19
+ if (severity === DiagnosticSeverity.Error) current.errors++;
20
+ else if (severity === DiagnosticSeverity.Warning) current.warnings++;
21
21
  }
22
22
  fileDiags.set(file, current);
23
23
  }
@@ -46,7 +46,7 @@ export function accumulateOutstandingDiagnostics(
46
46
  if (!isDiagnosticWithinThreshold(diagnostic, maxSeverity)) continue;
47
47
 
48
48
  next.total++;
49
- incrementOutstandingDiagnosticCount(next, diagnostic.severity);
49
+ incrementOutstandingDiagnosticCount(next, effectiveDiagnosticSeverity(diagnostic));
50
50
  }
51
51
 
52
52
  return next;
@@ -56,11 +56,8 @@ export function relativeFilePathFromUri(uri: string, cwd: string): string {
56
56
  return displayRelativeFilePath(uriToFile(uri), cwd);
57
57
  }
58
58
 
59
- function isDiagnosticWithinThreshold(
60
- diagnostic: Diagnostic,
61
- maxSeverity: number,
62
- ): diagnostic is Diagnostic & { severity: number } {
63
- return diagnostic.severity !== undefined && diagnostic.severity <= maxSeverity;
59
+ function isDiagnosticWithinThreshold(diagnostic: Diagnostic, maxSeverity: number): boolean {
60
+ return effectiveDiagnosticSeverity(diagnostic) <= maxSeverity;
64
61
  }
65
62
 
66
63
  function incrementOutstandingDiagnosticCount(
@@ -35,8 +35,3 @@ export function summarizeDiagnosticEvidence(
35
35
  for (const document of documents) counts[document.status]++;
36
36
  return { ...counts, documents: [...documents] };
37
37
  }
38
-
39
- /** Return an empty evidence summary for a refresh with no tracked documents. */
40
- export function emptyDiagnosticEvidence(): DiagnosticEvidenceSummary {
41
- return summarizeDiagnosticEvidence([]);
42
- }
@@ -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
- }
@@ -0,0 +1,106 @@
1
+ import * as path from "node:path";
2
+ import {
3
+ MAX_PROCESS_CRASH_RECOVERY_ENTRIES,
4
+ type ProcessCrashRecoveryEntry,
5
+ type ProcessCrashRecoveryNextAction,
6
+ type ProcessCrashRecoveryOutcome,
7
+ type ProcessCrashRecoveryReport,
8
+ } from "../session/runtime-diagnostics.ts";
9
+
10
+ /** One route result before its root is projected to a workspace-relative path. */
11
+ export interface ProcessCrashRecoveryRouteResult {
12
+ readonly name: string;
13
+ readonly root: string;
14
+ readonly outcome: ProcessCrashRecoveryOutcome;
15
+ /** The caught Error.message from a failed replacement, when available. */
16
+ readonly failureMessage?: string;
17
+ }
18
+
19
+ /** Maximum failure detail retained in a process-crash report. */
20
+ export const MAX_PROCESS_CRASH_FAILURE_MESSAGE_LENGTH = 512;
21
+
22
+ const OUTCOME_ORDER: Record<ProcessCrashRecoveryOutcome, number> = {
23
+ "recovery-failed": 0,
24
+ "recovery-exhausted": 1,
25
+ "skipped-no-retained-file": 2,
26
+ recovered: 3,
27
+ };
28
+
29
+ /** Build the bounded, deterministic report for one process-crash demand. */
30
+ export function buildProcessCrashRecoveryReport(
31
+ routes: readonly ProcessCrashRecoveryRouteResult[],
32
+ cwd: string,
33
+ ): ProcessCrashRecoveryReport {
34
+ const entries = routes.map((route) => toReportEntry(route, cwd)).sort(compareEntries);
35
+ const visibleEntries = entries.slice(0, MAX_PROCESS_CRASH_RECOVERY_ENTRIES);
36
+
37
+ return {
38
+ recoveredRoutes: countRoutes(routes, "recovered"),
39
+ skippedRoutes: countRoutes(routes, "skipped-no-retained-file"),
40
+ failedRoutes: countRoutes(routes, "recovery-failed"),
41
+ exhaustedRoutes: countRoutes(routes, "recovery-exhausted"),
42
+ entries: visibleEntries,
43
+ omittedEntries: Math.max(0, entries.length - visibleEntries.length),
44
+ };
45
+ }
46
+
47
+ /** Limit one caught Error.message without retaining other error fields. */
48
+ export function boundProcessCrashFailureMessage(message: string): string {
49
+ if (message.length <= MAX_PROCESS_CRASH_FAILURE_MESSAGE_LENGTH) return message;
50
+ return `${message.slice(0, MAX_PROCESS_CRASH_FAILURE_MESSAGE_LENGTH - 1)}…`;
51
+ }
52
+
53
+ function toReportEntry(
54
+ route: ProcessCrashRecoveryRouteResult,
55
+ cwd: string,
56
+ ): ProcessCrashRecoveryEntry {
57
+ const action = nextAction(route.outcome);
58
+ const entry: ProcessCrashRecoveryEntry = {
59
+ name: route.name,
60
+ root: relativeRoot(cwd, route.root),
61
+ outcome: route.outcome,
62
+ ...(action ? { nextAction: action } : {}),
63
+ ...(route.outcome === "recovery-failed" && route.failureMessage
64
+ ? { failureMessage: boundProcessCrashFailureMessage(route.failureMessage) }
65
+ : {}),
66
+ };
67
+ return entry;
68
+ }
69
+
70
+ function relativeRoot(cwd: string, root: string): string {
71
+ return (path.relative(path.resolve(cwd), path.resolve(cwd, root)) || ".").replaceAll("\\", "/");
72
+ }
73
+
74
+ function nextAction(outcome: ProcessCrashRecoveryOutcome): ProcessCrashRecoveryNextAction | null {
75
+ switch (outcome) {
76
+ case "skipped-no-retained-file":
77
+ return "use-exact-file";
78
+ case "recovery-failed":
79
+ case "recovery-exhausted":
80
+ return "reload-workspace";
81
+ case "recovered":
82
+ return null;
83
+ }
84
+ }
85
+
86
+ function compareEntries(
87
+ first: ProcessCrashRecoveryEntry,
88
+ second: ProcessCrashRecoveryEntry,
89
+ ): number {
90
+ return (
91
+ OUTCOME_ORDER[first.outcome] - OUTCOME_ORDER[second.outcome] ||
92
+ compareStrings(first.root, second.root) ||
93
+ compareStrings(first.name, second.name)
94
+ );
95
+ }
96
+
97
+ function compareStrings(first: string, second: string): number {
98
+ return first < second ? -1 : first > second ? 1 : 0;
99
+ }
100
+
101
+ function countRoutes(
102
+ routes: readonly ProcessCrashRecoveryRouteResult[],
103
+ outcome: ProcessCrashRecoveryOutcome,
104
+ ): number {
105
+ return routes.filter((route) => route.outcome === outcome).length;
106
+ }
@@ -1,5 +1,4 @@
1
1
  import type { LspClient } from "../client/client.ts";
2
- import { getSupportedLspServerActions } from "../config/server-actions.ts";
3
2
  import type { ProjectServerInfo, ProjectServerStatusReason } from "../config/server-config.ts";
4
3
  import { displayRelativeFilePath } from "../summary.ts";
5
4
 
@@ -10,6 +9,8 @@ interface ProjectServerInfoInput {
10
9
  client: LspClient | undefined;
11
10
  unavailableReason?: "missing-command" | "start-failed" | "runtime-error";
12
11
  statusReason?: ProjectServerStatusReason;
12
+ /** Filter files from ambient project status without changing explicit routing. */
13
+ includeOpenFile?: (file: string) => boolean;
13
14
  }
14
15
 
15
16
  export function buildProjectServerInfo(
@@ -32,8 +33,10 @@ export function buildProjectServerInfo(
32
33
  fileTypes: input.fileTypes,
33
34
  status,
34
35
  ...(input.statusReason ? { statusReason: input.statusReason } : {}),
35
- supportedActions: getSupportedLspServerActions(input.client?.serverCapabilities),
36
- openFiles: input.client?.openFiles.map((file) => displayRelativeFilePath(file, cwd)) ?? [],
36
+ openFiles:
37
+ input.client?.openFiles
38
+ .filter((file) => input.includeOpenFile?.(file) ?? true)
39
+ .map((file) => displayRelativeFilePath(file, cwd)) ?? [],
37
40
  ready: input.client?.ready ?? false,
38
41
  };
39
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
+ emptyProcessCrashRecoveryReport,
21
+ type ProcessCrashRecoveryReport,
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: ProcessCrashRecoveryReport;
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: emptyProcessCrashRecoveryReport(),
160
168
  attemptedServers,
161
169
  restartedServers: restartServerNames,
162
170
  ...(restartReason ? { restartReason } : {}),