local_file_summary
Analyze code files to generate AST-based summaries and identify key symbols for quick understanding of file structure and contents.
Instructions
π Get quick AST-based summary and key symbols for any file. Fast file analysis without external dependencies. Accepts absolute paths or relative paths (when workspace can be detected).
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| filePath | Yes | File path for analysis. Can be absolute (recommended) or relative to workspace. Examples: "C:\Dev\my-project\src\index.ts", "/Users/username/project/src/index.ts", or "src/index.ts". | |
| includeSymbols | No | Include detailed symbol information | |
| maxSymbols | No | Maximum number of symbols to return | |
| format | No | Output format preference | structured |
Input Schema (JSON Schema)
{
"properties": {
"filePath": {
"description": "File path for analysis. Can be absolute (recommended) or relative to workspace. Examples: \"C:\\Dev\\my-project\\src\\index.ts\", \"/Users/username/project/src/index.ts\", or \"src/index.ts\".",
"type": "string"
},
"format": {
"default": "structured",
"description": "Output format preference",
"enum": [
"xml",
"structured",
"compact"
],
"type": "string"
},
"includeSymbols": {
"default": true,
"description": "Include detailed symbol information",
"type": "boolean"
},
"maxSymbols": {
"default": 20,
"description": "Maximum number of symbols to return",
"maximum": 50,
"minimum": 5,
"type": "number"
}
},
"required": [
"filePath"
],
"type": "object"
}
Implementation Reference
- Core handler function that implements the 'local_file_summary' tool. Validates input path, performs AST parsing using SemanticCompactor and ASTParser, extracts functions/classes/interfaces/symbols, calculates complexity, handles non-code files, and returns formatted summary.export async function handleFileSummary(args: any): Promise<any> { const { filePath, includeSymbols = true, maxSymbols = 20, format = 'structured' } = args; logger.info('π handleFileSummary called with', { filePath, argsKeys: Object.keys(args), args: args, }); // Validate that filePath is provided and is absolute if (!filePath) { throw new Error('β filePath is required. Please provide an absolute path to the file.'); } const resolvedFilePath = validateAndResolvePath(filePath); const projectPath = path.dirname(resolvedFilePath); logger.info('π Analyzing file', { originalPath: filePath, resolvedPath: resolvedFilePath, projectPath, includeSymbols, maxSymbols, }); try { // Check if file exists before proceeding const fs = await import('fs/promises'); try { await fs.access(resolvedFilePath); } catch (error) { throw new Error('File not found'); } const languageInfo = getLanguageFromPath(filePath); const language = languageInfo.lang; // Check if this is a non-code file that doesn't need AST analysis if ( language === 'json' || language === 'markdown' || language === 'yaml' || language === 'toml' || language === 'text' ) { return await handleNonCodeFile( resolvedFilePath, language, format, includeSymbols, maxSymbols ); } // Use semantic compactor for single file analysis (optional) let nodes = []; try { const compactor = new SemanticCompactor(projectPath); nodes = await compactor.getSummary(resolvedFilePath); compactor.dispose(); } catch (error) { logger.warn('Skipping semantic compactor summary for single file', { projectPath, error: error instanceof Error ? error.message : String(error), }); nodes = []; } // π Get comprehensive analysis directly from AST (bypass semantic compactor filtering) logger.info('π About to call getComprehensiveASTAnalysis', { resolvedFilePath }); let astAnalysis; try { astAnalysis = await getComprehensiveASTAnalysis(resolvedFilePath); } catch (error) { logger.error('β getComprehensiveASTAnalysis failed', { error: error instanceof Error ? error.message : String(error), filePath: resolvedFilePath, }); throw error; } if (!astAnalysis) { logger.error('β getComprehensiveASTAnalysis returned undefined', { resolvedFilePath }); throw new Error('AST analysis returned undefined'); } logger.info('β Got AST analysis', { totalSymbols: astAnalysis?.totalSymbols, hasAllFunctions: !!astAnalysis?.allFunctions, hasAllClasses: !!astAnalysis?.allClasses, hasAllInterfaces: !!astAnalysis?.allInterfaces, }); // Get semantic compactor results for comparison (but don't rely on them for symbol count) // Debug: Log the AST analysis results logger.debug('π AST Analysis Results', { filePath: resolvedFilePath, totalSymbols: astAnalysis.totalSymbols, functions: astAnalysis.allFunctions.length, classes: astAnalysis.allClasses.length, interfaces: astAnalysis.allInterfaces.length, exportedSymbols: astAnalysis.exportedSymbols.length, topSymbols: astAnalysis.topSymbols.length, sampleFunctions: astAnalysis.allFunctions .slice(0, 3) .map(f => ({ name: f.name, type: f.type, line: f.line })), sampleClasses: astAnalysis.allClasses .slice(0, 3) .map(c => ({ name: c.name, type: c.type, line: c.line })), }); // Force use the actual AST analysis results instead of semantic compactor logger.info('π§ Using AST analysis results directly', { symbolCount: astAnalysis.totalSymbols, functions: astAnalysis.allFunctions.length, classes: astAnalysis.allClasses.length, }); // Extract file header information const fileHeader = await extractFileHeader(resolvedFilePath); // Calculate cyclomatic complexity let complexityData = { rating: 'low', description: 'Simple code', totalComplexity: 1, decisionPoints: 0, breakdown: {}, }; try { const fs = await import('fs'); const fileContent = await fs.promises.readFile(resolvedFilePath, 'utf8'); complexityData = calculateCyclomaticComplexity(fileContent); } catch (error) { // Fallback to simple symbol-based complexity if file reading fails complexityData.rating = astAnalysis.totalSymbols > 50 ? 'high' : astAnalysis.totalSymbols > 20 ? 'medium' : 'low'; complexityData.description = `Based on ${astAnalysis.totalSymbols} symbols`; } // Clean up resources // compactor.dispose(); // Moved inside try-catch const summary = { file: filePath, exists: astAnalysis.totalSymbols > 0, symbolCount: astAnalysis.totalSymbols, // π Use actual AST symbol count fileHeader: fileHeader, allFunctions: astAnalysis.allFunctions, exportedSymbols: astAnalysis.exportedSymbols, allClasses: astAnalysis.allClasses, // π Add class information allInterfaces: astAnalysis.allInterfaces, // π Add interface information symbols: includeSymbols ? astAnalysis.topSymbols.slice(0, maxSymbols) // π Use better symbol selection : [], complexity: complexityData.rating, complexityData: complexityData, // π Add detailed complexity information language, }; const quickAnalysis = generateQuickFileAnalysis(summary); // Format the output based on preference let formattedSummary: string; try { formattedSummary = formatFileSummaryOutput(summary, quickAnalysis, format); if (typeof formattedSummary !== 'string') { logger.warn('Formatter returned non-string value', { type: typeof formattedSummary, value: formattedSummary, }); formattedSummary = `Error: Formatter returned ${typeof formattedSummary} instead of string`; } } catch (error) { logger.error('Failed to format file summary', { error: error instanceof Error ? error.message : String(error), }); formattedSummary = `Error formatting summary: ${error instanceof Error ? error.message : String(error)}`; } logger.info('β File analysis completed', { symbolCount: summary.symbolCount, complexity: summary.complexity, }); return { success: true, summary: formattedSummary, quickAnalysis, metadata: { format, symbolCount: summary.symbolCount, complexity: summary.complexity, language: summary.language, }, usage: `Found ${summary.symbolCount} symbols with ${summary.complexity} complexity`, }; } catch (error) { logger.error('β File analysis failed', { filePath, error: error instanceof Error ? error.message : String(error), }); return { success: false, error: error instanceof Error ? error.message : String(error), fallback: `Could not analyze ${filePath}. File may not exist, be too large, or contain unsupported language.`, suggestion: 'Try local_project_hints to understand the overall project structure instead.', }; } }
- Tool schema/definition for 'local_file_summary' including name, description, and inputSchema defining parameters: filePath (required), includeSymbols, maxSymbols, format.export const localFileSummaryTool = { name: 'local_file_summary', description: 'π Get quick AST-based summary and key symbols for any file. Fast file analysis without external dependencies. Accepts absolute paths or relative paths (when workspace can be detected).', inputSchema: { type: 'object', properties: { filePath: { type: 'string', description: 'File path for analysis. Can be absolute (recommended) or relative to workspace. Examples: "C:\\Dev\\my-project\\src\\index.ts", "/Users/username/project/src/index.ts", or "src/index.ts".', }, includeSymbols: { type: 'boolean', default: true, description: 'Include detailed symbol information', }, maxSymbols: { type: 'number', default: 20, minimum: 5, maximum: 50, description: 'Maximum number of symbols to return', }, format: { type: 'string', enum: ['xml', 'structured', 'compact'], default: 'structured', description: 'Output format preference', }, }, required: ['filePath'], }, };
- src/tools/localTools/index.ts:129-137 (registration)Registration of the handler in the localHandlers object mapping 'local_file_summary' to handleFileSummary function.export const localHandlers = { ...(allowLocalContext ? { local_context: handleSemanticCompact } : {}), local_project_hints: handleProjectHints, local_file_summary: handleFileSummary, frontend_insights: handleFrontendInsights, local_debug_context: handleLocalDebugContext, manage_embeddings: handleManageEmbeddings, ast_grep_search: handleAstGrep, };
- src/index.ts:135-142 (registration)Final MCP server registration in AmbianceMCPServer constructor, adding to this.handlers map used by setupToolHandlers for MCP protocol.this.handlers = { ...(allowLocalContext ? { local_context: handleSemanticCompact } : {}), local_project_hints: handleProjectHints, local_file_summary: handleFileSummary, frontend_insights: handleFrontendInsights, local_debug_context: handleLocalDebugContext, ast_grep_search: handleAstGrep, };
- Key helper function for AST analysis, extracts functions, classes, interfaces, symbols; used by handler; includes ast-grep fallback for Python, Go, etc.export async function getComprehensiveASTAnalysis(filePath: string): Promise<{ totalSymbols: number; allFunctions: any[]; allClasses: any[]; allInterfaces: any[]; exportedSymbols: string[]; topSymbols: any[]; }> { try { logger.info('π Starting AST analysis', { filePath }); const languageInfo = getLanguageFromPath(filePath); const language = languageInfo.lang as any; const grepLang = languageInfo.grep; logger.info('π Detected language', { language, grepLang }); const parser = new ASTParser(); logger.info('βοΈ Created AST parser, about to parse file'); let parsedFile = await parser.parseFile(filePath, language); // Handle undefined/null from parser for unsupported langs (e.g., Python/Go no grammar) if (!parsedFile) { logger.warn('ASTParser returned undefined, forcing fallback', { filePath, language }); parsedFile = { symbols: [], imports: [], exports: [], errors: [], absPath: filePath, language, }; } logger.info('β Parsed file successfully', { hasSymbols: !!parsedFile?.symbols, symbolCount: parsedFile?.symbols?.length || 0, }); let allFunctions: any[] = []; let allClasses: any[] = []; const allInterfaces: any[] = []; let exportedSymbols: string[] = []; // Existing JS/TS/Python processing for (const symbol of parsedFile.symbols) { if (symbol.type === 'function') { allFunctions.push({ name: symbol.name, signature: resolveTypeFromAST(symbol.signature), line: symbol.startLine, isAsync: symbol.isAsync || symbol.signature.includes('async'), isExported: symbol.isExported, isMethod: false, className: undefined, parameters: extractParametersFromSignature(symbol.signature), returnType: resolveTypeFromAST(symbol.returnType || ''), returnedSymbols: extractReturnedSymbols(symbol.body), purpose: 'Function', }); } else if (symbol.type === 'method') { // Handle individual method symbols with full signatures allFunctions.push({ name: symbol.name, signature: resolveTypeFromAST(symbol.signature), line: symbol.startLine, isAsync: symbol.isAsync || false, isExported: symbol.isExported, isMethod: true, className: symbol.className, parameters: symbol.parameters || extractParametersFromSignature(symbol.signature), returnType: resolveTypeFromAST(symbol.returnType || ''), returnedSymbols: extractReturnedSymbols(symbol.body), purpose: 'Method', }); } else if (symbol.type === 'class') { allClasses.push({ name: symbol.name, line: symbol.startLine, isExported: symbol.isExported, signature: resolveTypeFromAST(symbol.signature), methods: symbol.body && symbol.body.startsWith('Methods:') ? symbol.body .replace('Methods:', '') .trim() .split(',') .map(m => m.trim()) .filter(m => m && m !== 'constructor') : [], }); } else if (symbol.type === 'interface') { allInterfaces.push({ name: symbol.name, line: symbol.startLine, signature: resolveTypeFromAST(symbol.signature), purpose: 'Type definition', }); } // Collect exported symbols if (symbol.isExported && !exportedSymbols.includes(symbol.name)) { exportedSymbols.push(symbol.name); } } // Close the for loop // Add exports from export statements if (parsedFile.exports) { parsedFile.exports.forEach(exportStmt => { if (exportStmt.name && !exportedSymbols.includes(exportStmt.name)) { exportedSymbols.push(exportStmt.name); } }); } // Fallback to ast-grep if few/no symbols or non-JS/TS (force for samples/unsupported) let initialSymbolCount = parsedFile.symbols.length || 0; const isNonJsTs = grepLang && !['ts', 'js', 'jsx', 'tsx'].includes(grepLang); if ((initialSymbolCount < 2 || isNonJsTs) && grepLang) { logger.info('π Falling back to ast-grep for symbol extraction', { filePath, grepLang, initialSymbolCount, }); const astGrepSymbols = extractSymbolsWithAstGrep(filePath, grepLang); allFunctions = [...allFunctions, ...astGrepSymbols.functions]; allClasses = [...allClasses, ...astGrepSymbols.classes]; exportedSymbols = [...exportedSymbols, ...(astGrepSymbols.exports || [])]; logger.info('β Ast-grep extraction complete', { functions: astGrepSymbols.functions.length, classes: astGrepSymbols.classes.length, }); initialSymbolCount += astGrepSymbols.functions.length + astGrepSymbols.classes.length; } // Create top symbols list (prioritize important symbols) const topSymbols: any[] = []; // Add classes first (most important) allClasses.forEach(cls => { topSymbols.push({ name: cls.name, type: 'class', line: cls.line, purpose: 'Core class', signature: resolveTypeFromAST(cls.signature).substring(0, 100) + (cls.signature.length > 100 ? '...' : ''), }); }); // Add interfaces allInterfaces.forEach(iface => { topSymbols.push({ name: iface.name, type: 'interface', line: iface.line, purpose: iface.purpose, signature: resolveTypeFromAST(iface.signature).substring(0, 100) + (iface.signature.length > 100 ? '...' : ''), }); }); // Add some key functions (exported first) const keyFunctions = allFunctions .filter(f => !f.isMethod) // Only standalone functions .sort((a, b) => (b.isExported ? 1 : 0) - (a.isExported ? 1 : 0)) .slice(0, 5); keyFunctions.forEach(func => { topSymbols.push({ name: func.name, type: 'function', line: func.line, purpose: func.purpose, signature: resolveTypeFromAST(func.signature).substring(0, 100) + (func.signature.length > 100 ? '...' : ''), }); }); parser.dispose(); return { totalSymbols: initialSymbolCount, // Use updated count after fallback allFunctions, allClasses, allInterfaces, exportedSymbols, topSymbols, }; } catch (error) { logger.warn('Failed to get comprehensive AST analysis', { filePath, error: error instanceof Error ? error.message : String(error), }); return { totalSymbols: 0, allFunctions: [], allClasses: [], allInterfaces: [], exportedSymbols: [], topSymbols: [], }; } }