UNPKG

claude-code-automation

Version:

🚀 Generic project automation system with anti-compaction protection and recovery capabilities. Automatically detects project type (React, Node.js, Python, Rust, Go, Java) and provides intelligent analysis. Claude Code optimized - run 'welcome' after inst

519 lines (444 loc) 19.6 kB
/** * Context Preservation Engine * Anti-compaction system that maintains project state and context * Ensures project can be fully reconstructed after prompt compaction */ const fs = require('fs').promises; const path = require('path'); class ContextPreservationEngine { constructor() { // Use current working directory as project root, not relative to this module this.projectRoot = process.cwd(); this.stateDir = path.join(this.projectRoot, '.automation/state'); this.decisionsDir = path.join(this.projectRoot, '.automation/decisions'); this.automationDir = path.join(this.projectRoot, '.automation'); } /** * Preserve complete project state for compaction resistance * Creates redundant, comprehensive project documentation */ async preserveCurrentState() { const timestamp = new Date().toISOString(); console.log(`🔄 Preserving project state at ${timestamp}`); try { const projectState = { timestamp, metadata: await this.captureProjectMetadata(), phase: await this.getCurrentPhase(), progress: await this.captureProgress(), architecture: await this.captureArchitecture(), codebase: await this.captureCodebaseSnapshot(), tests: await this.captureTestState(), dependencies: await this.captureDependencies(), decisions: await this.captureDecisionHistory(), automationState: await this.captureAutomationState() }; // Store in multiple locations for redundancy await this.storeStateRedundantly(projectState, timestamp); // Update living documentation await this.updateLivingDocumentation(projectState); // Create recovery instructions await this.generateRecoveryInstructions(projectState); console.log(`✅ Project state preserved successfully`); return projectState; } catch (error) { // During installation, silently handle errors and continue if (process.argv.includes('install')) { return { timestamp, error: 'skipped-during-install' }; } console.error(`❌ Error preserving project state:`, error.message); throw error; } } /** * Capture essential project metadata */ async captureProjectMetadata() { const packageJson = JSON.parse( await fs.readFile(path.join(this.projectRoot, 'package.json'), 'utf8') ); return { name: packageJson.name, version: packageJson.version, description: packageJson.description, scripts: packageJson.scripts, dependencies: packageJson.dependencies, devDependencies: packageJson.devDependencies, projectStructure: await this.captureDirectoryStructure() }; } /** * Determine current development phase */ async getCurrentPhase() { // Analyze completed tasks and current state to determine phase const completedFeatures = await this.analyzeCompletedFeatures(); const testCoverage = await this.getTestCoverage(); const codeComplexity = await this.analyzeCodeComplexity(); return { currentSprint: this.determineCurrentSprint(completedFeatures), completedEpics: completedFeatures.epics, completedStories: completedFeatures.stories, testCoverage, codeComplexity, nextPriorities: this.determineNextPriorities(completedFeatures) }; } /** * Capture current project progress */ async captureProgress() { return { projectFeatures: await this.analyzeProjectFeatures(), testingInfrastructure: await this.analyzeTestingState(), cicdPipeline: await this.analyzeCICDState(), automationLevel: await this.analyzeAutomationLevel(), qualityMetrics: await this.captureQualityMetrics() }; } /** * Capture architectural decisions and patterns */ async captureArchitecture() { return { patterns: await this.identifyArchitecturalPatterns(), modules: await this.analyzeModuleStructure(), interfaces: await this.documentInterfaces(), dataFlow: await this.mapDataFlow(), designDecisions: await this.captureDesignDecisions() }; } /** * Create snapshot of codebase with analysis */ async captureCodebaseSnapshot() { const snapshot = { files: {}, statistics: await this.calculateCodeStatistics(), complexity: await this.analyzeCodeComplexity(), dependencies: await this.analyzeDependencyGraph() }; // Capture key files with metadata const keyFiles = await this.identifyKeyFiles(); for (const file of keyFiles) { const content = await fs.readFile(file, 'utf8'); snapshot.files[file] = { content, size: content.length, lines: content.split('\n').length, lastModified: (await fs.stat(file)).mtime, analysis: await this.analyzeFile(content, file) }; } return snapshot; } /** * Store project state with redundancy */ async storeStateRedundantly(projectState, timestamp) { const stateFile = `project-state-${timestamp.replace(/[:.]/g, '-')}.json`; // Ensure state directory exists await fs.mkdir(this.stateDir, { recursive: true }); // Primary storage await fs.writeFile( path.join(this.stateDir, stateFile), JSON.stringify(projectState, null, 2) ); // Backup storage in multiple locations const backupLocations = [ path.join(this.projectRoot, '.automation', 'backups', stateFile), path.join(this.automationDir, 'latest-state.json') ]; for (const location of backupLocations) { await fs.mkdir(path.dirname(location), { recursive: true }); await fs.writeFile(location, JSON.stringify(projectState, null, 2)); } // Create compressed summary for quick reference const summary = this.createStateSummary(projectState); await fs.writeFile( path.join(this.stateDir, 'current-state-summary.json'), JSON.stringify(summary, null, 2) ); } /** * Update living documentation based on current state */ async updateLivingDocumentation(projectState) { // Store documentation in automation directory only const techDoc = await this.generateTechnicalDocumentation(projectState); await fs.writeFile( path.join(this.automationDir, 'technical-overview.md'), techDoc ); // Store project state summary const stateSummary = await this.generateStateSummary(projectState); await fs.writeFile( path.join(this.automationDir, 'project-summary.md'), stateSummary ); // Update progress documentation const progressDoc = await this.generateProgressDocumentation(projectState); await fs.writeFile( path.join(this.automationDir, 'current-progress.md'), progressDoc ); } /** * Generate recovery instructions for project reconstruction */ async generateRecoveryInstructions(projectState) { const instructions = `# Project Recovery Instructions Generated: ${projectState.timestamp} ## Quick Recovery (5 minutes) 1. \`npm install\` - Install dependencies 2. \`npm run test\` - Verify basic functionality 3. \`npm start\` - Launch application 4. Check .automation/state/current-state-summary.json for latest status ## Full Context Recovery (15 minutes) 1. Review .automation/technical-overview.md for architecture 2. Check .automation/current-progress.md for completion status 3. Examine source directory structure for implementation details 4. Run automation scripts for full analysis ## Current Phase: ${projectState.phase.currentSprint} ## Next Priority: ${projectState.phase.nextPriorities[0] || 'Analyze current state'} ## Key Files to Examine: ${Object.keys(projectState.codebase.files).map(file => `- ${file}`).join('\n')} ## Automation Status: - Testing Infrastructure: ${projectState.progress.testingInfrastructure.status} - CI/CD Pipeline: ${projectState.progress.cicdPipeline.status} - Project Features: ${projectState.progress.projectFeatures.implementationStatus} ## Recovery Verification: - [ ] Application launches without errors - [ ] All tests pass: \`npm test\` - [ ] Core features are accessible - [ ] Context preservation system is functional For detailed recovery, see .automation/recovery/ directory. `; await fs.mkdir(path.join(this.projectRoot, '.automation/recovery'), { recursive: true }); await fs.writeFile( path.join(this.projectRoot, '.automation/recovery', 'RECOVERY.md'), instructions ); } /** * Analyze project features and components */ async analyzeProjectFeatures() { try { // Look for HTML files with feature attributes const htmlFiles = await this.findFilesWithExtension('.html'); let features = []; for (const htmlFile of htmlFiles) { try { const content = await fs.readFile(htmlFile, 'utf8'); // Look for common feature patterns const dataAttributes = content.match(/data-\w+="[^"]+"/g) || []; const classNames = content.match(/class="[^"]*"/g) || []; features.push(...dataAttributes, ...classNames); } catch (error) { // Skip files that can't be read } } // Also check JavaScript/TypeScript files for feature exports const jsFiles = await this.findFilesWithExtension('.js', '.ts', '.jsx', '.tsx'); for (const jsFile of jsFiles.slice(0, 10)) { // Limit to first 10 files try { const content = await fs.readFile(jsFile, 'utf8'); const exports = content.match(/export\s+(?:const|function|class)\s+(\w+)/g) || []; features.push(...exports); } catch (error) { // Skip files that can't be read } } return { total: features.length, implemented: features.slice(0, Math.min(20, features.length)), // Limit output pending: [], implementationStatus: `${features.length} features detected` }; } catch (error) { return { error: error.message, total: 0, implemented: [], pending: [] }; } } async analyzeTestingState() { // Analyze current testing infrastructure try { const vitestConfig = await fs.readFile( path.join(this.projectRoot, 'vitest.config.js'), 'utf8' ); return { status: 'configured', framework: 'vitest', hasConfig: true, coverageEnabled: vitestConfig.includes('coverage') }; } catch (error) { return { status: 'not-configured', error: error.message }; } } async identifyKeyFiles() { const keyFiles = []; // Always include package.json and project documentation const essentialFiles = ['package.json', 'README.md', 'CLAUDE.md', '.gitignore']; for (const file of essentialFiles) { const fullPath = path.join(this.projectRoot, file); try { await fs.access(fullPath); keyFiles.push(fullPath); } catch (error) { // File doesn't exist, skip it } } // Find main application files (entry points) const commonEntryPoints = [ 'index.js', 'index.ts', 'main.js', 'main.ts', 'app.js', 'app.ts', 'src/index.js', 'src/index.ts', 'src/main.js', 'src/main.ts', 'src/app.js', 'src/app.ts', 'public/index.html', 'index.html' ]; for (const entryPoint of commonEntryPoints) { const fullPath = path.join(this.projectRoot, entryPoint); try { await fs.access(fullPath); keyFiles.push(fullPath); } catch (error) { // File doesn't exist, skip it } } // Find configuration files const configFiles = await this.findFilesWithExtension('.json', '.config.js', '.config.ts'); keyFiles.push(...configFiles.filter(f => f.includes('config') || f.includes('package.json') || f.includes('tsconfig') ).slice(0, 5)); // Limit to 5 config files // Find the largest source files (likely important) const sourceFiles = await this.findFilesWithExtension('.js', '.ts', '.jsx', '.tsx'); const sourceFilesWithSize = []; for (const file of sourceFiles.slice(0, 20)) { // Limit to first 20 for performance try { const stats = await fs.stat(file); sourceFilesWithSize.push({ path: file, size: stats.size }); } catch (error) { // Skip files we can't stat } } // Sort by size and take the largest ones sourceFilesWithSize.sort((a, b) => b.size - a.size); keyFiles.push(...sourceFilesWithSize.slice(0, 10).map(f => f.path)); // Remove duplicates and return return [...new Set(keyFiles)]; } async findFilesWithExtension(...extensions) { const files = []; const walkDir = async (dir) => { try { const entries = await fs.readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory() && !entry.name.startsWith('.') && !entry.name.includes('node_modules')) { await walkDir(fullPath); } else if (entry.isFile()) { const ext = path.extname(entry.name); if (extensions.includes(ext)) { files.push(fullPath); } } } } catch (error) { // Skip directories we can't read } }; await walkDir(this.projectRoot); return files; } async generateStateSummary(projectState) { const fileCount = Object.keys(projectState.codebase?.files || {}).length; const keyFiles = Object.keys(projectState.codebase?.files || {}); return `# Project State Summary Auto-generated: ${new Date().toISOString()} ## Current Status: ${projectState.phase?.currentSprint || 'Unknown'} Components: ${fileCount} files tracked ## Project Health - Phase: ${projectState.phase?.currentSprint || 'Unknown'} - Files: ${fileCount} tracked - Test Coverage: ${projectState.phase?.testCoverage || 'unknown'} - Automation Level: ${projectState.progress?.automationLevel || 'basic'} ## Key Components ${keyFiles.slice(0, 5).map(f => `- ${f}`).join('\n') || '- No files analyzed yet'} *This is an automated summary stored in .automation/ directory* `; } createStateSummary(projectState) { return { timestamp: projectState.timestamp, phase: projectState.phase.currentSprint, projectFeatures: projectState.progress.projectFeatures.implementationStatus, testCoverage: projectState.phase.testCoverage || 'unknown', automationLevel: projectState.progress.automationLevel || 'basic', nextPriority: projectState.phase.nextPriorities[0] || 'analyze-state', keyMetrics: { filesAnalyzed: Object.keys(projectState.codebase.files).length, totalLines: projectState.codebase.statistics?.totalLines || 0, testFiles: projectState.tests?.testFiles?.length || 0 } }; } // Placeholder methods for future implementation async captureTestState() { return { status: 'pending', testFiles: [] }; } async captureDependencies() { return { npm: [], dev: [] }; } async captureDecisionHistory() { return []; } async captureAutomationState() { return { level: 'basic' }; } async captureDirectoryStructure() { return {}; } async analyzeCompletedFeatures() { return { epics: [], stories: [] }; } async getTestCoverage() { return 'unknown'; } async analyzeCodeComplexity() { return { average: 'medium' }; } async analyzeCICDState() { return { status: 'not-configured' }; } async analyzeAutomationLevel() { return 'basic'; } async captureQualityMetrics() { return {}; } async identifyArchitecturalPatterns() { return []; } async analyzeModuleStructure() { return {}; } async documentInterfaces() { return {}; } async mapDataFlow() { return {}; } async captureDesignDecisions() { return []; } async calculateCodeStatistics() { return { totalLines: 0 }; } async analyzeDependencyGraph() { return {}; } async analyzeFile(content, file) { return { type: 'unknown' }; } determineCurrentSprint(features) { // Logic to determine current sprint based on completed features return 'Sprint 1: Foundation'; } determineNextPriorities(features) { return ['Set up testing infrastructure', 'Implement automation']; } async generateTechnicalDocumentation(state) { return `# Technical Overview Generated: ${state.timestamp} ## Architecture Current phase: ${state.phase.currentSprint} ## Implementation Status ${state.progress.projectFeatures.implemented.map(feature => `✅ ${feature}`).join('\n')} ${state.progress.projectFeatures.pending.map(feature => `⏳ ${feature}`).join('\n')} ## Next Steps ${state.phase.nextPriorities.map(priority => `- ${priority}`).join('\n')} `; } async generateProgressDocumentation(state) { return `# Current Progress Updated: ${state.timestamp} ## Sprint Progress: ${state.phase.currentSprint} ### Completed ${state.phase.completedStories.map(story => `✅ ${story}`).join('\n') || '- Initial setup'} ### In Progress - Context preservation system implementation ### Next ${state.phase.nextPriorities.map(priority => `⏳ ${priority}`).join('\n')} `; } } module.exports = ContextPreservationEngine; // Auto-execute if run directly if (require.main === module) { const engine = new ContextPreservationEngine(); engine.preserveCurrentState() .then(() => console.log('✅ Context preservation completed')) .catch(error => console.error('❌ Context preservation failed:', error)); }