import { appendFile, mkdir, readFile, rename, writeFile } from "node:fs/promises"; import { repoRelative, ultragoalDir, ultragoalGoalsPath, ultragoalLedgerPath } from "./paths.js"; import type { UltragoalLedgerEntry, UltragoalPlan } from "./types.js"; import { iso, ULTRAGOAL_DIR, ULTRAGOAL_GOALS, ULTRAGOAL_LEDGER, UltragoalError } from "./types.js"; const AGGREGATE_CODEX_OBJECTIVE = `Complete the durable ultragoal plan in ${ULTRAGOAL_DIR}/${ULTRAGOAL_GOALS}, including later accepted/appended stories, under the original brief constraints; use ${ULTRAGOAL_DIR}/${ULTRAGOAL_LEDGER} as the audit trail.`; const LEGACY_OBJECTIVE_PREFIX = `Complete all ultragoal stories in ${ULTRAGOAL_DIR}/${ULTRAGOAL_GOALS}: `; const LEGACY_OBJECTIVE = `Complete all ultragoal stories listed in ${ULTRAGOAL_DIR}/${ULTRAGOAL_GOALS}. Use ${ULTRAGOAL_DIR}/${ULTRAGOAL_LEDGER} as the durable audit trail.`; const locks = new Map>(); function hasCode(error: unknown, code: string): boolean { return error instanceof Error && "code" in error && error.code === code; } function isLegacyEnumeratedAggregateObjective(objective: string | undefined): objective is string { return objective === LEGACY_OBJECTIVE || Boolean(objective?.startsWith(LEGACY_OBJECTIVE_PREFIX)); } function isSteeringKind(value: unknown): value is UltragoalLedgerEntry["kind"] { return value === "steering_accepted" || value === "steering_rejected" || value === "criteria_revised"; } export async function withUltragoalMutationLock(repoRoot: string, fn: () => Promise): Promise { const prior = locks.get(repoRoot) ?? Promise.resolve(); const run = prior.then(fn, fn); locks.set( repoRoot, run.catch(() => undefined), ); return run; } export async function readUltragoalPlan(repoRoot: string): Promise { const path = ultragoalGoalsPath(repoRoot); let raw: string; try { raw = await readFile(path, "utf8"); } catch (error) { if (!hasCode(error, "ENOENT")) throw error; throw new UltragoalError( `No ultragoal plan found at ${repoRelative(path, repoRoot)}. Run \`omo ultragoal create-goals ...\` first.`, "ULTRAGOAL_PLAN_MISSING", { cause: error }, ); } const parsed: UltragoalPlan = JSON.parse(raw); if (parsed.version !== 1 || !Array.isArray(parsed.goals)) { throw new UltragoalError(`Invalid ultragoal plan at ${repoRelative(path, repoRoot)}.`, "ULTRAGOAL_PLAN_INVALID"); } const previousObjective = parsed.codexObjective; if ( (parsed.codexGoalMode ?? "per_story") === "aggregate" && isLegacyEnumeratedAggregateObjective(previousObjective) ) { const now = iso(); parsed.codexObjective = AGGREGATE_CODEX_OBJECTIVE; parsed.codexObjectiveAliases = [...new Set([...(parsed.codexObjectiveAliases ?? []), previousObjective])]; parsed.updatedAt = now; await writePlan(repoRoot, parsed); await appendLedger(repoRoot, { at: now, kind: "aggregate_objective_migrated", message: "Migrated legacy enumerated aggregate Codex objective to the stable pointer objective.", before: { codexObjective: previousObjective }, after: { codexObjective: parsed.codexObjective }, }); } return parsed; } export async function writePlan(repoRoot: string, plan: UltragoalPlan): Promise { await mkdir(ultragoalDir(repoRoot), { recursive: true }); const path = ultragoalGoalsPath(repoRoot); const tmpPath = `${path}.${process.pid}.${Date.now()}.tmp`; await writeFile(tmpPath, `${JSON.stringify(plan, null, 2)}\n`, "utf8"); await rename(tmpPath, path); } export async function appendLedger(repoRoot: string, entry: UltragoalLedgerEntry): Promise { await mkdir(ultragoalDir(repoRoot), { recursive: true }); await appendFile(ultragoalLedgerPath(repoRoot), `${JSON.stringify(entry)}\n`, "utf8"); } export async function readSteeringLedgerEntries(repoRoot: string): Promise { let raw: string; try { raw = await readFile(ultragoalLedgerPath(repoRoot), "utf8"); } catch (error) { if (hasCode(error, "ENOENT")) return []; throw error; } const entries: UltragoalLedgerEntry[] = []; for (const line of raw.split(/\r?\n/).filter(Boolean)) { const entry: UltragoalLedgerEntry = JSON.parse(line); if (isSteeringKind(entry.kind)) entries.push(entry); } return entries; }