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