MCP tool results used Markdown ATX headings (##/###/####) for section headers — the status summary, each search hit, every file section in an exploration — which Markdown-rendering clients (e.g. the Claude Code VSCode extension) blow up to H1–H4 font size, filling the transcript with oversized lines (worst on search/explore, where the noise scales with result count). Swap them all for bold labels, which render at body size while keeping the same structure. CLI/TTY output (ContextBuilder) is unchanged — the issue notes it's fine. The format is parse-coupled, so kept in sync: - The explore truncation boundary and the offload chunker (reasoning/reasoner.ts) both key off the per-file header, now a unique `**`-prefixed marker emitted via a shared fileSectionHeader() helper. - Updated the offload strip regexes and switched the opt-in report-style prompt off ATX headings (same client, same rendering issue). - Updated test helpers (sectionFor, sourcedFiles, the callers section-boundary scan) that scanned the old markers. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ace8d8a0d0
commit
3e1547bbe1
+56
-39
@@ -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;
|
||||
@@ -326,6 +326,23 @@ function numberSourceLines(slice: string, firstLineNumber: number): string {
|
||||
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
|
||||
* file watcher has pending events for files referenced by the response.
|
||||
@@ -1350,7 +1367,7 @@ export class ToolHandler {
|
||||
private definitionHeading(group: Node[]): string {
|
||||
const head = group[0]!;
|
||||
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
|
||||
// `file` to focus a single definition.
|
||||
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) {
|
||||
const { callers, labels } = collect(group);
|
||||
@@ -1478,7 +1495,7 @@ export class ToolHandler {
|
||||
|
||||
// Multiple DISTINCT definitions (#764): per-definition sections.
|
||||
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) {
|
||||
const { callees, labels } = collect(group);
|
||||
@@ -1547,7 +1564,7 @@ export class ToolHandler {
|
||||
// merging unrelated same-named classes (one UserService per monorepo app)
|
||||
// overstated impact and confused agents. Narrow with `file`.
|
||||
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) {
|
||||
const head = group[0]!;
|
||||
@@ -1765,7 +1782,7 @@ export class ToolHandler {
|
||||
if (synthLines.length === 0 && !boundaries) return EMPTY;
|
||||
const out: string[] = [];
|
||||
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, '');
|
||||
if (boundaries) out.push(boundaries);
|
||||
@@ -1880,7 +1897,7 @@ export class ToolHandler {
|
||||
if (!hasMain && synthLines.length === 0 && !boundaryText && !polyText) 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}`); }
|
||||
@@ -1890,7 +1907,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,
|
||||
@@ -1958,7 +1975,7 @@ export class ToolHandler {
|
||||
}
|
||||
if (notes.length === 0) return '';
|
||||
return [
|
||||
'## Dynamic boundaries (the static path ends at runtime dispatch)',
|
||||
'**Dynamic boundaries (the static path ends at runtime dispatch)**',
|
||||
'',
|
||||
...notes,
|
||||
'',
|
||||
@@ -2045,7 +2062,7 @@ export class ToolHandler {
|
||||
}
|
||||
if (notes.length === 0) return '';
|
||||
return [
|
||||
'## Interface dispatch (a named method has many implementations)',
|
||||
'**Interface dispatch (a named method has many implementations)**',
|
||||
'',
|
||||
...notes,
|
||||
'',
|
||||
@@ -2174,7 +2191,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,
|
||||
'',
|
||||
@@ -2643,7 +2660,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.`,
|
||||
'',
|
||||
@@ -2661,7 +2678,7 @@ export class ToolHandler {
|
||||
);
|
||||
|
||||
if (budget.includeRelationships && significantEdges.length > 0) {
|
||||
lines.push('### Relationships');
|
||||
lines.push('**Relationships**');
|
||||
lines.push('');
|
||||
|
||||
// Group edges by kind for readability
|
||||
@@ -2748,7 +2765,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('');
|
||||
@@ -2892,7 +2909,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(fileSectionHeader(filePath, `${names} · ${tag}`), '', '```' + lang, skel.join('\n'), '```', '');
|
||||
totalChars += skel.join('\n').length + 120;
|
||||
filesIncluded++;
|
||||
continue;
|
||||
@@ -2933,7 +2950,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 = fileSectionHeader(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
|
||||
@@ -3200,7 +3217,7 @@ export class ToolHandler {
|
||||
const headerSuffix = omittedCount > 0
|
||||
? `${headerSymbols.join(', ')}, +${omittedCount} more`
|
||||
: 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 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);
|
||||
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(', ');
|
||||
@@ -3290,13 +3307,13 @@ export class ToolHandler {
|
||||
|
||||
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 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' + FILE_SECTION_PREFIX);
|
||||
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.)');
|
||||
@@ -3411,7 +3428,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`);
|
||||
@@ -3493,7 +3510,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')));
|
||||
@@ -3503,7 +3520,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')));
|
||||
}
|
||||
@@ -3517,7 +3534,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')));
|
||||
}
|
||||
@@ -3614,7 +3631,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` : ''}`);
|
||||
}
|
||||
@@ -3650,7 +3667,7 @@ export class ToolHandler {
|
||||
const mismatch = this.worktreeMismatchFor(args.projectPath as string | undefined);
|
||||
|
||||
const lines: string[] = [
|
||||
'## CodeGraph Status',
|
||||
'**CodeGraph Status**',
|
||||
'',
|
||||
];
|
||||
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)) {
|
||||
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)) {
|
||||
if ((count as number) > 0) {
|
||||
lines.push(`- ${lang}: ${count}`);
|
||||
@@ -3703,7 +3720,7 @@ export class ToolHandler {
|
||||
if (cg.isWatcherDegraded()) {
|
||||
lines.push(
|
||||
'',
|
||||
'### Auto-sync disabled:',
|
||||
'**Auto-sync disabled:**',
|
||||
`- ${cg.getWatcherDegradedReason() ?? 'live file watching stopped'}`,
|
||||
'- The index is frozen; Read files directly for current content.'
|
||||
);
|
||||
@@ -3715,7 +3732,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);
|
||||
@@ -3806,7 +3823,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) {
|
||||
@@ -3831,13 +3848,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)`);
|
||||
@@ -3889,7 +3906,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;
|
||||
@@ -4102,13 +4119,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('');
|
||||
@@ -4118,7 +4135,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}` : '';
|
||||
@@ -4153,7 +4170,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**`,
|
||||
'',
|
||||
];
|
||||
|
||||
@@ -4201,7 +4218,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}`,
|
||||
];
|
||||
|
||||
@@ -124,8 +124,8 @@ CORRECTNESS OVERRIDES EVERYTHING. Being incomplete is fine; being WRONG is not
|
||||
const SYSTEM_PROMPT_REPORT = `${ROLE}
|
||||
|
||||
Produce a single self-contained exploration report, formatted exactly like the summary a thorough senior engineer hands back after investigating. Clean Markdown, in this shape:
|
||||
- Open with the one-line coverage verdict (above). Then, ONLY if covered, a title: "## <Topic> — <Flow / Trace / Overview>". If coverage is not-found, the verdict + the names to explore next is the entire reply. NO preamble ("Here is", "Now I understand").
|
||||
- Body is numbered sections with bold headers: "### 1. **<step or aspect>**", "### 2. **<...>**", …
|
||||
- Open with the one-line coverage verdict (above). Then, ONLY if covered, a bold title: "**<Topic> — <Flow / Trace / Overview>**". If coverage is not-found, the verdict + the names to explore next is the entire reply. NO preamble ("Here is", "Now I understand"). Use bold labels for headers, never Markdown ATX headings (\`#\`/\`##\`) — they render oversized in some clients.
|
||||
- Body is numbered sections with bold headers: "**1. <step or aspect>**", "**2. <...>**", …
|
||||
- Cite every location inline and in bold as **\`path/to/file.ts:line\`** (or a line range), exactly as given in the source. Bold key classes, methods, and symbols.
|
||||
- For a flow/path question, include a call-chain diagram in a fenced code block using down-arrows:
|
||||
\`\`\`
|
||||
@@ -135,7 +135,7 @@ Produce a single self-contained exploration report, formatted exactly like the s
|
||||
\`\`\`
|
||||
- Quote only the code lines that carry the logic, in fenced code blocks, keeping their line numbers. Keep snippets tight.
|
||||
- Separate major sections with a "---" rule.
|
||||
- End with "### Summary" — the end-to-end chain in one compact block.
|
||||
- End with "**Summary**" — the end-to-end chain in one compact block.
|
||||
|
||||
Be precise and dense — an engineer should be able to act from this report without opening a file.`;
|
||||
|
||||
@@ -172,11 +172,13 @@ export function stripAgentDirectives(context: string): string {
|
||||
let i = 0;
|
||||
while (i < lines.length) {
|
||||
const ln = lines[i] ?? '';
|
||||
if (/^##\s+Exploration:/.test(ln) || /^Found \d+ symbols? across \d+ files?/.test(ln)) { i++; continue; }
|
||||
// "Not shown above" pointer section: drop header + its bullets/blanks until the next rule/heading/blockquote.
|
||||
if (/^###\s+Not shown above/i.test(ln)) {
|
||||
// Headers are bold labels, not ATX headings (tools.ts, issue #778): the
|
||||
// explore header is `**Exploration: …**`, file sections start with ``**` ``.
|
||||
if (/^\*\*Exploration:/.test(ln) || /^Found \d+ symbols? across \d+ files?/.test(ln)) { i++; continue; }
|
||||
// "Not shown above" pointer section: drop header + its bullets/blanks until the next rule/header/blockquote.
|
||||
if (/^\*\*Not shown above/i.test(ln)) {
|
||||
i++;
|
||||
while (i < lines.length && !/^(---|#{2,4}\s|>\s)/.test(lines[i] ?? '')) i++;
|
||||
while (i < lines.length && !/^(---|\*\*|>\s)/.test(lines[i] ?? '')) i++;
|
||||
continue;
|
||||
}
|
||||
// Agent-directed blockquote notes (completeness / budget / trimmed).
|
||||
|
||||
Reference in New Issue
Block a user