Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/ultra/__tests__/bug.test.ts

Raw
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
import { runWorkflow } from "../engine.ts";
import { compileSchema, discoverWorkflows, parseWorkflow } from "../spec.ts";

const here = path.dirname(fileURLToPath(import.meta.url));
const BUG_PATH = path.join(here, "..", "workflows", "bug.json");
const READ_ONLY_TOOLS = new Set(["read", "grep", "find", "ls", "bash"]);

function loadRaw(): string {
	return fs.readFileSync(BUG_PATH, "utf8");
}

describe("bug shipped workflow", () => {
	it("parses the investigate → regression → fix → verify → report flow", () => {
		const spec = parseWorkflow(loadRaw());
		expect(spec.name).toBe("bug");
		expect(spec.args?.params).toEqual(["bug"]);
		expect(spec.phases.map((phase) => phase.id)).toEqual([
			"investigate",
			"regression",
			"fix",
			"verify",
			"report",
		]);
		expect(spec.return).toBe("{report.results}");
	});

	it("selects tiers by phase role and leaves thinking to settings", () => {
		const spec = parseWorkflow(loadRaw());
		expect(
			Object.fromEntries(
				spec.phases.map((phase) => [phase.id, phase.step.model]),
			),
		).toEqual({
			investigate: "large",
			regression: "medium",
			fix: "medium",
			verify: "large",
			report: "medium",
		});
		for (const phase of spec.phases)
			expect(phase.step.thinkingLevel).toBeUndefined();
	});

	it("chains evidence forward and conditionally skips fix and verification", () => {
		const spec = parseWorkflow(loadRaw());
		const get = (id: string) => spec.phases.find((phase) => phase.id === id);

		expect(get("regression")?.when).toBe("{investigate.results | where ready}");
		expect(get("regression")?.step.prompt).toContain("{investigate.results}");
		expect(get("fix")?.step.prompt).toContain("{regression.results}");
		expect(get("fix")?.when).toBe("{regression.results | where ready}");
		expect(get("verify")?.step.prompt).toContain("{fix.results}");
		expect(get("verify")?.when).toBe("{fix.results | where fixed}");
		const fix = spec.schemas?.Fix as { required?: string[] };
		expect(fix.required).toContain("fixed");
	});

	it("writes only in sequential regression and fix phases; verification fans out read-only", () => {
		const spec = parseWorkflow(loadRaw());
		for (const id of ["regression", "fix"]) {
			const phase = spec.phases.find((candidate) => candidate.id === id);
			expect(phase?.kind).toBe("single");
			expect(phase?.step.tools).toEqual(
				expect.arrayContaining(["edit", "write"]),
			);
		}

		const verify = spec.phases.find((phase) => phase.id === "verify");
		expect(verify?.kind).toBe("fanout");
		expect(verify?.over).toEqual(["regression", "correctness", "security"]);
		for (const tool of verify?.step.tools ?? []) {
			expect(READ_ONLY_TOOLS.has(tool)).toBe(true);
		}
	});

	it("requires a settled correction/test brief and preserves instruction and evidence obligations", () => {
		const spec = parseWorkflow(loadRaw());
		const investigation = spec.schemas?.Investigation as { required: string[] };
		expect(investigation.required).toEqual(
			expect.arrayContaining([
				"ready",
				"correction",
				"test_brief",
				"rationale",
				"instructions",
				"evidence_paths",
				"completion_checks",
				"blockers",
				"gaps",
			]),
		);
		for (const phase of spec.phases.filter((phase) => phase.id !== "report")) {
			expect(phase.step.prompt).toContain("AGENTS.md");
			expect(phase.step.prompt).toMatch(/user instructions/);
			expect(phase.step.prompt).toMatch(/overflow/);
		}
		const report = spec.phases.find((phase) => phase.id === "report");
		expect(report?.when).toBeUndefined();
		for (const id of ["investigate", "regression", "fix", "verify"]) {
			expect(report?.step.prompt).toContain(`{${id}.results}`);
			expect(report?.step.prompt).toContain(`{${id}.failures}`);
		}
		for (const schema of Object.values(spec.schemas ?? {}))
			expect(compileSchema(schema)).toBeDefined();
	});

	it.each([
		["investigate", false, "blocked"],
		["investigate", true, "blocked"],
		["regression", false, "blocked"],
		["regression", true, "blocked"],
		["fix", false, "blocked"],
		["fix", true, "blocked"],
		["verify", false, "unresolved"],
		["verify", true, "unresolved"],
		["none", false, "completed"],
	])(
		"delivers %s (failure=%s) evidence to an unconditional simulated %s report",
		async (stoppedAt, failure, status) => {
			const spec = parseWorkflow(loadRaw());
			const reason = `${stoppedAt}: evidence unavailable`;
			const calls: string[] = [];
			const fixtures: Record<string, Record<string, unknown>> = {
				investigate: {
					ready: true,
					summary: "Settled",
					reproduction: "Input 0 throws",
					likely_cause: "Wrong bound",
					correction: "Correct bound in src/a.ts",
					test_brief: "Assert input 0 succeeds",
					rationale: "Shared route",
					instructions: ["AGENTS.md: keep public API"],
					files: ["src/a.ts"],
					evidence_paths: ["src/a.ts:10"],
					completion_checks: ["focused test"],
					blockers: [],
					gaps: [],
				},
				regression: {
					ready: true,
					test_files: ["test/a.ts"],
					command: "focused test",
					failure: "Input 0 throws as expected",
					evidence_paths: ["test/a.ts:4"],
					blockers: [],
					gaps: [],
				},
				fix: {
					fixed: true,
					summary: "Corrected bound",
					modified_files: ["src/a.ts"],
					checks: ["focused test passed"],
					evidence_paths: ["src/a.ts:10"],
					blockers: [],
					gaps: [],
				},
			};
			const report = {
				status,
				summary: status === "completed" ? "Verified" : reason,
				blockers: status === "blocked" ? [reason] : [],
				unresolved: status === "unresolved" ? [reason] : [],
				checks: [],
				findings: [],
				recommendations: [],
			};
			const result = await runWorkflow(
				spec,
				{ bug: "Input 0 fails" },
				{
					concurrency: 3,
					stepRunner: async ({ phase, step, item }) => {
						calls.push(phase);
						if (phase === "report") {
							if (stoppedAt !== "none") expect(step.prompt).toContain(reason);
							expect(step.prompt).toContain("Investigation failures:");
							expect(
								compileSchema(spec.schemas?.BugReport ?? {}).Check(report),
							).toBe(true);
							return { ok: true, value: report };
						}
						const stopped =
							phase === stoppedAt &&
							(phase !== "verify" || item === "security");
						if (stopped && failure)
							return {
								ok: false,
								value: null,
								failure: {
									code: "transport",
									message: reason,
									retryable: true,
									attempts: 1,
								},
							};
						const value =
							phase === "verify"
								? {
										dimension: item,
										passed: !stopped,
										findings: [],
										checks: [],
										unresolved: stopped ? [reason] : [],
										recommendations: [],
									}
								: { ...fixtures[phase] };
						if (stopped && phase !== "verify") {
							Object.assign(value, {
								[phase === "fix" ? "fixed" : "ready"]: false,
								blockers: [reason],
							});
						}
						expect(
							compileSchema(spec.schemas?.[String(step.schema)] ?? {}).Check(
								value,
							),
						).toBe(true);
						return { ok: true, value };
					},
				},
			);
			expect(result.result).toEqual([report]);
			expect(calls.at(-1)).toBe("report");
			if (stoppedAt === "investigate")
				expect(calls).toEqual(["investigate", "report"]);
			if (stoppedAt === "regression")
				expect(calls).toEqual(["investigate", "regression", "report"]);
			if (stoppedAt === "fix") expect(calls).not.toContain("verify");
		},
	);

	it("contains no loop construct", () => {
		expect(loadRaw()).not.toMatch(/"loop"|"repeat"/);
	});

	it("is discoverable as a bundled workflow", () => {
		const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "ultra-bug-cwd-"));
		const home = fs.mkdtempSync(path.join(os.tmpdir(), "ultra-bug-home-"));
		try {
			const bug = discoverWorkflows(cwd, { home }).find(
				(workflow) => workflow.name === "bug",
			);
			expect(bug?.source).toBe("bundled");
		} finally {
			fs.rmSync(cwd, { recursive: true, force: true });
			fs.rmSync(home, { recursive: true, force: true });
		}
	});
});