UNPKG

@vibe-dev-kit/cli

Version:

Advanced Command-line toolkit that analyzes your codebase and deploys project-aware rules, memories, commands and agents to any AI coding assistant - VDK is the world's first Vibe Development Kit

589 lines (516 loc) 18 kB
/** * ArchPatternDetector.js * Detects architectural patterns in project codebases */ import path from 'node:path' /** * Detects architectural patterns in a project * @param {Object} projectStructure - Project structure data * @returns {Array} Detected architectural patterns */ export async function detectArchitecturalPatterns(projectStructure) { const patterns = [] // Note: projectStructure is passed directly to detection functions // Check for MVC pattern const mvcConfidence = detectMVCPattern(projectStructure) if (mvcConfidence > 0.7) { patterns.push({ name: 'Model-View-Controller (MVC)', confidence: mvcConfidence, description: 'The codebase follows the Model-View-Controller architectural pattern.', locations: findMVCComponents(projectStructure), }) } // Check for MVVM pattern const mvvmConfidence = detectMVVMPattern(projectStructure) if (mvvmConfidence > 0.7) { patterns.push({ name: 'Model-View-ViewModel (MVVM)', confidence: mvvmConfidence, description: 'The codebase follows the Model-View-ViewModel architectural pattern.', locations: findMVVMComponents(projectStructure), }) } // Check for Clean Architecture const cleanArchConfidence = detectCleanArchitecture(projectStructure) if (cleanArchConfidence > 0.7) { patterns.push({ name: 'Clean Architecture', confidence: cleanArchConfidence, description: 'The codebase follows Clean Architecture principles with separated layers.', locations: findCleanArchComponents(projectStructure), }) } // Check for Microservices architecture const microservicesConfidence = detectMicroservices(projectStructure) if (microservicesConfidence > 0.7) { patterns.push({ name: 'Microservices', confidence: microservicesConfidence, description: 'The project follows a microservices architecture with separate services.', locations: findMicroserviceComponents(projectStructure), }) } // Check for Hexagonal/Ports and Adapters architecture const hexagonalConfidence = detectHexagonalArchitecture(projectStructure) if (hexagonalConfidence > 0.7) { patterns.push({ name: 'Hexagonal Architecture (Ports & Adapters)', confidence: hexagonalConfidence, description: 'The codebase implements a hexagonal architecture with clear ports and adapters.', locations: findHexagonalComponents(projectStructure), }) } // Check for Event-Driven Architecture const eventDrivenConfidence = detectEventDrivenArchitecture(projectStructure) if (eventDrivenConfidence > 0.7) { patterns.push({ name: 'Event-Driven Architecture', confidence: eventDrivenConfidence, description: 'The codebase uses event-driven patterns with publishers and subscribers.', locations: findEventDrivenComponents(projectStructure), }) } return patterns } /** * Detects whether the project follows an MVC architecture * @param {Object} projectStructure - The project structure data * @returns {number} Confidence score (0-1) */ function detectMVCPattern(projectStructure) { let score = 0 const { directories, files } = projectStructure // Check for explicit MVC folders const dirNames = Array.isArray(directories) ? directories.map((dir) => path.basename(typeof dir === 'string' ? dir : dir.path || dir.name || '').toLowerCase() ) : Object.keys(directories || {}) const hasMvcFolders = dirNames.includes('models') && (dirNames.includes('views') || dirNames.includes('templates')) && dirNames.includes('controllers') if (hasMvcFolders) { score += 0.6 } // Check for files that follow MVC naming conventions const fileNames = files.map((file) => path.basename(file.path).toLowerCase()) const modelFiles = fileNames.filter((name) => name.includes('model')) const viewFiles = fileNames.filter((name) => name.includes('view')) const controllerFiles = fileNames.filter((name) => name.includes('controller')) if (modelFiles.length > 0 && viewFiles.length > 0 && controllerFiles.length > 0) { score += 0.3 } // Check for explicit framework that uses MVC const hasMvcFramework = checkForMvcFrameworks(projectStructure) if (hasMvcFramework) { score += 0.2 } // Cap at 1.0 return Math.min(score, 1.0) } /** * Detects whether the project follows an MVVM architecture * @param {Object} projectStructure - The project structure data * @returns {number} Confidence score (0-1) */ function detectMVVMPattern(projectStructure) { let score = 0 const { directories, files } = projectStructure // Check for explicit MVVM folders const dirNames = directories.map((dir) => path.basename(typeof dir === 'string' ? dir : dir.path || dir.name || '').toLowerCase() ) const hasMvvmFolders = dirNames.includes('models') && (dirNames.includes('views') || dirNames.includes('templates')) && (dirNames.includes('viewmodels') || dirNames.includes('view-models')) if (hasMvvmFolders) { score += 0.6 } // Check for files that follow MVVM naming conventions const fileNames = files.map((file) => path.basename(file.path).toLowerCase()) const modelFiles = fileNames.filter((name) => name.includes('model')) const viewFiles = fileNames.filter((name) => name.includes('view')) const viewModelFiles = fileNames.filter( (name) => name.includes('viewmodel') || name.includes('view-model') || name.includes('vm') ) if (modelFiles.length > 0 && viewFiles.length > 0 && viewModelFiles.length > 0) { score += 0.3 } // Check for explicit framework that uses MVVM const hasMvvmFramework = checkForMvvmFrameworks(projectStructure) if (hasMvvmFramework) { score += 0.2 } // Cap at 1.0 return Math.min(score, 1.0) } /** * Detects whether the project follows Clean Architecture * @param {Object} projectStructure - The project structure data * @returns {number} Confidence score (0-1) */ function detectCleanArchitecture(projectStructure) { let score = 0 const { directories } = projectStructure const dirNames = directories.map((dir) => path.basename(typeof dir === 'string' ? dir : dir.path || dir.name || '').toLowerCase() ) // Check for Clean Architecture layers const hasEntities = dirNames.includes('entities') || dirNames.includes('domain') const hasUseCases = dirNames.includes('usecases') || dirNames.includes('use-cases') || dirNames.includes('interactors') || dirNames.includes('application') const hasAdapters = dirNames.includes('adapters') || dirNames.includes('interfaces') const hasFrameworks = dirNames.includes('frameworks') || dirNames.includes('infrastructure') if (hasEntities) { score += 0.25 } if (hasUseCases) { score += 0.25 } if (hasAdapters) { score += 0.25 } if (hasFrameworks) { score += 0.25 } return score } /** * Detects whether the project follows a microservices architecture * @param {Object} projectStructure - The project structure data * @returns {number} Confidence score (0-1) */ function detectMicroservices(projectStructure) { let score = 0 const { directories, files } = projectStructure // Check for service directories const serviceDirectories = directories.filter((dir) => { const name = ( typeof dir === 'string' ? path.basename(dir) : dir.name || path.basename(dir.path || '') ).toLowerCase() return name.includes('service') || name.endsWith('-svc') }) if (serviceDirectories.length >= 2) { score += 0.4 } else if (serviceDirectories.length === 1) { score += 0.2 } // Check for common microservice files const hasDockerfnile = files.some((file) => path.basename(file.path) === 'Dockerfile') const hasDockerCompose = files.some((file) => path.basename(file.path) === 'docker-compose.yml') const hasK8s = files.some( (file) => file.path.includes('kubernetes') || file.path.endsWith('.yaml') ) const hasApiGateway = files.some( (file) => file.path.includes('gateway') || file.path.includes('proxy') ) if (hasDockerfnile) { score += 0.15 } if (hasDockerCompose) { score += 0.15 } if (hasK8s) { score += 0.15 } if (hasApiGateway) { score += 0.15 } return Math.min(score, 1.0) } /** * Detects whether the project follows a Hexagonal/Ports and Adapters architecture * @param {Object} projectStructure - The project structure data * @returns {number} Confidence score (0-1) */ function detectHexagonalArchitecture(projectStructure) { let score = 0 const { directories, files } = projectStructure const dirNames = directories.map((dir) => path.basename(typeof dir === 'string' ? dir : dir.path || dir.name || '').toLowerCase() ) // Check for hexagonal architecture directories const hasDomain = dirNames.includes('domain') const hasPorts = dirNames.includes('ports') const hasAdapters = dirNames.includes('adapters') const hasInterfaces = dirNames.includes('interfaces') const hasInfrastructure = dirNames.includes('infrastructure') if (hasDomain) { score += 0.2 } if (hasPorts) { score += 0.2 } if (hasAdapters) { score += 0.2 } if (hasInterfaces || hasInfrastructure) { score += 0.2 } // Look for port/adapter patterns in file names const fileNames = files.map((file) => path.basename(file.path).toLowerCase()) const portFiles = fileNames.filter((name) => name.includes('port')) const adapterFiles = fileNames.filter((name) => name.includes('adapter')) if (portFiles.length > 0) { score += 0.1 } if (adapterFiles.length > 0) { score += 0.1 } return Math.min(score, 1.0) } /** * Detects whether the project follows an Event-Driven Architecture * @param {Object} projectStructure - The project structure data * @returns {number} Confidence score (0-1) */ function detectEventDrivenArchitecture(projectStructure) { let score = 0 const { directories, files } = projectStructure // Check for event-driven directories const dirNames = directories.map((dir) => path.basename(typeof dir === 'string' ? dir : dir.path || dir.name || '').toLowerCase() ) const hasEvents = dirNames.includes('events') const hasHandlers = dirNames.includes('handlers') || dirNames.includes('listeners') const hasPublishers = dirNames.includes('publishers') || dirNames.includes('dispatchers') const hasSubscribers = dirNames.includes('subscribers') || dirNames.includes('consumers') if (hasEvents) { score += 0.2 } if (hasHandlers) { score += 0.2 } if (hasPublishers) { score += 0.2 } if (hasSubscribers) { score += 0.2 } // Check for message broker configuration const hasBrokerConfig = files.some((file) => { const name = path.basename(file.path).toLowerCase() return ( name.includes('kafka') || name.includes('rabbitmq') || name.includes('activemq') || name.includes('eventbus') ) }) if (hasBrokerConfig) { score += 0.2 } return Math.min(score, 1.0) } /** * Helper function to check for MVC frameworks in the project * @param {Object} projectStructure - The project structure data * @returns {boolean} True if an MVC framework is detected */ function checkForMvcFrameworks(projectStructure) { const { files } = projectStructure // List of common MVC frameworks const mvcFrameworks = [ 'spring-webmvc', 'spring-mvc', '@angular/core', 'express', 'django', 'rails', 'laravel', 'asp.net mvc', 'flask', ] // Check for package files const packageJsonFiles = files.filter((file) => path.basename(file.path) === 'package.json') // Check package.json files for MVC framework dependencies for (const packageFile of packageJsonFiles) { try { if (packageFile.content) { const packageData = JSON.parse(packageFile.content) const dependencies = { ...packageData.dependencies, ...packageData.devDependencies } for (const framework of mvcFrameworks) { if (dependencies[framework]) { return true } } } } catch { // Skip invalid JSON files } } // Check for framework-specific directory patterns const hasControllers = files.some((file) => file.path.includes('/controllers/')) const hasModels = files.some((file) => file.path.includes('/models/')) const hasViews = files.some( (file) => file.path.includes('/views/') || file.path.includes('/templates/') ) // Basic MVC pattern detection based on directory structure return hasControllers && hasModels && hasViews } /** * Helper function to check for MVVM frameworks in the project * @param {Object} projectStructure - The project structure data * @returns {boolean} True if an MVVM framework is detected */ function checkForMvvmFrameworks(projectStructure) { const { files } = projectStructure // List of common MVVM frameworks const mvvmFrameworks = [ 'knockout.js', 'vue', 'angularjs', 'wpf', 'androidx.lifecycle', 'androidx.viewmodel', 'reactiveui', 'mobx', 'rxswift', ] // Check for package files const packageJsonFiles = files.filter((file) => path.basename(file.path) === 'package.json') // Check package.json files for MVVM framework dependencies for (const packageFile of packageJsonFiles) { try { if (packageFile.content) { const packageData = JSON.parse(packageFile.content) const dependencies = { ...packageData.dependencies, ...packageData.devDependencies } for (const framework of mvvmFrameworks) { if (dependencies[framework]) { return true } } } } catch { // Skip invalid JSON files } } // Check for MVVM-specific directory patterns const hasViewModels = files.some( (file) => file.path.includes('/viewmodel') || file.path.includes('/view-model') || file.path.toLowerCase().includes('viewmodel') ) const hasViews = files.some( (file) => file.path.includes('/views/') || file.path.includes('/components/') ) return hasViewModels && hasViews } /** * Finds MVC components in the project structure * @param {Object} projectStructure - The project structure data * @returns {Object} Component locations */ function findMVCComponents(projectStructure) { const { files } = projectStructure return { models: files.filter((file) => file.path.includes('/models/')), views: files.filter( (file) => file.path.includes('/views/') || file.path.includes('/templates/') ), controllers: files.filter((file) => file.path.includes('/controllers/')), } } /** * Finds MVVM components in the project structure * @param {Object} projectStructure - The project structure data * @returns {Object} Component locations */ function findMVVMComponents(projectStructure) { const { files } = projectStructure return { models: files.filter((file) => file.path.includes('/models/')), views: files.filter( (file) => file.path.includes('/views/') || file.path.includes('/components/') ), viewModels: files.filter( (file) => file.path.includes('/viewmodel') || file.path.includes('/view-model') || file.path.toLowerCase().includes('viewmodel') ), } } /** * Finds Clean Architecture components in the project structure * @param {Object} projectStructure - The project structure data * @returns {Object} Component locations */ function findCleanArchComponents(projectStructure) { const { files } = projectStructure return { entities: files.filter( (file) => file.path.includes('/entities/') || file.path.includes('/domain/') ), useCases: files.filter( (file) => file.path.includes('/usecases/') || file.path.includes('/application/') ), adapters: files.filter( (file) => file.path.includes('/adapters/') || file.path.includes('/interfaces/') ), frameworks: files.filter( (file) => file.path.includes('/frameworks/') || file.path.includes('/infrastructure/') ), } } /** * Finds Microservice components in the project structure * @param {Object} projectStructure - The project structure data * @returns {Object} Component locations */ function findMicroserviceComponents(projectStructure) { const { files } = projectStructure return { services: files.filter((file) => file.path.includes('service')), infrastructure: files.filter( (file) => file.path.includes('docker') || file.path.includes('kubernetes') || file.path.includes('k8s') ), gateway: files.filter((file) => file.path.includes('gateway') || file.path.includes('proxy')), } } /** * Finds Hexagonal Architecture components in the project structure * @param {Object} projectStructure - The project structure data * @returns {Object} Component locations */ function findHexagonalComponents(projectStructure) { const { files } = projectStructure return { domain: files.filter((file) => file.path.includes('/domain/')), ports: files.filter((file) => file.path.includes('/ports/') || file.path.includes('port')), adapters: files.filter( (file) => file.path.includes('/adapters/') || file.path.includes('adapter') ), } } /** * Finds Event-Driven Architecture components in the project structure * @param {Object} projectStructure - The project structure data * @returns {Object} Component locations */ function findEventDrivenComponents(projectStructure) { const { files } = projectStructure return { events: files.filter((file) => file.path.includes('/events/')), publishers: files.filter( (file) => file.path.includes('/publishers/') || file.path.includes('/dispatchers/') ), subscribers: files.filter( (file) => file.path.includes('/subscribers/') || file.path.includes('/consumers/') || file.path.includes('/listeners/') ), } }