vibe-coder-mcp
Version:
Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.
263 lines (262 loc) • 11.4 kB
JavaScript
import { LanguageHandlerRegistry } from '../../code-map-generator/languageHandlers/registry.js';
import { languageConfigurations } from '../../code-map-generator/parser.js';
import { readDirSecure } from '../../code-map-generator/fsUtils.js';
import { getUnifiedSecurityConfig } from '../security/unified-security-config.js';
import { getTimeoutManager } from './timeout-manager.js';
import logger from '../../../logger.js';
import path from 'path';
export class ProjectAnalyzer {
static instance;
languageRegistry;
constructor() {
this.languageRegistry = LanguageHandlerRegistry.getInstance();
}
static getInstance() {
if (!ProjectAnalyzer.instance) {
ProjectAnalyzer.instance = new ProjectAnalyzer();
}
return ProjectAnalyzer.instance;
}
async detectProjectLanguages(projectPath) {
try {
logger.debug({ projectPath }, 'Starting language detection');
const securityConfig = getUnifiedSecurityConfig().getConfig();
const timeoutManager = getTimeoutManager();
const filesResult = await timeoutManager.executeWithTimeout('fileOperations', async () => readDirSecure(projectPath, securityConfig.allowedReadDirectory));
if (!filesResult.success || filesResult.timedOut) {
logger.warn({
projectPath,
error: filesResult.error,
timedOut: filesResult.timedOut
}, 'File system operation failed or timed out, using fallback');
return ['javascript'];
}
const files = filesResult.data;
const detectedLanguages = new Set();
for (const file of files) {
if (file.isFile()) {
const extension = this.getFileExtension(file.name);
if (extension) {
const language = this.getLanguageFromExtension(extension);
if (language) {
detectedLanguages.add(language);
}
}
}
}
const languages = Array.from(detectedLanguages);
if (languages.length === 0) {
logger.warn({ projectPath }, 'No languages detected, falling back to JavaScript');
return ['javascript'];
}
logger.debug({ projectPath, languages }, 'Languages detected successfully');
return languages;
}
catch (error) {
logger.error({ error, projectPath }, 'Error detecting project languages');
return ['javascript'];
}
}
async detectProjectFrameworks(projectPath) {
try {
logger.debug({ projectPath }, 'Starting framework detection');
const detectedLanguages = await this.detectProjectLanguages(projectPath);
const frameworks = [];
for (const lang of detectedLanguages) {
const extensions = this.getExtensionsForLanguage(lang);
for (const ext of extensions) {
const handler = this.languageRegistry.getHandler(ext);
if (handler && typeof handler.detectFramework === 'function') {
try {
const sampleContent = await this.getSampleFileContent(projectPath, ext);
if (sampleContent) {
const framework = handler.detectFramework(sampleContent);
if (framework) {
frameworks.push(framework);
}
}
}
catch (handlerError) {
logger.warn({ error: handlerError, lang, ext }, 'Framework detection failed for language');
}
}
}
}
const uniqueFrameworks = [...new Set(frameworks)];
if (uniqueFrameworks.length === 0) {
const fallbackFrameworks = this.getFallbackFrameworks(detectedLanguages);
logger.debug({ projectPath, fallbackFrameworks }, 'Using fallback frameworks');
return fallbackFrameworks;
}
logger.debug({ projectPath, frameworks: uniqueFrameworks }, 'Frameworks detected successfully');
return uniqueFrameworks;
}
catch (error) {
logger.error({ error, projectPath }, 'Error detecting project frameworks');
return ['node.js'];
}
}
async detectProjectTools(projectPath) {
try {
logger.debug({ projectPath }, 'Starting tools detection');
const tools = ['git'];
const securityConfig = getUnifiedSecurityConfig().getConfig();
const timeoutManager = getTimeoutManager();
const filesResult = await timeoutManager.executeWithTimeout('fileOperations', async () => readDirSecure(projectPath, securityConfig.allowedReadDirectory));
if (!filesResult.success || filesResult.timedOut) {
logger.warn({
projectPath,
error: filesResult.error,
timedOut: filesResult.timedOut
}, 'File system operation failed or timed out, using fallback tools');
return tools;
}
const files = filesResult.data;
const configFileMap = {
'webpack.config.js': 'webpack',
'vite.config.js': 'vite',
'rollup.config.js': 'rollup',
'jest.config.js': 'jest',
'.eslintrc.js': 'eslint',
'.eslintrc.json': 'eslint',
'prettier.config.js': 'prettier',
'.prettierrc': 'prettier',
'tailwind.config.js': 'tailwind',
'next.config.js': 'next.js',
'nuxt.config.js': 'nuxt.js',
'tsconfig.json': 'typescript',
'babel.config.js': 'babel',
'.babelrc': 'babel'
};
for (const file of files) {
if (file.isFile() && configFileMap[file.name]) {
tools.push(configFileMap[file.name]);
}
}
if (files.some((f) => f.name === 'package-lock.json'))
tools.push('npm');
if (files.some((f) => f.name === 'yarn.lock'))
tools.push('yarn');
if (files.some((f) => f.name === 'pnpm-lock.yaml'))
tools.push('pnpm');
if (files.some((f) => f.name === 'Cargo.lock'))
tools.push('cargo');
if (files.some((f) => f.name === 'Pipfile.lock'))
tools.push('pipenv');
if (files.some((f) => f.name === 'poetry.lock'))
tools.push('poetry');
const uniqueTools = [...new Set(tools)];
logger.debug({ projectPath, tools: uniqueTools }, 'Tools detected successfully');
return uniqueTools;
}
catch (error) {
logger.error({ error, projectPath }, 'Error detecting project tools');
return ['git', 'npm'];
}
}
getFileExtension(filename) {
const lastDot = filename.lastIndexOf('.');
if (lastDot === -1 || lastDot === 0)
return null;
return filename.substring(lastDot);
}
getLanguageFromExtension(extension) {
const extensionMap = {
'.js': 'javascript',
'.jsx': 'javascript',
'.ts': 'typescript',
'.tsx': 'typescript',
'.py': 'python',
'.java': 'java',
'.cs': 'csharp',
'.php': 'php',
'.rb': 'ruby',
'.go': 'go',
'.rs': 'rust',
'.cpp': 'cpp',
'.c': 'c',
'.css': 'css',
'.scss': 'scss',
'.sass': 'sass',
'.html': 'html',
'.xml': 'xml',
'.json': 'json',
'.yaml': 'yaml',
'.yml': 'yaml',
'.md': 'markdown',
'.sh': 'shell',
'.sql': 'sql'
};
return extensionMap[extension.toLowerCase()] || null;
}
getExtensionsForLanguage(language) {
const extensions = [];
for (const [ext, config] of Object.entries(languageConfigurations)) {
if (config.name.toLowerCase() === language.toLowerCase()) {
extensions.push(ext);
}
}
return extensions;
}
async getSampleFileContent(projectPath, extension) {
try {
const securityConfig = getUnifiedSecurityConfig().getConfig();
const timeoutManager = getTimeoutManager();
const filesResult = await timeoutManager.executeWithTimeout('fileOperations', async () => readDirSecure(projectPath, securityConfig.allowedReadDirectory));
if (!filesResult.success || filesResult.timedOut) {
logger.warn({
projectPath,
extension,
error: filesResult.error,
timedOut: filesResult.timedOut
}, 'File directory read failed or timed out');
return null;
}
const files = filesResult.data;
const targetFile = files.find((f) => f.isFile() && f.name.endsWith(extension));
if (targetFile) {
const contentResult = await timeoutManager.executeWithTimeout('fileOperations', async () => {
const fsPromises = await import('fs/promises');
const filePath = path.join(projectPath, targetFile.name);
const content = await fsPromises.readFile(filePath, 'utf-8');
return content.substring(0, 1000);
});
if (!contentResult.success || contentResult.timedOut) {
logger.warn({
projectPath,
extension,
fileName: targetFile.name,
error: contentResult.error,
timedOut: contentResult.timedOut
}, 'File content read failed or timed out');
return null;
}
return contentResult.data;
}
return null;
}
catch (error) {
logger.warn({ error, projectPath, extension }, 'Failed to read sample file content');
return null;
}
}
getFallbackFrameworks(languages) {
const fallbacks = [];
if (languages.includes('javascript') || languages.includes('typescript')) {
fallbacks.push('node.js');
}
if (languages.includes('python')) {
fallbacks.push('django');
}
if (languages.includes('java')) {
fallbacks.push('spring');
}
if (languages.includes('csharp')) {
fallbacks.push('dotnet');
}
if (languages.includes('php')) {
fallbacks.push('laravel');
}
return fallbacks.length > 0 ? fallbacks : ['node.js'];
}
}