import { existsSync, lstatSync, readFileSync } from "node:fs"; import path from "node:path"; import type { EditToolInput, WriteToolInput, } from "@earendil-works/pi-coding-agent"; import { type Diagnostic, type DocumentMetadata, discoverDocumentBundles, discoverProjectRoot, indexBundleDocuments, parseAuthoredDocument, parseSystemConfig, readCurrentPhase, resolveGovernedPath, SCHEMA_VERSION, validateBundleCandidate, } from "./documents.ts"; import { inspectInitialization } from "./init.ts"; export const CORE_BYTE_LIMIT = 32 * 1024; const CORES = ["SYSTEM.md", "MISSION.md", "RULES.md"] as const; const CONFIG_PATH = ".system/config.json"; const governanceError = (message: string) => `# The System governance error\n\n${message}\n\nDo not mutate .system until this governance error is resolved.`; const coreBlock = (root: string, name: (typeof CORES)[number]) => { const relative = `.system/${name}`; const target = path.join(root, relative); try { const stats = lstatSync(target, { throwIfNoEntry: false }); if (!stats) return undefined; if (!stats.isFile()) throw new Error( `Malformed core path ${relative}: expected a regular file.`, ); if (stats.size > CORE_BYTE_LIMIT) return `## ${name}\n\n[Not injected: ${name} exceeds the ${CORE_BYTE_LIMIT}-byte injection limit.]`; return `## ${name}\n\n${readFileSync(target, "utf8")}`; } catch (error) { throw new Error( `Unable to read core path ${relative}: ${error instanceof Error ? error.message : String(error)}`, ); } }; export function composeSystemSection(cwd: string): string | undefined { const root = discoverProjectRoot(cwd); if (!root) return undefined; try { const [system, mission, rules] = CORES.map((name) => coreBlock(root, name)); let phase: string | undefined; const configFile = path.join(root, CONFIG_PATH); if (existsSync(configFile)) { const config = parseSystemConfig(readFileSync(configFile, "utf8")); const current = readCurrentPhase(root, config); if (current) phase = `## Current phase: ${current.metadata.title} · ${current.metadata.label}\n\n${current.markdown}`; } const blocks = [system, mission, phase, rules].filter( (block): block is string => block !== undefined, ); return blocks.length === 0 ? undefined : `# The System project law\n\n${blocks.join("\n\n")}`; } catch (error) { return governanceError( error instanceof Error ? error.message : String(error), ); } } export type SystemRuntimeState = Readonly<{ active: boolean; prefix?: string; phaseLabel?: string; notice?: string; }>; const INCOMPLETE_NOTICE = "The System is incomplete or invalid; run /system init before governed work."; const SCHEMA_NOTICE = `The System schema does not match ${SCHEMA_VERSION}; run /system init to update it.`; export function runtimeState(cwd: string): SystemRuntimeState { const root = discoverProjectRoot(cwd); if (!root) return { active: false }; try { const inspection = inspectInitialization(root); if (inspection.rewrites.length > 0) return { active: true, notice: SCHEMA_NOTICE }; const incomplete = inspection.creates.length > 0 || inspection.cores.some(({ state }) => state === "missing"); let phaseLabel: string | undefined; if (inspection.prefix) { const config = parseSystemConfig( readFileSync(path.join(root, CONFIG_PATH), "utf8"), ); phaseLabel = readCurrentPhase(root, config)?.metadata.label; } const notice = incomplete ? INCOMPLETE_NOTICE : undefined; return { active: true, ...(inspection.prefix ? { prefix: inspection.prefix } : {}), ...(phaseLabel ? { phaseLabel } : {}), ...(notice ? { notice } : {}), }; } catch { return { active: true, notice: INCOMPLETE_NOTICE }; } } export function runtimeNotice(cwd: string): string | undefined { return runtimeState(cwd).notice; } type MutationKind = "write" | "edit"; type MutationInput = WriteToolInput | EditToolInput; type GateResult = Readonly<{ block: true; reason: string }> | undefined; export type GovernanceUI = Readonly<{ hasUI: boolean; select(title: string, items: string[]): Promise; }>; type MutationOptions = Readonly<{ bundleMigration?: boolean }>; const CORE_PATHS = new Set([ ".system/SYSTEM.md", ".system/MISSION.md", ".system/RULES.md", ]); const posixRelative = (root: string, target: string) => path.relative(root, target).split(path.sep).join("/"); const blocked = (reason: string): GateResult => ({ block: true, reason }); const diagnosticsReason = (diagnostics: readonly Diagnostic[]) => { const shown = diagnostics .slice(0, 3) .map((item) => `${item.path} ${item.code}: ${item.message}`); if (diagnostics.length > shown.length) shown.push(`+${diagnostics.length - shown.length} more finding(s)`); return `The System candidate validation failed: ${shown.join("; ")}`; }; const applyExactEdits = (source: string, edits: EditToolInput["edits"]) => { const replacements = edits .map((edit) => { const start = source.indexOf(edit.oldText); if (start < 0 || source.indexOf(edit.oldText, start + 1) >= 0) throw new Error("each governed edit oldText must match exactly once"); return { start, end: start + edit.oldText.length, newText: edit.newText }; }) .sort((left, right) => left.start - right.start); for (let index = 1; index < replacements.length; index++) { const previous = replacements[index - 1]; const next = replacements[index]; if (previous && next && previous.end > next.start) throw new Error("governed edits must not overlap"); } let candidate = source; for (const replacement of replacements.reverse()) candidate = candidate.slice(0, replacement.start) + replacement.newText + candidate.slice(replacement.end); return candidate; }; const candidateContent = ( target: string, kind: MutationKind, input: MutationInput, ) => { if (kind === "write") return (input as WriteToolInput).content; if (!existsSync(target)) throw new Error("edit cannot create a governed file; use write"); return applyExactEdits( readFileSync(target, "utf8"), (input as EditToolInput).edits, ); }; const approveOwnedMutation = async ( relative: string, kind: MutationKind, ui: GovernanceUI, ): Promise => { if (!ui.hasUI) return blocked( `The System blocked existing human-owned ${relative}: interactive UI is unavailable.`, ); const approve = `Approve once: ${kind} ${relative}`; const deny = `Deny: keep ${relative} unchanged`; const decision = await ui.select("The System ownership approval", [ approve, deny, ]); if (decision === approve) return undefined; return blocked( decision === deny ? `The System ownership approval was denied for ${relative}.` : `The System ownership approval was cancelled for ${relative}.`, ); }; const projectPrefix = (root: string) => parseSystemConfig(readFileSync(path.join(root, CONFIG_PATH), "utf8")).prefix; const preflightCurrentPhase = async ( root: string, target: string, kind: MutationKind, input: MutationInput, ui: GovernanceUI, ): Promise => { const stats = lstatSync(target, { throwIfNoEntry: false }); if (!stats?.isFile()) return blocked( "The System config must be initialized before selecting a phase.", ); try { const current = parseSystemConfig(readFileSync(target, "utf8")); const candidate = parseSystemConfig(candidateContent(target, kind, input)); if ( candidate.prefix !== current.prefix || candidate.title !== current.title ) return blocked( "The System config mutation may only change currentPhase.", ); if (candidate.currentPhase === current.currentPhase) return blocked( candidate.currentPhase ? `The System phase is already ${candidate.currentPhase}.` : "The System already has no current phase.", ); const phase = readCurrentPhase(root, candidate); if (!ui.hasUI) return blocked( `The System blocked phase ${candidate.currentPhase ? "activation" : "deactivation"}: interactive UI is unavailable.`, ); const approve = phase ? `Activate: ${phase.metadata.label} · ${phase.metadata.title}` : "Deactivate current phase"; const deny = `Deny: keep ${current.currentPhase ?? "no current phase"}`; const decision = await ui.select("The System phase transition", [ approve, deny, ]); if (decision === approve) return undefined; return blocked( decision === deny ? "The System phase transition was denied." : "The System phase transition was cancelled.", ); } catch (error) { return blocked( `The System rejected the config change: ${error instanceof Error ? error.message : String(error)}`, ); } }; type GovernedDocumentPath = Readonly<{ directory: "specs" | "phases" | "plans" | "issues" | "research"; bundle: string; asset: string; index: boolean; }>; const governedDocumentPath = ( relative: string, ): GovernedDocumentPath | undefined => { const match = /^\.system\/(specs|phases|plans|issues|research)\/([^/]+)\/(.+)$/.exec( relative, ); if (!match) return undefined; const [, directory, bundle, asset] = match; if (!directory || !bundle || !asset || asset.endsWith("/index.md")) return undefined; return { directory: directory as GovernedDocumentPath["directory"], bundle, asset, index: asset === "index.md", }; }; type ResolvedBundleMutation = Readonly<{ location: GovernedDocumentPath; metadata?: DocumentMetadata; findings: readonly Diagnostic[]; }>; const resolveBundleMutation = ( root: string, relative: string, content: string | undefined, ): ResolvedBundleMutation => { const location = governedDocumentPath(relative); if (!location) throw new Error( `Authored paths require .system//-/: ${relative}`, ); const prefix = projectPrefix(root); const discovery = discoverDocumentBundles(root); const bundlePath = `.system/${location.directory}/${location.bundle}`; const localDiscovery = discovery.diagnostics .filter( (item) => item.path === bundlePath || item.path.startsWith(`${bundlePath}/`), ) .filter( (item) => !( location.index && item.path === bundlePath && item.code === "bundle.missing-index" ), ); if (localDiscovery.length > 0) return { location, findings: localDiscovery }; if (location.index) { if (content === undefined) throw new Error("Missing index candidate content"); const findings = validateBundleCandidate( discovery.sources, { path: relative, markdown: content }, prefix, ); const parsed = parseAuthoredDocument(content, prefix); return { location, metadata: parsed.metadata, findings }; } const indexPath = `${bundlePath}/index.md`; const owner = discovery.sources.find((source) => source.path === indexPath); if (!owner) throw new Error( `Bundle requires a valid index.md before assets: ${bundlePath}`, ); const indexed = indexBundleDocuments(discovery.sources, prefix); const findings = indexed.diagnostics.filter( (item) => item.path === indexPath, ); const document = indexed.documents.find((item) => item.path === indexPath); if (!document) throw new Error(`Bundle index is invalid: ${indexPath}`); return { location, metadata: document.metadata, findings }; }; const currentBundleMetadata = ( root: string, location: GovernedDocumentPath, ): DocumentMetadata | undefined => { const prefix = projectPrefix(root); const bundlePath = `.system/${location.directory}/${location.bundle}`; const indexPath = `${bundlePath}/index.md`; const indexed = indexBundleDocuments( discoverDocumentBundles(root).sources, prefix, ); return indexed.documents.find((item) => item.path === indexPath)?.metadata; }; const symbolicLinkInPath = (root: string, target: string) => { let current = root; for (const part of path.relative(root, target).split(path.sep)) { if (!part) continue; current = path.join(current, part); if (lstatSync(current, { throwIfNoEntry: false })?.isSymbolicLink()) return posixRelative(root, current); } return undefined; }; export async function preflightFileMutation( cwd: string, kind: MutationKind, input: MutationInput, ui: GovernanceUI, options: MutationOptions = {}, ): Promise { const root = discoverProjectRoot(cwd); if (!root) return undefined; const candidate = path.resolve(cwd, input.path); const candidateRelative = path.relative(root, candidate); if ( candidateRelative === ".." || candidateRelative.startsWith(`..${path.sep}`) || path.isAbsolute(candidateRelative) ) return undefined; let target: string; try { target = resolveGovernedPath(root, candidate); } catch (error) { return blocked( `The System rejected the governed path: ${error instanceof Error ? error.message : String(error)}`, ); } const relative = posixRelative(root, target); if ( relative !== CONFIG_PATH && !CORE_PATHS.has(relative) && !/^\.system\/(specs|phases|plans|issues|research)(?:\/|$)/.test(relative) ) return relative.startsWith(".system/") ? blocked( `The System only allows governed document bundles under .system/specs, .system/phases, .system/plans, .system/issues, or .system/research: ${relative}.`, ) : undefined; try { const link = symbolicLinkInPath(root, target); if (link) return blocked( `The System rejected symbolic link in governed path: ${link}.`, ); } catch (error) { return blocked( `The System rejected unsafe governed path: ${error instanceof Error ? error.message : String(error)}`, ); } const targetStats = lstatSync(target, { throwIfNoEntry: false }); if (relative === CONFIG_PATH) return preflightCurrentPhase(root, target, kind, input, ui); if (CORE_PATHS.has(relative)) { if (targetStats && !targetStats.isFile()) return blocked( `The System expected a regular owned file at ${relative}.`, ); let content: string; try { content = candidateContent(target, kind, input); } catch (error) { return blocked( `The System candidate validation failed: ${error instanceof Error ? error.message : String(error)}`, ); } if (Buffer.byteLength(content, "utf8") > CORE_BYTE_LIMIT) return blocked( `The System candidate validation failed: ${relative} exceeds the ${CORE_BYTE_LIMIT}-byte core limit.`, ); return targetStats ? approveOwnedMutation(relative, kind, ui) : undefined; } const location = governedDocumentPath(relative); if (!location) return blocked( `The System requires document bundles at .system//-/: ${relative}.`, ); if (options.bundleMigration) { if (!location.index || !targetStats?.isFile()) return blocked( `The System bundle migration may only update an existing index.md: ${relative}.`, ); try { const content = candidateContent(target, kind, input); const discovery = discoverDocumentBundles(root); const findings = validateBundleCandidate( discovery.sources, { path: relative, markdown: content }, projectPrefix(root), ); if (findings.length > 0) return blocked(diagnosticsReason(findings)); return undefined; } catch (error) { return blocked( `The System candidate validation failed: ${error instanceof Error ? error.message : String(error)}`, ); } } try { const current = currentBundleMetadata(root, location); if (current?.type === "plan" && current.status === "approved") return blocked( `The System blocked mutation of approved ${current.type} ${current.id}; create a new bundle instead.`, ); } catch (error) { return blocked( `The System candidate validation failed: ${error instanceof Error ? error.message : String(error)}`, ); } let content: string | undefined; if (location.index) try { content = candidateContent(target, kind, input); } catch (error) { return blocked( `The System candidate validation failed: ${error instanceof Error ? error.message : String(error)}`, ); } let resolved: ResolvedBundleMutation; try { resolved = resolveBundleMutation(root, relative, content); } catch (error) { return blocked( `The System candidate validation failed: ${error instanceof Error ? error.message : String(error)}`, ); } if (resolved.findings.length > 0) return blocked(diagnosticsReason(resolved.findings)); if (!resolved.metadata) return blocked( `The System could not determine bundle ownership: ${relative}.`, ); if (!targetStats) return undefined; if (!targetStats.isFile()) return blocked(`The System expected a regular owned file at ${relative}.`); if ( location.index && resolved.metadata.type === "plan" && resolved.metadata.status === "approved" ) { const current = currentBundleMetadata(root, location); if (current?.type === "plan" && current.status !== "approved") { if (!ui.hasUI) return blocked( `The System blocked approval of ${resolved.metadata.type} ${resolved.metadata.id}: interactive UI is unavailable.`, ); const approve = `Approve: ${resolved.metadata.id}`; const deny = `Deny: keep ${resolved.metadata.id} draft`; const decision = await ui.select("The System document approval", [ approve, deny, ]); if (decision !== approve) return blocked( decision === deny ? `The System approval was denied for ${resolved.metadata.id}.` : `The System approval was cancelled for ${resolved.metadata.id}.`, ); } } if (resolved.metadata.type !== "spec" && resolved.metadata.type !== "phase") return undefined; return approveOwnedMutation(relative, kind, ui); }