UNPKG

@jmkim85/dev-flow-mcp

Version:

MCP-based Dev Flow - AI-powered development workflow management with 13 essential tools for TDD and context management

325 lines (323 loc) โ€ข 14.7 kB
#!/usr/bin/env node /** * Dev Flow helper โ€“ remove .devflow/tmp & IDE scrap files */ import { promises as fs } from 'fs'; import { join } from 'path'; import { glob } from 'glob'; import { exec } from 'child_process'; import { promisify } from 'util'; const execAsync = promisify(exec); async function cleanTempFiles(options = {}) { const root = process.cwd(); const devflowDir = join(root, '.devflow'); const tmpDir = join(devflowDir, 'tmp'); let cleaned = 0; let gitCleaned = 0; let oldCheckpointsRemoved = 0; let brokenFilesFixed = 0; let totalSizeFreed = 0; const directoriesCreated = []; const patternsCleaned = []; const errors = []; const { useGitClean = false, cleanOldCheckpoints = true, fixBrokenFiles = true, dryRun = false } = options; if (dryRun) { console.log('๐Ÿ” DRY RUN MODE - No files will be actually deleted\n'); } try { // 1. Clean .devflow/tmp directory with size tracking try { const tmpFiles = await fs.readdir(tmpDir); if (tmpFiles.length > 0) { // Calculate size before deletion let tmpSize = 0; for (const file of tmpFiles) { try { const filePath = join(tmpDir, file); const stats = await fs.stat(filePath); tmpSize += stats.size; } catch { // File might be inaccessible } } if (!dryRun) { await fs.rm(tmpDir, { recursive: true, force: true }); await fs.mkdir(tmpDir, { recursive: true }); } console.log(`๐Ÿ—‘๏ธ Cleaned ${tmpFiles.length} files from .devflow/tmp/ (${formatBytes(tmpSize)})`); cleaned += tmpFiles.length; totalSizeFreed += tmpSize; patternsCleaned.push('.devflow/tmp/*'); } } catch { // tmp directory doesn't exist, create it if (!dryRun) { await fs.mkdir(tmpDir, { recursive: true }); } console.log('๐Ÿ“ Created .devflow/tmp/ directory'); directoriesCreated.push('.devflow/tmp'); } // 2. Git clean integration (4-1 requirement) if (useGitClean) { try { console.log('๐Ÿ”ง Running git clean...'); // First, show what would be cleaned (dry run) const { stdout: dryRunOutput } = await execAsync('git clean -fdXn', { cwd: root }); const filesToClean = dryRunOutput.trim().split('\n').filter(line => line.startsWith('Would remove')); if (filesToClean.length > 0) { console.log(` Found ${filesToClean.length} git-ignored files to clean:`); filesToClean.slice(0, 5).forEach(line => console.log(` - ${line.replace('Would remove ', '')}`)); if (filesToClean.length > 5) { console.log(` ... and ${filesToClean.length - 5} more`); } if (!dryRun) { // Actually clean the files await execAsync('git clean -fdX', { cwd: root }); } gitCleaned = filesToClean.length; patternsCleaned.push('git-ignored-files'); } else { console.log(' No git-ignored files to clean'); } } catch (error) { if (error.message.includes('not a git repository')) { console.log(' โ„น๏ธ Not a git repository - skipping git clean'); } else { errors.push(`Git clean failed: ${error.message}`); } } } // 3. Enhanced temporary files cleanup with size tracking console.log('๐Ÿงน Cleaning temporary files...'); const tempPatterns = [ { pattern: '**/.DS_Store', description: 'macOS metadata files' }, { pattern: '**/Thumbs.db', description: 'Windows thumbnail cache' }, { pattern: '**/*.tmp', description: 'Temporary files' }, { pattern: '**/*.temp', description: 'Temporary files' }, { pattern: '**/*~', description: 'Backup files' }, { pattern: '**/.#*', description: 'Lock files' }, { pattern: '**/._*', description: 'macOS resource forks' }, { pattern: '**/*.log', description: 'Log files' }, { pattern: '**/*.bak', description: 'Backup files' }, { pattern: '**/*.swp', description: 'Vim swap files' }, { pattern: '**/*.swo', description: 'Vim swap files' }, { pattern: '**/desktop.ini', description: 'Windows folder settings' }, { pattern: '**/.vscode/settings.json.bak', description: 'VSCode backup settings' } ]; for (const { pattern, description } of tempPatterns) { try { const files = await glob(pattern, { cwd: root, ignore: ['node_modules/**', '.git/**', '.devflow/context/**'], absolute: true }); if (files.length > 0) { let patternSize = 0; for (const file of files) { try { const stats = await fs.stat(file); patternSize += stats.size; if (!dryRun) { await fs.unlink(file); } cleaned++; } catch (error) { errors.push(`Failed to delete ${file}: ${error.message}`); } } console.log(` - ${description}: ${files.length} files (${formatBytes(patternSize)})`); totalSizeFreed += patternSize; patternsCleaned.push(pattern); } } catch (error) { errors.push(`Pattern ${pattern} failed: ${error.message}`); } } // 4. Clean old checkpoints (4-1 requirement) if (cleanOldCheckpoints) { try { console.log('๐Ÿ“ฆ Cleaning old checkpoints...'); const stateFile = join(devflowDir, 'state.yaml'); if (await fs.access(stateFile).then(() => true).catch(() => false)) { const stateContent = await fs.readFile(stateFile, 'utf-8'); // Simple checkpoint cleanup - remove checkpoints older than 30 days const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); // This is a simplified approach - in a real implementation, // we would parse YAML and filter checkpoints by date const lines = stateContent.split('\n'); const oldCheckpointLines = lines.filter(line => line.includes('timestamp:') && line.includes('2024') && // Simple date check new Date(line.split('timestamp:')[1]?.trim() || '') < thirtyDaysAgo); if (oldCheckpointLines.length > 0) { console.log(` Found ${oldCheckpointLines.length} old checkpoint entries`); oldCheckpointsRemoved = oldCheckpointLines.length; // Note: Actual removal would require proper YAML parsing console.log(' โ„น๏ธ Old checkpoint removal requires manual review'); } else { console.log(' No old checkpoints found'); } } } catch (error) { errors.push(`Checkpoint cleanup failed: ${error.message}`); } } // 5. Fix broken files (4-1 requirement) if (fixBrokenFiles) { try { console.log('๐Ÿ”ง Checking for broken files...'); // Check for empty or corrupted YAML files const yamlFiles = await glob('**/*.yaml', { cwd: devflowDir, absolute: true, ignore: ['**/node_modules/**'] }); for (const yamlFile of yamlFiles) { try { const content = await fs.readFile(yamlFile, 'utf-8'); // Check for empty files if (content.trim().length === 0) { console.log(` - Fixed empty YAML file: ${yamlFile}`); if (!dryRun) { await fs.writeFile(yamlFile, '# Empty YAML file\n'); } brokenFilesFixed++; } // Check for files with only whitespace else if (content.trim().length < 10 && content.includes('\n\n\n')) { console.log(` - Fixed whitespace-only file: ${yamlFile}`); if (!dryRun) { await fs.writeFile(yamlFile, content.trim() + '\n'); } brokenFilesFixed++; } } catch (error) { if (error.code === 'ENOENT') { // File was deleted during processing continue; } errors.push(`Failed to check ${yamlFile}: ${error.message}`); } } if (brokenFilesFixed > 0) { console.log(` Fixed ${brokenFilesFixed} broken files`); } else { console.log(' No broken files found'); } } catch (error) { errors.push(`Broken file check failed: ${error.message}`); } } // 6. Summary report console.log('\n๐Ÿ“Š Cleanup Summary:'); console.log(` Temporary files cleaned: ${cleaned}`); console.log(` Git-ignored files cleaned: ${gitCleaned}`); console.log(` Old checkpoints removed: ${oldCheckpointsRemoved}`); console.log(` Broken files fixed: ${brokenFilesFixed}`); console.log(` Total space freed: ${formatBytes(totalSizeFreed)}`); console.log(` Patterns processed: ${patternsCleaned.length}`); if (errors.length > 0) { console.log(` Errors encountered: ${errors.length}`); } const totalCleaned = cleaned + gitCleaned + oldCheckpointsRemoved + brokenFilesFixed; if (totalCleaned > 0) { console.log(`\nโœ… Successfully cleaned ${totalCleaned} items total`); } else { console.log('\nโœจ No cleanup needed - project is already clean!'); } return { cleaned_files: cleaned, git_cleaned_files: gitCleaned, old_checkpoints_removed: oldCheckpointsRemoved, broken_files_fixed: brokenFilesFixed, total_size_freed: totalSizeFreed, directories_created: directoriesCreated, patterns_cleaned: patternsCleaned, errors: errors }; } catch (error) { console.error('โŒ Error during cleanup:', error.message); return { cleaned_files: 0, git_cleaned_files: 0, old_checkpoints_removed: 0, broken_files_fixed: 0, total_size_freed: 0, directories_created: directoriesCreated, patterns_cleaned: patternsCleaned, errors: [...errors, `Fatal error: ${error.message}`] }; } } // Utility function to format bytes function formatBytes(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; } async function main() { console.log('๐Ÿงน Dev Flow Enhanced Temporary File Cleanup v2.1\n'); // Parse command line arguments const args = process.argv.slice(2); const options = { useGitClean: args.includes('--git') || args.includes('-g'), cleanOldCheckpoints: !args.includes('--no-checkpoints'), fixBrokenFiles: !args.includes('--no-fix'), dryRun: args.includes('--dry-run') || args.includes('-n') }; if (args.includes('--help') || args.includes('-h')) { console.log(`Usage: node clean-temp.js [options] Options: --git, -g Enable git clean integration --no-checkpoints Skip old checkpoint cleanup --no-fix Skip broken file fixing --dry-run, -n Show what would be cleaned without actually deleting --help, -h Show this help message Examples: node clean-temp.js # Basic cleanup node clean-temp.js --git # Include git-ignored files node clean-temp.js --dry-run # Preview what would be cleaned node clean-temp.js --git --dry-run # Preview git clean + temp files `); return; } console.log('Options:'); console.log(` Git clean: ${options.useGitClean ? 'enabled' : 'disabled'}`); console.log(` Checkpoint cleanup: ${options.cleanOldCheckpoints ? 'enabled' : 'disabled'}`); console.log(` Broken file fixing: ${options.fixBrokenFiles ? 'enabled' : 'disabled'}`); console.log(` Dry run: ${options.dryRun ? 'enabled' : 'disabled'}`); console.log(''); const result = await cleanTempFiles(options); // Exit with error code if there were errors if (result.errors.length > 0) { console.error('\nโŒ Cleanup completed with errors:'); result.errors.forEach(error => console.error(` - ${error}`)); process.exit(1); } } // Auto-run if this file is executed directly const isMainModule = process.argv[1] && import.meta.url.endsWith(process.argv[1].replace(/\\/g, '/')); if (isMainModule) { main().catch(error => { console.error('โŒ Fatal error:', error); process.exit(1); }); } export { cleanTempFiles, formatBytes }; //# sourceMappingURL=clean-temp.js.map