@mrclrchtr/supi-code-intelligence 6.0.0 → 6.1.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 +3 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/README.md +2 -0
- 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/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/README.md +7 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/README.md +2 -0
- 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/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- 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 +3 -3
- package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +2 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +6 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/config/server-config.ts +8 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/config/types.ts +1 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-project-info.ts +6 -3
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +582 -26
- package/node_modules/@mrclrchtr/supi-lsp/src/provider/lsp-semantic-provider.ts +2 -3
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostic-surface.ts +10 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +36 -9
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-transition-debug.ts +2 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/session/workspace-lsp-runtime.ts +4 -2
- package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/README.md +2 -0
- 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/README.md +2 -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/web-tree-sitter/debug/web-tree-sitter.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm.map +4 -4
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.wasm.map +4 -4
- package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -3
- 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/package.json +5 -5
- 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/target/symbol.ts +28 -16
- package/src/analysis/target/types.ts +1 -0
- package/src/session/find-workflow.ts +5 -12
- package/src/session/health-refresh.ts +6 -2
- package/src/session/health-types.ts +8 -2
- package/src/session/health-workflow.ts +5 -1
- package/src/session/semantic-demand.ts +36 -0
- package/src/session/target-workflow.ts +9 -12
- package/src/tool/code_health/guidance.ts +2 -2
- package/src/tool/code_health/markdown.ts +15 -3
- package/src/tool/code_health/result.ts +14 -5
- package/src/tool/code_health/spec.ts +2 -1
- package/src/tool/code_health/tui.ts +24 -3
- package/src/tool/result/errors.ts +1 -0
- package/src/tool/result/types.ts +6 -0
- package/src/ui/footer.ts +7 -5
- package/src/ui/status-command.ts +31 -0
- package/src/ui/status-overlay.ts +69 -23
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-tree-sitter",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.1.0",
|
|
4
4
|
"description": "Structural AST analysis for SuPi code intelligence",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"jiti": "^2.7.0",
|
|
38
38
|
"web-tree-sitter": "^0.26.8",
|
|
39
|
-
"@mrclrchtr/supi-
|
|
40
|
-
"@mrclrchtr/supi-
|
|
39
|
+
"@mrclrchtr/supi-code-runtime": "6.1.0",
|
|
40
|
+
"@mrclrchtr/supi-core": "6.1.0"
|
|
41
41
|
},
|
|
42
42
|
"bundledDependencies": [
|
|
43
43
|
"@mrclrchtr/supi-code-runtime",
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"releaseAsset": "https://github.com/fwcd/tree-sitter-kotlin/releases/download/0.3.8/tree-sitter-kotlin.wasm"
|
|
7
7
|
},
|
|
8
8
|
"generatedWith": {
|
|
9
|
-
"treeSitterCli": "0.26.
|
|
9
|
+
"treeSitterCli": "0.26.13"
|
|
10
10
|
},
|
|
11
11
|
"sha256": "e08447e36ad5d86f39f91f13defce0636aea1490b8d65dafde6fa72649422061"
|
|
12
12
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-code-intelligence",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.1.0",
|
|
4
4
|
"description": "LSP and AST navigation, search, diagnostics, and refactoring",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -35,10 +35,10 @@
|
|
|
35
35
|
],
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"yaml": "^2.9.0",
|
|
38
|
-
"@mrclrchtr/supi-
|
|
39
|
-
"@mrclrchtr/supi-
|
|
40
|
-
"@mrclrchtr/supi-
|
|
41
|
-
"@mrclrchtr/supi-
|
|
38
|
+
"@mrclrchtr/supi-lsp": "6.1.0",
|
|
39
|
+
"@mrclrchtr/supi-core": "6.1.0",
|
|
40
|
+
"@mrclrchtr/supi-code-runtime": "6.1.0",
|
|
41
|
+
"@mrclrchtr/supi-tree-sitter": "6.1.0"
|
|
42
42
|
},
|
|
43
43
|
"bundledDependencies": [
|
|
44
44
|
"@mrclrchtr/supi-code-runtime",
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import type { CapabilityState, CodeRequestControl } from "@mrclrchtr/supi-code-runtime/api";
|
|
5
5
|
import type {
|
|
6
6
|
DiagnosticEvidenceSummary,
|
|
7
|
+
ProcessCrashDiagnosticDemand,
|
|
7
8
|
RecoverDiagnosticsResult,
|
|
8
9
|
WorkspaceLspRuntime,
|
|
9
10
|
} from "@mrclrchtr/supi-lsp/api";
|
|
@@ -16,6 +17,8 @@ interface RecoverOptions {
|
|
|
16
17
|
control?: CodeRequestControl;
|
|
17
18
|
/** Evidence from the maintenance refresh, so the recovery pass skips its own refresh. */
|
|
18
19
|
initialEvidence?: DiagnosticEvidenceSummary;
|
|
20
|
+
/** Explicit demand for crashed diagnostic routes before diagnostic recovery. */
|
|
21
|
+
processCrashDemand?: ProcessCrashDiagnosticDemand;
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
/** Run the runtime's best-effort recovery and preserve its established outcome. */
|
|
@@ -26,6 +29,7 @@ export async function recoverDiagnosticRuntime(
|
|
|
26
29
|
return opts.service.recoverDiagnostics({
|
|
27
30
|
restartIfStillStale: true,
|
|
28
31
|
...(opts.initialEvidence !== undefined ? { initialEvidence: opts.initialEvidence } : {}),
|
|
32
|
+
...(opts.processCrashDemand ? { processCrashDemand: opts.processCrashDemand } : {}),
|
|
29
33
|
control: opts.control,
|
|
30
34
|
});
|
|
31
35
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import type { ProjectServerInfo, ProjectServerStatusReason } from "@mrclrchtr/supi-lsp/api";
|
|
3
|
+
|
|
4
|
+
/** Workspace-wide counts for non-running LSP routes. */
|
|
5
|
+
export interface ProjectServerRouteStatusCounts {
|
|
6
|
+
recovering: number;
|
|
7
|
+
error: number;
|
|
8
|
+
unavailable: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Count typed route states without changing semantic readiness. */
|
|
12
|
+
export function countProjectServerRouteStatuses(
|
|
13
|
+
servers: readonly Pick<ProjectServerInfo, "status" | "statusReason">[],
|
|
14
|
+
): ProjectServerRouteStatusCounts {
|
|
15
|
+
return {
|
|
16
|
+
recovering: servers.filter((server) => server.statusReason === "process-crash-recovery-pending")
|
|
17
|
+
.length,
|
|
18
|
+
error: servers.filter(
|
|
19
|
+
(server) =>
|
|
20
|
+
server.status === "error" && server.statusReason !== "process-crash-recovery-pending",
|
|
21
|
+
).length,
|
|
22
|
+
unavailable: servers.filter((server) => server.status === "unavailable").length,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Render nonzero workspace route counts for an aggregate status surface. */
|
|
27
|
+
export function formatProjectServerRouteSummary(
|
|
28
|
+
servers: readonly Pick<ProjectServerInfo, "status" | "statusReason">[],
|
|
29
|
+
): string | null {
|
|
30
|
+
return formatProjectServerRouteStatusCounts(countProjectServerRouteStatuses(servers));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Render one structured workspace route-count value. */
|
|
34
|
+
export function formatProjectServerRouteStatusCounts(
|
|
35
|
+
counts: ProjectServerRouteStatusCounts,
|
|
36
|
+
): string | null {
|
|
37
|
+
const parts: string[] = [];
|
|
38
|
+
if (counts.recovering > 0) parts.push(`${counts.recovering} recovering`);
|
|
39
|
+
if (counts.error > 0) parts.push(`${counts.error} error${counts.error === 1 ? "" : "s"}`);
|
|
40
|
+
if (counts.unavailable > 0) parts.push(`${counts.unavailable} unavailable`);
|
|
41
|
+
return parts.length > 0 ? `workspace routes: ${parts.join(", ")}` : null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Render an LSP route root relative to its workspace. */
|
|
45
|
+
export function formatProjectServerRoot(cwd: string, root: string): string {
|
|
46
|
+
const relative = path.relative(cwd, root);
|
|
47
|
+
return (relative || ".").replaceAll("\\", "/");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Render a concise explanation for a process-crash recovery state. */
|
|
51
|
+
export function formatProjectServerStatusReason(
|
|
52
|
+
reason: ProjectServerStatusReason | undefined,
|
|
53
|
+
): string | null {
|
|
54
|
+
switch (reason) {
|
|
55
|
+
case "process-crashed":
|
|
56
|
+
return "process crashed; next evidence operation will recover";
|
|
57
|
+
case "process-crash-recovery-pending":
|
|
58
|
+
return "process recovery in progress";
|
|
59
|
+
case "process-crash-recovery-exhausted":
|
|
60
|
+
return "process recovery exhausted; reload required";
|
|
61
|
+
default:
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
package/src/analysis/provider.ts
CHANGED
|
@@ -97,7 +97,8 @@ export function withSemanticRequestControl<T extends SemanticProvider>(
|
|
|
97
97
|
references: (file, position) => provider.references(file, position, control),
|
|
98
98
|
implementation: (file, position) => provider.implementation(file, position, control),
|
|
99
99
|
documentSymbols: (file) => provider.documentSymbols(file, control),
|
|
100
|
-
workspaceSymbols: (query
|
|
100
|
+
workspaceSymbols: (query, _requestControl, scopes) =>
|
|
101
|
+
provider.workspaceSymbols(query, control, scopes),
|
|
101
102
|
hover: provider.hover
|
|
102
103
|
? (file, position) => provider.hover?.(file, position, control)
|
|
103
104
|
: undefined,
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import * as path from "node:path";
|
|
10
10
|
import {
|
|
11
|
+
type CodeQueryResult,
|
|
11
12
|
type CodeRequestControl,
|
|
12
13
|
type CodeSymbol,
|
|
13
14
|
isCodeRequestInterruption,
|
|
@@ -114,33 +115,27 @@ export async function resolveSymbolTarget(
|
|
|
114
115
|
control?: CodeRequestControl;
|
|
115
116
|
},
|
|
116
117
|
): Promise<TargetOutcome> {
|
|
117
|
-
const
|
|
118
|
+
const scopePath = options?.path ? normalizePath(options.path, cwd) : null;
|
|
119
|
+
const result = await semantic.workspaceSymbols(
|
|
120
|
+
symbol,
|
|
121
|
+
options?.control,
|
|
122
|
+
scopePath ? [scopePath] : undefined,
|
|
123
|
+
);
|
|
118
124
|
if (result.kind === "unavailable") {
|
|
119
125
|
return {
|
|
120
|
-
kind: "
|
|
121
|
-
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
if (result.data.length === 0) {
|
|
125
|
-
return {
|
|
126
|
-
kind: "error",
|
|
127
|
-
message: `Symbol not found: \`${symbol}\`. Try \`code_resolve\` to search for it, or use an anchor target (file/line/character) if you know the location.`,
|
|
126
|
+
kind: "unavailable",
|
|
127
|
+
reason: `Symbol discovery for \`${symbol}\` is unavailable: ${result.reason}`,
|
|
128
128
|
};
|
|
129
129
|
}
|
|
130
|
+
if (result.data.length === 0) return emptySymbolOutcome(symbol, result, options?.path);
|
|
130
131
|
|
|
131
|
-
const scopePath = options?.path ? normalizePath(options.path, cwd) : null;
|
|
132
132
|
const scoped = result.data.filter((candidate) =>
|
|
133
133
|
scopePath ? isWithinOrEqual(scopePath, candidate.file) : true,
|
|
134
134
|
);
|
|
135
135
|
const eligible = options?.exportedOnly
|
|
136
136
|
? scoped.filter((candidate) => !NON_EXPORTED_KINDS.has(candidate.kind))
|
|
137
137
|
: scoped;
|
|
138
|
-
if (eligible.length === 0)
|
|
139
|
-
return {
|
|
140
|
-
kind: "error",
|
|
141
|
-
message: `Symbol not found: \`${symbol}\`${scopePath ? ` in path \`${options?.path}\`` : ""}. Try \`code_resolve\` to search for it, or use an anchor target (file/line/character) if you know the location.`,
|
|
142
|
-
};
|
|
143
|
-
}
|
|
138
|
+
if (eligible.length === 0) return emptySymbolOutcome(symbol, result, options?.path);
|
|
144
139
|
|
|
145
140
|
const requestedKind = options?.kind;
|
|
146
141
|
if (requestedKind) {
|
|
@@ -248,6 +243,23 @@ type CandidateOutcomeOptions = CandidateOutcomeBase &
|
|
|
248
243
|
* exact provider result count for omission disclosure. No candidate is
|
|
249
244
|
* promoted when the requested provider kind did not match.
|
|
250
245
|
*/
|
|
246
|
+
function emptySymbolOutcome(
|
|
247
|
+
symbol: string,
|
|
248
|
+
result: Exclude<CodeQueryResult<CodeSymbol[]>, { kind: "unavailable" }>,
|
|
249
|
+
scope?: string,
|
|
250
|
+
): TargetOutcome {
|
|
251
|
+
if (result.kind === "partial") {
|
|
252
|
+
return {
|
|
253
|
+
kind: "unavailable",
|
|
254
|
+
reason: `Symbol discovery for \`${symbol}\` is incomplete: ${result.reason}`,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
kind: "error",
|
|
259
|
+
message: `Symbol not found: \`${symbol}\`${scope ? ` in path \`${scope}\`` : ""}. Try \`code_resolve\` to search for it, or use an anchor target (file/line/character) if you know the location.`,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
251
263
|
async function buildCandidateOutcome(
|
|
252
264
|
options: CandidateOutcomeOptions,
|
|
253
265
|
): Promise<Extract<TargetOutcome, { kind: "disambiguation" | "kind-mismatch" }>> {
|
|
@@ -9,6 +9,7 @@ import { isCodeFindAstKind } from "../tool/code_find/ast-kinds.ts";
|
|
|
9
9
|
import type { CapabilityAdapter } from "./capability-adapter.ts";
|
|
10
10
|
import type { FindWorkflowInput, FindWorkflowOutcome } from "./find-types.ts";
|
|
11
11
|
import { parseFindWorkflowInput } from "./input/workflows.ts";
|
|
12
|
+
import { resolveWorkspaceSemanticDemand } from "./semantic-demand.ts";
|
|
12
13
|
import { reportProgress, throwIfAborted, type WorkflowControl } from "./workflow-control.ts";
|
|
13
14
|
|
|
14
15
|
export interface FindWorkflowDeps {
|
|
@@ -71,22 +72,14 @@ async function runSemanticSearch(options: {
|
|
|
71
72
|
control?: WorkflowControl;
|
|
72
73
|
}): Promise<FindWorkflowOutcome> {
|
|
73
74
|
const { query, scopePaths, scopeLabel, maxResults, deps, control } = options;
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
{ kind: "workspace" },
|
|
77
|
-
control,
|
|
78
|
-
);
|
|
79
|
-
if (readiness.kind === "timeout") {
|
|
75
|
+
const semanticDemand = await resolveWorkspaceSemanticDemand(deps.capability, deps.cwd, control);
|
|
76
|
+
if (semanticDemand.kind === "timeout") {
|
|
80
77
|
return { kind: "unavailable", reason: SEMANTIC_READINESS_TIMEOUT_REASON };
|
|
81
78
|
}
|
|
82
|
-
if (
|
|
79
|
+
if (semanticDemand.kind === "unavailable") return semanticDemand;
|
|
83
80
|
throwIfAborted(control);
|
|
84
81
|
|
|
85
|
-
const
|
|
86
|
-
if (!provider?.workspaceSymbols) {
|
|
87
|
-
return { kind: "unavailable", reason: "No semantic workspace-symbol provider is active." };
|
|
88
|
-
}
|
|
89
|
-
const result = await provider.workspaceSymbols(query, control);
|
|
82
|
+
const result = await semanticDemand.provider.workspaceSymbols(query, control, scopePaths);
|
|
90
83
|
// A cancellation that landed during the request must not publish symbols
|
|
91
84
|
// the caller no longer awaits.
|
|
92
85
|
throwIfAborted(control);
|
|
@@ -105,9 +105,13 @@ async function collectWorkspaceRefreshAttempt(
|
|
|
105
105
|
service: options.runtime,
|
|
106
106
|
control: options.control,
|
|
107
107
|
progress: options.reportRecoveryProgress,
|
|
108
|
-
// The maintenance pass already refreshed every
|
|
109
|
-
// evidence
|
|
108
|
+
// The maintenance pass already refreshed every running route. Reuse its
|
|
109
|
+
// evidence unless process-crash demand restores a required route; the
|
|
110
|
+
// runtime then performs a fresh pass that includes the replacement.
|
|
110
111
|
initialEvidence: maintenance.diagnosticEvidence,
|
|
112
|
+
processCrashDemand: {
|
|
113
|
+
...(workspaceScope?.filter ? { scopes: [workspaceScope.filter] } : {}),
|
|
114
|
+
},
|
|
111
115
|
});
|
|
112
116
|
const diagnosticEvidence = mergeDiagnosticEvidence(
|
|
113
117
|
maintenance.diagnosticEvidence,
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
DiagnosticEvidenceSummary,
|
|
3
|
+
FileScopeDecision,
|
|
4
|
+
ProjectServerInfo,
|
|
5
|
+
ProjectServerStatusReason,
|
|
6
|
+
} from "@mrclrchtr/supi-lsp/api";
|
|
2
7
|
import type { CapabilityWarningReport } from "../analysis/capability/capability-warnings.ts";
|
|
3
8
|
|
|
4
9
|
export type HealthSection = "diagnostics" | "servers";
|
|
@@ -22,7 +27,8 @@ export interface HealthServerInfo {
|
|
|
22
27
|
readonly name: string;
|
|
23
28
|
readonly root: string;
|
|
24
29
|
readonly fileTypes: readonly string[];
|
|
25
|
-
readonly status:
|
|
30
|
+
readonly status: ProjectServerInfo["status"];
|
|
31
|
+
readonly statusReason?: ProjectServerStatusReason;
|
|
26
32
|
readonly ready: boolean;
|
|
27
33
|
}
|
|
28
34
|
|
|
@@ -59,6 +59,7 @@ export async function runHealthWorkflow(
|
|
|
59
59
|
if (prepared.kind === "invalid-input") return prepared;
|
|
60
60
|
const { request, scopeFilter, included, level } = prepared.value;
|
|
61
61
|
const semanticRequested = healthNeedsSemantic(included);
|
|
62
|
+
const fileReadinessRequested = included.includes("diagnostics");
|
|
62
63
|
throwIfAborted(control);
|
|
63
64
|
|
|
64
65
|
reportProgress(control, {
|
|
@@ -85,6 +86,7 @@ export async function runHealthWorkflow(
|
|
|
85
86
|
});
|
|
86
87
|
const semanticState = await establishSemanticHealthState({
|
|
87
88
|
requested: semanticRequested,
|
|
89
|
+
fileReadinessRequested,
|
|
88
90
|
runtime,
|
|
89
91
|
scopeFilter,
|
|
90
92
|
lspState,
|
|
@@ -139,6 +141,8 @@ function healthNeedsSemantic(included: readonly HealthSection[]): boolean {
|
|
|
139
141
|
|
|
140
142
|
interface SemanticHealthStateOptions {
|
|
141
143
|
requested: boolean;
|
|
144
|
+
/** Whether this call requests file-routed evidence rather than passive inventory. */
|
|
145
|
+
fileReadinessRequested: boolean;
|
|
142
146
|
runtime: WorkspaceLspRuntime | null;
|
|
143
147
|
scopeFilter: string | null;
|
|
144
148
|
lspState: WorkspaceLspRuntimeState;
|
|
@@ -150,7 +154,7 @@ async function establishSemanticHealthState(
|
|
|
150
154
|
options: SemanticHealthStateOptions,
|
|
151
155
|
): Promise<SemanticHealthState | null> {
|
|
152
156
|
if (!options.requested) return null;
|
|
153
|
-
if (options.runtime && isScopedFile(options.scopeFilter)) {
|
|
157
|
+
if (options.fileReadinessRequested && options.runtime && isScopedFile(options.scopeFilter)) {
|
|
154
158
|
const readiness = await options.runtime.waitUntilReadyForFile(
|
|
155
159
|
options.scopeFilter,
|
|
156
160
|
undefined,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { CodeRequestControl, SemanticProvider } from "@mrclrchtr/supi-code-runtime/api";
|
|
2
|
+
import type { CapabilityAdapter, ReadinessOutcome } from "./capability-adapter.ts";
|
|
3
|
+
|
|
4
|
+
export type WorkspaceSemanticDemandState =
|
|
5
|
+
| { kind: "ready"; provider: SemanticProvider }
|
|
6
|
+
| Exclude<ReadinessOutcome, { kind: "ready" }>;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Wait for normal workspace startup, but let a registered provider proceed
|
|
10
|
+
* when known crashed routes make readiness recoverable only through demand.
|
|
11
|
+
*/
|
|
12
|
+
export async function resolveWorkspaceSemanticDemand(
|
|
13
|
+
capability: CapabilityAdapter,
|
|
14
|
+
cwd: string,
|
|
15
|
+
control?: CodeRequestControl,
|
|
16
|
+
): Promise<WorkspaceSemanticDemandState> {
|
|
17
|
+
let provider = capability.getSemanticProvider(cwd);
|
|
18
|
+
const readiness = await capability.ensureSemanticReadiness(cwd, { kind: "workspace" }, control);
|
|
19
|
+
if (readiness.kind === "timeout") return readiness;
|
|
20
|
+
if (readiness.kind === "unavailable" && !hasProcessCrashRoute(capability, cwd)) {
|
|
21
|
+
return readiness;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
provider ??= capability.getSemanticProvider(cwd);
|
|
25
|
+
return provider
|
|
26
|
+
? { kind: "ready", provider }
|
|
27
|
+
: { kind: "unavailable", reason: "No semantic workspace-symbol provider is active." };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function hasProcessCrashRoute(capability: CapabilityAdapter, cwd: string): boolean {
|
|
31
|
+
const lspState = capability.getLspRuntimeState(cwd);
|
|
32
|
+
return (
|
|
33
|
+
lspState.kind === "ready" &&
|
|
34
|
+
lspState.runtime.getProjectServers().some((server) => server.statusReason !== undefined)
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -23,6 +23,7 @@ import type {
|
|
|
23
23
|
} from "../analysis/target/types.ts";
|
|
24
24
|
import type { CapabilityAdapter } from "./capability-adapter.ts";
|
|
25
25
|
import { parseTargetInput } from "./input/common.ts";
|
|
26
|
+
import { resolveWorkspaceSemanticDemand } from "./semantic-demand.ts";
|
|
26
27
|
import { registerTargetCandidates, type TargetWorkflowCandidate } from "./target-candidates.ts";
|
|
27
28
|
import type { TargetInput, TargetSymbolKind } from "./target-input.ts";
|
|
28
29
|
import {
|
|
@@ -201,22 +202,15 @@ async function resolveSymbolWorkflow(
|
|
|
201
202
|
scope = resolved.path;
|
|
202
203
|
}
|
|
203
204
|
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
{ kind: "workspace" },
|
|
207
|
-
control,
|
|
208
|
-
);
|
|
209
|
-
if (readiness.kind === "timeout") {
|
|
205
|
+
const semanticDemand = await resolveWorkspaceSemanticDemand(deps.capability, deps.cwd, control);
|
|
206
|
+
if (semanticDemand.kind === "timeout") {
|
|
210
207
|
return { kind: "unavailable", reason: "LSP readiness timed out. Retry shortly." };
|
|
211
208
|
}
|
|
212
|
-
if (
|
|
213
|
-
return { kind: "unavailable", reason:
|
|
209
|
+
if (semanticDemand.kind === "unavailable") {
|
|
210
|
+
return { kind: "unavailable", reason: semanticDemand.reason };
|
|
214
211
|
}
|
|
215
212
|
|
|
216
|
-
const provider = withSemanticRequestControl(
|
|
217
|
-
deps.capability.getSemanticProvider(deps.cwd),
|
|
218
|
-
control,
|
|
219
|
-
);
|
|
213
|
+
const provider = withSemanticRequestControl(semanticDemand.provider, control);
|
|
220
214
|
if (!provider) {
|
|
221
215
|
return {
|
|
222
216
|
kind: "unavailable",
|
|
@@ -291,6 +285,9 @@ function toWorkflowOutcome(
|
|
|
291
285
|
policy: TargetWorkflowPolicy,
|
|
292
286
|
deps: TargetWorkflowDeps,
|
|
293
287
|
): TargetWorkflowOutcome {
|
|
288
|
+
if (outcome.kind === "unavailable") {
|
|
289
|
+
return { kind: "unavailable", reason: outcome.reason };
|
|
290
|
+
}
|
|
294
291
|
if (outcome.kind === "error") {
|
|
295
292
|
return { kind: "invalid-input", message: outcome.message };
|
|
296
293
|
}
|
|
@@ -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,6 +6,11 @@
|
|
|
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";
|
|
9
14
|
import type {
|
|
10
15
|
HealthData,
|
|
11
16
|
HealthDiagnosticObservation,
|
|
@@ -28,7 +33,9 @@ export function renderHealthResult(result: HealthResultAssembly, cwd: string): s
|
|
|
28
33
|
renderRefreshStatus(lines, data, hasDiagnostics, cwd);
|
|
29
34
|
if (hasDiagnostics) renderDiagnosticsSection(lines, data, cwd);
|
|
30
35
|
if (semanticRequested) renderCapabilityWarningsSection(lines, result.details.capabilityWarnings);
|
|
31
|
-
if (hasSection("servers"))
|
|
36
|
+
if (hasSection("servers")) {
|
|
37
|
+
renderServersSection(lines, data, sectionStatus(result, "servers"), cwd);
|
|
38
|
+
}
|
|
32
39
|
return lines.join("\n");
|
|
33
40
|
}
|
|
34
41
|
|
|
@@ -158,7 +165,8 @@ function renderCapabilityWarningsSection(
|
|
|
158
165
|
function renderStatusLine(lines: string[], data: HealthData, semanticRequested: boolean): void {
|
|
159
166
|
if (!semanticRequested) return;
|
|
160
167
|
|
|
161
|
-
|
|
168
|
+
const routeSummary = formatProjectServerRouteSummary(data.servers);
|
|
169
|
+
lines.push(`**LSP**: ${displaySemanticStatus(data)}${routeSummary ? ` — ${routeSummary}` : ""}`);
|
|
162
170
|
if (data.structuralStatus) lines.push(`**Structural**: ${data.structuralStatus}`);
|
|
163
171
|
lines.push("");
|
|
164
172
|
}
|
|
@@ -316,6 +324,7 @@ function renderServersSection(
|
|
|
316
324
|
lines: string[],
|
|
317
325
|
data: HealthData,
|
|
318
326
|
status: "complete" | "partial" | "unavailable" | undefined,
|
|
327
|
+
cwd: string,
|
|
319
328
|
): void {
|
|
320
329
|
lines.push("### Servers");
|
|
321
330
|
lines.push("");
|
|
@@ -329,8 +338,11 @@ function renderServersSection(
|
|
|
329
338
|
} else {
|
|
330
339
|
for (const server of data.servers) {
|
|
331
340
|
const statusIcon = server.status === "running" ? "✓" : "✗";
|
|
341
|
+
const reason = server.statusReason
|
|
342
|
+
? ` — ${formatProjectServerStatusReason(server.statusReason)}`
|
|
343
|
+
: "";
|
|
332
344
|
lines.push(
|
|
333
|
-
`- ${statusIcon} **${server.name}** (${server.fileTypes.join(", ")}) — ${server.status}`,
|
|
345
|
+
`- ${statusIcon} **${server.name}** @ \`${formatProjectServerRoot(cwd, server.root)}\` (${server.fileTypes.join(", ")}) — ${server.status}${reason}`,
|
|
334
346
|
);
|
|
335
347
|
}
|
|
336
348
|
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { ConfidenceMode } from "@mrclrchtr/supi-code-runtime/api";
|
|
2
|
+
import {
|
|
3
|
+
countProjectServerRouteStatuses,
|
|
4
|
+
formatProjectServerRoot,
|
|
5
|
+
formatProjectServerStatusReason,
|
|
6
|
+
} from "../../analysis/health/server-status.ts";
|
|
2
7
|
import type { HealthData, HealthSection } from "../../session/health-types.ts";
|
|
3
8
|
import {
|
|
4
9
|
assembleToolResult,
|
|
@@ -36,7 +41,7 @@ const SECTION_TITLES: Record<HealthSection, string> = {
|
|
|
36
41
|
};
|
|
37
42
|
|
|
38
43
|
/** Assemble public code_health evidence and details before presentation adapters render it. */
|
|
39
|
-
export function assembleHealthResult(data: HealthData): HealthResultAssembly {
|
|
44
|
+
export function assembleHealthResult(data: HealthData, cwd?: string): HealthResultAssembly {
|
|
40
45
|
const projections = data.includedSections.map((section) => projectSection(section, data));
|
|
41
46
|
const sections = projections.map((projection) => projection.section);
|
|
42
47
|
const sectionDetails = projections.map((projection) => projection.details);
|
|
@@ -55,7 +60,7 @@ export function assembleHealthResult(data: HealthData): HealthResultAssembly {
|
|
|
55
60
|
provenance,
|
|
56
61
|
});
|
|
57
62
|
|
|
58
|
-
const displaySections = buildHealthDisplaySections(data);
|
|
63
|
+
const displaySections = buildHealthDisplaySections(data, cwd);
|
|
59
64
|
|
|
60
65
|
return {
|
|
61
66
|
data,
|
|
@@ -78,12 +83,13 @@ export function assembleHealthResult(data: HealthData): HealthResultAssembly {
|
|
|
78
83
|
capabilityWarnings: data.capabilityWarnings ?? null,
|
|
79
84
|
diagnosticFileCount: data.diagnostics.entries.length,
|
|
80
85
|
serverCount: data.servers.length,
|
|
86
|
+
serverRouteStatusCounts: countProjectServerRouteStatuses(data.servers),
|
|
81
87
|
evidenceLists: [...assembled.evidenceLists],
|
|
82
88
|
},
|
|
83
89
|
};
|
|
84
90
|
}
|
|
85
91
|
|
|
86
|
-
function buildHealthDisplaySections(data: HealthData): ToolDisplaySection[] {
|
|
92
|
+
function buildHealthDisplaySections(data: HealthData, cwd?: string): ToolDisplaySection[] {
|
|
87
93
|
const sections: ToolDisplaySection[] = [];
|
|
88
94
|
|
|
89
95
|
if (data.includedSections.includes("diagnostics") && data.diagnostics.entries.length > 0) {
|
|
@@ -107,7 +113,10 @@ function buildHealthDisplaySections(data: HealthData): ToolDisplaySection[] {
|
|
|
107
113
|
title: "Servers",
|
|
108
114
|
items: data.servers,
|
|
109
115
|
totalCount: data.servers.length,
|
|
110
|
-
format: (server) =>
|
|
116
|
+
format: (server) => {
|
|
117
|
+
const root = cwd ? formatProjectServerRoot(cwd, server.root) : server.root;
|
|
118
|
+
return `${server.name} @ ${root} (${server.fileTypes.join(", ")}) — ${server.status}${server.statusReason ? ` — ${formatProjectServerStatusReason(server.statusReason)}` : ""}`;
|
|
119
|
+
},
|
|
111
120
|
}),
|
|
112
121
|
);
|
|
113
122
|
}
|
|
@@ -255,7 +264,7 @@ export function finishHealthResult(outcome: HealthOutcome, cwd: string): CodeInt
|
|
|
255
264
|
return healthErrorResult(`**Error:** ${outcome.message}`, outcome.message);
|
|
256
265
|
}
|
|
257
266
|
|
|
258
|
-
const assembly = assembleHealthResult(outcome.data);
|
|
267
|
+
const assembly = assembleHealthResult(outcome.data, cwd);
|
|
259
268
|
return {
|
|
260
269
|
content: renderHealthResult(assembly, cwd),
|
|
261
270
|
details: {
|
|
@@ -17,7 +17,8 @@ export const codeHealthSpec = {
|
|
|
17
17
|
scope: Type.Optional(ScopeParam),
|
|
18
18
|
refresh: Type.Optional(
|
|
19
19
|
Type.Boolean({
|
|
20
|
-
description:
|
|
20
|
+
description:
|
|
21
|
+
"Attempt diagnostic and required process-crash recovery before collecting; result reports the outcome.",
|
|
21
22
|
}),
|
|
22
23
|
),
|
|
23
24
|
include: Type.Optional(
|
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { Container, Spacer, Text } from "@earendil-works/pi-tui";
|
|
7
|
+
import {
|
|
8
|
+
formatProjectServerRouteStatusCounts,
|
|
9
|
+
type ProjectServerRouteStatusCounts,
|
|
10
|
+
} from "../../analysis/health/server-status.ts";
|
|
7
11
|
import {
|
|
8
12
|
type EvidenceEntry,
|
|
9
13
|
formatCallValue,
|
|
@@ -132,8 +136,11 @@ function buildCompactSummary(data: Record<string, unknown> | null, theme: Theme)
|
|
|
132
136
|
);
|
|
133
137
|
if (semanticRequested) {
|
|
134
138
|
const semanticStatus = readSemanticStatus(data);
|
|
139
|
+
const routeSummary = readServerRouteStatusSummary(data);
|
|
135
140
|
const statusColor = semanticStatus.startsWith("ready") ? "success" : "warning";
|
|
136
|
-
segments.push(
|
|
141
|
+
segments.push(
|
|
142
|
+
`${theme.fg("dim", "lsp")} ${theme.fg(statusColor, semanticStatus)}${routeSummary ? ` ${theme.fg("warning", routeSummary)}` : ""}`,
|
|
143
|
+
);
|
|
137
144
|
}
|
|
138
145
|
const previousRefresh = readPreviousRefreshStatus(data);
|
|
139
146
|
if (previousRefresh) segments.push(theme.fg("dim", previousRefresh));
|
|
@@ -179,7 +186,10 @@ function buildStatusBar(data: Record<string, unknown> | null, theme: Theme): Tex
|
|
|
179
186
|
const lspColor = semanticStatus.startsWith("ready") ? "success" : "warning";
|
|
180
187
|
const structuralColor = structuralStatus === "ready" ? "success" : "muted";
|
|
181
188
|
|
|
182
|
-
const
|
|
189
|
+
const routeSummary = readServerRouteStatusSummary(data);
|
|
190
|
+
const lines: string[] = [
|
|
191
|
+
`LSP: ${theme.fg(lspColor, semanticStatus)}${routeSummary ? ` — ${theme.fg("warning", routeSummary)}` : ""}`,
|
|
192
|
+
];
|
|
183
193
|
if (structuralStatus) {
|
|
184
194
|
lines.push(`Tree-sitter: ${theme.fg(structuralColor, structuralStatus)}`);
|
|
185
195
|
}
|
|
@@ -287,7 +297,8 @@ function buildHealthSectionSummary(data: Record<string, unknown> | null, theme:
|
|
|
287
297
|
if (section.status === "unavailable" || !section.available) {
|
|
288
298
|
return `${label} unavailable`;
|
|
289
299
|
}
|
|
290
|
-
|
|
300
|
+
const routeSummary = section.key === "servers" ? readServerRouteStatusSummary(data) : null;
|
|
301
|
+
return `${label} ${section.itemCount}${routeSummary ? ` — ${routeSummary}` : ""}`;
|
|
291
302
|
});
|
|
292
303
|
return new Text(theme.fg("muted", lines.join(" ")), 0, 0);
|
|
293
304
|
}
|
|
@@ -345,6 +356,16 @@ function readSemanticStatus(data: Record<string, unknown> | null): string {
|
|
|
345
356
|
return formatSemanticHealthState(readSemanticHealthState(data?.semanticState));
|
|
346
357
|
}
|
|
347
358
|
|
|
359
|
+
function readServerRouteStatusSummary(data: Record<string, unknown> | null): string | null {
|
|
360
|
+
const counts = readRecord(data?.serverRouteStatusCounts);
|
|
361
|
+
if (!counts) return null;
|
|
362
|
+
return formatProjectServerRouteStatusCounts({
|
|
363
|
+
recovering: typeof counts.recovering === "number" ? counts.recovering : 0,
|
|
364
|
+
error: typeof counts.error === "number" ? counts.error : 0,
|
|
365
|
+
unavailable: typeof counts.unavailable === "number" ? counts.unavailable : 0,
|
|
366
|
+
} satisfies ProjectServerRouteStatusCounts);
|
|
367
|
+
}
|
|
368
|
+
|
|
348
369
|
function readString(data: Record<string, unknown> | null, key: string): string | null {
|
|
349
370
|
const value = data?.[key];
|
|
350
371
|
return typeof value === "string" ? value : null;
|
|
@@ -100,6 +100,7 @@ export function healthErrorResult(content: string, reason?: string): CodeIntelRe
|
|
|
100
100
|
capabilityWarnings: null,
|
|
101
101
|
diagnosticFileCount: 0,
|
|
102
102
|
serverCount: 0,
|
|
103
|
+
serverRouteStatusCounts: { recovering: 0, error: 0, unavailable: 0 },
|
|
103
104
|
},
|
|
104
105
|
status: "invalid-input",
|
|
105
106
|
message: reason ?? content,
|