Skip to main content
Glama

AI Agent Template MCP Server

by bswa006
pattern-detector.tsโ€ข13.8 kB
import { readdirSync, readFileSync, statSync } from 'fs'; import { join, extname } from 'path'; interface DetectedPattern { type: string; pattern: string; examples: string[]; frequency: number; confidence: number; } interface PatternAnalysis { directory: string; fileType: string; patterns: { imports: DetectedPattern[]; components: DetectedPattern[]; hooks: DetectedPattern[]; stateManagement: DetectedPattern[]; errorHandling: DetectedPattern[]; styling: DetectedPattern[]; }; recommendations: string[]; } export async function detectExistingPatterns( directory: string, fileType: string ): Promise<PatternAnalysis> { const analysis: PatternAnalysis = { directory, fileType, patterns: { imports: [], components: [], hooks: [], stateManagement: [], errorHandling: [], styling: [], }, recommendations: [], }; try { // Find all relevant files const files = findFiles(directory, fileType); if (files.length === 0) { analysis.recommendations.push(`No ${fileType} files found in ${directory}`); return analysis; } // Analyze each file const fileContents = files.map(file => ({ path: file, content: readFileSync(file, 'utf-8'), })); // Detect patterns analysis.patterns.imports = detectImportPatterns(fileContents); analysis.patterns.components = detectComponentPatterns(fileContents, fileType); analysis.patterns.hooks = detectHookPatterns(fileContents); analysis.patterns.stateManagement = detectStatePatterns(fileContents); analysis.patterns.errorHandling = detectErrorPatterns(fileContents); analysis.patterns.styling = detectStylingPatterns(fileContents); // Generate recommendations generateRecommendations(analysis); } catch (error) { analysis.recommendations.push(`Error analyzing directory: ${error}`); } return analysis; } function findFiles(directory: string, fileType: string): string[] { const files: string[] = []; const extensions = getExtensionsForFileType(fileType); function traverse(dir: string) { try { const items = readdirSync(dir); for (const item of items) { if (item.startsWith('.') || item === 'node_modules') continue; const fullPath = join(dir, item); const stats = statSync(fullPath); if (stats.isDirectory()) { traverse(fullPath); } else if (extensions.includes(extname(item))) { files.push(fullPath); } } } catch (error) { // Skip directories we can't read } } traverse(directory); return files; } function getExtensionsForFileType(fileType: string): string[] { const typeMap: Record<string, string[]> = { component: ['.tsx', '.jsx'], hook: ['.ts', '.tsx', '.js', '.jsx'], service: ['.ts', '.js'], api: ['.ts', '.js'], test: ['.test.ts', '.test.tsx', '.spec.ts', '.spec.tsx'], }; return typeMap[fileType] || ['.ts', '.tsx', '.js', '.jsx']; } function detectImportPatterns(files: Array<{path: string, content: string}>): DetectedPattern[] { const importCounts = new Map<string, number>(); const importExamples = new Map<string, string[]>(); files.forEach(({ content }) => { const imports = content.match(/import .+ from ['"](.+)['"]/g) || []; imports.forEach(imp => { // Categorize import style let style = 'unknown'; if (imp.includes('* as')) style = 'namespace'; else if (imp.includes('{')) style = 'named'; else if (imp.includes('import type')) style = 'type-only'; else style = 'default'; importCounts.set(style, (importCounts.get(style) || 0) + 1); const examples = importExamples.get(style) || []; if (examples.length < 3) { examples.push(imp.trim()); importExamples.set(style, examples); } }); }); const patterns: DetectedPattern[] = []; const totalImports = Array.from(importCounts.values()).reduce((a, b) => a + b, 0); importCounts.forEach((count, style) => { patterns.push({ type: 'import-style', pattern: style, examples: importExamples.get(style) || [], frequency: count, confidence: count / totalImports, }); }); return patterns.sort((a, b) => b.frequency - a.frequency); } function detectComponentPatterns( files: Array<{path: string, content: string}>, fileType: string ): DetectedPattern[] { if (fileType !== 'component') return []; const patterns: DetectedPattern[] = []; const componentStyles = new Map<string, number>(); const examples = new Map<string, string[]>(); files.forEach(({ content, path }) => { // Function components if (content.match(/const\s+\w+:\s*React\.FC/)) { incrementPattern(componentStyles, examples, 'React.FC', path); } else if (content.match(/function\s+\w+\s*\([^)]*\)\s*{/)) { incrementPattern(componentStyles, examples, 'function-declaration', path); } else if (content.match(/const\s+\w+\s*=\s*\([^)]*\)\s*=>/)) { incrementPattern(componentStyles, examples, 'arrow-function', path); } // Props patterns if (content.includes('interface') && content.includes('Props')) { incrementPattern(componentStyles, examples, 'interface-props', path); } else if (content.includes('type') && content.includes('Props')) { incrementPattern(componentStyles, examples, 'type-props', path); } // Export patterns if (content.includes('export default')) { incrementPattern(componentStyles, examples, 'default-export', path); } else if (content.match(/export\s*{/)) { incrementPattern(componentStyles, examples, 'named-export', path); } }); // Convert to pattern array componentStyles.forEach((count, style) => { patterns.push({ type: 'component-structure', pattern: style, examples: examples.get(style) || [], frequency: count, confidence: count / files.length, }); }); return patterns.sort((a, b) => b.frequency - a.frequency); } function detectHookPatterns(files: Array<{path: string, content: string}>): DetectedPattern[] { const patterns: DetectedPattern[] = []; const hookUsage = new Map<string, number>(); const customHooks = new Set<string>(); files.forEach(({ content }) => { // Built-in hooks const builtInHooks = [ 'useState', 'useEffect', 'useContext', 'useReducer', 'useCallback', 'useMemo', 'useRef', 'useLayoutEffect', ]; builtInHooks.forEach(hook => { const regex = new RegExp(`\\b${hook}\\b`, 'g'); const matches = content.match(regex); if (matches) { hookUsage.set(hook, (hookUsage.get(hook) || 0) + matches.length); } }); // Custom hooks const customHookMatches = content.match(/\buse[A-Z][a-zA-Z]+/g) || []; customHookMatches.forEach(hook => { if (!builtInHooks.includes(hook)) { customHooks.add(hook); } }); }); // Add built-in hook patterns hookUsage.forEach((count, hook) => { patterns.push({ type: 'built-in-hook', pattern: hook, examples: [`const [...] = ${hook}(...)`], frequency: count, confidence: 1.0, }); }); // Add custom hook patterns customHooks.forEach(hook => { patterns.push({ type: 'custom-hook', pattern: hook, examples: [hook], frequency: 1, confidence: 0.8, }); }); return patterns.sort((a, b) => b.frequency - a.frequency); } function detectStatePatterns(files: Array<{path: string, content: string}>): DetectedPattern[] { const patterns: DetectedPattern[] = []; const statePatterns = new Map<string, number>(); files.forEach(({ content }) => { // Local state if (content.includes('useState')) { statePatterns.set('local-state', (statePatterns.get('local-state') || 0) + 1); } // Reducer pattern if (content.includes('useReducer')) { statePatterns.set('reducer', (statePatterns.get('reducer') || 0) + 1); } // Context if (content.includes('useContext') || content.includes('createContext')) { statePatterns.set('context', (statePatterns.get('context') || 0) + 1); } // State management libraries if (content.includes('useSelector') || content.includes('useDispatch')) { statePatterns.set('redux', (statePatterns.get('redux') || 0) + 1); } if (content.includes('useStore') && content.includes('zustand')) { statePatterns.set('zustand', (statePatterns.get('zustand') || 0) + 1); } if (content.includes('useRecoilState')) { statePatterns.set('recoil', (statePatterns.get('recoil') || 0) + 1); } }); statePatterns.forEach((count, pattern) => { patterns.push({ type: 'state-management', pattern, examples: [], frequency: count, confidence: count / files.length, }); }); return patterns.sort((a, b) => b.frequency - a.frequency); } function detectErrorPatterns(files: Array<{path: string, content: string}>): DetectedPattern[] { const patterns: DetectedPattern[] = []; const errorPatterns = new Map<string, number>(); const examples = new Map<string, string[]>(); files.forEach(({ content, path }) => { // Try-catch if (content.includes('try {') && content.includes('catch')) { incrementPattern(errorPatterns, examples, 'try-catch', 'try-catch blocks'); } // .catch() on promises if (content.match(/\.\s*catch\s*\(/)) { incrementPattern(errorPatterns, examples, 'promise-catch', '.catch() on promises'); } // Error boundaries if (content.includes('componentDidCatch') || content.includes('ErrorBoundary')) { incrementPattern(errorPatterns, examples, 'error-boundary', 'React Error Boundary'); } // Loading/Error/Empty states if (content.includes('isLoading') || content.includes('loading')) { incrementPattern(errorPatterns, examples, 'loading-state', 'Loading state handling'); } if (content.includes('error') && content.includes('return')) { incrementPattern(errorPatterns, examples, 'error-state', 'Error state handling'); } }); errorPatterns.forEach((count, pattern) => { patterns.push({ type: 'error-handling', pattern, examples: examples.get(pattern) || [], frequency: count, confidence: count / files.length, }); }); return patterns.sort((a, b) => b.frequency - a.frequency); } function detectStylingPatterns(files: Array<{path: string, content: string}>): DetectedPattern[] { const patterns: DetectedPattern[] = []; const stylingPatterns = new Map<string, number>(); files.forEach(({ content }) => { // CSS Modules if (content.includes('styles.') || content.includes('.module.css')) { stylingPatterns.set('css-modules', (stylingPatterns.get('css-modules') || 0) + 1); } // Styled Components if (content.includes('styled.') || content.includes('styled(')) { stylingPatterns.set('styled-components', (stylingPatterns.get('styled-components') || 0) + 1); } // Tailwind if (content.match(/className\s*=\s*["'][^"']*\b(flex|grid|p-|m-|bg-|text-)/)) { stylingPatterns.set('tailwind', (stylingPatterns.get('tailwind') || 0) + 1); } // Inline styles if (content.includes('style={{') || content.includes('style={')) { stylingPatterns.set('inline-styles', (stylingPatterns.get('inline-styles') || 0) + 1); } // CSS-in-JS (Emotion) if (content.includes('@emotion') || content.includes('css`')) { stylingPatterns.set('emotion', (stylingPatterns.get('emotion') || 0) + 1); } }); stylingPatterns.forEach((count, pattern) => { patterns.push({ type: 'styling', pattern, examples: [], frequency: count, confidence: count / files.length, }); }); return patterns.sort((a, b) => b.frequency - a.frequency); } function incrementPattern( counts: Map<string, number>, examples: Map<string, string[]>, pattern: string, example: string ) { counts.set(pattern, (counts.get(pattern) || 0) + 1); const exampleList = examples.get(pattern) || []; if (exampleList.length < 3 && !exampleList.includes(example)) { exampleList.push(example); examples.set(pattern, exampleList); } } function generateRecommendations(analysis: PatternAnalysis) { const { patterns } = analysis; // Import recommendations const topImportStyle = patterns.imports[0]; if (topImportStyle) { analysis.recommendations.push( `Use ${topImportStyle.pattern} imports (${Math.round(topImportStyle.confidence * 100)}% of codebase)` ); } // Component recommendations const topComponentStyle = patterns.components[0]; if (topComponentStyle) { analysis.recommendations.push( `Follow ${topComponentStyle.pattern} pattern for components` ); } // State management recommendations const topStatePattern = patterns.stateManagement[0]; if (topStatePattern) { analysis.recommendations.push( `Use ${topStatePattern.pattern} for state management` ); } // Error handling recommendations if (patterns.errorHandling.length === 0) { analysis.recommendations.push( 'Add error handling patterns (try-catch, error boundaries)' ); } // Styling recommendations const topStylingPattern = patterns.styling[0]; if (topStylingPattern) { analysis.recommendations.push( `Continue using ${topStylingPattern.pattern} for styling` ); } // Hook usage const mostUsedHooks = patterns.hooks .filter(h => h.type === 'built-in-hook') .slice(0, 3) .map(h => h.pattern); if (mostUsedHooks.length > 0) { analysis.recommendations.push( `Common hooks: ${mostUsedHooks.join(', ')}` ); } }

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/bswa006/mcp-context-manager'

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