diff --git a/internal/server/handlers_edit.go b/internal/server/handlers_edit.go index a80d494..51362f1 100644 --- a/internal/server/handlers_edit.go +++ b/internal/server/handlers_edit.go @@ -92,22 +92,21 @@ func (s *Server) handleEdit(ctx context.Context, request mcp.CallToolRequest) (* return mcp.NewToolResultText(fmt.Sprintf("+%d -%d", added, removed)), nil case "diff": - var output strings.Builder - output.WriteString("Diff:\n```diff\n") - output.WriteString(result.Diff) - output.WriteString("```\n") - return mcp.NewToolResultText(output.String()), nil + return mcp.NewToolResultText(fencedDiff(result.Diff)), nil default: // Fallback: treat unknown values as "diff" for safety. - var output strings.Builder - output.WriteString("Diff:\n```diff\n") - output.WriteString(result.Diff) - output.WriteString("```\n") - return mcp.NewToolResultText(output.String()), nil + return mcp.NewToolResultText(fencedDiff(result.Diff)), nil } } +// fencedDiff wraps a unified diff in a ```diff code fence. No "Diff:" label is +// added — the fence and the diff's own ---/+++ headers already identify it, so +// the label would only cost tokens. +func fencedDiff(diff string) string { + return "```diff\n" + diff + "```\n" +} + // countDiffLines counts added (+) and removed (-) content lines in a unified diff. // Only lines inside hunks are counted (everything after the first "@@" header), so the // "---"/"+++" file headers are skipped structurally — and content whose own text starts diff --git a/internal/server/handlers_edit_test.go b/internal/server/handlers_edit_test.go index 181aa05..1a8d4cd 100644 --- a/internal/server/handlers_edit_test.go +++ b/internal/server/handlers_edit_test.go @@ -59,6 +59,33 @@ func TestEditApplyPreservesBackslashSequencesText(t *testing.T) { } } +// TestEditApplyDiffResponseFencedNoLabel verifies response=diff emits the unified +// diff in a ```diff fence with no redundant "Diff:" label (the fence and the diff's +// own headers already identify it; the label would only cost tokens). +func TestEditApplyDiffResponseFencedNoLabel(t *testing.T) { + tmpDir := t.TempDir() + srv := newTestServer(t, tmpDir) + f := writeFile(t, tmpDir, "note.txt", "before\n") + + res := callEdit(t, srv, map[string]any{ + "file": f, + "operation": "replace", + "selector_text": "before", + "new_content": "after", + "response": "diff", + }) + if res.IsError { + t.Fatalf("edit returned error: %+v", res.Content) + } + out := res.Content[0].(mcp.TextContent).Text + if !strings.HasPrefix(out, "```diff\n") { + t.Errorf("diff response should start with the ```diff fence, got:\n%s", out) + } + if strings.Contains(out, "Diff:") { + t.Errorf("diff response should not carry a redundant 'Diff:' label, got:\n%s", out) + } +} + // TestCountDiffLinesHunkAware verifies the line counter skips file headers structurally and // counts content lines whose own text begins with + or - (previously dropped as "+++"/"---"). func TestCountDiffLinesHunkAware(t *testing.T) { diff --git a/internal/server/server.go b/internal/server/server.go index d6e554a..a62da1d 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -193,7 +193,7 @@ func (s *Server) registerTools() { mcp.Description("Output mode: 'full' (default, full file), 'skeleton' (signatures + { ... } stubs, bodies elided), 'symbols_only' (symbol list only, alias for symbols_only=true)."), ), mcp.WithArray("strip", - mcp.Description("Strip content classes before line-numbering. Values: 'imports' (remove import blocks), 'license' (remove leading license comment), 'block_comments' (remove /* */ and Python triple-quoted strings). Emits [stripped: ...] footer."), + mcp.Description("Strip content classes before line-numbering. Values: 'imports' (remove import blocks), 'license' (remove leading license comment), 'block_comments' (remove /* */ and Python triple-quoted strings). Emits [stripped: ...] footer. Note: line numbers are renumbered from 1 over the stripped content, so they no longer match the file's original line numbers."), mcp.WithStringItems(), ), mcp.WithNumber("max_lines",