import { createHash, randomUUID } from "node:crypto"; import { lstat, readFile, realpath, rename, unlink, writeFile, } from "node:fs/promises"; import { basename, dirname, isAbsolute, join, relative, resolve, sep, } from "node:path"; import { createFactPack, type Draft, type DraftValidation, type FactPack, type RepositoryScan, type ScopePlan, validateDraft, } from "./repository.ts"; export const MANAGED_BEGIN_PREFIX = ""; const MANAGED_BEGIN = //g; const MAX_AUDIT_FINDINGS = 128; export interface ManagedPreview { readonly id: string; readonly scope: string; readonly targetPath: string; readonly preimageHash: string; readonly preimageExists: boolean; readonly before: string; readonly after: string; readonly generatedBytes: number; readonly draftValidation: DraftValidation; } export interface ManagedPlan { readonly id: string; readonly root: string; readonly maxGeneratedBytes: number; readonly factPacks: ReadonlyMap; readonly previews: ReadonlyMap; } export interface ManagedApplyResult { readonly planId: string; readonly appliedTargets: readonly string[]; } export interface AuditFinding { readonly severity: "error" | "info"; readonly targetPath: string; readonly message: string; } interface TargetSnapshot { readonly root: string; readonly targetPath: string; readonly absolutePath: string; readonly exists: boolean; readonly contents: string; readonly hash: string; readonly mode?: number; } interface MarkerRange { readonly beginStart: number; readonly beginEnd: number; readonly endStart: number; readonly endEnd: number; readonly scope: string; } export class ManagedPlanStore { private readonly plans = new Map(); create(scan: RepositoryScan, scopePlan: ScopePlan): ManagedPlan { const factPacks = new Map(); for (const decision of scopePlan.decisions) { if (!decision.selected) continue; factPacks.set( decision.scope, createFactPack(scan, scopePlan, decision.scope), ); } const plan: ManagedPlan = Object.freeze({ id: randomUUID(), root: scan.root, maxGeneratedBytes: scopePlan.generatedBytesBudget, factPacks, previews: new Map(), }); this.plans.set(plan.id, plan); return plan; } get(planId: string): ManagedPlan | undefined { return this.plans.get(planId); } delete(planId: string): void { this.plans.delete(planId); } clear(): void { this.plans.clear(); } async stageDraft( planId: string, scope: string, draft: Draft, ): Promise { const plan = this.requirePlan(planId); const pack = plan.factPacks.get(scope); if (!pack) throw new Error("Scope is not selected in this plan."); const validation = validateDraft(pack, draft); if (!validation.valid) throw new Error("Draft validation failed."); const preview = await stageManagedPreview( plan.root, pack, draft, validation, ); const existingBytes = [...plan.previews.values()] .filter((candidate) => candidate.scope !== scope) .reduce((total, candidate) => total + candidate.generatedBytes, 0); if (existingBytes + preview.generatedBytes > plan.maxGeneratedBytes) { throw new Error("Generated output exceeds the plan byte budget."); } const updated: ManagedPlan = Object.freeze({ ...plan, previews: new Map(plan.previews).set(scope, preview), }); this.plans.set(planId, updated); return preview; } async apply(planId: string): Promise { const plan = this.requirePlan(planId); const previews = [...plan.previews.values()].sort((left, right) => left.targetPath.localeCompare(right.targetPath), ); if (previews.length === 0) throw new Error("Plan has no staged managed updates."); const snapshots = await Promise.all( previews.map((preview) => readManagedTarget(plan.root, preview.targetPath), ), ); for (const [index, snapshot] of snapshots.entries()) { const preview = previews[index]; if ( !preview || snapshot.hash !== preview.preimageHash || snapshot.exists !== preview.preimageExists ) { throw new Error( "Managed preview is stale; create a new preview before applying.", ); } } const completed: Array = []; try { for (const [index, snapshot] of snapshots.entries()) { const preview = previews[index]; if (!preview) throw new Error("Managed preview is unavailable."); await writeAtomically(snapshot, preview.after); completed.push([snapshot, preview]); } } catch (error) { const rollbackFailures: string[] = []; for (const [snapshot, preview] of [...completed].reverse()) { try { await rollbackWrite(snapshot, preview.after); } catch { rollbackFailures.push(preview.targetPath); } } const restored = completed .map(([, preview]) => preview.targetPath) .filter((target) => !rollbackFailures.includes(target)); const outcome = rollbackFailures.length === 0 ? `Rolled back ${restored.length} target(s).` : `Could not roll back: ${rollbackFailures.join(", ")}.`; const reason = error instanceof Error ? error.message : "Managed write failed."; throw new Error(`Managed apply failed. ${outcome} ${reason}`); } return Object.freeze({ planId, appliedTargets: Object.freeze( previews.map((preview) => preview.targetPath), ), }); } private requirePlan(planId: string): ManagedPlan { const plan = this.plans.get(planId); if (!plan) throw new Error("Unknown managed plan."); return plan; } } export async function auditManagedScopes( root: string, scopes: readonly string[], ): Promise { const findings: AuditFinding[] = []; for (const scope of [...new Set(scopes)].sort()) { if (findings.length >= MAX_AUDIT_FINDINGS) break; const targetPath = scope === "." ? "AGENTS.md" : `${scope}/AGENTS.md`; try { const snapshot = await readManagedTarget(root, targetPath); if (!snapshot.exists) { findings.push({ severity: "info", targetPath, message: "No AGENTS.md exists for this scope.", }); continue; } const marker = inspectMarkers(snapshot.contents, scope); if (!marker) { findings.push({ severity: "info", targetPath, message: "No package-owned block exists.", }); continue; } const lines = snapshot.contents .split(/\r?\n/) .filter((line) => line.trim().length > 0).length; const hardLimit = scope === "." ? 100 : 60; if (lines > hardLimit) { findings.push({ severity: "error", targetPath, message: "Instruction file exceeds its hard line limit.", }); } else { findings.push({ severity: "info", targetPath, message: "Managed markers and line budget are valid; content provenance is not verified.", }); } } catch (error) { findings.push({ severity: "error", targetPath, message: error instanceof Error ? error.message : "Could not audit AGENTS.md.", }); } } return Object.freeze(findings); } export async function stageManagedPreview( root: string, pack: FactPack, draft: Draft, validation: DraftValidation = validateDraft(pack, draft), ): Promise { if (!validation.valid) throw new Error("Draft validation failed."); const snapshot = await readManagedTarget(root, pack.targetPath); const body = draft.markdown.trimEnd(); if ( body.includes(MANAGED_BEGIN_PREFIX) || body.includes(MANAGED_END_MARKER) ) { throw new Error("Draft must not contain managed markers."); } const after = mergeManagedBody(snapshot.contents, pack.scope, body); const hardLineLimit = pack.scope === "." ? 100 : 60; const targetLines = countNonBlankLines(after); if (targetLines > hardLineLimit) { throw new Error("Managed update exceeds the target file hard line limit."); } const inheritedLines = await countInheritedInstructionLines(root, pack.scope); if (inheritedLines + targetLines > 160) { throw new Error( "Managed update exceeds the effective instruction-chain hard line limit.", ); } return Object.freeze({ id: randomUUID(), scope: pack.scope, targetPath: pack.targetPath, preimageHash: snapshot.hash, preimageExists: snapshot.exists, before: snapshot.contents, after, generatedBytes: new TextEncoder().encode(body).byteLength, draftValidation: validation, }); } export function mergeManagedBody( existing: string, scope: string, body: string, ): string { const marker = inspectMarkers(existing, scope); const lineEnding = existing.includes("\r\n") ? "\r\n" : "\n"; const normalizedBody = `${lineEnding}${body}${lineEnding}`; if (marker) { return `${existing.slice(0, marker.beginEnd)}${normalizedBody}${existing.slice(marker.endStart)}`; } const block = `${managedBegin(scope)}${normalizedBody}${MANAGED_END_MARKER}${lineEnding}`; if (existing.length === 0) return block; return `${existing}${existing.endsWith("\n") ? lineEnding : `${lineEnding}${lineEnding}`}${block}`; } export function inspectMarkers( contents: string, expectedScope: string, ): MarkerRange | undefined { const begins = [...contents.matchAll(MANAGED_BEGIN)]; const ends = [ ...contents.matchAll(new RegExp(escapeRegex(MANAGED_END_MARKER), "g")), ]; const allManagedMarkers = [ ...contents.matchAll(//g), ]; const hasOwnedText = contents.includes("`; } async function countInheritedInstructionLines( root: string, scope: string, ): Promise { if (scope === ".") return 0; const parts = scope.split("/"); let total = 0; for (let index = 0; index < parts.length; index += 1) { const ancestor = index === 0 ? "." : parts.slice(0, index).join("/"); const targetPath = ancestor === "." ? "AGENTS.md" : `${ancestor}/AGENTS.md`; const snapshot = await readManagedTarget(root, targetPath); total += countNonBlankLines(snapshot.contents); } return total; } function countNonBlankLines(value: string): number { return value.split(/\r?\n/).filter((line) => line.trim().length > 0).length; } function sha256(value: string): string { return createHash("sha256").update(value, "utf8").digest("hex"); } function decodeUtf8(bytes: Uint8Array): string { return new TextDecoder("utf-8", { fatal: true }).decode(bytes); } function isSafeScope(scope: string): boolean { return scope === "." || isSafeTargetPath(scope); } function isSafeTargetPath(path: string): boolean { const portable = toPortablePath(path); if ( portable.length === 0 || portable.startsWith("/") || portable.includes("//") ) return false; return portable .split("/") .every((part) => part.length > 0 && part !== "." && part !== ".."); } function isContained(root: string, target: string): boolean { const child = relative(root, target); return ( child === "" || (!isAbsolute(child) && child !== ".." && !child.startsWith(`..${sep}`)) ); } function toPortablePath(path: string): string { return path.split(sep).join("/"); } function fromPortablePath(path: string): string { return path.split("/").join(sep); } function hasErrorCode(error: unknown, expectedCode: string): boolean { return ( typeof error === "object" && error !== null && "code" in error && error.code === expectedCode ); } function escapeRegex(value: string): string { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); }