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
JavaScript
/**
* 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