@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
@@ -11,9 +11,15 @@ import {
11
11
  formatProjectServerRouteSummary,
12
12
  formatProjectServerStatusReason,
13
13
  } from "../../analysis/health/server-status.ts";
14
+ import { makeRelative, renderFileScopeStatus } from "./file-scope-markdown.ts";
15
+ import {
16
+ formatProcessCrashRecovery,
17
+ formatRefreshElapsed,
18
+ formatStaleDiagnosticRestarts as formatStaleDiagnosticRestartsText,
19
+ isFileReadinessPending,
20
+ } from "./refresh-outcome.ts";
14
21
  import type {
15
22
  HealthData,
16
- HealthDiagnosticObservation,
17
23
  HealthDiagnosticScope,
18
24
  HealthRefreshAttempt,
19
25
  HealthResultAssembly,
@@ -48,19 +54,25 @@ function renderRefreshStatus(
48
54
  if (!hasDiagnostics) return;
49
55
 
50
56
  switch (data.refresh.kind) {
51
- case "completed":
57
+ case "completed": {
58
+ const fileReadinessPending = isFileReadinessPending(data.refresh, data.semanticState);
52
59
  lines.push(
53
- `**${refreshAttemptLabel(data.refresh)}**: ${asSentence(completedRefreshText(data.refresh))}`,
60
+ `**${refreshAttemptLabel(data.refresh)}**: ${asSentence(completedRefreshText(data.refresh, fileReadinessPending))}`,
54
61
  );
55
62
  lines.push(`**Stale assessment**: ${asSentence(staleAssessmentText(data.refresh))}`);
56
63
  lines.push("");
57
64
  return;
65
+ }
58
66
  case "failed": {
59
67
  const evidence = data.refresh.diagnosticEvidence
60
68
  ? `; ${formatDiagnosticEvidence(data.refresh.diagnosticEvidence)}`
61
69
  : "";
70
+ const processCrashRecovery = data.refresh.processCrashRecovery
71
+ ? formatProcessCrashRecovery(data.refresh.processCrashRecovery)
72
+ : null;
73
+ const staleRestart = formatStaleDiagnosticRestarts(data.refresh);
62
74
  lines.push(
63
- `**${refreshAttemptLabel(data.refresh)}**: failed — ${data.refresh.reason}${evidence}`,
75
+ `**${refreshAttemptLabel(data.refresh)}**: failed — ${data.refresh.reason}${staleRestart ? `; ${staleRestart}` : ""}${processCrashRecovery ? `; ${processCrashRecovery}` : ""}${evidence}`,
64
76
  );
65
77
  lines.push("");
66
78
  return;
@@ -84,14 +96,27 @@ function renderRefreshStatus(
84
96
 
85
97
  function completedRefreshText(
86
98
  attempt: Extract<HealthRefreshAttempt, { kind: "completed" }>,
99
+ fileReadinessPending = attempt.fileReadiness === "pending",
87
100
  ): string {
88
- const base =
89
- attempt.attemptedActiveClients === 0 && attempt.restartedClients === 0
101
+ const processCrashRecovery = formatProcessCrashRecovery(attempt.processCrashRecovery);
102
+ const noOp =
103
+ !fileReadinessPending &&
104
+ attempt.attemptedActiveClients === 0 &&
105
+ attempt.restartedClients === 0 &&
106
+ processCrashRecovery === null;
107
+ const base = fileReadinessPending
108
+ ? "readiness pending — LSP may still be warming; retry shortly"
109
+ : noOp
90
110
  ? "completed no-op — no active clients were targeted"
91
- : `completed — ${attempt.attemptedActiveClients} active client${plural(attempt.attemptedActiveClients)} targeted, ${attempt.restartedClients} restarted`;
111
+ : `completed — ${attempt.attemptedActiveClients} active client${plural(attempt.attemptedActiveClients)} targeted`;
112
+ const staleRestart = noOp ? null : formatStaleDiagnosticRestarts(attempt);
113
+ const outcome = [staleRestart, processCrashRecovery].filter(
114
+ (value): value is string => value !== null,
115
+ );
116
+ const withOutcome = outcome.length > 0 ? `${base}; ${outcome.join("; ")}` : base;
92
117
  return attempt.operationScope === "workspace-runtime"
93
- ? `${base}; ${formatDiagnosticEvidence(attempt.diagnosticEvidence)}`
94
- : base;
118
+ ? `${withOutcome}; ${formatDiagnosticEvidence(attempt.diagnosticEvidence)}`
119
+ : withOutcome;
95
120
  }
96
121
 
97
122
  function refreshAttemptLabel(attempt: Pick<HealthRefreshAttempt, "operationScope">): string {
@@ -117,7 +142,7 @@ function renderLastAttempt(lines: string[], attempt: HealthRefreshAttempt, cwd:
117
142
  const outcome = refreshAttemptOutcome(attempt);
118
143
  const label = refreshAttemptLabel(attempt);
119
144
  lines.push(
120
- `**Previous ${label.toLowerCase()}**: ${asSentence(outcome)} Started ${formatElapsed(Date.now() - attempt.attemptedAt)}; requested ${formatDiagnosticScope(attempt.requestedDiagnosticScope, cwd)}; operation scope: ${refreshOperationScopeText(attempt)}.`,
145
+ `**Previous ${label.toLowerCase()}**: ${asSentence(outcome)} Started ${formatRefreshElapsed(Date.now() - attempt.attemptedAt)}; requested ${formatDiagnosticScope(attempt.requestedDiagnosticScope, cwd)}; operation scope: ${refreshOperationScopeText(attempt)}.`,
121
146
  );
122
147
  }
123
148
 
@@ -125,24 +150,35 @@ function renderRetainedAttempt(lines: string[], attempt: HealthRefreshAttempt, c
125
150
  const label =
126
151
  attempt.operationScope === "file-runtime" ? "File LSP maintenance" : "Diagnostic refresh";
127
152
  lines.push(
128
- `**${label}**: not requested for this call. Previous attempt ${asSentence(refreshAttemptOutcome(attempt))} Started ${formatElapsed(Date.now() - attempt.attemptedAt)}; requested ${formatDiagnosticScope(attempt.requestedDiagnosticScope, cwd)}; operation scope: ${refreshOperationScopeText(attempt)}.`,
153
+ `**${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)}.`,
129
154
  );
130
155
  }
131
156
 
132
157
  function refreshAttemptOutcome(attempt: HealthRefreshAttempt): string {
133
- return attempt.kind === "completed"
134
- ? completedRefreshText(attempt)
135
- : `failed — ${attempt.reason}${attempt.diagnosticEvidence ? `; ${formatDiagnosticEvidence(attempt.diagnosticEvidence)}` : ""}`;
158
+ if (attempt.kind === "completed") return completedRefreshText(attempt);
159
+ const processCrashRecovery = attempt.processCrashRecovery
160
+ ? formatProcessCrashRecovery(attempt.processCrashRecovery)
161
+ : null;
162
+ const staleRestart = formatStaleDiagnosticRestarts(attempt);
163
+ const evidence = attempt.diagnosticEvidence
164
+ ? `; ${formatDiagnosticEvidence(attempt.diagnosticEvidence)}`
165
+ : "";
166
+ return `failed — ${attempt.reason}${staleRestart ? `; ${staleRestart}` : ""}${processCrashRecovery ? `; ${processCrashRecovery}` : ""}${evidence}`;
136
167
  }
137
168
 
138
169
  function refreshOperationScopeText(attempt: HealthRefreshAttempt): string {
139
170
  return attempt.operationScope === "file-runtime" ? "file runtime" : "workspace runtime";
140
171
  }
141
172
 
142
- function formatElapsed(milliseconds: number): string {
143
- const seconds = Math.max(0, Math.round(milliseconds / 1_000));
144
- if (seconds < 60) return `${seconds}s ago`;
145
- return `${Math.round(seconds / 60)}m ago`;
173
+ function formatStaleDiagnosticRestarts(attempt: {
174
+ kind?: HealthRefreshAttempt["kind"];
175
+ operationScope: HealthRefreshAttempt["operationScope"];
176
+ restartedClients?: number;
177
+ }): string | null {
178
+ if (attempt.operationScope !== "workspace-runtime" || attempt.restartedClients === undefined) {
179
+ return null;
180
+ }
181
+ return formatStaleDiagnosticRestartsText(attempt.restartedClients);
146
182
  }
147
183
 
148
184
  function renderCapabilityWarningsSection(
@@ -193,49 +229,6 @@ function renderDiagnosticsSection(lines: string[], data: HealthData, cwd: string
193
229
  lines.push("");
194
230
  }
195
231
 
196
- /**
197
- * Render the tsconfig coverage verdict for a single-file health request.
198
- *
199
- * Written in plain vocabulary (no basis terms) so a reader without tsconfig
200
- * background can act on it: the sentence names the deciding config and states
201
- * whether the file's errors are part of workspace diagnostics. The structured
202
- * decision basis stays in the diagnostics.scope debug event.
203
- */
204
- function renderFileScopeStatus(
205
- lines: string[],
206
- observation: Extract<
207
- HealthDiagnosticObservation,
208
- { kind: "completed" | "partial" | "unavailable" }
209
- >,
210
- cwd: string,
211
- ): void {
212
- if (observation.scope.kind !== "file" || !observation.scopeStatus) return;
213
-
214
- const decision = observation.scopeStatus;
215
- switch (decision.status) {
216
- case "included":
217
- lines.push(
218
- `**Tsconfig**: covered by ${formatConfigPath(decision.configPath, cwd)} — part of workspace diagnostics.`,
219
- );
220
- return;
221
- case "excluded":
222
- lines.push(
223
- `**Tsconfig**: NOT covered by ${formatConfigPath(decision.configPath, cwd)} — not part of workspace diagnostics.`,
224
- );
225
- return;
226
- case "no-config":
227
- lines.push("**Tsconfig**: no tsconfig.json or jsconfig.json found — nothing filtered.");
228
- return;
229
- case "out-of-tree":
230
- lines.push("**Tsconfig**: outside the project root — not part of workspace diagnostics.");
231
- }
232
- }
233
-
234
- function formatConfigPath(configPath: string | null, cwd: string): string {
235
- if (!configPath) return "no config";
236
- return `\`${makeRelative(cwd, configPath)}\``;
237
- }
238
-
239
232
  function formatDiagnosticEvidence(evidence: {
240
233
  requested: number;
241
234
  confirmed: number;
@@ -250,7 +243,14 @@ function renderDiagnosticObservation(lines: string[], data: HealthData, cwd: str
250
243
  const observation = data.diagnostics;
251
244
  if (observation.kind === "not-requested") return;
252
245
  if (observation.kind === "unavailable") {
253
- lines.push(`Diagnostics unavailable — ${asSentence(observation.reason)}`);
246
+ if (
247
+ observation.scope.kind === "file" &&
248
+ isFileReadinessPending(currentRefreshAttempt(data), data.semanticState)
249
+ ) {
250
+ lines.push("Diagnostics pending — LSP may still be warming; retry shortly.");
251
+ } else {
252
+ lines.push(`Diagnostics unavailable — ${asSentence(observation.reason)}`);
253
+ }
254
254
  return;
255
255
  }
256
256
  if (observation.kind === "partial") {
@@ -269,6 +269,10 @@ function renderDiagnosticObservation(lines: string[], data: HealthData, cwd: str
269
269
  renderDiagnosticEntries(lines, data, cwd);
270
270
  }
271
271
 
272
+ function currentRefreshAttempt(data: HealthData): HealthRefreshAttempt | null {
273
+ return data.refresh.kind === "completed" || data.refresh.kind === "failed" ? data.refresh : null;
274
+ }
275
+
272
276
  function asSentence(text: string): string {
273
277
  return /[.!?]$/.test(text) ? text : `${text}.`;
274
278
  }
@@ -371,7 +375,3 @@ function formatDiagnosticScope(scope: HealthDiagnosticScope, cwd: string): strin
371
375
  function plural(count: number): string {
372
376
  return count === 1 ? "" : "s";
373
377
  }
374
-
375
- function makeRelative(cwd: string, file: string): string {
376
- return file.startsWith(cwd) ? file.slice(cwd.length + 1) : file;
377
- }
@@ -0,0 +1,76 @@
1
+ import type {
2
+ ProcessCrashRecoveryEntry,
3
+ ProcessCrashRecoveryReport,
4
+ } from "@mrclrchtr/supi-lsp/api";
5
+ import type { SemanticHealthState } from "../../session/health-types.ts";
6
+
7
+ /** Report whether a file health result is waiting for semantic readiness. */
8
+ export function isFileReadinessPending(
9
+ attempt: { operationScope?: string; fileReadiness?: string } | null | undefined,
10
+ semanticState: SemanticHealthState | null | undefined,
11
+ ): boolean {
12
+ if (attempt?.operationScope !== undefined) {
13
+ return (
14
+ attempt.operationScope === "file-runtime" &&
15
+ (attempt.fileReadiness === "pending" ||
16
+ (attempt.fileReadiness === undefined && semanticState?.kind === "pending"))
17
+ );
18
+ }
19
+ return semanticState?.kind === "pending";
20
+ }
21
+
22
+ /** Format the outcome of stale-diagnostic client restarts. */
23
+ export function formatStaleDiagnosticRestarts(restartedClients: number): string {
24
+ return `stale diagnostic restarts: ${restartedClients} client${plural(restartedClients)} restarted`;
25
+ }
26
+
27
+ /** Format process-crash counts for the compact health view. */
28
+ export function formatCompactProcessCrashRecovery(
29
+ report: ProcessCrashRecoveryReport | null | undefined,
30
+ ): string | null {
31
+ if (
32
+ !report ||
33
+ (report.recoveredRoutes === 0 &&
34
+ report.skippedRoutes === 0 &&
35
+ report.failedRoutes === 0 &&
36
+ report.exhaustedRoutes === 0)
37
+ ) {
38
+ return null;
39
+ }
40
+ return `process-crash recovery: ${report.recoveredRoutes} route${plural(report.recoveredRoutes)} recovered (${report.skippedRoutes} skipped, ${report.failedRoutes} failed, ${report.exhaustedRoutes} exhausted)`;
41
+ }
42
+
43
+ /** Format process-crash counts and bounded route entries for detailed health output. */
44
+ export function formatProcessCrashRecovery(
45
+ report: ProcessCrashRecoveryReport | null | undefined,
46
+ ): string | null {
47
+ const summary = formatCompactProcessCrashRecovery(report);
48
+ if (!summary || !report) return summary;
49
+ const entries = report.entries.map(formatProcessCrashRecoveryEntry);
50
+ const omitted =
51
+ report.omittedEntries > 0
52
+ ? `${report.omittedEntries} more route${plural(report.omittedEntries)}`
53
+ : null;
54
+ const details = [...entries, omitted]
55
+ .filter((value): value is string => value !== null)
56
+ .join("; ");
57
+ return details ? `${summary}; ${details}` : summary;
58
+ }
59
+
60
+ function formatProcessCrashRecoveryEntry(entry: ProcessCrashRecoveryEntry): string {
61
+ const outcome = entry.outcome.replaceAll("-", " ");
62
+ const action = entry.nextAction ? `; next: ${entry.nextAction.replaceAll("-", " ")}` : "";
63
+ const failure = entry.failureMessage ? `; ${entry.failureMessage}` : "";
64
+ return `${entry.name} @ ${entry.root}: ${outcome}${action}${failure}`;
65
+ }
66
+
67
+ /** Format the age of a retained refresh attempt for a status line. */
68
+ export function formatRefreshElapsed(milliseconds: number): string {
69
+ const seconds = Math.max(0, Math.round(milliseconds / 1_000));
70
+ if (seconds < 60) return `${seconds}s ago`;
71
+ return `${Math.round(seconds / 60)}m ago`;
72
+ }
73
+
74
+ function plural(count: number): string {
75
+ return count === 1 ? "" : "s";
76
+ }
@@ -1,3 +1,17 @@
1
+ import {
2
+ MAX_PROCESS_CRASH_RECOVERY_ENTRIES,
3
+ type ProcessCrashRecoveryEntry,
4
+ type ProcessCrashRecoveryOutcome,
5
+ type ProcessCrashRecoveryReport,
6
+ } from "@mrclrchtr/supi-lsp/api";
7
+ import {
8
+ formatCompactProcessCrashRecovery,
9
+ formatProcessCrashRecovery,
10
+ formatStaleDiagnosticRestarts,
11
+ isFileReadinessPending,
12
+ } from "./refresh-outcome.ts";
13
+ import { readSemanticHealthState } from "./semantic-state.ts";
14
+
1
15
  /** Render current and retained health refresh status for the TUI. */
2
16
 
3
17
  export function readRefreshStatus(data: Record<string, unknown> | null): string | null {
@@ -5,25 +19,10 @@ export function readRefreshStatus(data: Record<string, unknown> | null): string
5
19
  if (!refresh) return null;
6
20
 
7
21
  switch (refresh.kind) {
8
- case "completed": {
9
- const attempted = readNumber(refresh.attemptedActiveClients);
10
- const restarted = readNumber(refresh.restartedClients);
11
- const stale = readRecord(refresh.staleAssessment);
12
- const noOp = attempted === 0 && restarted === 0;
13
- const label = refreshAttemptLabel(refresh);
14
- const base = noOp
15
- ? `${label} completed no-op`
16
- : `${label} completed: ${attempted} clients targeted, ${restarted} restarted`;
17
- const evidence = formatDiagnosticEvidence(readRecord(refresh.diagnosticEvidence));
18
- const withEvidence = evidence ? `${base}; ${evidence}` : base;
19
- return stale?.suspected === true
20
- ? `${withEvidence}; stale pattern suspected in ${readNumber(stale.matchedFileCount)} files`
21
- : withEvidence;
22
- }
23
- case "failed": {
24
- const evidence = formatDiagnosticEvidence(readRecord(refresh.diagnosticEvidence));
25
- return `${refreshAttemptLabel(refresh)} failed${typeof refresh.reason === "string" ? `: ${refresh.reason}` : ""}${evidence ? `; ${evidence}` : ""}`;
26
- }
22
+ case "completed":
23
+ return formatCompletedRefreshStatus(refresh, isPendingFileReadiness(data));
24
+ case "failed":
25
+ return formatFailedRefreshStatus(refresh);
27
26
  case "not-attempted":
28
27
  return `refresh attempt not started${formatLastRefreshSuffix(refresh)}`;
29
28
  case "not-requested":
@@ -33,6 +32,89 @@ export function readRefreshStatus(data: Record<string, unknown> | null): string
33
32
  }
34
33
  }
35
34
 
35
+ function formatCompletedRefreshStatus(
36
+ refresh: Record<string, unknown>,
37
+ fileReadinessPending: boolean,
38
+ ): string {
39
+ const attempted = readNumber(refresh.attemptedActiveClients);
40
+ const restarted = readNumber(refresh.restartedClients);
41
+ const processCrashRecovery = formatProcessCrashRecovery(
42
+ readProcessCrashRecovery(refresh.processCrashRecovery),
43
+ );
44
+ const noOp =
45
+ !fileReadinessPending && attempted === 0 && restarted === 0 && processCrashRecovery === null;
46
+ const label = refreshAttemptLabel(refresh);
47
+ const base = fileReadinessPending
48
+ ? `${label} waiting — LSP may still be warming; retry shortly`
49
+ : noOp
50
+ ? `${label} completed no-op`
51
+ : `${label} completed: ${attempted} clients targeted`;
52
+ const outcomes = formatRefreshOutcomes(refresh, restarted, processCrashRecovery, noOp);
53
+ const withOutcome = outcomes.length > 0 ? `${base}; ${outcomes.join("; ")}` : base;
54
+ const evidence = formatDiagnosticEvidence(readRecord(refresh.diagnosticEvidence));
55
+ const withEvidence = evidence ? `${withOutcome}; ${evidence}` : withOutcome;
56
+ const stale = readRecord(refresh.staleAssessment);
57
+ return stale?.suspected === true
58
+ ? `${withEvidence}; stale pattern suspected in ${readNumber(stale.matchedFileCount)} files`
59
+ : withEvidence;
60
+ }
61
+
62
+ function formatFailedRefreshStatus(refresh: Record<string, unknown>): string {
63
+ const processCrashRecovery = formatProcessCrashRecovery(
64
+ readProcessCrashRecovery(refresh.processCrashRecovery),
65
+ );
66
+ const staleRestart = formatStaleDiagnosticRestartsForRecord(refresh);
67
+ const evidence = formatDiagnosticEvidence(readRecord(refresh.diagnosticEvidence));
68
+ const reason = typeof refresh.reason === "string" ? `: ${refresh.reason}` : "";
69
+ return `${refreshAttemptLabel(refresh)} failed${reason}${staleRestart ? `; ${staleRestart}` : ""}${processCrashRecovery ? `; ${processCrashRecovery}` : ""}${evidence ? `; ${evidence}` : ""}`;
70
+ }
71
+
72
+ function formatRefreshOutcomes(
73
+ refresh: Record<string, unknown>,
74
+ restarted: number,
75
+ processCrashRecovery: string | null,
76
+ noOp: boolean,
77
+ ): string[] {
78
+ const outcomes: string[] = [];
79
+ if (refresh.operationScope === "workspace-runtime" && !noOp) {
80
+ outcomes.push(formatStaleDiagnosticRestarts(restarted));
81
+ }
82
+ if (processCrashRecovery) outcomes.push(processCrashRecovery);
83
+ return outcomes;
84
+ }
85
+
86
+ /** Render only the material current recovery outcome for compact TUI chrome. */
87
+ export function readCompactRefreshStatus(data: Record<string, unknown> | null): string | null {
88
+ const refresh = readRecord(data?.refresh);
89
+ if (!refresh || (refresh.kind !== "completed" && refresh.kind !== "failed")) return null;
90
+
91
+ const processCrashRecovery = formatCompactProcessCrashRecovery(
92
+ readProcessCrashRecovery(refresh.processCrashRecovery),
93
+ );
94
+ if (refresh.kind === "failed") {
95
+ const staleRestart = formatStaleDiagnosticRestartsForRecord(refresh);
96
+ return (
97
+ [staleRestart, processCrashRecovery]
98
+ .filter((value): value is string => value !== null)
99
+ .join("; ") || null
100
+ );
101
+ }
102
+ const fileReadinessPending = isPendingFileReadiness(data);
103
+ if (
104
+ !processCrashRecovery &&
105
+ readNumber(refresh.restartedClients) === 0 &&
106
+ !fileReadinessPending
107
+ ) {
108
+ return null;
109
+ }
110
+
111
+ const staleRestart = formatStaleDiagnosticRestartsForRecord(refresh);
112
+ const readiness = fileReadinessPending ? "LSP may still be warming; retry shortly" : null;
113
+ return [staleRestart, processCrashRecovery, readiness]
114
+ .filter((value): value is string => value !== null)
115
+ .join("; ");
116
+ }
117
+
36
118
  export function readPreviousRefreshStatus(data: Record<string, unknown> | null): string | null {
37
119
  const refresh = readRecord(data?.refresh);
38
120
  if (!refresh || (refresh.kind !== "not-requested" && refresh.kind !== "not-attempted")) {
@@ -41,11 +123,23 @@ export function readPreviousRefreshStatus(data: Record<string, unknown> | null):
41
123
  const attempt = readRecord(refresh.lastAttempt);
42
124
  if (!attempt) return null;
43
125
  const evidence = formatCompactDiagnosticEvidence(readRecord(attempt.diagnosticEvidence));
126
+ const processCrashRecovery = formatCompactProcessCrashRecovery(
127
+ readProcessCrashRecovery(attempt.processCrashRecovery),
128
+ );
129
+ const staleRestart = formatStaleDiagnosticRestartsForRecord(attempt);
130
+ const fileReadinessPending =
131
+ attempt.kind === "completed" &&
132
+ isFileReadinessPending(attempt, readSemanticHealthState(data?.semanticState));
133
+ const readiness = fileReadinessPending ? "LSP may still be warming; retry shortly" : null;
134
+ const outcome = [readiness, staleRestart, processCrashRecovery, evidence]
135
+ .filter((value): value is string => value !== null)
136
+ .join("; ");
44
137
  if (attempt.kind === "failed") {
45
- return `last ${refreshAttemptLabel(attempt)} failed${evidence ? ` (${evidence})` : ""}`;
138
+ return `last ${refreshAttemptLabel(attempt)} failed${outcome ? ` (${outcome})` : ""}`;
46
139
  }
47
140
  if (attempt.kind === "completed") {
48
- return `last ${refreshAttemptLabel(attempt)} completed${evidence ? ` (${evidence})` : ""}`;
141
+ const status = fileReadinessPending ? "waiting" : "completed";
142
+ return `last ${refreshAttemptLabel(attempt)} ${status}${outcome ? ` (${outcome})` : ""}`;
49
143
  }
50
144
  return null;
51
145
  }
@@ -54,16 +148,45 @@ function refreshAttemptLabel(attempt: Record<string, unknown>): string {
54
148
  return attempt.operationScope === "file-runtime" ? "file maintenance attempt" : "refresh attempt";
55
149
  }
56
150
 
151
+ function formatStaleDiagnosticRestartsForRecord(attempt: Record<string, unknown>): string | null {
152
+ if (
153
+ attempt.operationScope !== "workspace-runtime" ||
154
+ typeof attempt.restartedClients !== "number"
155
+ ) {
156
+ return null;
157
+ }
158
+ const processCrashRecovery = formatProcessCrashRecovery(
159
+ readProcessCrashRecovery(attempt.processCrashRecovery),
160
+ );
161
+ const noOp =
162
+ attempt.kind === "completed" &&
163
+ readNumber(attempt.attemptedActiveClients) === 0 &&
164
+ attempt.restartedClients === 0 &&
165
+ processCrashRecovery === null;
166
+ return noOp ? null : formatStaleDiagnosticRestarts(attempt.restartedClients);
167
+ }
168
+
57
169
  function formatLastRefreshSuffix(refresh: Record<string, unknown>): string {
58
170
  const attempt = readRecord(refresh.lastAttempt);
59
171
  if (!attempt) return "";
60
172
  const evidence = formatDiagnosticEvidence(readRecord(attempt.diagnosticEvidence));
173
+ const staleRestart = formatStaleDiagnosticRestartsForRecord(attempt);
174
+ const processCrashRecovery = formatProcessCrashRecovery(
175
+ readProcessCrashRecovery(attempt.processCrashRecovery),
176
+ );
177
+ const fileReadinessPending =
178
+ attempt.kind === "completed" && isFileReadinessPending(attempt, null);
179
+ const readiness = fileReadinessPending ? "LSP may still be warming; retry shortly" : null;
180
+ const outcome = [readiness, staleRestart, processCrashRecovery, evidence]
181
+ .filter((value): value is string => value !== null)
182
+ .join("; ");
61
183
  if (attempt.kind === "failed") {
62
184
  const reason = typeof attempt.reason === "string" ? `: ${attempt.reason}` : "";
63
- return `; last ${refreshAttemptLabel(attempt)} failed${reason}${evidence ? `; ${evidence}` : ""}`;
185
+ return `; last ${refreshAttemptLabel(attempt)} failed${reason}${outcome ? `; ${outcome}` : ""}`;
64
186
  }
65
187
  if (attempt.kind === "completed") {
66
- return `; last ${refreshAttemptLabel(attempt)} completed${evidence ? `; ${evidence}` : ""}`;
188
+ const status = fileReadinessPending ? "waiting" : "completed";
189
+ return `; last ${refreshAttemptLabel(attempt)} ${status}${outcome ? `; ${outcome}` : ""}`;
67
190
  }
68
191
  return "";
69
192
  }
@@ -80,6 +203,72 @@ function formatDiagnosticEvidence(evidence: Record<string, unknown> | null): str
80
203
  return `${counts[0]} requested, ${counts[1]} confirmed, ${counts[2]} unconfirmed, ${counts[3]} failed, ${counts[4]} removed`;
81
204
  }
82
205
 
206
+ export function isPendingFileReadiness(data: Record<string, unknown> | null): boolean {
207
+ const observation = readRecord(data?.diagnosticObservation);
208
+ const scope = readRecord(observation?.scope);
209
+ if (scope?.kind !== "file") return false;
210
+
211
+ const refresh = readRecord(data?.refresh);
212
+ const attempt = refresh && typeof refresh.operationScope === "string" ? refresh : null;
213
+ return isFileReadinessPending(attempt, readSemanticHealthState(data?.semanticState));
214
+ }
215
+
216
+ function readProcessCrashRecovery(value: unknown): ProcessCrashRecoveryReport | null {
217
+ const report = readRecord(value);
218
+ if (!report) return null;
219
+ const recoveredRoutes = report.recoveredRoutes;
220
+ const skippedRoutes = report.skippedRoutes;
221
+ const failedRoutes = report.failedRoutes;
222
+ const exhaustedRoutes = report.exhaustedRoutes;
223
+ const omittedEntries = report.omittedEntries;
224
+ if (
225
+ !isEvidenceCount(recoveredRoutes) ||
226
+ !isEvidenceCount(skippedRoutes) ||
227
+ !isEvidenceCount(failedRoutes) ||
228
+ !isEvidenceCount(exhaustedRoutes) ||
229
+ !isEvidenceCount(omittedEntries) ||
230
+ !Array.isArray(report.entries) ||
231
+ report.entries.length > MAX_PROCESS_CRASH_RECOVERY_ENTRIES
232
+ ) {
233
+ return null;
234
+ }
235
+ const entries = report.entries
236
+ .map(readProcessCrashRecoveryEntry)
237
+ .filter((entry): entry is ProcessCrashRecoveryEntry => entry !== null);
238
+ if (entries.length !== report.entries.length) return null;
239
+ return { recoveredRoutes, skippedRoutes, failedRoutes, exhaustedRoutes, entries, omittedEntries };
240
+ }
241
+
242
+ function readProcessCrashRecoveryEntry(value: unknown): ProcessCrashRecoveryEntry | null {
243
+ const entry = readRecord(value);
244
+ if (!entry || typeof entry.name !== "string" || typeof entry.root !== "string") return null;
245
+ if (!isProcessCrashRecoveryOutcome(entry.outcome)) return null;
246
+ if (
247
+ entry.nextAction !== undefined &&
248
+ entry.nextAction !== "use-exact-file" &&
249
+ entry.nextAction !== "reload-workspace"
250
+ ) {
251
+ return null;
252
+ }
253
+ if (entry.failureMessage !== undefined && typeof entry.failureMessage !== "string") return null;
254
+ return {
255
+ name: entry.name,
256
+ root: entry.root,
257
+ outcome: entry.outcome,
258
+ ...(entry.nextAction !== undefined ? { nextAction: entry.nextAction } : {}),
259
+ ...(entry.failureMessage !== undefined ? { failureMessage: entry.failureMessage } : {}),
260
+ };
261
+ }
262
+
263
+ function isProcessCrashRecoveryOutcome(value: unknown): value is ProcessCrashRecoveryOutcome {
264
+ return (
265
+ value === "recovered" ||
266
+ value === "skipped-no-retained-file" ||
267
+ value === "recovery-failed" ||
268
+ value === "recovery-exhausted"
269
+ );
270
+ }
271
+
83
272
  function readEvidenceCounts(evidence: Record<string, unknown> | null): number[] | null {
84
273
  if (!evidence) return null;
85
274
  const counts = [
@@ -19,6 +19,7 @@ export type {
19
19
  HealthDiagnosticEntry,
20
20
  HealthDiagnosticObservation,
21
21
  HealthDiagnosticScope,
22
+ HealthFileReadiness,
22
23
  HealthRefreshAttempt,
23
24
  HealthRefreshState,
24
25
  HealthSection,
@@ -24,7 +24,12 @@ import {
24
24
  type ToolResult,
25
25
  } from "../../ui/tui/common.ts";
26
26
  import type { CodeHealthToolParams } from "./execute.ts";
27
- import { readPreviousRefreshStatus, readRefreshStatus } from "./refresh-status.ts";
27
+ import {
28
+ isPendingFileReadiness,
29
+ readCompactRefreshStatus,
30
+ readPreviousRefreshStatus,
31
+ readRefreshStatus,
32
+ } from "./refresh-status.ts";
28
33
  import { formatSemanticHealthState, readSemanticHealthState } from "./semantic-state.ts";
29
34
 
30
35
  /** ── renderCall ────────────────────────────────────────────────── */
@@ -125,6 +130,7 @@ function buildCompactSummary(data: Record<string, unknown> | null, theme: Theme)
125
130
  const semanticRequested = sections.some(
126
131
  (section) => section.key === "diagnostics" || section.key === "servers",
127
132
  );
133
+ const fileReadinessPending = isPendingFileReadiness(data);
128
134
  const segments = sections.map((section) =>
129
135
  formatSectionSummary(
130
136
  section,
@@ -132,6 +138,7 @@ function buildCompactSummary(data: Record<string, unknown> | null, theme: Theme)
132
138
  ? formatCompactDiagnosticEvidence(readDiagnosticEvidence(data))
133
139
  : null,
134
140
  theme,
141
+ fileReadinessPending,
135
142
  ),
136
143
  );
137
144
  if (semanticRequested) {
@@ -142,8 +149,8 @@ function buildCompactSummary(data: Record<string, unknown> | null, theme: Theme)
142
149
  `${theme.fg("dim", "lsp")} ${theme.fg(statusColor, semanticStatus)}${routeSummary ? ` ${theme.fg("warning", routeSummary)}` : ""}`,
143
150
  );
144
151
  }
145
- const previousRefresh = readPreviousRefreshStatus(data);
146
- if (previousRefresh) segments.push(theme.fg("dim", previousRefresh));
152
+ const refreshStatus = readCompactRefreshStatus(data) ?? readPreviousRefreshStatus(data);
153
+ if (refreshStatus) segments.push(theme.fg("dim", refreshStatus));
147
154
  const capabilityWarningCount = readCapabilityWarnings(data).length;
148
155
  if (capabilityWarningCount > 0) {
149
156
  const label = capabilityWarningCount === 1 ? "capability warning" : "capability warnings";
@@ -158,11 +165,14 @@ function formatSectionSummary(
158
165
  section: HealthSectionSummary,
159
166
  coverage: string | null,
160
167
  theme: Theme,
168
+ fileReadinessPending: boolean,
161
169
  ): string {
162
170
  const label = sectionLabel(section.key);
163
171
  const coverageSuffix = coverage ? ` (${coverage})` : "";
164
172
  if (section.status === "unavailable" || !section.available) {
165
- return `${theme.fg("dim", label)} ${theme.fg("warning", "unavailable")}${coverageSuffix}`;
173
+ const status =
174
+ section.key === "diagnostics" && fileReadinessPending ? "pending" : "unavailable";
175
+ return `${theme.fg("dim", label)} ${theme.fg("warning", status)}${coverageSuffix}`;
166
176
  }
167
177
  if (section.status === "partial") {
168
178
  return `${theme.fg("dim", label)} ${theme.fg("warning", "partial")}${coverageSuffix}`;
@@ -307,7 +317,10 @@ function buildDiagnosticSummary(data: Record<string, unknown> | null, theme: The
307
317
  const section = readHealthSections(data).find((entry) => entry.key === "diagnostics");
308
318
  if (!section) return new Text("", 0, 0);
309
319
  if (section.status === "unavailable" || !section.available) {
310
- return new Text(theme.fg("warning", "Diagnostics unavailable"), 0, 0);
320
+ const label = isPendingFileReadiness(data)
321
+ ? "Diagnostics pending — LSP may still be warming; retry shortly"
322
+ : "Diagnostics unavailable";
323
+ return new Text(theme.fg("warning", label), 0, 0);
311
324
  }
312
325
  const trackedFiles = diagnosticScopeKind(data) === "tracked-files";
313
326
  if (section.status === "partial") {
@@ -1,5 +1,6 @@
1
- export const toolDescription = "Apply one stored refactor plan by planId.";
1
+ export const toolDescription =
2
+ "Apply one fresh stored refactor plan by planId after safety checks; mutates its files.";
2
3
 
3
- export const promptSnippet = "apply a fresh stored refactor plan";
4
+ export const promptSnippet = "apply a stored refactor plan";
4
5
 
5
6
  export const promptGuidelines: string[] = [];
@@ -1,5 +1,5 @@
1
1
  export const toolDescription =
2
- "Preview one semantic rename or extraction and return a planId without mutating files.";
2
+ "Preview one semantic rename, extraction, import cleanup, or dead-code deletion and return a planId without mutating files.";
3
3
 
4
4
  export const promptSnippet = "preview a precise semantic refactor";
5
5