@mrclrchtr/supi-code-intelligence 6.3.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/README.md +164 -82
  2. package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  3. package/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +8 -1
  4. package/node_modules/@mrclrchtr/supi-core/README.md +8 -0
  5. package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  6. package/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +10 -3
  7. package/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
  8. package/node_modules/@mrclrchtr/supi-core/src/llm.ts +141 -16
  9. package/node_modules/@mrclrchtr/supi-lsp/README.md +35 -13
  10. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  11. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +8 -1
  12. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +8 -0
  13. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  14. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +10 -3
  15. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
  16. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/llm.ts +141 -16
  17. package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/index.js +3 -1
  18. package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/legacy.js +3 -1
  19. package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/package.json +1 -1
  20. package/node_modules/@mrclrchtr/supi-lsp/package.json +3 -3
  21. package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +16 -0
  22. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-cache.ts +43 -35
  23. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-capabilities.ts +83 -17
  24. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-collection.ts +108 -92
  25. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-evidence.ts +44 -82
  26. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-host.ts +3 -4
  27. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-publication.ts +9 -68
  28. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-pull.ts +45 -58
  29. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-refresh.ts +230 -331
  30. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-request.ts +201 -1
  31. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-timing.ts +129 -70
  32. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-typescript.ts +395 -0
  33. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-waiters.ts +8 -12
  34. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostics.ts +685 -183
  35. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-state.ts +2 -2
  36. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-sync.ts +60 -60
  37. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-request-enrollment.ts +75 -0
  38. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-semantic-input-barrier.ts +443 -0
  39. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-semantic-input-errors.ts +30 -0
  40. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-semantic-input-file.ts +15 -0
  41. package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +290 -37
  42. package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +72 -16
  43. package/node_modules/@mrclrchtr/supi-lsp/src/config/capabilities.ts +1 -0
  44. package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-scope.ts +30 -4
  45. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/evidence.ts +1 -1
  46. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/workspace-sentinels.ts +4 -32
  47. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/workspace-sources.ts +108 -0
  48. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +10 -4
  49. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-process-crash-report.ts +50 -1
  50. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +11 -7
  51. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +770 -56
  52. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostic-surface.ts +3 -1
  53. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +37 -4
  54. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +48 -12
  55. package/node_modules/@mrclrchtr/supi-lsp/src/session/workspace-lsp-runtime.ts +50 -4
  56. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  57. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +8 -1
  58. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/README.md +8 -0
  59. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  60. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +10 -3
  61. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
  62. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/llm.ts +141 -16
  63. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -3
  64. package/node_modules/@mrclrchtr/supi-tree-sitter/src/provider/tree-sitter-provider.ts +2 -0
  65. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callee-display-name.ts +165 -0
  66. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callees.ts +67 -9
  67. package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +2 -0
  68. package/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +19 -18
  69. package/node_modules/yaml/browser/dist/nodes/Alias.js +25 -23
  70. package/node_modules/yaml/dist/compose/resolve-flow-scalar.js +19 -18
  71. package/node_modules/yaml/dist/nodes/Alias.js +25 -23
  72. package/node_modules/yaml/package.json +1 -1
  73. package/package.json +5 -5
  74. package/src/analysis/health/diagnostics.ts +1 -2
  75. package/src/analysis/health/file-scope.ts +4 -1
  76. package/src/analysis/health/server-status.ts +1 -1
  77. package/src/analysis/relations/callees.ts +17 -8
  78. package/src/analysis/relations/implementations.ts +1 -2
  79. package/src/analysis/relations/references.ts +8 -4
  80. package/src/analysis/relations/types.ts +6 -30
  81. package/src/analysis/target/anchored.ts +9 -1
  82. package/src/analysis/target/symbol.ts +29 -11
  83. package/src/analysis/target/types.ts +14 -6
  84. package/src/api.ts +2 -0
  85. package/src/session/graph/collect.ts +18 -3
  86. package/src/session/health-refresh.ts +27 -11
  87. package/src/session/health-types.ts +21 -4
  88. package/src/session/health-workflow.ts +28 -22
  89. package/src/session/orientation-types.ts +5 -6
  90. package/src/session/orientation-workflow.ts +10 -2
  91. package/src/session/session.ts +12 -5
  92. package/src/session/target-workflow.ts +9 -6
  93. package/src/substrate/lsp/maintenance.ts +53 -47
  94. package/src/substrate/lsp/source-tracking.ts +252 -0
  95. package/src/tool/code_find/guidance.ts +3 -3
  96. package/src/tool/code_find/spec.ts +5 -4
  97. package/src/tool/code_find/tui.ts +7 -1
  98. package/src/tool/code_graph/details.ts +32 -0
  99. package/src/tool/code_graph/display.ts +54 -0
  100. package/src/tool/code_graph/format.ts +60 -0
  101. package/src/tool/code_graph/guidance.ts +2 -2
  102. package/src/tool/code_graph/markdown.ts +76 -198
  103. package/src/tool/code_graph/read-next.ts +65 -0
  104. package/src/tool/code_graph/result.ts +118 -83
  105. package/src/tool/code_graph/tui.ts +216 -54
  106. package/src/tool/code_health/guidance.ts +3 -5
  107. package/src/tool/code_health/markdown.ts +90 -57
  108. package/src/tool/code_health/refresh-outcome.ts +51 -0
  109. package/src/tool/code_health/refresh-status.ts +130 -43
  110. package/src/tool/code_health/scope.ts +78 -0
  111. package/src/tool/code_health/spec.ts +1 -1
  112. package/src/tool/code_health/tui.ts +29 -38
  113. package/src/tool/code_inspect/guidance.ts +5 -3
  114. package/src/tool/code_inspect/tui.ts +7 -1
  115. package/src/tool/code_orientation/guidance.ts +3 -4
  116. package/src/tool/code_orientation/result.ts +31 -8
  117. package/src/tool/code_orientation/tui.ts +26 -5
  118. package/src/tool/code_refactor_apply/guidance.ts +1 -1
  119. package/src/tool/code_refactor_apply/tui.ts +7 -3
  120. package/src/tool/code_refactor_plan/guidance.ts +3 -5
  121. package/src/tool/code_refactor_plan/tui.ts +7 -1
  122. package/src/tool/code_resolve/guidance.ts +3 -5
  123. package/src/tool/code_resolve/result.ts +13 -10
  124. package/src/tool/code_resolve/tui.ts +18 -1
  125. package/src/tool/guidance.ts +11 -9
  126. package/src/tool/infra/truncate.ts +39 -13
  127. package/src/tool/result/candidate-row.ts +27 -0
  128. package/src/tool/result/errors.ts +21 -6
  129. package/src/tool/result/types.ts +7 -3
  130. package/src/tool/schemas.ts +5 -0
  131. package/src/types/execution.ts +2 -0
  132. package/src/types/index.ts +2 -0
  133. package/src/ui/tui/candidate-legacy-body.ts +69 -0
  134. package/src/ui/tui/candidate-selection.ts +366 -0
  135. package/src/ui/tui/common.ts +26 -31
  136. package/src/ui/tui/execution-error.ts +92 -0
@@ -1,6 +1,6 @@
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
+ import { fileToUri } from "@mrclrchtr/supi-core/path";
4
4
  import { FileChangeType, type FileEvent } from "../config/types.ts";
5
5
  import {
6
6
  type AutomaticLspPathPolicy,
@@ -11,12 +11,6 @@ import {
11
11
  const ROOT_LOCKFILES = ["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lockb"];
12
12
 
13
13
  export interface WorkspaceSentinelScanOptions {
14
- /**
15
- * Also track every regular source file (not just sentinels). The workspace
16
- * refresh path uses this to discover files created since the last refresh;
17
- * the diff then reports them as `sourceChanges` so the caller can pull them.
18
- */
19
- includeSourceFiles?: boolean;
20
14
  /** Runtime-owned automatic path policy. */
21
15
  policy?: AutomaticLspPathPolicy;
22
16
  }
@@ -36,7 +30,7 @@ export function scanWorkspaceSentinels(
36
30
  for (const entry of entries) {
37
31
  if (!entry.isFile() && !entry.isSymbolicLink()) continue;
38
32
  const filePath = path.join(directory, entry.name);
39
- if (!options.includeSourceFiles && !isWorkspaceSentinelPath(filePath, resolvedCwd)) continue;
33
+ if (!isWorkspaceSentinelPath(filePath, resolvedCwd)) continue;
40
34
  try {
41
35
  snapshot.set(filePath, fs.statSync(filePath).mtimeMs);
42
36
  } catch {
@@ -78,38 +72,16 @@ export function diffWorkspaceSentinelSnapshot(
78
72
  export interface WorkspaceSentinelSyncResult {
79
73
  snapshot: Map<string, number>;
80
74
  changes: FileEvent[];
81
- sourceChanges: FileEvent[];
82
75
  }
83
76
 
84
- /**
85
- * Refresh a previous snapshot and return the new snapshot plus change events.
86
- *
87
- * When `includeSourceFiles` is set, the returned `changes` list keeps
88
- * sentinel-file events only; source-file events are returned separately in
89
- * `sourceChanges` so the caller can act on them without interpreting every
90
- * regular file edit as a config-level workspace change.
91
- */
77
+ /** Refresh a previous sentinel snapshot and return its change events. */
92
78
  export function syncWorkspaceSentinelSnapshot(
93
79
  cwd: string,
94
80
  previous: Map<string, number>,
95
81
  options: WorkspaceSentinelScanOptions = {},
96
82
  ): WorkspaceSentinelSyncResult {
97
83
  const snapshot = scanWorkspaceSentinels(cwd, options);
98
- const changes = diffWorkspaceSentinelSnapshot(previous, snapshot);
99
- if (!options.includeSourceFiles) return { snapshot, changes, sourceChanges: [] };
100
-
101
- const sourceChanges = changes.filter((change) => {
102
- const filePath = uriToFile(change.uri);
103
- return !isWorkspaceSentinelPath(filePath, cwd);
104
- });
105
- return {
106
- snapshot,
107
- changes: changes.filter((change) => {
108
- const filePath = uriToFile(change.uri);
109
- return isWorkspaceSentinelPath(filePath, cwd);
110
- }),
111
- sourceChanges,
112
- };
84
+ return { snapshot, changes: diffWorkspaceSentinelSnapshot(previous, snapshot) };
113
85
  }
114
86
 
115
87
  function isWorkspaceSentinelPath(filePath: string, root: string): boolean {
@@ -0,0 +1,108 @@
1
+ import { type Dirent, promises as fs } from "node:fs";
2
+ import * as path from "node:path";
3
+ import {
4
+ type CodeRequestControl,
5
+ isCodeRequestInterruption,
6
+ throwIfCodeRequestInterrupted,
7
+ } from "@mrclrchtr/supi-code-runtime/api";
8
+ import { raceRequestControl } from "../session/readiness.ts";
9
+ import type { AutomaticLspPathPolicy } from "../workspace-path-policy.ts";
10
+
11
+ const MAX_SOURCE_INVENTORY_FILES = 50_000;
12
+
13
+ export type WorkspaceSourceInventoryReason = "file-limit" | "filesystem-error";
14
+
15
+ /** Complete or safety-limited automatic source inventory. */
16
+ export interface WorkspaceSourceInventory {
17
+ readonly status: "complete" | "limited";
18
+ /** The reason is null only when the inventory is complete. */
19
+ readonly reason: WorkspaceSourceInventoryReason | null;
20
+ /** Number of configured-extension files observed before the scan stopped. */
21
+ readonly observedFileCount: number;
22
+ /** Paths are present only as a usable complete inventory. */
23
+ readonly files: readonly string[];
24
+ }
25
+
26
+ /** Inputs for one automatic source inventory. */
27
+ export interface WorkspaceSourceScanOptions {
28
+ readonly fileTypes: readonly string[];
29
+ readonly policy: AutomaticLspPathPolicy;
30
+ readonly control?: CodeRequestControl;
31
+ }
32
+
33
+ /**
34
+ * Scan one workspace for automatic source paths.
35
+ *
36
+ * The scan uses the runtime path policy and configured LSP extensions. A
37
+ * limited result is not a source baseline and must not be diffed. Caller
38
+ * interruption is rethrown so a health call does not retain partial state.
39
+ */
40
+ export async function scanWorkspaceSources(
41
+ cwd: string,
42
+ options: WorkspaceSourceScanOptions,
43
+ ): Promise<WorkspaceSourceInventory> {
44
+ const extensionSet = new Set(
45
+ options.fileTypes.map((fileType) => fileType.replace(/^\./, "").toLowerCase()),
46
+ );
47
+ const files: string[] = [];
48
+ let observedFileCount = 0;
49
+ let reason: WorkspaceSourceInventoryReason | null = null;
50
+
51
+ const readEntries = async (directory: string): Promise<Dirent[] | null> => {
52
+ try {
53
+ return await raceRequestControl(
54
+ fs.readdir(directory, { withFileTypes: true }) as Promise<Dirent[]>,
55
+ options.control,
56
+ );
57
+ } catch (error) {
58
+ if (isCodeRequestInterruption(error, options.control)) throw error;
59
+ reason = "filesystem-error";
60
+ return null;
61
+ }
62
+ };
63
+
64
+ const visitEntry = async (directory: string, entry: Dirent): Promise<void> => {
65
+ const filePath = path.join(directory, entry.name);
66
+ const kind = entry.isDirectory() ? "directory" : "file";
67
+ if (!options.policy.isEligible(filePath, kind)) return;
68
+
69
+ if (entry.isDirectory()) {
70
+ await walk(filePath);
71
+ return;
72
+ }
73
+ if (!entry.isFile() && !entry.isSymbolicLink()) return;
74
+ if (!extensionSet.has(path.extname(entry.name).slice(1).toLowerCase())) return;
75
+
76
+ observedFileCount++;
77
+ if (observedFileCount > MAX_SOURCE_INVENTORY_FILES) {
78
+ reason = "file-limit";
79
+ return;
80
+ }
81
+ files.push(filePath);
82
+ };
83
+
84
+ const walk = async (directory: string): Promise<void> => {
85
+ if (reason !== null) return;
86
+ throwIfCodeRequestInterrupted(options.control);
87
+ if (!options.policy.isEligible(directory, "directory")) return;
88
+
89
+ const entries = await readEntries(directory);
90
+ if (!entries) return;
91
+ entries.sort((a, b) => a.name.localeCompare(b.name));
92
+ for (const entry of entries) {
93
+ throwIfCodeRequestInterrupted(options.control);
94
+ await visitEntry(directory, entry);
95
+ if (reason !== null) return;
96
+ }
97
+ };
98
+
99
+ await walk(path.resolve(cwd));
100
+ throwIfCodeRequestInterrupted(options.control);
101
+
102
+ return {
103
+ status: reason === null ? "complete" : "limited",
104
+ reason,
105
+ observedFileCount,
106
+ files: reason === null ? files.sort((a, b) => a.localeCompare(b)) : [],
107
+ };
108
+ }
@@ -10,17 +10,23 @@ import type { Diagnostic } from "../config/types.ts";
10
10
  import { effectiveDiagnosticSeverity } from "../diagnostics/diagnostic-severity.ts";
11
11
  import { relativeFilePathFromUri } from "../diagnostics/diagnostic-summary.ts";
12
12
 
13
+ /** Options for one manager-owned diagnostic request. */
14
+ interface SyncClientFileOptions {
15
+ readonly control?: CodeRequestControl;
16
+ readonly contentIsAuthoritative?: boolean;
17
+ }
18
+
13
19
  /** Sync one file and return diagnostics up to the supplied severity threshold. */
14
20
  export async function syncClientFileAndGetDiagnostics(
15
21
  client: Pick<LspClient, "syncAndWaitForDiagnostics">,
16
22
  filePath: string,
17
23
  maxSeverity: number,
18
- control?: CodeRequestControl,
24
+ options: SyncClientFileOptions = {},
19
25
  ): Promise<CodeQueryResult<Diagnostic[]>> {
20
26
  const content = readFileSync(filePath, "utf-8");
21
- const result = control
22
- ? await client.syncAndWaitForDiagnostics(filePath, content, control)
23
- : await client.syncAndWaitForDiagnostics(filePath, content);
27
+ const result = await client.syncAndWaitForDiagnostics(filePath, content, options.control, {
28
+ contentIsAuthoritative: options.contentIsAuthoritative ?? true,
29
+ });
24
30
  return mapCodeQueryResult(result, (diagnostics) =>
25
31
  filterDiagnosticsBySeverity(diagnostics, maxSeverity),
26
32
  );
@@ -5,6 +5,8 @@ import {
5
5
  type ProcessCrashRecoveryNextAction,
6
6
  type ProcessCrashRecoveryOutcome,
7
7
  type ProcessCrashRecoveryReport,
8
+ type StartupRetryEntry,
9
+ type StartupRetryReport,
8
10
  } from "../session/runtime-diagnostics.ts";
9
11
 
10
12
  /** One route result before its root is projected to a workspace-relative path. */
@@ -77,7 +79,7 @@ function nextAction(outcome: ProcessCrashRecoveryOutcome): ProcessCrashRecoveryN
77
79
  return "use-exact-file";
78
80
  case "recovery-failed":
79
81
  case "recovery-exhausted":
80
- return "reload-workspace";
82
+ return "refresh";
81
83
  case "recovered":
82
84
  return null;
83
85
  }
@@ -104,3 +106,50 @@ function countRoutes(
104
106
  ): number {
105
107
  return routes.filter((route) => route.outcome === outcome).length;
106
108
  }
109
+
110
+ /** One initial-start route result before its root is projected. */
111
+ export interface StartupRetryRouteResult {
112
+ readonly name: string;
113
+ readonly root: string;
114
+ readonly outcome: StartupRetryEntry["outcome"];
115
+ readonly failureMessage?: string;
116
+ }
117
+
118
+ /** Build a bounded, deterministic report for initial-start retries. */
119
+ export function buildStartupRetryReport(
120
+ routes: readonly StartupRetryRouteResult[],
121
+ cwd: string,
122
+ ): StartupRetryReport {
123
+ const entries = routes
124
+ .map(
125
+ (route): StartupRetryEntry => ({
126
+ name: route.name,
127
+ root: relativeRoot(cwd, route.root),
128
+ outcome: route.outcome,
129
+ ...(route.outcome === "retry-failed"
130
+ ? {
131
+ nextAction: "refresh" as const,
132
+ ...(route.failureMessage
133
+ ? { failureMessage: boundProcessCrashFailureMessage(route.failureMessage) }
134
+ : {}),
135
+ }
136
+ : {}),
137
+ }),
138
+ )
139
+ .sort(compareStartupEntries);
140
+ const visibleEntries = entries.slice(0, MAX_PROCESS_CRASH_RECOVERY_ENTRIES);
141
+ return {
142
+ recoveredRoutes: routes.filter((route) => route.outcome === "recovered").length,
143
+ failedRoutes: routes.filter((route) => route.outcome === "retry-failed").length,
144
+ entries: visibleEntries,
145
+ omittedEntries: Math.max(0, entries.length - visibleEntries.length),
146
+ };
147
+ }
148
+
149
+ function compareStartupEntries(first: StartupRetryEntry, second: StartupRetryEntry): number {
150
+ return (
151
+ Number(first.outcome !== "retry-failed") - Number(second.outcome !== "retry-failed") ||
152
+ compareStrings(first.root, second.root) ||
153
+ compareStrings(first.name, second.name)
154
+ );
155
+ }
@@ -18,7 +18,9 @@ import {
18
18
  } from "../diagnostics/stale-diagnostics.ts";
19
19
  import {
20
20
  emptyProcessCrashRecoveryReport,
21
+ emptyStartupRetryReport,
21
22
  type ProcessCrashRecoveryReport,
23
+ type StartupRetryReport,
22
24
  type WorkspaceDiagnosticReport,
23
25
  } from "../session/runtime-diagnostics.ts";
24
26
 
@@ -29,6 +31,8 @@ export interface WorkspaceRecoveryResult {
29
31
  restartedClients: number;
30
32
  /** No process-crash demand is handled by this generic recovery pass. */
31
33
  processCrashRecovery: ProcessCrashRecoveryReport;
34
+ /** No initial-start retry is handled by this generic recovery pass. */
35
+ startupRetry: StartupRetryReport;
32
36
  /** Server names of the active clients targeted by this pass, for telemetry identity. */
33
37
  attemptedServers: string[];
34
38
  /** Server names of the clients restarted during this pass, for telemetry identity. */
@@ -49,8 +53,8 @@ export interface WorkspaceRecoveryResult {
49
53
  /** One client route's diagnostic evidence capability for recovery targeting. */
50
54
  export interface WorkspaceDiagnosticRoute {
51
55
  key: string;
52
- /** False for push-only clients, which cannot confirm freshness by pull. */
53
- supportsPull: boolean;
56
+ /** False for push-only clients, which have no request adapter. */
57
+ hasDiagnosticRequestAdapter: boolean;
54
58
  /** Files tracked by the route whose evidence is unconfirmed. */
55
59
  unconfirmedFiles: string[];
56
60
  /** Protocol-stall signal observed on the route, or null when healthy. */
@@ -165,6 +169,7 @@ export async function recoverWorkspaceDiagnostics(
165
169
  attemptedClients,
166
170
  restartedClients,
167
171
  processCrashRecovery: emptyProcessCrashRecoveryReport(),
172
+ startupRetry: emptyStartupRetryReport(),
168
173
  attemptedServers,
169
174
  restartedServers: restartServerNames,
170
175
  ...(restartReason ? { restartReason } : {}),
@@ -232,15 +237,14 @@ async function collectInitialEvidence(
232
237
  /**
233
238
  * Collect restart targets: push-only routes with a protocol-stall signal.
234
239
  *
235
- * Unconfirmed evidence alone never restarts a client: the reopen-resync
236
- * fallback handles unconfirmed push-only documents without discarding
237
- * server state (ADR 0020). Restarts require a readiness stall or repeated
238
- * protocol failures, and stay limited to push-only routes.
240
+ * Unconfirmed evidence alone never restarts a client. Restarts require a
241
+ * readiness stall or repeated protocol failures, and stay limited to routes
242
+ * without a diagnostic request adapter.
239
243
  */
240
244
  function collectRecoveryRestartTargets(host: WorkspaceRecoveryHost): RecoveryRestartTarget[] {
241
245
  const targets: RecoveryRestartTarget[] = [];
242
246
  for (const route of host.getClientDiagnosticRoutes()) {
243
- if (route.supportsPull) continue;
247
+ if (route.hasDiagnosticRequestAdapter) continue;
244
248
  if (!route.stallSignal) continue;
245
249
  if (route.unconfirmedFiles.length === 0) continue;
246
250
  targets.push({