import { chmod, lstat, readFile, rename, rm, writeFile } from "node:fs/promises"; import { randomUUID } from "node:crypto"; import { join } from "node:path"; import { withFileMutationQueue } from "@earendil-works/pi-coding-agent"; import { mergeManagedRules, renderManagedRules } from "./agents-md.js"; import { detectRuleSets, type RuleSetId } from "./detect-project.js"; import { loadRuleDocuments } from "./rules.js"; const AGENTS_MD_FILENAME = "AGENTS.md"; export interface InitResult { agentsMdPath: string; ruleSets: RuleSetId[]; ruleCount: number; } async function readExistingAgentsMd(path: string): Promise<{ content: string | undefined; mode: number | undefined }> { try { const fileStats = await lstat(path); if (fileStats.isSymbolicLink()) { throw new Error("Refusing to replace a symbolic-link AGENTS.md."); } if (!fileStats.isFile()) { throw new Error("AGENTS.md exists but is not a regular file."); } return { content: await readFile(path, "utf8"), mode: fileStats.mode, }; } catch (error: unknown) { if ((error as NodeJS.ErrnoException).code === "ENOENT") { return { content: undefined, mode: undefined }; } throw error; } } export async function initializeAgentsMd(projectRoot: string): Promise { const agentsMdPath = join(projectRoot, AGENTS_MD_FILENAME); return withFileMutationQueue(agentsMdPath, async () => { const ruleSets = await detectRuleSets(projectRoot); const documents = await loadRuleDocuments(ruleSets); const managedBlock = renderManagedRules(ruleSets, documents); const existing = await readExistingAgentsMd(agentsMdPath); const nextContent = mergeManagedRules(existing.content, managedBlock); const temporaryPath = join(projectRoot, `.${AGENTS_MD_FILENAME}.${process.pid}.${randomUUID()}.tmp`); try { await writeFile(temporaryPath, nextContent, "utf8"); if (existing.mode !== undefined) await chmod(temporaryPath, existing.mode); await rename(temporaryPath, agentsMdPath); } catch (error: unknown) { await rm(temporaryPath, { force: true }); throw error; } return { agentsMdPath, ruleSets: [...ruleSets], ruleCount: documents.length, }; }); }