Skip to main content
Glama
enhancedHints.ts30 kB
/** * @fileOverview: Enhanced hint builder with capabilities mapping and risk assessment * @module: EnhancedHints * @keyFunctions: * - buildEnhancedProjectSummary(): Create rich project analysis with actionable hints * - buildCapabilitiesMap(): Infer domain capabilities from code surfaces * - assessRisks(): Identify potential project risks and issues * - generateNextActions(): Propose concrete next steps for agents * - generateAnswerDraft(): Deterministic query responses * @context: Transforms raw indexing data into actionable intelligence for AI agents */ import { FileInfo } from '../../core/compactor/fileDiscovery'; import { ExportItem, RouteItem, ToolItem, EnvItem, DbInfo, GitInfo, buildExportIndex, buildImportGraph, detectRoutes, detectMcpTools, detectEnvKeys, detectDb, detectGitInfo, } from './indexers'; import { generateRankedHints, ScoredHint, ScoreContext } from './scoring'; import { logger } from '../../utils/logger'; import { toPosix } from './utils/pathUtils'; import { formatSignature } from './utils/publicApi'; import * as path from 'path'; export interface EnhancedProjectSummary { summary: ProjectSummary; surfaces: PublicSurfaces; systems: SystemsDetection; capabilities: CapabilitiesMap; risks: RiskAssessment; hints: ScoredHint[]; next: NextActions; } export interface ProjectSummary { languages: string[]; files: number; entryPoints: string[]; codebaseSize: string; } export interface PublicSurfaces { exports: ExportSummary[]; routes: RouteSummary[]; mcpTools: ToolSummary[]; envKeys: string[]; } export interface SystemsDetection { db?: { engine: string; initializers: string[]; confidence: number; }; provider?: { initializer: string; confidence: number; }; architecture: string[]; } export interface CapabilitiesMap { domains: string[]; operations: string[]; integrations: string[]; } export interface RiskAssessment { flags: RiskFlag[]; score: number; // 0-100, lower is better recommendations: string[]; } export interface NextActions { mode: 'code_lookup' | 'project_research' | 'implementation_ready'; openFiles: string[]; checks: string[]; focus: string; } // Simplified interfaces for surfaces export interface ExportSummary { name: string; kind: string; file: string; line: number; role?: string; } export interface RouteSummary { method: string; path: string; file: string; line: number; } export interface ToolSummary { name: string; file: string; line: number; } export interface RiskFlag { type: 'security' | 'performance' | 'maintenance' | 'config'; severity: 'low' | 'medium' | 'high'; message: string; file?: string; } /** * Build enhanced project summary with all intelligence layers */ export async function buildEnhancedProjectSummary( projectPath: string, files: FileInfo[], query?: string, maxHints: number = 7 ): Promise<EnhancedProjectSummary> { logger.info('Building enhanced project summary', { projectPath, fileCount: files.length, hasQuery: !!query, }); try { // Build all indices in parallel for performance const [exports, importGraph, routes, mcpTools, envKeys, dbInfo, gitInfo] = await Promise.all([ buildExportIndex(files), buildImportGraph(files), detectRoutes(files), detectMcpTools(files), detectEnvKeys(files), detectDb(files), detectGitInfo(projectPath), ]); // Build git commit map for recency scoring const gitMap: Record<string, string | undefined> = {}; if (gitInfo.lastCommitDate) { // For now, assume all files have the same last commit date // In a real implementation, you'd get per-file git info files.forEach(file => { gitMap[file.relPath] = gitInfo.lastCommitDate; }); } // Prepare scoring context const queryTerms = query ? query.split(/\W+/).filter(term => term.length > 2) : []; const scoreContext: ScoreContext = { exports, routes, tools: mcpTools, importGraph, gitMap, queryTerms, }; // Generate ranked hints const hints = generateRankedHints(scoreContext, maxHints); // Build all analysis components const summary = buildProjectSummary(files); const surfaces = buildPublicSurfaces(exports, routes, mcpTools, envKeys); const systems = buildSystemsDetection(dbInfo, exports, files); const capabilities = buildCapabilitiesMap(exports, routes, mcpTools, envKeys); const risks = assessRisks(files, envKeys, exports, mcpTools); const next = generateNextActions(hints, query, capabilities, risks); const result: EnhancedProjectSummary = { summary, surfaces, systems, capabilities, risks, hints, next, }; logger.info('Enhanced project summary built', { hintsCount: hints.length, capabilitiesCount: capabilities.domains.length, risksCount: risks.flags.length, nextMode: next.mode, }); return result; } catch (error) { logger.error('Failed to build enhanced project summary', { error: (error as Error).message, projectPath, }); throw error; } } /** * Build basic project summary */ function buildProjectSummary(files: FileInfo[]): ProjectSummary { const languageCounts = files.reduce( (acc, file) => { acc[file.language] = (acc[file.language] || 0) + 1; return acc; }, {} as Record<string, number> ); const languages = Object.entries(languageCounts) .sort(([, a], [, b]) => b - a) .slice(0, 3) .map(([lang]) => lang); const entryPoints = files .filter(file => /(index|main|app|server)\.(ts|js|py)$/i.test(file.relPath)) .map(file => file.relPath) .slice(0, 5); const totalSize = files.reduce((sum, file) => sum + file.size, 0); const codebaseSize = formatFileSize(totalSize); return { languages, files: files.length, entryPoints, codebaseSize, }; } /** * Build public surfaces summary * Fix for critique item #2: Exclude HTTP method exports from Public API Surfaces */ function buildPublicSurfaces( exports: ExportItem[], routes: RouteItem[], mcpTools: ToolItem[], envKeys: EnvItem[] ): PublicSurfaces { // Filter out HTTP method exports that are actually route handlers const filteredExports = filterRouteExportsFromPublicAPI(exports, routes); return { exports: filteredExports.slice(0, 15).map(exp => ({ name: exp.name, kind: exp.kind, file: toPosix(exp.file), // Ensure POSIX paths in JSON line: exp.line, role: exp.role || inferExportRole(exp.name, exp.kind), signature: exp.signature || formatSignature(exp), })), routes: routes.slice(0, 10).map(route => ({ method: route.method, path: route.path, // Already POSIX from indexers file: toPosix(route.file), line: route.line, })), mcpTools: mcpTools.slice(0, 10).map(tool => ({ name: tool.name, file: toPosix(tool.file), line: tool.line, })), envKeys: [...new Set(envKeys.map(env => env.key))].slice(0, 15), }; } /** * Build systems detection from database and provider analysis */ function buildSystemsDetection( dbInfo: DbInfo, exports: ExportItem[], files: FileInfo[] ): SystemsDetection { const systems: SystemsDetection = { architecture: [], }; // Database system with evidence-based confidence if (dbInfo.engine && dbInfo.engine !== 'unknown') { systems.db = { engine: dbInfo.engine, initializers: dbInfo.initializers.map(init => `${toPosix(init.file)}:${init.symbol}`), confidence: dbInfo.confidence || 0.6, // Use evidence-based confidence or fallback }; } // Provider/Pipeline detection const providerExports = exports.filter( exp => /provider|pipeline|factory|manager|service/i.test(exp.name) && /init|create|setup|build/.test(exp.name.toLowerCase()) ); if (providerExports.length > 0) { const mainProvider = providerExports[0]; systems.provider = { initializer: `${toPosix(mainProvider.file)}:${mainProvider.name}`, confidence: 0.8, }; } // Architecture patterns const patterns = new Set<string>(); // Framework detection if (files.some(f => f.relPath.includes('package.json'))) { patterns.add('nodejs'); } if (exports.some(exp => /react|component|jsx/i.test(exp.name))) { patterns.add('react'); } if (exports.some(exp => /express|fastify|koa/i.test(exp.name))) { patterns.add('web-framework'); } if (exports.some(exp => /mcp|tool|handler/i.test(exp.name))) { patterns.add('mcp-server'); } if (dbInfo.engine && dbInfo.engine !== 'unknown') { patterns.add('database-driven'); } systems.architecture = Array.from(patterns); return systems; } /** * Build capabilities map from code analysis */ function buildCapabilitiesMap( exports: ExportItem[], routes: RouteItem[], mcpTools: ToolItem[], envKeys: EnvItem[] ): CapabilitiesMap { const domains = new Set<string>(); const operations = new Set<string>(); const integrations = new Set<string>(); // Verifier #5: Filter exports with noise reduction const filteredExports = filterNoisySymbols(exports); // Analyze filtered exports for domain capabilities for (const exp of filteredExports) { const name = exp.name.toLowerCase(); // Domain detection if (/auth|login|token|session/.test(name)) domains.add('authentication'); if (/search|index|query|find/.test(name)) domains.add('search'); if (/embed|vector|semantic/.test(name)) domains.add('embeddings'); if (/db|database|store|persist/.test(name)) domains.add('storage'); if (/file|upload|download|blob/.test(name)) domains.add('files'); if (/api|endpoint|route|handler/.test(name)) domains.add('api'); if (/user|account|profile/.test(name)) domains.add('user-management'); if (/config|setting|env/.test(name)) domains.add('configuration'); if (/tool|command|action/.test(name)) domains.add('tools'); if (/cache|memory|redis/.test(name)) domains.add('caching'); // Operation detection if (/create|add|insert|save|store/.test(name)) operations.add('create'); if (/get|read|fetch|load|retrieve/.test(name)) operations.add('read'); if (/update|modify|edit|change/.test(name)) operations.add('update'); if (/delete|remove|destroy/.test(name)) operations.add('delete'); if (/search|query|find|filter/.test(name)) operations.add('search'); if (/validate|verify|check/.test(name)) operations.add('validation'); if (/transform|convert|parse|format/.test(name)) operations.add('transformation'); if (/sync|replicate|backup/.test(name)) operations.add('synchronization'); } // Analyze routes for API capabilities for (const route of routes) { operations.add(route.method.toLowerCase()); const pathLower = route.path.toLowerCase(); if (/\/api\//.test(pathLower)) domains.add('api'); if (/\/auth\//.test(pathLower)) domains.add('authentication'); if (/\/user/.test(pathLower)) domains.add('user-management'); if (/\/search/.test(pathLower)) domains.add('search'); if (/\/file|\/upload/.test(pathLower)) domains.add('files'); } // Analyze MCP tools for capabilities for (const tool of mcpTools) { domains.add('mcp-tools'); const toolName = tool.name.toLowerCase(); if (/search|query|find/.test(toolName)) domains.add('search'); if (/context|hint|project/.test(toolName)) domains.add('project-analysis'); if (/file|directory/.test(toolName)) domains.add('file-system'); } // Analyze environment keys for integrations for (const env of envKeys) { const key = env.key.toLowerCase(); if (/openai|anthropic|claude/.test(key)) integrations.add('ai-services'); if (/supabase|firebase/.test(key)) integrations.add('backend-as-service'); if (/postgres|mysql|mongo/.test(key)) integrations.add('database'); if (/redis|cache/.test(key)) integrations.add('caching'); if (/stripe|payment/.test(key)) integrations.add('payments'); if (/aws|azure|gcp|s3/.test(key)) integrations.add('cloud-services'); if (/smtp|email|sendgrid/.test(key)) integrations.add('email-services'); if (/github|gitlab|git/.test(key)) integrations.add('version-control'); } return { domains: Array.from(domains).slice(0, 10), operations: Array.from(operations).slice(0, 10), integrations: Array.from(integrations).slice(0, 8), }; } /** * Assess project risks and generate recommendations */ function assessRisks( files: FileInfo[], envKeys: EnvItem[], exports: ExportItem[], mcpTools: ToolItem[] ): RiskAssessment { const flags: RiskFlag[] = []; let riskScore = 0; // Security risks - Smart .env handling for MCP servers // Note: MCP servers typically configure environment variables in mcp.json rather than .env files // since MCP clients (like Claude Desktop) pass env vars when launching the server // Improved env.example detection - scan for nested env.example files const hasEnvExample = files.some(f => { const relPath = f.relPath.replace(/\\/g, '/'); // Check for exact .env.example files at any nesting level return ( relPath.endsWith('.env.example') || relPath.endsWith('/.env.example') || relPath.includes('/env.example') || relPath.includes('/.env.example') ); }); const hasEnvFile = files.some(f => { const relPath = f.relPath.replace(/\\/g, '/'); // Check for .env files but exclude .env.example files return ( relPath.includes('.env') && !relPath.includes('.env.example') && !relPath.endsWith('.env.example') && !relPath.includes('/env.example') ); }); const hasMcpConfig = files.some(f => { const relPath = f.relPath.replace(/\\/g, '/'); return ( relPath.includes('mcp.json') || relPath.includes('.mcp.json') || relPath.includes('.mcp') || relPath.endsWith('mcp.json') || relPath.endsWith('.mcp.json') ); }); // Primary indicator: presence of MCP tools (more reliable than config file detection) // Secondary indicator: MCP config file detected in analyzed files const isMcpServer = mcpTools.length > 0 || hasMcpConfig; // For regular projects, missing .env.example is a documentation issue // For MCP servers, env vars are configured in the client's mcp.json, so .env.example is optional if (envKeys.length > 0 && !hasEnvExample && !isMcpServer) { flags.push({ type: 'security', severity: 'medium', message: 'Missing .env.example file - environment variables are not documented', file: 'ENV-001', }); riskScore += 15; } else if (envKeys.length > 0 && !hasEnvExample && isMcpServer && !hasMcpConfig) { // MCP server without detectable mcp.json config - suggest documenting env vars flags.push({ type: 'config', severity: 'low', message: 'MCP server: Consider documenting environment variables in README or mcp.json examples', file: 'MCP-ENV-001', }); riskScore += 5; } // Note: If isMcpServer && (hasMcpConfig || no additional check needed), no env warning is generated if (hasEnvFile) { flags.push({ type: 'security', severity: 'low', message: ".env file found in repository - ensure it's in .gitignore", file: 'ENV-001', }); riskScore += 5; } // Verifier #8: Enhanced risk rules // ENV-002: Server-only env keys referenced in UI/web code const webFiles = files.filter(f => { const posixPath = toPosix(f.relPath); return ( posixPath.includes('/web/') || posixPath.includes('/components/') || (posixPath.includes('/pages/') && !posixPath.includes('/pages/api/')) ); }); const serverOnlyEnvKeys = envKeys.filter( env => !env.key.startsWith('NEXT_PUBLIC_') && !env.key.startsWith('PUBLIC_') && !env.key.startsWith('VITE_') ); const webEnvLeaks = webFiles.filter(webFile => { const webFilePosix = toPosix(webFile.relPath); return serverOnlyEnvKeys.some(env => toPosix(env.file) === webFilePosix); }); if (webEnvLeaks.length > 0) { flags.push({ type: 'security', severity: 'high', message: `Server-only environment variables referenced in client code - potential leak risk`, file: toPosix(webEnvLeaks[0].relPath), }); riskScore += 25; } // API-AUTH-001: API route without auth guard const apiRoutes = files.filter(f => { const posixPath = toPosix(f.relPath); return posixPath.includes('/api/') || posixPath.match(/app\/.*\/route\.(ts|js)$/); }); const authGuardFiles = files.filter(f => /auth|middleware|guard|verify/i.test(f.relPath)); const hasAuthSystem = authGuardFiles.length > 0 || exports.some(exp => /auth|verify|middleware|guard/i.test(exp.name)); if (apiRoutes.length > 0 && !hasAuthSystem) { flags.push({ type: 'security', severity: 'medium', message: `${apiRoutes.length} API routes detected without apparent authentication system`, file: toPosix(apiRoutes[0].relPath), }); riskScore += 20; } // MCP-002: MCP tool handlers without input validation (only fires when MCP tools exist) const actualMcpToolsCount = mcpTools.length; if (actualMcpToolsCount > 0) { const mcpToolFiles = files.filter(f => { const posixPath = toPosix(f.relPath); return ( /tool|handler|mcp/i.test(posixPath) && !posixPath.includes('/__tests__/') && !posixPath.endsWith('.tsx') && // Exclude UI files !posixPath.includes('/components/') ); // Exclude components }); const hasValidationLibs = exports.some(exp => /zod|joi|yup|ajv|schema/i.test(exp.name)); if (mcpToolFiles.length > 0 && !hasValidationLibs) { flags.push({ type: 'security', severity: 'medium', message: `MCP tool handlers detected without input validation library (${actualMcpToolsCount} tools found)`, file: toPosix(mcpToolFiles[0].relPath), }); riskScore += 15; } } else { // Skip MCP-002 if no MCP tools detected } // BUILD-001: Build output directories tracked and included const buildArtifacts = files.filter(f => { const posixPath = toPosix(f.relPath).toLowerCase(); return ( posixPath.includes('/dist/') || posixPath.includes('/build/') || posixPath.includes('/.next/') || posixPath.includes('/out/') ); }); if (buildArtifacts.length > 0) { flags.push({ type: 'maintenance', severity: 'low', message: `${buildArtifacts.length} build artifacts found in analysis - may inflate signals`, file: toPosix(buildArtifacts[0].relPath), }); riskScore += 10; } // Performance risks const oversizedFiles = files.filter(f => f.size > 100000); // 100KB+ if (oversizedFiles.length > 0) { flags.push({ type: 'performance', severity: 'low', message: `${oversizedFiles.length} large files found (>100KB) - may impact context loading`, file: toPosix(oversizedFiles[0].relPath), }); riskScore += 10; } // Maintenance risks const hasTests = files.some(f => /(test|spec)/i.test(f.relPath)); if (!hasTests && exports.length > 20) { flags.push({ type: 'maintenance', severity: 'medium', message: 'No test files detected in project with significant codebase', }); riskScore += 20; } // Configuration risks const hasPackageJson = files.some(f => f.relPath.includes('package.json')); const hasTsConfig = files.some(f => f.relPath.includes('tsconfig.json')); if (files.some(f => f.language === 'typescript') && !hasTsConfig) { flags.push({ type: 'config', severity: 'medium', message: 'TypeScript files found but no tsconfig.json detected', }); riskScore += 15; } const recommendations = generateRiskRecommendations(flags); return { flags, score: Math.min(100, riskScore), recommendations, }; } /** * Generate concrete next actions for agents */ function generateNextActions( hints: ScoredHint[], query: string | undefined, capabilities: CapabilitiesMap, risks: RiskAssessment ): NextActions { const openFiles: string[] = []; const checks: string[] = []; let focus = 'project exploration'; let mode: NextActions['mode'] = 'project_research'; // Determine mode based on query and hints if (query) { const queryLower = query.toLowerCase(); if (/implement|add|create|build/.test(queryLower)) { mode = 'implementation_ready'; focus = 'feature implementation'; } else if (/debug|fix|error|issue/.test(queryLower)) { mode = 'code_lookup'; focus = 'debugging and analysis'; } else if (/how|what|where|understand|explain/.test(queryLower)) { mode = 'project_research'; focus = 'code understanding'; } } // Build file list from top hints for (const hint of hints.slice(0, 3)) { if (hint.line) { const lineRange = Math.max(1, hint.line - 35) + '-' + (hint.line + 35); openFiles.push(`${hint.file}:${lineRange}`); } else { openFiles.push(hint.file); } } // Add checks based on capabilities and query if (capabilities.domains.includes('storage') || capabilities.domains.includes('database')) { checks.push('grep -r "database\\|connect\\|init" src/ | head -10'); } if (capabilities.domains.includes('api') || capabilities.domains.includes('mcp-tools')) { checks.push('find src/ -name "*route*" -o -name "*handler*" -o -name "*tool*" | head -10'); } if (query && /test/i.test(query)) { checks.push('find . -name "*test*" -o -name "*spec*" | head -10'); } // Risk-based checks if (risks.score > 30) { checks.push('ls -la .env* 2>/dev/null || echo "No .env files found"'); } return { mode, openFiles: openFiles.slice(0, 6), checks: checks.slice(0, 4), focus, }; } /** * Generate deterministic answer draft for queries */ export function generateAnswerDraft( summary: EnhancedProjectSummary, query?: string ): string | undefined { if (!query) return undefined; const queryLower = query.toLowerCase(); const { systems, capabilities, hints } = summary; // Database queries if (/database|db|storage|persist/.test(queryLower)) { if (systems.db) { const topDbHints = hints .filter( h => h.role.includes('storage') || h.role.includes('initializer') || h.file.includes('database') || h.file.includes('storage') ) .slice(0, 2); return ( `This project uses ${systems.db.engine} for data storage. ` + `Key database components: ${topDbHints.map(h => `${h.symbol} in ${path.basename(h.file)}`).join(', ')}. ` + `Database initialization handled by: ${systems.db.initializers.join(', ')}.` ); } } // Search queries if (/search|query|find/.test(queryLower) && capabilities.domains.includes('search')) { const searchHints = hints .filter( h => h.role.includes('search') || h.symbol?.toLowerCase().includes('search') || h.symbol?.toLowerCase().includes('query') ) .slice(0, 2); if (searchHints.length > 0) { return ( `Search functionality is implemented through: ${searchHints.map(h => h.symbol).join(', ')}. ` + `Primary search components located in: ${searchHints.map(h => path.basename(h.file)).join(', ')}.` ); } } // API/MCP tools queries if (/api|tool|endpoint/.test(queryLower) && capabilities.domains.includes('mcp-tools')) { const toolCount = summary.surfaces.mcpTools.length; const routeCount = summary.surfaces.routes.length; return ( `This project implements ${toolCount} MCP tools` + (routeCount > 0 ? ` and ${routeCount} HTTP endpoints` : '') + '. ' + `Key tools: ${summary.surfaces.mcpTools .slice(0, 3) .map(t => t.name) .join(', ')}.` ); } return undefined; } // Helper functions function inferExportRole(name: string, kind: string): string { const nameLower = name.toLowerCase(); if (/init|initialize|bootstrap|setup/.test(nameLower)) return 'initializer'; if (/search|query|find/.test(nameLower)) return 'search'; if (/provider|pipeline|factory/.test(nameLower)) return 'provider'; if (/store|save|insert|upsert/.test(nameLower)) return 'storage'; if (/validate|verify|check/.test(nameLower)) return 'validation'; if (/handle|process|execute/.test(nameLower)) return 'handler'; return kind; } function generateRiskRecommendations(flags: RiskFlag[]): string[] { const recommendations: string[] = []; for (const flag of flags) { switch (flag.type) { case 'security': if (flag.message.includes('.env.example')) { recommendations.push( 'Create .env.example file documenting required environment variables' ); } if (flag.message.includes('.env file')) { recommendations.push('Ensure .env file is listed in .gitignore'); } break; case 'maintenance': if (flag.message.includes('test')) { recommendations.push('Add test files to improve code reliability'); } break; case 'config': if (flag.message.includes('tsconfig')) { recommendations.push('Add tsconfig.json for proper TypeScript configuration'); } if (flag.message.includes('MCP server: Consider documenting')) { recommendations.push('Document environment variables in mcp.json configuration file'); } break; case 'performance': if (flag.message.includes('large files')) { recommendations.push('Consider breaking down large files for better maintainability'); } break; } } return recommendations.slice(0, 5); } function formatFileSize(bytes: number): string { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; } /** * Verifier #5: Symbol & Import Noise Filters */ function filterNoisySymbols(exports: ExportItem[]): ExportItem[] { // Function stopwords - too generic unless in meaningful context const functionStopwords = new Set([ 'render', 'handler', 'default', 'index', 'config', 'props', 'children', 'value', 'data', 'item', 'element', 'component', 'wrapper', 'container', ]); return exports.filter(exp => { const name = exp.name.toLowerCase(); // Filter out generic function names unless in server contexts if (functionStopwords.has(name)) { // Allow in server/API contexts const isServerContext = /\/(api|server|mcpServer|worker|lib|core|utils|services)\//.test( exp.file.replace(/\\/g, '/') ); return isServerContext; } // Filter out obvious UI component patterns if (/^(button|card|modal|dialog|input|form|label|text|icon|image)$/i.test(name)) { return false; } // Filter out React/UI patterns if (/^(use[A-Z]|with[A-Z]|create[A-Z].*Component)/.test(exp.name)) { return false; } // Keep meaningful exports return true; }); } /** * Collapse UI imports into categories and focus on infrastructure */ function collapseImports(imports: string[]): string[] { const collapsed: string[] = []; const uiImports = new Set<string>(); for (const imp of imports) { // Collapse UI framework imports if (/^(@\/components\/ui|shadcn\/ui|lucide-react|@radix-ui)/.test(imp)) { uiImports.add('ui-kit'); continue; } // Collapse React ecosystem if (/^(react|next\/|@next\/)/.test(imp)) { uiImports.add('react-ecosystem'); continue; } // Keep infrastructure and local program modules if (imp.startsWith('./') || imp.startsWith('../')) { collapsed.push(imp); // Local modules } else if (/^(pg|postgres|supabase|redis|ioredis|mongodb|mysql)/.test(imp)) { collapsed.push(imp); // Database } else if (/^(express|fastify|koa|hapi)/.test(imp)) { collapsed.push(imp); // Web frameworks } else if (/^(@anthropic|openai|claude)/.test(imp)) { collapsed.push(imp); // AI services } else if (/^(bullmq|agenda|node-cron)/.test(imp)) { collapsed.push(imp); // Job queues } else if (/^(zod|joi|yup|ajv)/.test(imp)) { collapsed.push(imp); // Validation } else if (/^(dotenv|config)/.test(imp)) { collapsed.push(imp); // Configuration } } // Add collapsed UI categories if (uiImports.size > 0) { collapsed.push(...Array.from(uiImports)); } return collapsed.slice(0, 10); // Limit for focus } /** * Fix for critique item #2: Filter out HTTP method exports from Public API Surfaces * Route handler exports (GET, POST, etc.) should only appear in routes section */ function filterRouteExportsFromPublicAPI(exports: ExportItem[], routes: RouteItem[]): ExportItem[] { const httpMethods = new Set(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']); const routeFiles = new Set(routes.map(route => route.file.replace(/\\/g, '/'))); return exports.filter(exp => { // If this is an HTTP method export if (httpMethods.has(exp.name.toUpperCase())) { const normalizedFile = exp.file.replace(/\\/g, '/'); // Check if it's from a route file if ( routeFiles.has(normalizedFile) || normalizedFile.match(/app\/.*\/route\.(ts|js)$/) || normalizedFile.includes('/api/') ) { return false; // Exclude from public API, it's a route handler } } return true; // Include in public API }); }

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/sbarron/AmbianceMCP'

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