#!/usr/bin/env bun
/**
* End-to-end check for AI merge fixes against a running dev server started with MERGEFIX_PROVIDER=mock.
*
* bun scripts/e2e-mergefix.ts # seeds a user + token in the local D1, uses BASE_URL
* BASE_URL=http://localhost:3015 GITFOR_TOKEN=gitfor_pat_... bun scripts/e2e-mergefix.ts
*
* Scenario 1: a cosmetic conflict is fixed automatically; the merge commit lands on the head branch, is
* marker-free, and a CI run is scheduled for it. Scenario 2: a security-looking conflict is only suggested.
*/
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
const BASE_URL = (process.env.BASE_URL ?? "http://localhost:3015").replace(/\/$/, "");
const POLL_MS = 1000;
const FIX_TIMEOUT_MS = 120_000;
const CI_TIMEOUT_MS = 60_000;
/** A merge commit lists itself plus two parents in `rev-list --parents`. */
const MERGE_PARENT_FIELDS = 3;
/** Two generations and one Clef decision for the single hunk. */
const EXPECTED_MIN_CALLS = 3;
function sh(cmd: string[], cwd?: string): string {
const result = Bun.spawnSync(cmd, { cwd, stdout: "pipe", stderr: "pipe" });
if (result.exitCode !== 0) throw new Error(`${cmd.join(" ")} failed: ${result.stderr.toString()}`);
return result.stdout.toString();
}
function d1(sql: string): Array<Record<string, unknown>> {
const out = sh(["bunx", "wrangler", "d1", "execute", "gitfor", "--local", "--json", "--command", sql]);
return (JSON.parse(out) as Array<{ results: Array<Record<string, unknown>> }>)[0]?.results ?? [];
}
async function sha256Hex(value: string): Promise<string> {
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
}
/** Local-only bootstrap: a throwaway user with a classic token, written straight into the dev D1. */