@mrclrchtr/supi-code-intelligence 6.4.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 (129) 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 +6 -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/manager/manager-diagnostics.ts +10 -4
  47. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-process-crash-report.ts +50 -1
  48. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +11 -7
  49. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +608 -45
  50. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostic-surface.ts +3 -1
  51. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +37 -4
  52. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +31 -11
  53. package/node_modules/@mrclrchtr/supi-lsp/src/session/workspace-lsp-runtime.ts +19 -3
  54. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  55. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +8 -1
  56. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/README.md +8 -0
  57. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  58. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +10 -3
  59. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
  60. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/llm.ts +141 -16
  61. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -3
  62. package/node_modules/@mrclrchtr/supi-tree-sitter/src/provider/tree-sitter-provider.ts +2 -0
  63. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callee-display-name.ts +165 -0
  64. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callees.ts +67 -9
  65. package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +2 -0
  66. package/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +19 -18
  67. package/node_modules/yaml/browser/dist/nodes/Alias.js +25 -23
  68. package/node_modules/yaml/dist/compose/resolve-flow-scalar.js +19 -18
  69. package/node_modules/yaml/dist/nodes/Alias.js +25 -23
  70. package/node_modules/yaml/package.json +1 -1
  71. package/package.json +5 -5
  72. package/src/analysis/health/diagnostics.ts +1 -2
  73. package/src/analysis/health/file-scope.ts +4 -1
  74. package/src/analysis/health/server-status.ts +1 -1
  75. package/src/analysis/relations/callees.ts +17 -8
  76. package/src/analysis/relations/implementations.ts +1 -2
  77. package/src/analysis/relations/references.ts +8 -4
  78. package/src/analysis/relations/types.ts +6 -30
  79. package/src/analysis/target/anchored.ts +9 -1
  80. package/src/analysis/target/symbol.ts +29 -11
  81. package/src/analysis/target/types.ts +14 -6
  82. package/src/api.ts +2 -0
  83. package/src/session/graph/collect.ts +18 -3
  84. package/src/session/health-refresh.ts +12 -1
  85. package/src/session/health-types.ts +18 -6
  86. package/src/session/health-workflow.ts +21 -19
  87. package/src/session/orientation-types.ts +5 -6
  88. package/src/session/orientation-workflow.ts +10 -2
  89. package/src/session/target-workflow.ts +9 -6
  90. package/src/substrate/lsp/source-tracking.ts +11 -6
  91. package/src/tool/code_find/guidance.ts +2 -2
  92. package/src/tool/code_find/spec.ts +5 -4
  93. package/src/tool/code_find/tui.ts +7 -1
  94. package/src/tool/code_graph/details.ts +32 -0
  95. package/src/tool/code_graph/display.ts +54 -0
  96. package/src/tool/code_graph/format.ts +60 -0
  97. package/src/tool/code_graph/guidance.ts +1 -1
  98. package/src/tool/code_graph/markdown.ts +76 -198
  99. package/src/tool/code_graph/read-next.ts +65 -0
  100. package/src/tool/code_graph/result.ts +118 -83
  101. package/src/tool/code_graph/tui.ts +216 -54
  102. package/src/tool/code_health/guidance.ts +2 -4
  103. package/src/tool/code_health/markdown.ts +75 -58
  104. package/src/tool/code_health/refresh-outcome.ts +41 -1
  105. package/src/tool/code_health/refresh-status.ts +114 -48
  106. package/src/tool/code_health/scope.ts +78 -0
  107. package/src/tool/code_health/spec.ts +1 -1
  108. package/src/tool/code_health/tui.ts +25 -38
  109. package/src/tool/code_inspect/tui.ts +7 -1
  110. package/src/tool/code_orientation/result.ts +31 -8
  111. package/src/tool/code_orientation/tui.ts +26 -5
  112. package/src/tool/code_refactor_apply/guidance.ts +1 -1
  113. package/src/tool/code_refactor_apply/tui.ts +7 -3
  114. package/src/tool/code_refactor_plan/guidance.ts +1 -1
  115. package/src/tool/code_refactor_plan/tui.ts +7 -1
  116. package/src/tool/code_resolve/guidance.ts +2 -4
  117. package/src/tool/code_resolve/result.ts +13 -10
  118. package/src/tool/code_resolve/tui.ts +18 -1
  119. package/src/tool/infra/truncate.ts +39 -13
  120. package/src/tool/result/candidate-row.ts +27 -0
  121. package/src/tool/result/errors.ts +21 -6
  122. package/src/tool/result/types.ts +7 -3
  123. package/src/tool/schemas.ts +5 -0
  124. package/src/types/execution.ts +2 -0
  125. package/src/types/index.ts +2 -0
  126. package/src/ui/tui/candidate-legacy-body.ts +69 -0
  127. package/src/ui/tui/candidate-selection.ts +366 -0
  128. package/src/ui/tui/common.ts +26 -31
  129. package/src/ui/tui/execution-error.ts +92 -0
@@ -1,8 +1,6 @@
1
1
  export const toolDescription =
2
- "Report live diagnostics and language-server health. Diagnostic snapshots do not prove that the whole workspace is clean; server inventory and route-status counts are always workspace-wide.";
2
+ "Report live diagnostics and language-server health. Use refresh:true when you need fresh diagnostic evidence; without it, diagnostics may be a tracked-file snapshot. Diagnostic snapshots do not prove that the whole workspace is clean; server inventory and route-status counts are always workspace-wide.";
3
3
 
4
4
  export const promptSnippet = "check live diagnostics and language-server health";
5
5
 
6
- export const promptGuidelines = [
7
- "Use code_health with `refresh: true` before relying on diagnostics that can be stale.",
8
- ];
6
+ export const promptGuidelines: string[] = [];
@@ -1,10 +1,4 @@
1
- /**
2
- * Markdown renderer for code_health results.
3
- *
4
- * Renders structured health data from the code_health executor into
5
- * readable markdown sections keyed by requested `include` values.
6
- */
7
-
1
+ /** Markdown renderer for code_health results. */
8
2
  import type { CapabilityWarningReport } from "../../analysis/capability/capability-warnings.ts";
9
3
  import {
10
4
  formatProjectServerRoot,
@@ -16,7 +10,9 @@ import {
16
10
  formatProcessCrashRecovery,
17
11
  formatRefreshElapsed,
18
12
  formatSourceTracking,
13
+ formatSourceTrackingDetails,
19
14
  formatStaleDiagnosticRestarts as formatStaleDiagnosticRestartsText,
15
+ formatStartupRetry,
20
16
  isFileReadinessPending,
21
17
  } from "./refresh-outcome.ts";
22
18
  import type {
@@ -52,49 +48,52 @@ function renderRefreshStatus(
52
48
  hasDiagnostics: boolean,
53
49
  cwd: string,
54
50
  ): void {
55
- if (!hasDiagnostics) return;
56
-
57
- switch (data.refresh.kind) {
58
- case "completed": {
59
- const fileReadinessPending = isFileReadinessPending(data.refresh, data.semanticState);
60
- lines.push(
61
- `**${refreshAttemptLabel(data.refresh)}**: ${asSentence(completedRefreshText(data.refresh, fileReadinessPending, false))}`,
62
- );
63
- renderSourceTracking(lines, data.refresh);
64
- lines.push(`**Stale assessment**: ${asSentence(staleAssessmentText(data.refresh))}`);
65
- lines.push("");
66
- return;
67
- }
68
- case "failed": {
69
- const evidence = data.refresh.diagnosticEvidence
70
- ? `; ${formatDiagnosticEvidence(data.refresh.diagnosticEvidence)}`
71
- : "";
72
- const processCrashRecovery = data.refresh.processCrashRecovery
73
- ? formatProcessCrashRecovery(data.refresh.processCrashRecovery)
74
- : null;
75
- const staleRestart = formatStaleDiagnosticRestarts(data.refresh);
76
- lines.push(
77
- `**${refreshAttemptLabel(data.refresh)}**: failed — ${data.refresh.reason}${staleRestart ? `; ${staleRestart}` : ""}${processCrashRecovery ? `; ${processCrashRecovery}` : ""}${evidence}`,
78
- );
79
- renderSourceTracking(lines, data.refresh);
80
- lines.push("");
81
- return;
82
- }
83
- case "not-attempted":
84
- lines.push(`**Diagnostic refresh attempt**: not started — ${data.refresh.reason}`);
85
- if (data.refresh.lastAttempt) renderLastAttempt(lines, data.refresh.lastAttempt, cwd);
86
- lines.push("");
87
- return;
88
- case "not-requested":
89
- if (data.refresh.lastAttempt) {
90
- renderRetainedAttempt(lines, data.refresh.lastAttempt, cwd);
91
- } else {
92
- lines.push(
93
- "**Diagnostic refresh attempt**: not requested this session. Use `refresh: true` to try one.",
94
- );
95
- }
96
- lines.push("");
51
+ if (!hasDiagnostics && data.refresh.kind !== "completed" && data.refresh.kind !== "failed") {
52
+ return;
53
+ }
54
+ if (data.refresh.kind === "completed") {
55
+ renderCompletedRefreshStatus(lines, data);
56
+ return;
57
+ }
58
+ if (data.refresh.kind === "failed") {
59
+ renderFailedRefreshStatus(lines, data.refresh);
60
+ return;
61
+ }
62
+ if (data.refresh.kind === "not-attempted") {
63
+ lines.push(`**Diagnostic refresh attempt**: not started — ${data.refresh.reason}`);
64
+ if (data.refresh.lastAttempt) renderLastAttempt(lines, data.refresh.lastAttempt, cwd);
65
+ lines.push("");
66
+ return;
67
+ }
68
+ if (data.refresh.lastAttempt) {
69
+ renderRetainedAttempt(lines, data.refresh.lastAttempt, cwd);
70
+ } else {
71
+ lines.push(
72
+ "**Diagnostic refresh attempt**: not requested this session. Use `refresh: true` to try one.",
73
+ );
97
74
  }
75
+ lines.push("");
76
+ }
77
+
78
+ function renderCompletedRefreshStatus(lines: string[], data: HealthData): void {
79
+ if (data.refresh.kind !== "completed") return;
80
+ const fileReadinessPending = isFileReadinessPending(data.refresh, data.semanticState);
81
+ lines.push(
82
+ `**${refreshAttemptLabel(data.refresh)}**: ${asSentence(completedRefreshText(data.refresh, fileReadinessPending, false))}`,
83
+ );
84
+ renderSourceTracking(lines, data.refresh);
85
+ lines.push(`**Stale assessment**: ${asSentence(staleAssessmentText(data.refresh))}`);
86
+ lines.push("");
87
+ }
88
+
89
+ function renderFailedRefreshStatus(
90
+ lines: string[],
91
+ attempt: Extract<HealthRefreshAttempt, { kind: "failed" }>,
92
+ ): void {
93
+ const outcome = failedRefreshText(attempt, false);
94
+ lines.push(`**${refreshAttemptLabel(attempt)}**: ${asSentence(outcome)}`);
95
+ renderSourceTracking(lines, attempt);
96
+ lines.push("");
98
97
  }
99
98
 
100
99
  function completedRefreshText(
@@ -103,11 +102,13 @@ function completedRefreshText(
103
102
  includeSourceTracking = true,
104
103
  ): string {
105
104
  const processCrashRecovery = formatProcessCrashRecovery(attempt.processCrashRecovery);
105
+ const startupRetry = formatStartupRetry(attempt.startupRetry);
106
106
  const noOp =
107
107
  !fileReadinessPending &&
108
108
  attempt.attemptedActiveClients === 0 &&
109
109
  attempt.restartedClients === 0 &&
110
- processCrashRecovery === null;
110
+ processCrashRecovery === null &&
111
+ startupRetry === null;
111
112
  const base = fileReadinessPending
112
113
  ? "readiness pending — LSP may still be warming; retry shortly"
113
114
  : noOp
@@ -119,18 +120,20 @@ function completedRefreshText(
119
120
  includeSourceTracking ? sourceTracking : null,
120
121
  staleRestart,
121
122
  processCrashRecovery,
123
+ startupRetry,
122
124
  ].filter((value): value is string => value !== null);
123
125
  const withOutcome = outcome.length > 0 ? `${base}; ${outcome.join("; ")}` : base;
126
+ const withScope = `${withOutcome}; maintenance scope: ${refreshOperationScopeText(attempt)}`;
124
127
  return attempt.operationScope === "workspace-runtime"
125
- ? `${withOutcome}; ${formatDiagnosticEvidence(attempt.diagnosticEvidence)}`
126
- : withOutcome;
128
+ ? `${withScope}; maintenance evidence: ${formatDiagnosticEvidence(attempt.diagnosticEvidence)}`
129
+ : withScope;
127
130
  }
128
131
 
129
132
  function renderSourceTracking(
130
133
  lines: string[],
131
134
  attempt: Pick<HealthRefreshAttempt, "sourceTracking">,
132
135
  ): void {
133
- const sourceTracking = formatSourceTracking(attempt.sourceTracking);
136
+ const sourceTracking = formatSourceTrackingDetails(attempt.sourceTracking);
134
137
  if (sourceTracking) lines.push(`**Source discovery**: ${asSentence(sourceTracking)}`);
135
138
  }
136
139
 
@@ -157,7 +160,7 @@ function renderLastAttempt(lines: string[], attempt: HealthRefreshAttempt, cwd:
157
160
  const outcome = refreshAttemptOutcome(attempt);
158
161
  const label = refreshAttemptLabel(attempt);
159
162
  lines.push(
160
- `**Previous ${label.toLowerCase()}**: ${asSentence(outcome)} Started ${formatRefreshElapsed(Date.now() - attempt.attemptedAt)}; requested ${formatDiagnosticScope(attempt.requestedDiagnosticScope, cwd)}; operation scope: ${refreshOperationScopeText(attempt)}.`,
163
+ `**Previous ${label.toLowerCase()}**: ${asSentence(outcome)} Started ${formatRefreshElapsed(Date.now() - attempt.attemptedAt)}; requested evidence scope: ${formatDiagnosticScope(attempt.requestedDiagnosticScope, cwd)}.`,
161
164
  );
162
165
  }
163
166
 
@@ -165,21 +168,35 @@ function renderRetainedAttempt(lines: string[], attempt: HealthRefreshAttempt, c
165
168
  const label =
166
169
  attempt.operationScope === "file-runtime" ? "File LSP maintenance" : "Diagnostic refresh";
167
170
  lines.push(
168
- `**${label}**: not requested for this call. Previous attempt ${asSentence(refreshAttemptOutcome(attempt))} Started ${formatRefreshElapsed(Date.now() - attempt.attemptedAt)}; requested ${formatDiagnosticScope(attempt.requestedDiagnosticScope, cwd)}; operation scope: ${refreshOperationScopeText(attempt)}.`,
171
+ `**${label}**: not requested for this call. Previous attempt ${asSentence(refreshAttemptOutcome(attempt))} Started ${formatRefreshElapsed(Date.now() - attempt.attemptedAt)}; requested evidence scope: ${formatDiagnosticScope(attempt.requestedDiagnosticScope, cwd)}.`,
169
172
  );
170
173
  }
171
174
 
172
175
  function refreshAttemptOutcome(attempt: HealthRefreshAttempt): string {
173
176
  if (attempt.kind === "completed") return completedRefreshText(attempt);
177
+ return failedRefreshText(attempt);
178
+ }
179
+
180
+ function failedRefreshText(
181
+ attempt: Extract<HealthRefreshAttempt, { kind: "failed" }>,
182
+ includeSourceTracking = true,
183
+ ): string {
174
184
  const processCrashRecovery = attempt.processCrashRecovery
175
185
  ? formatProcessCrashRecovery(attempt.processCrashRecovery)
176
186
  : null;
187
+ const startupRetry = attempt.startupRetry ? formatStartupRetry(attempt.startupRetry) : null;
177
188
  const staleRestart = formatStaleDiagnosticRestarts(attempt);
178
- const sourceTracking = formatSourceTracking(attempt.sourceTracking);
189
+ const sourceTracking = includeSourceTracking
190
+ ? formatSourceTracking(attempt.sourceTracking)
191
+ : null;
179
192
  const evidence = attempt.diagnosticEvidence
180
- ? `; ${formatDiagnosticEvidence(attempt.diagnosticEvidence)}`
193
+ ? `; maintenance evidence: ${formatDiagnosticEvidence(attempt.diagnosticEvidence)}`
181
194
  : "";
182
- return `failed — ${attempt.reason}${staleRestart ? `; ${staleRestart}` : ""}${processCrashRecovery ? `; ${processCrashRecovery}` : ""}${sourceTracking ? `; ${sourceTracking}` : ""}${evidence}`;
195
+ return `failed — ${stripTrailingPunctuation(attempt.reason)}; maintenance scope: ${refreshOperationScopeText(attempt)}${staleRestart ? `; ${staleRestart}` : ""}${processCrashRecovery ? `; ${processCrashRecovery}` : ""}${startupRetry ? `; ${startupRetry}` : ""}${sourceTracking ? `; ${sourceTracking}` : ""}${evidence}`;
196
+ }
197
+
198
+ function stripTrailingPunctuation(text: string): string {
199
+ return text.replace(/[.!?]+$/, "");
183
200
  }
184
201
 
185
202
  function refreshOperationScopeText(attempt: HealthRefreshAttempt): string {
@@ -1,6 +1,8 @@
1
1
  import type {
2
2
  ProcessCrashRecoveryEntry,
3
3
  ProcessCrashRecoveryReport,
4
+ StartupRetryEntry,
5
+ StartupRetryReport,
4
6
  } from "@mrclrchtr/supi-lsp/api";
5
7
  import type { SemanticHealthState } from "../../session/health-types.ts";
6
8
  import type { SourceTrackingReport } from "../../substrate/lsp/source-tracking.ts";
@@ -65,14 +67,52 @@ function formatProcessCrashRecoveryEntry(entry: ProcessCrashRecoveryEntry): stri
65
67
  return `${entry.name} @ ${entry.root}: ${outcome}${action}${failure}`;
66
68
  }
67
69
 
70
+ /** Format initial-start retry counts for the compact health view. */
71
+ export function formatCompactStartupRetry(
72
+ report: StartupRetryReport | null | undefined,
73
+ ): string | null {
74
+ if (!report || (report.recoveredRoutes === 0 && report.failedRoutes === 0)) return null;
75
+ return `startup retry: ${report.recoveredRoutes} route${plural(report.recoveredRoutes)} recovered (${report.failedRoutes} failed)`;
76
+ }
77
+
78
+ /** Format initial-start retry counts and bounded route entries. */
79
+ export function formatStartupRetry(report: StartupRetryReport | null | undefined): string | null {
80
+ const summary = formatCompactStartupRetry(report);
81
+ if (!summary || !report) return summary;
82
+ const entries = report.entries.map(formatStartupRetryEntry);
83
+ const omitted =
84
+ report.omittedEntries > 0
85
+ ? `${report.omittedEntries} more route${plural(report.omittedEntries)}`
86
+ : null;
87
+ const details = [...entries, omitted]
88
+ .filter((value): value is string => value !== null)
89
+ .join("; ");
90
+ return details ? `${summary}; ${details}` : summary;
91
+ }
92
+
93
+ function formatStartupRetryEntry(entry: StartupRetryEntry): string {
94
+ const outcome = entry.outcome.replaceAll("-", " ");
95
+ const action = entry.nextAction ? `; next: ${entry.nextAction.replaceAll("-", " ")}` : "";
96
+ const failure = entry.failureMessage ? `; ${entry.failureMessage}` : "";
97
+ return `${entry.name} @ ${entry.root}: ${outcome}${action}${failure}`;
98
+ }
99
+
68
100
  /** Format source discovery and bounded tracking counts separately from diagnostics. */
69
101
  export function formatSourceTracking(
70
102
  report: SourceTrackingReport | null | undefined,
103
+ ): string | null {
104
+ const details = formatSourceTrackingDetails(report);
105
+ return details ? `source discovery: ${details}` : null;
106
+ }
107
+
108
+ /** Format source tracking counts without a label for a standalone heading. */
109
+ export function formatSourceTrackingDetails(
110
+ report: SourceTrackingReport | null | undefined,
71
111
  ): string | null {
72
112
  if (!report) return null;
73
113
  const inventory =
74
114
  report.status === "complete" ? "complete" : `limited (${report.reason ?? "unknown reason"})`;
75
- return `source discovery: ${inventory}; ${report.observedFileCount} source files observed, ${report.discovered.length} discovered, ${report.tracked.length} tracked, ${report.unsupported.length} unsupported, ${report.unavailable.length} unavailable, ${report.deferred} deferred`;
115
+ return `${inventory}; ${report.observedFileCount} source files observed, ${report.discovered.length} discovered, ${report.tracked.length} tracked, ${report.skipped.length} skipped, ${report.unavailable.length} unavailable, ${report.deferred} deferred`;
76
116
  }
77
117
 
78
118
  /** Format the age of a retained refresh attempt for a status line. */
@@ -3,13 +3,22 @@ import {
3
3
  type ProcessCrashRecoveryEntry,
4
4
  type ProcessCrashRecoveryOutcome,
5
5
  type ProcessCrashRecoveryReport,
6
+ type StartupRetryEntry,
7
+ type StartupRetryReport,
6
8
  } from "@mrclrchtr/supi-lsp/api";
7
9
  import {
8
10
  formatCompactProcessCrashRecovery,
11
+ formatCompactStartupRetry,
9
12
  formatProcessCrashRecovery,
10
13
  formatStaleDiagnosticRestarts,
14
+ formatStartupRetry,
11
15
  isFileReadinessPending,
12
16
  } from "./refresh-outcome.ts";
17
+ import {
18
+ formatCompactMaintenanceEvidence,
19
+ formatMaintenanceEvidence,
20
+ formatMaintenanceScope,
21
+ } from "./scope.ts";
13
22
  import { readSemanticHealthState } from "./semantic-state.ts";
14
23
 
15
24
  /** Render current and retained health refresh status for the TUI. */
@@ -19,7 +28,7 @@ export function readSourceTrackingStatus(source: Record<string, unknown> | null)
19
28
  if (!source || (source.status !== "complete" && source.status !== "limited")) return null;
20
29
  const reason =
21
30
  source.status === "limited" && typeof source.reason === "string" ? ` (${source.reason})` : "";
22
- return `${source.status}${reason}: ${readNumber(source.observedFileCount)} observed, ${readArrayLength(source.discovered)} discovered, ${readArrayLength(source.tracked)} tracked, ${readArrayLength(source.unsupported)} unsupported, ${readArrayLength(source.unavailable)} unavailable, ${readNumber(source.deferred)} deferred`;
31
+ return `${source.status}${reason}: ${readNumber(source.observedFileCount)} observed, ${readArrayLength(source.discovered)} discovered, ${readArrayLength(source.tracked)} tracked, ${readArrayLength(source.skipped)} skipped, ${readArrayLength(source.unavailable)} unavailable, ${readNumber(source.deferred)} deferred`;
23
32
  }
24
33
 
25
34
  export function readRefreshStatus(data: Record<string, unknown> | null): string | null {
@@ -49,18 +58,31 @@ function formatCompletedRefreshStatus(
49
58
  const processCrashRecovery = formatProcessCrashRecovery(
50
59
  readProcessCrashRecovery(refresh.processCrashRecovery),
51
60
  );
61
+ const startupRetry = formatStartupRetry(readStartupRetry(refresh.startupRetry));
52
62
  const noOp =
53
- !fileReadinessPending && attempted === 0 && restarted === 0 && processCrashRecovery === null;
63
+ !fileReadinessPending &&
64
+ attempted === 0 &&
65
+ restarted === 0 &&
66
+ processCrashRecovery === null &&
67
+ startupRetry === null;
54
68
  const label = refreshAttemptLabel(refresh);
55
69
  const base = fileReadinessPending
56
70
  ? `${label} waiting — LSP may still be warming; retry shortly`
57
71
  : noOp
58
72
  ? `${label} completed no-op`
59
73
  : `${label} completed: ${attempted} clients targeted`;
60
- const outcomes = formatRefreshOutcomes(refresh, restarted, processCrashRecovery, noOp);
74
+ const outcomes = formatRefreshOutcomes({
75
+ refresh,
76
+ restarted,
77
+ processCrashRecovery,
78
+ startupRetry,
79
+ noOp,
80
+ });
61
81
  const withOutcome = outcomes.length > 0 ? `${base}; ${outcomes.join("; ")}` : base;
62
- const evidence = formatDiagnosticEvidence(readRecord(refresh.diagnosticEvidence));
63
- const withEvidence = evidence ? `${withOutcome}; ${evidence}` : withOutcome;
82
+ const maintenanceScope = formatMaintenanceScope(refresh);
83
+ const withScope = maintenanceScope ? `${withOutcome}; ${maintenanceScope}` : withOutcome;
84
+ const evidence = formatMaintenanceEvidence(readRecord(refresh.diagnosticEvidence));
85
+ const withEvidence = evidence ? `${withScope}; ${evidence}` : withScope;
64
86
  const stale = readRecord(refresh.staleAssessment);
65
87
  return stale?.suspected === true
66
88
  ? `${withEvidence}; stale pattern suspected in ${readNumber(stale.matchedFileCount)} files`
@@ -71,23 +93,26 @@ function formatFailedRefreshStatus(refresh: Record<string, unknown>): string {
71
93
  const processCrashRecovery = formatProcessCrashRecovery(
72
94
  readProcessCrashRecovery(refresh.processCrashRecovery),
73
95
  );
96
+ const startupRetry = formatStartupRetry(readStartupRetry(refresh.startupRetry));
74
97
  const staleRestart = formatStaleDiagnosticRestartsForRecord(refresh);
75
- const evidence = formatDiagnosticEvidence(readRecord(refresh.diagnosticEvidence));
98
+ const maintenanceScope = formatMaintenanceScope(refresh);
99
+ const evidence = formatMaintenanceEvidence(readRecord(refresh.diagnosticEvidence));
76
100
  const reason = typeof refresh.reason === "string" ? `: ${refresh.reason}` : "";
77
- return `${refreshAttemptLabel(refresh)} failed${reason}${staleRestart ? `; ${staleRestart}` : ""}${processCrashRecovery ? `; ${processCrashRecovery}` : ""}${evidence ? `; ${evidence}` : ""}`;
101
+ return `${refreshAttemptLabel(refresh)} failed${reason}${maintenanceScope ? `; ${maintenanceScope}` : ""}${staleRestart ? `; ${staleRestart}` : ""}${processCrashRecovery ? `; ${processCrashRecovery}` : ""}${startupRetry ? `; ${startupRetry}` : ""}${evidence ? `; ${evidence}` : ""}`;
78
102
  }
79
-
80
- function formatRefreshOutcomes(
81
- refresh: Record<string, unknown>,
82
- restarted: number,
83
- processCrashRecovery: string | null,
84
- noOp: boolean,
85
- ): string[] {
103
+ function formatRefreshOutcomes(options: {
104
+ refresh: Record<string, unknown>;
105
+ restarted: number;
106
+ processCrashRecovery: string | null;
107
+ startupRetry: string | null;
108
+ noOp: boolean;
109
+ }): string[] {
86
110
  const outcomes: string[] = [];
87
- if (refresh.operationScope === "workspace-runtime" && !noOp) {
88
- outcomes.push(formatStaleDiagnosticRestarts(restarted));
111
+ if (options.refresh.operationScope === "workspace-runtime" && !options.noOp) {
112
+ outcomes.push(formatStaleDiagnosticRestarts(options.restarted));
89
113
  }
90
- if (processCrashRecovery) outcomes.push(processCrashRecovery);
114
+ if (options.processCrashRecovery) outcomes.push(options.processCrashRecovery);
115
+ if (options.startupRetry) outcomes.push(options.startupRetry);
91
116
  return outcomes;
92
117
  }
93
118
 
@@ -100,10 +125,12 @@ export function readCompactRefreshStatus(data: Record<string, unknown> | null):
100
125
  const processCrashRecovery = formatCompactProcessCrashRecovery(
101
126
  readProcessCrashRecovery(refresh.processCrashRecovery),
102
127
  );
128
+ const startupRetry = formatCompactStartupRetry(readStartupRetry(refresh.startupRetry));
129
+ const maintenanceScope = formatMaintenanceScope(refresh);
103
130
  if (refresh.kind === "failed") {
104
131
  const staleRestart = formatStaleDiagnosticRestartsForRecord(refresh);
105
132
  return (
106
- [sourceTracking, staleRestart, processCrashRecovery]
133
+ [maintenanceScope, sourceTracking, staleRestart, processCrashRecovery, startupRetry]
107
134
  .filter((value): value is string => value !== null)
108
135
  .join("; ") || null
109
136
  );
@@ -112,6 +139,7 @@ export function readCompactRefreshStatus(data: Record<string, unknown> | null):
112
139
  if (
113
140
  !sourceTracking &&
114
141
  !processCrashRecovery &&
142
+ !startupRetry &&
115
143
  readNumber(refresh.restartedClients) === 0 &&
116
144
  !fileReadinessPending
117
145
  ) {
@@ -120,7 +148,14 @@ export function readCompactRefreshStatus(data: Record<string, unknown> | null):
120
148
 
121
149
  const staleRestart = formatStaleDiagnosticRestartsForRecord(refresh);
122
150
  const readiness = fileReadinessPending ? "LSP may still be warming; retry shortly" : null;
123
- return [sourceTracking, staleRestart, processCrashRecovery, readiness]
151
+ return [
152
+ maintenanceScope,
153
+ sourceTracking,
154
+ staleRestart,
155
+ processCrashRecovery,
156
+ startupRetry,
157
+ readiness,
158
+ ]
124
159
  .filter((value): value is string => value !== null)
125
160
  .join("; ");
126
161
  }
@@ -132,17 +167,26 @@ export function readPreviousRefreshStatus(data: Record<string, unknown> | null):
132
167
  }
133
168
  const attempt = readRecord(refresh.lastAttempt);
134
169
  if (!attempt) return null;
135
- const evidence = formatCompactDiagnosticEvidence(readRecord(attempt.diagnosticEvidence));
170
+ const evidence = formatCompactMaintenanceEvidence(readRecord(attempt.diagnosticEvidence));
136
171
  const sourceTracking = formatSourceTracking(readRecord(attempt.sourceTracking));
137
172
  const processCrashRecovery = formatCompactProcessCrashRecovery(
138
173
  readProcessCrashRecovery(attempt.processCrashRecovery),
139
174
  );
175
+ const startupRetry = formatCompactStartupRetry(readStartupRetry(attempt.startupRetry));
140
176
  const staleRestart = formatStaleDiagnosticRestartsForRecord(attempt);
141
177
  const fileReadinessPending =
142
178
  attempt.kind === "completed" &&
143
179
  isFileReadinessPending(attempt, readSemanticHealthState(data?.semanticState));
144
180
  const readiness = fileReadinessPending ? "LSP may still be warming; retry shortly" : null;
145
- const outcome = [readiness, sourceTracking, staleRestart, processCrashRecovery, evidence]
181
+ const outcome = [
182
+ readiness,
183
+ formatMaintenanceScope(attempt),
184
+ sourceTracking,
185
+ staleRestart,
186
+ processCrashRecovery,
187
+ startupRetry,
188
+ evidence,
189
+ ]
146
190
  .filter((value): value is string => value !== null)
147
191
  .join("; ");
148
192
  if (attempt.kind === "failed") {
@@ -180,16 +224,25 @@ function formatStaleDiagnosticRestartsForRecord(attempt: Record<string, unknown>
180
224
  function formatLastRefreshSuffix(refresh: Record<string, unknown>): string {
181
225
  const attempt = readRecord(refresh.lastAttempt);
182
226
  if (!attempt) return "";
183
- const evidence = formatDiagnosticEvidence(readRecord(attempt.diagnosticEvidence));
227
+ const evidence = formatMaintenanceEvidence(readRecord(attempt.diagnosticEvidence));
184
228
  const sourceTracking = formatSourceTracking(readRecord(attempt.sourceTracking));
185
229
  const staleRestart = formatStaleDiagnosticRestartsForRecord(attempt);
186
230
  const processCrashRecovery = formatProcessCrashRecovery(
187
231
  readProcessCrashRecovery(attempt.processCrashRecovery),
188
232
  );
233
+ const startupRetry = formatStartupRetry(readStartupRetry(attempt.startupRetry));
189
234
  const fileReadinessPending =
190
235
  attempt.kind === "completed" && isFileReadinessPending(attempt, null);
191
236
  const readiness = fileReadinessPending ? "LSP may still be warming; retry shortly" : null;
192
- const outcome = [readiness, sourceTracking, staleRestart, processCrashRecovery, evidence]
237
+ const outcome = [
238
+ readiness,
239
+ formatMaintenanceScope(attempt),
240
+ sourceTracking,
241
+ staleRestart,
242
+ processCrashRecovery,
243
+ startupRetry,
244
+ evidence,
245
+ ]
193
246
  .filter((value): value is string => value !== null)
194
247
  .join("; ");
195
248
  if (attempt.kind === "failed") {
@@ -212,18 +265,6 @@ function readArrayLength(value: unknown): number {
212
265
  return Array.isArray(value) ? value.length : 0;
213
266
  }
214
267
 
215
- function formatCompactDiagnosticEvidence(evidence: Record<string, unknown> | null): string | null {
216
- const counts = readEvidenceCounts(evidence);
217
- if (!counts) return null;
218
- return `req ${counts[0]} · conf ${counts[1]} · unconf ${counts[2]} · failed ${counts[3]} · removed ${counts[4]}`;
219
- }
220
-
221
- function formatDiagnosticEvidence(evidence: Record<string, unknown> | null): string | null {
222
- const counts = readEvidenceCounts(evidence);
223
- if (!counts) return null;
224
- return `${counts[0]} requested, ${counts[1]} confirmed, ${counts[2]} unconfirmed, ${counts[3]} failed, ${counts[4]} removed`;
225
- }
226
-
227
268
  export function isPendingFileReadiness(data: Record<string, unknown> | null): boolean {
228
269
  const observation = readRecord(data?.diagnosticObservation);
229
270
  const scope = readRecord(observation?.scope);
@@ -234,6 +275,43 @@ export function isPendingFileReadiness(data: Record<string, unknown> | null): bo
234
275
  return isFileReadinessPending(attempt, readSemanticHealthState(data?.semanticState));
235
276
  }
236
277
 
278
+ function readStartupRetry(value: unknown): StartupRetryReport | null {
279
+ const report = readRecord(value);
280
+ if (!report) return null;
281
+ const recoveredRoutes = report.recoveredRoutes;
282
+ const failedRoutes = report.failedRoutes;
283
+ const omittedEntries = report.omittedEntries;
284
+ if (
285
+ !isEvidenceCount(recoveredRoutes) ||
286
+ !isEvidenceCount(failedRoutes) ||
287
+ !isEvidenceCount(omittedEntries) ||
288
+ !Array.isArray(report.entries) ||
289
+ report.entries.length > MAX_PROCESS_CRASH_RECOVERY_ENTRIES
290
+ ) {
291
+ return null;
292
+ }
293
+ const entries = report.entries
294
+ .map(readStartupRetryEntry)
295
+ .filter((entry): entry is StartupRetryEntry => entry !== null);
296
+ if (entries.length !== report.entries.length) return null;
297
+ return { recoveredRoutes, failedRoutes, entries, omittedEntries };
298
+ }
299
+
300
+ function readStartupRetryEntry(value: unknown): StartupRetryEntry | null {
301
+ const entry = readRecord(value);
302
+ if (!entry || typeof entry.name !== "string" || typeof entry.root !== "string") return null;
303
+ if (entry.outcome !== "recovered" && entry.outcome !== "retry-failed") return null;
304
+ if (entry.nextAction !== undefined && entry.nextAction !== "refresh") return null;
305
+ if (entry.failureMessage !== undefined && typeof entry.failureMessage !== "string") return null;
306
+ return {
307
+ name: entry.name,
308
+ root: entry.root,
309
+ outcome: entry.outcome,
310
+ ...(entry.nextAction !== undefined ? { nextAction: entry.nextAction } : {}),
311
+ ...(entry.failureMessage !== undefined ? { failureMessage: entry.failureMessage } : {}),
312
+ };
313
+ }
314
+
237
315
  function readProcessCrashRecovery(value: unknown): ProcessCrashRecoveryReport | null {
238
316
  const report = readRecord(value);
239
317
  if (!report) return null;
@@ -267,7 +345,7 @@ function readProcessCrashRecoveryEntry(value: unknown): ProcessCrashRecoveryEntr
267
345
  if (
268
346
  entry.nextAction !== undefined &&
269
347
  entry.nextAction !== "use-exact-file" &&
270
- entry.nextAction !== "reload-workspace"
348
+ entry.nextAction !== "refresh"
271
349
  ) {
272
350
  return null;
273
351
  }
@@ -290,18 +368,6 @@ function isProcessCrashRecoveryOutcome(value: unknown): value is ProcessCrashRec
290
368
  );
291
369
  }
292
370
 
293
- function readEvidenceCounts(evidence: Record<string, unknown> | null): number[] | null {
294
- if (!evidence) return null;
295
- const counts = [
296
- evidence.requested,
297
- evidence.confirmed,
298
- evidence.unconfirmed,
299
- evidence.failed,
300
- evidence.removed,
301
- ];
302
- return counts.every(isEvidenceCount) ? counts : null;
303
- }
304
-
305
371
  function isEvidenceCount(value: unknown): value is number {
306
372
  return typeof value === "number" && Number.isInteger(value) && value >= 0;
307
373
  }
@@ -0,0 +1,78 @@
1
+ /** Formatting for diagnostic result and maintenance scopes and their evidence. */
2
+
3
+ function readRecord(value: unknown): Record<string, unknown> | null {
4
+ return typeof value === "object" && value !== null ? (value as Record<string, unknown>) : null;
5
+ }
6
+
7
+ export function withDiagnosticScope(summary: string, data: Record<string, unknown> | null): string {
8
+ const scope = formatDiagnosticScope(readDiagnosticScope(data));
9
+ return scope ? `${summary} — evidence scope: ${scope}` : summary;
10
+ }
11
+
12
+ function readDiagnosticScope(data: Record<string, unknown> | null): Record<string, unknown> | null {
13
+ return readRecord(readRecord(data?.diagnosticObservation)?.scope);
14
+ }
15
+
16
+ function formatDiagnosticScope(scope: Record<string, unknown> | null): string | null {
17
+ if (scope?.kind === "file" && typeof scope.path === "string") {
18
+ return `live file diagnostic request for ${scope.path}`;
19
+ }
20
+ if (scope?.kind === "tracked-files") {
21
+ return typeof scope.filter === "string"
22
+ ? `tracked-file diagnostic snapshot under ${scope.filter}`
23
+ : "tracked-file diagnostic snapshot";
24
+ }
25
+ return null;
26
+ }
27
+
28
+ export function formatCompactDiagnosticEvidence(
29
+ evidence: Record<string, unknown> | null,
30
+ scopeKind: string | null,
31
+ ): string | null {
32
+ const counts = readEvidenceCounts(evidence);
33
+ if (!counts) return null;
34
+ const scopeLabel = scopeKind === "file" ? "file" : "tracked-file";
35
+ return `${scopeLabel} evidence req ${counts[0]} · conf ${counts[1]} · unconf ${counts[2]} · failed ${counts[3]} · removed ${counts[4]}`;
36
+ }
37
+
38
+ export function formatDiagnosticEvidence(evidence: Record<string, unknown> | null): string | null {
39
+ const counts = readEvidenceCounts(evidence);
40
+ if (!counts) return null;
41
+ return `${counts[0]} requested, ${counts[1]} confirmed, ${counts[2]} unconfirmed, ${counts[3]} failed, ${counts[4]} removed`;
42
+ }
43
+
44
+ export function formatMaintenanceEvidence(evidence: Record<string, unknown> | null): string | null {
45
+ const counts = readEvidenceCounts(evidence);
46
+ if (!counts) return null;
47
+ return `maintenance evidence: ${counts[0]} requested, ${counts[1]} confirmed, ${counts[2]} unconfirmed, ${counts[3]} failed, ${counts[4]} removed`;
48
+ }
49
+
50
+ export function formatCompactMaintenanceEvidence(
51
+ evidence: Record<string, unknown> | null,
52
+ ): string | null {
53
+ const counts = readEvidenceCounts(evidence);
54
+ if (!counts) return null;
55
+ return `maintenance evidence req ${counts[0]} · conf ${counts[1]} · unconf ${counts[2]} · failed ${counts[3]} · removed ${counts[4]}`;
56
+ }
57
+
58
+ export function formatMaintenanceScope(refresh: Record<string, unknown>): string | null {
59
+ if (refresh.operationScope === "file-runtime") return "maintenance scope: file runtime";
60
+ if (refresh.operationScope === "workspace-runtime") return "maintenance scope: workspace runtime";
61
+ return null;
62
+ }
63
+
64
+ function readEvidenceCounts(evidence: Record<string, unknown> | null): number[] | null {
65
+ if (!evidence) return null;
66
+ const counts = [
67
+ evidence.requested,
68
+ evidence.confirmed,
69
+ evidence.unconfirmed,
70
+ evidence.failed,
71
+ evidence.removed,
72
+ ];
73
+ return counts.every(isEvidenceCount) ? counts : null;
74
+ }
75
+
76
+ function isEvidenceCount(value: unknown): value is number {
77
+ return typeof value === "number" && Number.isInteger(value) && value >= 0;
78
+ }
@@ -18,7 +18,7 @@ export const codeHealthSpec = {
18
18
  refresh: Type.Optional(
19
19
  Type.Boolean({
20
20
  description:
21
- "Attempt diagnostic and required process-crash recovery before collecting; result reports the outcome.",
21
+ "Retry failed LSP routes and collect fresh diagnostic health evidence; result reports route recovery separately from diagnostic confirmation.",
22
22
  }),
23
23
  ),
24
24
  include: Type.Optional(