import * as path from 'path'; import { Command } from 'commander'; import chalk from 'chalk'; import { getWorkspacePath, loadWorkspaceConfig, sanitizeAgentName } from '../utils/file-utils'; import { generateDiff, createGit, getCurrentBranch } from '../utils/git-utils'; import { DiffLine } from '../types/index'; import { formatDiffAsMarkdown, formatDiffAsJson, formatDiffAsText, exportDiffToFile, generateShortDiffSummary } from '../utils/diff-utils'; import { GitAgentSyncError, WorkspaceNotFoundError } from '../utils/errors'; export function createDiffCommand(): Command { const cmd = new Command('diff') .description('Generate unified diffs for code review') .argument('[agent-name]', 'Agent name (defaults to current workspace)') .option('-o, --output ', 'Export diff to file') .option('--short', 'Show short summary only') .option('--json', 'Output as JSON') .option('--text', 'Output as plain text') .option('--compare ', 'Compare to a specific branch (default: main)') .action(async (agentName, options) => { try { const currentPath = process.cwd(); let workspacePath: string; let targetAgentName: string; if (agentName) { targetAgentName = sanitizeAgentName(agentName); workspacePath = getWorkspacePath(currentPath, targetAgentName); } else { const workspaceResult = await detectCurrentWorkspace(currentPath); workspacePath = workspaceResult.path; targetAgentName = workspaceResult.agentName; } const config = await loadWorkspaceConfig(workspacePath); if (!config) { throw new WorkspaceNotFoundError(targetAgentName); } const compareBranch = options.compare || config.mainBranch; const diffResult = await generateDiff(currentPath, workspacePath, targetAgentName, compareBranch); if (options.short) { const summary = generateShortDiffSummary(diffResult); console.log(chalk.white(`${targetAgentName}: ${summary}`)); return; } if (options.json) { console.log(formatDiffAsJson(diffResult)); return; } if (options.text) { console.log(formatDiffAsText(diffResult)); return; } if (options.output) { await exportDiffToFile(diffResult, options.output, 'markdown'); console.log(chalk.green(`\nāœ“ Diff exported to ${options.output}`)); return; } console.log(chalk.cyan(`\nšŸ“ Diff: ${targetAgentName}`)); console.log(chalk.gray(`Branch: ${diffResult.branch} → ${diffResult.comparedBranch}`)); console.log(chalk.gray('─'.repeat(60))); if (diffResult.files.length === 0) { console.log(chalk.yellow('\n No differences found.')); return; } console.log(chalk.cyan('\nšŸ“Š Summary:')); console.log(` Files: ${chalk.white(diffResult.summary.filesChanged)}`); console.log(` Additions: ${chalk.green(`+${diffResult.summary.additions}`)}`); console.log(` Deletions: ${chalk.red(`-${diffResult.summary.deletions}`)}`); console.log(chalk.gray('─'.repeat(60))); for (const file of diffResult.files) { console.log(chalk.cyan(`\nšŸ“„ ${file.file}`)); console.log(` +${file.additions} / -${file.deletions}`); const relevantChanges = file.changes.filter((c: DiffLine) => c.type !== 'context').slice(0, 20); if (relevantChanges.length > 0) { console.log(' Preview:'); for (const line of relevantChanges) { if (line.type === 'added') { console.log(` ${chalk.green(line.content)}`); } else if (line.type === 'deleted') { console.log(` ${chalk.red(line.content)}`); } } if (file.changes.filter((c: DiffLine) => c.type !== 'context').length > 20) { console.log(` ${chalk.gray('... and more changes (use --output to see full diff)')}`); } } } console.log(chalk.cyan('\nšŸ’” Use --output to export full diff to a file')); } catch (error) { const gitError = error instanceof GitAgentSyncError ? error : new GitAgentSyncError(String(error)); console.error(chalk.red(`\nāŒ Error: ${gitError.message}`)); process.exit(1); } }); return cmd; } async function detectCurrentWorkspace(currentPath: string): Promise<{ path: string; agentName: string }> { const git = createGit(currentPath); const branch = await getCurrentBranch(git); if (branch.startsWith('agent-')) { const agentName = branch.replace(/^agent-/, ''); const { getWorkspacePath } = await import('../utils/file-utils'); return { agentName, path: getWorkspacePath(currentPath, agentName) }; } throw new GitAgentSyncError('Not in an agent workspace. Please specify an agent name.'); }