@mrclrchtr/supi-code-intelligence 6.4.0 → 7.0.1
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 +6 -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/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 +608 -45
- 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 +31 -11
- package/node_modules/@mrclrchtr/supi-lsp/src/session/workspace-lsp-runtime.ts +19 -3
- 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 +12 -1
- package/src/session/health-types.ts +18 -6
- package/src/session/health-workflow.ts +21 -19
- package/src/session/orientation-types.ts +5 -6
- package/src/session/orientation-workflow.ts +10 -2
- package/src/session/target-workflow.ts +9 -6
- package/src/substrate/lsp/source-tracking.ts +11 -6
- package/src/tool/code_find/guidance.ts +2 -2
- 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 +1 -1
- 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 +2 -4
- package/src/tool/code_health/markdown.ts +75 -58
- package/src/tool/code_health/refresh-outcome.ts +41 -1
- package/src/tool/code_health/refresh-status.ts +114 -48
- 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 +25 -38
- package/src/tool/code_inspect/tui.ts +7 -1
- 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 +1 -1
- package/src/tool/code_refactor_plan/tui.ts +7 -1
- package/src/tool/code_resolve/guidance.ts +2 -4
- package/src/tool/code_resolve/result.ts +13 -10
- package/src/tool/code_resolve/tui.ts +18 -1
- 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
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type ReadNextItem, readNextRange } from "../../analysis/read-next.ts";
|
|
2
|
+
import type { GraphSection } from "../../session/graph-types.ts";
|
|
3
|
+
|
|
4
|
+
/** Prefer the known enclosing scope, then alternate relation sites before the display cap. */
|
|
5
|
+
export function graphReadNext(sections: readonly GraphSection[]): ReadNextItem[] {
|
|
6
|
+
const calls = sections.find((section) => section.kind === "ok" && section.rel === "callees");
|
|
7
|
+
const scopeReads =
|
|
8
|
+
calls?.kind === "ok"
|
|
9
|
+
? calls.readNext.map((item) => ({ ...item, reason: "inspect the enclosing scope" }))
|
|
10
|
+
: [];
|
|
11
|
+
const queues = sections.flatMap((section) => {
|
|
12
|
+
if (section.kind !== "ok" || section.rel === "callees") return [];
|
|
13
|
+
// Reference collection puts the approximate target range first. The known scope replaces it.
|
|
14
|
+
return [
|
|
15
|
+
section.rel === "references" && scopeReads.length > 0
|
|
16
|
+
? section.readNext.slice(1)
|
|
17
|
+
: section.readNext,
|
|
18
|
+
];
|
|
19
|
+
});
|
|
20
|
+
const candidates = [...scopeReads];
|
|
21
|
+
const maxLength = Math.max(0, ...queues.map((queue) => queue.length));
|
|
22
|
+
for (let index = 0; index < maxLength; index++) {
|
|
23
|
+
for (const queue of queues) {
|
|
24
|
+
const item = queue[index];
|
|
25
|
+
if (item) candidates.push(item);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return mergeRanges(candidates)
|
|
29
|
+
.slice(0, 3)
|
|
30
|
+
.map((item) => readNextRange(item));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Merge connected ranges before bounding them, including ranges that bridge two earlier reads. */
|
|
34
|
+
function mergeRanges(items: readonly ReadNextItem[]): ReadNextItem[] {
|
|
35
|
+
const result: ReadNextItem[] = [];
|
|
36
|
+
for (const item of items) {
|
|
37
|
+
const merged = { ...item };
|
|
38
|
+
let firstIndex = result.length;
|
|
39
|
+
let index = 0;
|
|
40
|
+
while (index < result.length) {
|
|
41
|
+
const other = result[index];
|
|
42
|
+
if (
|
|
43
|
+
other.file === merged.file &&
|
|
44
|
+
other.startLine <= merged.endLine &&
|
|
45
|
+
merged.startLine <= other.endLine
|
|
46
|
+
) {
|
|
47
|
+
firstIndex = Math.min(firstIndex, index);
|
|
48
|
+
if (
|
|
49
|
+
merged.reason !== other.reason ||
|
|
50
|
+
merged.startLine !== other.startLine ||
|
|
51
|
+
merged.endLine !== other.endLine
|
|
52
|
+
)
|
|
53
|
+
merged.reason = "inspect related source ranges";
|
|
54
|
+
merged.startLine = Math.min(merged.startLine, other.startLine);
|
|
55
|
+
merged.endLine = Math.max(merged.endLine, other.endLine);
|
|
56
|
+
result.splice(index, 1);
|
|
57
|
+
index = 0;
|
|
58
|
+
} else {
|
|
59
|
+
index++;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
result.splice(firstIndex, 0, merged);
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { ConfidenceMode } from "@mrclrchtr/supi-code-runtime/api";
|
|
2
|
-
import {
|
|
2
|
+
import type { EvidenceListMetadata } from "../../analysis/evidence.ts";
|
|
3
|
+
import {
|
|
4
|
+
createEvidenceList,
|
|
5
|
+
createPartialEvidenceList,
|
|
6
|
+
type EvidenceList,
|
|
7
|
+
} from "../../analysis/evidence.ts";
|
|
3
8
|
import type { RelationLocationPartialReason } from "../../analysis/relations/provider-locations.ts";
|
|
4
9
|
import type {
|
|
5
10
|
CallEntry,
|
|
@@ -9,13 +14,18 @@ import type {
|
|
|
9
14
|
import { toDisplayPath } from "../../analysis/search/paths.ts";
|
|
10
15
|
import type { GraphSection } from "../../session/graph-types.ts";
|
|
11
16
|
import {
|
|
12
|
-
|
|
17
|
+
assembledReadNext,
|
|
13
18
|
assembleToolResult,
|
|
14
19
|
type ResultProvenance,
|
|
15
20
|
type ToolResultAssembly,
|
|
16
21
|
} from "../result/assembly.ts";
|
|
22
|
+
import { formatCandidateRow } from "../result/candidate-row.ts";
|
|
17
23
|
import { createToolDisplaySection } from "../result/display.ts";
|
|
18
|
-
import type {
|
|
24
|
+
import type { ToolDisplaySection } from "../result/types.ts";
|
|
25
|
+
import type { GraphDetails, GraphFileGroup, GraphSectionDetails } from "./details.ts";
|
|
26
|
+
import { graphDisplaySection } from "./display.ts";
|
|
27
|
+
import { formatGraphEvidence } from "./format.ts";
|
|
28
|
+
import { graphReadNext } from "./read-next.ts";
|
|
19
29
|
|
|
20
30
|
export type { GraphRelationKind, GraphSection } from "../../session/graph-types.ts";
|
|
21
31
|
|
|
@@ -40,7 +50,7 @@ export interface GraphResultAssembly {
|
|
|
40
50
|
resolvedDisplayFile: string;
|
|
41
51
|
cwd: string;
|
|
42
52
|
assembled: ToolResultAssembly<{ readonly sections: readonly AssembledGraphSection[] }>;
|
|
43
|
-
details:
|
|
53
|
+
details: GraphDetails;
|
|
44
54
|
displaySections: readonly ToolDisplaySection[];
|
|
45
55
|
}
|
|
46
56
|
|
|
@@ -74,13 +84,14 @@ export function assembleGraphResult(input: {
|
|
|
74
84
|
})),
|
|
75
85
|
evidenceLists,
|
|
76
86
|
nextQueries: [],
|
|
77
|
-
readNext: sections
|
|
87
|
+
readNext: graphReadNext(sections),
|
|
78
88
|
candidateCount,
|
|
79
89
|
confidence,
|
|
80
90
|
provenance,
|
|
81
91
|
});
|
|
82
92
|
|
|
83
|
-
const
|
|
93
|
+
const displays = sections.map((section) => graphDisplaySection(section, input.cwd));
|
|
94
|
+
const displaySections = displays.map(({ display }) => display);
|
|
84
95
|
|
|
85
96
|
return {
|
|
86
97
|
displayName: input.displayName,
|
|
@@ -90,51 +101,36 @@ export function assembleGraphResult(input: {
|
|
|
90
101
|
assembled,
|
|
91
102
|
displaySections,
|
|
92
103
|
details: {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
104
|
+
targetName: input.displayName,
|
|
105
|
+
targetFile: input.resolvedDisplayFile,
|
|
106
|
+
sections: sections.map((section, index) =>
|
|
107
|
+
graphSectionDetails(section, input.cwd, displays[index].fileGroups),
|
|
108
|
+
),
|
|
109
|
+
readNext: assembledReadNext(assembled),
|
|
97
110
|
evidenceLists: [...assembled.evidenceLists],
|
|
98
|
-
nextQueries: assembledNextQueries(assembled),
|
|
99
111
|
},
|
|
100
112
|
};
|
|
101
113
|
}
|
|
102
114
|
|
|
103
|
-
function
|
|
104
|
-
|
|
105
|
-
return createToolDisplaySection({
|
|
106
|
-
key: `graph.${section.rel}`,
|
|
107
|
-
title: section.rel,
|
|
108
|
-
items: [section.message],
|
|
109
|
-
format: (message) => `Unavailable — ${message}`,
|
|
110
|
-
totalCount: 1,
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return createToolDisplaySection({
|
|
115
|
-
key: `graph.${section.rel}`,
|
|
116
|
-
title: section.rel,
|
|
117
|
-
items: section.evidence.items as readonly unknown[],
|
|
118
|
-
totalCount: section.evidence.metadata.totalCount,
|
|
119
|
-
omittedCount: section.evidence.metadata.omittedCount,
|
|
120
|
-
partialReason: section.evidence.metadata.partialReason,
|
|
121
|
-
format: (item) => formatGraphDisplayItem(section.rel, item, cwd),
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function formatGraphDisplayItem(
|
|
126
|
-
relation: "references" | "callees" | "implements",
|
|
127
|
-
item: unknown,
|
|
115
|
+
function graphSectionDetails(
|
|
116
|
+
section: AssembledGraphSection,
|
|
128
117
|
cwd: string,
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
118
|
+
fileGroups: GraphFileGroup[],
|
|
119
|
+
): GraphSectionDetails {
|
|
120
|
+
const calls = section.kind === "ok" && section.rel === "callees" ? section.data : null;
|
|
121
|
+
return {
|
|
122
|
+
rel: section.rel,
|
|
123
|
+
source: section.rel === "callees" ? "structural" : "semantic",
|
|
124
|
+
status: section.kind === "ok" ? evidenceStatus(section.evidence) : "unavailable",
|
|
125
|
+
message: section.kind === "unavailable" ? section.message : null,
|
|
126
|
+
externalCount:
|
|
127
|
+
section.kind === "ok" && section.rel !== "callees" ? section.data.externalCount : 0,
|
|
128
|
+
enclosingScope: calls
|
|
129
|
+
? { ...calls.enclosingScope, file: toDisplayPath(cwd, calls.enclosingScope.file) }
|
|
130
|
+
: null,
|
|
131
|
+
depth: calls?.depth ?? null,
|
|
132
|
+
fileGroups,
|
|
133
|
+
};
|
|
138
134
|
}
|
|
139
135
|
|
|
140
136
|
function assembleGraphSection(section: GraphSection, maxResults: number): AssembledGraphSection {
|
|
@@ -181,11 +177,10 @@ function createRelationEvidenceList<T>(params: {
|
|
|
181
177
|
invalidLocationCount: number;
|
|
182
178
|
partialReason: RelationLocationPartialReason | null;
|
|
183
179
|
}): EvidenceList<T> {
|
|
184
|
-
const evidence =
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
});
|
|
180
|
+
const evidence =
|
|
181
|
+
params.partialReason === "provider-limited"
|
|
182
|
+
? createPartialEvidenceList({ ...params, partialReason: params.partialReason })
|
|
183
|
+
: createEvidenceList(params);
|
|
189
184
|
if (params.partialReason === null) return evidence;
|
|
190
185
|
return {
|
|
191
186
|
...evidence,
|
|
@@ -247,38 +242,7 @@ export function finishGraphResult(outcome: GraphOutcome, cwd: string): CodeIntel
|
|
|
247
242
|
return searchErrorResult(`**Error:** ${outcome.message}`, { message: outcome.message });
|
|
248
243
|
}
|
|
249
244
|
if (outcome.kind === "disambiguation" || outcome.kind === "kind-mismatch") {
|
|
250
|
-
|
|
251
|
-
outcome.kind === "kind-mismatch"
|
|
252
|
-
? `**No target matched provider kind \`${outcome.requestedKind}\`. Near matches:**`
|
|
253
|
-
: "**Target is ambiguous. Choose one candidate handle:**",
|
|
254
|
-
"",
|
|
255
|
-
];
|
|
256
|
-
for (const candidate of outcome.candidates) {
|
|
257
|
-
lines.push(
|
|
258
|
-
`- \`${candidate.targetId}\` — ${candidate.name} (\`${candidate.kind ?? "unknown"}\`) at ${candidate.file}:${candidate.line}:${candidate.character}`,
|
|
259
|
-
);
|
|
260
|
-
}
|
|
261
|
-
if (outcome.kind === "kind-mismatch") {
|
|
262
|
-
lines.push(
|
|
263
|
-
"",
|
|
264
|
-
"Retry without `symbolKind`, use an observed provider kind, or choose a handle.",
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
return searchErrorResult(lines.join("\n"), {
|
|
268
|
-
message:
|
|
269
|
-
outcome.kind === "kind-mismatch"
|
|
270
|
-
? `No target matched provider kind ${outcome.requestedKind}.`
|
|
271
|
-
: "The target is ambiguous.",
|
|
272
|
-
displaySections: [
|
|
273
|
-
createToolDisplaySection({
|
|
274
|
-
key: "graph.candidates",
|
|
275
|
-
title: "Candidates",
|
|
276
|
-
items: outcome.candidates,
|
|
277
|
-
format: (candidate) =>
|
|
278
|
-
`${candidate.targetId} — ${candidate.name} (${candidate.kind ?? "unknown"}) at ${candidate.file}:${candidate.line}:${candidate.character}`,
|
|
279
|
-
}),
|
|
280
|
-
],
|
|
281
|
-
});
|
|
245
|
+
return finishGraphCandidates(outcome);
|
|
282
246
|
}
|
|
283
247
|
|
|
284
248
|
const assembly = assembleGraphResult({
|
|
@@ -288,13 +252,84 @@ export function finishGraphResult(outcome: GraphOutcome, cwd: string): CodeIntel
|
|
|
288
252
|
maxResults: outcome.maxResults,
|
|
289
253
|
cwd,
|
|
290
254
|
});
|
|
255
|
+
const displayTruncated = hasHiddenDisplayEvidence(assembly);
|
|
291
256
|
return {
|
|
292
257
|
content: renderGraphResult(assembly),
|
|
293
258
|
details: {
|
|
294
|
-
type: "
|
|
259
|
+
type: "graph",
|
|
295
260
|
data: assembly.details,
|
|
296
261
|
status: "completed",
|
|
297
262
|
displaySections: assembly.displaySections,
|
|
263
|
+
...(displayTruncated ? { truncation: { truncated: false, displayTruncated: true } } : {}),
|
|
298
264
|
},
|
|
299
265
|
};
|
|
300
266
|
}
|
|
267
|
+
|
|
268
|
+
function finishGraphCandidates(
|
|
269
|
+
outcome: Extract<GraphOutcome, { kind: "disambiguation" | "kind-mismatch" }>,
|
|
270
|
+
): CodeIntelResult {
|
|
271
|
+
const lines = [
|
|
272
|
+
outcome.kind === "kind-mismatch"
|
|
273
|
+
? `**No target matched provider kind \`${outcome.requestedKind}\`. Near matches:**`
|
|
274
|
+
: "**Target is ambiguous. Choose one candidate handle:**",
|
|
275
|
+
"",
|
|
276
|
+
];
|
|
277
|
+
for (const candidate of outcome.candidates) {
|
|
278
|
+
lines.push(
|
|
279
|
+
`- \`${candidate.targetId}\` — ${candidate.name} (\`${candidate.kind ?? "unknown"}\`) at ${candidate.file}:${candidate.line}:${candidate.character}`,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
const evidence = graphCandidateEvidence(outcome);
|
|
283
|
+
lines.push("", formatGraphEvidence(evidence, "candidates"));
|
|
284
|
+
if (outcome.kind === "kind-mismatch") {
|
|
285
|
+
lines.push(
|
|
286
|
+
"",
|
|
287
|
+
"Retry without `symbolKind`, use an observed provider kind, or choose a handle.",
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
const displaySections = [
|
|
291
|
+
createToolDisplaySection({
|
|
292
|
+
key: "graph.candidates",
|
|
293
|
+
title: "Candidates",
|
|
294
|
+
items: outcome.candidates,
|
|
295
|
+
totalCount: evidence.totalCount,
|
|
296
|
+
omittedCount: evidence.omittedCount,
|
|
297
|
+
partialReason: evidence.partialReason,
|
|
298
|
+
format: (candidate) => formatCandidateRow(candidate, "handle-first"),
|
|
299
|
+
}),
|
|
300
|
+
];
|
|
301
|
+
return searchErrorResult(lines.join("\n"), {
|
|
302
|
+
confidence: "semantic",
|
|
303
|
+
evidenceLists: [evidence],
|
|
304
|
+
nextQueries:
|
|
305
|
+
outcome.kind === "kind-mismatch"
|
|
306
|
+
? ["Retry without symbolKind, use an observed provider kind, or choose a near-match handle"]
|
|
307
|
+
: ["Choose one candidate handle, or narrow the symbol selector with scope or symbolKind"],
|
|
308
|
+
status: outcome.kind === "disambiguation" ? "disambiguation" : "invalid-input",
|
|
309
|
+
message:
|
|
310
|
+
outcome.kind === "kind-mismatch"
|
|
311
|
+
? `No target matched provider kind ${outcome.requestedKind}.`
|
|
312
|
+
: "The target is ambiguous.",
|
|
313
|
+
displaySections,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function graphCandidateEvidence(
|
|
318
|
+
outcome: Extract<GraphOutcome, { kind: "disambiguation" | "kind-mismatch" }>,
|
|
319
|
+
): EvidenceListMetadata {
|
|
320
|
+
return {
|
|
321
|
+
key: "graph.candidates",
|
|
322
|
+
totalCount: outcome.totalCount,
|
|
323
|
+
shownCount: outcome.candidates.length,
|
|
324
|
+
omittedCount: outcome.omittedCount,
|
|
325
|
+
partialReason: outcome.partialReason,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function hasHiddenDisplayEvidence(assembly: GraphResultAssembly): boolean {
|
|
330
|
+
return assembly.sections.some(
|
|
331
|
+
(section, index) =>
|
|
332
|
+
section.kind === "ok" &&
|
|
333
|
+
assembly.displaySections[index].shownCount < section.evidence.items.length,
|
|
334
|
+
);
|
|
335
|
+
}
|
|
@@ -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}` : "";
|