(args: z.infer<typeof ExplainCodebaseArgs>, ctx: ToolContext)
| 91 | argsSchema = ExplainCodebaseArgs; |
| 92 | |
| 93 | async execute(args: z.infer<typeof ExplainCodebaseArgs>, ctx: ToolContext): Promise<ToolResult> { |
| 94 | const root = args.path ? path.join(ctx.cwd, args.path) : ctx.cwd; |
| 95 | const maxFiles = args.max_files ?? 5000; |
| 96 | const files = await walkSource(root, maxFiles); |
| 97 | |
| 98 | const layers = new Map<string, { count: number; lines: number; samples: { rel: string; lines: number }[] }>(); |
| 99 | for (const f of files) { |
| 100 | const cat = categorizeFile(f.rel); |
| 101 | let entry = layers.get(cat); |
| 102 | if (!entry) { |
| 103 | entry = { count: 0, lines: 0, samples: [] }; |
| 104 | layers.set(cat, entry); |
| 105 | } |
| 106 | entry.count++; |
| 107 | entry.lines += f.lines; |
| 108 | // Keep top 3 by line count as representatives |
| 109 | entry.samples.push({ rel: f.rel, lines: f.lines }); |
| 110 | entry.samples.sort((a, b) => b.lines - a.lines); |
| 111 | if (entry.samples.length > 3) entry.samples = entry.samples.slice(0, 3); |
| 112 | } |
| 113 | |
| 114 | const out: string[] = []; |
| 115 | out.push(`# Codebase Architecture`); |
| 116 | out.push(`Scope: ${args.path ?? '.'}`); |
| 117 | out.push(`Files: ${files.length}, Total LOC: ${files.reduce((a, b) => a + b.lines, 0).toLocaleString()}`); |
| 118 | out.push(''); |
| 119 | |
| 120 | const orderedLayers = [ |
| 121 | 'entry', 'routes/controllers', 'services', 'middleware', 'data/models', |
| 122 | 'ui', 'hooks', 'utils', 'types', 'config', 'migrations', 'tests', 'other', |
| 123 | ]; |
| 124 | for (const layer of orderedLayers) { |
| 125 | const l = layers.get(layer); |
| 126 | if (!l || l.count === 0) continue; |
| 127 | out.push(`## ${layer} (${l.count} files, ${l.lines.toLocaleString()} lines)`); |
| 128 | for (const s of l.samples) out.push(` - ${s.rel} (${s.lines} lines)`); |
| 129 | out.push(''); |
| 130 | } |
| 131 | |
| 132 | // Layering hint |
| 133 | out.push(`## Hint`); |
| 134 | const entry = layers.get('entry')?.count ?? 0; |
| 135 | const routes = layers.get('routes/controllers')?.count ?? 0; |
| 136 | const services = layers.get('services')?.count ?? 0; |
| 137 | const data = layers.get('data/models')?.count ?? 0; |
| 138 | const ui = layers.get('ui')?.count ?? 0; |
| 139 | if (entry > 0 && routes > 0 && services > 0 && data > 0) { |
| 140 | out.push(` Looks like a typical layered backend (entry → routes → services → models). Trace a feature top-down via these layers.`); |
| 141 | } else if (ui > 0 && (layers.get('hooks')?.count ?? 0) > 0) { |
| 142 | out.push(` Looks like a frontend app (UI components + hooks). State likely lives in hooks; rendering in components.`); |
| 143 | } else { |
| 144 | out.push(` Mixed or atypical layout — use code_graph_find_symbol to navigate.`); |
| 145 | } |
| 146 | |
| 147 | return { content: out.join('\n'), metadata: { layers: Object.fromEntries(layers) } }; |
| 148 | } |
| 149 | } |
| 150 |
nothing calls this directly
no test coverage detected