import { Command } from 'commander'; import chalk from 'chalk'; import ora from 'ora'; import axios from 'axios'; import { glob } from 'glob'; import fs from 'fs/promises'; import path from 'path'; import inquirer from 'inquirer'; import * as Diff from 'diff'; import { getApiKey, getApiUrl, getProjectId } from '../utils/config.js'; import { readGitignore, shouldIgnore, isCodeFile } from '../utils/files.js'; export function createFixCommand(): Command { return new Command('fix') .description('Scan and interactively FIX detected issues using Rigstate AI') .argument('[path]', 'Directory or file to scan', '.') .option('--project ', 'Project ID to context-aware audit') .action(async (targetPath: string, options: { project?: string }) => { const spinner = ora(); try { const apiKey = getApiKey(); const apiUrl = getApiUrl(); const projectId = options.project || getProjectId(); if (!projectId) { console.log(chalk.yellow('āš ļø Project ID is required for fixing. Using default or pass --project ')); // We can proceed without it, but fix quality drops. Let's warn. } const scanPath = path.resolve(process.cwd(), targetPath); // --- Reuse Scan Logic (Simplified) --- const gitignorePatterns = await readGitignore(scanPath); const pattern = path.join(scanPath, '**/*'); const allFiles = await glob(pattern, { nodir: true, dot: false, ignore: ['**/node_modules/**', '**/.git/**'] }); const codeFiles = allFiles.filter(file => { const relativePath = path.relative(scanPath, file); return isCodeFile(file) && !shouldIgnore(relativePath, gitignorePatterns); }); if (codeFiles.length === 0) { console.log(chalk.yellow('No code files found.')); return; } console.log(chalk.bold(`\n🧠 Rigstate Fix Mode`)); console.log(chalk.dim(`Scanning ${codeFiles.length} files with Project Context...\n`)); let fixedCount = 0; for (let i = 0; i < codeFiles.length; i++) { const filePath = codeFiles[i]; const relativePath = path.relative(scanPath, filePath); spinner.start(`Analyzing ${relativePath}...`); try { const content = await fs.readFile(filePath, 'utf-8'); const response = await axios.post( `${apiUrl}/api/v1/audit`, { content, file_path: relativePath, project_id: projectId }, { headers: { 'Authorization': `Bearer ${apiKey}` }, timeout: 120000 } ); const vulnerabilities = response.data.vulnerabilities || []; const fixableIssues = vulnerabilities.filter((v: any) => v.fixed_content); if (fixableIssues.length > 0) { spinner.stop(); console.log(`\n${chalk.bold(relativePath)}: Found ${fixableIssues.length} fixable issues.`); for (const issue of fixableIssues) { console.log(chalk.red(`\n[${issue.type}] ${issue.title}`)); console.log(chalk.dim(issue.suggestion || issue.message)); // Show Diff const diff = Diff.createTwoFilesPatch(relativePath, relativePath, content, issue.fixed_content, 'Current', 'Fixed'); console.log('\n' + diff.split('\n').slice(0, 15).join('\n') + (diff.split('\n').length > 15 ? '\n...' : '')); const { apply } = await inquirer.prompt([{ type: 'confirm', name: 'apply', message: `Apply this fix to ${chalk.cyan(relativePath)}?`, default: true }]); if (apply) { await fs.writeFile(filePath, issue.fixed_content); console.log(chalk.green(`āœ… Fixed applied!`)); fixedCount++; if (issue.related_step_id) { const { completeStep } = await inquirer.prompt([{ type: 'confirm', name: 'completeStep', message: `Frank thinks this fix completes a Roadmap Step. Mark as COMPLETED in Rigstate?`, default: true }]); if (completeStep) { try { await axios.post( `${apiUrl}/api/v1/roadmap/update-status`, { step_id: issue.related_step_id, status: 'COMPLETED', project_id: projectId }, { headers: { 'Authorization': `Bearer ${apiKey}` } } ); console.log(chalk.green(`šŸš€ Roadmap updated! Mission Control is in sync.`)); } catch (err: any) { console.error(chalk.yellow(`Failed to update roadmap: ${err.message}`)); } } } // Stop processing this file as content changed break; } else { console.log(chalk.dim('Skipped.')); } } } else { spinner.text = `Checked ${relativePath} (No auto-fixes)`; } } catch (e: any) { // silent fail for file read/network errors in fix mode to keep flow } } spinner.stop(); console.log(chalk.bold.green(`\n\nšŸš€ Fix session complete!`)); console.log(`Frank fixed ${fixedCount} detected issues.`); console.log(chalk.dim(`Run 'rigstate scan' to verify remaining issues.`)); } catch (error: any) { spinner.fail('Fix session failed'); console.error(error.message); } }); }