@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
552 lines (479 loc) • 18 kB
JavaScript
/**
* ArchPatternDetector.js
* Detects architectural patterns in project codebases
*/
import path from '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 = [];
// Get relevant data structures
const { files, directories } = projectStructure;
// 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/')
),
};
}