import { execFileSync } from "node:child_process"; import * as fs from "node:fs"; import * as path from "node:path"; import { WorktreeManager } from "./worktree-isolation.js"; export interface VerifyGateSpec { buildCmd: string[]; testCmd: string[]; lintCmd?: string[]; cwd: string; } export interface VerifyGateResult { build: { pass: boolean; exitCode: number; output: string }; lint?: { pass: boolean; exitCode: number; output: string }; test: { pass: boolean; exitCode: number; output: string; passed: number; failed: number; total: number }; verdict: "pass" | "fail"; durationMs: number; } function run(cmd: string[], cwd: string): { exitCode: number; output: string } { try { const out = execFileSync(cmd[0], cmd.slice(1), { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], maxBuffer: 1024 * 1024, }); return { exitCode: 0, output: out.trim() }; } catch (e: any) { return { exitCode: e.status ?? 1, output: ((e.stdout ?? "") + (e.stderr ?? "")).trim() }; } } function parseTestOutput(output: string): { passed: number; failed: number; total: number } { const p = output.match(/(\d+)\s+passed/); const f = output.match(/(\d+)\s+failed/); return { passed: p ? parseInt(p[1]) : 0, failed: f ? parseInt(f[1]) : 0, total: (p ? parseInt(p[1]) : 0) + (f ? parseInt(f[1]) : 0), }; } export function runVerifyGate(spec: VerifyGateSpec): VerifyGateResult { const start = Date.now(); if (!fs.existsSync(path.join(spec.cwd, ".git"))) { return { build: { pass: false, exitCode: -1, output: "" }, test: { pass: false, exitCode: -1, output: "", passed: 0, failed: 0, total: 0 }, verdict: "fail", durationMs: Date.now() - start, }; } const buildResult = run(spec.buildCmd, spec.cwd); const build = { pass: buildResult.exitCode === 0, exitCode: buildResult.exitCode, output: buildResult.output }; if (build.exitCode !== 0) { return { build, test: { pass: false, exitCode: -1, output: "", passed: 0, failed: 0, total: 0 }, verdict: "fail", durationMs: Date.now() - start, }; } let lint: { pass: boolean; exitCode: number; output: string } | undefined; if (spec.lintCmd) { const r = run(spec.lintCmd, spec.cwd); lint = { pass: r.exitCode === 0, exitCode: r.exitCode, output: r.output }; } const testResult = run(spec.testCmd, spec.cwd); const counts = parseTestOutput(testResult.output); const testPass = testResult.exitCode === 0 && counts.failed === 0; const test = { pass: testPass, exitCode: testResult.exitCode, output: testResult.output, ...counts }; return { build, lint, test, verdict: testPass ? "pass" : "fail", durationMs: Date.now() - start, }; } export function verifyAgentTask(spec: VerifyGateSpec): { result: VerifyGateResult; passed: boolean; worktreeCleaned: boolean; } { const result = runVerifyGate(spec); const passed = result.verdict === "pass"; return { result, passed, worktreeCleaned: false }; } export function verifyAndClean(spec: VerifyGateSpec, worktreeName: string, wm: WorktreeManager): { result: VerifyGateResult; passed: boolean; worktreeCleaned: boolean; } { const result = runVerifyGate(spec); const passed = result.verdict === "pass"; if (!passed && wm) wm.remove(worktreeName); return { result, passed, worktreeCleaned: !passed }; }