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.

181 lines (180 loc) 7.71 kB
import { exec } from 'child_process'; import { promisify } from 'util'; import * as fs from 'fs'; import * as path from 'path'; import logger from '../../../logger.js'; import { UnifiedSecurityConfigManager } from '../../vibe-task-manager/security/unified-security-config.js'; const execAsync = promisify(exec); export class ClangdAdapter { allowedDir; outputDir; securityValidator; cache = new Map(); tempFiles = []; constructor(allowedDir, outputDir) { this.allowedDir = allowedDir; this.outputDir = outputDir; this.securityValidator = UnifiedSecurityConfigManager.getInstance(); } async analyzeImports(filePath, options) { try { const cacheKey = `${filePath}:${JSON.stringify(options)}`; if (this.cache.has(cacheKey)) { return this.cache.get(cacheKey); } if (!this.securityValidator.validatePathSecurity(filePath, { operation: 'read' })) { logger.warn({ filePath }, 'File path is outside allowed directory'); return []; } const clangdPath = options.clangdPath || await this.findClangdPath(); if (!clangdPath) { logger.warn('Clangd not found. Please install Clangd and set the path in options.'); return []; } const compileCommandsPath = await this.createCompilationDatabase(filePath, options.compileFlags || [], options.includePaths || []); this.tempFiles.push(compileCommandsPath); const includes = await this.extractIncludesWithClangd(filePath, clangdPath, compileCommandsPath); const imports = this.convertToImportInfo(includes, filePath); await fs.promises.unlink(compileCommandsPath); const fileIndex = this.tempFiles.indexOf(compileCommandsPath); if (fileIndex !== -1) { this.tempFiles.splice(fileIndex, 1); } this.cache.set(cacheKey, imports); return imports; } catch (error) { logger.error({ err: error, filePath }, 'Error analyzing imports with Clangd'); return []; } } async findClangdPath() { try { const { stdout } = await execAsync('which clangd || which clangd-15 || which clangd-14 || which clangd-13'); return stdout.trim(); } catch (error) { logger.warn({ err: error }, 'Clangd not found in PATH'); return null; } } async createCompilationDatabase(filePath, compileFlags, includePaths) { const includeFlags = includePaths.map(p => `-I${p}`); const compileCommandsPath = path.join(this.outputDir, `compile_commands_${Date.now()}.json`); const compileCommands = [ { directory: path.dirname(filePath), command: `clang++ -std=c++17 ${compileFlags.join(' ')} ${includeFlags.join(' ')} -c ${filePath}`, file: filePath } ]; await fs.promises.writeFile(compileCommandsPath, JSON.stringify(compileCommands, null, 2)); return compileCommandsPath; } async extractIncludesWithClangd(filePath, clangdPath, compileCommandsPath) { try { const outputFile = path.join(this.outputDir, `clangd_output_${Date.now()}.json`); this.tempFiles.push(outputFile); const command = `${clangdPath} --compile-commands-dir=${path.dirname(compileCommandsPath)} --path-mappings=. --background-index=false --log=verbose --input-style=protocol --pretty --enable-config --query-driver=** --pch-storage=memory ${filePath} > ${outputFile} 2>&1`; await execAsync(command); const output = await fs.promises.readFile(outputFile, 'utf8'); const includes = this.parseClangdOutput(output, filePath); await fs.promises.unlink(outputFile); const fileIndex = this.tempFiles.indexOf(outputFile); if (fileIndex !== -1) { this.tempFiles.splice(fileIndex, 1); } return includes; } catch (error) { logger.error({ err: error, filePath }, 'Error extracting includes with Clangd'); return []; } } parseClangdOutput(output, filePath) { const includes = []; try { const includeRegex = /#include\s+[<"]([^>"]+)[>"]/g; const fileContent = fs.readFileSync(filePath, 'utf8'); let match; while ((match = includeRegex.exec(fileContent)) !== null) { const includePath = match[1]; const isSystemInclude = match[0].includes('<'); includes.push({ path: includePath, isSystemInclude, }); } const resolvedPathRegex = /Resolved include\s+([^:]+):\s+([^\s]+)/g; let resolvedMatch; while ((resolvedMatch = resolvedPathRegex.exec(output)) !== null) { const includePath = resolvedMatch[1]; const resolvedPath = resolvedMatch[2]; const include = includes.find(inc => inc.path === includePath); if (include) { include.resolvedPath = resolvedPath; } } } catch (error) { logger.error({ err: error }, 'Error parsing Clangd output'); } return includes; } convertToImportInfo(includes, _filePath) { const imports = []; for (const include of includes) { const importedItems = []; importedItems.push({ name: this.extractNameFromPath(include.path), path: include.resolvedPath || include.path, isDefault: false, isNamespace: false, nodeText: include.isSystemInclude ? `#include <${include.path}>` : `#include "${include.path}"` }); const importInfo = { path: include.resolvedPath || include.path, importedItems, isCore: include.isSystemInclude, isExternalPackage: include.isSystemInclude, moduleSystem: 'c++', metadata: { isSystemInclude: include.isSystemInclude, originalPath: include.path } }; imports.push(importInfo); } return imports; } extractNameFromPath(includePath) { if (includePath.includes('/')) { const parts = includePath.split('/'); const filename = parts[parts.length - 1]; return this.removeExtension(filename); } return this.removeExtension(includePath); } removeExtension(filename) { const extIndex = filename.lastIndexOf('.'); return extIndex !== -1 ? filename.substring(0, extIndex) : filename; } dispose() { this.cache.clear(); if (this.tempFiles && this.tempFiles.length > 0) { this.tempFiles.forEach(file => { try { if (fs.existsSync(file)) { fs.unlinkSync(file); logger.debug({ file }, 'Deleted temporary file during ClangdAdapter disposal'); } } catch (error) { logger.warn({ file, error }, 'Failed to delete temporary file during ClangdAdapter disposal'); } }); this.tempFiles = []; } logger.debug('ClangdAdapter disposed'); } }