UNPKG

@simonecoelhosfo/optimizely-mcp-server

Version:

Optimizely MCP Server for AI assistants with integrated CLI tools

424 lines (353 loc) โ€ข 15.1 kB
#!/usr/bin/env node /** * Tool Consolidation Validation Script * * Usage: node validate-tool-consolidation.js <new-tool> <old-tool1> [old-tool2] ... * Example: node validate-tool-consolidation.js manage_flag_state enable_flag disable_flag */ const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); // ANSI color codes for output const colors = { red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', reset: '\x1b[0m' }; // Configuration const PATHS_TO_CHECK = [ 'src', 'docs', 'test', 'scripts', 'examples' ]; const FILE_EXTENSIONS = [ '.ts', '.js', '.md', '.json', '.yaml', '.yml' ]; const CRITICAL_FILES = [ 'src/index.ts', 'src/tools/OptimizelyMCPTools.ts', 'docs/development/GET-SUPPORTED-TOOLS.md', 'docs/developer-guides/developer-reference-guide.md' ]; class ToolConsolidationValidator { constructor(newTool, oldTools) { this.newTool = newTool; this.oldTools = oldTools; this.results = { oldToolReferences: {}, newToolReferences: {}, missingUpdates: [], validationErrors: [], suggestions: [] }; } log(message, color = 'reset') { console.log(`${colors[color]}${message}${colors.reset}`); } async validate() { this.log('\n๐Ÿ” TOOL CONSOLIDATION VALIDATION', 'blue'); this.log('=====================================', 'blue'); this.log(`New Tool: ${this.newTool}`, 'green'); this.log(`Old Tools: ${this.oldTools.join(', ')}`, 'yellow'); this.log(''); // Step 1: Find all references to old tools this.findOldToolReferences(); // Step 2: Find new tool implementation this.findNewToolImplementation(); // Step 3: Validate registration this.validateRegistration(); // Step 4: Check documentation this.checkDocumentation(); // Step 5: Validate test coverage this.validateTestCoverage(); // Step 6: Check for common mistakes this.checkCommonMistakes(); // Step 7: Generate report this.generateReport(); } findOldToolReferences() { this.log('๐Ÿ“ Finding references to old tools...', 'yellow'); this.oldTools.forEach(tool => { this.results.oldToolReferences[tool] = []; try { // Use grep to find references const grepPattern = tool.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const command = `grep -r "${grepPattern}" ${PATHS_TO_CHECK.join(' ')} --include="*${FILE_EXTENSIONS.join('" --include="*')}" 2>/dev/null || true`; const output = execSync(command, { encoding: 'utf8' }); const lines = output.split('\n').filter(line => line.trim()); lines.forEach(line => { const [filePath, ...content] = line.split(':'); if (filePath && content.length > 0) { this.results.oldToolReferences[tool].push({ file: filePath, content: content.join(':').trim() }); } }); this.log(` Found ${this.results.oldToolReferences[tool].length} references to "${tool}"`, this.results.oldToolReferences[tool].length > 0 ? 'yellow' : 'green'); } catch (error) { this.log(` Error searching for ${tool}: ${error.message}`, 'red'); } }); } findNewToolImplementation() { this.log('\n๐Ÿ”ง Checking new tool implementation...', 'yellow'); // Check if new tool is registered const indexPath = path.join(process.cwd(), 'src/index.ts'); if (fs.existsSync(indexPath)) { const content = fs.readFileSync(indexPath, 'utf8'); if (content.includes(`name: '${this.newTool}'`)) { this.log(` โœ… Found registration for "${this.newTool}"`, 'green'); // Extract the tool definition const toolRegex = new RegExp(`{[^}]*name:\\s*'${this.newTool}'[^}]*}`, 's'); const match = content.match(toolRegex); if (match) { // Check for required properties const definition = match[0]; const hasDescription = definition.includes('description:'); const hasInputSchema = definition.includes('inputSchema:'); if (!hasDescription) { this.results.validationErrors.push('New tool missing description'); } if (!hasInputSchema) { this.results.validationErrors.push('New tool missing inputSchema'); } } } else { this.results.validationErrors.push(`Tool "${this.newTool}" not found in index.ts`); this.log(` โŒ Registration not found for "${this.newTool}"`, 'red'); } } // Check implementation in OptimizelyMCPTools const toolsPath = path.join(process.cwd(), 'src/tools/OptimizelyMCPTools.ts'); if (fs.existsSync(toolsPath)) { const content = fs.readFileSync(toolsPath, 'utf8'); const methodName = this.convertToMethodName(this.newTool); if (content.includes(`async ${methodName}(`)) { this.log(` โœ… Found implementation method "${methodName}"`, 'green'); } else { this.results.validationErrors.push(`Implementation method "${methodName}" not found`); this.log(` โŒ Implementation not found for "${methodName}"`, 'red'); } } } validateRegistration() { this.log('\n๐Ÿ“ Validating tool registration...', 'yellow'); const indexPath = path.join(process.cwd(), 'src/index.ts'); if (!fs.existsSync(indexPath)) return; const content = fs.readFileSync(indexPath, 'utf8'); // Check if old tools are marked deprecated this.oldTools.forEach(tool => { const toolRegex = new RegExp(`name:\\s*'${tool}'[^}]*}`, 's'); const match = content.match(toolRegex); if (match) { const definition = match[0]; if (definition.includes('deprecated') || definition.includes('DEPRECATED')) { this.log(` โœ… "${tool}" marked as deprecated`, 'green'); } else { this.results.missingUpdates.push(`Mark "${tool}" as deprecated in registration`); this.log(` โš ๏ธ "${tool}" not marked as deprecated`, 'yellow'); } } }); // Check validTools array if (content.includes('validTools')) { const validToolsMatch = content.match(/validTools\s*=\s*\[[^\]]+\]/); if (validToolsMatch) { const validToolsArray = validToolsMatch[0]; if (!validToolsArray.includes(`'${this.newTool}'`)) { this.results.validationErrors.push(`"${this.newTool}" not in validTools array`); } // Check if old tools are still in validTools (should be for backward compatibility) this.oldTools.forEach(tool => { if (!validToolsArray.includes(`'${tool}'`)) { this.results.suggestions.push(`Consider keeping "${tool}" in validTools for backward compatibility`); } }); } } } checkDocumentation() { this.log('\n๐Ÿ“š Checking documentation...', 'yellow'); CRITICAL_FILES.forEach(filePath => { const fullPath = path.join(process.cwd(), filePath); if (!fs.existsSync(fullPath)) { this.log(` โš ๏ธ File not found: ${filePath}`, 'yellow'); return; } const content = fs.readFileSync(fullPath, 'utf8'); // Check if new tool is documented if (!content.includes(this.newTool)) { this.results.missingUpdates.push(`Document "${this.newTool}" in ${filePath}`); this.log(` โŒ "${this.newTool}" not found in ${filePath}`, 'red'); } else { this.log(` โœ… "${this.newTool}" documented in ${filePath}`, 'green'); } // Check if old tools have deprecation notices this.oldTools.forEach(tool => { if (content.includes(tool)) { const toolContext = this.getContextAroundMatch(content, tool); if (!toolContext.toLowerCase().includes('deprecated') && !toolContext.toLowerCase().includes('legacy') && !toolContext.toLowerCase().includes('migrate')) { this.results.missingUpdates.push(`Add deprecation notice for "${tool}" in ${filePath}`); } } }); }); } validateTestCoverage() { this.log('\n๐Ÿงช Checking test coverage...', 'yellow'); // Look for test files const testPatterns = [ `**/*${this.newTool}*.test.*`, `**/*${this.newTool}*.spec.*`, `**/test*${this.convertToMethodName(this.newTool)}*` ]; let testsFound = false; testPatterns.forEach(pattern => { try { const command = `find test -name "${pattern}" 2>/dev/null || true`; const output = execSync(command, { encoding: 'utf8' }).trim(); if (output) { testsFound = true; this.log(` โœ… Found test file: ${output}`, 'green'); } } catch (error) { // Ignore errors } }); if (!testsFound) { this.results.validationErrors.push(`No test files found for "${this.newTool}"`); this.log(` โŒ No test files found for "${this.newTool}"`, 'red'); } } checkCommonMistakes() { this.log('\nโš ๏ธ Checking for common mistakes...', 'yellow'); // Check 1: Ensure parameter routing is implemented const toolsPath = path.join(process.cwd(), 'src/tools/OptimizelyMCPTools.ts'); if (fs.existsSync(toolsPath)) { const content = fs.readFileSync(toolsPath, 'utf8'); const methodName = this.convertToMethodName(this.newTool); // Look for switch statement or if-else for parameter routing const methodMatch = content.match(new RegExp(`async ${methodName}\\([^)]*\\)[^{]*{[^}]+}`, 's')); if (methodMatch && !methodMatch[0].includes('switch') && !methodMatch[0].includes('if')) { this.results.suggestions.push('Consider adding parameter routing (switch/if) for action-based consolidation'); } } // Check 2: Backward compatibility wrappers this.oldTools.forEach(tool => { const oldMethodName = this.convertToMethodName(tool); const hasWrapper = this.checkForBackwardCompatibility(oldMethodName, this.convertToMethodName(this.newTool)); if (!hasWrapper) { this.results.missingUpdates.push(`Add backward compatibility wrapper for "${oldMethodName}"`); } }); // Check 3: Error messages still reference old tools this.results.oldToolReferences[this.oldTools[0]]?.forEach(ref => { if (ref.content.includes('Error') || ref.content.includes('error') || ref.content.includes('throw') || ref.content.includes('reject')) { this.results.suggestions.push(`Update error message in ${ref.file} to reference "${this.newTool}"`); } }); } generateReport() { this.log('\n๐Ÿ“Š VALIDATION REPORT', 'blue'); this.log('=====================================', 'blue'); // Summary const totalOldRefs = Object.values(this.results.oldToolReferences) .reduce((sum, refs) => sum + refs.length, 0); this.log(`\n๐Ÿ“ˆ Summary:`, 'blue'); this.log(` Total old tool references: ${totalOldRefs}`); this.log(` Validation errors: ${this.results.validationErrors.length}`); this.log(` Missing updates: ${this.results.missingUpdates.length}`); this.log(` Suggestions: ${this.results.suggestions.length}`); // Validation Errors (Critical) if (this.results.validationErrors.length > 0) { this.log(`\nโŒ VALIDATION ERRORS (Must Fix):`, 'red'); this.results.validationErrors.forEach(error => { this.log(` โ€ข ${error}`, 'red'); }); } // Missing Updates (Important) if (this.results.missingUpdates.length > 0) { this.log(`\nโš ๏ธ MISSING UPDATES (Should Fix):`, 'yellow'); this.results.missingUpdates.forEach(update => { this.log(` โ€ข ${update}`, 'yellow'); }); } // Suggestions (Nice to have) if (this.results.suggestions.length > 0) { this.log(`\n๐Ÿ’ก SUGGESTIONS (Consider):`, 'blue'); this.results.suggestions.forEach(suggestion => { this.log(` โ€ข ${suggestion}`, 'blue'); }); } // Old tool references details this.log(`\n๐Ÿ“ Old Tool References:`, 'yellow'); Object.entries(this.results.oldToolReferences).forEach(([tool, refs]) => { if (refs.length > 0) { this.log(`\n "${tool}" (${refs.length} references):`, 'yellow'); refs.slice(0, 5).forEach(ref => { this.log(` ${ref.file}: ${ref.content.substring(0, 80)}...`); }); if (refs.length > 5) { this.log(` ... and ${refs.length - 5} more references`); } } }); // Action items this.log(`\nโœ… ACTION ITEMS:`, 'green'); this.log(` 1. Fix all validation errors first`); this.log(` 2. Address missing updates`); this.log(` 3. Consider implementing suggestions`); this.log(` 4. Run validation again after fixes`); // Completion status const isComplete = this.results.validationErrors.length === 0 && this.results.missingUpdates.length === 0; this.log(`\n๐Ÿ CONSOLIDATION STATUS: ${isComplete ? 'โœ… READY' : 'โŒ NOT READY'}`, isComplete ? 'green' : 'red'); } // Helper methods convertToMethodName(toolName) { // Convert snake_case to camelCase return toolName.replace(/_([a-z])/g, (g) => g[1].toUpperCase()); } getContextAroundMatch(content, searchTerm) { const index = content.indexOf(searchTerm); if (index === -1) return ''; const start = Math.max(0, index - 100); const end = Math.min(content.length, index + searchTerm.length + 100); return content.substring(start, end); } checkForBackwardCompatibility(oldMethod, newMethod) { const toolsPath = path.join(process.cwd(), 'src/tools/OptimizelyMCPTools.ts'); if (!fs.existsSync(toolsPath)) return false; const content = fs.readFileSync(toolsPath, 'utf8'); const pattern = new RegExp(`${oldMethod}[^{]*{[^}]*${newMethod}`, 's'); return pattern.test(content); } } // Main execution if (require.main === module) { const args = process.argv.slice(2); if (args.length < 2) { console.error('Usage: node validate-tool-consolidation.js <new-tool> <old-tool1> [old-tool2] ...'); console.error('Example: node validate-tool-consolidation.js manage_flag_state enable_flag disable_flag'); process.exit(1); } const [newTool, ...oldTools] = args; const validator = new ToolConsolidationValidator(newTool, oldTools); validator.validate(); } module.exports = ToolConsolidationValidator;