58 lines
2.2 KiB
TypeScript
58 lines
2.2 KiB
TypeScript
#!/usr/bin/env bun
|
|||
|
|
// Reads workflow output JSON on stdin: [{file, approved, patch}], applies each
|
||
|
|
// approved patch with `git apply` (strict, then `--recount` fallback). Never
|
||
|
|
// uses `--unidiff-zero` or `-C1` — both silently corrupt by joining adjacent
|
||
|
|
// source lines when the agent's @@ header counts are off.
|
||
|
|
|
||
|
|
import { spawnSync } from "node:child_process";
|
||
|
|
import { mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
||
|
|
import { tmpdir } from "node:os";
|
||
|
|
import { join } from "node:path";
|
||
|
|
|
||
|
|
const input = readFileSync(0, "utf8");
|
||
|
|
const results: Array<{ file: string; approved: boolean; patch: string; reviewNotes?: string }> = JSON.parse(input);
|
||
|
|
|
||
|
|
const dir = mkdtempSync(join(tmpdir(), "clippy-patches-"));
|
||
|
|
let applied = 0;
|
||
|
|
let rejected = 0;
|
||
|
|
let failed = 0;
|
||
|
|
|
||
|
|
for (const r of results) {
|
||
|
|
if (!r.approved || !r.patch?.trim()) {
|
||
|
|
rejected++;
|
||
|
|
if (r.reviewNotes) console.error(`[skip] ${r.file}: ${r.reviewNotes}`);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
const patchFile = join(dir, r.file.replace(/[\\/]/g, "_") + ".patch");
|
||
|
|
// ensure trailing newline; git apply is picky
|
||
|
|
writeFileSync(patchFile, r.patch.endsWith("\n") ? r.patch : r.patch + "\n");
|
||
|
|
// Strict first; fall back to --recount only (recomputes @@ counts from body).
|
||
|
|
// NEVER use --unidiff-zero or -C1: both silently corrupt by joining adjacent
|
||
|
|
// source lines when the agent's @@ counts are off.
|
||
|
|
let res = spawnSync("git", ["apply", "--whitespace=nowarn", patchFile], {
|
||
|
|
cwd: process.cwd(),
|
||
|
|
encoding: "utf8",
|
||
|
|
});
|
||
|
|
if (res.status !== 0) {
|
||
|
|
res = spawnSync("git", ["apply", "--recount", "--whitespace=nowarn", patchFile], {
|
||
|
|
cwd: process.cwd(),
|
||
|
|
encoding: "utf8",
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (res.status === 0) {
|
||
|
|
applied++;
|
||
|
|
console.error(`[ok] ${r.file}`);
|
||
|
|
} else {
|
||
|
|
failed++;
|
||
|
|
const errText = res.stderr
|
||
|
|
? res.stderr.toString().trim().split("\n")[0]
|
||
|
|
: (res.error?.message ?? `git apply exited with status ${res.status}`);
|
||
|
|
console.error(`[fail] ${r.file}: ${errText}`);
|
||
|
|
console.error(` patch saved at ${patchFile}`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
console.error(`\napplied=${applied} rejected=${rejected} apply-failed=${failed}`);
|
||
|
|
process.stdout.write(JSON.stringify({ applied, rejected, failed }) + "\n");
|
||
|
|
process.exit(failed > 0 ? 1 : 0);
|