diff_files
Compare two files to identify differences. Supports multiple output formats (unified, context, side-by-side, JSON) and optional whitespace ignoring and context adjustment.
Instructions
Compare two files and show differences
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| file1 | Yes | Path to the first file | |
| file2 | Yes | Path to the second file | |
| format | No | Output format for the diff | unified |
| context | No | Number of context lines to show | |
| ignoreWhitespace | No | Ignore whitespace differences |
Implementation Reference
- Primary handler implementation of the diff_files tool. Extends BaseCommand with name='diff_files', defines inputSchema, validates args, and executes via IDiffService.compareFiles(). Returns a JSON result with additions, deletions, changes, and diff content.
import { BaseCommand } from '../../base/BaseCommand.js'; import { CommandResult, CommandContext } from '../../../core/interfaces/ICommand.js'; import { IDiffService } from '../../../core/interfaces/IDiffService.js'; export class DiffFilesCommand extends BaseCommand { readonly name = 'diff_files'; readonly description = 'Compare two files and show differences'; readonly inputSchema = { type: 'object', properties: { file1: { type: 'string', description: 'Path to the first file' }, file2: { type: 'string', description: 'Path to the second file' }, format: { type: 'string', enum: ['unified', 'context', 'side-by-side', 'json'], description: 'Output format for the diff', default: 'unified' }, context: { type: 'number', description: 'Number of context lines to show', minimum: 0, default: 3 }, ignoreWhitespace: { type: 'boolean', description: 'Ignore whitespace differences', default: false } }, required: ['file1', 'file2'], additionalProperties: false }; protected validateArgs(args: Record<string, any>): void { this.assertString(args.file1, 'file1'); this.assertString(args.file2, 'file2'); if (args.format !== undefined) { this.assertString(args.format, 'format'); const validFormats = ['unified', 'context', 'side-by-side', 'json']; if (!validFormats.includes(args.format)) { throw new Error(`Format must be one of: ${validFormats.join(', ')}`); } } if (args.context !== undefined) { this.assertNumber(args.context, 'context'); if (args.context < 0) { throw new Error('Context must be a non-negative number'); } } if (args.ignoreWhitespace !== undefined) { this.assertBoolean(args.ignoreWhitespace, 'ignoreWhitespace'); } } protected async executeCommand(context: CommandContext): Promise<CommandResult> { try { const diffService = context.container.getService<IDiffService>('diffService'); const diff = await diffService.compareFiles( context.args.file1, context.args.file2, { format: context.args.format, context: context.args.context, ignoreWhitespace: context.args.ignoreWhitespace } ); return { content: [{ type: 'text', text: JSON.stringify({ file1: context.args.file1, file2: context.args.file2, format: context.args.format, additions: diff.additions, deletions: diff.deletions, changes: diff.changes, diff: diff.content }, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: `Failed to compare files: ${error instanceof Error ? error.message : String(error)}` }] }; } } } - Alternative/legacy handler for diff_files in the old Command pattern. Delegates to FileSystemManager.diffFiles() which calls DiffManager.
export class DiffFilesCommand extends Command { readonly name = 'diff_files'; readonly description = 'Compare two files and show differences'; readonly inputSchema = { type: 'object' as const, properties: { file1: { type: 'string' as const, description: 'First file path' }, file2: { type: 'string' as const, description: 'Second file path' }, format: { type: 'string' as const, enum: ['unified', 'side-by-side', 'inline'], description: 'Diff output format' } }, required: ['file1', 'file2'] }; protected validateArgs(args: Record<string, any>): void { this.assertString(args.file1, 'file1'); this.assertString(args.file2, 'file2'); if (args.format !== undefined) { this.assertString(args.format, 'format'); if (!['unified', 'side-by-side', 'inline'].includes(args.format)) { throw new Error(`Invalid format: ${args.format}. Must be one of: unified, side-by-side, inline`); } } } protected async executeCommand(context: CommandContext): Promise<CommandResult> { const fsManager = context.fsManager as FileSystemManager; return await fsManager.diffFiles(context.args.file1, context.args.file2, context.args.format); } } - src/core/DiffManager.ts:1-119 (helper)Core diff engine. DiffManager.diffFiles() reads two files and computes line-by-line diff. Returns additions, deletions, formatted diff output, and patches array. Also supports patchFile() for applying patches.
import { promises as fs } from 'fs'; import * as path from 'path'; interface DiffResult { additions: number; deletions: number; formatted: string; patches: DiffPatch[]; } interface DiffPatch { start: number; end: number; type: 'addition' | 'deletion' | 'modification'; content: string; } interface DiffOptions { format?: 'unified' | 'side-by-side' | 'inline'; contextLines?: number; ignoreWhitespace?: boolean; } export class DiffManager { async diffFiles(file1: string, file2: string, options: DiffOptions = {}): Promise<DiffResult> { const content1 = await fs.readFile(file1, 'utf-8'); const content2 = await fs.readFile(file2, 'utf-8'); return this.diff(content1, content2, options); } diff(text1: string, text2: string, options: DiffOptions = {}): DiffResult { const lines1 = text1.split('\n'); const lines2 = text2.split('\n'); const patches: DiffPatch[] = []; let additions = 0; let deletions = 0; // Simple line-by-line diff algorithm const maxLines = Math.max(lines1.length, lines2.length); let formatted = ''; for (let i = 0; i < maxLines; i++) { const line1 = lines1[i]; const line2 = lines2[i]; if (line1 === undefined && line2 !== undefined) { // Addition additions++; formatted += `+ ${line2}\n`; patches.push({ start: i, end: i, type: 'addition', content: line2 }); } else if (line1 !== undefined && line2 === undefined) { // Deletion deletions++; formatted += `- ${line1}\n`; patches.push({ start: i, end: i, type: 'deletion', content: line1 }); } else if (line1 !== line2) { // Modification deletions++; additions++; formatted += `- ${line1}\n`; formatted += `+ ${line2}\n`; patches.push({ start: i, end: i, type: 'modification', content: `${line1} -> ${line2}` }); } else { // No change if (options.format === 'unified') { formatted += ` ${line1}\n`; } } } return { additions, deletions, formatted, patches }; } async patchFile(filePath: string, patches: DiffPatch[]): Promise<void> { const content = await fs.readFile(filePath, 'utf-8'); const lines = content.split('\n'); // Apply patches in reverse order to maintain line numbers patches.sort((a, b) => b.start - a.start); for (const patch of patches) { switch (patch.type) { case 'addition': lines.splice(patch.start, 0, patch.content); break; case 'deletion': lines.splice(patch.start, 1); break; case 'modification': lines[patch.start] = patch.content.split(' -> ')[1]; break; } } await fs.writeFile(filePath, lines.join('\n'), 'utf-8'); } } - FileSystemManager.diffFiles() bridge method used by the legacy MetadataCommands handler. Delegates to DiffManager and formats the result.
async diffFiles(file1: string, file2: string, format: 'unified' | 'side-by-side' | 'inline' = 'unified'): Promise<{ content: [{ type: string; text: string }] }> { const result = await this.diffManager.diffFiles(file1, file2, { format }); return { content: [{ type: 'text', text: `Diff between ${file1} and ${file2}:\n\nAdditions: ${result.additions}\nDeletions: ${result.deletions}\n\n${result.formatted}` }] }; } - IDiffService interface and DiffResult type used by the primary DiffFilesCommand handler (in the commands/ directory). Defines compareFiles() signature and options.
export interface DiffResult { additions: number; deletions: number; changes: number; content: string; } export interface IDiffService { compareFiles( file1: string, file2: string, options?: { format?: 'unified' | 'context' | 'side-by-side' | 'json'; context?: number; ignoreWhitespace?: boolean; } ): Promise<DiffResult>; }