|
@@ -134,7 +134,7 @@ export interface ExploreOutputBudget {
|
|
|
maxCharsPerFile: number;
|
|
maxCharsPerFile: number;
|
|
|
/** Cluster gap threshold in lines — tighter clustering on small projects. */
|
|
/** Cluster gap threshold in lines — tighter clustering on small projects. */
|
|
|
gapThreshold: number;
|
|
gapThreshold: number;
|
|
|
- /** Max symbols listed in the per-file header (`#### path — sym(kind), ...`). */
|
|
|
|
|
|
|
+ /** Max symbols listed in the per-file header (``**`path`** — sym(kind), ...``). */
|
|
|
maxSymbolsInFileHeader: number;
|
|
maxSymbolsInFileHeader: number;
|
|
|
/** Max edges shown per relationship kind in the Relationships section. */
|
|
/** Max edges shown per relationship kind in the Relationships section. */
|
|
|
maxEdgesPerRelationshipKind: number;
|
|
maxEdgesPerRelationshipKind: number;
|
|
@@ -326,6 +326,23 @@ function numberSourceLines(slice: string, firstLineNumber: number): string {
|
|
|
return out.join('\n');
|
|
return out.join('\n');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Unique line-prefix for a per-file source section in codegraph_explore output.
|
|
|
|
|
+ * Issue #778: tool results dropped ATX headings (`####`, `##`, `###`) for bold
|
|
|
|
|
+ * labels so Markdown-rendering MCP clients (e.g. the Claude Code VSCode
|
|
|
|
|
+ * extension) stop blowing every header up to H1–H4. The path is bold + a code
|
|
|
|
|
+ * span so it still reads as a header, and the leading ``**` `` stays a UNIQUE,
|
|
|
|
|
+ * greppable marker — no other explore line begins with it — that the explore
|
|
|
|
|
+ * truncation boundary (`handleExplore`) and the offload chunker
|
|
|
|
|
+ * (`reasoning/reasoner.ts`) both key off to cut on whole file sections.
|
|
|
|
|
+ */
|
|
|
|
|
+const FILE_SECTION_PREFIX = '**`';
|
|
|
|
|
+function fileSectionHeader(filePath: string, suffix: string): string {
|
|
|
|
|
+ return suffix
|
|
|
|
|
+ ? `${FILE_SECTION_PREFIX}${filePath}\`** — ${suffix}`
|
|
|
|
|
+ : `${FILE_SECTION_PREFIX}${filePath}\`**`;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* Per-file staleness banner emitted at the top of a tool response when the
|
|
* Per-file staleness banner emitted at the top of a tool response when the
|
|
|
* file watcher has pending events for files referenced by the response.
|
|
* file watcher has pending events for files referenced by the response.
|
|
@@ -1350,7 +1367,7 @@ export class ToolHandler {
|
|
|
private definitionHeading(group: Node[]): string {
|
|
private definitionHeading(group: Node[]): string {
|
|
|
const head = group[0]!;
|
|
const head = group[0]!;
|
|
|
const line = head.startLine ? `:${head.startLine}` : '';
|
|
const line = head.startLine ? `:${head.startLine}` : '';
|
|
|
- return `### ${head.qualifiedName} (${head.kind}) — ${head.filePath}${line}`;
|
|
|
|
|
|
|
+ return `**${head.qualifiedName}** (${head.kind}) — ${head.filePath}${line}`;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -1408,7 +1425,7 @@ export class ToolHandler {
|
|
|
// agent never mistakes one app's callers for another's. Narrow with
|
|
// agent never mistakes one app's callers for another's. Narrow with
|
|
|
// `file` to focus a single definition.
|
|
// `file` to focus a single definition.
|
|
|
const lines: string[] = [
|
|
const lines: string[] = [
|
|
|
- `## Callers of ${symbol} — ${groups.length} distinct definitions (narrow with \`file\`)`,
|
|
|
|
|
|
|
+ `**Callers of ${symbol} — ${groups.length} distinct definitions (narrow with \`file\`)**`,
|
|
|
];
|
|
];
|
|
|
for (const group of groups) {
|
|
for (const group of groups) {
|
|
|
const { callers, labels } = collect(group);
|
|
const { callers, labels } = collect(group);
|
|
@@ -1478,7 +1495,7 @@ export class ToolHandler {
|
|
|
|
|
|
|
|
// Multiple DISTINCT definitions (#764): per-definition sections.
|
|
// Multiple DISTINCT definitions (#764): per-definition sections.
|
|
|
const lines: string[] = [
|
|
const lines: string[] = [
|
|
|
- `## Callees of ${symbol} — ${groups.length} distinct definitions (narrow with \`file\`)`,
|
|
|
|
|
|
|
+ `**Callees of ${symbol} — ${groups.length} distinct definitions (narrow with \`file\`)**`,
|
|
|
];
|
|
];
|
|
|
for (const group of groups) {
|
|
for (const group of groups) {
|
|
|
const { callees, labels } = collect(group);
|
|
const { callees, labels } = collect(group);
|
|
@@ -1547,7 +1564,7 @@ export class ToolHandler {
|
|
|
// merging unrelated same-named classes (one UserService per monorepo app)
|
|
// merging unrelated same-named classes (one UserService per monorepo app)
|
|
|
// overstated impact and confused agents. Narrow with `file`.
|
|
// overstated impact and confused agents. Narrow with `file`.
|
|
|
const sections: string[] = [
|
|
const sections: string[] = [
|
|
|
- `## Impact of ${symbol} — ${groups.length} distinct definitions (each with its own blast radius; narrow with \`file\`)`,
|
|
|
|
|
|
|
+ `**Impact of ${symbol} — ${groups.length} distinct definitions (each with its own blast radius; narrow with \`file\`)**`,
|
|
|
];
|
|
];
|
|
|
for (const group of groups) {
|
|
for (const group of groups) {
|
|
|
const head = group[0]!;
|
|
const head = group[0]!;
|
|
@@ -1765,7 +1782,7 @@ export class ToolHandler {
|
|
|
if (synthLines.length === 0 && !boundaries) return EMPTY;
|
|
if (synthLines.length === 0 && !boundaries) return EMPTY;
|
|
|
const out: string[] = [];
|
|
const out: string[] = [];
|
|
|
if (synthLines.length) out.push(
|
|
if (synthLines.length) out.push(
|
|
|
- '## Dynamic-dispatch links among your symbols',
|
|
|
|
|
|
|
+ '**Dynamic-dispatch links among your symbols**',
|
|
|
'(synthesized — the indirect hops grep/Read would reconstruct; the `@file:line` is the wiring site)',
|
|
'(synthesized — the indirect hops grep/Read would reconstruct; the `@file:line` is the wiring site)',
|
|
|
'', ...synthLines, '');
|
|
'', ...synthLines, '');
|
|
|
if (boundaries) out.push(boundaries);
|
|
if (boundaries) out.push(boundaries);
|
|
@@ -1880,7 +1897,7 @@ export class ToolHandler {
|
|
|
if (!hasMain && synthLines.length === 0 && !boundaryText && !polyText) return EMPTY;
|
|
if (!hasMain && synthLines.length === 0 && !boundaryText && !polyText) return EMPTY;
|
|
|
const out: string[] = [];
|
|
const out: string[] = [];
|
|
|
if (hasMain) {
|
|
if (hasMain) {
|
|
|
- out.push('## Flow (call path among the symbols you queried)', '');
|
|
|
|
|
|
|
+ out.push('**Flow (call path among the symbols you queried)**', '');
|
|
|
for (let i = 0; i < best!.length; i++) {
|
|
for (let i = 0; i < best!.length; i++) {
|
|
|
const step = best![i]!;
|
|
const step = best![i]!;
|
|
|
if (step.edge) { const sy = this.synthEdgeNote(step.edge); out.push(` ↓ ${sy ? sy.compact : step.edge.kind}`); }
|
|
if (step.edge) { const sy = this.synthEdgeNote(step.edge); out.push(` ↓ ${sy ? sy.compact : step.edge.kind}`); }
|
|
@@ -1890,7 +1907,7 @@ export class ToolHandler {
|
|
|
}
|
|
}
|
|
|
if (synthLines.length) {
|
|
if (synthLines.length) {
|
|
|
out.push(
|
|
out.push(
|
|
|
- '## Dynamic-dispatch links among your symbols',
|
|
|
|
|
|
|
+ '**Dynamic-dispatch links among your symbols**',
|
|
|
'(synthesized — the indirect hops grep/Read would reconstruct; the `@file:line` is the wiring site)',
|
|
'(synthesized — the indirect hops grep/Read would reconstruct; the `@file:line` is the wiring site)',
|
|
|
'',
|
|
'',
|
|
|
...synthLines,
|
|
...synthLines,
|
|
@@ -1958,7 +1975,7 @@ export class ToolHandler {
|
|
|
}
|
|
}
|
|
|
if (notes.length === 0) return '';
|
|
if (notes.length === 0) return '';
|
|
|
return [
|
|
return [
|
|
|
- '## Dynamic boundaries (the static path ends at runtime dispatch)',
|
|
|
|
|
|
|
+ '**Dynamic boundaries (the static path ends at runtime dispatch)**',
|
|
|
'',
|
|
'',
|
|
|
...notes,
|
|
...notes,
|
|
|
'',
|
|
'',
|
|
@@ -2045,7 +2062,7 @@ export class ToolHandler {
|
|
|
}
|
|
}
|
|
|
if (notes.length === 0) return '';
|
|
if (notes.length === 0) return '';
|
|
|
return [
|
|
return [
|
|
|
- '## Interface dispatch (a named method has many implementations)',
|
|
|
|
|
|
|
+ '**Interface dispatch (a named method has many implementations)**',
|
|
|
'',
|
|
'',
|
|
|
...notes,
|
|
...notes,
|
|
|
'',
|
|
'',
|
|
@@ -2174,7 +2191,7 @@ export class ToolHandler {
|
|
|
if (entries.length === 0) return '';
|
|
if (entries.length === 0) return '';
|
|
|
|
|
|
|
|
return [
|
|
return [
|
|
|
- '### Blast radius — what depends on these (update/verify before editing)',
|
|
|
|
|
|
|
+ '**Blast radius — what depends on these (update/verify before editing)**',
|
|
|
'',
|
|
'',
|
|
|
...entries,
|
|
...entries,
|
|
|
'',
|
|
'',
|
|
@@ -2643,7 +2660,7 @@ export class ToolHandler {
|
|
|
|
|
|
|
|
// Step 3: Build relationship map
|
|
// Step 3: Build relationship map
|
|
|
const lines: string[] = [
|
|
const lines: string[] = [
|
|
|
- `## Exploration: ${query}`,
|
|
|
|
|
|
|
+ `**Exploration: ${query}**`,
|
|
|
'',
|
|
'',
|
|
|
`Found ${subgraph.nodes.size} symbols across ${fileGroups.size} files.`,
|
|
`Found ${subgraph.nodes.size} symbols across ${fileGroups.size} files.`,
|
|
|
'',
|
|
'',
|
|
@@ -2661,7 +2678,7 @@ export class ToolHandler {
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
if (budget.includeRelationships && significantEdges.length > 0) {
|
|
if (budget.includeRelationships && significantEdges.length > 0) {
|
|
|
- lines.push('### Relationships');
|
|
|
|
|
|
|
+ lines.push('**Relationships**');
|
|
|
lines.push('');
|
|
lines.push('');
|
|
|
|
|
|
|
|
// Group edges by kind for readability
|
|
// Group edges by kind for readability
|
|
@@ -2748,7 +2765,7 @@ export class ToolHandler {
|
|
|
return false;
|
|
return false;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- lines.push('### Source Code');
|
|
|
|
|
|
|
+ lines.push('**Source Code**');
|
|
|
lines.push('');
|
|
lines.push('');
|
|
|
lines.push('> The code below is the **verbatim, current on-disk source** of these files — re-read from disk on this call and line-numbered, byte-for-byte identical to what the Read tool returns. It is NOT a summary, outline, or stale cache. Treat each block as a Read you have already performed: do not Read a file shown here.');
|
|
lines.push('> The code below is the **verbatim, current on-disk source** of these files — re-read from disk on this call and line-numbered, byte-for-byte identical to what the Read tool returns. It is NOT a summary, outline, or stale cache. Treat each block as a Read you have already performed: do not Read a file shown here.');
|
|
|
lines.push('');
|
|
lines.push('');
|
|
@@ -2892,7 +2909,7 @@ export class ToolHandler {
|
|
|
const tag = bodyIds.size > 0
|
|
const tag = bodyIds.size > 0
|
|
|
? 'focused (the methods you named in full, the rest as signatures — codegraph_explore a signature by name for its body; do NOT Read)'
|
|
? 'focused (the methods you named in full, the rest as signatures — codegraph_explore a signature by name for its body; do NOT Read)'
|
|
|
: 'skeleton (signatures only — codegraph_explore a name for its full body; do NOT Read)';
|
|
: 'skeleton (signatures only — codegraph_explore a name for its full body; do NOT Read)';
|
|
|
- lines.push(`#### ${filePath} — ${names} · ${tag}`, '', '```' + lang, skel.join('\n'), '```', '');
|
|
|
|
|
|
|
+ lines.push(fileSectionHeader(filePath, `${names} · ${tag}`), '', '```' + lang, skel.join('\n'), '```', '');
|
|
|
totalChars += skel.join('\n').length + 120;
|
|
totalChars += skel.join('\n').length + 120;
|
|
|
filesIncluded++;
|
|
filesIncluded++;
|
|
|
continue;
|
|
continue;
|
|
@@ -2933,7 +2950,7 @@ export class ToolHandler {
|
|
|
)];
|
|
)];
|
|
|
const headerNames = uniqSymbols.slice(0, budget.maxSymbolsInFileHeader);
|
|
const headerNames = uniqSymbols.slice(0, budget.maxSymbolsInFileHeader);
|
|
|
const omitted = uniqSymbols.length - headerNames.length;
|
|
const omitted = uniqSymbols.length - headerNames.length;
|
|
|
- const wholeHeader = `#### ${filePath} — ${omitted > 0 ? `${headerNames.join(', ')}, +${omitted} more` : headerNames.join(', ')}`;
|
|
|
|
|
|
|
+ const wholeHeader = fileSectionHeader(filePath, omitted > 0 ? `${headerNames.join(', ')}, +${omitted} more` : headerNames.join(', '));
|
|
|
|
|
|
|
|
if (!fileNecessary && totalChars + wholeSection.length + 200 > budget.maxOutputChars) {
|
|
if (!fileNecessary && totalChars + wholeSection.length + 200 > budget.maxOutputChars) {
|
|
|
// Don't slice a whole file mid-method: an incidental file that doesn't
|
|
// Don't slice a whole file mid-method: an incidental file that doesn't
|
|
@@ -3200,7 +3217,7 @@ export class ToolHandler {
|
|
|
const headerSuffix = omittedCount > 0
|
|
const headerSuffix = omittedCount > 0
|
|
|
? `${headerSymbols.join(', ')}, +${omittedCount} more`
|
|
? `${headerSymbols.join(', ')}, +${omittedCount} more`
|
|
|
: headerSymbols.join(', ');
|
|
: headerSymbols.join(', ');
|
|
|
- const fileHeader = `#### ${filePath} — ${headerSuffix}`;
|
|
|
|
|
|
|
+ const fileHeader = fileSectionHeader(filePath, headerSuffix);
|
|
|
|
|
|
|
|
// The total cap bounds INCIDENTAL files only. A file that DEFINES a symbol
|
|
// The total cap bounds INCIDENTAL files only. A file that DEFINES a symbol
|
|
|
// the agent named (or that's on the flow spine) renders even when the
|
|
// the agent named (or that's on the flow spine) renders even when the
|
|
@@ -3241,7 +3258,7 @@ export class ToolHandler {
|
|
|
.sort((a, b) => b[1].score - a[1].score);
|
|
.sort((a, b) => b[1].score - a[1].score);
|
|
|
const remainingFiles = [...remainingRelevant, ...peripheralFiles];
|
|
const remainingFiles = [...remainingRelevant, ...peripheralFiles];
|
|
|
if (remainingFiles.length > 0) {
|
|
if (remainingFiles.length > 0) {
|
|
|
- lines.push('### Not shown above — explore these names for their source');
|
|
|
|
|
|
|
+ lines.push('**Not shown above — explore these names for their source**');
|
|
|
lines.push('');
|
|
lines.push('');
|
|
|
for (const [filePath, group] of remainingFiles.slice(0, 10)) {
|
|
for (const [filePath, group] of remainingFiles.slice(0, 10)) {
|
|
|
const symbols = group.nodes.map(n => `${n.name}:${n.startLine}`).join(', ');
|
|
const symbols = group.nodes.map(n => `${n.name}:${n.startLine}`).join(', ');
|
|
@@ -3290,13 +3307,13 @@ export class ToolHandler {
|
|
|
|
|
|
|
|
const hardCeiling = Math.min(Math.round(budget.maxOutputChars * 1.5), 25000);
|
|
const hardCeiling = Math.min(Math.round(budget.maxOutputChars * 1.5), 25000);
|
|
|
if (output.length > hardCeiling) {
|
|
if (output.length > hardCeiling) {
|
|
|
- // Cut at a FILE-SECTION boundary (the last `#### ` header before the
|
|
|
|
|
|
|
+ // Cut at a FILE-SECTION boundary (the last ``**` `` file header before the
|
|
|
// ceiling) so we drop whole trailing file-sections rather than slicing
|
|
// ceiling) so we drop whole trailing file-sections rather than slicing
|
|
|
// through a method body — a half-rendered method just forces the Read this
|
|
// through a method body — a half-rendered method just forces the Read this
|
|
|
// tool exists to prevent. Fall back to a line boundary only if no section
|
|
// tool exists to prevent. Fall back to a line boundary only if no section
|
|
|
// header sits in the back half (degenerate single-giant-section case).
|
|
// header sits in the back half (degenerate single-giant-section case).
|
|
|
const cut = output.slice(0, hardCeiling);
|
|
const cut = output.slice(0, hardCeiling);
|
|
|
- const lastSection = cut.lastIndexOf('\n#### ');
|
|
|
|
|
|
|
+ const lastSection = cut.lastIndexOf('\n' + FILE_SECTION_PREFIX);
|
|
|
const boundary = lastSection > hardCeiling * 0.5 ? lastSection : cut.lastIndexOf('\n');
|
|
const boundary = lastSection > hardCeiling * 0.5 ? lastSection : cut.lastIndexOf('\n');
|
|
|
const safe = boundary > 0 ? cut.slice(0, boundary) : cut;
|
|
const safe = boundary > 0 ? cut.slice(0, boundary) : cut;
|
|
|
return this.textResult(safe + '\n\n... (output truncated to budget; the source above is complete and verbatim — treat it as already Read. For any area not covered, run another codegraph_explore with the specific names — do NOT Read these files.)');
|
|
return this.textResult(safe + '\n\n... (output truncated to budget; the source above is complete and verbatim — treat it as already Read. For any area not covered, run another codegraph_explore with the specific names — do NOT Read these files.)');
|
|
@@ -3411,7 +3428,7 @@ export class ToolHandler {
|
|
|
const shownList = listed.slice(0, LIST_CAP);
|
|
const shownList = listed.slice(0, LIST_CAP);
|
|
|
out.push(
|
|
out.push(
|
|
|
'',
|
|
'',
|
|
|
- '### Other definitions',
|
|
|
|
|
|
|
+ '**Other definitions**',
|
|
|
...shownList.map((n) => `- \`${n.name}\` (${n.kind}) — ${n.filePath}:${n.startLine}`),
|
|
...shownList.map((n) => `- \`${n.name}\` (${n.kind}) — ${n.filePath}:${n.startLine}`),
|
|
|
);
|
|
);
|
|
|
if (listed.length > LIST_CAP) out.push(`- … +${listed.length - LIST_CAP} more`);
|
|
if (listed.length > LIST_CAP) out.push(`- … +${listed.length - LIST_CAP} more`);
|
|
@@ -3493,7 +3510,7 @@ export class ToolHandler {
|
|
|
// symbolsOnly → the cheap structural overview, no source.
|
|
// symbolsOnly → the cheap structural overview, no source.
|
|
|
if (opts.symbolsOnly) {
|
|
if (opts.symbolsOnly) {
|
|
|
const out = [`**${filePath}** — ${nodes.length} symbol${nodes.length === 1 ? '' : 's'}, ${depSummary}`, ''];
|
|
const out = [`**${filePath}** — ${nodes.length} symbol${nodes.length === 1 ? '' : 's'}, ${depSummary}`, ''];
|
|
|
- if (nodes.length) out.push(...symbolMap('### Symbols'));
|
|
|
|
|
|
|
+ if (nodes.length) out.push(...symbolMap('**Symbols**'));
|
|
|
else out.push('_No indexed symbols in this file._');
|
|
else out.push('_No indexed symbols in this file._');
|
|
|
out.push('', '> Drop `symbolsOnly` (or pass `offset`/`limit`) to read the source, like Read.');
|
|
out.push('', '> Drop `symbolsOnly` (or pass `offset`/`limit`) to read the source, like Read.');
|
|
|
return this.textResult(this.truncateOutput(out.join('\n')));
|
|
return this.textResult(this.truncateOutput(out.join('\n')));
|
|
@@ -3503,7 +3520,7 @@ export class ToolHandler {
|
|
|
// line is `key: <secret>`. Summarize by key and point to a real Read.
|
|
// line is `key: <secret>`. Summarize by key and point to a real Read.
|
|
|
if (CONFIG_LEAF_LANGUAGES.has(resolved.language)) {
|
|
if (CONFIG_LEAF_LANGUAGES.has(resolved.language)) {
|
|
|
const out = [`**${filePath}** — configuration/data file, ${depSummary}`, ''];
|
|
const out = [`**${filePath}** — configuration/data file, ${depSummary}`, ''];
|
|
|
- if (nodes.length) out.push(...symbolMap('### Keys (values withheld for safety)'));
|
|
|
|
|
|
|
+ if (nodes.length) out.push(...symbolMap('**Keys (values withheld for safety)**'));
|
|
|
out.push('', '> Values may be secrets, so codegraph indexes keys only. Read the file directly if you need a value.');
|
|
out.push('', '> Values may be secrets, so codegraph indexes keys only. Read the file directly if you need a value.');
|
|
|
return this.textResult(this.truncateOutput(out.join('\n')));
|
|
return this.textResult(this.truncateOutput(out.join('\n')));
|
|
|
}
|
|
}
|
|
@@ -3517,7 +3534,7 @@ export class ToolHandler {
|
|
|
}
|
|
}
|
|
|
if (content === null) {
|
|
if (content === null) {
|
|
|
const out = [`**${filePath}** — could not read from disk (it may have moved since indexing). ${depSummary}`, ''];
|
|
const out = [`**${filePath}** — could not read from disk (it may have moved since indexing). ${depSummary}`, ''];
|
|
|
- if (nodes.length) out.push(...symbolMap('### Symbols'));
|
|
|
|
|
|
|
+ if (nodes.length) out.push(...symbolMap('**Symbols**'));
|
|
|
out.push('', `> Read \`${filePath}\` directly for its current content.`);
|
|
out.push('', `> Read \`${filePath}\` directly for its current content.`);
|
|
|
return this.textResult(this.truncateOutput(out.join('\n')));
|
|
return this.textResult(this.truncateOutput(out.join('\n')));
|
|
|
}
|
|
}
|
|
@@ -3614,7 +3631,7 @@ export class ToolHandler {
|
|
|
const callees = collect(cg.getCallees(node.id));
|
|
const callees = collect(cg.getCallees(node.id));
|
|
|
const callers = collect(cg.getCallers(node.id));
|
|
const callers = collect(cg.getCallers(node.id));
|
|
|
if (callees.length === 0 && callers.length === 0) return '';
|
|
if (callees.length === 0 && callers.length === 0) return '';
|
|
|
- const lines: string[] = ['', '### Trail — codegraph_node any of these to follow it (no Read needed)'];
|
|
|
|
|
|
|
+ const lines: string[] = ['', '**Trail — codegraph_node any of these to follow it (no Read needed)**'];
|
|
|
if (callees.length > 0) {
|
|
if (callees.length > 0) {
|
|
|
lines.push(`**Calls →** ${callees.slice(0, TRAIL_CAP).map(fmt).join(', ')}${callees.length > TRAIL_CAP ? `, +${callees.length - TRAIL_CAP} more` : ''}`);
|
|
lines.push(`**Calls →** ${callees.slice(0, TRAIL_CAP).map(fmt).join(', ')}${callees.length > TRAIL_CAP ? `, +${callees.length - TRAIL_CAP} more` : ''}`);
|
|
|
}
|
|
}
|
|
@@ -3650,7 +3667,7 @@ export class ToolHandler {
|
|
|
const mismatch = this.worktreeMismatchFor(args.projectPath as string | undefined);
|
|
const mismatch = this.worktreeMismatchFor(args.projectPath as string | undefined);
|
|
|
|
|
|
|
|
const lines: string[] = [
|
|
const lines: string[] = [
|
|
|
- '## CodeGraph Status',
|
|
|
|
|
|
|
+ '**CodeGraph Status**',
|
|
|
'',
|
|
'',
|
|
|
];
|
|
];
|
|
|
if (mismatch) {
|
|
if (mismatch) {
|
|
@@ -3681,7 +3698,7 @@ export class ToolHandler {
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- lines.push('', '### Nodes by Kind:');
|
|
|
|
|
|
|
+ lines.push('', '**Nodes by Kind:**');
|
|
|
|
|
|
|
|
for (const [kind, count] of Object.entries(stats.nodesByKind)) {
|
|
for (const [kind, count] of Object.entries(stats.nodesByKind)) {
|
|
|
if ((count as number) > 0) {
|
|
if ((count as number) > 0) {
|
|
@@ -3689,7 +3706,7 @@ export class ToolHandler {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- lines.push('', '### Languages:');
|
|
|
|
|
|
|
+ lines.push('', '**Languages:**');
|
|
|
for (const [lang, count] of Object.entries(stats.filesByLanguage)) {
|
|
for (const [lang, count] of Object.entries(stats.filesByLanguage)) {
|
|
|
if ((count as number) > 0) {
|
|
if ((count as number) > 0) {
|
|
|
lines.push(`- ${lang}: ${count}`);
|
|
lines.push(`- ${lang}: ${count}`);
|
|
@@ -3703,7 +3720,7 @@ export class ToolHandler {
|
|
|
if (cg.isWatcherDegraded()) {
|
|
if (cg.isWatcherDegraded()) {
|
|
|
lines.push(
|
|
lines.push(
|
|
|
'',
|
|
'',
|
|
|
- '### Auto-sync disabled:',
|
|
|
|
|
|
|
+ '**Auto-sync disabled:**',
|
|
|
`- ${cg.getWatcherDegradedReason() ?? 'live file watching stopped'}`,
|
|
`- ${cg.getWatcherDegradedReason() ?? 'live file watching stopped'}`,
|
|
|
'- The index is frozen; Read files directly for current content.'
|
|
'- The index is frozen; Read files directly for current content.'
|
|
|
);
|
|
);
|
|
@@ -3715,7 +3732,7 @@ export class ToolHandler {
|
|
|
// banners on other tool calls.
|
|
// banners on other tool calls.
|
|
|
const pending = cg.getPendingFiles();
|
|
const pending = cg.getPendingFiles();
|
|
|
if (pending.length > 0) {
|
|
if (pending.length > 0) {
|
|
|
- lines.push('', '### Pending sync:');
|
|
|
|
|
|
|
+ lines.push('', '**Pending sync:**');
|
|
|
const now = Date.now();
|
|
const now = Date.now();
|
|
|
for (const p of pending) {
|
|
for (const p of pending) {
|
|
|
const ageMs = Math.max(0, now - p.lastSeenMs);
|
|
const ageMs = Math.max(0, now - p.lastSeenMs);
|
|
@@ -3806,7 +3823,7 @@ export class ToolHandler {
|
|
|
* Format files as a flat list
|
|
* Format files as a flat list
|
|
|
*/
|
|
*/
|
|
|
private formatFilesFlat(files: { path: string; language: string; nodeCount: number }[], includeMetadata: boolean): string {
|
|
private formatFilesFlat(files: { path: string; language: string; nodeCount: number }[], includeMetadata: boolean): string {
|
|
|
- const lines: string[] = [`## Files (${files.length})`, ''];
|
|
|
|
|
|
|
+ const lines: string[] = [`**Files (${files.length})**`, ''];
|
|
|
|
|
|
|
|
for (const file of files.sort((a, b) => a.path.localeCompare(b.path))) {
|
|
for (const file of files.sort((a, b) => a.path.localeCompare(b.path))) {
|
|
|
if (includeMetadata) {
|
|
if (includeMetadata) {
|
|
@@ -3831,13 +3848,13 @@ export class ToolHandler {
|
|
|
byLang.set(file.language, existing);
|
|
byLang.set(file.language, existing);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const lines: string[] = [`## Files by Language (${files.length} total)`, ''];
|
|
|
|
|
|
|
+ const lines: string[] = [`**Files by Language (${files.length} total)**`, ''];
|
|
|
|
|
|
|
|
// Sort languages by file count (descending)
|
|
// Sort languages by file count (descending)
|
|
|
const sortedLangs = [...byLang.entries()].sort((a, b) => b[1].length - a[1].length);
|
|
const sortedLangs = [...byLang.entries()].sort((a, b) => b[1].length - a[1].length);
|
|
|
|
|
|
|
|
for (const [lang, langFiles] of sortedLangs) {
|
|
for (const [lang, langFiles] of sortedLangs) {
|
|
|
- lines.push(`### ${lang} (${langFiles.length})`);
|
|
|
|
|
|
|
+ lines.push(`**${lang} (${langFiles.length})**`);
|
|
|
for (const file of langFiles.sort((a, b) => a.path.localeCompare(b.path))) {
|
|
for (const file of langFiles.sort((a, b) => a.path.localeCompare(b.path))) {
|
|
|
if (includeMetadata) {
|
|
if (includeMetadata) {
|
|
|
lines.push(`- ${file.path} (${file.nodeCount} symbols)`);
|
|
lines.push(`- ${file.path} (${file.nodeCount} symbols)`);
|
|
@@ -3889,7 +3906,7 @@ export class ToolHandler {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Render tree
|
|
// Render tree
|
|
|
- const lines: string[] = [`## Project Structure (${files.length} files)`, ''];
|
|
|
|
|
|
|
+ const lines: string[] = [`**Project Structure (${files.length} files)**`, ''];
|
|
|
|
|
|
|
|
const renderNode = (node: TreeNode, prefix: string, isLast: boolean, depth: number): void => {
|
|
const renderNode = (node: TreeNode, prefix: string, isLast: boolean, depth: number): void => {
|
|
|
if (maxDepth !== undefined && depth > maxDepth) return;
|
|
if (maxDepth !== undefined && depth > maxDepth) return;
|
|
@@ -4102,13 +4119,13 @@ export class ToolHandler {
|
|
|
// =========================================================================
|
|
// =========================================================================
|
|
|
|
|
|
|
|
private formatSearchResults(results: SearchResult[]): string {
|
|
private formatSearchResults(results: SearchResult[]): string {
|
|
|
- const lines: string[] = [`## Search Results (${results.length} found)`, ''];
|
|
|
|
|
|
|
+ const lines: string[] = [`**Search Results (${results.length} found)**`, ''];
|
|
|
|
|
|
|
|
for (const result of results) {
|
|
for (const result of results) {
|
|
|
const { node } = result;
|
|
const { node } = result;
|
|
|
const location = node.startLine ? `:${node.startLine}` : '';
|
|
const location = node.startLine ? `:${node.startLine}` : '';
|
|
|
// Compact format: one line per result with key info
|
|
// Compact format: one line per result with key info
|
|
|
- lines.push(`### ${node.name} (${node.kind})`);
|
|
|
|
|
|
|
+ lines.push(`**${node.name}** (${node.kind})`);
|
|
|
lines.push(`${node.filePath}${location}`);
|
|
lines.push(`${node.filePath}${location}`);
|
|
|
if (node.signature) lines.push(`\`${node.signature}\``);
|
|
if (node.signature) lines.push(`\`${node.signature}\``);
|
|
|
lines.push('');
|
|
lines.push('');
|
|
@@ -4118,7 +4135,7 @@ export class ToolHandler {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private formatNodeList(nodes: Node[], title: string, labels?: Map<string, string>): string {
|
|
private formatNodeList(nodes: Node[], title: string, labels?: Map<string, string>): string {
|
|
|
- const lines: string[] = [`## ${title} (${nodes.length} found)`, ''];
|
|
|
|
|
|
|
+ const lines: string[] = [`**${title} (${nodes.length} found)**`, ''];
|
|
|
|
|
|
|
|
for (const node of nodes) {
|
|
for (const node of nodes) {
|
|
|
const location = node.startLine ? `:${node.startLine}` : '';
|
|
const location = node.startLine ? `:${node.startLine}` : '';
|
|
@@ -4153,7 +4170,7 @@ export class ToolHandler {
|
|
|
|
|
|
|
|
// Compact format: just list affected symbols grouped by file
|
|
// Compact format: just list affected symbols grouped by file
|
|
|
const lines: string[] = [
|
|
const lines: string[] = [
|
|
|
- `## Impact: "${symbol}" affects ${nodeCount} symbols`,
|
|
|
|
|
|
|
+ `**Impact: "${symbol}" affects ${nodeCount} symbols**`,
|
|
|
'',
|
|
'',
|
|
|
];
|
|
];
|
|
|
|
|
|
|
@@ -4201,7 +4218,7 @@ export class ToolHandler {
|
|
|
private formatNodeDetails(node: Node, code: string | null, outline?: string | null): string {
|
|
private formatNodeDetails(node: Node, code: string | null, outline?: string | null): string {
|
|
|
const location = node.startLine ? `:${node.startLine}` : '';
|
|
const location = node.startLine ? `:${node.startLine}` : '';
|
|
|
const lines: string[] = [
|
|
const lines: string[] = [
|
|
|
- `## ${node.name} (${node.kind})`,
|
|
|
|
|
|
|
+ `**${node.name}** (${node.kind})`,
|
|
|
'',
|
|
'',
|
|
|
`**Location:** ${node.filePath}${location}`,
|
|
`**Location:** ${node.filePath}${location}`,
|
|
|
];
|
|
];
|