92 lines
3.1 KiB
TypeScript
92 lines
3.1 KiB
TypeScript
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<MemoryFile | null> {
|
|
try {
|
|
return { path, content: await readFile(path, "utf8") };
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function inspectClaudeDir(path: string): Promise<ClaudeDir | null> {
|
|
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, <dossier>/CLAUDE.md et <dossier>/.claude/CLAUDE.md ;
|
|
* - dossiers .claude : global (~/.claude), puis <dossier>/.claude pour chaque dossier de la chaîne.
|
|
*/
|
|
export async function loadClaudeConfig(cwd: string, homeDir: string): Promise<ClaudeConfig> {
|
|
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 };
|
|
}
|