UNPKG

claude-flow

Version:

Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)

329 lines (271 loc) 10.7 kB
import { getErrorMessage } from '../utils/error-handler.js'; /** * Migration Validator - Validates successful migration */ import * as fs from 'fs-extra'; import * as path from 'path'; import type { ValidationResult, ValidationCheck } from './types.js'; import { logger } from './logger.js'; import * as chalk from 'chalk'; import { glob } from 'glob'; export class MigrationValidator { private requiredFiles = [ '.claude/commands/sparc.md', '.claude/commands/claude-flow-help.md', '.claude/commands/claude-flow-memory.md', '.claude/BATCHTOOLS_GUIDE.md', '.claude/BATCHTOOLS_BEST_PRACTICES.md' ]; private requiredCommands = [ 'sparc', 'sparc-architect', 'sparc-code', 'sparc-tdd', 'claude-flow-help', 'claude-flow-memory', 'claude-flow-swarm' ]; async validate(projectPath: string): Promise<ValidationResult> { const result: ValidationResult = { valid: true, checks: [], errors: [], warnings: [] }; // Check file structure await this.validateFileStructure(projectPath, result); // Check command files await this.validateCommandFiles(projectPath, result); // Check configuration files await this.validateConfiguration(projectPath, result); // Check file integrity await this.validateFileIntegrity(projectPath, result); // Check functionality await this.validateFunctionality(projectPath, result); // Overall validation result.valid = result.errors.length === 0; return result; } private async validateFileStructure(projectPath: string, result: ValidationResult): Promise<void> { const check: ValidationCheck = { name: 'File Structure', passed: true }; // Check .claude directory exists const claudePath = path.join(projectPath, '.claude'); if (!await fs.pathExists(claudePath)) { check.passed = false; result.errors.push('.claude directory not found'); } // Check commands directory const commandsPath = path.join(claudePath, 'commands'); if (!await fs.pathExists(commandsPath)) { check.passed = false; result.errors.push('.claude/commands directory not found'); } // Check required files for (const file of this.requiredFiles) { const filePath = path.join(projectPath, file); if (!await fs.pathExists(filePath)) { check.passed = false; result.errors.push(`Required file missing: ${file}`); } } result.checks.push(check); } private async validateCommandFiles(projectPath: string, result: ValidationResult): Promise<void> { const check: ValidationCheck = { name: 'Command Files', passed: true }; const commandsPath = path.join(projectPath, '.claude/commands'); if (await fs.pathExists(commandsPath)) { for (const command of this.requiredCommands) { const commandFile = path.join(commandsPath, `${command}.md`); const sparcCommandFile = path.join(commandsPath, 'sparc', `${command.replace('sparc-', '')}.md`); const hasMainFile = await fs.pathExists(commandFile); const hasSparcFile = await fs.pathExists(sparcCommandFile); if (!hasMainFile && !hasSparcFile) { check.passed = false; result.errors.push(`Command file missing: ${command}.md`); } else { // Validate file content const filePath = hasMainFile ? commandFile : sparcCommandFile; await this.validateCommandFileContent(filePath, command, result); } } } else { check.passed = false; result.errors.push('Commands directory not found'); } result.checks.push(check); } private async validateCommandFileContent(filePath: string, command: string, result: ValidationResult): Promise<void> { try { const content = await fs.readFile(filePath, 'utf-8'); // Check for minimum content requirements const hasDescription = content.includes('description') || content.includes('Description'); const hasInstructions = content.length > 100; // Minimum content length if (!hasDescription) { result.warnings.push(`Command ${command} may be missing description`); } if (!hasInstructions) { result.warnings.push(`Command ${command} may have insufficient content`); } // Check for optimization indicators const hasOptimizedContent = content.includes('optimization') || content.includes('performance') || content.includes('efficient'); if (!hasOptimizedContent && command.includes('sparc')) { result.warnings.push(`SPARC command ${command} may not be optimized`); } } catch (error) { result.errors.push(`Failed to validate ${command}: ${(error instanceof Error ? error.message : String(error))}`); } } private async validateConfiguration(projectPath: string, result: ValidationResult): Promise<void> { const check: ValidationCheck = { name: 'Configuration Files', passed: true }; // Check CLAUDE.md const claudeMdPath = path.join(projectPath, 'CLAUDE.md'); if (await fs.pathExists(claudeMdPath)) { const content = await fs.readFile(claudeMdPath, 'utf-8'); // Check for SPARC configuration if (!content.includes('SPARC')) { result.warnings.push('CLAUDE.md may not include SPARC configuration'); } // Check for key sections const requiredSections = [ 'Project Overview', 'SPARC Development', 'Memory Integration' ]; for (const section of requiredSections) { if (!content.includes(section)) { result.warnings.push(`CLAUDE.md missing section: ${section}`); } } } else { result.warnings.push('CLAUDE.md not found'); } // Check .roomodes const roomodesPath = path.join(projectPath, '.roomodes'); if (await fs.pathExists(roomodesPath)) { try { const roomodes = await fs.readJson(roomodesPath); const requiredModes = ['architect', 'code', 'tdd', 'debug']; for (const mode of requiredModes) { if (!roomodes[mode]) { result.warnings.push(`Missing SPARC mode: ${mode}`); } } } catch (error) { result.errors.push(`Invalid .roomodes file: ${(error instanceof Error ? error.message : String(error))}`); check.passed = false; } } result.checks.push(check); } private async validateFileIntegrity(projectPath: string, result: ValidationResult): Promise<void> { const check: ValidationCheck = { name: 'File Integrity', passed: true }; // Check for corrupted files const claudePath = path.join(projectPath, '.claude'); if (await fs.pathExists(claudePath)) { const files = await glob('**/*.md', { cwd: claudePath }); for (const file of files) { try { const content = await fs.readFile(path.join(claudePath, file), 'utf-8'); // Basic integrity checks if (content.length === 0) { result.errors.push(`Empty file: ${file}`); check.passed = false; } // Check for binary data in text files if (content.includes('\0')) { result.errors.push(`Corrupted text file: ${file}`); check.passed = false; } } catch (error) { result.errors.push(`Cannot read file ${file}: ${(error instanceof Error ? error.message : String(error))}`); check.passed = false; } } } result.checks.push(check); } private async validateFunctionality(projectPath: string, result: ValidationResult): Promise<void> { const check: ValidationCheck = { name: 'Functionality', passed: true }; // Check directory permissions const claudePath = path.join(projectPath, '.claude'); if (await fs.pathExists(claudePath)) { try { // Test write permissions const testFile = path.join(claudePath, '.test-write'); await fs.writeFile(testFile, 'test'); await fs.remove(testFile); } catch (error) { result.warnings.push('.claude directory may not be writable'); } } // Check for potential conflicts const packageJsonPath = path.join(projectPath, 'package.json'); if (await fs.pathExists(packageJsonPath)) { try { const packageJson = await fs.readJson(packageJsonPath); // Check for script conflicts const scripts = packageJson.scripts || {}; const conflictingScripts = Object.keys(scripts).filter(script => script.startsWith('claude-flow') || script.startsWith('sparc') ); if (conflictingScripts.length > 0) { result.warnings.push(`Potential script conflicts: ${conflictingScripts.join(', ')}`); } } catch (error) { result.warnings.push('Could not validate package.json'); } } result.checks.push(check); } printValidation(validation: ValidationResult): void { console.log(chalk.bold('\n✅ Migration Validation Report')); console.log(chalk.gray('─'.repeat(50))); console.log(`\n${chalk.bold('Overall Status:')} ${validation.valid ? chalk.green('✓ Valid') : chalk.red('✗ Invalid')}`); // Show checks console.log(chalk.bold('\n📋 Validation Checks:')); validation.checks.forEach(check => { const status = check.passed ? chalk.green('✓') : chalk.red('✗'); console.log(` ${status} ${check.name}`); if (check.message) { console.log(` ${chalk.gray(check.message)}`); } }); // Show errors if (validation.errors.length > 0) { console.log(chalk.bold('\n❌ Errors:')); validation.errors.forEach(error => { console.log(` • ${chalk.red(error)}`); }); } // Show warnings if (validation.warnings.length > 0) { console.log(chalk.bold('\n⚠️ Warnings:')); validation.warnings.forEach(warning => { console.log(` • ${chalk.yellow(warning)}`); }); } console.log(chalk.gray('\n' + '─'.repeat(50))); if (validation.valid) { console.log(chalk.green('\n🎉 Migration validation passed! Your project is ready to use optimized prompts.')); } else { console.log(chalk.red('\n⚠️ Migration validation failed. Please address the errors above.')); } } }