fable-agent/src/fable5/research-map-receipt.test.ts

91 lines
3.5 KiB
TypeScript

import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { describe, expect, it } from "vitest";
import { createResearchMapReceipt, writeResearchMapReceipt } from "./research-map-receipt.js";
const phase = {
name: "Add receipt primitive",
goal: "Map research into a bounded receipt and tests",
acceptanceCriteria: ["receipt is typed", "forbidden implementation scope is blocked"],
verification: ["npm run -s test -- src/fable5/research-map-receipt.test.ts"],
};
describe("research map receipts", () => {
it("creates ready bounded research plans", () => {
const receipt = createResearchMapReceipt({
now: new Date("2026-07-01T00:00:00.000Z"),
taskLabel: "ast-context",
task: "Map AST context research into Fable receipt primitives",
constraints: ["advisory-only", "no production mutation"],
existingPrimitives: ["plan-receipt", "context-workspace-receipt", "factory-deploy", "rsi-reconcile"],
recommendedPlan: [phase],
skipped: ["deploy changes", "dashboard", "vendored tools", "new model architecture"],
});
expect(receipt).toMatchObject({
schema: "fable.research_map.receipt.v1",
decision: "ready",
reasons: [],
deployAttempted: false,
});
expect(receipt.skipped).toContain("deploy changes");
expect(receipt.existingPrimitives).toContain("plan-receipt");
});
it("blocks missing task metadata and phases", () => {
const receipt = createResearchMapReceipt({ taskLabel: "", task: "", recommendedPlan: [] });
expect(receipt.decision).toBe("blocked");
expect(receipt.reasons).toContain("missing task label");
expect(receipt.reasons).toContain("missing task");
expect(receipt.reasons).toContain("missing recommended phases");
});
it("requires acceptance criteria and verification", () => {
const receipt = createResearchMapReceipt({
taskLabel: "bad",
task: "Bad map",
recommendedPlan: [{ name: "x", goal: "y", acceptanceCriteria: [], verification: [] }],
});
expect(receipt.decision).toBe("blocked");
expect(receipt.reasons).toContain("each phase needs acceptance criteria");
expect(receipt.reasons).toContain("each phase needs verification steps");
});
it("blocks forbidden implementation scope in recommended phases", () => {
const receipt = createResearchMapReceipt({
taskLabel: "overbuild",
task: "Map research safely",
recommendedPlan: [{ ...phase, goal: "Build a new model architecture and dashboard" }],
});
expect(receipt.decision).toBe("blocked");
expect(receipt.reasons[0]).toContain("forbidden scope");
expect(receipt.reasons[0]).toContain("dashboard");
expect(receipt.reasons[0]).toContain("new model architecture");
});
it("does not block forbidden terms when they are explicitly skipped", () => {
const receipt = createResearchMapReceipt({
taskLabel: "bounded",
task: "Map research safely",
recommendedPlan: [phase],
skipped: ["deploy", "dashboard", "vendor", "new model architecture"],
});
expect(receipt.decision).toBe("ready");
});
it("writes receipts", () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "research-map-"));
const file = path.join(dir, "receipt.json");
writeResearchMapReceipt(file, createResearchMapReceipt({ taskLabel: "x", task: "y", recommendedPlan: [phase] }));
expect(JSON.parse(fs.readFileSync(file, "utf-8"))).toMatchObject({ schema: "fable.research_map.receipt.v1" });
fs.rmSync(dir, { recursive: true, force: true });
});
});