Parcourir la source

fix(mcp): language-neutral omission markers in explore output

The gap separator and the two tail-trim markers used C-style `//`
comments, which aren't comments in Python, Ruby, etc. Switch to plain
`... (gap) ...` / `... (trimmed) ...` so they read correctly inside any
language's fenced source block. With line numbers on, the line-number
jump already corroborates a gap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Colby McHenry il y a 1 mois
Parent
commit
e8c07b57ba
2 fichiers modifiés avec 14 ajouts et 3 suppressions
  1. 9 0
      __tests__/explore-output-budget.test.ts
  2. 5 3
      src/mcp/tools.ts

+ 9 - 0
__tests__/explore-output-budget.test.ts

@@ -210,6 +210,15 @@ describe('codegraph_explore output respects the adaptive budget', () => {
     }
   });
 
+  it('uses language-neutral omission markers (no C-style // in the output)', async () => {
+    // The gap/trimmed separators must not assume `//` is a comment — that's
+    // wrong in Python, Ruby, etc. They render inside fenced source blocks.
+    const result = await handler.execute('codegraph_explore', { query: 'Session method helper' });
+    const text = result.content?.[0]?.text ?? '';
+    expect(text).not.toContain('// ... (gap)');
+    expect(text).not.toContain('// ... trimmed');
+  });
+
   it('does not collapse a whole-file class into just its header (envelope filter)', async () => {
     // The synthetic `Session` class spans the entire file. Without the
     // envelope filter it would form one giant cluster that tail-trims to

+ 5 - 3
src/mcp/tools.ts

@@ -1061,7 +1061,9 @@ export class ToolHandler {
         // startIdx is 0-based, so the slice's first line is line startIdx + 1.
         return withLineNumbers ? numberSourceLines(slice, startIdx + 1) : slice;
       };
-      const GAP_MARKER = '\n\n// ... (gap) ...\n\n';
+      // Language-neutral separator (no `//` — not a comment in Python, Ruby,
+      // etc.). With line numbers on, the line-number jump also signals the gap.
+      const GAP_MARKER = '\n\n... (gap) ...\n\n';
 
       // Rank clusters for inclusion under the per-file cap. Entry-point
       // clusters come first: a cluster containing a query entry point
@@ -1117,7 +1119,7 @@ export class ToolHandler {
       // If a single chosen cluster is still oversize (long monolithic
       // function), tail-trim it. Better one trimmed view than nothing.
       if (fileSection.length > budget.maxCharsPerFile) {
-        fileSection = fileSection.slice(0, budget.maxCharsPerFile) + '\n// ... trimmed ...';
+        fileSection = fileSection.slice(0, budget.maxCharsPerFile) + '\n... (trimmed) ...';
         fileTrimmed = true;
       }
       if (chosenIndices.size < clusters.length || fileTrimmed) {
@@ -1147,7 +1149,7 @@ export class ToolHandler {
       if (totalChars + fileSection.length + 200 > budget.maxOutputChars) {
         const remaining = budget.maxOutputChars - totalChars - 200;
         if (remaining < 500) break;
-        const trimmed = fileSection.slice(0, remaining) + '\n// ... trimmed ...';
+        const trimmed = fileSection.slice(0, remaining) + '\n... (trimmed) ...';
 
         lines.push(fileHeader);
         lines.push('');