Skip to main content
Glama

Debugg AI MCP

Official
by debugg-ai
projectAnalyzer.tsโ€ข18 kB
/** * Project Analyzer for MCP Server * Simplified version of the project analyzer for file system-based analysis */ import { existsSync, readFileSync, readdirSync, statSync } from 'fs'; import { join, extname, dirname, resolve } from 'path'; import { Logger } from './logger.js'; const logger = new Logger({ module: 'projectAnalyzer' }); export interface ProjectAnalysis { primaryLanguage: string | undefined; testingLanguage: string | undefined; testingFramework: string | undefined; repoName: string | undefined; repoPath: string | undefined; branchName: string | undefined; framework: string | undefined; } export interface LanguageDetectionResult { language: string; confidence: number; evidence: string[]; } export interface TestingFrameworkResult { framework: string; confidence: number; evidence: string[]; } export interface CodebaseContext { repositoryName: string; branchName: string; commitHash: string; totalFiles: number; primaryLanguage: string; frameworks: string[]; testingFramework?: string; architecturalPatterns: string[]; focusAreas: string[]; timestamp: string; } /** * Simplified Project Analyzer class */ export class ProjectAnalyzer { /** * Analyze project structure from file system */ async analyzeProject(repoPath?: string): Promise<ProjectAnalysis> { try { const projectPath = repoPath || process.cwd(); if (!existsSync(projectPath)) { logger.warn('Project path does not exist', { projectPath }); return this.emptyAnalysis(); } logger.info('Starting project analysis', { projectPath }); const primaryLanguage = await this.detectPrimaryLanguage(projectPath); const testingFramework = await this.detectTestingFramework(projectPath); const testingLanguage = await this.detectTestingLanguage(projectPath, primaryLanguage.language); const framework = await this.detectFramework(projectPath); const repoName = this.extractRepoName(projectPath); const branchName = await this.getCurrentBranch(projectPath); return { primaryLanguage: primaryLanguage.language, testingLanguage: testingLanguage.language, testingFramework: testingFramework.framework, repoName, repoPath: projectPath, branchName, framework, }; } catch (error) { logger.error('Error analyzing project', error); return this.emptyAnalysis(); } } /** * Analyze codebase for context extraction */ async analyzeCodebase( repoPath: string, repoName: string, branchName: string, includeChanges: boolean = true ): Promise<CodebaseContext | null> { try { logger.info('Starting codebase analysis', { repoPath, repoName, branchName }); const analysis = await this.analyzeProject(repoPath); const fileCount = await this.countFiles(repoPath); const architecturalPatterns = await this.analyzeArchitecturalPatterns(repoPath); const focusAreas = await this.identifyFocusAreas(repoPath, analysis); // Get frameworks as array const frameworks = analysis.framework ? [analysis.framework] : []; if (analysis.testingFramework && !frameworks.includes(analysis.testingFramework)) { frameworks.push(analysis.testingFramework); } return { repositoryName: repoName, branchName, commitHash: await this.getCurrentCommitHash(repoPath) || 'unknown', totalFiles: fileCount, primaryLanguage: analysis.primaryLanguage || 'unknown', frameworks, testingFramework: analysis.testingFramework, architecturalPatterns, focusAreas, timestamp: new Date().toISOString(), }; } catch (error) { logger.error('Error analyzing codebase', error); return null; } } /** * Detect primary programming language */ private async detectPrimaryLanguage(projectPath: string): Promise<LanguageDetectionResult> { const evidence: string[] = []; let confidence = 0; let detectedLanguage = "unknown"; try { // Check for package.json (JavaScript/TypeScript) if (existsSync(join(projectPath, "package.json"))) { const packageJson = this.readPackageJson(projectPath); if (packageJson) { evidence.push("package.json found"); confidence += 30; // Check for TypeScript indicators if (packageJson.dependencies?.typescript || packageJson.devDependencies?.typescript || packageJson.dependencies?.["@types/node"] || packageJson.devDependencies?.["@types/node"]) { detectedLanguage = "typescript"; evidence.push("TypeScript dependencies in package.json"); confidence += 25; } else { detectedLanguage = "javascript"; evidence.push("JavaScript project (no TypeScript deps)"); confidence += 20; } } } // Check for tsconfig.json if (existsSync(join(projectPath, "tsconfig.json"))) { if (detectedLanguage === "unknown") { detectedLanguage = "typescript"; confidence += 35; } evidence.push("tsconfig.json found"); confidence += 15; } // Check for Python indicators if (existsSync(join(projectPath, "requirements.txt")) || existsSync(join(projectPath, "pyproject.toml")) || existsSync(join(projectPath, "setup.py"))) { if (detectedLanguage === "unknown") { detectedLanguage = "python"; confidence += 40; } evidence.push("Python project files found"); } // Check for Java indicators if (existsSync(join(projectPath, "pom.xml")) || existsSync(join(projectPath, "build.gradle"))) { if (detectedLanguage === "unknown") { detectedLanguage = "java"; confidence += 40; } evidence.push("Java build files found"); } // Check for Go indicators if (existsSync(join(projectPath, "go.mod"))) { if (detectedLanguage === "unknown") { detectedLanguage = "go"; confidence += 40; } evidence.push("Go module file found"); } // Check for Rust indicators if (existsSync(join(projectPath, "Cargo.toml"))) { if (detectedLanguage === "unknown") { detectedLanguage = "rust"; confidence += 40; } evidence.push("Cargo.toml found"); } // Fallback to file extension analysis if (confidence < 30) { const fileExtensionResult = await this.analyzeFileExtensions(projectPath); if (fileExtensionResult.confidence > confidence) { detectedLanguage = fileExtensionResult.language; confidence = fileExtensionResult.confidence; evidence.push(...fileExtensionResult.evidence); } } } catch (error) { logger.warn("Error detecting primary language", error); evidence.push("Error during detection"); } return { language: detectedLanguage, confidence: Math.min(confidence, 100), evidence, }; } /** * Detect testing framework */ private async detectTestingFramework(projectPath: string): Promise<TestingFrameworkResult> { const evidence: string[] = []; let confidence = 0; let framework = "unknown"; try { // Check package.json for testing frameworks if (existsSync(join(projectPath, "package.json"))) { const packageJson = this.readPackageJson(projectPath); if (packageJson) { const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies, }; // Playwright if (allDeps.playwright || allDeps["@playwright/test"]) { framework = "playwright"; evidence.push("Playwright dependencies found"); confidence += 40; } // Jest if (allDeps.jest || allDeps["@jest/core"]) { if (framework === "unknown") { framework = "jest"; confidence += 30; } evidence.push("Jest testing framework found"); } // Vitest if (allDeps.vitest) { if (framework === "unknown") { framework = "vitest"; confidence += 30; } evidence.push("Vitest testing framework found"); } } } // Check for config files const configFiles = [ { file: "playwright.config.js", framework: "playwright", weight: 35 }, { file: "playwright.config.ts", framework: "playwright", weight: 35 }, { file: "jest.config.js", framework: "jest", weight: 25 }, { file: "jest.config.ts", framework: "jest", weight: 25 }, { file: "vitest.config.js", framework: "vitest", weight: 25 }, { file: "vitest.config.ts", framework: "vitest", weight: 25 }, ]; for (const config of configFiles) { if (existsSync(join(projectPath, config.file))) { if (framework === "unknown" || config.weight > confidence) { framework = config.framework; confidence = Math.max(confidence, config.weight); } evidence.push(`${config.file} found`); } } // Check for Python testing frameworks if (existsSync(join(projectPath, "pytest.ini")) || existsSync(join(projectPath, "pyproject.toml"))) { if (framework === "unknown") { framework = "pytest"; confidence += 30; } evidence.push("Python testing configuration found"); } } catch (error) { logger.warn("Error detecting testing framework", error); evidence.push("Error during detection"); } return { framework, confidence: Math.min(confidence, 100), evidence, }; } /** * Detect testing language */ private async detectTestingLanguage(projectPath: string, primaryLanguage: string): Promise<LanguageDetectionResult> { // For simplicity, assume testing language matches primary language // In a real implementation, we'd analyze test files return { language: primaryLanguage, confidence: 50, evidence: [`Assumed same as primary language: ${primaryLanguage}`], }; } /** * Detect framework */ private async detectFramework(projectPath: string): Promise<string> { let detectedFramework = "unknown"; try { // Check for Node.js/JavaScript frameworks if (existsSync(join(projectPath, "package.json"))) { const packageJson = this.readPackageJson(projectPath); if (packageJson) { const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies, }; // NextJS if (allDeps.next || existsSync(join(projectPath, "next.config.js"))) { detectedFramework = "nextjs"; } // React else if (allDeps.react) { detectedFramework = "react"; } // Vue else if (allDeps.vue) { detectedFramework = "vue"; } // Express.js else if (allDeps.express) { detectedFramework = "express"; } } } // Check for Python frameworks if (detectedFramework === "unknown") { if (existsSync(join(projectPath, "manage.py"))) { detectedFramework = "django"; } } } catch (error) { logger.warn("Error detecting framework", error); } return detectedFramework; } /** * Analyze file extensions in the project */ private async analyzeFileExtensions(projectPath: string): Promise<LanguageDetectionResult> { const extensionCounts = new Map<string, number>(); const evidence: string[] = []; try { const files = readdirSync(projectPath, { withFileTypes: true }); for (const file of files) { if (file.isFile()) { const ext = extname(file.name).toLowerCase(); if (ext) { extensionCounts.set(ext, (extensionCounts.get(ext) || 0) + 1); } } } const sortedExtensions = Array.from(extensionCounts.entries()) .sort(([,a], [,b]) => b - a); if (sortedExtensions.length > 0) { const topExtension = sortedExtensions[0][0]; const count = sortedExtensions[0][1]; const languageMap: Record<string, string> = { ".js": "javascript", ".ts": "typescript", ".py": "python", ".java": "java", ".go": "go", ".rs": "rust", }; const language = languageMap[topExtension] || "unknown"; evidence.push(`File extension analysis: ${count} ${topExtension} files`); return { language, confidence: Math.min(count * 5, 50), evidence, }; } } catch (error) { logger.warn("Error analyzing file extensions", error); } return { language: "unknown", confidence: 0, evidence: ["No files analyzed"], }; } /** * Read and parse package.json if it exists */ private readPackageJson(projectPath: string): any | null { try { const packageJsonPath = join(projectPath, "package.json"); if (existsSync(packageJsonPath)) { const content = readFileSync(packageJsonPath, 'utf-8'); return JSON.parse(content); } } catch (error) { logger.warn("Error reading package.json", error); } return null; } /** * Count files in project (excluding node_modules, .git, etc.) */ private async countFiles(projectPath: string): Promise<number> { let count = 0; const excludeDirs = new Set(['.git', 'node_modules', '.vscode', 'dist', 'build']); const countRecursive = (dirPath: string) => { try { const entries = readdirSync(dirPath, { withFileTypes: true }); for (const entry of entries) { if (entry.isDirectory() && !excludeDirs.has(entry.name)) { countRecursive(join(dirPath, entry.name)); } else if (entry.isFile()) { count++; } } } catch (error) { // Skip directories we can't read } }; countRecursive(projectPath); return count; } /** * Analyze architectural patterns */ private async analyzeArchitecturalPatterns(projectPath: string): Promise<string[]> { const patterns: string[] = []; // Check for common patterns if (existsSync(join(projectPath, "src", "components"))) { patterns.push("Component-based architecture"); } if (existsSync(join(projectPath, "src", "pages")) || existsSync(join(projectPath, "pages"))) { patterns.push("Page-based routing"); } if (existsSync(join(projectPath, "src", "store")) || existsSync(join(projectPath, "store"))) { patterns.push("Centralized state management"); } if (existsSync(join(projectPath, "api")) || existsSync(join(projectPath, "src", "api"))) { patterns.push("API layer separation"); } return patterns; } /** * Identify focus areas for testing */ private async identifyFocusAreas(projectPath: string, analysis: ProjectAnalysis): Promise<string[]> { const areas: string[] = []; // Based on framework if (analysis.framework === "react" || analysis.framework === "nextjs") { areas.push("Component testing", "User interaction flows"); } if (analysis.framework === "express" || analysis.framework === "django") { areas.push("API endpoints", "Database integration"); } // Based on structure if (existsSync(join(projectPath, "src", "auth"))) { areas.push("Authentication flows"); } if (existsSync(join(projectPath, "src", "payment"))) { areas.push("Payment processing"); } return areas; } /** * Extract repository name from path */ private extractRepoName(projectPath: string): string { return projectPath.split('/').pop() || 'unknown'; } /** * Get current git branch (simplified) */ private async getCurrentBranch(projectPath: string): Promise<string | undefined> { try { const gitHeadPath = join(projectPath, '.git', 'HEAD'); if (existsSync(gitHeadPath)) { const headContent = readFileSync(gitHeadPath, 'utf-8').trim(); if (headContent.startsWith('ref: refs/heads/')) { return headContent.replace('ref: refs/heads/', ''); } } } catch (error) { logger.debug('Could not determine git branch', error); } return undefined; } /** * Get current commit hash (simplified) */ private async getCurrentCommitHash(projectPath: string): Promise<string | undefined> { try { const gitHeadPath = join(projectPath, '.git', 'HEAD'); if (existsSync(gitHeadPath)) { const headContent = readFileSync(gitHeadPath, 'utf-8').trim(); if (headContent.startsWith('ref: refs/heads/')) { // Read from refs const branchRefPath = join(projectPath, '.git', headContent.replace('ref: ', '')); if (existsSync(branchRefPath)) { return readFileSync(branchRefPath, 'utf-8').trim(); } } else { // Direct commit hash return headContent; } } } catch (error) { logger.debug('Could not determine git commit hash', error); } return undefined; } /** * Return empty analysis structure */ private emptyAnalysis(): ProjectAnalysis { return { primaryLanguage: undefined, testingLanguage: undefined, testingFramework: undefined, repoName: undefined, repoPath: undefined, branchName: undefined, framework: undefined, }; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/debugg-ai/debugg-ai-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server