Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 31 additions & 31 deletions src/mcp/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export interface ExploreOutputBudget {
maxCharsPerFile: number;
/** Cluster gap threshold in lines — tighter clustering on small projects. */
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;
/** Max edges shown per relationship kind in the Relationships section. */
maxEdgesPerRelationshipKind: number;
Expand Down Expand Up @@ -1673,7 +1673,7 @@ export class ToolHandler {
if (!hasMain && synthLines.length === 0 && !boundaryText) return EMPTY;
const out: string[] = [];
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++) {
const step = best![i]!;
if (step.edge) { const sy = this.synthEdgeNote(step.edge); out.push(` ↓ ${sy ? sy.compact : step.edge.kind}`); }
Expand All @@ -1683,7 +1683,7 @@ export class ToolHandler {
}
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)',
'',
...synthLines,
Expand Down Expand Up @@ -1879,7 +1879,7 @@ export class ToolHandler {
if (entries.length === 0) return '';

return [
'### Blast radius — what depends on these (update/verify before editing)',
'**Blast radius — what depends on these (update/verify before editing)**',
'',
...entries,
'',
Expand Down Expand Up @@ -2323,7 +2323,7 @@ export class ToolHandler {

// Step 3: Build relationship map
const lines: string[] = [
`## Exploration: ${query}`,
`**Exploration: ${query}`,
'',
`Found ${subgraph.nodes.size} symbols across ${fileGroups.size} files.`,
'',
Expand All @@ -2341,7 +2341,7 @@ export class ToolHandler {
);

if (budget.includeRelationships && significantEdges.length > 0) {
lines.push('### Relationships');
lines.push('**Relationships**');
lines.push('');

// Group edges by kind for readability
Expand Down Expand Up @@ -2428,7 +2428,7 @@ export class ToolHandler {
return false;
};

lines.push('### Source Code');
lines.push('**Source Code**');
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('');
Expand Down Expand Up @@ -2572,7 +2572,7 @@ export class ToolHandler {
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)'
: '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(`**${filePath} — ${names} · ${tag}`, '', '```' + lang, skel.join('\n'), '```', '');
totalChars += skel.join('\n').length + 120;
filesIncluded++;
continue;
Expand Down Expand Up @@ -2613,7 +2613,7 @@ export class ToolHandler {
)];
const headerNames = uniqSymbols.slice(0, budget.maxSymbolsInFileHeader);
const omitted = uniqSymbols.length - headerNames.length;
const wholeHeader = `#### ${filePath} — ${omitted > 0 ? `${headerNames.join(', ')}, +${omitted} more` : headerNames.join(', ')}`;
const wholeHeader = `**${filePath} — ${omitted > 0 ? `${headerNames.join(', ')}, +${omitted} more` : headerNames.join(', ')}**`;

if (!fileNecessary && totalChars + wholeSection.length + 200 > budget.maxOutputChars) {
// Don't slice a whole file mid-method: an incidental file that doesn't
Expand Down Expand Up @@ -2830,7 +2830,7 @@ export class ToolHandler {
const headerSuffix = omittedCount > 0
? `${headerSymbols.join(', ')}, +${omittedCount} more`
: headerSymbols.join(', ');
const fileHeader = `#### ${filePath} — ${headerSuffix}`;
const fileHeader = `**${filePath} — ${headerSuffix}**`;

// 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
Expand Down Expand Up @@ -2871,7 +2871,7 @@ export class ToolHandler {
.sort((a, b) => b[1].score - a[1].score);
const remainingFiles = [...remainingRelevant, ...peripheralFiles];
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('');
for (const [filePath, group] of remainingFiles.slice(0, 10)) {
const symbols = group.nodes.map(n => `${n.name}:${n.startLine}`).join(', ');
Expand Down Expand Up @@ -2919,13 +2919,13 @@ export class ToolHandler {
const output = flow.text + lines.join('\n');
const hardCeiling = Math.min(Math.round(budget.maxOutputChars * 1.5), 25000);
if (output.length > hardCeiling) {
// Cut at a FILE-SECTION boundary (the last `#### ` header before the
// Cut at a FILE-SECTION boundary (the last file-section header before the
// ceiling) so we drop whole trailing file-sections rather than slicing
// 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
// header sits in the back half (degenerate single-giant-section case).
const cut = output.slice(0, hardCeiling);
const lastSection = cut.lastIndexOf('\n#### ');
const lastSection = cut.lastIndexOf('\n**');
const boundary = lastSection > hardCeiling * 0.5 ? lastSection : cut.lastIndexOf('\n');
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.)');
Expand Down Expand Up @@ -3040,7 +3040,7 @@ export class ToolHandler {
const shownList = listed.slice(0, LIST_CAP);
out.push(
'',
'### Other definitions',
'**Other definitions**',
...shownList.map((n) => `- \`${n.name}\` (${n.kind}) — ${n.filePath}:${n.startLine}`),
);
if (listed.length > LIST_CAP) out.push(`- … +${listed.length - LIST_CAP} more`);
Expand Down Expand Up @@ -3122,7 +3122,7 @@ export class ToolHandler {
// symbolsOnly → the cheap structural overview, no source.
if (opts.symbolsOnly) {
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._');
out.push('', '> Drop `symbolsOnly` (or pass `offset`/`limit`) to read the source, like Read.');
return this.textResult(this.truncateOutput(out.join('\n')));
Expand All @@ -3132,7 +3132,7 @@ export class ToolHandler {
// line is `key: <secret>`. Summarize by key and point to a real Read.
if (CONFIG_LEAF_LANGUAGES.has(resolved.language)) {
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.');
return this.textResult(this.truncateOutput(out.join('\n')));
}
Expand All @@ -3146,7 +3146,7 @@ export class ToolHandler {
}
if (content === null) {
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.`);
return this.textResult(this.truncateOutput(out.join('\n')));
}
Expand Down Expand Up @@ -3243,7 +3243,7 @@ export class ToolHandler {
const callees = collect(cg.getCallees(node.id));
const callers = collect(cg.getCallers(node.id));
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) {
lines.push(`**Calls →** ${callees.slice(0, TRAIL_CAP).map(fmt).join(', ')}${callees.length > TRAIL_CAP ? `, +${callees.length - TRAIL_CAP} more` : ''}`);
}
Expand Down Expand Up @@ -3279,7 +3279,7 @@ export class ToolHandler {
const mismatch = this.worktreeMismatchFor(args.projectPath as string | undefined);

const lines: string[] = [
'## CodeGraph Status',
'**CodeGraph Status**',
'',
];
if (mismatch) {
Expand Down Expand Up @@ -3310,15 +3310,15 @@ export class ToolHandler {
);
}

lines.push('', '### Nodes by Kind:');
lines.push('', '**Nodes by Kind:**');

for (const [kind, count] of Object.entries(stats.nodesByKind)) {
if ((count as number) > 0) {
lines.push(`- ${kind}: ${count}`);
}
}

lines.push('', '### Languages:');
lines.push('', '**Languages:**');
for (const [lang, count] of Object.entries(stats.filesByLanguage)) {
if ((count as number) > 0) {
lines.push(`- ${lang}: ${count}`);
Expand All @@ -3331,7 +3331,7 @@ export class ToolHandler {
// banners on other tool calls.
const pending = cg.getPendingFiles();
if (pending.length > 0) {
lines.push('', '### Pending sync:');
lines.push('', '**Pending sync:**');
const now = Date.now();
for (const p of pending) {
const ageMs = Math.max(0, now - p.lastSeenMs);
Expand Down Expand Up @@ -3422,7 +3422,7 @@ export class ToolHandler {
* Format files as a flat list
*/
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))) {
if (includeMetadata) {
Expand All @@ -3447,13 +3447,13 @@ export class ToolHandler {
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)
const sortedLangs = [...byLang.entries()].sort((a, b) => b[1].length - a[1].length);

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))) {
if (includeMetadata) {
lines.push(`- ${file.path} (${file.nodeCount} symbols)`);
Expand Down Expand Up @@ -3505,7 +3505,7 @@ export class ToolHandler {
}

// 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 => {
if (maxDepth !== undefined && depth > maxDepth) return;
Expand Down Expand Up @@ -3718,13 +3718,13 @@ export class ToolHandler {
// =========================================================================

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) {
const { node } = result;
const location = node.startLine ? `:${node.startLine}` : '';
// 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}`);
if (node.signature) lines.push(`\`${node.signature}\``);
lines.push('');
Expand All @@ -3734,7 +3734,7 @@ export class ToolHandler {
}

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) {
const location = node.startLine ? `:${node.startLine}` : '';
Expand Down Expand Up @@ -3769,7 +3769,7 @@ export class ToolHandler {

// Compact format: just list affected symbols grouped by file
const lines: string[] = [
`## Impact: "${symbol}" affects ${nodeCount} symbols`,
`**Impact: "${symbol}" affects ${nodeCount} symbols**`,
'',
];

Expand Down Expand Up @@ -3817,7 +3817,7 @@ export class ToolHandler {
private formatNodeDetails(node: Node, code: string | null, outline?: string | null): string {
const location = node.startLine ? `:${node.startLine}` : '';
const lines: string[] = [
`## ${node.name} (${node.kind})`,
`**${node.name} (${node.kind})**`,
'',
`**Location:** ${node.filePath}${location}`,
];
Expand Down