MagicCode/packages/ext-claudecode/src/memory.ts

93 lines
3.1 KiB
TypeScript
Raw Normal View History

2026-10-11 00:11:20 +00:00
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 };
}