test(omo-codex): batch 101 (18 files)
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
import { mkdir, mkdtemp, readFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { ulwLoopBriefPath, ulwLoopGoalsPath, ulwLoopLedgerPath } from "../src/paths.js";
|
||||
import {
|
||||
addUlwLoopGoal,
|
||||
createUlwLoopPlan,
|
||||
deriveGoalCandidates,
|
||||
seedDefaultSuccessCriteria,
|
||||
startNextUlwLoop,
|
||||
summarizeUlwLoopPlan,
|
||||
} from "../src/plan-crud.js";
|
||||
import { writePlan } from "../src/plan-io.js";
|
||||
import type { UlwLoopItem, UlwLoopPlan, UlwLoopSuccessCriterion } from "../src/types.js";
|
||||
import { UlwLoopError } from "../src/types.js";
|
||||
|
||||
const NOW = "2026-05-23T00:00:00.000Z";
|
||||
|
||||
async function makeRepo(): Promise<string> {
|
||||
return mkdtemp(join(tmpdir(), "ug-crud-"));
|
||||
}
|
||||
|
||||
async function readBriefFixture(): Promise<string> {
|
||||
return readFile(join(process.cwd(), "test", "fixtures", "sample-brief.md"), "utf8");
|
||||
}
|
||||
|
||||
async function ledgerKinds(repoRoot: string): Promise<string[]> {
|
||||
const raw = await readFile(ulwLoopLedgerPath(repoRoot), "utf8");
|
||||
return raw
|
||||
.split(/\r?\n/)
|
||||
.filter(Boolean)
|
||||
.map((line) => JSON.parse(line).kind);
|
||||
}
|
||||
|
||||
function criterion(status: UlwLoopSuccessCriterion["status"]): UlwLoopSuccessCriterion {
|
||||
const [base] = seedDefaultSuccessCriteria(0, "Implement auth endpoint");
|
||||
if (base === undefined) throw new Error("expected seeded criterion");
|
||||
return { ...base, status };
|
||||
}
|
||||
|
||||
function makeGoal(overrides: Partial<UlwLoopItem> = {}): UlwLoopItem {
|
||||
return {
|
||||
id: "G001",
|
||||
title: "Build auth service",
|
||||
objective: "Implement JWT auth endpoint",
|
||||
status: "pending",
|
||||
successCriteria: seedDefaultSuccessCriteria(0, "Implement JWT auth endpoint"),
|
||||
attempt: 0,
|
||||
createdAt: NOW,
|
||||
updatedAt: NOW,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function makePlan(goals: UlwLoopItem[]): UlwLoopPlan {
|
||||
return {
|
||||
version: 1,
|
||||
createdAt: NOW,
|
||||
updatedAt: NOW,
|
||||
briefPath: ".omo/ulw-loop/brief.md",
|
||||
goalsPath: ".omo/ulw-loop/goals.json",
|
||||
ledgerPath: ".omo/ulw-loop/ledger.jsonl",
|
||||
codexGoalMode: "aggregate",
|
||||
goals,
|
||||
};
|
||||
}
|
||||
|
||||
function scheduled(result: Awaited<ReturnType<typeof startNextUlwLoop>>) {
|
||||
if ("done" in result) throw new Error("expected scheduled goal");
|
||||
return result;
|
||||
}
|
||||
|
||||
describe("seedDefaultSuccessCriteria", () => {
|
||||
it("produces 3 criteria with C001/C002/C003 ids", () => {
|
||||
const cs = seedDefaultSuccessCriteria(0, "Implement auth endpoint");
|
||||
expect(cs).toHaveLength(3);
|
||||
expect(cs.map((c) => c.id)).toEqual(["C001", "C002", "C003"]);
|
||||
});
|
||||
|
||||
it("covers happy + edge + regression user models", () => {
|
||||
const cs = seedDefaultSuccessCriteria(0, "Implement auth endpoint");
|
||||
expect(cs.map((c) => c.userModel).sort()).toEqual(["edge", "happy", "regression"]);
|
||||
});
|
||||
|
||||
it("seeds all criteria as pending with null capturedEvidence", () => {
|
||||
const cs = seedDefaultSuccessCriteria(0, "Implement auth endpoint");
|
||||
for (const c of cs) {
|
||||
expect(c.status).toBe("pending");
|
||||
expect(c.capturedEvidence).toBeNull();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("createUlwLoopPlan", () => {
|
||||
it("creates .omo/ulw-loop/{brief.md, goals.json, ledger.jsonl} in repoRoot", async () => {
|
||||
const repoRoot = await makeRepo();
|
||||
const brief = await readBriefFixture();
|
||||
|
||||
await createUlwLoopPlan(repoRoot, { brief });
|
||||
|
||||
expect(await readFile(ulwLoopBriefPath(repoRoot), "utf8")).toBe(brief.endsWith("\n") ? brief : `${brief}\n`);
|
||||
expect(await readFile(ulwLoopGoalsPath(repoRoot), "utf8")).toContain("G001-build-the-jwt-auth-endpoint");
|
||||
expect(await ledgerKinds(repoRoot)).toEqual(["plan_created"]);
|
||||
});
|
||||
|
||||
it("seeds at least 3 successCriteria per goal", async () => {
|
||||
const plan = await createUlwLoopPlan(await makeRepo(), { brief: await readBriefFixture() });
|
||||
|
||||
expect(plan.goals).toHaveLength(3);
|
||||
expect(plan.goals.every((goal) => goal.successCriteria.length >= 3)).toBe(true);
|
||||
});
|
||||
|
||||
it("refuses overwrite of an existing plan without --force", async () => {
|
||||
const repoRoot = await makeRepo();
|
||||
await createUlwLoopPlan(repoRoot, { brief: "first" });
|
||||
|
||||
await expect(createUlwLoopPlan(repoRoot, { brief: "second" })).rejects.toThrow(UlwLoopError);
|
||||
await expect(createUlwLoopPlan(repoRoot, { brief: "second" })).rejects.toThrow("Refusing to overwrite");
|
||||
});
|
||||
|
||||
it("aggregate is the default codexGoalMode", async () => {
|
||||
const plan = await createUlwLoopPlan(await makeRepo(), { brief: "Ship the feature" });
|
||||
|
||||
expect(plan.codexGoalMode).toBe("aggregate");
|
||||
expect(plan.codexObjective).toContain(".omo/ulw-loop/goals.json");
|
||||
});
|
||||
});
|
||||
|
||||
describe("deriveGoalCandidates", () => {
|
||||
it("extracts bullets as goals", () => {
|
||||
expect(deriveGoalCandidates("# Brief\n\n- Build auth\n- Add tests")).toEqual([
|
||||
{ title: "Build auth", objective: "Build auth" },
|
||||
{ title: "Add tests", objective: "Add tests" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("falls back to paragraph parsing when no bullets", () => {
|
||||
expect(deriveGoalCandidates("First objective.\n\nSecond objective.").map((goal) => goal.objective)).toEqual([
|
||||
"First objective.",
|
||||
"Second objective.",
|
||||
]);
|
||||
});
|
||||
|
||||
it("returns single default goal for empty/whitespace brief", () => {
|
||||
expect(deriveGoalCandidates(" \n\t ")).toEqual([
|
||||
{ title: "Complete the requested project objective.", objective: "Complete the requested project objective." },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("addUlwLoopGoal", () => {
|
||||
it("appends a new goal to plan with seeded successCriteria", async () => {
|
||||
const repoRoot = await makeRepo();
|
||||
await createUlwLoopPlan(repoRoot, { brief: "Build auth" });
|
||||
|
||||
const { plan, goal } = await addUlwLoopGoal(repoRoot, { title: "Add rate limit", objective: "Throttle login" });
|
||||
|
||||
expect(plan.goals).toHaveLength(2);
|
||||
expect(goal.id).toBe("G002-add-rate-limit");
|
||||
expect(goal.successCriteria).toHaveLength(3);
|
||||
});
|
||||
|
||||
it("appends a ledger entry for goal_added", async () => {
|
||||
const repoRoot = await makeRepo();
|
||||
await createUlwLoopPlan(repoRoot, { brief: "Build auth" });
|
||||
|
||||
await addUlwLoopGoal(repoRoot, { title: "Add rate limit", objective: "Throttle login" });
|
||||
|
||||
expect(await ledgerKinds(repoRoot)).toEqual(["plan_created", "goal_added"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("startNextUlwLoop", () => {
|
||||
it("picks the first pending goal", async () => {
|
||||
const repoRoot = await makeRepo();
|
||||
await createUlwLoopPlan(repoRoot, { brief: "- First\n- Second" });
|
||||
|
||||
const result = scheduled(await startNextUlwLoop(repoRoot, {}));
|
||||
|
||||
expect(result.goal.id).toBe("G001-first");
|
||||
expect(result.goal.status).toBe("in_progress");
|
||||
expect(result.resumed).toBe(false);
|
||||
});
|
||||
|
||||
it("resumes the in_progress goal when one exists", async () => {
|
||||
const repoRoot = await makeRepo();
|
||||
const plan = await createUlwLoopPlan(repoRoot, { brief: "- First\n- Second" });
|
||||
const active = makeGoal({ ...plan.goals[1], status: "in_progress" });
|
||||
await writePlan(repoRoot, { ...plan, goals: [makeGoal({ ...plan.goals[0] }), active], activeGoalId: active.id });
|
||||
|
||||
const result = scheduled(await startNextUlwLoop(repoRoot, {}));
|
||||
|
||||
expect(result.goal.id).toBe(active.id);
|
||||
expect(result.resumed).toBe(true);
|
||||
});
|
||||
|
||||
it("with retryFailed picks first failed (non-blocked) goal", async () => {
|
||||
const repoRoot = await makeRepo();
|
||||
const failed = makeGoal({ status: "failed", failureReason: "flake" });
|
||||
await mkdir(join(repoRoot, ".omo", "ulw-loop"), { recursive: true });
|
||||
await writePlan(repoRoot, makePlan([failed]));
|
||||
|
||||
const result = scheduled(await startNextUlwLoop(repoRoot, { retryFailed: true }));
|
||||
|
||||
expect(result.goal.id).toBe("G001");
|
||||
expect(result.goal.attempt).toBe(1);
|
||||
expect(await ledgerKinds(repoRoot)).toEqual(["goal_retried", "goal_started"]);
|
||||
});
|
||||
|
||||
it("returns { done: true } when no eligible goals remain", async () => {
|
||||
const repoRoot = await makeRepo();
|
||||
await mkdir(join(repoRoot, ".omo", "ulw-loop"), { recursive: true });
|
||||
await writePlan(repoRoot, makePlan([makeGoal({ status: "complete" })]));
|
||||
|
||||
const result = await startNextUlwLoop(repoRoot, {});
|
||||
|
||||
expect(result).toMatchObject({ done: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe("summarizeUlwLoopPlan", () => {
|
||||
it("counts goals by status", () => {
|
||||
const plan = makePlan([
|
||||
makeGoal({ id: "G001", status: "pending" }),
|
||||
makeGoal({ id: "G002", status: "in_progress" }),
|
||||
makeGoal({ id: "G003", status: "complete" }),
|
||||
makeGoal({ id: "G004", status: "failed" }),
|
||||
makeGoal({ id: "G005", status: "blocked", steeringStatus: "blocked" }),
|
||||
makeGoal({ id: "G006", status: "review_blocked" }),
|
||||
makeGoal({ id: "G007", status: "needs_user_decision", steeringStatus: "superseded" }),
|
||||
]);
|
||||
|
||||
expect(summarizeUlwLoopPlan(plan)).toMatchObject({
|
||||
total: 7,
|
||||
pending: 1,
|
||||
in_progress: 1,
|
||||
complete: 1,
|
||||
failed: 1,
|
||||
blocked: 1,
|
||||
review_blocked: 1,
|
||||
needs_user_decision: 1,
|
||||
superseded: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it("aggregates criteria pass/pending/fail/blocked across all goals", () => {
|
||||
const plan = makePlan([
|
||||
makeGoal({ successCriteria: [criterion("pass"), criterion("pending")] }),
|
||||
makeGoal({ id: "G002", successCriteria: [criterion("fail"), criterion("blocked"), criterion("pending")] }),
|
||||
]);
|
||||
|
||||
expect(summarizeUlwLoopPlan(plan).criteria).toEqual({ total: 5, pass: 1, pending: 2, fail: 1, blocked: 1 });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user