UNPKG

automagik-genie

Version:

Universal AI development companion that can be initialized in any codebase

499 lines (420 loc) 15.3 kB
const fs = require('fs').promises; const path = require('path'); /** * ValidationEngine - Validates update system components and operations * Provides pre/post update validation and system integrity checks */ class ValidationEngine { constructor() { this.validationRules = { metadata: this.validateMetadataStructure.bind(this), backup: this.validateBackupIntegrity.bind(this), templates: this.validateTemplateCache.bind(this), files: this.validateFileStructure.bind(this), system: this.validateSystemState.bind(this) }; } /** * Run comprehensive system validation * @param {Object} options - Validation options * @returns {Object} Validation results */ async runFullValidation(options = {}) { const { projectPath = process.cwd(), checkRemote = false } = options; const results = { overall: true, timestamp: new Date().toISOString(), results: {}, errors: [], warnings: [], recommendations: [] }; // Run all validation rules for (const [ruleName, ruleFunction] of Object.entries(this.validationRules)) { try { console.log(`🔍 Validating ${ruleName}...`); const ruleResult = await ruleFunction(projectPath, options); results.results[ruleName] = ruleResult; if (!ruleResult.valid) { results.overall = false; results.errors.push(...ruleResult.errors); } results.warnings.push(...ruleResult.warnings); results.recommendations.push(...ruleResult.recommendations); } catch (error) { results.overall = false; results.errors.push(`${ruleName} validation failed: ${error.message}`); results.results[ruleName] = { valid: false, errors: [error.message], warnings: [], recommendations: [] }; } } return results; } /** * Validate metadata structure and consistency * @param {string} projectPath - Project path * @param {Object} options - Options * @returns {Object} Validation result */ async validateMetadataStructure(projectPath, options) { const result = { valid: true, errors: [], warnings: [], recommendations: [] }; const { MetadataManager } = require('./metadata'); const metadata = new MetadataManager(); try { // Check if registries exist and are readable const stats = await metadata.getRegistryStats(); // Validate registry structure const agentRegistry = await metadata.loadAgentRegistry(); const hookRegistry = await metadata.loadHookRegistry(); const systemVersion = await metadata.loadSystemVersion(); // Check required fields if (!agentRegistry.version || !agentRegistry.lastUpdate) { result.errors.push('Agent registry missing required fields'); result.valid = false; } if (!hookRegistry.version || !hookRegistry.lastUpdate) { result.errors.push('Hook registry missing required fields'); result.valid = false; } if (!systemVersion.installedVersion) { result.errors.push('System version missing installed version'); result.valid = false; } // Check for inconsistencies if (stats.agents.total === 0 && stats.hooks.total === 0) { result.warnings.push('No agents or hooks registered - run scan to populate registries'); } } catch (error) { result.valid = false; result.errors.push(`Metadata validation failed: ${error.message}`); } return result; } /** * Validate backup system integrity * @param {string} projectPath - Project path * @param {Object} options - Options * @returns {Object} Validation result */ async validateBackupIntegrity(projectPath, options) { const result = { valid: true, errors: [], warnings: [], recommendations: [] }; const { BackupManager } = require('./backup'); const backup = new BackupManager(); try { // List available backups const backups = await backup.listAvailableBackups(); let validBackups = 0; let corruptedBackups = 0; // Validate each backup for (const backupInfo of backups) { if (backupInfo.valid) { validBackups++; } else { corruptedBackups++; result.warnings.push(`Corrupted backup found: ${backupInfo.id}`); } } if (corruptedBackups > 0) { result.recommendations.push(`Clean up ${corruptedBackups} corrupted backups`); } if (validBackups === 0 && backups.length > 0) { result.errors.push('No valid backups available - backup system may be compromised'); result.valid = false; } if (backups.length > 50) { result.warnings.push(`Large number of backups (${backups.length}) - consider cleanup`); result.recommendations.push('Run cleanup command to remove old backups'); } } catch (error) { result.valid = false; result.errors.push(`Backup validation failed: ${error.message}`); } return result; } /** * Validate template cache * @param {string} projectPath - Project path * @param {Object} options - Options * @returns {Object} Validation result */ async validateTemplateCache(projectPath, options) { const result = { valid: true, errors: [], warnings: [], recommendations: [] }; const { TemplateManager } = require('./templates'); const templates = new TemplateManager(); try { // List cached versions const cachedVersions = await templates.listCachedVersions(); let validCaches = 0; let invalidCaches = 0; for (const versionInfo of cachedVersions) { if (versionInfo.valid) { validCaches++; } else { invalidCaches++; result.warnings.push(`Invalid template cache: ${versionInfo.version}`); } } if (invalidCaches > 0) { result.recommendations.push(`Clean ${invalidCaches} invalid template caches`); } if (cachedVersions.length === 0) { result.warnings.push('No template cache found - templates will be downloaded on first update'); } // Check if we can fetch latest release if (options.checkRemote) { try { await templates.fetchLatestRelease(); } catch (error) { result.warnings.push('Cannot check for latest release - network connectivity issue'); } } } catch (error) { result.warnings.push(`Template cache validation warning: ${error.message}`); } return result; } /** * Validate file structure * @param {string} projectPath - Project path * @param {Object} options - Options * @returns {Object} Validation result */ async validateFileStructure(projectPath, options) { const result = { valid: true, errors: [], warnings: [], recommendations: [] }; try { // Check if this is a Genie project const claudeDir = path.join(projectPath, '.claude'); if (!await this.fileExists(claudeDir)) { result.errors.push('.claude directory not found - not a Genie project'); result.valid = false; return result; } // Check required directories const requiredDirs = [ '.claude/agents', '.claude/hooks', '.claude/hooks/examples' ]; for (const dir of requiredDirs) { const dirPath = path.join(projectPath, dir); if (!await this.fileExists(dirPath)) { result.warnings.push(`Missing directory: ${dir}`); result.recommendations.push(`Create missing directory: ${dir}`); } } // Check for common agent files const agentsDir = path.join(projectPath, '.claude/agents'); if (await this.fileExists(agentsDir)) { const agentFiles = await fs.readdir(agentsDir); const mdFiles = agentFiles.filter(f => f.endsWith('.md')); if (mdFiles.length === 0) { result.warnings.push('No agent files found in .claude/agents'); result.recommendations.push('Run initialization to create base agents'); } // Check for critical agents const criticalAgents = ['genie-analyzer.md']; for (const critical of criticalAgents) { if (!mdFiles.includes(critical)) { result.warnings.push(`Missing critical agent: ${critical}`); } } } // Check CLAUDE.md file const claudeMdPath = path.join(projectPath, 'CLAUDE.md'); if (!await this.fileExists(claudeMdPath)) { result.warnings.push('CLAUDE.md file not found'); result.recommendations.push('Ensure CLAUDE.md exists for proper Claude integration'); } } catch (error) { result.valid = false; result.errors.push(`File structure validation failed: ${error.message}`); } return result; } /** * Validate overall system state * @param {string} projectPath - Project path * @param {Object} options - Options * @returns {Object} Validation result */ async validateSystemState(projectPath, options) { const result = { valid: true, errors: [], warnings: [], recommendations: [] }; try { // Check Node.js version const nodeVersion = process.version; const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0]); if (majorVersion < 14) { result.errors.push(`Node.js version ${nodeVersion} is not supported (minimum: 14.0.0)`); result.valid = false; } // Check available disk space in backup directory const os = require('os'); const backupDir = path.join(os.homedir(), '.automagik-genie', 'backups'); try { const stats = await fs.stat(backupDir); // This is a basic check - in production you'd want to check actual disk space result.warnings.push('Backup directory exists and is accessible'); } catch (error) { result.warnings.push('Backup directory not yet created - will be created on first backup'); } // Check permissions try { const testFile = path.join(projectPath, '.claude', '.permission-test'); await fs.writeFile(testFile, 'test', 'utf-8'); await fs.unlink(testFile); } catch (error) { result.errors.push('Insufficient permissions to write to .claude directory'); result.valid = false; } // Check package.json dependencies const packageJsonPath = path.join(__dirname, '../../package.json'); if (await this.fileExists(packageJsonPath)) { const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8')); const requiredDeps = ['inquirer', 'colors', 'yargs', 'tar']; for (const dep of requiredDeps) { if (!packageJson.dependencies[dep]) { result.warnings.push(`Missing dependency: ${dep}`); result.recommendations.push('Run npm install to ensure all dependencies are available'); } } } } catch (error) { result.valid = false; result.errors.push(`System state validation failed: ${error.message}`); } return result; } /** * Validate specific update operation * @param {Object} updatePlan - Update plan to validate * @returns {Object} Validation result */ async validateUpdatePlan(updatePlan) { const result = { valid: true, errors: [], warnings: [], recommendations: [] }; try { // Check if update plan has required fields if (!updatePlan.fileAnalysis || !Array.isArray(updatePlan.fileAnalysis)) { result.errors.push('Update plan missing file analysis'); result.valid = false; return result; } // Validate each file in the plan for (const fileAnalysis of updatePlan.fileAnalysis) { if (!fileAnalysis.filePath) { result.errors.push('File analysis missing file path'); result.valid = false; } if (!fileAnalysis.action) { result.errors.push(`File analysis missing action for ${fileAnalysis.filePath}`); result.valid = false; } // Check for high-risk operations if (fileAnalysis.risk === 'high') { result.warnings.push(`High-risk update planned for ${fileAnalysis.fileName}`); result.recommendations.push('Review high-risk updates carefully'); } // Check for conflicts if (fileAnalysis.details && fileAnalysis.details.conflicts.length > 0) { result.warnings.push(`Conflicts detected in ${fileAnalysis.fileName}`); result.recommendations.push('Resolve conflicts before proceeding'); } } // Check overall update scope const totalFiles = updatePlan.fileAnalysis.length; if (totalFiles > 20) { result.warnings.push(`Large update scope (${totalFiles} files)`); result.recommendations.push('Consider updating in smaller batches'); } } catch (error) { result.valid = false; result.errors.push(`Update plan validation failed: ${error.message}`); } return result; } /** * Check if file exists * @param {string} filePath - File path to check * @returns {boolean} True if file exists */ async fileExists(filePath) { try { await fs.access(filePath); return true; } catch (error) { return false; } } /** * Generate validation report * @param {Object} validationResults - Results from validation * @returns {string} Formatted report */ generateReport(validationResults) { const lines = []; lines.push('═'.repeat(60)); lines.push('🔍 AUTOMAGIK GENIE UPDATE SYSTEM VALIDATION REPORT'); lines.push('═'.repeat(60)); lines.push(''); lines.push(`Overall Status: ${validationResults.overall ? '✅ VALID' : '❌ INVALID'}`); lines.push(`Timestamp: ${validationResults.timestamp}`); lines.push(''); // Results by category for (const [category, result] of Object.entries(validationResults.results)) { const status = result.valid ? '✅' : '❌'; lines.push(`${status} ${category.toUpperCase()}`); if (result.errors.length > 0) { lines.push(' Errors:'); result.errors.forEach(error => lines.push(` • ${error}`)); } if (result.warnings.length > 0) { lines.push(' Warnings:'); result.warnings.forEach(warning => lines.push(` • ${warning}`)); } lines.push(''); } // Overall recommendations if (validationResults.recommendations.length > 0) { lines.push('💡 RECOMMENDATIONS:'); const uniqueRecommendations = [...new Set(validationResults.recommendations)]; uniqueRecommendations.forEach(rec => lines.push(` • ${rec}`)); lines.push(''); } lines.push('─'.repeat(60)); return lines.join('\n'); } } module.exports = { ValidationEngine };