@mrclrchtr/supi-code-intelligence 6.4.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 (129) 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 +6 -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/manager/manager-diagnostics.ts +10 -4
  47. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-process-crash-report.ts +50 -1
  48. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +11 -7
  49. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +608 -45
  50. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostic-surface.ts +3 -1
  51. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +37 -4
  52. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +31 -11
  53. package/node_modules/@mrclrchtr/supi-lsp/src/session/workspace-lsp-runtime.ts +19 -3
  54. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  55. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +8 -1
  56. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/README.md +8 -0
  57. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  58. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +10 -3
  59. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
  60. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/llm.ts +141 -16
  61. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -3
  62. package/node_modules/@mrclrchtr/supi-tree-sitter/src/provider/tree-sitter-provider.ts +2 -0
  63. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callee-display-name.ts +165 -0
  64. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callees.ts +67 -9
  65. package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +2 -0
  66. package/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +19 -18
  67. package/node_modules/yaml/browser/dist/nodes/Alias.js +25 -23
  68. package/node_modules/yaml/dist/compose/resolve-flow-scalar.js +19 -18
  69. package/node_modules/yaml/dist/nodes/Alias.js +25 -23
  70. package/node_modules/yaml/package.json +1 -1
  71. package/package.json +5 -5
  72. package/src/analysis/health/diagnostics.ts +1 -2
  73. package/src/analysis/health/file-scope.ts +4 -1
  74. package/src/analysis/health/server-status.ts +1 -1
  75. package/src/analysis/relations/callees.ts +17 -8
  76. package/src/analysis/relations/implementations.ts +1 -2
  77. package/src/analysis/relations/references.ts +8 -4
  78. package/src/analysis/relations/types.ts +6 -30
  79. package/src/analysis/target/anchored.ts +9 -1
  80. package/src/analysis/target/symbol.ts +29 -11
  81. package/src/analysis/target/types.ts +14 -6
  82. package/src/api.ts +2 -0
  83. package/src/session/graph/collect.ts +18 -3
  84. package/src/session/health-refresh.ts +12 -1
  85. package/src/session/health-types.ts +18 -6
  86. package/src/session/health-workflow.ts +21 -19
  87. package/src/session/orientation-types.ts +5 -6
  88. package/src/session/orientation-workflow.ts +10 -2
  89. package/src/session/target-workflow.ts +9 -6
  90. package/src/substrate/lsp/source-tracking.ts +11 -6
  91. package/src/tool/code_find/guidance.ts +2 -2
  92. package/src/tool/code_find/spec.ts +5 -4
  93. package/src/tool/code_find/tui.ts +7 -1
  94. package/src/tool/code_graph/details.ts +32 -0
  95. package/src/tool/code_graph/display.ts +54 -0
  96. package/src/tool/code_graph/format.ts +60 -0
  97. package/src/tool/code_graph/guidance.ts +1 -1
  98. package/src/tool/code_graph/markdown.ts +76 -198
  99. package/src/tool/code_graph/read-next.ts +65 -0
  100. package/src/tool/code_graph/result.ts +118 -83
  101. package/src/tool/code_graph/tui.ts +216 -54
  102. package/src/tool/code_health/guidance.ts +2 -4
  103. package/src/tool/code_health/markdown.ts +75 -58
  104. package/src/tool/code_health/refresh-outcome.ts +41 -1
  105. package/src/tool/code_health/refresh-status.ts +114 -48
  106. package/src/tool/code_health/scope.ts +78 -0
  107. package/src/tool/code_health/spec.ts +1 -1
  108. package/src/tool/code_health/tui.ts +25 -38
  109. package/src/tool/code_inspect/tui.ts +7 -1
  110. package/src/tool/code_orientation/result.ts +31 -8
  111. package/src/tool/code_orientation/tui.ts +26 -5
  112. package/src/tool/code_refactor_apply/guidance.ts +1 -1
  113. package/src/tool/code_refactor_apply/tui.ts +7 -3
  114. package/src/tool/code_refactor_plan/guidance.ts +1 -1
  115. package/src/tool/code_refactor_plan/tui.ts +7 -1
  116. package/src/tool/code_resolve/guidance.ts +2 -4
  117. package/src/tool/code_resolve/result.ts +13 -10
  118. package/src/tool/code_resolve/tui.ts +18 -1
  119. package/src/tool/infra/truncate.ts +39 -13
  120. package/src/tool/result/candidate-row.ts +27 -0
  121. package/src/tool/result/errors.ts +21 -6
  122. package/src/tool/result/types.ts +7 -3
  123. package/src/tool/schemas.ts +5 -0
  124. package/src/types/execution.ts +2 -0
  125. package/src/types/index.ts +2 -0
  126. package/src/ui/tui/candidate-legacy-body.ts +69 -0
  127. package/src/ui/tui/candidate-selection.ts +366 -0
  128. package/src/ui/tui/common.ts +26 -31
  129. package/src/ui/tui/execution-error.ts +92 -0
@@ -5,6 +5,7 @@ import type { EvidenceListMetadata } from "../analysis/evidence.ts";
5
5
  import type { InstructionFilesMetadata } from "../analysis/instruction-files.ts";
6
6
  import type { CodeProvider } from "../analysis/provider.ts";
7
7
  import type { ReadNextItem } from "../analysis/read-next.ts";
8
+ import type { TargetCandidateCompleteness } from "../analysis/target/types.ts";
8
9
  import type { OrientationTargetInput, TargetSymbolKind } from "./target-input.ts";
9
10
  import type { AnchorKind, TargetStoreEntry } from "./target-store.ts";
10
11
 
@@ -118,16 +119,14 @@ export interface OrientationResultData {
118
119
 
119
120
  export type OrientationWorkflowOutcome =
120
121
  | { readonly kind: "completed"; readonly data: OrientationResultData }
121
- | {
122
+ | ({
122
123
  readonly kind: "disambiguation";
123
124
  readonly candidates: readonly OrientationCandidate[];
124
- readonly omittedCount: number;
125
- }
126
- | {
125
+ } & TargetCandidateCompleteness)
126
+ | ({
127
127
  readonly kind: "kind-mismatch";
128
128
  readonly requestedKind: TargetSymbolKind;
129
129
  readonly candidates: readonly OrientationCandidate[];
130
- readonly omittedCount: number;
131
- }
130
+ } & TargetCandidateCompleteness)
132
131
  | { readonly kind: "invalid-input"; readonly message: string }
133
132
  | { readonly kind: "unavailable"; readonly reason: string };
@@ -131,10 +131,18 @@ async function orientTarget(options: {
131
131
  ? {
132
132
  kind: "kind-mismatch",
133
133
  requestedKind: target.requestedKind,
134
- omittedCount: target.omittedCount,
135
134
  candidates,
135
+ totalCount: target.totalCount,
136
+ omittedCount: target.omittedCount,
137
+ partialReason: target.partialReason,
136
138
  }
137
- : { kind: "disambiguation", omittedCount: target.omittedCount, candidates };
139
+ : {
140
+ kind: "disambiguation",
141
+ candidates,
142
+ totalCount: target.totalCount,
143
+ omittedCount: target.omittedCount,
144
+ partialReason: target.partialReason,
145
+ };
138
146
  }
139
147
  if (target.kind !== "resolved") return target;
140
148
  throwIfAborted(control);
@@ -19,6 +19,7 @@ import { resolveSymbolTarget } from "../analysis/target/symbol.ts";
19
19
  import type {
20
20
  ResolvedTargetData,
21
21
  ResolvedTargetGroupData,
22
+ TargetCandidateCompleteness,
22
23
  TargetOutcome,
23
24
  } from "../analysis/target/types.ts";
24
25
  import type { CapabilityAdapter } from "./capability-adapter.ts";
@@ -59,17 +60,15 @@ export type TargetWorkflowOutcome =
59
60
  omittedCount: number;
60
61
  unknownNestingCount: number;
61
62
  }
62
- | {
63
+ | ({
63
64
  kind: "disambiguation";
64
65
  candidates: ReadonlyArray<TargetWorkflowCandidate>;
65
- omittedCount: number;
66
- }
67
- | {
66
+ } & TargetCandidateCompleteness)
67
+ | ({
68
68
  kind: "kind-mismatch";
69
69
  requestedKind: TargetSymbolKind;
70
70
  candidates: ReadonlyArray<TargetWorkflowCandidate>;
71
- omittedCount: number;
72
- }
71
+ } & TargetCandidateCompleteness)
73
72
  | { kind: "invalid-input"; message: string }
74
73
  | { kind: "unavailable"; reason: string };
75
74
 
@@ -318,12 +317,16 @@ function toWorkflowOutcome(
318
317
  kind: "kind-mismatch",
319
318
  requestedKind: outcome.requestedKind,
320
319
  candidates,
320
+ totalCount: outcome.totalCount,
321
321
  omittedCount: outcome.omittedCount,
322
+ partialReason: outcome.partialReason,
322
323
  }
323
324
  : {
324
325
  kind: "disambiguation",
325
326
  candidates,
327
+ totalCount: outcome.totalCount,
326
328
  omittedCount: outcome.omittedCount,
329
+ partialReason: outcome.partialReason,
327
330
  };
328
331
  }
329
332
 
@@ -26,7 +26,12 @@ export interface SourceTrackingReport {
26
26
  readonly observedFileCount: number;
27
27
  readonly discovered: readonly string[];
28
28
  readonly tracked: readonly string[];
29
- readonly unsupported: readonly string[];
29
+ /**
30
+ * Candidates skipped by automatic policy or tracking preconditions. This can
31
+ * include policy exclusions, unsupported automatic routes, and paths that
32
+ * are no longer regular; it is not a server capability result.
33
+ */
34
+ readonly skipped: readonly string[];
30
35
  readonly unavailable: readonly string[];
31
36
  readonly deferred: number;
32
37
  }
@@ -68,7 +73,7 @@ export async function trackCreatedSources(options: {
68
73
  inventory,
69
74
  discovered: [],
70
75
  tracked: [],
71
- unsupported: [],
76
+ skipped: [],
72
77
  unavailable: [],
73
78
  deferred: options.state.createdSourceQueue,
74
79
  control: options.control,
@@ -103,7 +108,7 @@ export async function trackCreatedSources(options: {
103
108
  };
104
109
 
105
110
  const tracked = pathsForOutcome(batch.outcomes, "tracked");
106
- const unsupported = pathsForOutcome(batch.outcomes, "unsupported");
111
+ const skipped = pathsForOutcome(batch.outcomes, "skipped");
107
112
  const unavailable = pathsForOutcome(batch.outcomes, "unavailable");
108
113
  return {
109
114
  state: nextState,
@@ -112,7 +117,7 @@ export async function trackCreatedSources(options: {
112
117
  inventory,
113
118
  discovered,
114
119
  tracked,
115
- unsupported,
120
+ skipped,
116
121
  unavailable,
117
122
  deferred: nextQueue,
118
123
  control: options.control,
@@ -188,7 +193,7 @@ function createReport(options: {
188
193
  inventory: WorkspaceSourceInventory;
189
194
  discovered: readonly string[];
190
195
  tracked: readonly string[];
191
- unsupported: readonly string[];
196
+ skipped: readonly string[];
192
197
  unavailable: readonly string[];
193
198
  deferred: readonly string[];
194
199
  control?: CodeRequestControl;
@@ -199,7 +204,7 @@ function createReport(options: {
199
204
  observedFileCount: options.inventory.observedFileCount,
200
205
  discovered: displayPaths(options.cwd, options.discovered, options.control),
201
206
  tracked: displayPaths(options.cwd, options.tracked, options.control),
202
- unsupported: displayPaths(options.cwd, options.unsupported, options.control),
207
+ skipped: displayPaths(options.cwd, options.skipped, options.control),
203
208
  unavailable: displayPaths(options.cwd, options.unavailable, options.control),
204
209
  deferred: options.deferred.length,
205
210
  };
@@ -1,8 +1,8 @@
1
1
  export const toolDescription =
2
- "Search code structure or workspace symbols for structural or semantic matches. It never silently falls back to another mode or to text search.";
2
+ "Search workspace symbols in semantic mode, or declaration, import, export, and call names in AST mode. Use PI grep for literal or regular-expression file-content search. The selected mode does not switch to another search method.";
3
3
 
4
4
  export const promptSnippet = "search code structure or workspace symbols";
5
5
 
6
6
  export const promptGuidelines = [
7
- "Use code_find for structural or semantic search, grep for text search, and code_graph for symbol relationships.",
7
+ "Use code_find for broad code-aware discovery, code_resolve for target identity, and code_graph for relationships.",
8
8
  ];
@@ -1,7 +1,7 @@
1
1
  import { StringEnum } from "@earendil-works/pi-ai";
2
2
  import { Type } from "typebox";
3
3
  import type { CodeIntelToolExecCtx } from "../../types/index.ts";
4
- import { FindScopeParam, MaxResultsParam, QueryParam } from "../schemas.ts";
4
+ import { FindQueryParam, FindScopeParam, MaxResultsParam } from "../schemas.ts";
5
5
  import { CODE_FIND_AST_KINDS } from "./ast-kinds.ts";
6
6
  import { executeFindTool } from "./execute.ts";
7
7
  import { CODE_FIND_MODES } from "./modes.ts";
@@ -16,14 +16,15 @@ export const codeFindSpec = {
16
16
  label: CODE_FIND_TOOL_LABEL,
17
17
  parameters: Type.Object(
18
18
  {
19
- query: QueryParam,
19
+ query: FindQueryParam,
20
20
  scope: Type.Optional(FindScopeParam),
21
21
  mode: StringEnum(CODE_FIND_MODES, {
22
- description: 'Required code-aware search mode. mode:"ast" requires `kind`.',
22
+ description:
23
+ "Required code-aware search mode. AST mode requires `kind`; semantic mode rejects `kind`.",
23
24
  }),
24
25
  kind: Type.Optional(
25
26
  StringEnum(CODE_FIND_AST_KINDS, {
26
- description: 'AST kind for mode:"ast".',
27
+ description: 'AST structure kind; required only for mode:"ast".',
27
28
  }),
28
29
  ),
29
30
  maxResults: Type.Optional(MaxResultsParam),
@@ -37,5 +37,11 @@ export function renderFindResult(
37
37
  theme: Theme,
38
38
  context: ToolRendererContext | undefined,
39
39
  ): ReturnType<typeof renderSimpleResult> {
40
- return renderSimpleResult(result, options, theme, "Searching…", context);
40
+ return renderSimpleResult(
41
+ result,
42
+ options,
43
+ theme,
44
+ { progress: "Searching…", failure: "code_find failed" },
45
+ context,
46
+ );
41
47
  }
@@ -0,0 +1,32 @@
1
+ import type { EvidenceListMetadata } from "../../analysis/evidence.ts";
2
+ import type { ReadNextItem } from "../../analysis/read-next.ts";
3
+ import type { CalleeScope } from "../../analysis/relations/types.ts";
4
+ import type { GraphRelationKind } from "../../session/graph-types.ts";
5
+
6
+ /** File heading for the next count rows in the relation's bounded display section. */
7
+ export interface GraphFileGroup {
8
+ file: string;
9
+ count: number;
10
+ }
11
+
12
+ /** Relation status and context kept for the human transcript. Counts live in evidenceLists. */
13
+ export interface GraphSectionDetails {
14
+ rel: GraphRelationKind;
15
+ source: "semantic" | "structural";
16
+ status: "complete" | "partial" | "unavailable";
17
+ message: string | null;
18
+ externalCount: number;
19
+ enclosingScope: CalleeScope | null;
20
+ depth: "direct" | "deep" | null;
21
+ /** Absent in older results whose display rows already contain full paths. */
22
+ fileGroups?: GraphFileGroup[];
23
+ }
24
+
25
+ /** Persisted graph facts. The TUI does not read or parse agent Markdown. */
26
+ export interface GraphDetails {
27
+ targetName: string;
28
+ targetFile: string;
29
+ sections: GraphSectionDetails[];
30
+ evidenceLists: EvidenceListMetadata[];
31
+ readNext: ReadNextItem[];
32
+ }
@@ -0,0 +1,54 @@
1
+ import type { CallEntry, ReferenceEntry } from "../../analysis/relations/types.ts";
2
+ import { toDisplayPath } from "../../analysis/search/paths.ts";
3
+ import { createToolDisplaySection } from "../result/display.ts";
4
+ import type { ToolDisplaySection } from "../result/types.ts";
5
+ import type { GraphFileGroup } from "./details.ts";
6
+ import { compactGraphLabel } from "./format.ts";
7
+ import type { AssembledGraphSection } from "./result.ts";
8
+
9
+ /** Bound sites before grouping them. Each file group partitions the display rows. */
10
+ export function graphDisplaySection(
11
+ section: AssembledGraphSection,
12
+ cwd: string,
13
+ ): {
14
+ display: ToolDisplaySection;
15
+ fileGroups: GraphFileGroup[];
16
+ } {
17
+ if (section.kind === "unavailable") {
18
+ return {
19
+ display: createToolDisplaySection({
20
+ key: `graph.${section.rel}`,
21
+ title: section.rel,
22
+ items: [],
23
+ format: () => "",
24
+ }),
25
+ fileGroups: [],
26
+ };
27
+ }
28
+ const items = section.evidence.items as readonly (CallEntry | ReferenceEntry)[];
29
+ const display = createToolDisplaySection({
30
+ key: `graph.${section.rel}`,
31
+ title: section.rel,
32
+ items,
33
+ totalCount: section.evidence.metadata.totalCount,
34
+ omittedCount: section.evidence.metadata.omittedCount,
35
+ partialReason: section.evidence.metadata.partialReason,
36
+ format: (item) => {
37
+ const site = `L${item.line}:${item.character}`;
38
+ if (section.rel !== "callees") return site;
39
+ const call = item as CallEntry;
40
+ return `${site} — ${compactGraphLabel(call.displayName ?? call.name)}`;
41
+ },
42
+ });
43
+ const groups = new Map<string, string[]>();
44
+ for (let index = 0; index < display.shownCount; index++) {
45
+ const file = toDisplayPath(cwd, items[index].file);
46
+ const rows = groups.get(file) ?? [];
47
+ rows.push(display.lines[index]);
48
+ groups.set(file, rows);
49
+ }
50
+ return {
51
+ display: { ...display, lines: [...groups.values()].flat() },
52
+ fileGroups: [...groups].map(([file, rows]) => ({ file, count: rows.length })),
53
+ };
54
+ }
@@ -0,0 +1,60 @@
1
+ import type { EvidenceListMetadata } from "../../analysis/evidence.ts";
2
+
3
+ /** Format graph counts and all known limits in one place. */
4
+ export function formatGraphEvidence(
5
+ metadata: Omit<EvidenceListMetadata, "partialReason"> & { partialReason: string | null },
6
+ noun: "locations" | "call sites" | "candidates",
7
+ externalCount = 0,
8
+ ): string {
9
+ const { shownCount, totalCount, omittedCount, partialReason, invalidLocationCount } = metadata;
10
+ const count = totalCount ?? shownCount;
11
+ const label = count === 1 ? singularGraphNoun(noun) : noun;
12
+ let text =
13
+ totalCount === null
14
+ ? `${shownCount} ${label} shown${omittedCount ? `; ${omittedCount} collected omitted` : ""}; more may exist — ${partialReason}`
15
+ : omittedCount
16
+ ? `${shownCount} of ${totalCount} ${label} shown; ${omittedCount} omitted`
17
+ : `${totalCount} ${label}`;
18
+ if (externalCount > 0)
19
+ text += `; ${externalCount} external location${externalCount === 1 ? "" : "s"}`;
20
+ if (invalidLocationCount) {
21
+ const invalidNoun = invalidLocationCount === 1 ? "location" : "locations";
22
+ text += `; ${invalidLocationCount} invalid provider ${invalidNoun} omitted (invalid-provider-location)`;
23
+ }
24
+ return text;
25
+ }
26
+
27
+ function singularGraphNoun(noun: "locations" | "call sites" | "candidates"): string {
28
+ switch (noun) {
29
+ case "locations":
30
+ return "location";
31
+ case "call sites":
32
+ return "call site";
33
+ case "candidates":
34
+ return "candidate";
35
+ }
36
+ }
37
+
38
+ /** Format a 1-based source-line range for either graph presentation. */
39
+ export function formatGraphRange(scope: { startLine: number; endLine: number }): string {
40
+ return scope.startLine === scope.endLine
41
+ ? `L${scope.startLine}`
42
+ : `L${scope.startLine}–L${scope.endLine}`;
43
+ }
44
+
45
+ /** Bound a source label while keeping both ends of a chained expression. */
46
+ export function compactGraphLabel(value: string, limit = 100): string {
47
+ const text = value.replace(/\s+/g, " ").trim();
48
+ if (text.length <= limit) return text;
49
+ const tail = Math.floor(limit / 3);
50
+ return `${text.slice(0, limit - tail - 1)}…${text.slice(-tail)}`;
51
+ }
52
+
53
+ /** Quote source text that can contain Markdown backticks. */
54
+ export function graphCodeSpan(text: string): string {
55
+ const runs = text.match(/`+/g) ?? [];
56
+ const delimiter = "`".repeat(Math.max(0, ...runs.map((run) => run.length)) + 1);
57
+ return text.startsWith("`") || text.endsWith("`")
58
+ ? `${delimiter} ${text} ${delimiter}`
59
+ : `${delimiter}${text}${delimiter}`;
60
+ }
@@ -1,5 +1,5 @@
1
1
  export const toolDescription =
2
- "Find symbol references and implementations, and list calls from a target's enclosing scope. Callee results match source shape, not symbol identity.";
2
+ "Find semantic references and implementations, or non-symbol-aware structural calls written inside a target's enclosing scope.";
3
3
 
4
4
  export const promptSnippet = "trace code relationships";
5
5
 
@@ -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
  }