import type { AgentFn, Role, RoleResult, StartStep } from "@uncaged/nerve-core"; import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; import { createRole, isDryRun } from "@uncaged/nerve-workflow-utils"; import { z } from "zod"; export const committerMetaSchema = z.object({ committed: z .boolean() .describe("true if branch created, changes committed, and pushed successfully"), }); export type CommitterMeta = z.infer; function workspaceCommitterPrompt(threadId: string): string { return `You are the committer agent. The coder finished with a passing build; your job is to branch, commit, and push. 1. Read the workflow thread: \`nerve thread show ${threadId}\` — understand what was planned, coded, and reviewed. 2. Run \`git status\`. If nothing to commit, set committed=false. 3. Create a feature branch: infer a good \`fix/\` or \`feat/\` name from the thread context. 4. \`git add -A\` 5. Write a conventional commit message based on the thread context. 6. \`git commit -m ""\` — do NOT pass \`--author\`, use repo git config. 7. \`git push -u origin \` **committed=true** only if branch was created, commit succeeded, and **push** succeeded. End your reply with a JSON line: \`\`\`json { "committed": true } \`\`\` or \`\`\`json { "committed": false } \`\`\``; } export function createWorkspaceCommitterRole( adapter: AgentFn, extract: LlmExtractorConfig, ): Role { const innerRole = createRole( adapter, async (start: StartStep) => workspaceCommitterPrompt(start.meta.threadId), committerMetaSchema, extract, ); return async (start, _messages): Promise> => { if (isDryRun(start)) { return { content: "[dry-run] committer skipped (no git branch/commit/push)", meta: { committed: true }, }; } try { return await innerRole(start, _messages); } catch (e) { const msg = e instanceof Error ? e.message : String(e); return { content: `committer failed: ${msg}`, meta: { committed: false }, }; } }; }