import type { ReviewDetail, ReviewDiff, StateResponse } from "../src/contracts.ts";
import { fixtureState } from "./fixtures.ts";
/** FH-039 contract fixtures only; no core or owner account is contacted. */
export const reviewScenarios = [
"review-current",
"review-approved",
"review-promoted",
"review-returned",
"review-stale",
"review-main-moved",
"review-main-moved-stale",
"review-out-of-date",
"review-reconciler",
"review-reconciler-stale",
"review-pending-promotion",
"review-pending-promotion-stale",
"review-advisory",
"review-changes",
"review-error",
"review-loading",
"review-not-found",
"review-conflict",
];
export function reviewFixtureState(id: string): StateResponse {
const state = fixtureState("receipts");
state.project.id = id;
state.reviews = [state.reviews[2]];
const r = state.reviews[0];
r.id = "review-1";
r.packet = "FH-040";
r.returned = null;
r.approval = null;
state.project.settings = { requireReceipt: id !== "review-advisory" };
state.packets = [
{
id: r.packet,
title: "Review in a minute",
goal: "Understand the request and inspect each claim.\nKeep the evidence separate.",
paths: ["packages/forkhorse-dashboard/**"],
acceptance: [
"Owner and agent see the request.",
"Return records a reason.",
"Changed files open inline diffs.",
],
},
];
state.blockers = [];
if (id === "review-approved" || id === "review-promoted")
r.approval = { sha: r.sha, receiptDigest: r.receiptDigest };
if (id === "review-promoted") {
r.promoted = true;
state.ledger = [
{
id: "promotion-1",
review: r.id,
repoName: state.project.repoName,
ref: "refs/heads/main",
before: r.base,
after: r.sha,
receiptDigest: r.receiptDigest,
recordedAt: "2026-10-13T08:00:00Z",
},
];
}
if (id === "review-returned")
r.returned = {
reason:
"Explain the changed behavior.\n\nPreserve the acceptance evidence.",
at: "2026-10-13T08:00:00Z",
};
if (id === "review-stale") {
r.stale = true;
state.forks.find((f) => f.repoName === r.repoName)!.head = "9".repeat(40);
}
if (id === "review-out-of-date") r.stale = true;
if (/^review-(main-moved|reconciler|pending-promotion)/.test(id)) {
r.approval = { sha: r.sha, receiptDigest: r.receiptDigest };
r.stale = id.endsWith("-stale");
state.ledger = [{
id: "another-promotion", review: "another-review", repoName: state.project.repoName,
ref: "refs/heads/main", before: r.base, after: "7".repeat(40),
receiptDigest: null, recordedAt: "2026-10-13T08:00:00Z",
}];
if (id.startsWith("review-reconciler")) state.reconcilerAlerts = [{
id: "current-head", repoName: state.project.repoName, ref: "refs/heads/main",
before: "7".repeat(40), after: "8".repeat(40), reason: "Fixture main changed outside a promotion.",
}];
if (id.startsWith("review-pending-promotion")) state.promotionPending = state.ledger[0];
}
// Quick-check duration fixture; every receipt surface uses the same formatter.
if (r.receipt) {
r.receipt.durationMs = 227;
r.receipt.checks[0].durationMs = 227;
}
if (id === "review-advisory")
r.receipt = {
...r.receipt!,
status: "failed",
checks: [{ ...r.receipt!.checks[0], outcome: "failed" }],
};
if (id === "review-changes") {
state.activity[0].changedPaths = Array.from({ length: 400 }, (_, i) => `src/path-${i}.ts`);
state.activity[0].changedPathCount = 400;
}
return state;
}
export function reviewFixtureDetail(state: StateResponse): ReviewDetail {
const review = state.reviews[0];
const large = state.project.id === "review-changes";
const extraCommits = large
? Array.from({ length: 48 }, (_, i) => ({
sha: (i + 16).toString(16).padStart(40, "0"),
subject: `Earlier fixture change ${i + 1}`,
authorName: "fixture-agent",
packetTrailer: null,
agentTrailer: null,
}))
: [];
return {
review,
packet: state.packets[0],
changes: {
base: review.base,
head: review.sha,
fileCount: large ? 400 : 4,
truncated: large,
files: (
[
{ path: "src/review.tsx", status: "modified" },
{ path: "assets/image.bin", status: "added" },
{ path: "docs/large.txt", status: "deleted" },
{ path: "missing.txt", status: "modified" },
...(large
? Array.from({ length: 196 }, (_, i) => ({
path: `src/very/long/nested/fixture/path-${i}.ts`,
status: "modified" as const,
}))
: []),
] satisfies ReviewDetail["changes"]["files"]
).toSorted((a, b) => a.path.localeCompare(b.path)),
},
commits: [
{
sha: review.sha,
subject: "🐎 Add the review view",
authorName: "fixture-agent",
packetTrailer: "FH-040",
agentTrailer: "codex",
},
{
sha: "8".repeat(40),
subject: "Prepare review contract",
authorName: "fixture-agent",
packetTrailer: null,
agentTrailer: null,
},
...extraCommits,
],
commitsTruncated: large,
promotion: state.ledger.find((l) => l.review === review.id) ?? null,
// FH-048 preserves claimant-packet detail; pushing gates/history come from /state.
blockers: state.blockers.filter((blocker) => blocker.packet === review.packet),
};
}
export function reviewFixtureDiff(path: string): ReviewDiff {
return {
path,
status:
path === "assets/image.bin" ? "added" : path === "docs/large.txt" ? "deleted" : "modified",
binary: path === "assets/image.bin",
truncated: path === "docs/large.txt",
patch:
path === "docs/large.txt"
? "@@ -1,2 +0,0 @@\n-old content\n-more removed content"
: "--- a/src/review.tsx\n+++ b/src/review.tsx\n@@ -1 +1 @@\n-previous value\n+\n context with a very long " +
"fixture ".repeat(40),
};
}