import { writeFileSync, mkdirSync } from "node:fs"; import { join } from "node:path"; import type { Role, RoleResult } from "@uncaged/nerve-core"; import { isDryRun, spawnSafe } from "@uncaged/nerve-workflow-utils"; export type CommitterMeta = { success: boolean; }; export type BuildCommitterDeps = { nerveRoot: string; }; function logPath(nerveRoot: string): string { return join(nerveRoot, "logs", `committer-${Date.now()}.log`); } function writeLog(path: string, content: string): void { mkdirSync(join(path, ".."), { recursive: true }); writeFileSync(path, content, "utf-8"); } export function buildCommitterRole({ nerveRoot }: BuildCommitterDeps): Role { return async (start, _messages) => { const dry = isDryRun(start); const file = logPath(nerveRoot); if (dry) { writeLog(file, "[dry-run] committer skipped\n"); return { content: `[dry-run] committer skipped — log: ${file}`, meta: { success: true }, } satisfies RoleResult; } const lines: string[] = []; let success = true; const run = async (cmd: string, args: string[]): Promise => { const r = await spawnSafe(cmd, args, { cwd: nerveRoot, env: null, timeoutMs: 60_000, dryRun: false }); if (r.ok) { lines.push(`$ ${cmd} ${args.join(" ")}`); if (r.value.stdout) lines.push(r.value.stdout); if (r.value.stderr) lines.push(r.value.stderr); lines.push(""); return true; } const e = r.error; lines.push(`$ ${cmd} ${args.join(" ")} — FAILED`); if (e.kind === "non_zero_exit") { lines.push(`exit ${e.exitCode}`); if (e.stdout) lines.push(e.stdout); if (e.stderr) lines.push(e.stderr); } else if (e.kind === "timeout") { lines.push("timeout"); if (e.stdout) lines.push(e.stdout); if (e.stderr) lines.push(e.stderr); } else { lines.push(e.message); } lines.push(""); return false; }; await run("git", ["add", "-A"]); // Use a generic message; git diff will show what actually changed const committed = await run("git", ["commit", "-m", "chore(workflow): auto-generated commit"]); if (!committed) { success = false; } else { const pushed = await run("git", ["push"]); if (!pushed) success = false; } const log = lines.join("\n"); writeLog(file, log); const summary = success ? "committed and pushed" : "commit/push failed — see log"; return { content: `committer: ${summary}\nLog: ${file}`, meta: { success }, } satisfies RoleResult; }; }