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
JavaScript
/**
* 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));
}