import { Symbol } from '../parsers/symbolExtractor.js';
import path from 'path';
export interface FileSymbols {
relativePath: string;
symbols: Symbol[];
}
/**
* Generates LLM-optimized markdown from extracted symbols
*/
export class MarkdownGenerator {
/**
* Generate complete markdown document
*/
generate(fileSymbols: FileSymbols[], basePath: string): string {
const sections: string[] = [];
// Header
sections.push('# Codebase Structure\n');
sections.push(`**Base Path:** \`${basePath}\`\n`);
sections.push(`**Total Files:** ${fileSymbols.length}\n`);
// Calculate total symbols
const totalSymbols = fileSymbols.reduce((sum, fs) => sum + fs.symbols.length, 0);
sections.push(`**Total Symbols:** ${totalSymbols}\n`);
sections.push('---\n');
// Group files by directory
const byDirectory = this.groupByDirectory(fileSymbols);
// Generate sections for each directory
for (const [dir, files] of Object.entries(byDirectory)) {
sections.push(this.generateDirectorySection(dir, files));
}
return sections.join('\n');
}
private groupByDirectory(fileSymbols: FileSymbols[]): Record<string, FileSymbols[]> {
const groups: Record<string, FileSymbols[]> = {};
for (const fs of fileSymbols) {
const dir = path.dirname(fs.relativePath) || '.';
if (!groups[dir]) {
groups[dir] = [];
}
groups[dir].push(fs);
}
return groups;
}
private generateDirectorySection(directory: string, files: FileSymbols[]): string {
const sections: string[] = [];
sections.push(`## 📁 ${directory === '.' ? 'Root Directory' : directory}\n`);
for (const file of files) {
sections.push(this.generateFileSection(file));
}
return sections.join('\n');
}
private generateFileSection(file: FileSymbols): string {
const sections: string[] = [];
const fileName = path.basename(file.relativePath);
const ext = path.extname(fileName);
sections.push(`### 📄 \`${fileName}\``);
sections.push(`**Path:** \`${file.relativePath}\``);
sections.push(`**Symbols:** ${file.symbols.length}\n`);
if (file.symbols.length === 0) {
sections.push('*No symbols found*\n');
return sections.join('\n');
}
// Group symbols by type
const byType = this.groupSymbolsByType(file.symbols);
// Classes
if (byType.class && byType.class.length > 0) {
sections.push('**Classes:**');
for (const symbol of byType.class) {
sections.push(`- \`${symbol.name}\` (Line ${symbol.line})`);
if (symbol.signature) {
sections.push(` \`\`\`${this.getLanguageForExtension(ext)}\n ${symbol.signature}\n \`\`\``);
}
// List methods of this class
const methods = file.symbols.filter(s => s.parentClass === symbol.name);
if (methods.length > 0) {
sections.push(' **Methods:**');
for (const method of methods) {
sections.push(` - \`${method.name}\` (Line ${method.line})`);
}
}
}
sections.push('');
}
// Interfaces
if (byType.interface && byType.interface.length > 0) {
sections.push('**Interfaces:**');
for (const symbol of byType.interface) {
sections.push(`- \`${symbol.name}\` (Line ${symbol.line})`);
if (symbol.signature) {
sections.push(` \`\`\`${this.getLanguageForExtension(ext)}\n ${symbol.signature}\n \`\`\``);
}
}
sections.push('');
}
// Types
if (byType.type && byType.type.length > 0) {
sections.push('**Types:**');
for (const symbol of byType.type) {
sections.push(`- \`${symbol.name}\` (Line ${symbol.line})`);
}
sections.push('');
}
// Enums
if (byType.enum && byType.enum.length > 0) {
sections.push('**Enums:**');
for (const symbol of byType.enum) {
sections.push(`- \`${symbol.name}\` (Line ${symbol.line})`);
}
sections.push('');
}
// Functions (exclude methods)
const functions = file.symbols.filter(s => s.type === 'function' && !s.parentClass);
if (functions.length > 0) {
sections.push('**Functions:**');
for (const symbol of functions) {
sections.push(`- \`${symbol.name}\` (Line ${symbol.line})`);
if (symbol.signature) {
sections.push(` \`\`\`${this.getLanguageForExtension(ext)}\n ${symbol.signature}\n \`\`\``);
}
}
sections.push('');
}
// Constants
if (byType.const && byType.const.length > 0) {
sections.push('**Constants:**');
for (const symbol of byType.const) {
sections.push(`- \`${symbol.name}\` (Line ${symbol.line})`);
}
sections.push('');
}
return sections.join('\n');
}
private groupSymbolsByType(symbols: Symbol[]): Record<string, Symbol[]> {
const groups: Record<string, Symbol[]> = {};
for (const symbol of symbols) {
if (symbol.type === 'method') continue; // Methods are shown under their parent class
if (!groups[symbol.type]) {
groups[symbol.type] = [];
}
groups[symbol.type].push(symbol);
}
return groups;
}
private getLanguageForExtension(ext: string): string {
const map: Record<string, string> = {
'.js': 'javascript',
'.jsx': 'javascript',
'.ts': 'typescript',
'.tsx': 'typescript',
'.py': 'python',
'.java': 'java',
'.go': 'go',
'.rs': 'rust',
'.c': 'c',
'.cpp': 'cpp',
'.php': 'php',
'.rb': 'ruby',
'.cs': 'csharp'
};
return map[ext] || 'text';
}
}