@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.
Files changed (136) hide show
  1. package/README.md +164 -82
  2. package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  3. package/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +8 -1
  4. package/node_modules/@mrclrchtr/supi-core/README.md +8 -0
  5. package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  6. package/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +10 -3
  7. package/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
  8. package/node_modules/@mrclrchtr/supi-core/src/llm.ts +141 -16
  9. package/node_modules/@mrclrchtr/supi-lsp/README.md +35 -13
  10. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  11. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +8 -1
  12. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +8 -0
  13. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  14. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +10 -3
  15. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
  16. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/llm.ts +141 -16
  17. package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/index.js +3 -1
  18. package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/legacy.js +3 -1
  19. package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/package.json +1 -1
  20. package/node_modules/@mrclrchtr/supi-lsp/package.json +3 -3
  21. package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +16 -0
  22. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-cache.ts +43 -35
  23. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-capabilities.ts +83 -17
  24. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-collection.ts +108 -92
  25. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-evidence.ts +44 -82
  26. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-host.ts +3 -4
  27. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-publication.ts +9 -68
  28. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-pull.ts +45 -58
  29. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-refresh.ts +230 -331
  30. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-request.ts +201 -1
  31. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-timing.ts +129 -70
  32. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-typescript.ts +395 -0
  33. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-waiters.ts +8 -12
  34. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostics.ts +685 -183
  35. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-state.ts +2 -2
  36. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-sync.ts +60 -60
  37. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-request-enrollment.ts +75 -0
  38. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-semantic-input-barrier.ts +443 -0
  39. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-semantic-input-errors.ts +30 -0
  40. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-semantic-input-file.ts +15 -0
  41. package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +290 -37
  42. package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +72 -16
  43. package/node_modules/@mrclrchtr/supi-lsp/src/config/capabilities.ts +1 -0
  44. package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-scope.ts +30 -4
  45. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/evidence.ts +1 -1
  46. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/workspace-sentinels.ts +4 -32
  47. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/workspace-sources.ts +108 -0
  48. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +10 -4
  49. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-process-crash-report.ts +50 -1
  50. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +11 -7
  51. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +770 -56
  52. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostic-surface.ts +3 -1
  53. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +37 -4
  54. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +48 -12
  55. package/node_modules/@mrclrchtr/supi-lsp/src/session/workspace-lsp-runtime.ts +50 -4
  56. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  57. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +8 -1
  58. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/README.md +8 -0
  59. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  60. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +10 -3
  61. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
  62. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/llm.ts +141 -16
  63. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -3
  64. package/node_modules/@mrclrchtr/supi-tree-sitter/src/provider/tree-sitter-provider.ts +2 -0
  65. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callee-display-name.ts +165 -0
  66. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callees.ts +67 -9
  67. package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +2 -0
  68. package/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +19 -18
  69. package/node_modules/yaml/browser/dist/nodes/Alias.js +25 -23
  70. package/node_modules/yaml/dist/compose/resolve-flow-scalar.js +19 -18
  71. package/node_modules/yaml/dist/nodes/Alias.js +25 -23
  72. package/node_modules/yaml/package.json +1 -1
  73. package/package.json +5 -5
  74. package/src/analysis/health/diagnostics.ts +1 -2
  75. package/src/analysis/health/file-scope.ts +4 -1
  76. package/src/analysis/health/server-status.ts +1 -1
  77. package/src/analysis/relations/callees.ts +17 -8
  78. package/src/analysis/relations/implementations.ts +1 -2
  79. package/src/analysis/relations/references.ts +8 -4
  80. package/src/analysis/relations/types.ts +6 -30
  81. package/src/analysis/target/anchored.ts +9 -1
  82. package/src/analysis/target/symbol.ts +29 -11
  83. package/src/analysis/target/types.ts +14 -6
  84. package/src/api.ts +2 -0
  85. package/src/session/graph/collect.ts +18 -3
  86. package/src/session/health-refresh.ts +27 -11
  87. package/src/session/health-types.ts +21 -4
  88. package/src/session/health-workflow.ts +28 -22
  89. package/src/session/orientation-types.ts +5 -6
  90. package/src/session/orientation-workflow.ts +10 -2
  91. package/src/session/session.ts +12 -5
  92. package/src/session/target-workflow.ts +9 -6
  93. package/src/substrate/lsp/maintenance.ts +53 -47
  94. package/src/substrate/lsp/source-tracking.ts +252 -0
  95. package/src/tool/code_find/guidance.ts +3 -3
  96. package/src/tool/code_find/spec.ts +5 -4
  97. package/src/tool/code_find/tui.ts +7 -1
  98. package/src/tool/code_graph/details.ts +32 -0
  99. package/src/tool/code_graph/display.ts +54 -0
  100. package/src/tool/code_graph/format.ts +60 -0
  101. package/src/tool/code_graph/guidance.ts +2 -2
  102. package/src/tool/code_graph/markdown.ts +76 -198
  103. package/src/tool/code_graph/read-next.ts +65 -0
  104. package/src/tool/code_graph/result.ts +118 -83
  105. package/src/tool/code_graph/tui.ts +216 -54
  106. package/src/tool/code_health/guidance.ts +3 -5
  107. package/src/tool/code_health/markdown.ts +90 -57
  108. package/src/tool/code_health/refresh-outcome.ts +51 -0
  109. package/src/tool/code_health/refresh-status.ts +130 -43
  110. package/src/tool/code_health/scope.ts +78 -0
  111. package/src/tool/code_health/spec.ts +1 -1
  112. package/src/tool/code_health/tui.ts +29 -38
  113. package/src/tool/code_inspect/guidance.ts +5 -3
  114. package/src/tool/code_inspect/tui.ts +7 -1
  115. package/src/tool/code_orientation/guidance.ts +3 -4
  116. package/src/tool/code_orientation/result.ts +31 -8
  117. package/src/tool/code_orientation/tui.ts +26 -5
  118. package/src/tool/code_refactor_apply/guidance.ts +1 -1
  119. package/src/tool/code_refactor_apply/tui.ts +7 -3
  120. package/src/tool/code_refactor_plan/guidance.ts +3 -5
  121. package/src/tool/code_refactor_plan/tui.ts +7 -1
  122. package/src/tool/code_resolve/guidance.ts +3 -5
  123. package/src/tool/code_resolve/result.ts +13 -10
  124. package/src/tool/code_resolve/tui.ts +18 -1
  125. package/src/tool/guidance.ts +11 -9
  126. package/src/tool/infra/truncate.ts +39 -13
  127. package/src/tool/result/candidate-row.ts +27 -0
  128. package/src/tool/result/errors.ts +21 -6
  129. package/src/tool/result/types.ts +7 -3
  130. package/src/tool/schemas.ts +5 -0
  131. package/src/types/execution.ts +2 -0
  132. package/src/types/index.ts +2 -0
  133. package/src/ui/tui/candidate-legacy-body.ts +69 -0
  134. package/src/ui/tui/candidate-selection.ts +366 -0
  135. package/src/ui/tui/common.ts +26 -31
  136. package/src/ui/tui/execution-error.ts +92 -0
@@ -1,222 +1,100 @@
1
1
  /** Markdown adapter for assembled code_graph evidence. */
2
2
 
3
- import {
4
- type EvidenceList,
5
- type EvidenceListMetadata,
6
- renderEvidenceListMetadataDisclosure,
7
- } from "../../analysis/evidence.ts";
8
3
  import { renderReadNextSection } from "../../analysis/read-next.ts";
9
- import {
10
- compactLineRanges,
11
- formatAssembledReferenceList,
12
- } from "../../analysis/references/semantic-refs.ts";
13
- import type {
14
- CallEntry,
15
- CalleeScope,
16
- ImplementationEntry,
17
- ReferenceEntry,
18
- } from "../../analysis/relations/types.ts";
4
+ import type { CallEntry, CalleeScope } from "../../analysis/relations/types.ts";
19
5
  import { toDisplayPath } from "../../analysis/search/paths.ts";
20
- import type { GraphResultAssembly } from "./result.ts";
21
-
22
- /** Render an assembled graph result. */
6
+ import { assembledReadNext } from "../result/assembly.ts";
7
+ import {
8
+ compactGraphLabel,
9
+ formatGraphEvidence,
10
+ formatGraphRange,
11
+ graphCodeSpan,
12
+ } from "./format.ts";
13
+ import type { AssembledGraphSection, GraphResultAssembly } from "./result.ts";
14
+
15
+ /** Render one target and one summary per requested relation. */
23
16
  export function renderGraphResult(assembly: GraphResultAssembly): string {
24
17
  const lines = [
25
- `# Graph of \`${assembly.displayName}\``,
26
- "",
27
- `_File: \`${assembly.resolvedDisplayFile}\`_`,
18
+ `Target: ${graphCodeSpan(assembly.displayName)} ${graphCodeSpan(assembly.resolvedDisplayFile)}`,
28
19
  "",
29
20
  ];
30
-
31
- const available = assembly.sections.filter((section) => section.kind === "ok");
32
- if (available.length > 0) {
33
- lines.push(
34
- available
35
- .map(
36
- (section) =>
37
- `**${section.rel}**: ${sectionCount(section)} result${sectionCount(section) === 1 ? "" : "s"}`,
38
- )
39
- .join(" | "),
40
- "",
41
- );
42
- }
43
-
44
- const unavailable = assembly.sections.filter((section) => section.kind === "unavailable");
45
- if (unavailable.length > 0) {
46
- lines.push(
47
- `_Unavailable: ${unavailable.map((section) => `\`${section.rel}\``).join(", ")}_`,
48
- "",
49
- );
50
- }
51
-
52
- for (const section of assembly.sections) {
53
- if (section.kind === "unavailable") {
54
- lines.push(`**${section.rel}**: ${section.message}`, "");
55
- continue;
56
- }
57
-
58
- switch (section.rel) {
59
- case "references": {
60
- const rendered = renderReferencesResult({
61
- symbolName: assembly.displayName,
62
- references: section.evidence,
63
- externalCount: section.data.externalCount,
64
- confidence: section.data.confidence,
65
- cwd: assembly.cwd,
66
- });
67
- lines.push(rendered.content, "");
68
- break;
69
- }
70
- case "callees": {
71
- const rendered = renderCallsResult(
72
- section.data.enclosingScope,
73
- section.evidence,
74
- assembly.resolvedDisplayFile,
75
- section.data.depth,
76
- );
77
- lines.push(rendered.content, "");
78
- break;
79
- }
80
- case "implements": {
81
- const rendered = renderImplementationsResult({
82
- implementations: section.evidence,
83
- externalCount: section.data.externalCount,
84
- cwd: assembly.cwd,
85
- targetName: assembly.displayName,
86
- });
87
- lines.push(rendered.content, "");
88
- break;
89
- }
90
- }
91
- }
92
-
93
- lines.push(...renderReadNextSection(available.flatMap((section) => section.readNext)));
21
+ for (const section of assembly.sections) lines.push(...renderSection(section, assembly), "");
22
+ lines.push(...renderReadNextSection(assembledReadNext(assembly.assembled)));
94
23
  return lines.join("\n");
95
24
  }
96
25
 
97
- function sectionCount(
98
- section: Extract<GraphResultAssembly["sections"][number], { kind: "ok" }>,
99
- ): number {
100
- return section.evidence.metadata.totalCount ?? section.evidence.metadata.shownCount;
101
- }
102
-
103
- function renderReferencesResult(options: {
104
- symbolName: string;
105
- references: EvidenceList<ReferenceEntry>;
106
- externalCount: number;
107
- confidence: string;
108
- cwd: string;
109
- }): { content: string; evidenceList: EvidenceListMetadata | null } {
110
- const total = options.references.metadata.totalCount ?? options.references.metadata.shownCount;
111
- const lines = [
112
- `# References of \`${options.symbolName}\``,
113
- "",
114
- `**${total} reference${total === 1 ? "" : "s"}** (${options.confidence})`,
115
- ];
116
- if (options.externalCount > 0) {
117
- lines.push(
118
- `_+${options.externalCount} external reference${options.externalCount === 1 ? "" : "s"}_`,
119
- );
120
- }
121
- const invalidDisclosure = renderInvalidProviderLocations(options.references.metadata);
122
- if (invalidDisclosure) lines.push(invalidDisclosure);
123
- lines.push("");
124
-
125
- const displayEvidence = {
126
- key: options.references.key,
127
- items: options.references.items.map((reference) => ({
128
- file: toDisplayPath(options.cwd, reference.file),
129
- line: reference.line,
130
- })),
131
- metadata: options.references.metadata,
132
- };
133
- const evidenceList = formatAssembledReferenceList(lines, displayEvidence);
134
- return { content: lines.join("\n"), evidenceList };
135
- }
136
-
137
- function renderCallsResult(
138
- enclosingScope: CalleeScope,
139
- calls: EvidenceList<CallEntry>,
140
- relPath: string,
141
- depth: "direct" | "deep" = "direct",
142
- ): { content: string; evidenceList: EvidenceListMetadata } {
143
- const lines: string[] = [];
144
- const depthLabel = depth === "deep" ? "Deep structural calls" : "Direct structural calls";
145
- const depthNote =
146
- depth === "deep"
147
- ? "_Deep: includes calls from nested function/method/callback scopes within the enclosing scope._"
148
- : "_Structural only: call expressions are reported by source shape, not symbol identity; calls inside nested function/method/callback scopes are excluded from this enclosing scope._";
149
- const total = calls.metadata.totalCount ?? calls.metadata.shownCount;
150
-
151
- lines.push(`# ${depthLabel} from \`${enclosingScope.name}\``, "");
26
+ function renderSection(section: AssembledGraphSection, assembly: GraphResultAssembly): string[] {
27
+ const source = section.rel === "callees" ? "structural" : "semantic";
28
+ const depth = section.kind === "ok" && section.rel === "callees" ? `, ${section.data.depth}` : "";
29
+ const lines = [`## ${section.rel} (${source}${depth})`];
30
+ if (section.kind === "unavailable") return [...lines, `Unavailable — ${section.message}`];
152
31
  lines.push(
153
- `**${total} ${depth === "deep" ? "" : "direct "}structural call${total === 1 ? "" : "s"}** from enclosing scope \`${enclosingScope.name}\` (${formatScopeRange(enclosingScope)}) in \`${relPath}\``,
154
- "",
155
- depthNote,
32
+ formatGraphEvidence(
33
+ section.evidence.metadata,
34
+ section.rel === "callees" ? "call sites" : "locations",
35
+ section.rel === "callees" ? 0 : section.data.externalCount,
36
+ ),
156
37
  "",
157
38
  );
158
-
159
- for (const call of calls.items) lines.push(`- \`${call.name}\` (L${call.line})`);
160
- const disclosure = renderEvidenceListMetadataDisclosure(calls.metadata);
161
- if (disclosure) lines.push(disclosure);
162
- return { content: lines.join("\n"), evidenceList: calls.metadata };
163
- }
164
-
165
- function renderImplementationsResult(options: {
166
- implementations: EvidenceList<ImplementationEntry>;
167
- externalCount: number;
168
- cwd: string;
169
- targetName?: string;
170
- }): { content: string; evidenceList: EvidenceListMetadata } {
171
- const lines: string[] = [
172
- options.targetName
173
- ? `# Implementations of \`${options.targetName}\` (semantic)`
174
- : "# Implementations (semantic)",
39
+ if (section.rel !== "callees") return [...lines, ...formatLocations(section, assembly.cwd)];
40
+ return [
41
+ ...lines,
42
+ formatScope(section.data.enclosingScope, assembly.displayName),
43
+ section.data.depth === "direct"
44
+ ? "Nested scopes excluded; calls are source expressions, not resolved symbols."
45
+ : "Nested scopes included; calls are source expressions, not resolved symbols.",
175
46
  "",
47
+ ...formatCalls(section.evidence.items, assembly),
176
48
  ];
177
- const total =
178
- options.implementations.metadata.totalCount ?? options.implementations.metadata.shownCount;
179
- if (total > 0) {
180
- lines.push(`**${total} implementation${total === 1 ? "" : "s"}** in the project`, "");
181
- for (const [file, locations] of groupByFile(options.implementations.items, options.cwd)) {
182
- lines.push(`### ${file}`, `- ${compactLineRanges(locations)}`, "");
183
- }
184
- const disclosure = renderEvidenceListMetadataDisclosure(options.implementations.metadata);
185
- if (disclosure) lines.push(disclosure);
186
- }
187
-
188
- if (options.externalCount > 0) {
189
- lines.push(
190
- `_+${options.externalCount} external location${options.externalCount === 1 ? "" : "s"} (outside this project)_`,
191
- "",
192
- );
193
- }
194
- const invalidDisclosure = renderInvalidProviderLocations(options.implementations.metadata);
195
- if (invalidDisclosure) lines.push(invalidDisclosure, "");
196
-
197
- return { content: lines.join("\n"), evidenceList: options.implementations.metadata };
198
49
  }
199
50
 
200
- function formatScopeRange(scope: CalleeScope): string {
201
- if (scope.startLine === scope.endLine) return `L${scope.startLine}`;
202
- return `L${scope.startLine}–L${scope.endLine}`;
51
+ function formatScope(scope: CalleeScope, targetName: string): string {
52
+ const name = scope.name === targetName ? "" : `${graphCodeSpan(scope.name)}`;
53
+ return `Scope: ${name}${formatGraphRange(scope)}`;
203
54
  }
204
55
 
205
- function groupByFile(impls: readonly ImplementationEntry[], cwd: string): Map<string, number[]> {
206
- const byFile = new Map<string, number[]>();
207
- for (const impl of impls) {
208
- const displayPath = toDisplayPath(cwd, impl.file);
209
- const group = byFile.get(displayPath) ?? [];
210
- group.push(impl.line);
211
- byFile.set(displayPath, group);
56
+ function formatLocations(
57
+ section: Extract<AssembledGraphSection, { kind: "ok"; rel: "references" | "implements" }>,
58
+ cwd: string,
59
+ ): string[] {
60
+ const groups = new Map<string, string[]>();
61
+ for (const location of section.evidence.items) {
62
+ const file = toDisplayPath(cwd, location.file);
63
+ const sites = groups.get(file) ?? [];
64
+ sites.push(`L${location.line}:${location.character}`);
65
+ groups.set(file, sites);
212
66
  }
213
- return byFile;
67
+ return [...groups].map(([file, sites]) => `- ${graphCodeSpan(file)}: ${sites.join(", ")}`);
214
68
  }
215
69
 
216
- /** Render an explicit disclosure for unusable semantic-provider locations. */
217
- function renderInvalidProviderLocations(metadata: EvidenceListMetadata): string | null {
218
- const count = metadata.invalidLocationCount ?? 0;
219
- if (count <= 0 || metadata.partialReason === null) return null;
220
- const noun = count === 1 ? "location" : "locations";
221
- return `_${count} invalid provider ${noun} omitted (${metadata.partialReason})_`;
70
+ /** Group by the full expression, never by a shortened display label. */
71
+ function formatCalls(calls: readonly CallEntry[], assembly: GraphResultAssembly): string[] {
72
+ const groups = new Map<string, { name: string; file: string; sites: string[] }>();
73
+ for (const call of calls) {
74
+ const file = toDisplayPath(assembly.cwd, call.file);
75
+ const key = JSON.stringify([file, call.name]);
76
+ const group = groups.get(key) ?? { name: call.displayName ?? call.name, file, sites: [] };
77
+ group.sites.push(`L${call.line}:${call.character}`);
78
+ groups.set(key, group);
79
+ }
80
+ const previews = [...groups.values()].map((group) => ({
81
+ ...group,
82
+ label: compactGraphLabel(group.name),
83
+ }));
84
+ const collisions = new Map<string, { count: number; seen: number }>();
85
+ for (const { file, label } of previews) {
86
+ const key = JSON.stringify([file, label]);
87
+ const count = (collisions.get(key)?.count ?? 0) + 1;
88
+ collisions.set(key, { count, seen: 0 });
89
+ }
90
+ return previews.map(({ label, file, sites }) => {
91
+ const collision = collisions.get(JSON.stringify([file, label]));
92
+ let qualifier = "";
93
+ if (collision && collision.count > 1) {
94
+ collision.seen++;
95
+ qualifier = ` (expression ${collision.seen}/${collision.count})`;
96
+ }
97
+ const prefix = file === assembly.resolvedDisplayFile ? "" : `${graphCodeSpan(file)}: `;
98
+ return `- ${graphCodeSpan(label)}${qualifier} — ${prefix}${sites.join(", ")}`;
99
+ });
222
100
  }
@@ -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 { createEvidenceList, type EvidenceList } from "../../analysis/evidence.ts";
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
- assembledNextQueries,
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 { SearchDetails, ToolDisplaySection } from "../result/types.ts";
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: SearchDetails;
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.flatMap((section) => (section.kind === "ok" ? section.readNext : [])),
87
+ readNext: graphReadNext(sections),
78
88
  candidateCount,
79
89
  confidence,
80
90
  provenance,
81
91
  });
82
92
 
83
- const displaySections = sections.map((section) => graphDisplaySection(section, input.cwd));
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
- confidence,
94
- scope: null,
95
- candidateCount,
96
- omittedCount: assembled.totals.omittedCount,
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 graphDisplaySection(section: AssembledGraphSection, cwd: string): ToolDisplaySection {
104
- if (section.kind === "unavailable") {
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
- ): string {
130
- if (relation === "callees") {
131
- const call = item as { name: string; file: string; line: number };
132
- return `${call.name} — ${toDisplayPath(cwd, call.file)}:L${call.line}`;
133
- }
134
-
135
- const location = item as { name: string | null; file: string; line: number; character: number };
136
- const name = location.name ? `${location.name} — ` : "";
137
- return `${name}${toDisplayPath(cwd, location.file)}:L${location.line}:${location.character}`;
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 = createEvidenceList({
185
- key: params.key,
186
- items: params.items,
187
- maxResults: params.maxResults,
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
- const lines = [
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: "search",
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
+ }