UNPKG

aiwf

Version:

AI Workflow Framework for Claude Code with multi-language support (Korean/English)

376 lines (338 loc) 11.8 kB
import fs from 'fs/promises'; import path from 'path'; import chalk from 'chalk'; import { TOOL_DIRS, AIWF_DIRS, FILES } from '../utils/paths.js'; // Validation constants const VALIDATION_CONSTANTS = { MIN_FILE_SIZE: 10, MIN_RULE_FILE_SIZE: 50, MIN_FILE_COUNT: { CURSOR_MDC: 2, WINDSURF_MD: 2, CLAUDE_COMMANDS: 4 } }; /** * Simplified validation with clean interface * @param {Array} selectedTools - Tools to validate * @param {string} language - Language code * @returns {Promise<Object>} Validation results */ export async function validateInstallation(selectedTools, language) { const results = { success: [], failed: [], warnings: [] }; // Always validate common files (AIWF core) const commonValid = await validateCommonFiles(); if (!commonValid.success) { results.failed.push({ tool: 'aiwf', reason: commonValid.reason }); } else { results.success.push('aiwf'); } // Validate selected tools if (selectedTools && selectedTools.length > 0) { for (const tool of selectedTools) { const validation = await validateTool(tool); if (validation.success) { results.success.push(tool); } else { results.failed.push({ tool, reason: validation.reason }); } } } else { // Default: validate all tools if no selection provided const allTools = ['claudeCode', 'geminiCLI', 'cursor', 'windsurf']; for (const tool of allTools) { const validation = await validateTool(tool); if (validation.success) { results.success.push(tool); } else { results.failed.push({ tool, reason: validation.reason }); } } } return results; } /** * Validate common/core files * @returns {Promise<Object>} Validation result */ async function validateCommonFiles() { try { const requiredFiles = [ FILES.CLAUDE_MD.ROOT, FILES.PROJECT_MANIFEST ]; for (const file of requiredFiles) { const exists = await fs .access(file) .then(() => true) .catch(() => false); if (!exists) { return { success: false, reason: `Missing core file: ${file}` }; } } return { success: true }; } catch (error) { return { success: false, reason: `Core validation error: ${error.message}` }; } } /** * Validate specific tool * @param {string} tool - Tool name * @returns {Promise<Object>} Validation result */ async function validateTool(tool) { switch (tool) { case 'claudeCode': case 'claude-code': return validateClaudeCode(); case 'geminiCLI': case 'gemini-cli': return validateGeminiCLI(); case 'cursor': return validateCursorTool(); case 'windsurf': return validateWindsurfTool(); case 'aiwf': return validateCommonFiles(); default: return { success: false, reason: `Unknown tool: ${tool}` }; } } /** * Validate Claude Code with simplified interface * @returns {Promise<Object>} Validation result */ async function validateClaudeCode() { try { // 기본 필수 파일 (현재 리포지토리에 존재하는 핵심 명령어) const baseRequired = [ 'aiwf_initialize.md', 'aiwf_do_task.md', 'aiwf_commit.md' ]; // 과거 필수였던 리뷰 명령어의 대체 후보들 const reviewPrimary = 'aiwf_code_review.md'; const reviewAlternatives = [ 'aiwf_yolo.md', 'aiwf_prime.md', 'aiwf_agent_workflow.md' ]; // 디렉토리 존재 및 파일 개수 간단 점검 const dirExists = await fs .access(TOOL_DIRS.CLAUDE_COMMANDS) .then(() => true) .catch(() => false); if (!dirExists) { return { success: false, reason: `Missing Claude commands directory: ${TOOL_DIRS.CLAUDE_COMMANDS}` }; } const allFiles = await fs.readdir(TOOL_DIRS.CLAUDE_COMMANDS); if (allFiles.length < VALIDATION_CONSTANTS.MIN_FILE_COUNT.CLAUDE_COMMANDS) { return { success: false, reason: `Insufficient Claude commands files: expected at least ${VALIDATION_CONSTANTS.MIN_FILE_COUNT.CLAUDE_COMMANDS}, found ${allFiles.length}` }; } // 기본 필수 파일 존재/크기 확인 for (const fileName of baseRequired) { const filePath = path.join(TOOL_DIRS.CLAUDE_COMMANDS, fileName); const exists = await fs .access(filePath) .then(() => true) .catch(() => false); if (!exists) { return { success: false, reason: `Missing file: ${filePath}` }; } try { const stats = await fs.stat(filePath); if (stats.size < VALIDATION_CONSTANTS.MIN_FILE_SIZE) { return { success: false, reason: `File ${fileName} is too small (${stats.size} bytes)` }; } } catch (error) { return { success: false, reason: `Cannot read file ${fileName}: ${error.message}` }; } } // 리뷰 명령어: 기본 파일 또는 대체 파일 중 하나라도 존재하면 통과 const reviewPrimaryPath = path.join(TOOL_DIRS.CLAUDE_COMMANDS, reviewPrimary); const primaryExists = await fs.access(reviewPrimaryPath).then(() => true).catch(() => false); let alternativeUsed = null; if (!primaryExists) { for (const alt of reviewAlternatives) { const altPath = path.join(TOOL_DIRS.CLAUDE_COMMANDS, alt); const altExists = await fs.access(altPath).then(() => true).catch(() => false); if (altExists) { alternativeUsed = alt; // 크기 확인 try { const stats = await fs.stat(altPath); if (stats.size < VALIDATION_CONSTANTS.MIN_FILE_SIZE) { return { success: false, reason: `File ${alt} is too small (${stats.size} bytes)` }; } } catch (error) { return { success: false, reason: `Cannot read file ${alt}: ${error.message}` }; } break; } } } if (!primaryExists && !alternativeUsed) { return { success: false, reason: `Missing file: ${reviewPrimary} (or one of alternatives: ${reviewAlternatives.join(', ')})` }; } return { success: true }; } catch (error) { return { success: false, reason: `Claude Code validation error: ${error.message}` }; } } /** * Validate Gemini CLI with simplified interface * @returns {Promise<Object>} Validation result */ async function validateGeminiCLI() { try { const dirExists = await fs .access(TOOL_DIRS.GEMINI_PROMPTS) .then(() => true) .catch(() => false); if (!dirExists) { return { success: false, reason: `Missing Gemini-CLI prompts directory: ${TOOL_DIRS.GEMINI_PROMPTS}` }; } return { success: true }; } catch (error) { return { success: false, reason: `Gemini-CLI validation error: ${error.message}` }; } } /** * Validate Cursor tool with simplified interface * @returns {Promise<Object>} Validation result */ async function validateCursorTool() { try { const dirExists = await fs .access(TOOL_DIRS.CURSOR_RULES) .then(() => true) .catch(() => false); if (!dirExists) { return { success: false, reason: `Missing Cursor rules directory: ${TOOL_DIRS.CURSOR_RULES}` }; } // Enhanced validation: Check file count and types const files = await fs.readdir(TOOL_DIRS.CURSOR_RULES); const mdcFiles = files.filter(file => file.endsWith('.mdc')); if (mdcFiles.length < VALIDATION_CONSTANTS.MIN_FILE_COUNT.CURSOR_MDC) { return { success: false, reason: `Cursor rules: Expected at least ${VALIDATION_CONSTANTS.MIN_FILE_COUNT.CURSOR_MDC} .mdc files, found ${mdcFiles.length}` }; } // Validate file sizes for (const file of mdcFiles) { const filePath = path.join(TOOL_DIRS.CURSOR_RULES, file); try { const stats = await fs.stat(filePath); if (stats.size < VALIDATION_CONSTANTS.MIN_RULE_FILE_SIZE) { return { success: false, reason: `Cursor rules file ${file} is too small (${stats.size} bytes, minimum ${VALIDATION_CONSTANTS.MIN_RULE_FILE_SIZE})` }; } } catch (error) { return { success: false, reason: `Cannot read Cursor rules file ${file}: ${error.message}` }; } } return { success: true }; } catch (error) { return { success: false, reason: `Cursor validation error: ${error.message}` }; } } /** * Validate Windsurf tool with simplified interface * @returns {Promise<Object>} Validation result */ async function validateWindsurfTool() { try { const dirExists = await fs .access(TOOL_DIRS.WINDSURF_RULES) .then(() => true) .catch(() => false); if (!dirExists) { return { success: false, reason: `Missing Windsurf rules directory: ${TOOL_DIRS.WINDSURF_RULES}` }; } // Enhanced validation: Check file count and types const files = await fs.readdir(TOOL_DIRS.WINDSURF_RULES); const mdFiles = files.filter(file => file.endsWith('.md')); if (mdFiles.length < VALIDATION_CONSTANTS.MIN_FILE_COUNT.WINDSURF_MD) { return { success: false, reason: `Windsurf rules: Expected at least ${VALIDATION_CONSTANTS.MIN_FILE_COUNT.WINDSURF_MD} .md files, found ${mdFiles.length}` }; } // Validate file sizes for (const file of mdFiles) { const filePath = path.join(TOOL_DIRS.WINDSURF_RULES, file); try { const stats = await fs.stat(filePath); if (stats.size < VALIDATION_CONSTANTS.MIN_RULE_FILE_SIZE) { return { success: false, reason: `Windsurf rules file ${file} is too small (${stats.size} bytes, minimum ${VALIDATION_CONSTANTS.MIN_RULE_FILE_SIZE})` }; } } catch (error) { return { success: false, reason: `Cannot read Windsurf rules file ${file}: ${error.message}` }; } } return { success: true }; } catch (error) { return { success: false, reason: `Windsurf validation error: ${error.message}` }; } } /** * Display validation results in spec-compliant format * @param {Object} validationResults - Validation results * @param {string} language - Language code */ export function displaySpecCompliantValidationResults(validationResults, language) { console.log(chalk.blue('\n=== Installation Validation Results ===')); // Success summary if (validationResults.success.length > 0) { console.log( chalk.green(`\n✅ Successfully Validated Tools (${validationResults.success.length}):`) ); validationResults.success.forEach(tool => { console.log(chalk.green(` ✓ ${tool}`)); }); } // Failed summary if (validationResults.failed.length > 0) { console.log(chalk.red(`\n❌ Failed Validations (${validationResults.failed.length}):`)); validationResults.failed.forEach(({ tool, reason }) => { console.log(chalk.red(` ✗ ${tool}: ${reason}`)); }); } // Warnings summary if (validationResults.warnings.length > 0) { console.log(chalk.yellow(`\n⚠️ Warnings (${validationResults.warnings.length}):`)); validationResults.warnings.forEach(warning => { console.log(chalk.yellow(` ⚠ ${warning}`)); }); } // Overall status const isSuccess = validationResults.failed.length === 0; const statusIcon = isSuccess ? '✅' : '❌'; const statusColor = isSuccess ? chalk.green : chalk.red; const statusText = isSuccess ? 'PASSED' : 'FAILED'; console.log(statusColor(`\n${statusIcon} Overall Validation: ${statusText}`)); if (isSuccess) { console.log(chalk.green('🎉 All selected tools are properly installed and validated!')); } else { console.log(chalk.yellow('⚠️ Some installations failed. See details above.')); } }