claude-flow
Version:
Enterprise-grade AI agent orchestration with WASM-powered ReasoningBank memory and AgentDB vector database (always uses latest agentic-flow)
355 lines (299 loc) • 10.6 kB
text/typescript
/**
* 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.'));
}
}
}