UNPKG

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
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']; } }