feat(omo-codex): batch 103 (19 files)

This commit is contained in:
YeonGyu-Kim
2026-05-30 19:12:21 +09:00
parent 20b0784811
commit 89ac00f0e7
19 changed files with 2298 additions and 0 deletions
@@ -0,0 +1,155 @@
// biome-ignore-all format: keep checkpoint orchestration below the pure LOC budget.
import { existsSync } from "node:fs";
import { readFile } from "node:fs/promises";
import { resolve } from "node:path";
import { formatCodexGoalReconciliation, readCodexGoalSnapshotInput, reconcileCodexGoalSnapshot } from "./codex-goal-snapshot.js";
import { requireAllCriteriaPass } from "./evidence.js";
import { codexGoalMode, compatibleCodexObjectives, expectedCodexObjective, isFinalRunCompletionCandidate } from "./goal-status.js";
import { type UlwLoopScope, ulwLoopBriefPath } from "./paths.js";
import { appendLedger, readUlwLoopPlan, withUlwLoopMutationLock, writePlan } from "./plan-io.js";
import { classifyExternalAuthorizationBlocker, clearGoalBlockerFields, sameBlockerOccurrences, validateQualityGate } from "./quality-gate.js";
import type { UlwLoopAggregateCompletion, UlwLoopItem, UlwLoopLedgerEntry, UlwLoopPlan, UlwLoopQualityGate } from "./types.js";
import { iso, ULW_LOOP_DIR, ULW_LOOP_GOALS, ULW_LOOP_LEDGER, UlwLoopError } from "./types.js";
export interface CheckpointUlwLoopArgs { readonly goalId: string; readonly status: "complete" | "failed" | "blocked"; readonly evidence: string; readonly codexGoalJson?: string; readonly qualityGateJson?: string }
export interface CheckpointUlwLoopResult { readonly plan: UlwLoopPlan; readonly goal: UlwLoopItem; readonly ledgerEntry: UlwLoopLedgerEntry; readonly aggregateCompletion?: UlwLoopAggregateCompletion }
function ulwLoopFail(message: string, code: string): never { throw new UlwLoopError(message, code); }
function normalizeObjective(value: string): string { return value.replace(/\s+/g, " ").trim(); }
function nonEmptyEvidence(value: string): string { const trimmed = value.trim(); return trimmed || ulwLoopFail("Evidence must be a non-empty string.", "ulw_loop_evidence_required"); }
function findGoal(plan: UlwLoopPlan, goalId: string): UlwLoopItem { const goal = plan.goals.find((candidate) => candidate.id === goalId); return goal ?? ulwLoopFail(`Unknown ulw-loop id: ${goalId}.`, "ulw_loop_goal_not_found"); }
function textMentionsUlwLoopPlanArtifact(value: string | undefined): boolean {
const normalized = (value ?? "").toLowerCase();
return normalized.includes(ULW_LOOP_DIR.toLowerCase()) || normalized.includes(ULW_LOOP_GOALS.toLowerCase()) || normalized.includes(ULW_LOOP_LEDGER.toLowerCase());
}
function textMentionsGoalId(value: string | undefined, goalId: string): boolean { return (value ?? "").toLowerCase().includes(goalId.toLowerCase()); }
function textHasCompletionValidationEvidence(value: string | undefined): boolean {
const normalized = (value ?? "").toLowerCase();
const done = /\b(?:planned work|implementation|deliverables?|scope|task|work)\b/.test(normalized) && /\b(?:done|complete|completed|finished|shipped)\b/.test(normalized);
const verified = /\b(?:validation|verification|tests?|build|lint|review|quality gate|code-review)\b/.test(normalized) && /\b(?:passed|complete|completed|clean|green|approve|approved|clear)\b/.test(normalized);
return done && verified;
}
async function snapshotObjectiveMapsToUlwLoopPlan(repoRoot: string, snapshotObjective: string, scope?: UlwLoopScope): Promise<boolean> {
const actual = normalizeObjective(snapshotObjective).toLowerCase();
if (textMentionsUlwLoopPlanArtifact(actual)) return true;
if (actual.length < 24 || !existsSync(ulwLoopBriefPath(repoRoot, scope))) return false;
try {
const brief = normalizeObjective(await readFile(ulwLoopBriefPath(repoRoot, scope), "utf8")).toLowerCase();
return brief.length >= 24 && (brief.includes(actual) || actual.includes(brief));
} catch (error) {
if (error instanceof Error) return false;
throw error;
}
}
async function canReconcileCompletedTaskScopedAggregateSnapshot(repoRoot: string, plan: UlwLoopPlan, goal: UlwLoopItem, snapshotObjective: string, evidence: string, scope?: UlwLoopScope): Promise<boolean> {
if (codexGoalMode(plan) !== "aggregate") return false;
if (goal.status !== "in_progress" || plan.activeGoalId !== goal.id) return false;
if (isFinalRunCompletionCandidate(plan, goal)) return snapshotObjectiveMapsToUlwLoopPlan(repoRoot, snapshotObjective, scope);
if (!textMentionsUlwLoopPlanArtifact(evidence) || !textMentionsGoalId(evidence, goal.id)) return false;
if (!textHasCompletionValidationEvidence(evidence)) return false;
return snapshotObjectiveMapsToUlwLoopPlan(repoRoot, snapshotObjective, scope);
}
function buildCompletedLegacyGoalRemediation(goal: UlwLoopItem): string {
return [
"If get_goal returns a different completed legacy/thread objective, do not repeat --status complete in this thread.",
`Record a non-terminal blocker with: omo ulw-loop checkpoint --goal-id ${goal.id} --status blocked --evidence "<completed legacy Codex goal blocks create_goal in this thread>" --codex-goal-json "<different completed get_goal JSON or path>".`,
"Then continue only from a Codex goal context with no active/completed conflicting goal, in the same repo/worktree, and create the intended goal there.",
].join(" ");
}
function buildTaskScopedAggregateReconciliationHint(goal: UlwLoopItem, final: boolean): string {
if (final) {
return ` Final task-scoped aggregate reconciliation requires the checkpoint goal to be the active in-progress final OMO goal and the completed get_goal objective to map to the ulw-loop brief or artifact. ${buildCompletedLegacyGoalRemediation(goal)}`;
}
return ` Completed task-scoped aggregate reconciliation requires the checkpoint goal to be the active in-progress OMO goal, evidence that names that active OMO goal id, names .omo/ulw-loop/goals.json or ledger.jsonl, includes completed implementation plus validation/review evidence, and a get_goal objective that maps to the ulw-loop brief/artifact. ${buildCompletedLegacyGoalRemediation(goal)}`;
}
async function readJsonInput(raw: string | undefined, repoRoot: string): Promise<unknown> {
if (raw === undefined || raw.trim() === "") return undefined;
const trimmed = raw.trim();
try { return JSON.parse(trimmed); } catch (error) { if (!(error instanceof SyntaxError)) throw error; }
const path = resolve(repoRoot, trimmed);
if (!existsSync(path)) return ulwLoopFail("Quality gate JSON is neither valid JSON nor a readable path.", "ulw_loop_json_input_invalid");
try { return JSON.parse(await readFile(path, "utf8")); } catch (error) { return ulwLoopFail(`Quality gate path does not contain valid JSON${error instanceof Error ? `: ${error.message}` : "."}`, "ulw_loop_json_input_invalid"); }
}
function makeAggregateCompletion(now: string, evidence: string, codexGoal: unknown): UlwLoopAggregateCompletion {
return { status: "complete", completedAt: now, evidence, codexGoal };
}
function applyBlockedOrFailed(goal: UlwLoopItem, plan: UlwLoopPlan, status: "failed" | "blocked", evidence: string, now: string): void {
const signature = classifyExternalAuthorizationBlocker(evidence);
const occurrences = signature === null ? 0 : sameBlockerOccurrences(plan, signature) + 1;
const needsDecision = signature !== null && occurrences >= 3;
goal.status = needsDecision ? "needs_user_decision" : status;
goal.updatedAt = now;
if (status === "failed" || needsDecision) { goal.failedAt = now; goal.failureReason = evidence; }
if (status === "blocked" || needsDecision) goal.blockedReason = evidence;
if (signature !== null) { goal.blockerSignature = signature; goal.blockerOccurrenceCount = occurrences; goal.requiredExternalDecision = `Resolve external authorization: ${signature}`; }
if (needsDecision) goal.nonRetriable = true;
if (plan.activeGoalId === goal.id) delete plan.activeGoalId;
}
function ledgerKind(status: CheckpointUlwLoopArgs["status"], goal: UlwLoopItem, aggregateCompletion: UlwLoopAggregateCompletion | undefined): UlwLoopLedgerEntry["kind"] {
if (aggregateCompletion !== undefined) return "aggregate_completed";
if (status === "complete") return "goal_completed";
if (goal.status === "needs_user_decision") return "goal_needs_user_decision";
return status === "blocked" ? "goal_blocked" : "goal_failed";
}
function buildLedger(now: string, args: CheckpointUlwLoopArgs, goal: UlwLoopItem, qualityGate: UlwLoopQualityGate | undefined, codexGoal: unknown, aggregateCompletion: UlwLoopAggregateCompletion | undefined): UlwLoopLedgerEntry {
const entry: UlwLoopLedgerEntry = { at: now, kind: ledgerKind(args.status, goal, aggregateCompletion), goalId: goal.id, status: goal.status, evidence: args.evidence };
if (codexGoal !== undefined) entry.codexGoal = codexGoal;
if (qualityGate !== undefined) entry.qualityGate = qualityGate;
if (goal.blockerSignature !== undefined) entry.blockerSignature = goal.blockerSignature;
if (goal.blockerOccurrenceCount !== undefined) entry.blockerOccurrenceCount = goal.blockerOccurrenceCount;
if (goal.requiredExternalDecision !== undefined) entry.requiredExternalDecision = goal.requiredExternalDecision;
return entry;
}
export async function checkpointUlwLoop(repoRoot: string, args: CheckpointUlwLoopArgs, scope?: UlwLoopScope): Promise<CheckpointUlwLoopResult> {
return withUlwLoopMutationLock(repoRoot, scope, async () => {
const plan = await readUlwLoopPlan(repoRoot, scope);
const goal = findGoal(plan, args.goalId);
if (args.status === "complete") requireAllCriteriaPass(goal);
const evidence = nonEmptyEvidence(args.evidence);
const now = iso();
let aggregateCompletion: UlwLoopAggregateCompletion | undefined;
let qualityGate: UlwLoopQualityGate | undefined;
let codexGoal: unknown;
if (args.status === "complete") {
const aggregate = codexGoalMode(plan) === "aggregate";
const final = isFinalRunCompletionCandidate(plan, goal);
const snapshot = await readCodexGoalSnapshotInput(args.codexGoalJson, repoRoot);
const reconciliation = reconcileCodexGoalSnapshot(snapshot, { expectedObjective: expectedCodexObjective(plan, goal), ...(aggregate ? { acceptedObjectives: compatibleCodexObjectives(plan) } : {}), allowedStatuses: aggregate ? (final ? ["complete"] : ["active"]) : ["complete"], requireSnapshot: true, requireComplete: !aggregate || final });
codexGoal = reconciliation.snapshot.raw;
if (!reconciliation.ok) {
const objective = snapshot?.objective;
const taskScoped = snapshot?.available === true && snapshot.status === "complete" && objective !== undefined && normalizeObjective(objective) !== normalizeObjective(expectedCodexObjective(plan, goal)) && await canReconcileCompletedTaskScopedAggregateSnapshot(repoRoot, plan, goal, objective, evidence, scope);
if (!taskScoped) throw new UlwLoopError(`${formatCodexGoalReconciliation(reconciliation)}${aggregate && snapshot?.status === "complete" && objective !== undefined ? buildTaskScopedAggregateReconciliationHint(goal, final) : ""}`, "ulw_loop_codex_snapshot_mismatch");
aggregateCompletion = makeAggregateCompletion(now, evidence, codexGoal);
}
if (final) aggregateCompletion = makeAggregateCompletion(now, evidence, codexGoal);
if (final || aggregateCompletion !== undefined) qualityGate = validateQualityGate(await readJsonInput(args.qualityGateJson, repoRoot));
goal.status = "complete";
goal.completedAt = now;
goal.evidence = evidence;
delete goal.failedAt;
delete goal.failureReason;
clearGoalBlockerFields(goal);
if (plan.activeGoalId === goal.id) delete plan.activeGoalId;
} else applyBlockedOrFailed(goal, plan, args.status, evidence, now);
goal.updatedAt = now;
if (aggregateCompletion !== undefined) plan.aggregateCompletion = aggregateCompletion;
plan.updatedAt = now;
await writePlan(repoRoot, plan, scope);
const ledgerEntry = buildLedger(now, args, goal, qualityGate, codexGoal, aggregateCompletion);
await appendLedger(repoRoot, ledgerEntry, scope);
return aggregateCompletion === undefined ? { plan, goal, ledgerEntry } : { plan, goal, ledgerEntry, aggregateCompletion };
});
}
@@ -0,0 +1,95 @@
// biome-ignore-all format: keep this module under the mandated pure LOC budget.
import { readFile } from "node:fs/promises";
import { UlwLoopError } from "./types.js";
type RecordEvidenceCliArgs = { readonly goalId: string; readonly criterionId: string; readonly status: "pass" | "fail" | "blocked"; readonly evidence: string; readonly notes?: string };
const VALUE_FLAGS = new Set("--brief --brief-file --session-id --codex-goal-mode --goal --goal-id --criterion-id --status --evidence --notes --codex-goal-json --quality-gate-json --kind --rationale --title --objective --target-goal-id --source --after-json --directive-json --directive-file --idempotency-key".split(" "));
const SUBCOMMANDS = new Set("create-goals status complete-goals criteria record-evidence checkpoint steer add-goal record-review-blockers".split(" "));
export function hasFlag(argv: readonly string[], flag: string): boolean { return argv.includes(flag); }
export function readValue(argv: readonly string[], flag: string): string | undefined {
const index = argv.indexOf(flag);
if (index >= 0) {
const next = argv[index + 1];
return next === undefined || next.startsWith("--") ? undefined : next;
}
const prefix = `${flag}=`;
return argv.find((arg) => arg.startsWith(prefix))?.slice(prefix.length);
}
export function readRepeated(argv: readonly string[], flag: string): string[] {
const values: string[] = [];
const prefix = `${flag}=`;
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
const next = argv[index + 1];
if (arg === flag && next !== undefined && !next.startsWith("--")) { values.push(next); index += 1; }
else if (arg?.startsWith(prefix)) values.push(arg.slice(prefix.length));
}
return values;
}
export function parseGoalArg(argv: readonly string[]): string | undefined { return readValue(argv, "--goal-id") ?? readValue(argv, "--goal"); }
export async function readStdin(): Promise<string> {
const chunks: Buffer[] = [];
for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
return Buffer.concat(chunks).toString("utf8");
}
export function positionalText(argv: readonly string[]): string {
const words: string[] = [];
for (let index = SUBCOMMANDS.has(argv[0] ?? "") ? 1 : 0; index < argv.length; index += 1) {
const arg = argv[index];
if (arg === undefined) continue;
if (VALUE_FLAGS.has(arg)) { index += 1; continue; }
if (arg.startsWith("--")) continue;
words.push(arg);
}
return words.join(" ").trim();
}
function looksLikeJson(value: string): boolean { const trimmed = value.trim(); return trimmed.startsWith("{") || trimmed.startsWith("["); }
export async function readJsonInput(value: string | undefined): Promise<unknown | undefined> {
if (value === undefined) return undefined;
try { return JSON.parse(looksLikeJson(value) ? value : await readFile(value, "utf8")); }
catch (error) {
const message = error instanceof Error ? error.message : "unknown error";
throw new UlwLoopError(`Invalid JSON input: ${message}`, "ULW_LOOP_JSON_INPUT_INVALID", { cause: error });
}
}
export async function parseCodexGoalJson(value: string | undefined): Promise<string | undefined> {
if (value === undefined) return undefined;
const raw = looksLikeJson(value) ? value : await readFile(value, "utf8");
try { JSON.parse(raw); return raw; }
catch (error) {
const message = error instanceof Error ? error.message : "unknown error";
throw new UlwLoopError(`Invalid --codex-goal-json: ${message}`, "ULW_LOOP_CODEX_GOAL_JSON_INVALID", { cause: error });
}
}
function required(argv: readonly string[], flag: string, code: string): string {
const value = readValue(argv, flag)?.trim();
if (value) return value;
throw new UlwLoopError(`Missing ${flag}.`, code, { details: { flag } });
}
function evidenceStatus(value: string): RecordEvidenceCliArgs["status"] {
switch (value) {
case "pass": return "pass";
case "fail": return "fail";
case "blocked": return "blocked";
default: throw new UlwLoopError("Invalid --status; expected pass, fail, or blocked.", "ULW_LOOP_EVIDENCE_STATUS_INVALID", { details: { status: value } });
}
}
export function parseRecordEvidenceArgs(argv: readonly string[]): RecordEvidenceCliArgs {
const result = { goalId: required(argv, "--goal-id", "ULW_LOOP_GOAL_ID_REQUIRED"), criterionId: required(argv, "--criterion-id", "ULW_LOOP_CRITERION_ID_REQUIRED"), status: evidenceStatus(required(argv, "--status", "ULW_LOOP_EVIDENCE_STATUS_REQUIRED")), evidence: required(argv, "--evidence", "ULW_LOOP_EVIDENCE_REQUIRED") };
const notes = readValue(argv, "--notes")?.trim();
return notes ? { ...result, notes } : result;
}
@@ -0,0 +1,156 @@
// biome-ignore-all format: keep cli-commands dispatcher under the 200 pure LOC budget.
import { readFile } from "node:fs/promises";
import { type CheckpointUlwLoopArgs, checkpointUlwLoop } from "./checkpoint.js";
import { hasFlag, parseCodexGoalJson, parseRecordEvidenceArgs, positionalText, readStdin, readValue } from "./cli-arg-parser.js";
import { blockedDecisionHandoff, normalizeCodexGoalMode, printJson, printStatus, ULW_LOOP_HELP } from "./cli-output.js";
import { parseSteeringProposal, printSteerResult } from "./cli-steering.js";
import { buildCodexGoalInstruction } from "./codex-goal-instruction.js";
import { recordEvidence } from "./evidence.js";
import { resolveUlwLoopSessionIdFromEnv, type UlwLoopScope } from "./paths.js";
import { addUlwLoopGoal, createUlwLoopPlan, startNextUlwLoop, summarizeUlwLoopPlan } from "./plan-crud.js";
import { readUlwLoopPlan } from "./plan-io.js";
import { recordFinalReviewBlockers } from "./review-blockers.js";
import { steerUlwLoop } from "./steering.js";
import type { UlwLoopItem } from "./types.js";
import { UlwLoopError } from "./types.js";
type CheckpointStatus = "complete" | "failed" | "blocked";
export async function ulwLoopCommand(argv: readonly string[]): Promise<number> {
const command = argv[0] ?? "help";
const rest = argv.slice(1);
const repoRoot = process.cwd();
const json = hasFlag(rest, "--json");
const scope = commandScope(rest);
try {
switch (command) {
case "help": case "--help": case "-h": process.stdout.write(`${ULW_LOOP_HELP}\n`); return 0;
case "create-goals": return await createGoals(repoRoot, rest, json, scope);
case "status": return await status(repoRoot, json, scope);
case "complete-goals": return await completeGoals(repoRoot, rest, json, scope);
case "checkpoint": return await checkpoint(repoRoot, rest, json, scope);
case "steer": return await steer(repoRoot, rest, json, scope);
case "add-goal": return await addGoal(repoRoot, rest, json, scope);
case "criteria": return await criteria(repoRoot, rest, json, scope);
case "record-evidence": return await captureEvidence(repoRoot, rest, json, scope);
case "record-review-blockers": return await reviewBlockers(repoRoot, rest, json, scope);
default: process.stdout.write(`${ULW_LOOP_HELP}\n`); return 1;
}
} catch (error) {
if (error instanceof UlwLoopError) process.stderr.write(`[ulw-loop] ${error.message}\n`);
else if (error instanceof Error) process.stderr.write(`[ulw-loop] unexpected: ${error.message}\n`);
else process.stderr.write("[ulw-loop] unknown error\n");
return 1;
}
}
function commandScope(argv: readonly string[]): UlwLoopScope | undefined {
const sessionId = readValue(argv, "--session-id") ?? resolveUlwLoopSessionIdFromEnv();
return sessionId === null ? undefined : { sessionId };
}
async function createGoals(repoRoot: string, argv: readonly string[], json: boolean, scope?: UlwLoopScope): Promise<number> {
const briefFile = readValue(argv, "--brief-file");
const brief = readValue(argv, "--brief") ?? (briefFile === undefined ? undefined : await readFile(briefFile, "utf8")) ?? (hasFlag(argv, "--from-stdin") ? await readStdin() : undefined) ?? positionalText(argv);
if (!brief.trim()) throw new UlwLoopError("Missing brief text. Pass --brief, --brief-file, --from-stdin, or positional text.", "ULW_LOOP_BRIEF_REQUIRED");
const plan = await createUlwLoopPlan(repoRoot, { brief, codexGoalMode: normalizeCodexGoalMode(readValue(argv, "--codex-goal-mode")), force: hasFlag(argv, "--force") }, scope);
if (json) printJson({ ok: true, plan, summary: summarizeUlwLoopPlan(plan) });
else process.stdout.write(`ulw-loop plan created: ${plan.goals.length} goal(s)\nbrief: ${plan.briefPath}\ngoals: ${plan.goalsPath}\nledger: ${plan.ledgerPath}\n`);
return 0;
}
async function status(repoRoot: string, json: boolean, scope?: UlwLoopScope): Promise<number> {
const plan = await readUlwLoopPlan(repoRoot, scope);
if (json) printJson({ ok: true, plan, summary: summarizeUlwLoopPlan(plan) });
else printStatus(plan);
return 0;
}
async function completeGoals(repoRoot: string, argv: readonly string[], json: boolean, scope?: UlwLoopScope): Promise<number> {
const result = await startNextUlwLoop(repoRoot, { retryFailed: hasFlag(argv, "--retry-failed") }, scope);
if ("done" in result) {
const handoff = blockedDecisionHandoff(result.plan);
if (json) printJson({ ok: true, done: true, blocked: handoff.length > 0, handoff, summary: summarizeUlwLoopPlan(result.plan), plan: result.plan });
else process.stdout.write(`${handoff || "ulw-loop: all goals complete"}\n`);
return 0;
}
const instruction = buildCodexGoalInstruction({ plan: result.plan, goal: result.goal });
if (json) printJson({ ok: true, resumed: result.resumed, goal: result.goal, instruction, plan: result.plan });
else process.stdout.write(`${instruction.text}\n`);
return 0;
}
async function checkpoint(repoRoot: string, argv: readonly string[], json: boolean, scope?: UlwLoopScope): Promise<number> {
const goalId = required(argv, "--goal-id");
const statusValue = checkpointStatus(required(argv, "--status"));
const evidence = required(argv, "--evidence");
const codexGoalJson = await parseCodexGoalJson(statusValue === "complete" ? required(argv, "--codex-goal-json") : readValue(argv, "--codex-goal-json"));
if (statusValue === "complete" && codexGoalJson === undefined) throw new UlwLoopError("Missing --codex-goal-json.", "ULW_LOOP_CODEX_GOAL_JSON_REQUIRED");
const qualityGateJson = readValue(argv, "--quality-gate-json");
const args: CheckpointUlwLoopArgs = {
goalId,
status: statusValue,
evidence,
...(codexGoalJson === undefined ? {} : { codexGoalJson }),
...(qualityGateJson === undefined ? {} : { qualityGateJson }),
};
const result = await checkpointUlwLoop(repoRoot, args, scope);
if (json) printJson({ ok: true, ...result, summary: summarizeUlwLoopPlan(result.plan) });
else process.stdout.write(`ulw-loop checkpoint: ${result.goal.id} -> ${result.goal.status}\n`);
return 0;
}
async function steer(repoRoot: string, argv: readonly string[], json: boolean, scope?: UlwLoopScope): Promise<number> {
const proposal = await parseSteeringProposal(argv);
const result = await steerUlwLoop(repoRoot, proposal, scope);
printSteerResult(result, json);
return result.accepted ? 0 : 1;
}
async function addGoal(repoRoot: string, argv: readonly string[], json: boolean, scope?: UlwLoopScope): Promise<number> {
const result = await addUlwLoopGoal(repoRoot, { title: required(argv, "--title"), objective: required(argv, "--objective") }, scope);
if (json) printJson({ ok: true, plan: result.plan, goal: result.goal, summary: summarizeUlwLoopPlan(result.plan) });
else { process.stdout.write(`ulw-loop added goal: ${result.goal.id}\n`); printStatus(result.plan); }
return 0;
}
async function criteria(repoRoot: string, argv: readonly string[], json: boolean, scope?: UlwLoopScope): Promise<number> {
const goalId = required(argv, "--goal-id");
const goal = findGoal(await readUlwLoopPlan(repoRoot, scope), goalId);
if (json) printJson({ ok: true, goalId: goal.id, criteria: goal.successCriteria });
else process.stdout.write(`criteria for ${goal.id}:\n${goal.successCriteria.map((c) => `- ${c.id} [${c.status}] (${c.userModel}) ${c.scenario} evidence: ${c.capturedEvidence ?? "pending"}`).join("\n")}\n`);
return 0;
}
async function captureEvidence(repoRoot: string, argv: readonly string[], json: boolean, scope?: UlwLoopScope): Promise<number> {
const result = await recordEvidence(repoRoot, parseRecordEvidenceArgs(argv), scope);
if (json) printJson({ ok: true, ...result, summary: summarizeUlwLoopPlan(result.plan) });
else process.stdout.write(`ulw-loop evidence recorded: ${result.goal.id}/${result.criterion.id} -> ${result.criterion.status}\n`);
return 0;
}
async function reviewBlockers(repoRoot: string, argv: readonly string[], json: boolean, scope?: UlwLoopScope): Promise<number> {
const codexGoalJson = await parseCodexGoalJson(required(argv, "--codex-goal-json"));
if (codexGoalJson === undefined) throw new UlwLoopError("Missing --codex-goal-json.", "ULW_LOOP_CODEX_GOAL_JSON_REQUIRED");
const result = await recordFinalReviewBlockers(repoRoot, { goalId: required(argv, "--goal-id"), title: required(argv, "--title"), objective: required(argv, "--objective"), evidence: required(argv, "--evidence"), codexGoalJson }, scope);
if (json) printJson({ ok: true, plan: result.plan, blockedGoal: result.blockedGoal, goal: result.newGoal, ledgerEntries: result.ledgerEntries, summary: summarizeUlwLoopPlan(result.plan) });
else process.stdout.write(`ulw-loop final review blockers recorded: ${result.blockedGoal.id} -> review_blocked; added ${result.newGoal.id}\n`);
return 0;
}
function required(argv: readonly string[], flag: string): string {
const value = readValue(argv, flag)?.trim();
if (value) return value;
throw new UlwLoopError(`Missing ${flag}.`, "ULW_LOOP_ARGUMENT_MISSING", { details: { flag } });
}
function checkpointStatus(value: string): CheckpointStatus {
if (value === "complete" || value === "failed" || value === "blocked") return value;
throw new UlwLoopError("Missing or invalid --status; expected complete, failed, or blocked.", "ULW_LOOP_STATUS_INVALID", { details: { status: value } });
}
function findGoal(plan: { readonly goals: readonly UlwLoopItem[] }, goalId: string): UlwLoopItem {
const goal = plan.goals.find((candidate) => candidate.id === goalId);
if (goal !== undefined) return goal;
throw new UlwLoopError(`Unknown ulw-loop id: ${goalId}.`, "ULW_LOOP_GOAL_NOT_FOUND", { details: { goalId } });
}
@@ -0,0 +1,63 @@
import type { UlwLoopCodexGoalMode, UlwLoopItem, UlwLoopPlan } from "./types.js";
import { UlwLoopError } from "./types.js";
export const ULW_LOOP_HELP = `Usage:
omo ulw-loop create-goals --brief "..." [--brief-file <path>] [--from-stdin] [--codex-goal-mode aggregate|per_story] [--force] [--json]
omo ulw-loop status [--json]
omo ulw-loop complete-goals [--retry-failed] [--json]
omo ulw-loop criteria --goal-id <id> [--json]
omo ulw-loop record-evidence --goal-id <id> --criterion-id <id> --status pass|fail|blocked --evidence "..." [--notes "..."] [--json]
omo ulw-loop checkpoint --goal-id <id> --status complete|failed|blocked --evidence "..." --codex-goal-json <...> [--quality-gate-json <...>] [--json]
omo ulw-loop steer --kind <kind> ... --evidence "..." --rationale "..." [--json]
omo ulw-loop add-goal --title "..." --objective "..." [--json]
omo ulw-loop record-review-blockers --goal-id <id> --title "..." --objective "..." --evidence "..." --codex-goal-json <...> [--json]
All subcommands accept [--session-id <id>] to isolate state under .omo/ulw-loop/<id>/; without it, Codex session env is used when present.`;
type CriteriaCounts = { readonly pass: number; readonly total: number };
export function printJson(value: unknown): void {
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
}
function criteriaCounts(goal: UlwLoopItem): CriteriaCounts {
let pass = 0;
for (const criterion of goal.successCriteria) if (criterion.status === "pass") pass += 1;
return { pass, total: goal.successCriteria.length };
}
export function printStatus(plan: UlwLoopPlan): void {
let totalCriteria = 0;
let passCriteria = 0;
const lines = ["ulw-loop status", "", "goals:"];
for (const goal of plan.goals) {
const counts = criteriaCounts(goal);
totalCriteria += counts.total;
passCriteria += counts.pass;
const marker = goal.id === plan.activeGoalId ? "*" : "-";
lines.push(`${marker} ${goal.id} [${goal.status}] ${goal.title} (criteria: ${counts.pass}/${counts.total})`);
}
lines.push("", "summary:", `total goals: ${plan.goals.length}`, `criteria: ${passCriteria}/${totalCriteria} pass`);
process.stdout.write(`${lines.join("\n")}\n`);
}
export function blockedDecisionHandoff(plan: UlwLoopPlan): string {
const blocked = plan.goals.find((goal) => goal.status === "needs_user_decision" && goal.nonRetriable);
if (blocked === undefined) return "";
return [
"ulw-loop: blocked on repeated external authorization; no retryable failed goals remain.",
`Goal: ${blocked.id} - ${blocked.title}`,
`Required external decision: ${blocked.requiredExternalDecision ?? "provide the missing authorization or choose a different unblock path"}.`,
"Do not run complete-goals --retry-failed again until external state changes or the user authorizes an unblock path.",
].join("\n");
}
export function normalizeCodexGoalMode(value: string | undefined): UlwLoopCodexGoalMode {
if (value === undefined) return "aggregate";
if (value === "aggregate" || value === "per_story") return value;
throw new UlwLoopError(
"Invalid --codex-goal-mode; expected aggregate or per_story.",
"ULW_LOOP_CODEX_GOAL_MODE_INVALID",
{ details: { value } },
);
}
@@ -0,0 +1,94 @@
// biome-ignore-all format: keep this module under the mandated pure LOC budget.
import { parseGoalArg, readJsonInput, readValue } from "./cli-arg-parser.js";
import { printJson, printStatus } from "./cli-output.js";
import type { SteerUlwLoopResult, UlwLoopSteeringChildGoal, UlwLoopSteeringMutationKind, UlwLoopSteeringProposal, UlwLoopSteeringSource, UlwLoopSuccessCriterionUserModel } from "./types.js";
import { ULW_LOOP_STEERING_MUTATION_KINDS, ULW_LOOP_SUCCESS_CRITERION_USER_MODELS, UlwLoopError } from "./types.js";
const SOURCES = ["user_prompt_submit", "finding", "cli"] as const satisfies readonly UlwLoopSteeringSource[];
export type CliSteeringProposal = UlwLoopSteeringProposal & { readonly goalId?: string; readonly scenario?: string; readonly expectedEvidence?: string; readonly userModel?: UlwLoopSuccessCriterionUserModel };
function isKind(value: string | undefined): value is UlwLoopSteeringMutationKind { return value !== undefined && ULW_LOOP_STEERING_MUTATION_KINDS.some((kind) => kind === value); }
function isSource(value: string | undefined): value is UlwLoopSteeringSource { return value !== undefined && SOURCES.some((source) => source === value); }
function isModel(value: string): value is UlwLoopSuccessCriterionUserModel { return ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((model) => model === value); }
function fail(message: string, code: string, details: Record<string, unknown>): never { throw new UlwLoopError(message, code, { details }); }
function text(value: string | undefined, field: string): string | undefined { if (value === undefined) return undefined; const trimmed = value.trim(); if (trimmed.length > 0) return trimmed; return fail(`Empty ${field}.`, "ULW_LOOP_STEERING_FIELD_EMPTY", { field }); }
function required(argv: readonly string[], flag: string): string { const value = text(readValue(argv, flag), flag); return value ?? fail(`Missing ${flag}.`, "ULW_LOOP_STEERING_FIELD_REQUIRED", { flag }); }
function requiredGoal(argv: readonly string[]): string { const value = text(parseGoalArg(argv), "--goal-id"); return value ?? fail("Missing --goal-id.", "ULW_LOOP_GOAL_ID_REQUIRED", { flag: "--goal-id" }); }
function readObject(value: object, key: string): unknown { return Object.entries(value).find(([name]) => name === key)?.[1]; }
function isPlain(value: unknown): value is object { return typeof value === "object" && value !== null && !Array.isArray(value); }
function objectText(value: object, key: string): string | undefined { const candidate = readObject(value, key); return typeof candidate === "string" ? candidate : undefined; }
export function parseSteeringKind(argv: readonly string[]): UlwLoopSteeringMutationKind {
const value = readValue(argv, "--kind");
if (isKind(value)) return value;
return value === undefined ? fail("Missing --kind.", "ULW_LOOP_STEERING_KIND_REQUIRED", { flag: "--kind" }) : fail(`Invalid --kind: ${value}.`, "ULW_LOOP_STEERING_KIND_INVALID", { value, expected: ULW_LOOP_STEERING_MUTATION_KINDS });
}
export function parseSteeringSource(argv: readonly string[]): UlwLoopSteeringSource {
const value = readValue(argv, "--source");
if (value === undefined) return "cli";
return isSource(value) ? value : fail(`Invalid --source: ${value}.`, "ULW_LOOP_STEERING_SOURCE_INVALID", { value, expected: SOURCES });
}
function child(value: unknown): UlwLoopSteeringChildGoal | null {
if (!isPlain(value)) return null;
const title = text(objectText(value, "title"), "title"); const objective = text(objectText(value, "objective"), "objective");
if (title === undefined || objective === undefined) return null;
return { title, objective };
}
async function children(argv: readonly string[], flag: string, needed: boolean): Promise<UlwLoopSteeringChildGoal[]> {
const input = needed ? required(argv, flag) : text(readValue(argv, flag), flag);
if (input === undefined) return [];
const raw = await readJsonInput(input);
if (!Array.isArray(raw)) return fail(`${flag} must be a JSON array.`, "ULW_LOOP_STEERING_JSON_ARRAY_REQUIRED", { flag });
const parsed: UlwLoopSteeringChildGoal[] = [];
for (const item of raw) { const next = child(item); if (next === null) return fail(`${flag} entries require title/objective.`, "ULW_LOOP_STEERING_CHILD_INVALID", { flag }); parsed.push(next); }
return parsed;
}
async function stringArray(argv: readonly string[], flag: string): Promise<string[]> {
const raw = await readJsonInput(required(argv, flag));
if (!Array.isArray(raw)) return fail(`${flag} must be a JSON array.`, "ULW_LOOP_STEERING_JSON_ARRAY_REQUIRED", { flag });
const values: string[] = [];
for (const item of raw) { if (typeof item !== "string") return fail(`${flag} entries must be strings.`, "ULW_LOOP_STEERING_STRING_ARRAY_REQUIRED", { flag }); values.push(text(item, flag) ?? ""); }
return values;
}
function model(value: string | undefined): UlwLoopSuccessCriterionUserModel | undefined { const trimmed = text(value, "--user-model"); if (trimmed === undefined) return undefined; return isModel(trimmed) ? trimmed : fail(`Invalid --user-model: ${trimmed}.`, "ULW_LOOP_STEERING_USER_MODEL_INVALID", { value: trimmed, expected: ULW_LOOP_SUCCESS_CRITERION_USER_MODELS }); }
function neverKind(kind: never): never { return fail(`Unsupported steering kind: ${String(kind)}.`, "ULW_LOOP_STEERING_KIND_UNSUPPORTED", { kind }); }
export async function parseSteeringProposal(argv: readonly string[]): Promise<CliSteeringProposal> {
const kind = parseSteeringKind(argv); const source = parseSteeringSource(argv); const base = { kind, source, evidence: required(argv, "--evidence"), rationale: required(argv, "--rationale") };
switch (kind) {
case "add_subgoal": return normalizeSteeringProposal({ ...base, title: required(argv, "--title"), objective: required(argv, "--objective") });
case "split_subgoal": { const goalId = requiredGoal(argv); return normalizeSteeringProposal({ ...base, goalId, targetGoalId: goalId, childGoals: await children(argv, "--children", true) }); }
case "reorder_pending": return normalizeSteeringProposal({ ...base, pendingOrder: await stringArray(argv, "--order") });
case "revise_pending_wording": { const goalId = requiredGoal(argv); const revisedTitle = readValue(argv, "--title"); const revisedObjective = readValue(argv, "--objective"); if (revisedTitle === undefined && revisedObjective === undefined) return fail("revise_pending_wording requires --title or --objective.", "ULW_LOOP_STEERING_UPDATE_REQUIRED", { kind }); return normalizeSteeringProposal({ ...base, goalId, targetGoalId: goalId, ...(revisedTitle === undefined ? {} : { revisedTitle }), ...(revisedObjective === undefined ? {} : { revisedObjective }) }); }
case "revise_criterion": { const goalId = requiredGoal(argv); const criterionId = required(argv, "--criterion-id"); const scenario = readValue(argv, "--scenario"); const expectedEvidence = readValue(argv, "--expected-evidence"); const userModel = model(readValue(argv, "--user-model")); if (scenario === undefined && expectedEvidence === undefined && userModel === undefined) return fail("revise_criterion requires scenario, expected-evidence, or user-model.", "ULW_LOOP_STEERING_UPDATE_REQUIRED", { kind }); return normalizeSteeringProposal({ ...base, goalId, targetGoalId: goalId, criterionId, ...(scenario === undefined ? {} : { scenario }), ...(expectedEvidence === undefined ? {} : { expectedEvidence }), ...(userModel === undefined ? {} : { userModel }) }); }
case "annotate_ledger": return normalizeSteeringProposal(base);
case "mark_blocked_superseded": { const goalId = requiredGoal(argv); const childGoals = await children(argv, "--replacements", false); return normalizeSteeringProposal({ ...base, goalId, targetGoalId: goalId, ...(childGoals.length === 0 ? {} : { childGoals }) }); }
default: return neverKind(kind);
}
}
function normalizedChildren(values: readonly UlwLoopSteeringChildGoal[] | undefined): UlwLoopSteeringChildGoal[] | undefined { if (values === undefined) return undefined; return values.map((item) => ({ title: text(item.title, "child.title") ?? "", objective: text(item.objective, "child.objective") ?? "" })); }
function normalizedStrings(values: readonly string[] | undefined, field: string): string[] | undefined { if (values === undefined) return undefined; return values.map((value) => text(value, field) ?? ""); }
export function normalizeSteeringProposal(proposal: CliSteeringProposal): CliSteeringProposal {
const evidence = text(proposal.evidence, "evidence") ?? ""; const rationale = text(proposal.rationale, "rationale") ?? ""; const goalId = text(proposal.goalId, "goalId"); const targetGoalId = text(proposal.targetGoalId, "targetGoalId"); const targetGoalIds = normalizedStrings(proposal.targetGoalIds, "targetGoalIds");
const criterionId = text(proposal.criterionId, "criterionId"); const title = text(proposal.title, "title"); const objective = text(proposal.objective, "objective"); const revisedTitle = text(proposal.revisedTitle, "revisedTitle"); const revisedObjective = text(proposal.revisedObjective, "revisedObjective");
const blockedReason = text(proposal.blockedReason, "blockedReason"); const directiveText = text(proposal.directiveText, "directiveText"); const promptSignature = text(proposal.promptSignature, "promptSignature"); const idempotencyKey = text(proposal.idempotencyKey, "idempotencyKey");
const scenario = text(proposal.scenario, "scenario"); const expectedEvidence = text(proposal.expectedEvidence, "expectedEvidence"); const childGoals = normalizedChildren(proposal.childGoals); const pendingOrder = normalizedStrings(proposal.pendingOrder, "pendingOrder");
return { kind: proposal.kind, source: proposal.source, evidence, rationale, ...(goalId === undefined ? {} : { goalId }), ...(targetGoalId === undefined ? {} : { targetGoalId }), ...(targetGoalIds === undefined ? {} : { targetGoalIds }), ...(criterionId === undefined ? {} : { criterionId }), ...(title === undefined ? {} : { title }), ...(objective === undefined ? {} : { objective }), ...(childGoals === undefined ? {} : { childGoals }), ...(revisedTitle === undefined ? {} : { revisedTitle }), ...(revisedObjective === undefined ? {} : { revisedObjective }), ...(pendingOrder === undefined ? {} : { pendingOrder }), ...(blockedReason === undefined ? {} : { blockedReason }), ...(proposal.after === undefined ? {} : { after: proposal.after }), ...(directiveText === undefined ? {} : { directiveText }), ...(promptSignature === undefined ? {} : { promptSignature }), ...(idempotencyKey === undefined ? {} : { idempotencyKey }), ...(proposal.now === undefined ? {} : { now: proposal.now }), ...(scenario === undefined ? {} : { scenario }), ...(expectedEvidence === undefined ? {} : { expectedEvidence }), ...(proposal.userModel === undefined ? {} : { userModel: proposal.userModel }) };
}
export function printSteerResult(result: SteerUlwLoopResult, json: boolean): void {
if (json) { printJson({ ok: result.accepted, accepted: result.accepted, rejectedReasons: result.rejectedReasons, deduped: result.deduped, audit: result.audit, plan: result.plan }); return; }
const outcome = result.deduped ? "deduped" : result.accepted ? "accepted" : "rejected";
process.stdout.write(`ulw-loop steer: ${outcome} ${result.audit.kind}\n`);
if (result.rejectedReasons.length > 0) process.stdout.write(`rejected: ${result.rejectedReasons.join("; ")}\n`);
if (result.audit.idempotencyKey !== undefined) process.stdout.write(`idempotency-key: ${result.audit.idempotencyKey}\n`);
printStatus(result.plan);
}
@@ -0,0 +1,40 @@
#!/usr/bin/env node
import { ulwLoopCommand } from "./cli-commands.js";
import { runPreToolUseGoalBudgetGuardCli, runUlwLoopHookCli } from "./codex-hook.js";
const TOP_LEVEL_HELP =
"Usage:\n omo ulw-loop <subcommand> [args]\n omo hook user-prompt-submit (Codex UserPromptSubmit hook)\n omo help | --help | -h (this message)\n\nRun `omo ulw-loop help` for ulw-loop subcommands.\n";
async function main(): Promise<number> {
const argv = process.argv.slice(2);
const command = argv[0];
if (command === undefined || command === "help" || command === "--help" || command === "-h") {
process.stdout.write(TOP_LEVEL_HELP);
return 0;
}
if (command === "ulw-loop") return ulwLoopCommand(argv.slice(1));
if (command === "hook") {
const sub = argv[1];
if (sub === "user-prompt-submit") {
await runUlwLoopHookCli(process.stdin, process.stdout);
return 0;
}
if (sub === "pre-tool-use") {
await runPreToolUseGoalBudgetGuardCli(process.stdin, process.stdout);
return 0;
}
process.stderr.write(`[omo] unknown hook subcommand: ${sub ?? "(none)"}\n`);
return 1;
}
process.stderr.write(`[omo] unknown command: ${command}\n${TOP_LEVEL_HELP}`);
return 1;
}
main()
.then((code) => {
process.exit(code);
})
.catch((error: unknown) => {
process.stderr.write(`[omo] ${error instanceof Error ? error.message : String(error)}\n`);
process.exit(1);
});
@@ -0,0 +1,129 @@
import { codexGoalMode, expectedCodexObjective, isFinalRunCompletionCandidate } from "./goal-status.js";
import type { UlwLoopCodexGoalMode, UlwLoopItem, UlwLoopPlan, UlwLoopSuccessCriterion } from "./types.js";
export interface CodexCreateGoalPayload {
readonly objective: string;
readonly status: "active";
}
export interface UlwLoopGoalInstruction {
readonly text: string;
readonly json: CodexCreateGoalPayload;
}
export function buildCodexGoalInstruction(args: {
readonly plan: UlwLoopPlan;
readonly goal: UlwLoopItem;
readonly isFinal?: boolean;
}): UlwLoopGoalInstruction {
const mode = codexGoalMode(args.plan);
const createGoal = buildCreateGoalPayload(args.plan, args.goal);
const isFinal = args.isFinal ?? isFinalRunCompletionCandidate(args.plan, args.goal);
return { text: buildText(mode, args.plan, args.goal, createGoal, isFinal), json: createGoal };
}
function buildCreateGoalPayload(plan: UlwLoopPlan, goal: UlwLoopItem): CodexCreateGoalPayload {
return { objective: expectedCodexObjective(plan, goal), status: "active" };
}
function buildText(
mode: UlwLoopCodexGoalMode,
plan: UlwLoopPlan,
goal: UlwLoopItem,
createGoal: CodexCreateGoalPayload,
isFinal: boolean,
): string {
return joinLines([
mode === "aggregate" ? "UlwLoop aggregate-goal handoff" : "UlwLoop active-goal handoff",
`Mode: ${mode}`,
`Plan: ${plan.goalsPath}`,
`Ledger: ${plan.ledgerPath}`,
`Goal: ${goal.id}${goal.title}`,
"",
...activeGoalLines(goal),
"",
...successCriteriaLines(goal.successCriteria),
"",
"Codex goal integration constraints:",
"- Use the create_goal payload exactly as rendered: objective and status only.",
"- Goals are unlimited. Do not add numeric limits.",
...modeConstraintLines(mode, isFinal),
finalSection(plan, goal, isFinal, mode === "aggregate"),
...checkpointLines(plan, mode),
"",
"create_goal payload:",
JSON.stringify(createGoal, null, 2),
]);
}
function modeConstraintLines(mode: UlwLoopCodexGoalMode, isFinal: boolean): readonly string[] {
if (mode === "per_story") {
return [
"- First call get_goal. If no active goal exists, call create_goal with the payload below.",
"- If a different active Codex goal exists, finish/checkpoint that goal before starting this ulw-loop.",
"- Work only this goal until its completion audit passes.",
];
}
return [
"- Codex goal = the whole omo ulw-loop run; OMO G001/G002/etc. = ledger stories.",
"- First call get_goal. If no active goal exists, call create_goal with the aggregate payload below.",
"- If get_goal reports the same aggregate objective as active, continue this OMO story without creating a new Codex goal.",
"- If a different active or incomplete Codex goal exists, finish/checkpoint that goal before starting this ulw-loop.",
isFinal
? "- This is the final story; update_goal is allowed only after the mandatory quality gate passes."
: "- This is not the final story: do not call update_goal yet; the aggregate Codex goal must remain active while later OMO stories remain.",
];
}
function checkpointLines(plan: UlwLoopPlan, mode: UlwLoopCodexGoalMode): readonly string[] {
const failureLine = `- If blocked or failed, checkpoint with --status failed and the failure evidence; rerun complete-goals${sessionOption(plan)} --retry-failed to resume.`;
if (mode === "per_story") return [failureLine];
return [
"- Checkpoint this OMO story with a fresh get_goal snapshot whose objective matches the aggregate payload.",
failureLine,
];
}
function activeGoalLines(goal: UlwLoopItem): readonly string[] {
return ["Active goal:", `- id: ${goal.id}`, `- title: ${goal.title}`, `- objective: ${goal.objective}`];
}
function successCriteriaLines(criteria: readonly UlwLoopSuccessCriterion[]): readonly string[] {
if (criteria.length === 0) return ["Success criteria:", "- No success criteria recorded for this goal."];
return ["Success criteria:", ...criteria.map(formatCriterionLine)];
}
function formatCriterionLine(criterion: UlwLoopSuccessCriterion): string {
const remainingWork = criterion.status === "pending" ? " remaining work:" : "";
return `-${remainingWork} [${criterion.id}] (${criterion.userModel}) ${criterion.scenario} — expect: ${criterion.expectedEvidence} — status: ${criterion.status}`;
}
function finalSection(plan: UlwLoopPlan, goal: UlwLoopItem, isFinal: boolean, aggregate: boolean): string {
if (!isFinal)
return "- This is not the final ulw-loop story; do not run the final ai-slop-cleaner/$code-review gate yet.";
const option = sessionOption(plan);
const blockerCommand = `omo ulw-loop record-review-blockers${option} --goal-id ${goal.id} --title "Resolve final code-review blockers" --objective "<blocker-resolution objective>" --evidence "<review findings>" --codex-goal-json "<active get_goal JSON or path>"`;
const checkpointCommand = `omo ulw-loop checkpoint${option} --goal-id ${goal.id} --status complete --evidence "<tests/files/PR evidence>" --codex-goal-json "<fresh complete get_goal JSON or path>" --quality-gate-json "<quality gate JSON or path>"`;
return joinLines([
"Final story — run mandatory quality gate before update_goal:",
"- Run ai-slop-cleaner on changed files even when it is a no-op, rerun verification, then run $code-review.",
"- If final $code-review is not APPROVE with architect status CLEAR, do not call update_goal. Record blocker work first:",
` ${blockerCommand}`,
aggregate
? '- If final $code-review is clean, call update_goal({status: "complete"}), call get_goal again, then checkpoint the aggregate story:'
: '- If final $code-review is clean, call update_goal({status: "complete"}), call get_goal again, then checkpoint:',
` ${checkpointCommand}`,
]);
}
function sessionOption(plan: UlwLoopPlan): string {
const prefix = ".omo/ulw-loop/";
const suffix = "/goals.json";
if (!plan.goalsPath.startsWith(prefix) || !plan.goalsPath.endsWith(suffix)) return "";
const sessionId = plan.goalsPath.slice(prefix.length, -suffix.length);
return sessionId.length === 0 ? "" : ` --session-id ${sessionId}`;
}
function joinLines(lines: readonly string[]): string {
return lines.join("\n");
}
@@ -0,0 +1,139 @@
import { existsSync } from "node:fs";
import { readFile } from "node:fs/promises";
import { resolve } from "node:path";
export type CodexGoalSnapshotStatus = "active" | "complete" | "cancelled" | "failed" | "unknown";
export interface CodexGoalSnapshot {
available: boolean;
objective?: string;
status?: CodexGoalSnapshotStatus;
raw: unknown;
}
export interface CodexGoalReconciliation {
ok: boolean;
snapshot: CodexGoalSnapshot;
warnings: string[];
errors: string[];
}
export interface ReconcileCodexGoalOptions {
expectedObjective: string;
acceptedObjectives?: readonly string[];
allowedStatuses?: readonly CodexGoalSnapshotStatus[];
requireSnapshot?: boolean;
requireComplete?: boolean;
}
export class CodexGoalSnapshotError extends Error {}
function safeObject(value: unknown): Record<string, unknown> {
return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : {};
}
function safeString(value: unknown): string {
return typeof value === "string" ? value.trim() : "";
}
function normalizeStatus(value: unknown): CodexGoalSnapshotStatus {
const status = safeString(value).toLowerCase();
if (status === "complete" || status === "completed" || status === "done") return "complete";
if (status === "cancelled" || status === "canceled") return "cancelled";
if (status === "failed" || status === "failure") return "failed";
if (status === "active" || status === "in_progress" || status === "pending" || status === "running") return "active";
return "unknown";
}
function normalizeObjective(value: string): string {
return value.replace(/\s+/g, " ").trim();
}
export function parseCodexGoalSnapshot(value: unknown): CodexGoalSnapshot {
const root = safeObject(value);
const goalValue = Object.hasOwn(root, "goal") ? root["goal"] : value;
if (goalValue === null || goalValue === undefined || goalValue === false) {
return { available: false, raw: value };
}
const goal = safeObject(goalValue);
const objective = safeString(goal["objective"] ?? goal["goal"] ?? goal["description"] ?? root["objective"]);
const status = normalizeStatus(goal["status"] ?? root["status"]);
return {
available: Boolean(objective || status !== "unknown"),
...(objective ? { objective } : {}),
status,
raw: value,
};
}
export async function readCodexGoalSnapshotInput(
raw: string | undefined,
cwd = process.cwd(),
): Promise<CodexGoalSnapshot | null> {
if (!raw?.trim()) return null;
const trimmed = raw.trim();
try {
return parseCodexGoalSnapshot(JSON.parse(trimmed));
} catch {
const path = resolve(cwd, trimmed);
if (!existsSync(path)) {
throw new CodexGoalSnapshotError(`Codex goal snapshot is neither valid JSON nor a readable path: ${trimmed}`);
}
try {
return parseCodexGoalSnapshot(JSON.parse(await readFile(path, "utf-8")));
} catch (error) {
throw new CodexGoalSnapshotError(
`Codex goal snapshot path does not contain valid JSON: ${trimmed}${error instanceof Error ? ` (${error.message})` : ""}`,
);
}
}
}
export function reconcileCodexGoalSnapshot(
snapshot: CodexGoalSnapshot | null | undefined,
options: ReconcileCodexGoalOptions,
): CodexGoalReconciliation {
const effectiveSnapshot = snapshot ?? { available: false, raw: null };
const errors: string[] = [];
const warnings: string[] = [];
if (!effectiveSnapshot.available) {
const message =
"Codex goal snapshot is absent or reports no active goal; call get_goal and pass its JSON with --codex-goal-json.";
if (options.requireSnapshot) errors.push(message);
else warnings.push(message);
return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
}
const expected = normalizeObjective(options.expectedObjective);
const accepted = new Set(
[expected, ...(options.acceptedObjectives ?? []).map((objective) => normalizeObjective(objective))].filter(
Boolean,
),
);
const actual = normalizeObjective(effectiveSnapshot.objective ?? "");
if (!actual) {
errors.push("Codex goal snapshot is missing objective text.");
} else if (!accepted.has(actual)) {
errors.push(`Codex goal objective mismatch: expected "${expected}", got "${actual}".`);
}
const allowed = options.allowedStatuses ?? (options.requireComplete ? ["complete"] : ["active", "complete"]);
const actualStatus = effectiveSnapshot.status ?? "unknown";
if (!allowed.includes(actualStatus)) {
errors.push(`Codex goal status mismatch: expected ${allowed.join(" or ")}, got ${actualStatus}.`);
}
if (options.requireComplete && actualStatus !== "complete") {
errors.push(
'Codex goal is not complete; call update_goal({status: "complete"}) only after the objective is actually complete, then pass the fresh get_goal JSON.',
);
}
return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
}
export function formatCodexGoalReconciliation(reconciliation: CodexGoalReconciliation): string {
const parts = [...reconciliation.errors, ...reconciliation.warnings];
return parts.join(" ");
}
@@ -0,0 +1,177 @@
import type { UlwLoopScope } from "./paths.js";
import { parseUlwLoopSteeringDirective, steerUlwLoop } from "./steering.js";
export interface UserPromptSubmitPayload {
readonly cwd: string;
readonly hook_event_name: "UserPromptSubmit";
readonly model?: string;
readonly permission_mode?: string;
readonly prompt: string;
readonly session_id: string;
readonly transcript_path?: string;
readonly turn_id?: string;
}
export interface PreToolUsePayload {
readonly cwd: string;
readonly hook_event_name: "PreToolUse";
readonly model: string;
readonly permission_mode: string;
readonly session_id: string;
readonly tool_input: unknown;
readonly tool_name: string;
readonly tool_use_id: string;
readonly transcript_path: string | null;
readonly turn_id: string;
}
interface PreToolUseHookOutput {
readonly hookSpecificOutput: {
readonly hookEventName: "PreToolUse";
readonly permissionDecision: "deny";
readonly permissionDecisionReason: string;
readonly additionalContext: string;
};
}
const CREATE_GOAL_TOOL_NAME = "create_goal";
const GOAL_BUDGET_WARNING =
"Do not set token_budget on create_goal. Omit the budget field so the goal stays unlimited; ultrawork and ulw-loop runs must always use unlimited goals.";
export function parseUserPromptSubmitPayload(raw: string): UserPromptSubmitPayload | null {
if (raw.trim().length === 0) return null;
try {
const parsed: unknown = JSON.parse(raw);
return isUserPromptSubmitPayload(parsed) ? parsed : null;
} catch (error) {
if (error instanceof SyntaxError) return null;
return null;
}
}
export function parsePreToolUsePayload(raw: string): PreToolUsePayload | null {
if (raw.trim().length === 0) return null;
try {
const parsed: unknown = JSON.parse(raw);
return isPreToolUsePayload(parsed) ? parsed : null;
} catch (error) {
if (error instanceof SyntaxError) return null;
return null;
}
}
export async function applyUserPromptUlwLoopSteering(payload: UserPromptSubmitPayload): Promise<string> {
try {
if (payload.hook_event_name !== "UserPromptSubmit") return "";
const proposal = parseUlwLoopSteeringDirective(payload.prompt);
if (proposal === null) return "";
const result = await steerUlwLoop(payload.cwd, proposal, payloadScope(payload));
if (!result.accepted) return "";
return JSON.stringify({
status: "accepted",
kind: result.audit.kind,
source: result.audit.source,
deduped: result.deduped,
});
} catch (error) {
if (error instanceof Error) return "";
return "";
}
}
function payloadScope(payload: UserPromptSubmitPayload): UlwLoopScope {
return { sessionId: payload.session_id };
}
export function applyPreToolUseGoalBudgetGuard(payload: PreToolUsePayload): string {
if (payload.hook_event_name !== "PreToolUse") return "";
if (payload.tool_name !== CREATE_GOAL_TOOL_NAME) return "";
if (!hasGoalBudgetInput(payload.tool_input)) return "";
const output: PreToolUseHookOutput = {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: GOAL_BUDGET_WARNING,
additionalContext: GOAL_BUDGET_WARNING,
},
};
return `${JSON.stringify(output)}\n`;
}
export async function runUlwLoopHookCli(stdin: NodeJS.ReadableStream, stdout: NodeJS.WritableStream): Promise<void> {
try {
const payload = parseUserPromptSubmitPayload(await readAll(stdin));
if (payload === null) return;
const output = await applyUserPromptUlwLoopSteering(payload);
if (output.length > 0) stdout.write(output);
} catch (error) {
if (error instanceof Error) return;
return;
}
}
export async function runPreToolUseGoalBudgetGuardCli(
stdin: NodeJS.ReadableStream,
stdout: NodeJS.WritableStream,
): Promise<void> {
try {
const payload = parsePreToolUsePayload(await readAll(stdin));
if (payload === null) return;
const output = applyPreToolUseGoalBudgetGuard(payload);
if (output.length > 0) stdout.write(output);
} catch (error) {
if (error instanceof Error) return;
return;
}
}
function isUserPromptSubmitPayload(value: unknown): value is UserPromptSubmitPayload {
if (!isRecord(value)) return false;
return (
value["hook_event_name"] === "UserPromptSubmit" &&
typeof value["cwd"] === "string" &&
typeof value["prompt"] === "string" &&
typeof value["session_id"] === "string" &&
["model", "permission_mode", "transcript_path", "turn_id"].every((key) => optionalString(value[key]))
);
}
function isPreToolUsePayload(value: unknown): value is PreToolUsePayload {
if (!isRecord(value)) return false;
return (
value["hook_event_name"] === "PreToolUse" &&
typeof value["cwd"] === "string" &&
typeof value["model"] === "string" &&
typeof value["permission_mode"] === "string" &&
typeof value["session_id"] === "string" &&
typeof value["tool_name"] === "string" &&
typeof value["tool_use_id"] === "string" &&
(value["transcript_path"] === null || typeof value["transcript_path"] === "string") &&
typeof value["turn_id"] === "string" &&
Object.hasOwn(value, "tool_input")
);
}
function hasGoalBudgetInput(value: unknown): boolean {
return isRecord(value) && (Object.hasOwn(value, "token_budget") || Object.hasOwn(value, "tokenBudget"));
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function optionalString(value: unknown): boolean {
return value === undefined || typeof value === "string";
}
function readAll(stdin: NodeJS.ReadableStream): Promise<string> {
return new Promise((resolve, reject) => {
let data = "";
stdin.setEncoding("utf8");
stdin.on("data", (chunk: unknown) => {
data += chunk instanceof Buffer ? chunk.toString() : String(chunk);
});
stdin.once("error", reject);
stdin.once("end", () => resolve(data));
});
}
@@ -0,0 +1,122 @@
// biome-ignore-all format: keep this module under the mandated pure LOC budget.
import { hasAllCriteriaPass } from "./goal-status.js";
import type { UlwLoopScope } from "./paths.js";
import { appendLedger, readUlwLoopPlan, withUlwLoopMutationLock, writePlan } from "./plan-io.js";
import type { UlwLoopItem, UlwLoopLedgerEntry, UlwLoopPlan, UlwLoopSuccessCriterion } from "./types.js";
import { iso, UlwLoopError } from "./types.js";
type EvidenceStatus = "pass" | "fail" | "blocked";
type RecordEvidenceArgs = { readonly goalId: string; readonly criterionId: string; readonly status: EvidenceStatus; readonly evidence: string; readonly notes?: string };
function ulwLoopFail(message: string, code: string, details: Record<string, unknown>): never { throw new UlwLoopError(message, code, { details }); }
function ledgerKind(status: EvidenceStatus): UlwLoopLedgerEntry["kind"] {
switch (status) {
case "pass":
return "evidence_captured";
case "fail":
return "criterion_failed";
case "blocked":
return "criterion_blocked";
default:
return ulwLoopFail("Invalid criterion status.", "ULW_LOOP_CRITERION_STATUS_INVALID", { status });
}
}
function findGoal(plan: UlwLoopPlan, goalId: string): UlwLoopItem {
const goal = plan.goals.find((candidate) => candidate.id === goalId);
return goal ?? ulwLoopFail(`UlwLoop goal not found: ${goalId}.`, "ULW_LOOP_GOAL_NOT_FOUND", { goalId });
}
function findCriterion(goal: UlwLoopItem, criterionId: string): UlwLoopSuccessCriterion {
const criterion = goal.successCriteria.find((candidate) => candidate.id === criterionId);
return criterion ?? ulwLoopFail(`Success criterion not found: ${criterionId}.`, "ULW_LOOP_CRITERION_NOT_FOUND", { goalId: goal.id, criterionId });
}
function nonEmptyEvidence(evidence: string): string { const trimmed = evidence.trim(); return trimmed || ulwLoopFail("Evidence must be a non-empty string.", "ULW_LOOP_EVIDENCE_REQUIRED", {}); }
export async function recordEvidence(repoRoot: string, args: RecordEvidenceArgs, scope?: UlwLoopScope): Promise<{ plan: UlwLoopPlan; goal: UlwLoopItem; criterion: UlwLoopSuccessCriterion; ledgerEntry: UlwLoopLedgerEntry }> {
return withUlwLoopMutationLock(repoRoot, scope, async () => {
const plan = await readUlwLoopPlan(repoRoot, scope);
const goal = findGoal(plan, args.goalId);
const criterion = findCriterion(goal, args.criterionId);
const evidence = nonEmptyEvidence(args.evidence);
const kind = ledgerKind(args.status);
const prevStatus = criterion.status;
const capturedAt = iso();
criterion.status = args.status;
criterion.capturedEvidence = evidence;
criterion.capturedAt = capturedAt;
if (args.notes !== undefined) criterion.notes = args.notes;
goal.updatedAt = capturedAt;
plan.updatedAt = capturedAt;
await writePlan(repoRoot, plan, scope);
const ledgerEntry: UlwLoopLedgerEntry = {
at: capturedAt,
kind,
goalId: goal.id,
criterionId: criterion.id,
criterionStatus: args.status,
evidence,
capturedEvidence: evidence,
before: { status: prevStatus },
after: { goalId: goal.id, criterionId: criterion.id, status: args.status, evidence, capturedAt, prevStatus },
};
await appendLedger(repoRoot, ledgerEntry, scope);
return { plan, goal, criterion, ledgerEntry };
});
}
export async function markCriteriaPendingResetForGoal(repoRoot: string, goalId: string, scope?: UlwLoopScope): Promise<{ plan: UlwLoopPlan; resetCount: number }> {
return withUlwLoopMutationLock(repoRoot, scope, async () => {
const plan = await readUlwLoopPlan(repoRoot, scope);
const goal = findGoal(plan, goalId);
const now = iso();
const before = goal.successCriteria.map((criterion) => ({ id: criterion.id, status: criterion.status, capturedEvidence: criterion.capturedEvidence, capturedAt: criterion.capturedAt ?? null }));
for (const criterion of goal.successCriteria) {
criterion.status = "pending";
criterion.capturedEvidence = null;
delete criterion.capturedAt;
delete criterion.notes;
}
goal.updatedAt = now;
plan.updatedAt = now;
await writePlan(repoRoot, plan, scope);
await appendLedger(repoRoot, { at: now, kind: "criteria_revised", goalId, message: `Reset ${goal.successCriteria.length} criteria to pending.`, before, after: { resetCount: goal.successCriteria.length } }, scope);
return { plan, resetCount: goal.successCriteria.length };
});
}
export function criteriaSummary(plan: UlwLoopPlan): { totalCriteria: number; passCount: number; pendingCount: number; failCount: number; blockedCount: number; goalsWithUnresolvedCriteria: string[] } {
let totalCriteria = 0;
let passCount = 0;
let pendingCount = 0;
let failCount = 0;
let blockedCount = 0;
const goalsWithUnresolvedCriteria: string[] = [];
for (const goal of plan.goals) {
let unresolved = false;
for (const criterion of goal.successCriteria) {
totalCriteria += 1;
if (criterion.status !== "pass") unresolved = true;
switch (criterion.status) {
case "pass": passCount += 1; break;
case "pending": pendingCount += 1; break;
case "fail": failCount += 1; break;
case "blocked": blockedCount += 1; break;
default: ulwLoopFail("Invalid criterion status.", "ULW_LOOP_CRITERION_STATUS_INVALID", { status: criterion.status });
}
}
if (unresolved) goalsWithUnresolvedCriteria.push(goal.id);
}
return { totalCriteria, passCount, pendingCount, failCount, blockedCount, goalsWithUnresolvedCriteria };
}
export function unresolvedCriteriaOf(goal: UlwLoopItem): UlwLoopSuccessCriterion[] { return goal.successCriteria.filter((criterion) => criterion.status !== "pass"); }
export function requireAllCriteriaPass(goal: UlwLoopItem): void {
if (hasAllCriteriaPass(goal)) return;
throw new UlwLoopError(`Goal ${goal.id} has unresolved success criteria.`, "ulw_loop_criteria_not_all_pass", {
details: { goalId: goal.id, unresolved: unresolvedCriteriaOf(goal).map((criterion) => ({ id: criterion.id, status: criterion.status })) },
});
}
@@ -0,0 +1,88 @@
import { type UlwLoopScope, ulwLoopGoalsRelativePath, ulwLoopLedgerRelativePath } from "./paths.js";
import type {
UlwLoopCodexGoalMode,
UlwLoopItem,
UlwLoopPlan,
UlwLoopStatus,
UlwLoopSuccessCriterion,
} from "./types.js";
export const ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE: string = aggregateCodexObjectiveForScope();
export function aggregateCodexObjectiveForScope(scope?: UlwLoopScope): string {
return `Complete the durable ulw-loop plan in ${ulwLoopGoalsRelativePath(scope)}, including later accepted/appended stories, under the original brief constraints; use ${ulwLoopLedgerRelativePath(scope)} as the audit trail.`;
}
export function codexGoalMode(plan: UlwLoopPlan): UlwLoopCodexGoalMode {
return plan.codexGoalMode ?? "per_story";
}
function isResolvedStatus(status: UlwLoopStatus): boolean {
return status === "complete";
}
function isSupersededResolved(goal: UlwLoopItem, plan: UlwLoopPlan): boolean {
if (goal.steeringStatus !== "superseded") return false;
const replacements = goal.supersededBy ?? [];
if (replacements.length === 0) return false;
return replacements.every((id) => {
const replacement = plan.goals.find((candidate) => candidate.id === id);
return replacement !== undefined && isResolvedStatus(replacement.status);
});
}
function isCompletionBlocking(goal: UlwLoopItem, plan: UlwLoopPlan): boolean {
if (goal.steeringStatus === "superseded") return !isSupersededResolved(goal, plan);
if (goal.steeringStatus === "blocked") return true;
return !isResolvedStatus(goal.status);
}
function isCompletionBlockingForFinalCandidate(
candidate: UlwLoopItem,
finalCandidate: UlwLoopItem,
plan: UlwLoopPlan,
): boolean {
if (candidate.id === finalCandidate.id) return false;
if (candidate.steeringStatus === "superseded") {
const replacements = candidate.supersededBy ?? [];
if (replacements.length === 0) return true;
return !replacements.every((id) => {
if (id === finalCandidate.id) return true;
const replacement = plan.goals.find((goal) => goal.id === id);
return replacement !== undefined && isResolvedStatus(replacement.status);
});
}
return isCompletionBlocking(candidate, plan);
}
export function isUlwLoopDone(plan: UlwLoopPlan): boolean {
if (plan.aggregateCompletion?.status === "complete") return true;
return plan.goals.every((goal) => !isCompletionBlocking(goal, plan));
}
export function isFinalRunCompletionCandidate(plan: UlwLoopPlan, goal: UlwLoopItem): boolean {
return (
isCompletionBlocking(goal, plan) &&
plan.goals.every((candidate) => !isCompletionBlockingForFinalCandidate(candidate, goal, plan))
);
}
export function aggregateCodexObjective(plan: UlwLoopPlan): string {
return plan.codexObjective ?? ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE;
}
export function expectedCodexObjective(plan: UlwLoopPlan, goal: UlwLoopItem): string {
return codexGoalMode(plan) === "aggregate" ? aggregateCodexObjective(plan) : goal.objective;
}
export function compatibleCodexObjectives(plan: UlwLoopPlan): readonly string[] {
return [aggregateCodexObjective(plan), ...(plan.codexObjectiveAliases ?? [])];
}
export function hasAllCriteriaPass(goal: UlwLoopItem): boolean {
return goal.successCriteria.length > 0 && goal.successCriteria.every((criterion) => criterion.status === "pass");
}
export function firstUnresolvedCriterion(goal: UlwLoopItem): UlwLoopSuccessCriterion | undefined {
return goal.successCriteria.find((criterion) => criterion.status !== "pass");
}
@@ -0,0 +1,73 @@
import { join } from "node:path";
import { ULW_LOOP_BRIEF, ULW_LOOP_DIR, ULW_LOOP_GOALS, ULW_LOOP_LEDGER } from "./types.js";
export interface UlwLoopScope {
readonly sessionId?: string | null;
}
const SESSION_ENV_KEYS = ["OMO_ULW_LOOP_SESSION_ID", "CODEX_SESSION_ID", "CODEX_THREAD_ID"] as const;
type EnvMap = Readonly<Record<string, string | undefined>>;
export function normalizeUlwLoopSessionId(sessionId: string | null | undefined): string | null {
const trimmed = sessionId?.trim();
if (!trimmed) return null;
const pathSegments = trimmed
.split(/[\\/]+/)
.filter((segment) => segment.length > 0 && segment !== "." && segment !== "..");
const candidate = (pathSegments.length > 0 ? pathSegments.join("-") : trimmed)
.replace(/[^A-Za-z0-9._-]+/g, "-")
.replace(/-+/g, "-")
.replace(/^\.+/, "")
.replace(/^[.-]+|[.-]+$/g, "");
return candidate.length > 0 ? candidate : null;
}
export function resolveUlwLoopSessionIdFromEnv(env: EnvMap = process.env): string | null {
for (const key of SESSION_ENV_KEYS) {
const normalized = normalizeUlwLoopSessionId(env[key]);
if (normalized !== null) return normalized;
}
return null;
}
export function ulwLoopRelativeDir(scope?: UlwLoopScope): string {
const sessionId = normalizeUlwLoopSessionId(scope?.sessionId);
return sessionId === null ? ULW_LOOP_DIR : `${ULW_LOOP_DIR}/${sessionId}`;
}
export function ulwLoopDir(repoRoot: string, scope?: UlwLoopScope): string {
return join(repoRoot, ulwLoopRelativeDir(scope));
}
export function ulwLoopBriefRelativePath(scope?: UlwLoopScope): string {
return `${ulwLoopRelativeDir(scope)}/${ULW_LOOP_BRIEF}`;
}
export function ulwLoopGoalsRelativePath(scope?: UlwLoopScope): string {
return `${ulwLoopRelativeDir(scope)}/${ULW_LOOP_GOALS}`;
}
export function ulwLoopLedgerRelativePath(scope?: UlwLoopScope): string {
return `${ulwLoopRelativeDir(scope)}/${ULW_LOOP_LEDGER}`;
}
export function ulwLoopBriefPath(repoRoot: string, scope?: UlwLoopScope): string {
return join(ulwLoopDir(repoRoot, scope), ULW_LOOP_BRIEF);
}
export function ulwLoopGoalsPath(repoRoot: string, scope?: UlwLoopScope): string {
return join(ulwLoopDir(repoRoot, scope), ULW_LOOP_GOALS);
}
export function ulwLoopLedgerPath(repoRoot: string, scope?: UlwLoopScope): string {
return join(ulwLoopDir(repoRoot, scope), ULW_LOOP_LEDGER);
}
export function repoRelative(absolutePath: string, repoRoot: string): string {
const slashPrefix = `${repoRoot}/`;
const backslashPrefix = `${repoRoot}\\`;
if (absolutePath.startsWith(slashPrefix)) return absolutePath.slice(slashPrefix.length).split("\\").join("/");
if (absolutePath.startsWith(backslashPrefix))
return absolutePath.slice(backslashPrefix.length).split("\\").join("/");
return absolutePath.split("\\").join("/");
}
@@ -0,0 +1,113 @@
// biome-ignore-all format: keep this port under the mandated pure LOC budget.
import { existsSync } from "node:fs";
import { mkdir, writeFile } from "node:fs/promises";
import { aggregateCodexObjectiveForScope } from "./goal-status.js";
import { type UlwLoopScope, ulwLoopBriefPath, ulwLoopBriefRelativePath, ulwLoopDir, ulwLoopGoalsPath, ulwLoopGoalsRelativePath, ulwLoopLedgerPath, ulwLoopLedgerRelativePath } from "./paths.js";
import { appendLedger, readUlwLoopPlan, withUlwLoopMutationLock, writePlan } from "./plan-io.js";
import type { UlwLoopCodexGoalMode, UlwLoopItem, UlwLoopPlan, UlwLoopSuccessCriterion } from "./types.js";
import { iso, UlwLoopError } from "./types.js";
export type UlwLoopPlanSummary = { readonly total: number; readonly pending: number; readonly in_progress: number; readonly complete: number; readonly failed: number; readonly blocked: number; readonly review_blocked: number; readonly needs_user_decision: number; readonly superseded: number; readonly criteria: { readonly total: number; readonly pass: number; readonly pending: number; readonly fail: number; readonly blocked: number } };
function cleanLine(line: string): string { return line.replace(/^\s*(?:[-*+]\s+|\d+[.)]\s+)/, "").trim(); }
function normalizeObjective(value: string): string { return value.replace(/\s+/g, " ").trim(); }
function titleFromObjective(objective: string, fallback: string): string { const firstLine = objective.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? fallback; return firstLine.length > 72 ? `${firstLine.slice(0, 69).trimEnd()}...` : firstLine; }
function normalizeGoalId(title: string, index: number): string { const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 36).replace(/-+$/g, ""); return `G${String(index + 1).padStart(3, "0")}${slug ? `-${slug}` : ""}`; }
function assertNonEmpty(value: string | undefined, label: string): string { const trimmed = value?.trim(); if (!trimmed) throw new UlwLoopError(`Missing ${label}.`, "ULW_LOOP_ARGUMENT_MISSING"); return trimmed; }
function truncateObjective(objective: string): string { return objective.length > 80 ? `${objective.slice(0, 77).trimEnd()}...` : objective; }
export function seedDefaultSuccessCriteria(goalIndex: number, objective: string): UlwLoopSuccessCriterion[] {
const subject = truncateObjective(normalizeObjective(objective) || `Goal ${goalIndex + 1}`);
const rows = [
["C001", "happy", `happy path for: ${subject}`, `Replace via revise_criterion with observable happy-path proof for goal ${goalIndex + 1}.`],
["C002", "edge", "edge case (boundary/empty/malformed)", `Replace via revise_criterion with boundary or malformed-input proof for: ${subject}.`],
["C003", "regression", "regression: adjacent surface still works", `Replace via revise_criterion with regression proof for neighboring behavior after: ${subject}.`],
] as const;
return rows.map(([id, userModel, scenario, expectedEvidence]) => ({ id, scenario, userModel, expectedEvidence, capturedEvidence: null, status: "pending" }));
}
export function deriveGoalCandidates(brief: string): Array<{ title: string; objective: string }> {
const bulletGoals = brief.split(/\r?\n/).map((line) => ({ original: line, cleaned: normalizeObjective(cleanLine(line)) })).filter(({ cleaned }) => cleaned.length > 0 && cleaned.length <= 1200).filter(({ original, cleaned }, index, all) => /^\s*(?:[-*+]\s+|\d+[.)]\s+)/.test(original) && all.findIndex((candidate) => candidate.cleaned === cleaned) === index).map(({ cleaned }) => cleaned);
const paragraphs = brief.split(/\n\s*\n/).map(normalizeObjective).filter((paragraph) => paragraph.length > 0 && !paragraph.startsWith("#"));
const selected = (bulletGoals.length > 0 ? bulletGoals : paragraphs).length > 0 ? (bulletGoals.length > 0 ? bulletGoals : paragraphs) : ["Complete the requested project objective."];
return selected.map((objective, index) => ({ title: titleFromObjective(objective, `Goal ${index + 1}`), objective }));
}
function makeGoal(title: string, objective: string, index: number, now: string): UlwLoopItem {
const cleanTitle = assertNonEmpty(title, "title");
const cleanObjective = assertNonEmpty(objective, "objective");
return { id: normalizeGoalId(cleanTitle, index), title: cleanTitle, objective: cleanObjective, status: "pending", successCriteria: seedDefaultSuccessCriteria(index, cleanObjective), attempt: 0, createdAt: now, updatedAt: now };
}
function appendGoalToPlan(plan: UlwLoopPlan, title: string, objective: string, now: string): UlwLoopItem {
const goal = makeGoal(title, objective, plan.goals.length, now);
plan.goals.push(goal);
plan.updatedAt = now;
return goal;
}
function isScheduleEligible(goal: UlwLoopItem): boolean { return goal.steeringStatus !== "superseded" && goal.steeringStatus !== "blocked"; }
function clearGoalBlockerFields(goal: UlwLoopItem): void {
for (const key of ["blockedReason", "blockerSignature", "blockerOccurrenceCount", "requiredExternalDecision", "nonRetriable", "failedAt", "failureReason"] as const) delete goal[key];
}
export async function createUlwLoopPlan(repoRoot: string, args: { brief: string; codexGoalMode?: UlwLoopCodexGoalMode; force?: boolean }, scope?: UlwLoopScope): Promise<UlwLoopPlan> {
return withUlwLoopMutationLock(repoRoot, scope, async () => {
if (!args.force && existsSync(ulwLoopGoalsPath(repoRoot, scope))) throw new UlwLoopError(`Refusing to overwrite existing ${ulwLoopGoalsRelativePath(scope)}; pass --force to recreate it.`, "ULW_LOOP_PLAN_EXISTS");
const now = iso();
const goals = deriveGoalCandidates(args.brief).map((goal, index) => makeGoal(goal.title, goal.objective, index, now));
const plan: UlwLoopPlan = { version: 1, createdAt: now, updatedAt: now, briefPath: ulwLoopBriefRelativePath(scope), goalsPath: ulwLoopGoalsRelativePath(scope), ledgerPath: ulwLoopLedgerRelativePath(scope), codexGoalMode: args.codexGoalMode ?? "aggregate", goals };
if (plan.codexGoalMode === "aggregate") plan.codexObjective = aggregateCodexObjectiveForScope(scope);
await mkdir(ulwLoopDir(repoRoot, scope), { recursive: true });
await writeFile(ulwLoopBriefPath(repoRoot, scope), args.brief.endsWith("\n") ? args.brief : `${args.brief}\n`, "utf8");
await writePlan(repoRoot, plan, scope);
await writeFile(ulwLoopLedgerPath(repoRoot, scope), "", "utf8");
await appendLedger(repoRoot, { at: now, kind: "plan_created", message: `${goals.length} goal(s) created` }, scope);
return plan;
});
}
export async function addUlwLoopGoal(repoRoot: string, args: { title: string; objective: string }, scope?: UlwLoopScope): Promise<{ plan: UlwLoopPlan; goal: UlwLoopItem }> {
return withUlwLoopMutationLock(repoRoot, scope, async () => {
const plan = await readUlwLoopPlan(repoRoot, scope);
const now = iso();
const goal = appendGoalToPlan(plan, args.title, args.objective, now);
await writePlan(repoRoot, plan, scope);
await appendLedger(repoRoot, { at: now, kind: "goal_added", goalId: goal.id, status: goal.status, message: goal.title }, scope);
return { plan, goal };
});
}
export async function startNextUlwLoop(repoRoot: string, args: { retryFailed?: boolean } = {}, scope?: UlwLoopScope): Promise<{ plan: UlwLoopPlan; goal: UlwLoopItem; resumed: boolean } | { done: true; plan: UlwLoopPlan }> {
return withUlwLoopMutationLock(repoRoot, scope, async () => {
const plan = await readUlwLoopPlan(repoRoot, scope);
const now = iso();
if (plan.aggregateCompletion?.status === "complete") return { done: true, plan };
const existing = plan.goals.find((goal) => goal.status === "in_progress" && isScheduleEligible(goal));
if (existing) { await appendLedger(repoRoot, { at: now, kind: "goal_resumed", goalId: existing.id, status: existing.status, message: "Resuming active ulw-loop" }, scope); return { plan, goal: existing, resumed: true }; }
let next = plan.goals.find((goal) => goal.status === "pending" && isScheduleEligible(goal));
if (!next && args.retryFailed) {
next = plan.goals.find((goal) => goal.status === "failed" && !goal.nonRetriable && isScheduleEligible(goal));
if (next) await appendLedger(repoRoot, { at: now, kind: "goal_retried", goalId: next.id, status: "pending", ...(next.failureReason ? { message: next.failureReason } : {}) }, scope);
}
if (!next) return { done: true, plan };
next.status = "in_progress";
next.attempt += 1;
next.startedAt = now;
clearGoalBlockerFields(next);
next.updatedAt = now;
plan.activeGoalId = next.id;
plan.updatedAt = now;
await writePlan(repoRoot, plan, scope);
await appendLedger(repoRoot, { at: now, kind: "goal_started", goalId: next.id, status: next.status, message: `Attempt ${next.attempt}` }, scope);
return { plan, goal: next, resumed: false };
});
}
export function summarizeUlwLoopPlan(plan: UlwLoopPlan): UlwLoopPlanSummary {
const countStatus = (status: UlwLoopItem["status"]): number => plan.goals.filter((goal) => goal.status === status).length;
const countCriteria = (status: UlwLoopSuccessCriterion["status"]): number => plan.goals.reduce((sum, goal) => sum + goal.successCriteria.filter((criterion) => criterion.status === status).length, 0);
return { total: plan.goals.length, pending: countStatus("pending"), in_progress: countStatus("in_progress"), complete: countStatus("complete"), failed: countStatus("failed"), blocked: countStatus("blocked"), review_blocked: countStatus("review_blocked"), needs_user_decision: countStatus("needs_user_decision"), superseded: plan.goals.filter((goal) => goal.steeringStatus === "superseded").length, criteria: { total: plan.goals.reduce((sum, goal) => sum + goal.successCriteria.length, 0), pass: countCriteria("pass"), pending: countCriteria("pending"), fail: countCriteria("fail"), blocked: countCriteria("blocked") } };
}
@@ -0,0 +1,124 @@
import { appendFile, mkdir, readFile, rename, writeFile } from "node:fs/promises";
import { aggregateCodexObjectiveForScope } from "./goal-status.js";
import {
repoRelative,
type UlwLoopScope,
ulwLoopDir,
ulwLoopGoalsPath,
ulwLoopLedgerPath,
ulwLoopRelativeDir,
} from "./paths.js";
import type { UlwLoopLedgerEntry, UlwLoopPlan } from "./types.js";
import { iso, ULW_LOOP_DIR, ULW_LOOP_GOALS, ULW_LOOP_LEDGER, UlwLoopError } from "./types.js";
const LEGACY_OBJECTIVE_PREFIX = `Complete all ulw-loop stories in ${ULW_LOOP_DIR}/${ULW_LOOP_GOALS}: `;
const LEGACY_OBJECTIVE = `Complete all ulw-loop stories listed in ${ULW_LOOP_DIR}/${ULW_LOOP_GOALS}. Use ${ULW_LOOP_DIR}/${ULW_LOOP_LEDGER} as the durable audit trail.`;
const locks = new Map<string, Promise<unknown>>();
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 UlwLoopLedgerEntry["kind"] {
return value === "steering_accepted" || value === "steering_rejected" || value === "criteria_revised";
}
export async function withUlwLoopMutationLock<T>(repoRoot: string, fn: () => Promise<T>): Promise<T>;
export async function withUlwLoopMutationLock<T>(
repoRoot: string,
scope: UlwLoopScope | undefined,
fn: () => Promise<T>,
): Promise<T>;
export async function withUlwLoopMutationLock<T>(
repoRoot: string,
scopeOrFn: UlwLoopScope | (() => Promise<T>) | undefined,
maybeFn?: () => Promise<T>,
): Promise<T> {
const scope = typeof scopeOrFn === "function" ? undefined : scopeOrFn;
const fn = typeof scopeOrFn === "function" ? scopeOrFn : maybeFn;
if (fn === undefined) throw new UlwLoopError("Missing ulw-loop mutation body.", "ULW_LOOP_LOCK_BODY_MISSING");
const lockKey = `${repoRoot}\0${ulwLoopRelativeDir(scope)}`;
const prior = locks.get(lockKey) ?? Promise.resolve();
const run = prior.then(fn, fn);
locks.set(
lockKey,
run.catch(() => undefined),
);
return run;
}
export async function readUlwLoopPlan(repoRoot: string, scope?: UlwLoopScope): Promise<UlwLoopPlan> {
const path = ulwLoopGoalsPath(repoRoot, scope);
let raw: string;
try {
raw = await readFile(path, "utf8");
} catch (error) {
if (!hasCode(error, "ENOENT")) throw error;
throw new UlwLoopError(
`No ulw-loop plan found at ${repoRelative(path, repoRoot)}. Run \`omo ulw-loop create-goals ...\` first.`,
"ULW_LOOP_PLAN_MISSING",
{ cause: error },
);
}
const parsed: UlwLoopPlan = JSON.parse(raw);
if (parsed.version !== 1 || !Array.isArray(parsed.goals)) {
throw new UlwLoopError(`Invalid ulw-loop plan at ${repoRelative(path, repoRoot)}.`, "ULW_LOOP_PLAN_INVALID");
}
const previousObjective = parsed.codexObjective;
if (
(parsed.codexGoalMode ?? "per_story") === "aggregate" &&
isLegacyEnumeratedAggregateObjective(previousObjective)
) {
const now = iso();
parsed.codexObjective = aggregateCodexObjectiveForScope(scope);
parsed.codexObjectiveAliases = [...new Set([...(parsed.codexObjectiveAliases ?? []), previousObjective])];
parsed.updatedAt = now;
await writePlan(repoRoot, parsed, scope);
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 },
},
scope,
);
}
return parsed;
}
export async function writePlan(repoRoot: string, plan: UlwLoopPlan, scope?: UlwLoopScope): Promise<void> {
await mkdir(ulwLoopDir(repoRoot, scope), { recursive: true });
const path = ulwLoopGoalsPath(repoRoot, scope);
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: UlwLoopLedgerEntry, scope?: UlwLoopScope): Promise<void> {
await mkdir(ulwLoopDir(repoRoot, scope), { recursive: true });
await appendFile(ulwLoopLedgerPath(repoRoot, scope), `${JSON.stringify(entry)}\n`, "utf8");
}
export async function readSteeringLedgerEntries(repoRoot: string, scope?: UlwLoopScope): Promise<UlwLoopLedgerEntry[]> {
let raw: string;
try {
raw = await readFile(ulwLoopLedgerPath(repoRoot, scope), "utf8");
} catch (error) {
if (hasCode(error, "ENOENT")) return [];
throw error;
}
const entries: UlwLoopLedgerEntry[] = [];
for (const line of raw.split(/\r?\n/).filter(Boolean)) {
const entry: UlwLoopLedgerEntry = JSON.parse(line);
if (isSteeringKind(entry.kind)) entries.push(entry);
}
return entries;
}
@@ -0,0 +1,102 @@
import type { UlwLoopItem, UlwLoopPlan, UlwLoopQualityGate } from "./types.js";
import { UlwLoopError } from "./types.js";
const BLOCKER_FIELD_KEYS = "blocker blockerSignature blockerEvidence blockerOccurrences blockedAt".split(" ");
const URL_PATTERN = /https?:\/\/\S+/g;
const PUNCTUATION_PATTERN = /[`"'()[\]{}:,;]/g;
const WHITESPACE_PATTERN = /\s+/g;
const AUTH_PATTERN = /\b(auth\w*|credential\w*|token|permission\w*|scope\w*|access|unauthorized|forbidden|401|403)\b/;
const MISSING_PATTERN =
/\b(unset|missing|required|requires|without|omit\w*|not set|not available|no read packages|read packages)\b/;
const GHCR_PATTERN =
/\b(ghcr|github container registry|read packages|imagepullsecret|package api|anonymous|container image)\b/;
const GHCR_401_PATTERN = /\b(401|unauthorized|anonymous pull|authentication required)\b/;
const GHCR_403_PATTERN = /\b(403|forbidden|read packages|package api)\b/;
function invalid(message: string, field: string): never {
throw new UlwLoopError(message, "ULW_LOOP_QUALITY_GATE_INVALID", { details: { field } });
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function section(value: unknown, field: string): Record<string, unknown> {
return isRecord(value) ? value : invalid(`Final quality gate is missing ${field} evidence.`, field);
}
function nonEmptyString(value: unknown, field: string): string {
return typeof value === "string" && value.trim() !== ""
? value
: invalid(`Final quality gate requires non-empty ${field}.`, field);
}
function numberField(value: unknown, field: string): number {
return typeof value === "number" && Number.isFinite(value)
? value
: invalid(`Final quality gate requires numeric ${field}.`, field);
}
function stringArray(value: unknown, field: string): string[] {
if (!Array.isArray(value) || value.length === 0) return invalid(`Final quality gate requires ${field}.`, field);
return value.map((item) => nonEmptyString(item, field));
}
export function validateQualityGate(input: unknown): UlwLoopQualityGate {
const gate = section(input, "qualityGate");
const cleaner = section(gate["aiSlopCleaner"], "aiSlopCleaner");
const verification = section(gate["verification"], "verification");
const review = section(gate["codeReview"], "codeReview");
const coverage = section(gate["criteriaCoverage"], "criteriaCoverage");
if (cleaner["status"] !== "passed") invalid("aiSlopCleaner.status must be passed.", "aiSlopCleaner.status");
if (verification["status"] !== "passed") invalid("verification.status must be passed.", "verification.status");
if (review["recommendation"] !== "APPROVE") invalid("recommendation must be APPROVE.", "codeReview.recommendation");
if (review["architectStatus"] !== "CLEAR") invalid("architectStatus must be CLEAR.", "codeReview.architectStatus");
const totalCriteria = numberField(coverage["totalCriteria"], "criteriaCoverage.totalCriteria");
const passCount = numberField(coverage["passCount"], "criteriaCoverage.passCount");
if (passCount < totalCriteria)
invalid("criteriaCoverage.passCount must cover totalCriteria.", "criteriaCoverage.passCount");
const commands = stringArray(verification["commands"], "verification.commands");
const covered = stringArray(coverage["adversarialClassesCovered"], "criteriaCoverage.adversarialClassesCovered");
const cleanerEvidence = nonEmptyString(cleaner["evidence"], "aiSlopCleaner.evidence");
const verificationEvidence = nonEmptyString(verification["evidence"], "verification.evidence");
const reviewEvidence = nonEmptyString(review["evidence"], "codeReview.evidence");
const result: UlwLoopQualityGate = {
aiSlopCleaner: { status: "passed", evidence: cleanerEvidence },
verification: { status: "passed", commands, evidence: verificationEvidence },
codeReview: { recommendation: "APPROVE", architectStatus: "CLEAR", evidence: reviewEvidence },
};
Object.assign(result, { criteriaCoverage: { totalCriteria, passCount, adversarialClassesCovered: covered } });
return result;
}
export function normalizeBlockerEvidence(evidence: string): string {
const withoutUrls = evidence.toLowerCase().replace(URL_PATTERN, " ");
const withoutPunctuation = withoutUrls.replace(PUNCTUATION_PATTERN, " ");
return withoutPunctuation.replace(WHITESPACE_PATTERN, " ").trim();
}
export function classifyExternalAuthorizationBlocker(evidence: string): string | null {
const normalized = normalizeBlockerEvidence(evidence);
if (!normalized || !AUTH_PATTERN.test(normalized) || !MISSING_PATTERN.test(normalized)) return null;
if (!GHCR_PATTERN.test(normalized)) return "EXTERNAL_AUTHORIZATION_REQUIRED";
const status401 = GHCR_401_PATTERN.test(normalized) ? "HTTP_401_ANONYMOUS" : null;
const status403 = GHCR_403_PATTERN.test(normalized) ? "HTTP_403_NO_READ_PACKAGES" : null;
const status = [status401, status403].filter((part): part is string => part !== null).join("+");
return `GHCR_PULL_ACCESS:${status || "AUTHORIZATION_REQUIRED"}:GHCR_VISIBILITY_OR_CREDENTIAL_REQUIRED`;
}
function nestedBlockerSignature(goal: UlwLoopItem): string | null {
const blocker = Reflect.get(goal, "blocker");
const signature = isRecord(blocker) ? blocker["signature"] : null;
return typeof signature === "string" ? signature : null;
}
export function sameBlockerOccurrences(plan: UlwLoopPlan, signature: string): number {
return plan.goals.filter((goal) => goal.blockerSignature === signature || nestedBlockerSignature(goal) === signature)
.length;
}
export function clearGoalBlockerFields(goal: UlwLoopItem): void {
for (const key of BLOCKER_FIELD_KEYS) Reflect.deleteProperty(goal, key);
}
@@ -0,0 +1,81 @@
// biome-ignore-all format: compact port must stay within the requested pure LOC budget.
import { readCodexGoalSnapshotInput, reconcileCodexGoalSnapshot } from "./codex-goal-snapshot.js";
import { codexGoalMode, compatibleCodexObjectives, expectedCodexObjective, isFinalRunCompletionCandidate } from "./goal-status.js";
import type { UlwLoopScope } from "./paths.js";
import { seedDefaultSuccessCriteria } from "./plan-crud.js";
import { appendLedger, readUlwLoopPlan, withUlwLoopMutationLock, writePlan } from "./plan-io.js";
import type { UlwLoopItem, UlwLoopLedgerEntry, UlwLoopPlan } from "./types.js";
import { iso, UlwLoopError } from "./types.js";
export interface RecordFinalReviewBlockersArgs { readonly goalId: string; readonly title: string; readonly objective: string; readonly evidence: string; readonly codexGoalJson: string }
export interface RecordFinalReviewBlockersResult { readonly plan: UlwLoopPlan; readonly blockedGoal: UlwLoopItem; readonly newGoal: UlwLoopItem; readonly ledgerEntries: UlwLoopLedgerEntry[] }
const BLOCKER_FIELDS = "blockedReason blockerSignature blockerOccurrenceCount requiredExternalDecision nonRetriable failedAt failureReason completedAt blocker blockerEvidence blockerOccurrences blockedAt".split(" ");
function ulwLoopError(message: string, code: string): never {
throw new UlwLoopError(message, code);
}
function nextGoalId(plan: UlwLoopPlan): string {
const max = plan.goals.reduce((current, goal) => {
const digits = /^G(\d+)/u.exec(goal.id)?.[1];
return digits === undefined ? current : Math.max(current, Number(digits));
}, 0);
return `G${String(max + 1).padStart(3, "0")}`;
}
function appendBlockerGoal(plan: UlwLoopPlan, args: RecordFinalReviewBlockersArgs, now: string): UlwLoopItem {
const index = plan.goals.length;
const goal: UlwLoopItem = {
id: nextGoalId(plan),
title: args.title,
objective: args.objective,
status: "pending",
successCriteria: seedDefaultSuccessCriteria(index, args.objective),
attempt: 0,
createdAt: now,
updatedAt: now,
};
plan.goals.push(goal);
return goal;
}
export async function recordFinalReviewBlockers(
repoRoot: string,
args: RecordFinalReviewBlockersArgs,
scope?: UlwLoopScope,
): Promise<RecordFinalReviewBlockersResult> {
return withUlwLoopMutationLock(repoRoot, scope, async () => {
const plan = await readUlwLoopPlan(repoRoot, scope);
const goal = plan.goals.find((candidate) => candidate.id === args.goalId);
if (goal === undefined) ulwLoopError(`Unknown ulw-loop id: ${args.goalId}`, "ulw_loop_goal_not_found");
if (goal.status !== "in_progress") ulwLoopError(`${goal.id} is ${goal.status}.`, "ulw_loop_goal_not_in_progress");
if (!isFinalRunCompletionCandidate(plan, goal)) ulwLoopError(`${goal.id} is not final.`, "ulw_loop_not_final_story");
const snapshot = await readCodexGoalSnapshotInput(args.codexGoalJson, repoRoot);
const aggregate = codexGoalMode(plan) === "aggregate";
const reconciliation = reconcileCodexGoalSnapshot(snapshot, { expectedObjective: expectedCodexObjective(plan, goal), ...(aggregate ? { acceptedObjectives: compatibleCodexObjectives(plan) } : {}), allowedStatuses: ["active"], requireSnapshot: true, requireComplete: false });
if (!reconciliation.ok) ulwLoopError(reconciliation.errors.join(" "), "ulw_loop_codex_snapshot_mismatch");
const now = iso();
for (const field of BLOCKER_FIELDS) Reflect.deleteProperty(goal, field);
goal.status = "review_blocked";
goal.reviewBlockedAt = now;
goal.evidence = args.evidence;
goal.updatedAt = now;
if (plan.activeGoalId === goal.id) delete plan.activeGoalId;
const newGoal = appendBlockerGoal(plan, args, now);
plan.updatedAt = now;
const codexGoal = reconciliation.snapshot.raw;
const blockedEntry: UlwLoopLedgerEntry = { at: now, kind: "goal_review_blocked", goalId: goal.id, status: goal.status, evidence: args.evidence, codexGoal };
const addedEntry: UlwLoopLedgerEntry = { at: now, kind: "goal_added", goalId: newGoal.id, status: newGoal.status, evidence: args.evidence, message: newGoal.title };
const summaryEntry: UlwLoopLedgerEntry = { at: now, kind: "goal_review_blocked", goalId: goal.id, status: goal.status, evidence: args.evidence, codexGoal, message: `Review blockers recorded; appended ${newGoal.id}.` };
Reflect.set(summaryEntry, "kind", "blocker_recorded");
const ledgerEntries = [blockedEntry, addedEntry, summaryEntry];
await writePlan(repoRoot, plan, scope);
for (const entry of ledgerEntries) await appendLedger(repoRoot, entry, scope);
return { plan, blockedGoal: goal, newGoal, ledgerEntries };
});
}
@@ -0,0 +1,270 @@
// biome-ignore-all format: compact steering module must stay below the 240 pure-LOC budget
import { isUlwLoopDone } from "./goal-status.js";
import type { UlwLoopScope } from "./paths.js";
import { seedDefaultSuccessCriteria } from "./plan-crud.js";
import { appendLedger, readSteeringLedgerEntries, readUlwLoopPlan, withUlwLoopMutationLock, writePlan } from "./plan-io.js";
import type {
SteerUlwLoopResult,
UlwLoopItem,
UlwLoopLedgerEntry,
UlwLoopPlan,
UlwLoopSteeringAudit,
UlwLoopSteeringChildGoal,
UlwLoopSteeringMutationKind,
UlwLoopSteeringProposal,
UlwLoopSteeringSource,
UlwLoopSuccessCriterionUserModel,
} from "./types.js";
import { iso, ULW_LOOP_STEERING_MUTATION_KINDS, ULW_LOOP_SUCCESS_CRITERION_USER_MODELS } from "./types.js";
const SOURCES = ["user_prompt_submit", "finding", "cli"] as const satisfies readonly UlwLoopSteeringSource[];
const PROTECTED = new Set(["aggregateCompletion", "codexObjective", "codexObjectiveAliases", "originalConstraints", "qualityGate", "status", "completedAt", "completionStatus"]);
const isObject = (value: unknown): value is object => typeof value === "object" && value !== null; const isPlain = (value: unknown): value is object => isObject(value) && !Array.isArray(value);
const read = (value: object, key: string): unknown => Object.entries(value).find(([name]) => name === key)?.[1];
const isText = (value: unknown): value is string => typeof value === "string" && value.trim().length > 0;
const text = (value: object, key: string): string | undefined => {
const candidate = read(value, key);
return isText(candidate) ? candidate.trim() : undefined;
};
const isKind = (value: unknown): value is UlwLoopSteeringMutationKind => typeof value === "string" && ULW_LOOP_STEERING_MUTATION_KINDS.some((kind) => kind === value);
const isSource = (value: unknown): value is UlwLoopSteeringSource => typeof value === "string" && SOURCES.some((source) => source === value);
const isModel = (value: unknown): value is UlwLoopSuccessCriterionUserModel => typeof value === "string" && ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((model) => model === value);
const texts = (value: object, key: string): string[] => {
const candidate = read(value, key);
return Array.isArray(candidate) && candidate.every((item) => typeof item === "string") ? candidate : [];
};
function targets(proposal: object): string[] {
const many = texts(proposal, "targetGoalIds");
const one = text(proposal, "targetGoalId") ?? text(proposal, "goalId");
return many.length > 0 ? many : one === undefined ? [] : [one];
}
const after = (proposal: object): object | undefined => {
const candidate = read(proposal, "after");
return isPlain(candidate) ? candidate : undefined;
};
const revised = (proposal: object, direct: string, nested: string): string | undefined => text(proposal, direct) ?? text(after(proposal) ?? proposal, nested);
function child(value: unknown): UlwLoopSteeringChildGoal | null {
if (!isPlain(value)) return null;
const title = text(value, "title");
const objective = text(value, "objective");
if (title === undefined || objective === undefined) return null;
return { title, objective };
}
function childValues(proposal: object): unknown[] {
const direct = read(proposal, "childGoals");
if (Array.isArray(direct) && direct.length > 0) return direct;
const nested = after(proposal);
const fromAfter = nested === undefined ? undefined : read(nested, "children");
return Array.isArray(fromAfter) ? fromAfter : [];
}
const children = (proposal: object): UlwLoopSteeringChildGoal[] => childValues(proposal).map(child).filter((item): item is UlwLoopSteeringChildGoal => item !== null);
const pendingOrder = (proposal: object): string[] => {
const direct = texts(proposal, "pendingOrder");
return direct.length > 0 ? direct : texts(after(proposal) ?? proposal, "pendingGoalIds");
};
function hasProtected(value: unknown): boolean {
if (!isObject(value)) return false;
for (const [key, childValue] of Object.entries(value)) if (PROTECTED.has(key) || key.toLowerCase().includes("complete") || hasProtected(childValue)) return true;
return false;
}
function allText(value: unknown): string {
if (typeof value === "string") return value;
return isObject(value) ? Object.values(value).map(allText).filter(Boolean).join("\n") : "";
}
function weakens(value: unknown): boolean {
const valueText = allText(value).toLowerCase();
return /\b(skip|bypass|weaken|remove|omit|auto[-\s]?complete|mark complete|complete faster)\b/.test(valueText) && /\b(test|tests|verification|review|quality gate|complete|completion)\b/.test(valueText);
}
function auditFor(proposal: unknown, reasons: string[]): UlwLoopSteeringAudit {
const object = isPlain(proposal) ? proposal : undefined;
const kindRaw = object === undefined ? undefined : read(object, "kind");
const sourceRaw = object === undefined ? undefined : read(object, "source");
const evidence = object === undefined ? "" : (text(object, "evidence") ?? "");
const rationale = object === undefined ? "" : (text(object, "rationale") ?? "");
const audit: UlwLoopSteeringAudit = { kind: isKind(kindRaw) ? kindRaw : "annotate_ledger", source: isSource(sourceRaw) ? sourceRaw : "cli", targetGoalIds: object === undefined ? [] : targets(object), evidence, rationale, invariant: { accepted: reasons.length === 0, structuralInvariantAccepted: reasons.length === 0, evidenceBackedNecessity: evidence.length > 0 && rationale.length > 0, noEasierCompletion: !weakens(proposal), rejectedReasons: reasons, reasons } };
if (object === undefined) return audit;
const criterionId = text(object, "criterionId");
const directiveText = text(object, "directiveText");
const promptSignature = text(object, "promptSignature");
const idempotencyKey = text(object, "idempotencyKey");
if (criterionId !== undefined) audit.criterionId = criterionId;
if (directiveText !== undefined) audit.directiveText = directiveText;
if (promptSignature !== undefined) audit.promptSignature = promptSignature;
if (idempotencyKey !== undefined) audit.idempotencyKey = idempotencyKey;
return audit;
}
export function validateUlwLoopSteeringProposal(plan: UlwLoopPlan, proposal: unknown): UlwLoopSteeringAudit {
const reasons: string[] = [];
if (!isPlain(proposal)) reasons.push("proposal must be an object");
const object = isPlain(proposal) ? proposal : {};
const kind = read(object, "kind");
if (!isKind(kind)) reasons.push(`invalid kind: ${String(kind)}`);
if (!isSource(read(object, "source"))) reasons.push(`invalid source: ${String(read(object, "source"))}`);
if (text(object, "evidence") === undefined) reasons.push("missing evidence");
if (text(object, "rationale") === undefined) reasons.push("missing rationale");
if (hasProtected(proposal)) reasons.push("protected payload");
if (weakens(proposal)) reasons.push("weakened completion");
if (isUlwLoopDone(plan)) reasons.push("plan already complete");
if (isKind(kind)) validateKind(plan, object, kind, reasons);
return auditFor(proposal, reasons);
}
function goal(plan: UlwLoopPlan, id: string | undefined): UlwLoopItem | undefined {
return id === undefined ? undefined : plan.goals.find((item) => item.id === id);
}
function validateKind(plan: UlwLoopPlan, proposal: object, kind: UlwLoopSteeringMutationKind, reasons: string[]): void {
const target = goal(plan, targets(proposal)[0]);
if (kind === "add_subgoal" && (text(proposal, "title") === undefined || text(proposal, "objective") === undefined)) reasons.push("add_subgoal requires title/objective");
if ((kind === "split_subgoal" || kind === "revise_pending_wording" || kind === "mark_blocked_superseded") && target === undefined) reasons.push(`${kind} requires target`);
if ((kind === "split_subgoal" || kind === "revise_pending_wording") && target !== undefined && target.status !== "pending") reasons.push(`${kind} requires pending target`);
const rawChildren = childValues(proposal);
if (kind === "split_subgoal" && rawChildren.length === 0) reasons.push("split_subgoal requires children");
if ((kind === "split_subgoal" || kind === "mark_blocked_superseded") && rawChildren.some((item) => child(item) === null)) reasons.push(`${kind} children require title/objective`);
if (kind === "reorder_pending") validateOrder(plan, proposal, reasons);
if (kind === "revise_pending_wording" && revised(proposal, "revisedTitle", "title") === undefined && revised(proposal, "revisedObjective", "objective") === undefined) reasons.push("revise_pending_wording requires update");
if (kind === "revise_criterion") validateCriterion(plan, proposal, reasons);
}
function validateOrder(plan: UlwLoopPlan, proposal: object, reasons: string[]): void {
const requested = pendingOrder(proposal);
const pending = plan.goals.filter((item) => item.status === "pending" && item.steeringStatus === undefined).map((item) => item.id);
if (requested.length === 0) reasons.push("reorder_pending requires ids");
if (new Set(requested).size !== requested.length) reasons.push("duplicate pending id");
if (requested.some((id) => !pending.includes(id))) reasons.push("unknown pending id");
}
function validateCriterion(plan: UlwLoopPlan, proposal: object, reasons: string[]): void {
const target = goal(plan, targets(proposal)[0]);
const criterionId = text(proposal, "criterionId");
if (target === undefined) reasons.push("revise_criterion requires goalId");
else if (criterionId === undefined || target.successCriteria.every((item) => item.id !== criterionId)) reasons.push("revise_criterion requires criterionId");
const model = read(proposal, "userModel");
if (read(proposal, "scenario") === undefined && read(proposal, "expectedEvidence") === undefined && model === undefined) reasons.push("revise_criterion requires update");
if (model !== undefined && !isModel(model)) reasons.push("invalid userModel");
}
function nextId(plan: UlwLoopPlan, offset: number): string {
const max = plan.goals.reduce((current, item) => {
const digits = /^G(\d+)(?:-|$)/u.exec(item.id)?.[1];
return digits === undefined ? current : Math.max(current, Number(digits));
}, 0);
return `G${String(max + offset).padStart(3, "0")}`;
}
function makeGoal(plan: UlwLoopPlan, childGoal: UlwLoopSteeringChildGoal, evidence: string, now: string, offset: number): UlwLoopItem {
const id = nextId(plan, offset);
const digits = /^G(\d+)/u.exec(id)?.[1];
const goalIndex = digits === undefined ? plan.goals.length + offset - 1 : Number(digits) - 1;
return { id, title: childGoal.title, objective: childGoal.objective, status: "pending", successCriteria: seedDefaultSuccessCriteria(goalIndex, childGoal.objective), attempt: 0, createdAt: now, updatedAt: now, evidence };
}
export function applySteeringMutation(plan: UlwLoopPlan, proposal: UlwLoopSteeringProposal, audit: UlwLoopSteeringAudit): UlwLoopPlan {
const next = structuredClone(plan);
if (!audit.invariant.accepted) return next;
const now = proposal.now?.toISOString() ?? iso();
if (proposal.kind === "add_subgoal") next.goals.push(makeGoal(next, { title: proposal.title ?? "", objective: proposal.objective ?? "" }, proposal.evidence, now, 1));
if (proposal.kind === "reorder_pending") {
const order = pendingOrder(proposal);
next.goals = [...order.map((id) => goal(next, id)).filter((item): item is UlwLoopItem => item !== undefined), ...next.goals.filter((item) => !order.includes(item.id))];
}
if (proposal.kind === "revise_pending_wording") reviseWording(next, proposal, now);
if (proposal.kind === "split_subgoal" || proposal.kind === "mark_blocked_superseded") splitOrBlock(next, proposal, now);
if (proposal.kind === "revise_criterion") reviseCriterion(next, proposal, now);
if (proposal.kind !== "annotate_ledger") next.updatedAt = now;
return next;
}
function reviseWording(plan: UlwLoopPlan, proposal: UlwLoopSteeringProposal, now: string): void {
const target = goal(plan, targets(proposal)[0]);
if (target === undefined) return;
target.title = revised(proposal, "revisedTitle", "title") ?? target.title;
target.objective = revised(proposal, "revisedObjective", "objective") ?? target.objective;
target.steeringEvidence = proposal.evidence;
target.steeringRationale = proposal.rationale;
target.updatedAt = now;
}
function splitOrBlock(plan: UlwLoopPlan, proposal: UlwLoopSteeringProposal, now: string): void {
const target = goal(plan, targets(proposal)[0]);
if (target === undefined) return;
const replacements = children(proposal).map((item, index) => makeGoal(plan, item, proposal.evidence, now, index + 1));
target.steeringEvidence = proposal.evidence;
target.steeringRationale = proposal.rationale;
target.updatedAt = now;
if (replacements.length === 0) {
target.status = "blocked";
target.steeringStatus = "blocked";
target.blockedReason = proposal.blockedReason ?? proposal.rationale;
} else {
target.steeringStatus = "superseded";
target.supersededBy = replacements.map((item) => item.id);
for (const item of replacements) item.supersedes = [target.id];
plan.goals.splice(plan.goals.indexOf(target) + 1, 0, ...replacements);
}
if (plan.activeGoalId === target.id) delete plan.activeGoalId;
}
function reviseCriterion(plan: UlwLoopPlan, proposal: UlwLoopSteeringProposal, now: string): void {
const target = goal(plan, targets(proposal)[0]);
const index = target?.successCriteria.findIndex((item) => item.id === proposal.criterionId) ?? -1;
const current = target?.successCriteria[index];
if (target === undefined || current === undefined) return;
const model = read(proposal, "userModel");
target.successCriteria[index] = { ...current, scenario: text(proposal, "scenario") ?? current.scenario, expectedEvidence: text(proposal, "expectedEvidence") ?? current.expectedEvidence, userModel: isModel(model) ? model : current.userModel };
target.updatedAt = now;
}
function isProposal(value: unknown): value is UlwLoopSteeringProposal {
return isPlain(value) && isKind(read(value, "kind")) && isSource(read(value, "source")) && isText(read(value, "evidence")) && isText(read(value, "rationale"));
}
export function parseUlwLoopSteeringDirective(text: string): UlwLoopSteeringProposal | null {
const match = /(?:^|\s)(?:OMO_ULW_LOOP_STEER|omo\.ulw-loop\.steer|omo ulw-loop steer):\s*([\s\S]+)$/u.exec(text);
if (match?.[1] === undefined) return null;
try {
const parsed: unknown = JSON.parse(match[1].trim());
return isProposal(parsed) ? parsed : null;
} catch (error) {
if (error instanceof SyntaxError) return null;
throw error;
}
}
export async function steerUlwLoop(repoRoot: string, proposal: UlwLoopSteeringProposal, scope?: UlwLoopScope): Promise<SteerUlwLoopResult> {
return withUlwLoopMutationLock(repoRoot, scope, async () => {
const plan = await readUlwLoopPlan(repoRoot, scope);
const key = proposal.idempotencyKey ?? proposal.promptSignature;
const prior = key === undefined ? undefined : (await readSteeringLedgerEntries(repoRoot, scope)).find((entry) => entry.steering?.invariant.accepted === true && (entry.idempotencyKey === key || entry.steering.idempotencyKey === key || entry.steering.promptSignature === key));
if (prior?.steering !== undefined) return { plan, accepted: true, audit: { ...prior.steering, deduped: true }, rejectedReasons: [], deduped: true };
const audit = validateUlwLoopSteeringProposal(plan, proposal);
const accepted = audit.invariant.accepted;
const next = accepted ? applySteeringMutation(plan, proposal, audit) : plan;
const finalAudit: UlwLoopSteeringAudit = { ...audit, before: plan };
if (accepted) finalAudit.after = next;
if (accepted) await writePlan(repoRoot, next, scope);
await appendLedger(repoRoot, ledgerEntry(proposal, finalAudit, proposal.now?.toISOString() ?? iso()), scope);
return { plan: next, accepted, audit: finalAudit, rejectedReasons: audit.invariant.rejectedReasons, deduped: false };
});
}
function ledgerEntry(proposal: UlwLoopSteeringProposal, audit: UlwLoopSteeringAudit, at: string): UlwLoopLedgerEntry {
const entry: UlwLoopLedgerEntry = { at, kind: audit.invariant.accepted ? (proposal.kind === "revise_criterion" ? "criteria_revised" : "steering_accepted") : "steering_rejected", evidence: proposal.evidence, message: proposal.rationale, steering: audit, mutationKind: proposal.kind };
const goalId = audit.targetGoalIds[0];
if (goalId !== undefined) entry.goalId = goalId;
if (proposal.criterionId !== undefined) entry.criterionId = proposal.criterionId;
if (proposal.idempotencyKey !== undefined) entry.idempotencyKey = proposal.idempotencyKey;
if (audit.before !== undefined) entry.before = audit.before;
if (audit.after !== undefined) entry.after = audit.after;
return entry;
}
@@ -0,0 +1,277 @@
export const ULW_LOOP_DIR = ".omo/ulw-loop";
export const ULW_LOOP_BRIEF = "brief.md";
export const ULW_LOOP_GOALS = "goals.json";
export const ULW_LOOP_LEDGER = "ledger.jsonl";
export type UlwLoopStatus =
| "pending"
| "in_progress"
| "complete"
| "failed"
| "blocked"
| "review_blocked"
| "needs_user_decision";
export type UlwLoopCodexGoalMode = "aggregate" | "per_story";
export type UlwLoopSteeringStatus = "superseded" | "blocked";
export const ULW_LOOP_STEERING_MUTATION_KINDS = [
"add_subgoal",
"split_subgoal",
"reorder_pending",
"revise_pending_wording",
"revise_criterion",
"annotate_ledger",
"mark_blocked_superseded",
] as const satisfies readonly string[];
export type UlwLoopSteeringMutationKind = (typeof ULW_LOOP_STEERING_MUTATION_KINDS)[number];
export type UlwLoopSteeringSource = "user_prompt_submit" | "finding" | "cli";
export const ULW_LOOP_SUCCESS_CRITERION_USER_MODELS = [
"happy",
"edge",
"regression",
"adversarial",
] as const satisfies readonly string[];
export type UlwLoopSuccessCriterionUserModel = (typeof ULW_LOOP_SUCCESS_CRITERION_USER_MODELS)[number];
export const ULW_LOOP_CRITERION_STATUSES = ["pending", "pass", "fail", "blocked"] as const satisfies readonly string[];
export type UlwLoopCriterionStatus = (typeof ULW_LOOP_CRITERION_STATUSES)[number];
export const ULW_LOOP_LEDGER_EVENT_KINDS = [
"plan_created",
"goal_started",
"goal_resumed",
"goal_completed",
"goal_blocked",
"goal_failed",
"goal_needs_user_decision",
"goal_retried",
"aggregate_completed",
"aggregate_objective_migrated",
"goal_added",
"steering_accepted",
"steering_rejected",
"final_review_failed",
"goal_review_blocked",
"evidence_captured",
"criterion_failed",
"criterion_blocked",
"criteria_revised",
] as const satisfies readonly string[];
export type UlwLoopLedgerEventKind = (typeof ULW_LOOP_LEDGER_EVENT_KINDS)[number];
export interface UlwLoopSuccessCriterion {
readonly id: string;
readonly scenario: string;
readonly userModel: UlwLoopSuccessCriterionUserModel;
readonly expectedEvidence: string;
capturedEvidence: string | null;
status: UlwLoopCriterionStatus;
capturedAt?: string;
notes?: string;
}
export interface UlwLoopSteeringInvariantResult {
accepted: boolean;
structuralInvariantAccepted: boolean;
evidenceBackedNecessity: boolean;
noEasierCompletion: boolean;
rejectedReasons: string[];
reasons?: string[];
}
export interface UlwLoopSteeringChildGoal {
title: string;
objective: string;
}
export interface UlwLoopSteeringAfterPayload {
title?: string;
objective?: string;
pendingGoalIds?: string[];
children?: UlwLoopSteeringChildGoal[];
}
export interface UlwLoopSteeringProposal {
kind: UlwLoopSteeringMutationKind;
source: UlwLoopSteeringSource;
targetGoalId?: string;
targetGoalIds?: string[];
criterionId?: string;
evidence: string;
rationale: string;
title?: string;
objective?: string;
childGoals?: UlwLoopSteeringChildGoal[];
revisedTitle?: string;
revisedObjective?: string;
pendingOrder?: string[];
blockedReason?: string;
after?: UlwLoopSteeringAfterPayload;
directiveText?: string;
promptSignature?: string;
idempotencyKey?: string;
now?: Date;
}
export interface UlwLoopSteeringAudit {
kind: UlwLoopSteeringMutationKind;
source: UlwLoopSteeringSource;
targetGoalIds: string[];
criterionId?: string;
before?: unknown;
after?: unknown;
evidence: string;
rationale: string;
invariant: UlwLoopSteeringInvariantResult;
directiveText?: string;
promptSignature?: string;
idempotencyKey?: string;
deduped?: boolean;
}
export interface SteerUlwLoopResult {
plan: UlwLoopPlan;
accepted: boolean;
audit: UlwLoopSteeringAudit;
rejectedReasons: string[];
deduped: boolean;
}
export interface UlwLoopItem {
id: string;
title: string;
objective: string;
status: UlwLoopStatus;
successCriteria: UlwLoopSuccessCriterion[];
attempt: number;
createdAt: string;
updatedAt: string;
startedAt?: string;
completedAt?: string;
failedAt?: string;
reviewBlockedAt?: string;
evidence?: string;
failureReason?: string;
steeringStatus?: UlwLoopSteeringStatus;
supersededBy?: string[];
supersedes?: string[];
blockedReason?: string;
blockerSignature?: string;
blockerOccurrenceCount?: number;
requiredExternalDecision?: string;
nonRetriable?: boolean;
steeringEvidence?: string;
steeringRationale?: string;
}
export interface UlwLoopAggregateCompletion {
status: "complete";
completedAt: string;
evidence: string;
codexGoal?: unknown;
}
export interface UlwLoopPlan {
version: 1;
createdAt: string;
updatedAt: string;
briefPath: string;
goalsPath: string;
ledgerPath: string;
codexGoalMode?: UlwLoopCodexGoalMode;
codexObjective?: string;
codexObjectiveAliases?: string[];
aggregateCompletion?: UlwLoopAggregateCompletion;
activeGoalId?: string;
goals: UlwLoopItem[];
}
export interface UlwLoopLedgerEntry {
at: string;
kind: UlwLoopLedgerEventKind;
goalId?: string;
criterionId?: string;
status?: UlwLoopStatus;
criterionStatus?: UlwLoopCriterionStatus;
message?: string;
codexGoal?: unknown;
evidence?: string;
capturedEvidence?: string;
qualityGate?: UlwLoopQualityGate;
steering?: UlwLoopSteeringAudit;
before?: unknown;
after?: unknown;
mutationKind?: UlwLoopSteeringMutationKind;
idempotencyKey?: string;
blockerSignature?: string;
blockerOccurrenceCount?: number;
requiredExternalDecision?: string;
}
export interface CreateUlwLoopOptions {
brief: string;
goals?: Array<{ title?: string; objective: string }>;
codexGoalMode?: UlwLoopCodexGoalMode;
now?: Date;
force?: boolean;
}
export interface StartNextOptions {
now?: Date;
retryFailed?: boolean;
}
export interface CheckpointOptions {
goalId: string;
status: Extract<UlwLoopStatus, "complete" | "failed"> | "blocked";
evidence?: string;
codexGoal?: unknown;
qualityGate?: unknown;
allowActiveFinalCodexGoal?: boolean;
now?: Date;
}
export interface AddUlwLoopGoalOptions {
title: string;
objective: string;
evidence?: string;
now?: Date;
}
export interface RecordFinalReviewBlockersOptions extends AddUlwLoopGoalOptions {
goalId: string;
codexGoal?: unknown;
}
export interface UlwLoopQualityGate {
aiSlopCleaner: { status: "passed"; evidence: string };
verification: { status: "passed"; commands: string[]; evidence: string };
codeReview: { recommendation: "APPROVE"; architectStatus: "CLEAR"; evidence: string };
}
export interface UlwLoopErrorOptions {
readonly cause?: unknown;
readonly details?: Record<string, unknown>;
}
export class UlwLoopError extends Error {
readonly code: string;
readonly details?: Record<string, unknown>;
constructor(message: string, code: string, opts?: UlwLoopErrorOptions) {
super(message, opts?.cause === undefined ? undefined : { cause: opts.cause });
this.name = "UlwLoopError";
this.code = code;
if (opts?.details !== undefined) {
this.details = opts.details;
}
}
}
export function iso(): string {
return new Date().toISOString();
}