@mrclrchtr/supi-code-intelligence 6.0.1 → 6.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +5 -0
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/debug-identity.ts +11 -0
- package/node_modules/@mrclrchtr/supi-core/src/debug.ts +5 -0
- package/node_modules/@mrclrchtr/supi-lsp/README.md +26 -4
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +5 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-identity.ts +11 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug.ts +5 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-jsonrpc/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-languageserver-protocol/lib/common/protocol.colorProvider.d.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-languageserver-protocol/lib/common/protocol.d.ts +2 -2
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-languageserver-protocol/lib/common/protocol.fileOperations.d.ts +9 -9
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-languageserver-protocol/package.json +3 -3
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-languageserver-types/lib/esm/main.d.ts +2 -2
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-languageserver-types/lib/esm/main.js +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-languageserver-types/lib/umd/main.d.ts +2 -2
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-languageserver-types/lib/umd/main.js +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/vscode-languageserver-types/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/package.json +4 -5
- package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +14 -7
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-cache.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-capabilities.ts +0 -5
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-publication.ts +6 -3
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-refresh.ts +2 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-timing.ts +5 -2
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostics.ts +2 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +23 -5
- package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +40 -18
- package/node_modules/@mrclrchtr/supi-lsp/src/config/config.ts +164 -30
- package/node_modules/@mrclrchtr/supi-lsp/src/config/lsp-settings.ts +9 -87
- package/node_modules/@mrclrchtr/supi-lsp/src/config/server-config.ts +8 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-scope.ts +4 -5
- package/node_modules/@mrclrchtr/supi-lsp/src/config/types.ts +1 -29
- package/node_modules/@mrclrchtr/supi-lsp/src/debug-telemetry.ts +7 -21
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-severity.ts +6 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-summary.ts +11 -14
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/evidence.ts +0 -5
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/stale-diagnostics.ts +0 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/workspace-sentinels.ts +29 -49
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-client-state.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +4 -8
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-helpers.ts +1 -9
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-project-info.ts +12 -6
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +9 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-symbol.ts +26 -41
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +849 -117
- package/node_modules/@mrclrchtr/supi-lsp/src/provider/lsp-semantic-provider.ts +6 -4
- package/node_modules/@mrclrchtr/supi-lsp/src/provider/semantic-symbol-mapper.ts +59 -28
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-controller.ts +27 -19
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostic-surface.ts +10 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +26 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registration.ts +0 -17
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +118 -27
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-transition-debug.ts +7 -2
- package/node_modules/@mrclrchtr/supi-lsp/src/session/scanner.ts +24 -21
- package/node_modules/@mrclrchtr/supi-lsp/src/session/workspace-lsp-runtime.ts +33 -5
- package/node_modules/@mrclrchtr/supi-lsp/src/summary.ts +19 -40
- package/node_modules/@mrclrchtr/supi-lsp/src/utils.ts +13 -19
- package/node_modules/@mrclrchtr/supi-lsp/src/workspace-path-policy.ts +287 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +6 -18
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +5 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug-identity.ts +11 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug.ts +5 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -5
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/kotlin/tree-sitter-kotlin.wasm.json +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/sql/tree-sitter-sql.wasm.json +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/api.ts +1 -11
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/coordinates.ts +19 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/language.ts +5 -23
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/operation-support.ts +3 -21
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/provider/tree-sitter-provider.ts +5 -9
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime-controller.ts +10 -20
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/session.ts +7 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-client.ts +9 -5
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-protocol.ts +134 -9
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/call-name.ts +96 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/call-sites.ts +11 -12
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callees.ts +39 -56
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/exports.ts +9 -7
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/js-binding-pattern.ts +26 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/node-at.ts +2 -21
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline-html-sql.ts +144 -12
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline.ts +50 -37
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/scope.ts +138 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +0 -7
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/parsed-file-store.ts +1 -14
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/runtime.ts +0 -10
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/service.ts +6 -8
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/worker-main.ts +34 -10
- package/package.json +5 -5
- package/src/analysis/capability/capability-warnings.ts +4 -22
- package/src/analysis/health/recovery.ts +4 -0
- package/src/analysis/health/server-status.ts +64 -0
- package/src/analysis/health/signals.ts +1 -0
- package/src/analysis/provider.ts +2 -1
- package/src/analysis/search/ast-scan-timing.ts +4 -2
- package/src/analysis/search/ast-scan.ts +1 -1
- package/src/analysis/target/symbol.ts +28 -16
- package/src/analysis/target/types.ts +1 -0
- package/src/api.ts +1 -0
- package/src/extension.ts +4 -2
- package/src/session/find-workflow.ts +5 -12
- package/src/session/health-refresh.ts +30 -4
- package/src/session/health-types.ts +27 -2
- package/src/session/health-workflow.ts +5 -1
- package/src/session/input/health-refactor.ts +16 -14
- package/src/session/refactor-types.ts +14 -1
- package/src/session/refactor-workflow.ts +21 -16
- package/src/session/semantic-demand.ts +36 -0
- package/src/session/target-workflow.ts +9 -12
- package/src/substrate/lsp/lifecycle.ts +0 -3
- package/src/substrate/lsp/maintenance.ts +4 -5
- package/src/substrate/lsp/settings.ts +6 -9
- package/src/substrate/lsp/state.ts +0 -2
- package/src/substrate/workspace-provider-host.ts +2 -2
- package/src/tool/code_health/file-scope-markdown.ts +43 -0
- package/src/tool/code_health/guidance.ts +2 -2
- package/src/tool/code_health/markdown.ts +81 -69
- package/src/tool/code_health/refresh-outcome.ts +46 -0
- package/src/tool/code_health/refresh-status.ts +166 -23
- package/src/tool/code_health/result.ts +15 -5
- package/src/tool/code_health/spec.ts +2 -1
- package/src/tool/code_health/tui.ts +42 -8
- package/src/tool/code_refactor_apply/guidance.ts +3 -2
- package/src/tool/code_refactor_plan/guidance.ts +1 -1
- package/src/tool/register.ts +5 -2
- package/src/tool/result/errors.ts +1 -0
- package/src/tool/result/types.ts +6 -0
- package/src/tool/schemas.ts +21 -11
- package/src/ui/footer.ts +7 -5
- package/src/ui/status-command.ts +31 -0
- package/src/ui/status-overlay.ts +69 -23
- package/node_modules/@mrclrchtr/supi-lsp/src/config/server-actions.ts +0 -59
- package/node_modules/@mrclrchtr/supi-lsp/src/pattern-matcher.ts +0 -24
- package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/generate-kotlin-wasm.mjs +0 -132
- package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/generate-sql-wasm.mjs +0 -158
- package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/vendor-wasm.mjs +0 -167
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/index.ts +0 -42
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime-registration.ts +0 -37
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/service-registry.ts +0 -30
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/structure.ts +0 -8
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
// LSP settings registration for the code-intelligence umbrella extension.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// `lsp.servers.<language>.enabled: false` is the only supported opt-out.
|
|
3
|
+
// Per-language disable via `lsp.servers.<language>.enabled: false` is the
|
|
4
|
+
// supported opt-out.
|
|
6
5
|
//
|
|
7
6
|
// Registered fields:
|
|
8
7
|
// - exclude: stringList
|
|
@@ -21,11 +20,9 @@ import {
|
|
|
21
20
|
} from "@mrclrchtr/supi-core/settings";
|
|
22
21
|
import { type LspSettings, loadConfig } from "@mrclrchtr/supi-lsp/api";
|
|
23
22
|
|
|
24
|
-
const LSP_DEFAULTS
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
exclude: [],
|
|
28
|
-
};
|
|
23
|
+
const LSP_DEFAULTS = {
|
|
24
|
+
exclude: [] as string[],
|
|
25
|
+
} satisfies LspSettings;
|
|
29
26
|
|
|
30
27
|
/** Discover configured servers from the defaults + effective LSP config. */
|
|
31
28
|
function getConfiguredServers(cwd: string): string[] {
|
|
@@ -153,7 +150,7 @@ export function registerLspSettings(pi: ExtensionAPI): void {
|
|
|
153
150
|
kind: "stringList" as const,
|
|
154
151
|
key: "exclude",
|
|
155
152
|
label: "Exclude Patterns",
|
|
156
|
-
description: "Gitignore patterns
|
|
153
|
+
description: "Gitignore patterns for automatic LSP workspace work (comma-separated)",
|
|
157
154
|
},
|
|
158
155
|
{
|
|
159
156
|
kind: "custom" as const,
|
|
@@ -22,7 +22,6 @@ export interface LspAdapterState {
|
|
|
22
22
|
controller: LspRuntimeController | null;
|
|
23
23
|
providerLease: WorkspaceProviderHostLease | null;
|
|
24
24
|
inspector: LspInspectorState;
|
|
25
|
-
lspActive: boolean;
|
|
26
25
|
/** Snapshot of workspace sentinel files (package.json, tsconfig, lockfiles) for change detection. */
|
|
27
26
|
sentinelSnapshot: Map<string, number>;
|
|
28
27
|
/** Fired for runtime lifecycle changes that require a footer render. */
|
|
@@ -34,7 +33,6 @@ export function createLspAdapterState(): LspAdapterState {
|
|
|
34
33
|
controller: null,
|
|
35
34
|
providerLease: null,
|
|
36
35
|
inspector: { handle: null, close: null },
|
|
37
|
-
lspActive: false,
|
|
38
36
|
sentinelSnapshot: new Map(),
|
|
39
37
|
stateChanges: new EventTarget(),
|
|
40
38
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { realpathSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { getDefaultWorkspaceRuntime } from "@mrclrchtr/supi-code-runtime/api";
|
|
4
|
-
import { LspRuntimeController
|
|
4
|
+
import { LspRuntimeController } from "@mrclrchtr/supi-lsp/api";
|
|
5
5
|
import { TreeSitterRuntimeController } from "@mrclrchtr/supi-tree-sitter/api";
|
|
6
6
|
|
|
7
7
|
const HOSTS = Symbol.for("supi-code-intelligence/workspace-provider-hosts");
|
|
@@ -77,7 +77,7 @@ class WorkspaceProviderHost {
|
|
|
77
77
|
lspController: projectTrusted ? this.#lsp : null,
|
|
78
78
|
sentinelSnapshot:
|
|
79
79
|
projectTrusted && this.#lsp?.kind === "ready"
|
|
80
|
-
?
|
|
80
|
+
? (this.#lsp.workspaceRuntime?.scanWorkspaceSentinels() ?? new Map())
|
|
81
81
|
: new Map(),
|
|
82
82
|
release: async () => {
|
|
83
83
|
if (released) return;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { HealthDiagnosticObservation } from "../../session/health-types.ts";
|
|
2
|
+
|
|
3
|
+
/** Render the tsconfig coverage result for a single-file health request. */
|
|
4
|
+
export function renderFileScopeStatus(
|
|
5
|
+
lines: string[],
|
|
6
|
+
observation: Extract<
|
|
7
|
+
HealthDiagnosticObservation,
|
|
8
|
+
{ kind: "completed" | "partial" | "unavailable" }
|
|
9
|
+
>,
|
|
10
|
+
cwd: string,
|
|
11
|
+
): void {
|
|
12
|
+
if (observation.scope.kind !== "file" || !observation.scopeStatus) return;
|
|
13
|
+
|
|
14
|
+
const decision = observation.scopeStatus;
|
|
15
|
+
switch (decision.status) {
|
|
16
|
+
case "included":
|
|
17
|
+
lines.push(
|
|
18
|
+
`**Tsconfig**: covered by ${formatConfigPath(decision.configPath, cwd)} — part of workspace diagnostics.`,
|
|
19
|
+
);
|
|
20
|
+
return;
|
|
21
|
+
case "excluded":
|
|
22
|
+
lines.push(
|
|
23
|
+
`**Tsconfig**: NOT covered by ${formatConfigPath(decision.configPath, cwd)} — not part of workspace diagnostics.`,
|
|
24
|
+
);
|
|
25
|
+
return;
|
|
26
|
+
case "no-config":
|
|
27
|
+
lines.push("**Tsconfig**: no tsconfig.json or jsconfig.json found — nothing filtered.");
|
|
28
|
+
return;
|
|
29
|
+
case "out-of-tree":
|
|
30
|
+
lines.push("**Tsconfig**: outside the project root — not part of workspace diagnostics.");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Format a config path relative to the current workspace. */
|
|
35
|
+
function formatConfigPath(configPath: string | null, cwd: string): string {
|
|
36
|
+
if (!configPath) return "no config";
|
|
37
|
+
return `\`${makeRelative(cwd, configPath)}\``;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Format a workspace path without exposing an absolute root. */
|
|
41
|
+
export function makeRelative(cwd: string, file: string): string {
|
|
42
|
+
return file.startsWith(cwd) ? file.slice(cwd.length + 1) : file;
|
|
43
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export const toolDescription =
|
|
2
|
-
"Report live diagnostics as observations, language-server status, and final semantic health state. Tracked-file snapshots do not prove workspace completeness; server inventory
|
|
2
|
+
"Report live diagnostics as observations, language-server route status, and final semantic health state. Tracked-file snapshots do not prove workspace completeness; server inventory and route issue counts are workspace-wide. Server-only calls are passive.";
|
|
3
3
|
|
|
4
4
|
export const promptSnippet = "live workspace health observations";
|
|
5
5
|
|
|
6
6
|
export const promptGuidelines = [
|
|
7
|
-
"Use code_health with refresh:true before relying on potentially stale diagnostics.",
|
|
7
|
+
"Use code_health with refresh:true before relying on potentially stale diagnostics; explicit refresh can recover crashed routes with tracked files in scope.",
|
|
8
8
|
];
|
|
@@ -6,9 +6,20 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { CapabilityWarningReport } from "../../analysis/capability/capability-warnings.ts";
|
|
9
|
+
import {
|
|
10
|
+
formatProjectServerRoot,
|
|
11
|
+
formatProjectServerRouteSummary,
|
|
12
|
+
formatProjectServerStatusReason,
|
|
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";
|
|
9
21
|
import type {
|
|
10
22
|
HealthData,
|
|
11
|
-
HealthDiagnosticObservation,
|
|
12
23
|
HealthDiagnosticScope,
|
|
13
24
|
HealthRefreshAttempt,
|
|
14
25
|
HealthResultAssembly,
|
|
@@ -28,7 +39,9 @@ export function renderHealthResult(result: HealthResultAssembly, cwd: string): s
|
|
|
28
39
|
renderRefreshStatus(lines, data, hasDiagnostics, cwd);
|
|
29
40
|
if (hasDiagnostics) renderDiagnosticsSection(lines, data, cwd);
|
|
30
41
|
if (semanticRequested) renderCapabilityWarningsSection(lines, result.details.capabilityWarnings);
|
|
31
|
-
if (hasSection("servers"))
|
|
42
|
+
if (hasSection("servers")) {
|
|
43
|
+
renderServersSection(lines, data, sectionStatus(result, "servers"), cwd);
|
|
44
|
+
}
|
|
32
45
|
return lines.join("\n");
|
|
33
46
|
}
|
|
34
47
|
|
|
@@ -41,19 +54,25 @@ function renderRefreshStatus(
|
|
|
41
54
|
if (!hasDiagnostics) return;
|
|
42
55
|
|
|
43
56
|
switch (data.refresh.kind) {
|
|
44
|
-
case "completed":
|
|
57
|
+
case "completed": {
|
|
58
|
+
const fileReadinessPending = isFileReadinessPending(data.refresh, data.semanticState);
|
|
45
59
|
lines.push(
|
|
46
|
-
`**${refreshAttemptLabel(data.refresh)}**: ${asSentence(completedRefreshText(data.refresh))}`,
|
|
60
|
+
`**${refreshAttemptLabel(data.refresh)}**: ${asSentence(completedRefreshText(data.refresh, fileReadinessPending))}`,
|
|
47
61
|
);
|
|
48
62
|
lines.push(`**Stale assessment**: ${asSentence(staleAssessmentText(data.refresh))}`);
|
|
49
63
|
lines.push("");
|
|
50
64
|
return;
|
|
65
|
+
}
|
|
51
66
|
case "failed": {
|
|
52
67
|
const evidence = data.refresh.diagnosticEvidence
|
|
53
68
|
? `; ${formatDiagnosticEvidence(data.refresh.diagnosticEvidence)}`
|
|
54
69
|
: "";
|
|
70
|
+
const processCrashRecovery = data.refresh.processCrashRecovery
|
|
71
|
+
? formatProcessCrashRecovery(data.refresh.processCrashRecovery)
|
|
72
|
+
: null;
|
|
73
|
+
const staleRestart = formatStaleDiagnosticRestarts(data.refresh);
|
|
55
74
|
lines.push(
|
|
56
|
-
`**${refreshAttemptLabel(data.refresh)}**: failed — ${data.refresh.reason}${evidence}`,
|
|
75
|
+
`**${refreshAttemptLabel(data.refresh)}**: failed — ${data.refresh.reason}${staleRestart ? `; ${staleRestart}` : ""}${processCrashRecovery ? `; ${processCrashRecovery}` : ""}${evidence}`,
|
|
57
76
|
);
|
|
58
77
|
lines.push("");
|
|
59
78
|
return;
|
|
@@ -77,14 +96,27 @@ function renderRefreshStatus(
|
|
|
77
96
|
|
|
78
97
|
function completedRefreshText(
|
|
79
98
|
attempt: Extract<HealthRefreshAttempt, { kind: "completed" }>,
|
|
99
|
+
fileReadinessPending = attempt.fileReadiness === "pending",
|
|
80
100
|
): string {
|
|
81
|
-
const
|
|
82
|
-
|
|
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
|
|
83
110
|
? "completed no-op — no active clients were targeted"
|
|
84
|
-
: `completed — ${attempt.attemptedActiveClients} active client${plural(attempt.attemptedActiveClients)} targeted
|
|
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;
|
|
85
117
|
return attempt.operationScope === "workspace-runtime"
|
|
86
|
-
? `${
|
|
87
|
-
:
|
|
118
|
+
? `${withOutcome}; ${formatDiagnosticEvidence(attempt.diagnosticEvidence)}`
|
|
119
|
+
: withOutcome;
|
|
88
120
|
}
|
|
89
121
|
|
|
90
122
|
function refreshAttemptLabel(attempt: Pick<HealthRefreshAttempt, "operationScope">): string {
|
|
@@ -110,7 +142,7 @@ function renderLastAttempt(lines: string[], attempt: HealthRefreshAttempt, cwd:
|
|
|
110
142
|
const outcome = refreshAttemptOutcome(attempt);
|
|
111
143
|
const label = refreshAttemptLabel(attempt);
|
|
112
144
|
lines.push(
|
|
113
|
-
`**Previous ${label.toLowerCase()}**: ${asSentence(outcome)} Started ${
|
|
145
|
+
`**Previous ${label.toLowerCase()}**: ${asSentence(outcome)} Started ${formatRefreshElapsed(Date.now() - attempt.attemptedAt)}; requested ${formatDiagnosticScope(attempt.requestedDiagnosticScope, cwd)}; operation scope: ${refreshOperationScopeText(attempt)}.`,
|
|
114
146
|
);
|
|
115
147
|
}
|
|
116
148
|
|
|
@@ -118,24 +150,35 @@ function renderRetainedAttempt(lines: string[], attempt: HealthRefreshAttempt, c
|
|
|
118
150
|
const label =
|
|
119
151
|
attempt.operationScope === "file-runtime" ? "File LSP maintenance" : "Diagnostic refresh";
|
|
120
152
|
lines.push(
|
|
121
|
-
`**${label}**: not requested for this call. Previous attempt ${asSentence(refreshAttemptOutcome(attempt))} Started ${
|
|
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)}.`,
|
|
122
154
|
);
|
|
123
155
|
}
|
|
124
156
|
|
|
125
157
|
function refreshAttemptOutcome(attempt: HealthRefreshAttempt): string {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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}`;
|
|
129
167
|
}
|
|
130
168
|
|
|
131
169
|
function refreshOperationScopeText(attempt: HealthRefreshAttempt): string {
|
|
132
170
|
return attempt.operationScope === "file-runtime" ? "file runtime" : "workspace runtime";
|
|
133
171
|
}
|
|
134
172
|
|
|
135
|
-
function
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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);
|
|
139
182
|
}
|
|
140
183
|
|
|
141
184
|
function renderCapabilityWarningsSection(
|
|
@@ -158,7 +201,8 @@ function renderCapabilityWarningsSection(
|
|
|
158
201
|
function renderStatusLine(lines: string[], data: HealthData, semanticRequested: boolean): void {
|
|
159
202
|
if (!semanticRequested) return;
|
|
160
203
|
|
|
161
|
-
|
|
204
|
+
const routeSummary = formatProjectServerRouteSummary(data.servers);
|
|
205
|
+
lines.push(`**LSP**: ${displaySemanticStatus(data)}${routeSummary ? ` — ${routeSummary}` : ""}`);
|
|
162
206
|
if (data.structuralStatus) lines.push(`**Structural**: ${data.structuralStatus}`);
|
|
163
207
|
lines.push("");
|
|
164
208
|
}
|
|
@@ -185,49 +229,6 @@ function renderDiagnosticsSection(lines: string[], data: HealthData, cwd: string
|
|
|
185
229
|
lines.push("");
|
|
186
230
|
}
|
|
187
231
|
|
|
188
|
-
/**
|
|
189
|
-
* Render the tsconfig coverage verdict for a single-file health request.
|
|
190
|
-
*
|
|
191
|
-
* Written in plain vocabulary (no basis terms) so a reader without tsconfig
|
|
192
|
-
* background can act on it: the sentence names the deciding config and states
|
|
193
|
-
* whether the file's errors are part of workspace diagnostics. The structured
|
|
194
|
-
* decision basis stays in the diagnostics.scope debug event.
|
|
195
|
-
*/
|
|
196
|
-
function renderFileScopeStatus(
|
|
197
|
-
lines: string[],
|
|
198
|
-
observation: Extract<
|
|
199
|
-
HealthDiagnosticObservation,
|
|
200
|
-
{ kind: "completed" | "partial" | "unavailable" }
|
|
201
|
-
>,
|
|
202
|
-
cwd: string,
|
|
203
|
-
): void {
|
|
204
|
-
if (observation.scope.kind !== "file" || !observation.scopeStatus) return;
|
|
205
|
-
|
|
206
|
-
const decision = observation.scopeStatus;
|
|
207
|
-
switch (decision.status) {
|
|
208
|
-
case "included":
|
|
209
|
-
lines.push(
|
|
210
|
-
`**Tsconfig**: covered by ${formatConfigPath(decision.configPath, cwd)} — part of workspace diagnostics.`,
|
|
211
|
-
);
|
|
212
|
-
return;
|
|
213
|
-
case "excluded":
|
|
214
|
-
lines.push(
|
|
215
|
-
`**Tsconfig**: NOT covered by ${formatConfigPath(decision.configPath, cwd)} — not part of workspace diagnostics.`,
|
|
216
|
-
);
|
|
217
|
-
return;
|
|
218
|
-
case "no-config":
|
|
219
|
-
lines.push("**Tsconfig**: no tsconfig.json or jsconfig.json found — nothing filtered.");
|
|
220
|
-
return;
|
|
221
|
-
case "out-of-tree":
|
|
222
|
-
lines.push("**Tsconfig**: outside the project root — not part of workspace diagnostics.");
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function formatConfigPath(configPath: string | null, cwd: string): string {
|
|
227
|
-
if (!configPath) return "no config";
|
|
228
|
-
return `\`${makeRelative(cwd, configPath)}\``;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
232
|
function formatDiagnosticEvidence(evidence: {
|
|
232
233
|
requested: number;
|
|
233
234
|
confirmed: number;
|
|
@@ -242,7 +243,14 @@ function renderDiagnosticObservation(lines: string[], data: HealthData, cwd: str
|
|
|
242
243
|
const observation = data.diagnostics;
|
|
243
244
|
if (observation.kind === "not-requested") return;
|
|
244
245
|
if (observation.kind === "unavailable") {
|
|
245
|
-
|
|
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
|
+
}
|
|
246
254
|
return;
|
|
247
255
|
}
|
|
248
256
|
if (observation.kind === "partial") {
|
|
@@ -261,6 +269,10 @@ function renderDiagnosticObservation(lines: string[], data: HealthData, cwd: str
|
|
|
261
269
|
renderDiagnosticEntries(lines, data, cwd);
|
|
262
270
|
}
|
|
263
271
|
|
|
272
|
+
function currentRefreshAttempt(data: HealthData): HealthRefreshAttempt | null {
|
|
273
|
+
return data.refresh.kind === "completed" || data.refresh.kind === "failed" ? data.refresh : null;
|
|
274
|
+
}
|
|
275
|
+
|
|
264
276
|
function asSentence(text: string): string {
|
|
265
277
|
return /[.!?]$/.test(text) ? text : `${text}.`;
|
|
266
278
|
}
|
|
@@ -316,6 +328,7 @@ function renderServersSection(
|
|
|
316
328
|
lines: string[],
|
|
317
329
|
data: HealthData,
|
|
318
330
|
status: "complete" | "partial" | "unavailable" | undefined,
|
|
331
|
+
cwd: string,
|
|
319
332
|
): void {
|
|
320
333
|
lines.push("### Servers");
|
|
321
334
|
lines.push("");
|
|
@@ -329,8 +342,11 @@ function renderServersSection(
|
|
|
329
342
|
} else {
|
|
330
343
|
for (const server of data.servers) {
|
|
331
344
|
const statusIcon = server.status === "running" ? "✓" : "✗";
|
|
345
|
+
const reason = server.statusReason
|
|
346
|
+
? ` — ${formatProjectServerStatusReason(server.statusReason)}`
|
|
347
|
+
: "";
|
|
332
348
|
lines.push(
|
|
333
|
-
`- ${statusIcon} **${server.name}** (${server.fileTypes.join(", ")}) — ${server.status}`,
|
|
349
|
+
`- ${statusIcon} **${server.name}** @ \`${formatProjectServerRoot(cwd, server.root)}\` (${server.fileTypes.join(", ")}) — ${server.status}${reason}`,
|
|
334
350
|
);
|
|
335
351
|
}
|
|
336
352
|
}
|
|
@@ -359,7 +375,3 @@ function formatDiagnosticScope(scope: HealthDiagnosticScope, cwd: string): strin
|
|
|
359
375
|
function plural(count: number): string {
|
|
360
376
|
return count === 1 ? "" : "s";
|
|
361
377
|
}
|
|
362
|
-
|
|
363
|
-
function makeRelative(cwd: string, file: string): string {
|
|
364
|
-
return file.startsWith(cwd) ? file.slice(cwd.length + 1) : file;
|
|
365
|
-
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ProcessCrashRecoverySummary } from "@mrclrchtr/supi-lsp/api";
|
|
2
|
+
import type { SemanticHealthState } from "../../session/health-types.ts";
|
|
3
|
+
|
|
4
|
+
/** Report whether a file health result is waiting for semantic readiness. */
|
|
5
|
+
export function isFileReadinessPending(
|
|
6
|
+
attempt: { operationScope?: string; fileReadiness?: string } | null | undefined,
|
|
7
|
+
semanticState: SemanticHealthState | null | undefined,
|
|
8
|
+
): boolean {
|
|
9
|
+
if (attempt?.operationScope !== undefined) {
|
|
10
|
+
return (
|
|
11
|
+
attempt.operationScope === "file-runtime" &&
|
|
12
|
+
(attempt.fileReadiness === "pending" ||
|
|
13
|
+
(attempt.fileReadiness === undefined && semanticState?.kind === "pending"))
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
return semanticState?.kind === "pending";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Format the outcome of stale-diagnostic client restarts. */
|
|
20
|
+
export function formatStaleDiagnosticRestarts(restartedClients: number): string {
|
|
21
|
+
return `stale diagnostic restarts: ${restartedClients} client${plural(restartedClients)} restarted`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Format a non-empty process-crash route recovery outcome. */
|
|
25
|
+
export function formatProcessCrashRecovery(
|
|
26
|
+
summary: ProcessCrashRecoverySummary | null | undefined,
|
|
27
|
+
): string | null {
|
|
28
|
+
if (
|
|
29
|
+
!summary ||
|
|
30
|
+
(summary.attemptedRoutes === 0 && summary.recoveredRoutes === 0 && summary.failedRoutes === 0)
|
|
31
|
+
) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return `process-crash recovery: ${summary.recoveredRoutes} route${plural(summary.recoveredRoutes)} recovered (${summary.attemptedRoutes} attempted, ${summary.failedRoutes} failed)`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Format the age of a retained refresh attempt for a status line. */
|
|
38
|
+
export function formatRefreshElapsed(milliseconds: number): string {
|
|
39
|
+
const seconds = Math.max(0, Math.round(milliseconds / 1_000));
|
|
40
|
+
if (seconds < 60) return `${seconds}s ago`;
|
|
41
|
+
return `${Math.round(seconds / 60)}m ago`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function plural(count: number): string {
|
|
45
|
+
return count === 1 ? "" : "s";
|
|
46
|
+
}
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import type { ProcessCrashRecoverySummary } from "@mrclrchtr/supi-lsp/api";
|
|
2
|
+
import {
|
|
3
|
+
formatProcessCrashRecovery,
|
|
4
|
+
formatStaleDiagnosticRestarts,
|
|
5
|
+
isFileReadinessPending,
|
|
6
|
+
} from "./refresh-outcome.ts";
|
|
7
|
+
import { readSemanticHealthState } from "./semantic-state.ts";
|
|
8
|
+
|
|
1
9
|
/** Render current and retained health refresh status for the TUI. */
|
|
2
10
|
|
|
3
11
|
export function readRefreshStatus(data: Record<string, unknown> | null): string | null {
|
|
@@ -5,25 +13,10 @@ export function readRefreshStatus(data: Record<string, unknown> | null): string
|
|
|
5
13
|
if (!refresh) return null;
|
|
6
14
|
|
|
7
15
|
switch (refresh.kind) {
|
|
8
|
-
case "completed":
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
}
|
|
16
|
+
case "completed":
|
|
17
|
+
return formatCompletedRefreshStatus(refresh, isPendingFileReadiness(data));
|
|
18
|
+
case "failed":
|
|
19
|
+
return formatFailedRefreshStatus(refresh);
|
|
27
20
|
case "not-attempted":
|
|
28
21
|
return `refresh attempt not started${formatLastRefreshSuffix(refresh)}`;
|
|
29
22
|
case "not-requested":
|
|
@@ -33,6 +26,89 @@ export function readRefreshStatus(data: Record<string, unknown> | null): string
|
|
|
33
26
|
}
|
|
34
27
|
}
|
|
35
28
|
|
|
29
|
+
function formatCompletedRefreshStatus(
|
|
30
|
+
refresh: Record<string, unknown>,
|
|
31
|
+
fileReadinessPending: boolean,
|
|
32
|
+
): string {
|
|
33
|
+
const attempted = readNumber(refresh.attemptedActiveClients);
|
|
34
|
+
const restarted = readNumber(refresh.restartedClients);
|
|
35
|
+
const processCrashRecovery = formatProcessCrashRecovery(
|
|
36
|
+
readProcessCrashRecovery(refresh.processCrashRecovery),
|
|
37
|
+
);
|
|
38
|
+
const noOp =
|
|
39
|
+
!fileReadinessPending && attempted === 0 && restarted === 0 && processCrashRecovery === null;
|
|
40
|
+
const label = refreshAttemptLabel(refresh);
|
|
41
|
+
const base = fileReadinessPending
|
|
42
|
+
? `${label} waiting — LSP may still be warming; retry shortly`
|
|
43
|
+
: noOp
|
|
44
|
+
? `${label} completed no-op`
|
|
45
|
+
: `${label} completed: ${attempted} clients targeted`;
|
|
46
|
+
const outcomes = formatRefreshOutcomes(refresh, restarted, processCrashRecovery, noOp);
|
|
47
|
+
const withOutcome = outcomes.length > 0 ? `${base}; ${outcomes.join("; ")}` : base;
|
|
48
|
+
const evidence = formatDiagnosticEvidence(readRecord(refresh.diagnosticEvidence));
|
|
49
|
+
const withEvidence = evidence ? `${withOutcome}; ${evidence}` : withOutcome;
|
|
50
|
+
const stale = readRecord(refresh.staleAssessment);
|
|
51
|
+
return stale?.suspected === true
|
|
52
|
+
? `${withEvidence}; stale pattern suspected in ${readNumber(stale.matchedFileCount)} files`
|
|
53
|
+
: withEvidence;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function formatFailedRefreshStatus(refresh: Record<string, unknown>): string {
|
|
57
|
+
const processCrashRecovery = formatProcessCrashRecovery(
|
|
58
|
+
readProcessCrashRecovery(refresh.processCrashRecovery),
|
|
59
|
+
);
|
|
60
|
+
const staleRestart = formatStaleDiagnosticRestartsForRecord(refresh);
|
|
61
|
+
const evidence = formatDiagnosticEvidence(readRecord(refresh.diagnosticEvidence));
|
|
62
|
+
const reason = typeof refresh.reason === "string" ? `: ${refresh.reason}` : "";
|
|
63
|
+
return `${refreshAttemptLabel(refresh)} failed${reason}${staleRestart ? `; ${staleRestart}` : ""}${processCrashRecovery ? `; ${processCrashRecovery}` : ""}${evidence ? `; ${evidence}` : ""}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function formatRefreshOutcomes(
|
|
67
|
+
refresh: Record<string, unknown>,
|
|
68
|
+
restarted: number,
|
|
69
|
+
processCrashRecovery: string | null,
|
|
70
|
+
noOp: boolean,
|
|
71
|
+
): string[] {
|
|
72
|
+
const outcomes: string[] = [];
|
|
73
|
+
if (refresh.operationScope === "workspace-runtime" && !noOp) {
|
|
74
|
+
outcomes.push(formatStaleDiagnosticRestarts(restarted));
|
|
75
|
+
}
|
|
76
|
+
if (processCrashRecovery) outcomes.push(processCrashRecovery);
|
|
77
|
+
return outcomes;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Render only the material current recovery outcome for compact TUI chrome. */
|
|
81
|
+
export function readCompactRefreshStatus(data: Record<string, unknown> | null): string | null {
|
|
82
|
+
const refresh = readRecord(data?.refresh);
|
|
83
|
+
if (!refresh || (refresh.kind !== "completed" && refresh.kind !== "failed")) return null;
|
|
84
|
+
|
|
85
|
+
const processCrashRecovery = formatProcessCrashRecovery(
|
|
86
|
+
readProcessCrashRecovery(refresh.processCrashRecovery),
|
|
87
|
+
);
|
|
88
|
+
if (refresh.kind === "failed") {
|
|
89
|
+
const staleRestart = formatStaleDiagnosticRestartsForRecord(refresh);
|
|
90
|
+
return (
|
|
91
|
+
[staleRestart, processCrashRecovery]
|
|
92
|
+
.filter((value): value is string => value !== null)
|
|
93
|
+
.join("; ") || null
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
const fileReadinessPending = isPendingFileReadiness(data);
|
|
97
|
+
if (
|
|
98
|
+
!processCrashRecovery &&
|
|
99
|
+
readNumber(refresh.restartedClients) === 0 &&
|
|
100
|
+
!fileReadinessPending
|
|
101
|
+
) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const staleRestart = formatStaleDiagnosticRestartsForRecord(refresh);
|
|
106
|
+
const readiness = fileReadinessPending ? "LSP may still be warming; retry shortly" : null;
|
|
107
|
+
return [staleRestart, processCrashRecovery, readiness]
|
|
108
|
+
.filter((value): value is string => value !== null)
|
|
109
|
+
.join("; ");
|
|
110
|
+
}
|
|
111
|
+
|
|
36
112
|
export function readPreviousRefreshStatus(data: Record<string, unknown> | null): string | null {
|
|
37
113
|
const refresh = readRecord(data?.refresh);
|
|
38
114
|
if (!refresh || (refresh.kind !== "not-requested" && refresh.kind !== "not-attempted")) {
|
|
@@ -41,11 +117,23 @@ export function readPreviousRefreshStatus(data: Record<string, unknown> | null):
|
|
|
41
117
|
const attempt = readRecord(refresh.lastAttempt);
|
|
42
118
|
if (!attempt) return null;
|
|
43
119
|
const evidence = formatCompactDiagnosticEvidence(readRecord(attempt.diagnosticEvidence));
|
|
120
|
+
const processCrashRecovery = formatProcessCrashRecovery(
|
|
121
|
+
readProcessCrashRecovery(attempt.processCrashRecovery),
|
|
122
|
+
);
|
|
123
|
+
const staleRestart = formatStaleDiagnosticRestartsForRecord(attempt);
|
|
124
|
+
const fileReadinessPending =
|
|
125
|
+
attempt.kind === "completed" &&
|
|
126
|
+
isFileReadinessPending(attempt, readSemanticHealthState(data?.semanticState));
|
|
127
|
+
const readiness = fileReadinessPending ? "LSP may still be warming; retry shortly" : null;
|
|
128
|
+
const outcome = [readiness, staleRestart, processCrashRecovery, evidence]
|
|
129
|
+
.filter((value): value is string => value !== null)
|
|
130
|
+
.join("; ");
|
|
44
131
|
if (attempt.kind === "failed") {
|
|
45
|
-
return `last ${refreshAttemptLabel(attempt)} failed${
|
|
132
|
+
return `last ${refreshAttemptLabel(attempt)} failed${outcome ? ` (${outcome})` : ""}`;
|
|
46
133
|
}
|
|
47
134
|
if (attempt.kind === "completed") {
|
|
48
|
-
|
|
135
|
+
const status = fileReadinessPending ? "waiting" : "completed";
|
|
136
|
+
return `last ${refreshAttemptLabel(attempt)} ${status}${outcome ? ` (${outcome})` : ""}`;
|
|
49
137
|
}
|
|
50
138
|
return null;
|
|
51
139
|
}
|
|
@@ -54,16 +142,45 @@ function refreshAttemptLabel(attempt: Record<string, unknown>): string {
|
|
|
54
142
|
return attempt.operationScope === "file-runtime" ? "file maintenance attempt" : "refresh attempt";
|
|
55
143
|
}
|
|
56
144
|
|
|
145
|
+
function formatStaleDiagnosticRestartsForRecord(attempt: Record<string, unknown>): string | null {
|
|
146
|
+
if (
|
|
147
|
+
attempt.operationScope !== "workspace-runtime" ||
|
|
148
|
+
typeof attempt.restartedClients !== "number"
|
|
149
|
+
) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
const processCrashRecovery = formatProcessCrashRecovery(
|
|
153
|
+
readProcessCrashRecovery(attempt.processCrashRecovery),
|
|
154
|
+
);
|
|
155
|
+
const noOp =
|
|
156
|
+
attempt.kind === "completed" &&
|
|
157
|
+
readNumber(attempt.attemptedActiveClients) === 0 &&
|
|
158
|
+
attempt.restartedClients === 0 &&
|
|
159
|
+
processCrashRecovery === null;
|
|
160
|
+
return noOp ? null : formatStaleDiagnosticRestarts(attempt.restartedClients);
|
|
161
|
+
}
|
|
162
|
+
|
|
57
163
|
function formatLastRefreshSuffix(refresh: Record<string, unknown>): string {
|
|
58
164
|
const attempt = readRecord(refresh.lastAttempt);
|
|
59
165
|
if (!attempt) return "";
|
|
60
166
|
const evidence = formatDiagnosticEvidence(readRecord(attempt.diagnosticEvidence));
|
|
167
|
+
const staleRestart = formatStaleDiagnosticRestartsForRecord(attempt);
|
|
168
|
+
const processCrashRecovery = formatProcessCrashRecovery(
|
|
169
|
+
readProcessCrashRecovery(attempt.processCrashRecovery),
|
|
170
|
+
);
|
|
171
|
+
const fileReadinessPending =
|
|
172
|
+
attempt.kind === "completed" && isFileReadinessPending(attempt, null);
|
|
173
|
+
const readiness = fileReadinessPending ? "LSP may still be warming; retry shortly" : null;
|
|
174
|
+
const outcome = [readiness, staleRestart, processCrashRecovery, evidence]
|
|
175
|
+
.filter((value): value is string => value !== null)
|
|
176
|
+
.join("; ");
|
|
61
177
|
if (attempt.kind === "failed") {
|
|
62
178
|
const reason = typeof attempt.reason === "string" ? `: ${attempt.reason}` : "";
|
|
63
|
-
return `; last ${refreshAttemptLabel(attempt)} failed${reason}${
|
|
179
|
+
return `; last ${refreshAttemptLabel(attempt)} failed${reason}${outcome ? `; ${outcome}` : ""}`;
|
|
64
180
|
}
|
|
65
181
|
if (attempt.kind === "completed") {
|
|
66
|
-
|
|
182
|
+
const status = fileReadinessPending ? "waiting" : "completed";
|
|
183
|
+
return `; last ${refreshAttemptLabel(attempt)} ${status}${outcome ? `; ${outcome}` : ""}`;
|
|
67
184
|
}
|
|
68
185
|
return "";
|
|
69
186
|
}
|
|
@@ -80,6 +197,32 @@ function formatDiagnosticEvidence(evidence: Record<string, unknown> | null): str
|
|
|
80
197
|
return `${counts[0]} requested, ${counts[1]} confirmed, ${counts[2]} unconfirmed, ${counts[3]} failed, ${counts[4]} removed`;
|
|
81
198
|
}
|
|
82
199
|
|
|
200
|
+
export function isPendingFileReadiness(data: Record<string, unknown> | null): boolean {
|
|
201
|
+
const observation = readRecord(data?.diagnosticObservation);
|
|
202
|
+
const scope = readRecord(observation?.scope);
|
|
203
|
+
if (scope?.kind !== "file") return false;
|
|
204
|
+
|
|
205
|
+
const refresh = readRecord(data?.refresh);
|
|
206
|
+
const attempt = refresh && typeof refresh.operationScope === "string" ? refresh : null;
|
|
207
|
+
return isFileReadinessPending(attempt, readSemanticHealthState(data?.semanticState));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function readProcessCrashRecovery(value: unknown): ProcessCrashRecoverySummary | null {
|
|
211
|
+
const summary = readRecord(value);
|
|
212
|
+
if (!summary) return null;
|
|
213
|
+
const attemptedRoutes = summary.attemptedRoutes;
|
|
214
|
+
const recoveredRoutes = summary.recoveredRoutes;
|
|
215
|
+
const failedRoutes = summary.failedRoutes;
|
|
216
|
+
if (
|
|
217
|
+
!isEvidenceCount(attemptedRoutes) ||
|
|
218
|
+
!isEvidenceCount(recoveredRoutes) ||
|
|
219
|
+
!isEvidenceCount(failedRoutes)
|
|
220
|
+
) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
return { attemptedRoutes, recoveredRoutes, failedRoutes };
|
|
224
|
+
}
|
|
225
|
+
|
|
83
226
|
function readEvidenceCounts(evidence: Record<string, unknown> | null): number[] | null {
|
|
84
227
|
if (!evidence) return null;
|
|
85
228
|
const counts = [
|