@mrclrchtr/supi-code-intelligence 6.3.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +164 -82
- package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +8 -1
- package/node_modules/@mrclrchtr/supi-core/README.md +8 -0
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +10 -3
- package/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
- package/node_modules/@mrclrchtr/supi-core/src/llm.ts +141 -16
- package/node_modules/@mrclrchtr/supi-lsp/README.md +35 -13
- 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/types.ts +8 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +8 -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/footer-registry.ts +10 -3
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/llm.ts +141 -16
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/index.js +3 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/legacy.js +3 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/package.json +3 -3
- package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +16 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-cache.ts +43 -35
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-capabilities.ts +83 -17
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-collection.ts +108 -92
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-evidence.ts +44 -82
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-host.ts +3 -4
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-publication.ts +9 -68
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-pull.ts +45 -58
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-refresh.ts +230 -331
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-request.ts +201 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-timing.ts +129 -70
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-typescript.ts +395 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-waiters.ts +8 -12
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostics.ts +685 -183
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-state.ts +2 -2
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-sync.ts +60 -60
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-request-enrollment.ts +75 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-semantic-input-barrier.ts +443 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-semantic-input-errors.ts +30 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-semantic-input-file.ts +15 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +290 -37
- package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +72 -16
- package/node_modules/@mrclrchtr/supi-lsp/src/config/capabilities.ts +1 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-scope.ts +30 -4
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/evidence.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/workspace-sentinels.ts +4 -32
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/workspace-sources.ts +108 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +10 -4
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-process-crash-report.ts +50 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +11 -7
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +770 -56
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostic-surface.ts +3 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +37 -4
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +48 -12
- package/node_modules/@mrclrchtr/supi-lsp/src/session/workspace-lsp-runtime.ts +50 -4
- 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/types.ts +8 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/README.md +8 -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/footer-registry.ts +10 -3
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/llm.ts +141 -16
- package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -3
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/provider/tree-sitter-provider.ts +2 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callee-display-name.ts +165 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callees.ts +67 -9
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +2 -0
- package/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +19 -18
- package/node_modules/yaml/browser/dist/nodes/Alias.js +25 -23
- package/node_modules/yaml/dist/compose/resolve-flow-scalar.js +19 -18
- package/node_modules/yaml/dist/nodes/Alias.js +25 -23
- package/node_modules/yaml/package.json +1 -1
- package/package.json +5 -5
- package/src/analysis/health/diagnostics.ts +1 -2
- package/src/analysis/health/file-scope.ts +4 -1
- package/src/analysis/health/server-status.ts +1 -1
- package/src/analysis/relations/callees.ts +17 -8
- package/src/analysis/relations/implementations.ts +1 -2
- package/src/analysis/relations/references.ts +8 -4
- package/src/analysis/relations/types.ts +6 -30
- package/src/analysis/target/anchored.ts +9 -1
- package/src/analysis/target/symbol.ts +29 -11
- package/src/analysis/target/types.ts +14 -6
- package/src/api.ts +2 -0
- package/src/session/graph/collect.ts +18 -3
- package/src/session/health-refresh.ts +27 -11
- package/src/session/health-types.ts +21 -4
- package/src/session/health-workflow.ts +28 -22
- package/src/session/orientation-types.ts +5 -6
- package/src/session/orientation-workflow.ts +10 -2
- package/src/session/session.ts +12 -5
- package/src/session/target-workflow.ts +9 -6
- package/src/substrate/lsp/maintenance.ts +53 -47
- package/src/substrate/lsp/source-tracking.ts +252 -0
- package/src/tool/code_find/guidance.ts +3 -3
- package/src/tool/code_find/spec.ts +5 -4
- package/src/tool/code_find/tui.ts +7 -1
- package/src/tool/code_graph/details.ts +32 -0
- package/src/tool/code_graph/display.ts +54 -0
- package/src/tool/code_graph/format.ts +60 -0
- package/src/tool/code_graph/guidance.ts +2 -2
- package/src/tool/code_graph/markdown.ts +76 -198
- package/src/tool/code_graph/read-next.ts +65 -0
- package/src/tool/code_graph/result.ts +118 -83
- package/src/tool/code_graph/tui.ts +216 -54
- package/src/tool/code_health/guidance.ts +3 -5
- package/src/tool/code_health/markdown.ts +90 -57
- package/src/tool/code_health/refresh-outcome.ts +51 -0
- package/src/tool/code_health/refresh-status.ts +130 -43
- package/src/tool/code_health/scope.ts +78 -0
- package/src/tool/code_health/spec.ts +1 -1
- package/src/tool/code_health/tui.ts +29 -38
- package/src/tool/code_inspect/guidance.ts +5 -3
- package/src/tool/code_inspect/tui.ts +7 -1
- package/src/tool/code_orientation/guidance.ts +3 -4
- package/src/tool/code_orientation/result.ts +31 -8
- package/src/tool/code_orientation/tui.ts +26 -5
- package/src/tool/code_refactor_apply/guidance.ts +1 -1
- package/src/tool/code_refactor_apply/tui.ts +7 -3
- package/src/tool/code_refactor_plan/guidance.ts +3 -5
- package/src/tool/code_refactor_plan/tui.ts +7 -1
- package/src/tool/code_resolve/guidance.ts +3 -5
- package/src/tool/code_resolve/result.ts +13 -10
- package/src/tool/code_resolve/tui.ts +18 -1
- package/src/tool/guidance.ts +11 -9
- package/src/tool/infra/truncate.ts +39 -13
- package/src/tool/result/candidate-row.ts +27 -0
- package/src/tool/result/errors.ts +21 -6
- package/src/tool/result/types.ts +7 -3
- package/src/tool/schemas.ts +5 -0
- package/src/types/execution.ts +2 -0
- package/src/types/index.ts +2 -0
- package/src/ui/tui/candidate-legacy-body.ts +69 -0
- package/src/ui/tui/candidate-selection.ts +366 -0
- package/src/ui/tui/common.ts +26 -31
- package/src/ui/tui/execution-error.ts +92 -0
|
@@ -1,28 +1,34 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Human transcript renderer for code_graph. */
|
|
2
2
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import { Container, Spacer, Text } from "@earendil-works/pi-tui";
|
|
3
|
+
import { type Component, Container, Spacer, Text } from "@earendil-works/pi-tui";
|
|
4
|
+
import { renderCandidateSelection } from "../../ui/tui/candidate-selection.ts";
|
|
4
5
|
import {
|
|
5
6
|
buildSimpleCompact,
|
|
6
|
-
buildSimpleHeader,
|
|
7
7
|
type EvidenceEntry,
|
|
8
8
|
formatCallPath,
|
|
9
9
|
formatCallValue,
|
|
10
10
|
type ResultOptios,
|
|
11
|
+
readEvidenceEntries,
|
|
11
12
|
renderDomainError,
|
|
12
|
-
renderDomainResult,
|
|
13
|
-
renderEvidenceLines,
|
|
14
13
|
renderExecutionError,
|
|
15
14
|
renderMarkdownDetail,
|
|
16
15
|
renderPartial,
|
|
17
|
-
renderStructuredDetailBody,
|
|
18
16
|
renderToolDisplaySections,
|
|
19
17
|
renderTruncationDisclosure,
|
|
20
18
|
type ToolRendererContext,
|
|
21
19
|
type ToolResult,
|
|
22
20
|
} from "../../ui/tui/common.ts";
|
|
21
|
+
import {
|
|
22
|
+
MAX_TUI_DISPLAY_ITEMS,
|
|
23
|
+
readToolDisplaySections,
|
|
24
|
+
truncateDisplayText,
|
|
25
|
+
} from "../result/display.ts";
|
|
26
|
+
import type { ToolDisplaySection } from "../result/types.ts";
|
|
27
|
+
import type { GraphFileGroup } from "./details.ts";
|
|
23
28
|
import type { CodeGraphToolParams, GraphRelation } from "./execute.ts";
|
|
29
|
+
import { compactGraphLabel, formatGraphEvidence, formatGraphRange } from "./format.ts";
|
|
24
30
|
|
|
25
|
-
/** Render the compact
|
|
31
|
+
/** Render the compact call header from the requested selector. */
|
|
26
32
|
export function renderGraphCall(
|
|
27
33
|
args: unknown,
|
|
28
34
|
theme: Theme,
|
|
@@ -30,84 +36,240 @@ export function renderGraphCall(
|
|
|
30
36
|
): Text {
|
|
31
37
|
const params = (args ?? {}) as Partial<CodeGraphToolParams>;
|
|
32
38
|
const relations: GraphRelation[] = Array.isArray(params.relations)
|
|
33
|
-
?
|
|
39
|
+
? params.relations
|
|
34
40
|
: ["references"];
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
let content = theme.fg("toolTitle", "code_graph");
|
|
38
|
-
if (relationLabel) content += ` ${theme.fg("accent", relationLabel)}`;
|
|
39
|
-
|
|
41
|
+
const label = formatRelations(relations);
|
|
40
42
|
const target = formatTarget(params.target);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
return new Text(
|
|
44
|
+
theme.fg("toolTitle", "code_graph") +
|
|
45
|
+
(label ? ` ${theme.fg("accent", label)}` : "") +
|
|
46
|
+
(target ? ` ${theme.fg("muted", target)}` : ""),
|
|
47
|
+
0,
|
|
48
|
+
0,
|
|
49
|
+
);
|
|
44
50
|
}
|
|
45
51
|
|
|
46
|
-
/** Render
|
|
52
|
+
/** Render one structured body, with no duplicate agent Markdown. */
|
|
47
53
|
export function renderGraphResult(
|
|
48
54
|
result: ToolResult,
|
|
49
55
|
options: ResultOptios,
|
|
50
56
|
theme: Theme,
|
|
51
57
|
context: ToolRendererContext | undefined,
|
|
52
|
-
):
|
|
58
|
+
): Component {
|
|
53
59
|
if (options.isPartial) return renderPartial("Collecting relations…", theme);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
60
|
+
const executionError = renderExecutionError(
|
|
61
|
+
result,
|
|
62
|
+
{ isError: context?.isError, expanded: options.expanded, label: "code_graph failed" },
|
|
63
|
+
theme,
|
|
64
|
+
);
|
|
58
65
|
if (executionError) return executionError;
|
|
66
|
+
const candidateSelection = renderCandidateSelection(result, options, theme, "graph.candidates");
|
|
67
|
+
if (candidateSelection) return candidateSelection;
|
|
59
68
|
const domainError = renderDomainError(result, theme);
|
|
60
|
-
if (domainError) return
|
|
69
|
+
if (domainError) return renderGraphDomainError(result, options.expanded, domainError, theme);
|
|
61
70
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
71
|
+
const data = asRecord(result.details?.data);
|
|
72
|
+
const container = new Container();
|
|
73
|
+
if (result.details?.type === "graph" && data) {
|
|
74
|
+
renderGraphBody(data, result, { container, expanded: options.expanded, theme });
|
|
75
|
+
} else {
|
|
76
|
+
// Older persisted results have only generic search details.
|
|
77
|
+
container.addChild(buildSimpleCompact(data ?? undefined, theme));
|
|
78
|
+
if (options.expanded)
|
|
79
|
+
renderToolDisplaySections(container, result.details?.displaySections, theme);
|
|
80
|
+
}
|
|
81
|
+
const truncation = renderTruncationDisclosure(result, theme);
|
|
82
|
+
if (truncation) {
|
|
68
83
|
container.addChild(new Spacer(1));
|
|
69
84
|
container.addChild(truncation);
|
|
70
|
-
return container;
|
|
71
85
|
}
|
|
86
|
+
return container;
|
|
87
|
+
}
|
|
72
88
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
89
|
+
interface SectionView {
|
|
90
|
+
rel: string;
|
|
91
|
+
label: string;
|
|
92
|
+
unavailable: boolean;
|
|
93
|
+
message: string | null;
|
|
94
|
+
externalCount: number;
|
|
95
|
+
scope: Record<string, unknown> | null;
|
|
96
|
+
fileGroups: GraphFileGroup[];
|
|
97
|
+
}
|
|
76
98
|
|
|
77
|
-
|
|
78
|
-
|
|
99
|
+
function renderGraphBody(
|
|
100
|
+
data: Record<string, unknown>,
|
|
101
|
+
result: ToolResult,
|
|
102
|
+
options: { container: Container; expanded: boolean; theme: Theme },
|
|
103
|
+
): void {
|
|
104
|
+
const { container, expanded, theme } = options;
|
|
105
|
+
const target =
|
|
106
|
+
typeof data.targetName === "string" ? compactGraphLabel(data.targetName) : "Target";
|
|
107
|
+
const file = typeof data.targetFile === "string" ? truncateDisplayText(data.targetFile) : "";
|
|
108
|
+
container.addChild(new Text(theme.fg("accent", `${target}${file ? ` — ${file}` : ""}`), 0, 0));
|
|
109
|
+
const evidence = readEvidenceEntries(data.evidenceLists);
|
|
110
|
+
const displays = readToolDisplaySections(result.details?.displaySections);
|
|
111
|
+
const sections = readSections(data.sections);
|
|
112
|
+
const summaries: string[] = [];
|
|
113
|
+
for (const section of sections) {
|
|
114
|
+
const display = displays.find((entry) => entry.key === `graph.${section.rel}`);
|
|
115
|
+
const metadata = evidence.find((entry) => entry.key.startsWith(`${section.rel}.`));
|
|
116
|
+
const summary = sectionSummary(section, metadata, display);
|
|
117
|
+
if (!expanded) {
|
|
118
|
+
summaries.push(summary);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
79
121
|
container.addChild(new Spacer(1));
|
|
80
|
-
|
|
122
|
+
container.addChild(
|
|
123
|
+
new Text(theme.fg(section.unavailable ? "warning" : "accent", summary), 0, 0),
|
|
124
|
+
);
|
|
125
|
+
renderRelationBody(container, { section, display, targetName: data.targetName }, theme);
|
|
81
126
|
}
|
|
127
|
+
if (!expanded)
|
|
128
|
+
container.addChild(
|
|
129
|
+
new Text(theme.fg("muted", summaries.join(" · ") || "No graph sections"), 0, 0),
|
|
130
|
+
);
|
|
131
|
+
}
|
|
82
132
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
133
|
+
function renderRelationBody(
|
|
134
|
+
container: Container,
|
|
135
|
+
input: { section: SectionView; display: ToolDisplaySection | undefined; targetName: unknown },
|
|
136
|
+
theme: Theme,
|
|
137
|
+
): void {
|
|
138
|
+
const { section, display, targetName } = input;
|
|
139
|
+
if (section.unavailable) {
|
|
140
|
+
if (section.message)
|
|
141
|
+
container.addChild(new Text(theme.fg("warning", truncateDisplayText(section.message)), 0, 0));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const scope = scopeLabel(section.scope, targetName);
|
|
145
|
+
if (scope) container.addChild(new Text(theme.fg("dim", scope), 0, 0));
|
|
146
|
+
const rows = display?.lines ?? [];
|
|
147
|
+
let index = 0;
|
|
148
|
+
const groups =
|
|
149
|
+
section.fileGroups.reduce((sum, group) => sum + group.count, 0) === rows.length
|
|
150
|
+
? section.fileGroups
|
|
151
|
+
: [];
|
|
152
|
+
for (const group of groups) {
|
|
153
|
+
if (index >= rows.length) break;
|
|
154
|
+
container.addChild(new Text(theme.fg("dim", group.file), 0, 0));
|
|
155
|
+
for (const row of rows.slice(index, index + group.count))
|
|
156
|
+
container.addChild(new Text(theme.fg("muted", ` ${row}`), 0, 0));
|
|
157
|
+
index += group.count;
|
|
89
158
|
}
|
|
90
|
-
|
|
159
|
+
// Older results contain full paths in each row and have no file groups.
|
|
160
|
+
for (const row of rows.slice(index)) container.addChild(new Text(theme.fg("muted", row), 0, 0));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function renderGraphDomainError(
|
|
164
|
+
result: ToolResult,
|
|
165
|
+
expanded: boolean,
|
|
166
|
+
domainError: Text,
|
|
167
|
+
theme: Theme,
|
|
168
|
+
): Container | Text {
|
|
169
|
+
if (!expanded) return domainError;
|
|
170
|
+
const container = new Container();
|
|
171
|
+
container.addChild(domainError);
|
|
172
|
+
const displays = readToolDisplaySections(result.details?.displaySections);
|
|
173
|
+
renderToolDisplaySections(container, displays, theme);
|
|
174
|
+
if (displays.length === 0) renderMarkdownDetail(container, result, theme);
|
|
91
175
|
return container;
|
|
92
176
|
}
|
|
93
177
|
|
|
178
|
+
function sectionSummary(
|
|
179
|
+
section: SectionView,
|
|
180
|
+
metadata: EvidenceEntry | undefined,
|
|
181
|
+
display: ToolDisplaySection | undefined,
|
|
182
|
+
): string {
|
|
183
|
+
if (section.unavailable) return `${section.label}: unavailable`;
|
|
184
|
+
if (!metadata) return `${section.label}: evidence unavailable`;
|
|
185
|
+
// The display has its own bounded row count; the underlying evidence total is unchanged.
|
|
186
|
+
const shown = display
|
|
187
|
+
? { ...metadata, shownCount: display.shownCount, omittedCount: display.omittedCount }
|
|
188
|
+
: metadata;
|
|
189
|
+
return `${section.label}: ${formatGraphEvidence(shown, section.rel === "callees" ? "call sites" : "locations", section.externalCount)}`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function readSections(value: unknown): SectionView[] {
|
|
193
|
+
if (!Array.isArray(value)) return [];
|
|
194
|
+
return value.slice(0, 3).flatMap((item) => {
|
|
195
|
+
const section = asRecord(item);
|
|
196
|
+
if (
|
|
197
|
+
!section ||
|
|
198
|
+
typeof section.rel !== "string" ||
|
|
199
|
+
!["references", "callees", "implements"].includes(section.rel) ||
|
|
200
|
+
(section.source !== "structural" && section.source !== "semantic")
|
|
201
|
+
)
|
|
202
|
+
return [];
|
|
203
|
+
const rel = section.rel;
|
|
204
|
+
const source = section.source;
|
|
205
|
+
const depth =
|
|
206
|
+
rel === "callees" && (section.depth === "direct" || section.depth === "deep")
|
|
207
|
+
? `, ${section.depth}`
|
|
208
|
+
: "";
|
|
209
|
+
return [
|
|
210
|
+
{
|
|
211
|
+
rel,
|
|
212
|
+
label: `${rel} (${source}${depth})`,
|
|
213
|
+
unavailable: section.status === "unavailable",
|
|
214
|
+
message: typeof section.message === "string" ? section.message : null,
|
|
215
|
+
externalCount: typeof section.externalCount === "number" ? section.externalCount : 0,
|
|
216
|
+
scope: asRecord(section.enclosingScope),
|
|
217
|
+
fileGroups: readFileGroups(section.fileGroups),
|
|
218
|
+
},
|
|
219
|
+
];
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function readFileGroups(value: unknown): GraphFileGroup[] {
|
|
224
|
+
if (!Array.isArray(value)) return [];
|
|
225
|
+
const groups: GraphFileGroup[] = [];
|
|
226
|
+
for (const entry of value.slice(0, MAX_TUI_DISPLAY_ITEMS)) {
|
|
227
|
+
const group = asRecord(entry);
|
|
228
|
+
if (
|
|
229
|
+
!group ||
|
|
230
|
+
typeof group.file !== "string" ||
|
|
231
|
+
typeof group.count !== "number" ||
|
|
232
|
+
!Number.isInteger(group.count) ||
|
|
233
|
+
group.count < 1 ||
|
|
234
|
+
group.count > MAX_TUI_DISPLAY_ITEMS
|
|
235
|
+
)
|
|
236
|
+
return [];
|
|
237
|
+
groups.push({ file: truncateDisplayText(group.file), count: group.count });
|
|
238
|
+
}
|
|
239
|
+
return groups;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function scopeLabel(scope: Record<string, unknown> | null, targetName: unknown): string | null {
|
|
243
|
+
if (!scope || typeof scope.startLine !== "number" || typeof scope.endLine !== "number")
|
|
244
|
+
return null;
|
|
245
|
+
const name =
|
|
246
|
+
typeof scope.name === "string" && scope.name !== targetName
|
|
247
|
+
? `${compactGraphLabel(scope.name)} — `
|
|
248
|
+
: "";
|
|
249
|
+
const range = formatGraphRange({ startLine: scope.startLine, endLine: scope.endLine });
|
|
250
|
+
return `Scope: ${name}${range}; source expressions, not resolved symbols`;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
254
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
255
|
+
? (value as Record<string, unknown>)
|
|
256
|
+
: null;
|
|
257
|
+
}
|
|
258
|
+
|
|
94
259
|
function formatRelations(relations: readonly GraphRelation[]): string {
|
|
95
|
-
|
|
96
|
-
if (relations.length === 1) return `→ ${relations[0]}`;
|
|
97
|
-
if (relations.length <= 3) return `→ ${relations.join(", ")}`;
|
|
98
|
-
return `→ ${relations.slice(0, 2).join(", ")} +${relations.length - 2}`;
|
|
260
|
+
return relations.length ? `→ ${relations.slice(0, 3).join(", ")}` : "";
|
|
99
261
|
}
|
|
100
262
|
|
|
101
263
|
function formatTarget(target: unknown): string {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (
|
|
106
|
-
const query = formatCallValue(
|
|
264
|
+
const record = asRecord(target);
|
|
265
|
+
if (!record) return "";
|
|
266
|
+
const symbol = asRecord(record.symbol);
|
|
267
|
+
if (symbol) {
|
|
268
|
+
const query = formatCallValue(symbol.query);
|
|
107
269
|
return query ? `of ${query}` : "";
|
|
108
270
|
}
|
|
109
|
-
|
|
110
|
-
|
|
271
|
+
const anchor = asRecord(record.anchor);
|
|
272
|
+
if (anchor) {
|
|
111
273
|
const file = formatCallPath(anchor.file);
|
|
112
274
|
const line = typeof anchor.line === "number" ? `:${anchor.line}` : "";
|
|
113
275
|
return file ? `at ${file}${line}` : "";
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
export const toolDescription =
|
|
2
|
-
"Report live diagnostics
|
|
2
|
+
"Report live diagnostics and language-server health. Use refresh:true when you need fresh diagnostic evidence; without it, diagnostics may be a tracked-file snapshot. Diagnostic snapshots do not prove that the whole workspace is clean; server inventory and route-status counts are always workspace-wide.";
|
|
3
3
|
|
|
4
|
-
export const promptSnippet = "live
|
|
4
|
+
export const promptSnippet = "check live diagnostics and language-server health";
|
|
5
5
|
|
|
6
|
-
export const promptGuidelines = [
|
|
7
|
-
"Use code_health with refresh:true before relying on potentially stale diagnostics; explicit refresh can recover crashed routes with tracked files in scope.",
|
|
8
|
-
];
|
|
6
|
+
export const promptGuidelines: string[] = [];
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Markdown renderer for code_health results.
|
|
3
|
-
*
|
|
4
|
-
* Renders structured health data from the code_health executor into
|
|
5
|
-
* readable markdown sections keyed by requested `include` values.
|
|
6
|
-
*/
|
|
7
|
-
|
|
1
|
+
/** Markdown renderer for code_health results. */
|
|
8
2
|
import type { CapabilityWarningReport } from "../../analysis/capability/capability-warnings.ts";
|
|
9
3
|
import {
|
|
10
4
|
formatProjectServerRoot,
|
|
@@ -15,7 +9,10 @@ import { makeRelative, renderFileScopeStatus } from "./file-scope-markdown.ts";
|
|
|
15
9
|
import {
|
|
16
10
|
formatProcessCrashRecovery,
|
|
17
11
|
formatRefreshElapsed,
|
|
12
|
+
formatSourceTracking,
|
|
13
|
+
formatSourceTrackingDetails,
|
|
18
14
|
formatStaleDiagnosticRestarts as formatStaleDiagnosticRestartsText,
|
|
15
|
+
formatStartupRetry,
|
|
19
16
|
isFileReadinessPending,
|
|
20
17
|
} from "./refresh-outcome.ts";
|
|
21
18
|
import type {
|
|
@@ -51,72 +48,93 @@ function renderRefreshStatus(
|
|
|
51
48
|
hasDiagnostics: boolean,
|
|
52
49
|
cwd: string,
|
|
53
50
|
): void {
|
|
54
|
-
if (!hasDiagnostics)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
lines.push("");
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
case "not-attempted":
|
|
81
|
-
lines.push(`**Diagnostic refresh attempt**: not started — ${data.refresh.reason}`);
|
|
82
|
-
if (data.refresh.lastAttempt) renderLastAttempt(lines, data.refresh.lastAttempt, cwd);
|
|
83
|
-
lines.push("");
|
|
84
|
-
return;
|
|
85
|
-
case "not-requested":
|
|
86
|
-
if (data.refresh.lastAttempt) {
|
|
87
|
-
renderRetainedAttempt(lines, data.refresh.lastAttempt, cwd);
|
|
88
|
-
} else {
|
|
89
|
-
lines.push(
|
|
90
|
-
"**Diagnostic refresh attempt**: not requested this session. Use `refresh: true` to try one.",
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
lines.push("");
|
|
51
|
+
if (!hasDiagnostics && data.refresh.kind !== "completed" && data.refresh.kind !== "failed") {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (data.refresh.kind === "completed") {
|
|
55
|
+
renderCompletedRefreshStatus(lines, data);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (data.refresh.kind === "failed") {
|
|
59
|
+
renderFailedRefreshStatus(lines, data.refresh);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (data.refresh.kind === "not-attempted") {
|
|
63
|
+
lines.push(`**Diagnostic refresh attempt**: not started — ${data.refresh.reason}`);
|
|
64
|
+
if (data.refresh.lastAttempt) renderLastAttempt(lines, data.refresh.lastAttempt, cwd);
|
|
65
|
+
lines.push("");
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (data.refresh.lastAttempt) {
|
|
69
|
+
renderRetainedAttempt(lines, data.refresh.lastAttempt, cwd);
|
|
70
|
+
} else {
|
|
71
|
+
lines.push(
|
|
72
|
+
"**Diagnostic refresh attempt**: not requested this session. Use `refresh: true` to try one.",
|
|
73
|
+
);
|
|
94
74
|
}
|
|
75
|
+
lines.push("");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function renderCompletedRefreshStatus(lines: string[], data: HealthData): void {
|
|
79
|
+
if (data.refresh.kind !== "completed") return;
|
|
80
|
+
const fileReadinessPending = isFileReadinessPending(data.refresh, data.semanticState);
|
|
81
|
+
lines.push(
|
|
82
|
+
`**${refreshAttemptLabel(data.refresh)}**: ${asSentence(completedRefreshText(data.refresh, fileReadinessPending, false))}`,
|
|
83
|
+
);
|
|
84
|
+
renderSourceTracking(lines, data.refresh);
|
|
85
|
+
lines.push(`**Stale assessment**: ${asSentence(staleAssessmentText(data.refresh))}`);
|
|
86
|
+
lines.push("");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function renderFailedRefreshStatus(
|
|
90
|
+
lines: string[],
|
|
91
|
+
attempt: Extract<HealthRefreshAttempt, { kind: "failed" }>,
|
|
92
|
+
): void {
|
|
93
|
+
const outcome = failedRefreshText(attempt, false);
|
|
94
|
+
lines.push(`**${refreshAttemptLabel(attempt)}**: ${asSentence(outcome)}`);
|
|
95
|
+
renderSourceTracking(lines, attempt);
|
|
96
|
+
lines.push("");
|
|
95
97
|
}
|
|
96
98
|
|
|
97
99
|
function completedRefreshText(
|
|
98
100
|
attempt: Extract<HealthRefreshAttempt, { kind: "completed" }>,
|
|
99
101
|
fileReadinessPending = attempt.fileReadiness === "pending",
|
|
102
|
+
includeSourceTracking = true,
|
|
100
103
|
): string {
|
|
101
104
|
const processCrashRecovery = formatProcessCrashRecovery(attempt.processCrashRecovery);
|
|
105
|
+
const startupRetry = formatStartupRetry(attempt.startupRetry);
|
|
102
106
|
const noOp =
|
|
103
107
|
!fileReadinessPending &&
|
|
104
108
|
attempt.attemptedActiveClients === 0 &&
|
|
105
109
|
attempt.restartedClients === 0 &&
|
|
106
|
-
processCrashRecovery === null
|
|
110
|
+
processCrashRecovery === null &&
|
|
111
|
+
startupRetry === null;
|
|
107
112
|
const base = fileReadinessPending
|
|
108
113
|
? "readiness pending — LSP may still be warming; retry shortly"
|
|
109
114
|
: noOp
|
|
110
115
|
? "completed no-op — no active clients were targeted"
|
|
111
116
|
: `completed — ${attempt.attemptedActiveClients} active client${plural(attempt.attemptedActiveClients)} targeted`;
|
|
112
117
|
const staleRestart = noOp ? null : formatStaleDiagnosticRestarts(attempt);
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
118
|
+
const sourceTracking = formatSourceTracking(attempt.sourceTracking);
|
|
119
|
+
const outcome = [
|
|
120
|
+
includeSourceTracking ? sourceTracking : null,
|
|
121
|
+
staleRestart,
|
|
122
|
+
processCrashRecovery,
|
|
123
|
+
startupRetry,
|
|
124
|
+
].filter((value): value is string => value !== null);
|
|
116
125
|
const withOutcome = outcome.length > 0 ? `${base}; ${outcome.join("; ")}` : base;
|
|
126
|
+
const withScope = `${withOutcome}; maintenance scope: ${refreshOperationScopeText(attempt)}`;
|
|
117
127
|
return attempt.operationScope === "workspace-runtime"
|
|
118
|
-
? `${
|
|
119
|
-
:
|
|
128
|
+
? `${withScope}; maintenance evidence: ${formatDiagnosticEvidence(attempt.diagnosticEvidence)}`
|
|
129
|
+
: withScope;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function renderSourceTracking(
|
|
133
|
+
lines: string[],
|
|
134
|
+
attempt: Pick<HealthRefreshAttempt, "sourceTracking">,
|
|
135
|
+
): void {
|
|
136
|
+
const sourceTracking = formatSourceTrackingDetails(attempt.sourceTracking);
|
|
137
|
+
if (sourceTracking) lines.push(`**Source discovery**: ${asSentence(sourceTracking)}`);
|
|
120
138
|
}
|
|
121
139
|
|
|
122
140
|
function refreshAttemptLabel(attempt: Pick<HealthRefreshAttempt, "operationScope">): string {
|
|
@@ -142,7 +160,7 @@ function renderLastAttempt(lines: string[], attempt: HealthRefreshAttempt, cwd:
|
|
|
142
160
|
const outcome = refreshAttemptOutcome(attempt);
|
|
143
161
|
const label = refreshAttemptLabel(attempt);
|
|
144
162
|
lines.push(
|
|
145
|
-
`**Previous ${label.toLowerCase()}**: ${asSentence(outcome)} Started ${formatRefreshElapsed(Date.now() - attempt.attemptedAt)}; requested ${formatDiagnosticScope(attempt.requestedDiagnosticScope, cwd)}
|
|
163
|
+
`**Previous ${label.toLowerCase()}**: ${asSentence(outcome)} Started ${formatRefreshElapsed(Date.now() - attempt.attemptedAt)}; requested evidence scope: ${formatDiagnosticScope(attempt.requestedDiagnosticScope, cwd)}.`,
|
|
146
164
|
);
|
|
147
165
|
}
|
|
148
166
|
|
|
@@ -150,20 +168,35 @@ function renderRetainedAttempt(lines: string[], attempt: HealthRefreshAttempt, c
|
|
|
150
168
|
const label =
|
|
151
169
|
attempt.operationScope === "file-runtime" ? "File LSP maintenance" : "Diagnostic refresh";
|
|
152
170
|
lines.push(
|
|
153
|
-
`**${label}**: not requested for this call. Previous attempt ${asSentence(refreshAttemptOutcome(attempt))} Started ${formatRefreshElapsed(Date.now() - attempt.attemptedAt)}; requested ${formatDiagnosticScope(attempt.requestedDiagnosticScope, cwd)}
|
|
171
|
+
`**${label}**: not requested for this call. Previous attempt ${asSentence(refreshAttemptOutcome(attempt))} Started ${formatRefreshElapsed(Date.now() - attempt.attemptedAt)}; requested evidence scope: ${formatDiagnosticScope(attempt.requestedDiagnosticScope, cwd)}.`,
|
|
154
172
|
);
|
|
155
173
|
}
|
|
156
174
|
|
|
157
175
|
function refreshAttemptOutcome(attempt: HealthRefreshAttempt): string {
|
|
158
176
|
if (attempt.kind === "completed") return completedRefreshText(attempt);
|
|
177
|
+
return failedRefreshText(attempt);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function failedRefreshText(
|
|
181
|
+
attempt: Extract<HealthRefreshAttempt, { kind: "failed" }>,
|
|
182
|
+
includeSourceTracking = true,
|
|
183
|
+
): string {
|
|
159
184
|
const processCrashRecovery = attempt.processCrashRecovery
|
|
160
185
|
? formatProcessCrashRecovery(attempt.processCrashRecovery)
|
|
161
186
|
: null;
|
|
187
|
+
const startupRetry = attempt.startupRetry ? formatStartupRetry(attempt.startupRetry) : null;
|
|
162
188
|
const staleRestart = formatStaleDiagnosticRestarts(attempt);
|
|
189
|
+
const sourceTracking = includeSourceTracking
|
|
190
|
+
? formatSourceTracking(attempt.sourceTracking)
|
|
191
|
+
: null;
|
|
163
192
|
const evidence = attempt.diagnosticEvidence
|
|
164
|
-
? `; ${formatDiagnosticEvidence(attempt.diagnosticEvidence)}`
|
|
193
|
+
? `; maintenance evidence: ${formatDiagnosticEvidence(attempt.diagnosticEvidence)}`
|
|
165
194
|
: "";
|
|
166
|
-
return `failed — ${attempt.reason}${staleRestart ? `; ${staleRestart}` : ""}${processCrashRecovery ? `; ${processCrashRecovery}` : ""}${evidence}`;
|
|
195
|
+
return `failed — ${stripTrailingPunctuation(attempt.reason)}; maintenance scope: ${refreshOperationScopeText(attempt)}${staleRestart ? `; ${staleRestart}` : ""}${processCrashRecovery ? `; ${processCrashRecovery}` : ""}${startupRetry ? `; ${startupRetry}` : ""}${sourceTracking ? `; ${sourceTracking}` : ""}${evidence}`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function stripTrailingPunctuation(text: string): string {
|
|
199
|
+
return text.replace(/[.!?]+$/, "");
|
|
167
200
|
}
|
|
168
201
|
|
|
169
202
|
function refreshOperationScopeText(attempt: HealthRefreshAttempt): string {
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ProcessCrashRecoveryEntry,
|
|
3
3
|
ProcessCrashRecoveryReport,
|
|
4
|
+
StartupRetryEntry,
|
|
5
|
+
StartupRetryReport,
|
|
4
6
|
} from "@mrclrchtr/supi-lsp/api";
|
|
5
7
|
import type { SemanticHealthState } from "../../session/health-types.ts";
|
|
8
|
+
import type { SourceTrackingReport } from "../../substrate/lsp/source-tracking.ts";
|
|
6
9
|
|
|
7
10
|
/** Report whether a file health result is waiting for semantic readiness. */
|
|
8
11
|
export function isFileReadinessPending(
|
|
@@ -64,6 +67,54 @@ function formatProcessCrashRecoveryEntry(entry: ProcessCrashRecoveryEntry): stri
|
|
|
64
67
|
return `${entry.name} @ ${entry.root}: ${outcome}${action}${failure}`;
|
|
65
68
|
}
|
|
66
69
|
|
|
70
|
+
/** Format initial-start retry counts for the compact health view. */
|
|
71
|
+
export function formatCompactStartupRetry(
|
|
72
|
+
report: StartupRetryReport | null | undefined,
|
|
73
|
+
): string | null {
|
|
74
|
+
if (!report || (report.recoveredRoutes === 0 && report.failedRoutes === 0)) return null;
|
|
75
|
+
return `startup retry: ${report.recoveredRoutes} route${plural(report.recoveredRoutes)} recovered (${report.failedRoutes} failed)`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Format initial-start retry counts and bounded route entries. */
|
|
79
|
+
export function formatStartupRetry(report: StartupRetryReport | null | undefined): string | null {
|
|
80
|
+
const summary = formatCompactStartupRetry(report);
|
|
81
|
+
if (!summary || !report) return summary;
|
|
82
|
+
const entries = report.entries.map(formatStartupRetryEntry);
|
|
83
|
+
const omitted =
|
|
84
|
+
report.omittedEntries > 0
|
|
85
|
+
? `${report.omittedEntries} more route${plural(report.omittedEntries)}`
|
|
86
|
+
: null;
|
|
87
|
+
const details = [...entries, omitted]
|
|
88
|
+
.filter((value): value is string => value !== null)
|
|
89
|
+
.join("; ");
|
|
90
|
+
return details ? `${summary}; ${details}` : summary;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function formatStartupRetryEntry(entry: StartupRetryEntry): string {
|
|
94
|
+
const outcome = entry.outcome.replaceAll("-", " ");
|
|
95
|
+
const action = entry.nextAction ? `; next: ${entry.nextAction.replaceAll("-", " ")}` : "";
|
|
96
|
+
const failure = entry.failureMessage ? `; ${entry.failureMessage}` : "";
|
|
97
|
+
return `${entry.name} @ ${entry.root}: ${outcome}${action}${failure}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Format source discovery and bounded tracking counts separately from diagnostics. */
|
|
101
|
+
export function formatSourceTracking(
|
|
102
|
+
report: SourceTrackingReport | null | undefined,
|
|
103
|
+
): string | null {
|
|
104
|
+
const details = formatSourceTrackingDetails(report);
|
|
105
|
+
return details ? `source discovery: ${details}` : null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Format source tracking counts without a label for a standalone heading. */
|
|
109
|
+
export function formatSourceTrackingDetails(
|
|
110
|
+
report: SourceTrackingReport | null | undefined,
|
|
111
|
+
): string | null {
|
|
112
|
+
if (!report) return null;
|
|
113
|
+
const inventory =
|
|
114
|
+
report.status === "complete" ? "complete" : `limited (${report.reason ?? "unknown reason"})`;
|
|
115
|
+
return `${inventory}; ${report.observedFileCount} source files observed, ${report.discovered.length} discovered, ${report.tracked.length} tracked, ${report.skipped.length} skipped, ${report.unavailable.length} unavailable, ${report.deferred} deferred`;
|
|
116
|
+
}
|
|
117
|
+
|
|
67
118
|
/** Format the age of a retained refresh attempt for a status line. */
|
|
68
119
|
export function formatRefreshElapsed(milliseconds: number): string {
|
|
69
120
|
const seconds = Math.max(0, Math.round(milliseconds / 1_000));
|