/**
* CLAUDE.md Loader — auto-load project instructions from cwd + parents
*
* Extracted from agent-loop.ts for single-responsibility.
* All consumers should import from agent-loop.ts (re-export facade).
*/
import { readFileSync, existsSync } from "fs";
import { join, resolve, dirname } from "path";
/** CLI-only: cached CLAUDE.md content (cleared by reloadClaudeMd/resetClaudeMdCache) */
let cachedClaudeMd: string | null = null;
/** CLI-only: cached CLAUDE.md path */
let cachedClaudeMdPath: string | null = null;
function findClaudeMd(startDir?: string): { content: string; path: string } | null {
const cwd = startDir || process.cwd();
const checked = new Set<string>();
// Walk up from cwd looking for CLAUDE.md
let dir = resolve(cwd);
while (dir && !checked.has(dir)) {
checked.add(dir);
const candidates = [
join(dir, "CLAUDE.md"),
join(dir, ".claude", "CLAUDE.md"),
];
for (const candidate of candidates) {
if (existsSync(candidate)) {
try {
const content = readFileSync(candidate, "utf-8");
if (content.trim()) return { content: content.trim(), path: candidate };
} catch { /* skip unreadable */ }
}
}
const parent = dirname(dir);
if (parent === dir) break;
dir = parent;
}
return null;
}
export function loadClaudeMd(): { content: string; path: string } | null {
if (cachedClaudeMd !== null) return cachedClaudeMd ? { content: cachedClaudeMd, path: cachedClaudeMdPath! } : null;
const result = findClaudeMd();
cachedClaudeMd = result?.content || "";
cachedClaudeMdPath = result?.path || null;
return result;
}
export function reloadClaudeMd(): { content: string; path: string } | null {
cachedClaudeMd = null;
cachedClaudeMdPath = null;
return loadClaudeMd();
}
/** Reset CLAUDE.md cache (called by resetSessionState) */
export function resetClaudeMdCache(): void {
cachedClaudeMd = null;
cachedClaudeMdPath = null;
}