@sethdouglasford/claude-flow
Version:
Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology
243 lines ⢠10.8 kB
JavaScript
/**
* Migration Analyzer - Analyzes existing projects for migration readiness
*/
import * as path from "node:path";
import * as fs from "fs-extra";
import * as crypto from "crypto";
import { readFile } from "node:fs/promises";
import { logger } from "./logger";
import chalk from "chalk";
import glob from "glob";
export class MigrationAnalyzer {
optimizedCommands = [
"sparc", "sparc-architect", "sparc-code", "sparc-tdd",
"claude-flow-help", "claude-flow-memory", "claude-flow-swarm",
];
async analyze(projectPath) {
logger.info(`Analyzing project at ${projectPath}...`);
const analysis = {
projectPath,
hasClaudeFolder: false,
hasOptimizedPrompts: false,
customCommands: [],
customConfigurations: {},
conflictingFiles: [],
migrationRisks: [],
recommendations: [],
timestamp: new Date(),
};
// Check for .claude folder
const claudePath = path.join(projectPath, ".claude");
if (await fs.pathExists(claudePath)) {
analysis.hasClaudeFolder = true;
// Analyze existing commands
await this.analyzeCommands(claudePath, analysis);
// Check for optimized prompts
await this.checkOptimizedPrompts(claudePath, analysis);
// Analyze configurations
await this.analyzeConfigurations(projectPath, analysis);
// Detect conflicts
await this.detectConflicts(projectPath, analysis);
}
// Generate risks and recommendations
this.assessRisks(analysis);
this.generateRecommendations(analysis);
return analysis;
}
async analyzeCommands(claudePath, analysis) {
const commandsPath = path.join(claudePath, "commands");
if (await fs.pathExists(commandsPath)) {
const files = glob.sync("**/*.md", { cwd: commandsPath });
for (const file of files) {
const commandName = path.basename(file, ".md");
if (!this.optimizedCommands.includes(commandName)) {
analysis.customCommands.push(commandName);
}
}
}
}
async checkOptimizedPrompts(claudePath, analysis) {
// Check for key optimized prompt files
const optimizedFiles = [
"BATCHTOOLS_GUIDE.md",
"BATCHTOOLS_BEST_PRACTICES.md",
"MIGRATION_GUIDE.md",
"PERFORMANCE_BENCHMARKS.md",
];
let hasOptimized = 0;
for (const file of optimizedFiles) {
if (await fs.pathExists(path.join(claudePath, file))) {
hasOptimized++;
}
}
analysis.hasOptimizedPrompts = hasOptimized >= 2;
}
async analyzeConfigurations(projectPath, analysis) {
// Check for CLAUDE.md
const claudeMdPath = path.join(projectPath, "CLAUDE.md");
if (await fs.pathExists(claudeMdPath)) {
const content = await readFile(claudeMdPath, "utf-8");
analysis.customConfigurations["CLAUDE.md"] = {
exists: true,
size: content.length,
hasCustomContent: !content.includes("SPARC Development Environment"),
};
}
// Check for .roomodes
const roomodesPath = path.join(projectPath, ".roomodes");
if (await fs.pathExists(roomodesPath)) {
try {
const roomodes = await fs.readJson(roomodesPath);
analysis.customConfigurations[".roomodes"] = {
exists: true,
modeCount: Object.keys(roomodes).length,
customModes: Object.keys(roomodes).filter(mode => !["architect", "code", "tdd", "debug", "docs-writer"].includes(mode)),
};
}
catch (error) {
analysis.migrationRisks.push({
level: "medium",
description: "Invalid .roomodes file",
file: roomodesPath,
mitigation: "File will be backed up and replaced",
});
}
}
}
async detectConflicts(projectPath, analysis) {
// Check for files that might conflict with migration
const potentialConflicts = [
".claude/commands/sparc.md",
".claude/BATCHTOOLS_GUIDE.md",
"memory/memory-store.json",
"coordination/config.json",
];
for (const file of potentialConflicts) {
const filePath = path.join(projectPath, file);
if (await fs.pathExists(filePath)) {
const content = await readFile(filePath, "utf-8");
const checksum = crypto.createHash("md5").update(content).digest("hex");
// Check if it's a custom version
if (!this.isStandardFile(file, checksum)) {
analysis.conflictingFiles.push(file);
}
}
}
}
isStandardFile(file, checksum) {
// This would contain checksums of standard files
// For now, we'll assume all existing files are potentially custom
return false;
}
assessRisks(analysis) {
// High risk: Custom commands that might be overwritten
if (analysis.customCommands.length > 0) {
analysis.migrationRisks.push({
level: "high",
description: `Found ${analysis.customCommands.length} custom commands that may be affected`,
mitigation: "Use --preserve-custom flag or selective migration",
});
}
// Medium risk: Existing optimized prompts
if (analysis.hasOptimizedPrompts) {
analysis.migrationRisks.push({
level: "medium",
description: "Project already has some optimized prompts",
mitigation: "Consider using merge strategy to preserve customizations",
});
}
// Low risk: No .claude folder
if (!analysis.hasClaudeFolder) {
analysis.migrationRisks.push({
level: "low",
description: "No existing .claude folder found",
mitigation: "Fresh installation will be performed",
});
}
// High risk: Conflicting files
if (analysis.conflictingFiles.length > 0) {
analysis.migrationRisks.push({
level: "high",
description: `${analysis.conflictingFiles.length} files may have custom modifications`,
mitigation: "Files will be backed up before migration",
});
}
}
generateRecommendations(analysis) {
// Strategy recommendations
if (analysis.customCommands.length > 0 || analysis.conflictingFiles.length > 0) {
analysis.recommendations.push("Use \"selective\" or \"merge\" strategy to preserve customizations");
}
else if (!analysis.hasClaudeFolder) {
analysis.recommendations.push("Use \"full\" strategy for clean installation");
}
// Backup recommendations
if (analysis.hasClaudeFolder) {
analysis.recommendations.push("Create a backup before migration (automatic with default settings)");
}
// Custom command recommendations
if (analysis.customCommands.length > 0) {
analysis.recommendations.push(`Review custom commands: ${analysis.customCommands.join(", ")}`);
}
// Validation recommendations
if (analysis.migrationRisks.some(r => r.level === "high")) {
analysis.recommendations.push("Run with --dry-run first to preview changes");
}
}
printAnalysis(analysis, detailed = false) {
console.log(chalk.bold("\nš Migration Analysis Report"));
console.log(chalk.gray("ā".repeat(50)));
console.log(`\n${chalk.bold("Project:")} ${analysis.projectPath}`);
console.log(`${chalk.bold("Timestamp:")} ${analysis.timestamp.toISOString()}`);
// Status
console.log(chalk.bold("\nš Current Status:"));
console.log(` ⢠.claude folder: ${analysis.hasClaudeFolder ? chalk.green("ā") : chalk.red("ā")}`);
console.log(` ⢠Optimized prompts: ${analysis.hasOptimizedPrompts ? chalk.green("ā") : chalk.red("ā")}`);
console.log(` ⢠Custom commands: ${analysis.customCommands.length > 0 ? chalk.yellow(analysis.customCommands.length) : chalk.green("0")}`);
console.log(` ⢠Conflicts: ${analysis.conflictingFiles.length > 0 ? chalk.yellow(analysis.conflictingFiles.length) : chalk.green("0")}`);
// Risks
if (analysis.migrationRisks.length > 0) {
console.log(chalk.bold("\nā ļø Migration Risks:"));
analysis.migrationRisks.forEach(risk => {
const icon = risk.level === "high" ? "š“" : risk.level === "medium" ? "š”" : "š¢";
console.log(` ${icon} ${chalk.bold(risk.level.toUpperCase())}: ${risk.description}`);
if (risk.mitigation) {
console.log(` ${chalk.gray("ā")} ${chalk.italic(risk.mitigation)}`);
}
});
}
// Recommendations
if (analysis.recommendations.length > 0) {
console.log(chalk.bold("\nš” Recommendations:"));
analysis.recommendations.forEach(rec => {
console.log(` ⢠${rec}`);
});
}
// Detailed information
if (detailed) {
if (analysis.customCommands.length > 0) {
console.log(chalk.bold("\nš§ Custom Commands:"));
analysis.customCommands.forEach(cmd => {
console.log(` ⢠${cmd}`);
});
}
if (analysis.conflictingFiles.length > 0) {
console.log(chalk.bold("\nš Conflicting Files:"));
analysis.conflictingFiles.forEach(file => {
console.log(` ⢠${file}`);
});
}
if (Object.keys(analysis.customConfigurations).length > 0) {
console.log(chalk.bold("\nāļø Configurations:"));
Object.entries(analysis.customConfigurations).forEach(([file, config]) => {
console.log(` ⢠${file}: ${JSON.stringify(config, null, 2)}`);
});
}
}
console.log(chalk.gray(`\n${"ā".repeat(50)}`));
}
async saveAnalysis(analysis, outputPath) {
await fs.writeJson(outputPath, analysis, { spaces: 2 });
}
}
//# sourceMappingURL=migration-analyzer.js.map