/** * DoctorCommand - Diagnostic and repair command * DD §17. doctor [--fix|--bundle]. * * @module packages/cli/src/commands/doctor */ import { loadConfig } from '../bootstrap/loadConfig.js' import { DoctorService } from '@aircoding/runtime' import { createInterface } from 'readline' async function ask_user(prompt: string): Promise { const rl = createInterface({ input: process.stdin, output: process.stdout }) return new Promise((resolve) => { rl.question(prompt, (answer) => { rl.close() resolve(answer.toLowerCase().startsWith('y')) }) }) } export async function doctorCommand(options: { fix?: boolean; bundle?: boolean; scope?: string }): Promise { const config = loadConfig() const project_root = config.project_root || process.cwd() const doctor = new DoctorService(project_root) console.log('Running diagnostics...\n') const report = await doctor.run_diagnostics(options.scope as any || 'all') // Group checks by category for clean output const categories = new Map>() for (const check of report.checks) { const cat = categories.get(check.category) || [] cat.push(check) categories.set(check.category, cat) } for (const [category, checks] of categories) { console.log(` [${category}]`) for (const check of checks) { const icon = check.passed ? '✅' : '❌' const fixHint = check.fixable ? ` → fix: ${check.fix || 'manual'}` : '' console.log(` ${icon} ${check.name}: ${check.message}${fixHint}`) } } console.log(`\nBootstrap: ${report.bootstrap_passed ? '✅ PASS' : '❌ FAIL'}`) console.log(`All checks: ${report.all_passed ? '✅ PASS' : '❌ FAIL'}`) console.log(`Fixable: ${report.fixable_count} issues`) if (options.fix) { const fixable = report.checks.filter(c => !c.passed && c.fixable) if (fixable.length === 0) { console.log('\nNothing to fix.') return } console.log(`\n${fixable.length} fixable issue(s) found:`) for (const check of fixable) { console.log(` - ${check.name}: ${check.fix || 'manual fix required'}`) } // Permissioned fix mode: ask user before each fix (§6.12) const approved = await ask_user(`\nApply these fixes? This may install system packages. [y/N] `) if (!approved) { console.log('Fix cancelled.') return } console.log('\nApplying fixes...') for (const check of fixable) { const result = await doctor.fix(check.name) const icon = result.ok ? '✅' : '❌' console.log(` ${icon} ${check.name}: ${result.message}`) } // Re-run diagnostics to show updated state console.log('\nRe-running diagnostics...\n') const updated = await doctor.run_diagnostics(options.scope as any || 'all') for (const check of updated.checks.filter(c => !c.passed)) { console.log(` ❌ ${check.name}: ${check.message}`) } if (updated.all_passed) { console.log(' ✅ All checks passed after fix!') } console.log(`\nUpdated: ${updated.all_passed ? '✅ PASS' : '❌ FAIL'}`) } if (options.bundle) { console.log('\nBundle feature not yet implemented (P8)') } }