UNPKG

devibe

Version:

Intelligent repository cleanup with auto mode, AI learning, markdown consolidation, auto-consolidate workflow, context-aware classification, and cost optimization

816 lines (806 loc) 37.9 kB
/** * Auto Executor * * Automatically executes repository cleanup using AI classification * without requiring user confirmation for each file. * * Features: * - Uses IntelligentClassifier for smart file classification * - Batch processing for efficiency * - Automatic conflict resolution * - Progress reporting * - Full backup before execution */ import { IntelligentClassifier } from './intelligent-classifier.js'; import { OperationPlanner, OperationExecutor } from './operation-executor.js'; import { GitDetector } from './git-detector.js'; import { BackupManager } from './backup-manager.js'; import { AIClassifierFactory } from './ai-classifier.js'; import { GitIgnoreManager } from './gitignore-manager.js'; import { getPreferencesManager } from './user-preferences.js'; import { ProjectConventionAnalyzer } from './project-convention-analyzer.js'; import * as fs from 'fs/promises'; import * as path from 'path'; export class AutoExecutor { detector = new GitDetector(); classifier = new IntelligentClassifier(); gitignoreManager = new GitIgnoreManager(); conventionAnalyzer = new ProjectConventionAnalyzer(); projectConventions; /** * Automatically clean up repository using AI */ async execute(options) { const startTime = Date.now(); const errors = []; try { // Step 1: Check AI availability and prompt user if not configured if (!await AIClassifierFactory.isAvailable()) { const preferences = getPreferencesManager(); const shouldPrompt = await preferences.shouldPromptForAPIKey(); if (shouldPrompt) { console.log('\n⚠️ No AI API key configured\n'); console.log('Auto mode works best with AI classification for accurate results:'); console.log(' • With AI: 90% accuracy'); console.log(' • Without AI: 65% accuracy (heuristics only)\n'); console.log('To enable AI classification, add an API key:'); console.log(' devibe ai-key add anthropic <your-key> # Recommended: Claude'); console.log(' devibe ai-key add openai <your-key> # Alternative: GPT-4'); console.log(' devibe ai-key add google <your-key> # Budget: Gemini\n'); // Ask user if they want to continue without AI const readline = await import('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); const answer = await new Promise((resolve) => { rl.question('Continue with heuristics only? (y/n): ', (answer) => { rl.close(); resolve(answer.toLowerCase().trim()); }); }); if (answer !== 'y' && answer !== 'yes') { throw new Error('Operation cancelled. Please add an API key and try again.'); } // Increment decline count const currentCount = (await preferences.get('apiKeyPromptDeclineCount')) || 0; await preferences.incrementAPIKeyPromptDecline(); // If this was the second decline, let them know if (currentCount === 1) { console.log('\n💡 Note: We won\'t ask about API keys again.'); console.log(' To re-enable this prompt: devibe ai-key reset-prompt\n'); } console.log('📊 Continuing with heuristics-based classification...\n'); } else { // User has already declined twice, just continue silently with heuristics if (options.verbose) { console.log('📊 Using heuristics-based classification (no AI key configured)...\n'); } } } this.reportProgress(options, 0, 6, 'Initializing auto executor...'); // Step 2: Detect repositories this.reportProgress(options, 1, 7, 'Detecting repositories...'); const repoResult = await this.detector.detectRepositories(options.path); // If no git repos found, treat the directory as a single "non-git" repository let repositoriesToProcess = repoResult.repositories; if (repositoriesToProcess.length === 0) { repositoriesToProcess = [{ path: options.path, rootPath: options.path, isRoot: true }]; } // Step 2.5: Update .gitignore files (only for actual git repos) if (!options.dryRun && repoResult.repositories.length > 0) { this.reportProgress(options, 2, 7, 'Updating .gitignore files...'); const gitignoreResult = await this.gitignoreManager.updateAllRepositories(repoResult.repositories); if (options.verbose) { console.log(`\n${GitIgnoreManager.formatResult(gitignoreResult)}`); } } // Step 3: Analyze project structure and conventions (for intelligent classification) this.reportProgress(options, 3, 7, 'Analyzing project structure and conventions...'); // Analyze existing conventions to respect project patterns (with caching) this.projectConventions = await this.conventionAnalyzer.analyzeWithCache(options.path, repositoriesToProcess); if (options.verbose) { console.log(this.conventionAnalyzer.getSummary(this.projectConventions)); } else { console.log(`\n📋 Detected project conventions - respecting existing structure\n`); } // Set conventions on classifier so it can use them for smarter classification this.classifier.setProjectConventions(this.projectConventions); await this.classifier.classifyBatch([], repositoriesToProcess); // Step 4: Create execution plan using intelligent classification this.reportProgress(options, 4, 7, 'Creating execution plan with AI...'); const planner = new OperationPlanner(this.detector, this.classifier, undefined // Skip usage detection for auto mode (faster) ); const plan = await planner.planRootFileDistribution(options.path, (current, total, file) => { this.reportProgress(options, 4, 7, `Analyzing files: ${current}/${total} - ${path.basename(file)}`); }); // Initialize result variables let executionResult = { success: true, operationsCompleted: 0, operationsFailed: 0, backupManifestId: undefined, errors: [], }; // Step 5: Execute operations with backup (if any) if (plan.operations.length > 0) { this.reportProgress(options, 5, 7, `Executing ${plan.operations.length} operations...`); const backupManager = new BackupManager(path.join(options.path, '.unvibe', 'backups')); const executor = new OperationExecutor(backupManager); const execResult = await executor.execute(plan, options.dryRun || false); executionResult = { success: execResult.success, operationsCompleted: execResult.operationsCompleted, operationsFailed: execResult.operationsFailed, backupManifestId: execResult.backupManifestId, errors: execResult.errors, }; // Step 6.5: Create documentation index if documents were moved if (!options.dryRun && executionResult.success) { await this.createDocumentationIndex(options.path, plan.operations); } } // Step 6.8: Consolidate markdown documentation if requested if (executionResult.success && options.consolidateDocs && options.consolidateDocs !== 'none') { this.reportProgress(options, 6, 7, 'Consolidating markdown documentation...'); // Always show consolidation message (not just in verbose mode) const action = options.dryRun ? 'Would compress' : 'Compressing'; console.log(`\n📄 ${action} markdown files (consolidate + cleanup originals)...`); try { await this.consolidateMarkdownDocumentation(options.path, options.consolidateDocs, options.verbose || false, options.dryRun || false); } catch (error) { console.error(`\n⚠️ Markdown consolidation failed: ${error.message}`); // Don't fail the entire auto-executor if consolidation fails } } // Step 6.9: Generate docs folder index if (executionResult.success) { try { await this.generateDocsIndex(options.path, options.verbose || false, options.dryRun || false); } catch (error) { if (options.verbose) { console.error(`\n⚠️ Docs index generation failed: ${error.message}`); } // Don't fail if docs index fails } } // Step 6.10: Generate scripts folder index if (executionResult.success) { try { await this.generateScriptsIndex(options.path, options.verbose || false, options.dryRun || false); } catch (error) { if (options.verbose) { console.error(`\n⚠️ Scripts index generation failed: ${error.message}`); } // Don't fail if scripts index fails } } // Step 7: Complete this.reportProgress(options, 7, 7, 'Auto cleanup complete!'); return { success: executionResult.success, filesAnalyzed: plan.operations.length, filesMovedOrDeleted: executionResult.operationsCompleted, operationsCompleted: executionResult.operationsCompleted, operationsFailed: executionResult.operationsFailed, backupManifestId: executionResult.backupManifestId, errors: executionResult.errors, duration: Date.now() - startTime, }; } catch (error) { errors.push(error instanceof Error ? error.message : String(error)); return { success: false, filesAnalyzed: 0, filesMovedOrDeleted: 0, operationsCompleted: 0, operationsFailed: 0, errors, duration: Date.now() - startTime, }; } } /** * Preview what auto mode would do (dry-run) */ async preview(options) { // Check AI availability if (!await AIClassifierFactory.isAvailable()) { throw new Error('AI classification is required for auto mode. Please set ANTHROPIC_API_KEY or OPENAI_API_KEY.'); } this.reportProgress(options, 0, 3, 'Analyzing repository...'); // Detect repositories const repoResult = await this.detector.detectRepositories(options.path); // If no git repos found, treat the directory as a single "non-git" repository let repositoriesToProcess = repoResult.repositories; if (repositoriesToProcess.length === 0) { repositoriesToProcess = [{ path: options.path, rootPath: options.path, isRoot: true }]; } this.reportProgress(options, 1, 3, 'Analyzing project structure...'); await this.classifier.classifyBatch([], repositoriesToProcess); // Create plan this.reportProgress(options, 2, 3, 'Creating execution plan...'); const planner = new OperationPlanner(this.detector, this.classifier, undefined); const plan = await planner.planRootFileDistribution(options.path, (current, total, file) => { this.reportProgress(options, 2, 3, `Analyzing: ${current}/${total} - ${path.basename(file)}`); }); this.reportProgress(options, 3, 3, 'Preview ready!'); return { operations: plan.operations, warnings: plan.warnings, estimatedDuration: plan.estimatedDuration, }; } /** * Load folder preference from project-specific .devibe/config.json */ async loadFolderPreference(repoPath, type) { const configPath = path.join(repoPath, '.devibe', 'config.json'); try { const data = await fs.readFile(configPath, 'utf-8'); const config = JSON.parse(data); if (type === 'docs') { return config.folderPreferences?.docsFolderChoice || null; } else { return config.folderPreferences?.scriptsFolderChoice || null; } } catch { return null; } } /** * Save folder preference to project-specific .devibe/config.json */ async saveFolderPreference(repoPath, type, choice) { const devibePath = path.join(repoPath, '.devibe'); const configPath = path.join(devibePath, 'config.json'); // Ensure .devibe directory exists await fs.mkdir(devibePath, { recursive: true }); // Load existing config or create new one let config = {}; try { const data = await fs.readFile(configPath, 'utf-8'); config = JSON.parse(data); } catch { // Config doesn't exist, start fresh } // Update folder preferences if (!config.folderPreferences) { config.folderPreferences = {}; } if (type === 'docs') { config.folderPreferences.docsFolderChoice = choice; } else { config.folderPreferences.scriptsFolderChoice = choice; } // Save config await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8'); } /** * Check for docs/documents folder conflicts and prompt user * * Logic: * - If only one exists: use it automatically (no prompt) * - If both exist: check saved preference in .devibe/config.json * - If preference exists: use it * - If no preference: prompt user and save their choice */ async resolveFolderConflict(repoPath, dryRun = false) { const docsPath = path.join(repoPath, 'docs'); const documentsPath = path.join(repoPath, 'documents'); let docsExists = false; let documentsExists = false; try { const docsStat = await fs.stat(docsPath); docsExists = docsStat.isDirectory(); } catch { // docs doesn't exist } try { const documentsStat = await fs.stat(documentsPath); documentsExists = documentsStat.isDirectory(); } catch { // documents doesn't exist } // If both exist, check saved preference or prompt user if (docsExists && documentsExists) { // Check for saved preference in .devibe/config.json const savedChoice = await this.loadFolderPreference(repoPath, 'docs'); if (savedChoice) { console.log(`\n📁 Using saved preference: ${savedChoice}/ folder`); return savedChoice; } // No saved preference - prompt user console.log('\n⚠️ Found both docs/ and documents/ folders\n'); console.log('Options:'); console.log(' 1. Use docs/ folder (skip documents/)'); console.log(' 2. Use documents/ folder (skip docs/)'); console.log(' 3. Merge documents/ → docs/ (move all files)'); console.log(' 4. Merge docs/ → documents/ (move all files)'); console.log(' 5. Skip indexing for now\n'); const readline = await import('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); const answer = await new Promise((resolve) => { rl.question('Choose option (1-5): ', (answer) => { rl.close(); resolve(answer.trim()); }); }); let choice = null; switch (answer) { case '1': choice = 'docs'; await this.saveFolderPreference(repoPath, 'docs', 'docs'); console.log('✓ Saved preference: docs/'); break; case '2': choice = 'documents'; await this.saveFolderPreference(repoPath, 'docs', 'documents'); console.log('✓ Saved preference: documents/'); break; case '3': if (!dryRun) { await this.mergeFolders(documentsPath, docsPath); console.log('✓ Merged documents/ → docs/'); } else { console.log('📋 Would merge: documents/ → docs/'); } choice = 'docs'; await this.saveFolderPreference(repoPath, 'docs', 'docs'); console.log('✓ Saved preference: docs/'); break; case '4': if (!dryRun) { await this.mergeFolders(docsPath, documentsPath); console.log('✓ Merged docs/ → documents/'); } else { console.log('📋 Would merge: docs/ → documents/'); } choice = 'documents'; await this.saveFolderPreference(repoPath, 'docs', 'documents'); console.log('✓ Saved preference: documents/'); break; case '5': choice = null; break; default: console.log('Invalid choice, skipping indexing'); choice = null; } return choice; } // Only one exists - return it if (docsExists) return 'docs'; if (documentsExists) return 'documents'; // Neither exists return null; } /** * Merge source folder into destination folder */ async mergeFolders(sourcePath, destPath) { const mergeRecursive = async (src, dest) => { const entries = await fs.readdir(src, { withFileTypes: true }); for (const entry of entries) { const srcPath = path.join(src, entry.name); const destPath = path.join(dest, entry.name); if (entry.isDirectory()) { await fs.mkdir(destPath, { recursive: true }); await mergeRecursive(srcPath, destPath); } else { // Check if file already exists in destination try { await fs.stat(destPath); // File exists - prompt for overwrite console.log(` ⚠️ ${entry.name} exists in destination, keeping original`); } catch { // File doesn't exist - copy it await fs.copyFile(srcPath, destPath); } } } }; await mergeRecursive(sourcePath, destPath); // Remove source folder after merge await fs.rm(sourcePath, { recursive: true, force: true }); } /** * Generate intelligent docs folder index */ async generateDocsIndex(repoPath, verbose, dryRun = false) { // Check for folder conflicts first (always check, but behavior differs based on dryRun) const resolvedFolder = await this.resolveFolderConflict(repoPath, dryRun); if (resolvedFolder === null) { // User chose to skip or no folder exists return; } const { DocsIndexGenerator } = await import('./markdown-consolidation/docs-index-generator.js'); const generator = new DocsIndexGenerator(); if (dryRun) { const preview = await generator.preview(repoPath, this.projectConventions); if (verbose && preview) { console.log('\n' + preview); } } else { const result = await generator.generate(repoPath, this.projectConventions, false); if (result.filesIndexed > 0) { if (verbose) { console.log(`\n📚 Generated documentation index:`); console.log(` • Index: ${path.relative(repoPath, result.indexPath)}`); console.log(` • Categories: ${result.categoriesFound}`); console.log(` • Files indexed: ${result.filesIndexed}`); console.log(` • README updated: ${result.readmeUpdated ? '✓' : '✗'}`); } else { console.log(`\n📚 Documentation index created: ${result.filesIndexed} files indexed`); } } } } /** * Check for script/scripts folder conflicts and prompt user */ async resolveScriptsFolderConflict(repoPath, dryRun = false) { const scriptPath = path.join(repoPath, 'script'); const scriptsPath = path.join(repoPath, 'scripts'); let scriptExists = false; let scriptsExists = false; try { const scriptStat = await fs.stat(scriptPath); scriptExists = scriptStat.isDirectory(); } catch { // script doesn't exist } try { const scriptsStat = await fs.stat(scriptsPath); scriptsExists = scriptsStat.isDirectory(); } catch { // scripts doesn't exist } // If both exist, check saved preference or prompt user if (scriptExists && scriptsExists) { // Check for saved preference in .devibe/config.json const savedChoice = await this.loadFolderPreference(repoPath, 'scripts'); if (savedChoice) { console.log(`\n📁 Using saved preference: ${savedChoice}/ folder`); return savedChoice; } // No saved preference - prompt user console.log('\n⚠️ Found both script/ and scripts/ folders\n'); console.log('Options:'); console.log(' 1. Use scripts/ folder (skip script/)'); console.log(' 2. Use script/ folder (skip scripts/)'); console.log(' 3. Merge script/ → scripts/ (move all files)'); console.log(' 4. Merge scripts/ → script/ (move all files)'); console.log(' 5. Skip indexing for now\n'); const readline = await import('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); const answer = await new Promise((resolve) => { rl.question('Choose option (1-5): ', (answer) => { rl.close(); resolve(answer.trim()); }); }); let choice = null; switch (answer) { case '1': choice = 'scripts'; await this.saveFolderPreference(repoPath, 'scripts', 'scripts'); console.log('✓ Saved preference: scripts/'); break; case '2': choice = 'script'; await this.saveFolderPreference(repoPath, 'scripts', 'script'); console.log('✓ Saved preference: script/'); break; case '3': if (!dryRun) { await this.mergeFolders(scriptPath, scriptsPath); console.log('✓ Merged script/ → scripts/'); } else { console.log('📋 Would merge: script/ → scripts/'); } choice = 'scripts'; await this.saveFolderPreference(repoPath, 'scripts', 'scripts'); console.log('✓ Saved preference: scripts/'); break; case '4': if (!dryRun) { await this.mergeFolders(scriptsPath, scriptPath); console.log('✓ Merged scripts/ → script/'); } else { console.log('📋 Would merge: scripts/ → script/'); } choice = 'script'; await this.saveFolderPreference(repoPath, 'scripts', 'script'); console.log('✓ Saved preference: script/'); break; case '5': choice = null; break; default: console.log('Invalid choice, skipping indexing'); choice = null; } return choice; } // Only one exists - return it if (scriptsExists) return 'scripts'; if (scriptExists) return 'script'; // Neither exists return null; } /** * Generate intelligent scripts folder index */ async generateScriptsIndex(repoPath, verbose, dryRun = false) { // Check for folder conflicts first (always check, but behavior differs based on dryRun) const resolvedFolder = await this.resolveScriptsFolderConflict(repoPath, dryRun); if (resolvedFolder === null) { // User chose to skip or no folder exists return; } const { ScriptsIndexGenerator } = await import('./scripts-index-generator.js'); const generator = new ScriptsIndexGenerator(); if (dryRun) { const preview = await generator.preview(repoPath, this.projectConventions); if (verbose && preview) { console.log('\n' + preview); } } else { const result = await generator.generate(repoPath, this.projectConventions, false); if (result.scriptsIndexed > 0) { if (verbose) { console.log(`\n🔧 Generated scripts index:`); console.log(` • Index: ${path.relative(repoPath, result.indexPath)}`); console.log(` • Categories: ${result.categoriesFound}`); console.log(` • Scripts indexed: ${result.scriptsIndexed}`); console.log(` • README updated: ${result.readmeUpdated ? '✓' : '✗'}`); } else { console.log(`\n🔧 Scripts index created: ${result.scriptsIndexed} scripts indexed`); } } } } /** * Consolidate markdown documentation * * @param repoPath - Repository path * @param mode - 'safe' for folder-by-folder, 'aggressive' for summarize-all * @param verbose - Enable verbose output * @param dryRun - Preview what would be consolidated without making changes */ async consolidateMarkdownDocumentation(repoPath, mode, verbose, dryRun = false) { try { // Note: Main consolidation message is shown by caller // Only show this if in verbose mode for additional detail if (verbose) { const action = dryRun ? 'Preview' : 'Details'; console.log(` ℹ️ ${action}:`); } // Use the new AutoConsolidateService which does everything const { AutoConsolidateService } = await import('./markdown-consolidation/auto-consolidate-service.js'); const { MarkdownScanner } = await import('./markdown-consolidation/markdown-scanner.js'); const { MarkdownAnalyzer } = await import('./markdown-consolidation/markdown-analyzer.js'); const { AIContentAnalyzer } = await import('./markdown-consolidation/ai-content-analyzer.js'); const { MarkdownConsolidator } = await import('./markdown-consolidation/markdown-consolidator.js'); const { AIClassifierFactory } = await import('./ai-classifier.js'); const { BackupManager } = await import('./backup-manager.js'); // Get AI provider (optional) const preferredProvider = await AIClassifierFactory.getPreferredProvider(); const providerToUse = (preferredProvider === 'google' ? 'anthropic' : preferredProvider) || 'anthropic'; const aiProvider = await AIClassifierFactory.create(providerToUse); // Initialize components const scanner = new MarkdownScanner(); const analyzer = new MarkdownAnalyzer(); const aiAnalyzer = new AIContentAnalyzer(aiProvider); const backupDir = path.join(repoPath, '.devibe', 'backups'); const backupManager = new BackupManager(backupDir); const consolidator = new MarkdownConsolidator(aiAnalyzer, backupManager); // DRY RUN: Preview what would be consolidated if (dryRun) { const files = await scanner.scan({ targetDirectory: repoPath, recursive: false, excludePatterns: ['node_modules/**', '.git/**', '.devibe/**'], includeHidden: false }); const mdFiles = files.filter(f => path.basename(f.path).toLowerCase() !== 'readme.md'); if (mdFiles.length === 0) { if (verbose) { console.log(' ℹ️ No markdown files to consolidate'); } return; } console.log(`\n 📋 Consolidation Preview:`); console.log(` • Would consolidate ${mdFiles.length} markdown file(s):`); for (const file of mdFiles) { console.log(` - ${path.basename(file.path)} (${file.metadata.wordCount} words)`); } // Check for related files (.txt, .log) try { const dirEntries = await fs.readdir(repoPath); const relatedFiles = dirEntries.filter(f => ['.txt', '.log'].includes(path.extname(f).toLowerCase()) && !f.startsWith('.')); if (relatedFiles.length > 0) { console.log(`\n • Would analyze ${relatedFiles.length} related file(s) with AI:`); for (const file of relatedFiles.slice(0, 5)) { console.log(` - ${file}`); } if (relatedFiles.length > 5) { console.log(` ... and ${relatedFiles.length - 5} more`); } } } catch { // Ignore if can't read directory } console.log(`\n • Would create: CONSOLIDATED_DOCUMENTATION.md`); console.log(` • Would update: README.md (if exists)`); console.log(` • Would backup originals to: .devibe/backups/`); console.log(` • Would create: BACKUP_INDEX.md`); console.log(` • Would delete original markdown files after backup`); console.log(` • Would clean up UUID backup artifacts\n`); return; } // ACTUAL EXECUTION const autoService = new AutoConsolidateService(scanner, analyzer, aiAnalyzer, consolidator, backupManager); // Run consolidation in compress mode (default) // This will consolidate all .md files, include related .txt files, and clean up const result = await autoService.execute({ targetDirectory: repoPath, mode: 'compress', // Always use compress mode for --auto maxOutputFiles: 1, // Single consolidated file suppressToC: false, respectGitBoundaries: false, // Process current repo only includeRelated: true // Include .txt, .log files }); if (result.success && verbose) { console.log(` ✓ Consolidated ${result.processedFiles} markdown files`); if (result.consolidatedFiles.length > 0) { console.log(` ✓ Created: ${path.basename(result.consolidatedFiles[0])}`); } } } catch (error) { if (verbose) { console.error(` ⚠️ Consolidation failed: ${error.message}`); } // Don't throw - consolidation failure shouldn't stop the main cleanup } } /** * Report progress if callback provided */ reportProgress(options, current, total, message) { if (options.onProgress) { options.onProgress(current, total, message); } } /** * Create documentation index in documents folder */ async createDocumentationIndex(repoPath, operations) { // Find all operations that moved files to documents/ const docOperations = operations.filter(op => op.type === 'move' && op.targetPath?.includes('/documents/')); if (docOperations.length === 0) { return; // No docs moved, skip } const documentsDir = path.join(repoPath, 'documents'); // Check if documents directory exists try { await fs.access(documentsDir); } catch { return; // Documents dir doesn't exist } // Get list of all files in documents directory const files = await fs.readdir(documentsDir); const mdFiles = files .filter(f => f.endsWith('.md')) .sort(); // Group files by category const categories = { 'AI & Intelligence': [], 'Setup & Configuration': [], 'Architecture & Design': [], 'Features & Demos': [], 'Reference & Specs': [], 'Other Documentation': [] }; for (const file of mdFiles) { const lower = file.toLowerCase(); if (lower.startsWith('ai_') || lower.includes('intelligent') || lower.includes('batching')) { categories['AI & Intelligence'].push(file); } else if (lower.includes('setup') || lower.includes('config') || lower.includes('install')) { categories['Setup & Configuration'].push(file); } else if (lower.includes('architecture') || lower.includes('design') || lower.includes('spec')) { categories['Architecture & Design'].push(file); } else if (lower.includes('demo') || lower.includes('feature') || lower.includes('mode')) { categories['Features & Demos'].push(file); } else if (lower.includes('reference') || lower.includes('requirement') || lower.includes('changelog')) { categories['Reference & Specs'].push(file); } else { categories['Other Documentation'].push(file); } } // Create index content let indexContent = `# Documentation Index Welcome to the D-Vibe documentation! All project documentation has been organized here for easy navigation. **📍 You are here:** \`documents/\` **🏠 Main README:** [\`../README.md\`](../README.md) --- ## 📚 Documentation Categories `; // Add each category for (const [category, files] of Object.entries(categories)) { if (files.length > 0) { indexContent += `### ${category}\n\n`; for (const file of files) { const name = file.replace(/\.md$/, '').replace(/_/g, ' '); indexContent += `- [${name}](./${file})\n`; } indexContent += '\n'; } } // Add footer with stats indexContent += `--- ## 📊 Quick Stats - **Total Documents:** ${mdFiles.length} - **Organized by:** D-Vibe Auto Mode - **Last Updated:** ${new Date().toISOString().split('T')[0]} --- **Tip:** Use your IDE's file search or \`grep\` to find specific topics across all documentation. `; // Write index file const indexPath = path.join(documentsDir, 'README.md'); await fs.writeFile(indexPath, indexContent, 'utf-8'); } } //# sourceMappingURL=auto-executor.js.map