import { readFile, readdir, stat } from "node:fs/promises"; import { dirname, join, resolve } from "node:path"; export interface MemoryFile { path: string; content: string; } export interface ClaudeDir { path: string; /** Éléments reconnus et exploités, ex. "settings.json", "commands/", "skills/". */ entries: string[]; /** Autres éléments présents mais NON exploités (ex. "agents/", "rules/", ou un lien symbolique). */ unsupported: string[]; } export interface ClaudeConfig { /** CLAUDE.md chargés, du plus général au plus spécifique. */ memoryFiles: MemoryFile[]; /** Dossiers .claude trouvés (global, puis de la racine jusqu'au dossier courant). */ claudeDirs: ClaudeDir[]; } const KNOWN_FILES = ["settings.json", "settings.local.json", "CLAUDE.md"]; const KNOWN_DIRS = ["commands", "skills"]; const IGNORED_NAMES = new Set([".DS_Store", "Thumbs.db"]); async function tryRead(path: string): Promise { try { return { path, content: await readFile(path, "utf8") }; } catch { return null; } } async function inspectClaudeDir(path: string): Promise { try { if (!(await stat(path)).isDirectory()) return null; const dirents = await readdir(path, { withFileTypes: true }); const files = dirents.filter((d) => d.isFile() && KNOWN_FILES.includes(d.name)).map((d) => d.name); const dirs = dirents.filter((d) => d.isDirectory() && KNOWN_DIRS.includes(d.name)).map((d) => d.name); const known = new Set([...files, ...dirs]); const unsupported = dirents .filter((d) => !known.has(d.name) && !IGNORED_NAMES.has(d.name)) .map((d) => (d.isDirectory() ? `${d.name}/` : d.name)); return { path, entries: [...files, ...dirs.map((n) => `${n}/`)], unsupported }; } catch { return null; } } /** Dossiers de la racine jusqu'à cwd (le plus spécifique en dernier). */ function directoryChain(cwd: string): string[] { const chain: string[] = []; let dir = resolve(cwd); for (;;) { chain.unshift(dir); const parent = dirname(dir); if (parent === dir) break; dir = parent; } return chain; } /** * Découvre la configuration ClaudeCode : * - CLAUDE.md : global (~/.claude/CLAUDE.md), puis, pour chaque dossier de la racine * à cwd, /CLAUDE.md et /.claude/CLAUDE.md ; * - dossiers .claude : global (~/.claude), puis /.claude pour chaque dossier de la chaîne. */ export async function loadClaudeConfig(cwd: string, homeDir: string): Promise { const chain = directoryChain(cwd); const memoryCandidates = [ join(homeDir, ".claude", "CLAUDE.md"), ...chain.flatMap((d) => [join(d, "CLAUDE.md"), join(d, ".claude", "CLAUDE.md")]), ]; const memoryFiles: MemoryFile[] = []; for (const path of new Set(memoryCandidates)) { const file = await tryRead(path); if (file) memoryFiles.push(file); } const dirCandidates = [join(homeDir, ".claude"), ...chain.map((d) => join(d, ".claude"))]; const claudeDirs: ClaudeDir[] = []; for (const path of new Set(dirCandidates)) { const dir = await inspectClaudeDir(path); if (dir) claudeDirs.push(dir); } return { memoryFiles, claudeDirs }; }