/**
* Diff Command - Generate unified diff patch for code changes
*/
import chalk from 'chalk';
import { readFileSync } from 'fs';
import { MCPClient, MCPResponse } from '../client.js';
import { basename, extname } from 'path';
import { displayEscalation, promptEscalationConfirm } from '../utils/escalation.js';
import {
shouldUseClaudeCode,
promptClaudeCodeInsteadOfEscalation,
executeWithClaudeCode,
createTaskSummary
} from '../utils/claudeIntegration.js';
export async function diffCommand(
filePath: string,
options: {
prompt?: string;
endpoint?: string;
apiKey?: string;
username?: string;
password?: string;
apply?: boolean;
useClaudeCode?: boolean;
}
): Promise<void> {
const client = new MCPClient(options.endpoint, options.apiKey, options.username, options.password);
let fileContent: string;
let fileName: string;
let language: string;
// Read file
try {
fileContent = readFileSync(filePath, 'utf-8');
fileName = basename(filePath);
language = detectLanguage(filePath);
console.log(chalk.dim(`๐ Read file: ${fileName} (${language})\n`));
} catch (error) {
console.error(chalk.red(`โ Cannot read file: ${filePath}`));
console.error(chalk.dim(error instanceof Error ? error.message : String(error)));
process.exit(1);
}
const prompt = options.prompt || 'Please suggest improvements and provide a unified diff patch.';
console.log(chalk.dim('โณ Requesting diff from MCP server...\n'));
const context = client.getCurrentContext();
// Add file info to context
if (context.context) {
context.context.filename = filePath;
context.context.language = language;
}
// Include file content in message
const fullMessage = `${prompt}\n\nFile: ${filePath}\nLanguage: ${language}\n\n\`\`\`${language}\n${fileContent}\n\`\`\``;
try {
let response = await client.send({
mode: 'diff',
message: fullMessage,
budget: 0, // Free tier for diff generation
...context,
});
// Handle escalation confirmation if required
if (response.requiresEscalationConfirm && response.suggestedLayer) {
const currentLayer = response.metadata?.layer || 'L0';
const shouldEscalate = await promptEscalationConfirm(
currentLayer,
response.suggestedLayer,
response.escalationReason || 'Quality improvement needed'
);
if (shouldEscalate) {
console.log(chalk.cyan(`\n๐ Escalating to ${response.suggestedLayer}...\n`));
const escalatedMessage = response.optimizedPrompt || fullMessage;
response = await client.send({
mode: 'diff',
message: escalatedMessage,
budget: 0, // Free tier for diff generation
...context,
});
}
}
printResponse(response);
// If --apply flag is set and patch exists
if (options.apply && response.patch) {
console.log(chalk.yellow('\nโ ๏ธ Apply patch functionality not yet implemented'));
console.log(chalk.dim('To apply manually: save patch to file.patch and run:'));
console.log(chalk.dim(' git apply file.patch'));
console.log(chalk.dim(' or'));
console.log(chalk.dim(' patch -p1 < file.patch\n'));
}
} catch (error) {
process.exit(1);
}
}
/**
* Detect programming language from file extension
*/
function detectLanguage(filePath: string): string {
const ext = extname(filePath).toLowerCase();
const langMap: Record<string, string> = {
'.js': 'javascript',
'.ts': 'typescript',
'.jsx': 'javascript',
'.tsx': 'typescript',
'.py': 'python',
'.java': 'java',
'.c': 'c',
'.cpp': 'cpp',
'.go': 'go',
'.rs': 'rust',
'.rb': 'ruby',
'.php': 'php',
};
return langMap[ext] || 'text';
}
/**
* Print diff response with patch
*/
function printResponse(response: any): void {
const { message, patch, model, tokens, cost } = response;
// Print explanation message
console.log(chalk.cyan('โ' + 'โ'.repeat(58) + 'โ'));
console.log(chalk.cyan('โ') + chalk.bold(' MCP DIFF RESPONSE ').padEnd(58) + chalk.cyan('โ'));
console.log(chalk.cyan('โ' + 'โ'.repeat(58) + 'โ\n'));
console.log(chalk.white(message || 'Diff generated'));
console.log();
// Print patch if available
if (patch) {
console.log(chalk.yellow('โ' + 'โ'.repeat(58) + 'โ'));
console.log(chalk.yellow('โ') + chalk.bold(' UNIFIED DIFF PATCH ').padEnd(58) + chalk.yellow('โ'));
console.log(chalk.yellow('โ' + 'โ'.repeat(58) + 'โ\n'));
// Syntax highlight diff
printHighlightedDiff(patch);
console.log();
console.log(chalk.dim('๐ก Tip: To apply this patch, save it to a file and run:'));
console.log(chalk.dim(' git apply <patch-file>'));
console.log(chalk.dim(' or'));
console.log(chalk.dim(' patch < <patch-file>\n'));
} else {
console.log(chalk.yellow('โ ๏ธ No patch generated by AI\n'));
}
// Display escalation if present
if (response.escalation?.required) {
displayEscalation(response.escalation);
}
if (model) {
const totalTokens = tokens?.total || 0;
const costStr = cost ? `$${cost.toFixed(4)}` : '';
console.log(chalk.dim(`๐ Model: ${model} | Tokens: ${totalTokens} ${costStr ? `| Cost: ${costStr}` : ''}\n`));
}
}
/**
* Print diff with syntax highlighting
*/
function printHighlightedDiff(diff: string): void {
const lines = diff.split('\n');
for (const line of lines) {
if (line.startsWith('+++') || line.startsWith('---')) {
console.log(chalk.bold.white(line));
} else if (line.startsWith('+')) {
console.log(chalk.green(line));
} else if (line.startsWith('-')) {
console.log(chalk.red(line));
} else if (line.startsWith('@@')) {
console.log(chalk.cyan(line));
} else if (line.startsWith('diff --git')) {
console.log(chalk.yellow(line));
} else {
console.log(chalk.dim(line));
}
}
}