import { readdir, stat } from "fs/promises"; import { join, relative, extname } from "path"; export interface WorkspaceSnapshot { workspace_root: string; current_directory: string; top_level_entries: string[]; key_files: string[]; total_files: number; file_type_counts: Record; } const KEY_FILE_NAMES = new Set([ "package.json", "tsconfig.json", "Cargo.toml", "go.mod", "pyproject.toml", "requirements.txt", "Makefile", "Dockerfile", "docker-compose.yml", "README.md", "CLAUDE.md", "LLMTUNE.md", ".env.example", ".gitignore", "prisma", "schema.prisma", ]); const IGNORED_DIRS = new Set([ "node_modules", ".git", "__pycache__", ".next", "dist", "build", ".venv", "venv", ".tox", "target", ".cache", ".turbo", "coverage", ]); export async function buildWorkspaceSnapshot( workspaceRoot: string, cwd?: string, ): Promise { const root = workspaceRoot; const current = cwd ?? workspaceRoot; const topEntries = await safeReaddir(root); const topLevelEntries = topEntries.map((e) => e.name); const keyFiles: string[] = []; const fileTypeCounts: Record = {}; let totalFiles = 0; await walkDir(root, "", async (name, relPath, isDir) => { if (isDir) return; totalFiles++; const ext = extname(name).toLowerCase(); fileTypeCounts[ext] = (fileTypeCounts[ext] ?? 0) + 1; if (KEY_FILE_NAMES.has(name)) { keyFiles.push(relative(root, join(root, relPath, name))); } }); return { workspace_root: root, current_directory: current, top_level_entries: topLevelEntries.slice(0, 30), key_files: keyFiles.slice(0, 20), total_files: totalFiles, file_type_counts: fileTypeCounts, }; } export function renderWorkspaceSection(snapshot: WorkspaceSnapshot): string { const today = new Date().toISOString().split("T")[0]; const lines = [ "## Runtime Context", `- Today's date: ${today}`, `- Workspace root: ${snapshot.workspace_root}`, `- Current directory: ${snapshot.current_directory}`, `- Total files: ${snapshot.total_files}`, ]; const topExts = Object.entries(snapshot.file_type_counts) .sort((a, b) => b[1] - a[1]) .slice(0, 5) .map(([ext, count]) => `${ext || "none"}: ${count}`); if (topExts.length > 0) { lines.push(`- Top file types: ${topExts.join(", ")}`); } if (snapshot.key_files.length > 0) { lines.push(`- Key files: ${snapshot.key_files.join(", ")}`); } if (snapshot.top_level_entries.length > 0) { lines.push( `- Top-level entries: ${snapshot.top_level_entries.slice(0, 15).join(", ")}`, ); } return lines.join("\n"); } async function walkDir( root: string, relDir: string, visitor: (name: string, relPath: string, isDir: boolean) => Promise, depth = 0, ): Promise { if (depth > 3) return; const entries = await safeReaddir(join(root, relDir)); for (const entry of entries) { if (IGNORED_DIRS.has(entry.name)) continue; const relPath = relDir ? `${relDir}/${entry.name}` : entry.name; const isDir = entry.isDirectory(); await visitor(entry.name, relDir, isDir); if (isDir && depth < 3) { await walkDir(root, relPath, visitor, depth + 1); } } } async function safeReaddir(dirPath: string) { try { const entries = await readdir(dirPath, { withFileTypes: true }); return entries; } catch { return []; } }