Skip to main content
Glama
mdz-axo

PT-MCP (Paul Test Man Context Protocol)

by mdz-axo
extract-patterns.ts12.6 kB
/** * extract_patterns tool implementation * * Extracts architectural and coding patterns with KG enrichment */ import { readdir, readFile } from 'fs/promises'; import { join, extname, relative } from 'path'; import { getDatabase } from '../knowledge-graph/database.js'; import { getYAGOResolver } from '../knowledge-graph/yago-resolver.js'; interface ExtractPatternsArgs { path: string; pattern_types?: string[]; min_occurrences?: number; } interface DetectedPattern { type: string; name: string; occurrences: number; examples: string[]; description?: string; confidence: number; kg_entities?: any[]; } export async function extractPatterns( args: ExtractPatternsArgs ): Promise<{ content: Array<{ type: string; text: string }> }> { const { path, pattern_types = ['architectural', 'design', 'naming', 'testing'], min_occurrences = 3 } = args; const patterns: Record<string, DetectedPattern[]> = { architectural: [], design: [], naming: [], testing: [], }; // Scan codebase for patterns const files = await getCodeFiles(path); for (const file of files) { const content = await readFile(file, 'utf-8'); const relPath = relative(path, file); // Extract different pattern types if (pattern_types.includes('architectural')) { const archPatterns = extractArchitecturalPatterns(content, relPath); patterns.architectural.push(...archPatterns); } if (pattern_types.includes('design')) { const designPatterns = extractDesignPatterns(content, relPath); patterns.design.push(...designPatterns); } if (pattern_types.includes('naming')) { const namingPatterns = extractNamingPatterns(content, relPath); patterns.naming.push(...namingPatterns); } if (pattern_types.includes('testing')) { const testPatterns = extractTestingPatterns(content, relPath); patterns.testing.push(...testPatterns); } } // Aggregate and filter by min_occurrences const aggregated: Record<string, DetectedPattern[]> = {}; for (const [type, typePatterns] of Object.entries(patterns)) { const grouped = groupPatterns(typePatterns, min_occurrences); aggregated[type] = grouped; // Enrich with knowledge graph for (const pattern of grouped) { await enrichPatternWithKG(pattern); } } // Store in database await storePatternsInDB(aggregated); // Format results const results = formatPatternResults(aggregated); return { content: [ { type: 'text', text: results, }, ], }; } /** * Get all code files in directory */ async function getCodeFiles(dir: string): Promise<string[]> { const files: string[] = []; const entries = await readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = join(dir, entry.name); // Skip node_modules, .git, etc. if (entry.name === 'node_modules' || entry.name === '.git' || entry.name.startsWith('.')) { continue; } if (entry.isDirectory()) { const subFiles = await getCodeFiles(fullPath); files.push(...subFiles); } else if (entry.isFile()) { const ext = extname(entry.name); if (['.ts', '.js', '.tsx', '.jsx', '.py', '.java', '.go', '.rs'].includes(ext)) { files.push(fullPath); } } } return files; } /** * Extract architectural patterns */ function extractArchitecturalPatterns(content: string, file: string): DetectedPattern[] { const patterns: DetectedPattern[] = []; // MVC pattern if (file.includes('/controllers/') || content.includes('Controller')) { patterns.push({ type: 'architectural', name: 'MVC', occurrences: 1, examples: [file], confidence: 0.85, }); } // Repository pattern if (file.includes('/repositories/') || content.match(/class \w+Repository/)) { patterns.push({ type: 'architectural', name: 'Repository Pattern', occurrences: 1, examples: [file], confidence: 0.9, }); } // Service layer if (file.includes('/services/') || content.match(/class \w+Service/)) { patterns.push({ type: 'architectural', name: 'Service Layer', occurrences: 1, examples: [file], confidence: 0.85, }); } // Dependency injection if (content.includes('inject(') || content.includes('@Injectable') || content.includes('constructor(')) { patterns.push({ type: 'architectural', name: 'Dependency Injection', occurrences: 1, examples: [file], confidence: 0.8, }); } // Microservices if (file.includes('/microservices/') || file.includes('/services/') && content.includes('express')) { patterns.push({ type: 'architectural', name: 'Microservices', occurrences: 1, examples: [file], confidence: 0.75, }); } return patterns; } /** * Extract design patterns */ function extractDesignPatterns(content: string, file: string): DetectedPattern[] { const patterns: DetectedPattern[] = []; // Singleton pattern if (content.match(/private static instance|getInstance\(\)/)) { patterns.push({ type: 'design', name: 'Singleton', occurrences: 1, examples: [file], confidence: 0.95, }); } // Factory pattern if (content.match(/createFactory|Factory\.create|makeFactory/)) { patterns.push({ type: 'design', name: 'Factory', occurrences: 1, examples: [file], confidence: 0.9, }); } // Observer pattern if (content.match(/addEventListener|subscribe|on\(|emit\(/)) { patterns.push({ type: 'design', name: 'Observer', occurrences: 1, examples: [file], confidence: 0.85, }); } // Decorator pattern if (content.match(/@\w+\(|@decorator|\.decorator/)) { patterns.push({ type: 'design', name: 'Decorator', occurrences: 1, examples: [file], confidence: 0.8, }); } // Strategy pattern if (content.match(/Strategy|strategy\s*:/)) { patterns.push({ type: 'design', name: 'Strategy', occurrences: 1, examples: [file], confidence: 0.75, }); } // Builder pattern if (content.match(/\.build\(\)|Builder|\.with\w+\(/)) { patterns.push({ type: 'design', name: 'Builder', occurrences: 1, examples: [file], confidence: 0.8, }); } return patterns; } /** * Extract naming patterns */ function extractNamingPatterns(content: string, file: string): DetectedPattern[] { const patterns: DetectedPattern[] = []; // CamelCase if (content.match(/[a-z][A-Z]/)) { patterns.push({ type: 'naming', name: 'camelCase', occurrences: 1, examples: [file], confidence: 1.0, }); } // PascalCase if (content.match(/class [A-Z][a-z]/)) { patterns.push({ type: 'naming', name: 'PascalCase', occurrences: 1, examples: [file], confidence: 1.0, }); } // snake_case if (content.match(/[a-z]+_[a-z]+/)) { patterns.push({ type: 'naming', name: 'snake_case', occurrences: 1, examples: [file], confidence: 1.0, }); } // SCREAMING_SNAKE_CASE (constants) if (content.match(/[A-Z]+_[A-Z]+/)) { patterns.push({ type: 'naming', name: 'SCREAMING_SNAKE_CASE', occurrences: 1, examples: [file], confidence: 1.0, }); } return patterns; } /** * Extract testing patterns */ function extractTestingPatterns(content: string, file: string): DetectedPattern[] { const patterns: DetectedPattern[] = []; // Unit tests if (file.includes('.test.') || file.includes('.spec.')) { patterns.push({ type: 'testing', name: 'Unit Testing', occurrences: 1, examples: [file], confidence: 1.0, }); } // describe/it pattern (BDD) if (content.includes('describe(') && content.includes('it(')) { patterns.push({ type: 'testing', name: 'BDD (Behavior-Driven Development)', occurrences: 1, examples: [file], confidence: 0.95, }); } // Test fixtures if (content.match(/beforeEach|beforeAll|afterEach|afterAll|setUp|tearDown/)) { patterns.push({ type: 'testing', name: 'Test Fixtures', occurrences: 1, examples: [file], confidence: 0.9, }); } // Mocking if (content.match(/mock|stub|spy|jest\.fn|sinon/)) { patterns.push({ type: 'testing', name: 'Test Mocking', occurrences: 1, examples: [file], confidence: 0.9, }); } return patterns; } /** * Group patterns by name and count occurrences */ function groupPatterns(patterns: DetectedPattern[], minOccurrences: number): DetectedPattern[] { const grouped = new Map<string, DetectedPattern>(); for (const pattern of patterns) { const key = pattern.name; if (grouped.has(key)) { const existing = grouped.get(key)!; existing.occurrences += pattern.occurrences; existing.examples.push(...pattern.examples); existing.confidence = Math.max(existing.confidence, pattern.confidence); } else { grouped.set(key, { ...pattern }); } } return Array.from(grouped.values()) .filter((p) => p.occurrences >= minOccurrences) .sort((a, b) => b.occurrences - a.occurrences); } /** * Enrich pattern with knowledge graph data */ async function enrichPatternWithKG(pattern: DetectedPattern): Promise<void> { try { const yagoResolver = getYAGOResolver(); const entities = await yagoResolver.resolveEntity(pattern.name, 2); if (entities.length > 0) { pattern.kg_entities = entities; pattern.description = entities[0].description || pattern.description; } } catch (error) { console.error(`Failed to enrich pattern ${pattern.name}:`, error); } } /** * Store patterns in database */ async function storePatternsInDB(patterns: Record<string, DetectedPattern[]>): Promise<void> { const db = await getDatabase(); for (const [category, categoryPatterns] of Object.entries(patterns)) { for (const pattern of categoryPatterns) { try { // Check if already exists const existing = db.findProgrammingConceptByName(pattern.name); if (!existing) { await db.insertProgrammingConcept({ concept_name: pattern.name, category, description: pattern.description, examples: pattern.examples.slice(0, 5), metadata: { occurrences: pattern.occurrences, confidence: pattern.confidence, kg_enriched: !!pattern.kg_entities, }, }); } } catch (error) { console.error(`Failed to store pattern ${pattern.name}:`, error); } } } } /** * Format pattern results for display */ function formatPatternResults(patterns: Record<string, DetectedPattern[]>): string { const lines: string[] = []; lines.push('# Extracted Patterns'); lines.push(''); for (const [type, typePatterns] of Object.entries(patterns)) { if (typePatterns.length === 0) continue; lines.push(`## ${type.charAt(0).toUpperCase() + type.slice(1)} Patterns`); lines.push(''); for (const pattern of typePatterns) { lines.push(`### ${pattern.name}`); lines.push(`- **Occurrences:** ${pattern.occurrences}`); lines.push(`- **Confidence:** ${(pattern.confidence * 100).toFixed(0)}%`); if (pattern.description) { lines.push(`- **Description:** ${pattern.description}`); } if (pattern.kg_entities && pattern.kg_entities.length > 0) { const entity = pattern.kg_entities[0]; lines.push(`- **Knowledge Graph:** ${entity.label}`); if (entity.facts.length > 0) { lines.push(` - ${entity.facts.length} related facts in YAGO`); } } lines.push(`- **Examples:**`); for (const example of pattern.examples.slice(0, 3)) { lines.push(` - ${example}`); } lines.push(''); } } return lines.join('\n'); }

Latest Blog Posts

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/mdz-axo/pt-mcp'

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