Skip to main content
Glama

Learning Hour MCP

by SDiamante13
RepositoryAnalyzer.ts6.59 kB
import { GitHubMCPClient } from './GitHubMCPClient'; import { logger } from './logger'; export interface CodeExample { filePath: string; lineNumbers: { start: number; end: number; }; confidenceScore: number; complexityRating: string; experienceLevel: string; codeSnippet: string; } export interface AnalysisResult { examples: CodeExample[]; codeSmell: string; repositoryUrl: string; } export class RepositoryAnalyzer { private githubClient: GitHubMCPClient; constructor(githubClient: GitHubMCPClient) { this.githubClient = githubClient; } async analyzeRepository(repositoryUrl: string, codeSmell: string): Promise<AnalysisResult> { const { owner, repo } = this.parseGitHubUrl(repositoryUrl); try { const searchQueries = this.getSearchQueriesForCodeSmell(codeSmell); const examples: CodeExample[] = []; for (const query of searchQueries) { try { const searchResult = await this.githubClient.searchRepositoryFiles(owner, repo, query); const items = (searchResult as any)?.content?.[0]?.text ? JSON.parse((searchResult as any).content[0].text).items : []; for (const item of items.slice(0, 3)) { const fileContent = await this.githubClient.getFileContent(owner, repo, item.path); const content = this.extractFileContent(fileContent); const example = this.analyzeFileForCodeSmell( item.path, content, codeSmell ); if (example) { examples.push(example); } } } catch (error) { if (error instanceof Error && error.message.includes('GitHub MCP client not connected')) { throw error; } logger.error(`Search query failed: ${query}`, error); } } if (examples.length === 0) { throw new Error(`No examples of '${codeSmell}' found in repository ${repositoryUrl}. Try searching for a different code smell or repository.`); } return { examples: examples.slice(0, 5), codeSmell, repositoryUrl }; } catch (error) { if (error instanceof Error && error.message.includes('GitHub MCP client not connected')) { throw new Error('GitHub integration not configured. Please ensure GITHUB_TOKEN is set in your environment.'); } throw error; } } private parseGitHubUrl(url: string): { owner: string; repo: string } { const match = url.match(/github\.com[/:]([\w-]+)\/([\w-]+)/); if (!match) { throw new Error("Invalid GitHub URL format"); } return { owner: match[1], repo: match[2] }; } private getSearchQueriesForCodeSmell(codeSmell: string): string[] { const codeSmellQueries: Record<string, string[]> = { 'Feature Envy': [ 'get().get().get()', 'getCustomer().getAddress()', 'user.getAccount().getBalance()' ], 'Long Method': [ 'function.*\\{[^}]{300,}\\}', 'def.*:\\n(?:.*\\n){50,}', 'public void' ], 'God Class': [ 'class.*\\{[^}]{1000,}\\}', 'Manager', 'Controller' ], 'Primitive Obsession': [ 'int.*int.*int.*int', 'String.*String.*String.*String', 'ArrayList<String>' ] }; return codeSmellQueries[codeSmell] || [codeSmell.toLowerCase()]; } private extractFileContent(fileResponse: any): string { try { const text = (fileResponse as any)?.content?.[0]?.text; if (!text) return ''; const parsed = JSON.parse(text); if (parsed.content) { return Buffer.from(parsed.content, 'base64').toString('utf-8'); } return ''; } catch { return ''; } } private analyzeFileForCodeSmell( filePath: string, content: string, codeSmell: string ): CodeExample | null { const lines = content.split('\n'); const patterns = this.getPatternForCodeSmell(codeSmell); for (const pattern of patterns) { const regex = new RegExp(pattern, 'gm'); let match; while ((match = regex.exec(content)) !== null) { const startLine = content.substring(0, match.index).split('\n').length; const endLine = startLine + match[0].split('\n').length - 1; return { filePath, lineNumbers: { start: startLine, end: endLine }, confidenceScore: this.calculateConfidence(match[0], codeSmell), complexityRating: this.assessComplexity(match[0]), experienceLevel: this.determineExperienceLevel(match[0]), codeSnippet: match[0].trim() }; } } return null; } private getPatternForCodeSmell(codeSmell: string): string[] { const patterns: Record<string, string[]> = { 'Feature Envy': [ '\\w+\\.get\\w+\\(\\)\\.get\\w+\\(\\)', '\\w+\\.\\w+\\(\\)\\.\\w+\\(\\)\\.\\w+\\(\\)', 'get[A-Z]\\w*\\(\\)\\.[gs]et[A-Z]\\w*\\(\\)' ], 'Long Method': [ '(function|def|public\\s+\\w+)\\s+\\w+\\s*\\([^)]*\\)\\s*\\{[^}]{200,}\\}', 'def\\s+\\w+.*?:\\n(\\s{4,}.*\\n){20,}' ], 'God Class': [ 'class\\s+\\w+.*?\\{[^}]{500,}\\}', 'class\\s+\\w*(Manager|Controller|Service)\\s*' ], 'Primitive Obsession': [ '(int|string|float|double)\\s+\\w+,\\s*(int|string|float|double)\\s+\\w+,\\s*(int|string|float|double)', 'List<(String|Integer|Double)>' ] }; return patterns[codeSmell] || ['\\b' + codeSmell.toLowerCase().replace(/\s+/g, '.*') + '\\b']; } private calculateConfidence(code: string, codeSmell: string): number { let score = 0.7; const featureEnvyMatches = code.match(/\.\w+\(\)\./g); if (codeSmell === 'Feature Envy' && featureEnvyMatches && featureEnvyMatches.length >= 2) { score += 0.2; } if (codeSmell === 'Long Method' && code.split('\n').length > 30) { score += 0.15; } return Math.min(score, 0.95); } private assessComplexity(code: string): string { const lines = code.split('\n').length; const conditions = (code.match(/if|else|for|while|switch/g) || []).length; if (lines > 50 || conditions > 10) return 'high'; if (lines > 20 || conditions > 5) return 'medium'; return 'low'; } private determineExperienceLevel(code: string): string { const complexity = this.assessComplexity(code); if (complexity === 'high') return 'advanced'; if (complexity === 'medium') return 'intermediate'; return 'beginner'; } }

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/SDiamante13/learning-hour-mcp'

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