From 0a9da468f74fde02e2744a5973e625827df0b3bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Wed, 29 Apr 2026 12:52:56 +0000 Subject: [PATCH] =?UTF-8?q?refactor:=20simplify=20workspace=20committer=20?= =?UTF-8?q?=E2=80=94=20agent=20infers=20context=20from=20thread?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove nerveRoot, workflowName, conventionalCommitScopeHint, branchCheckoutExample params. Signature: createWorkspaceCommitterRole(adapter, extract) Agent reads thread history to decide branch name, scope, and commit message. Closes #17 --- workflows/_shared/workspace-committer.ts | 70 ++++--------------- workflows/develop-sense/build.ts | 8 +-- workflows/develop-sense/roles/committer.ts | 1 - workflows/develop-workflow/build.ts | 8 +-- workflows/develop-workflow/roles/committer.ts | 1 - workflows/solve-issue/build.ts | 2 +- .../solve-issue/roles/committer/index.ts | 29 +------- .../solve-issue/roles/committer/prompt.ts | 45 ++++-------- 8 files changed, 31 insertions(+), 133 deletions(-) diff --git a/workflows/_shared/workspace-committer.ts b/workflows/_shared/workspace-committer.ts index 938b4b3..94ca540 100644 --- a/workflows/_shared/workspace-committer.ts +++ b/workflows/_shared/workspace-committer.ts @@ -10,44 +10,16 @@ export const committerMetaSchema = z.object({ }); export type CommitterMeta = z.infer; -export type BuildWorkspaceCommitterDeps = { - extract: LlmExtractorConfig; - nerveRoot: string; - workflowName: string; - conventionalCommitScopeHint: string; - branchCheckoutExample: string; -}; +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. -function workspaceCommitterPrompt({ - threadId, - nerveRoot, - workflowName, - conventionalCommitScopeHint, - branchCheckoutExample, -}: { - threadId: string; - nerveRoot: string; - workflowName: string; - conventionalCommitScopeHint: string; - branchCheckoutExample: string; -}): string { - return `You are the **committer** agent (Hermes) for the **${workflowName}** workflow. The coder finished with a passing build; you branch, commit, and push workspace changes. - -## Context - -1. Read the workflow thread: \`nerve thread show ${threadId}\` -2. Your git repository root is: \`${nerveRoot}\` — \`cd\` there for all git commands. - -## Steps (in order) - -1. Run \`git status\`. There should be uncommitted changes from the coder. If there is nothing to commit, set **committed** to false and explain. -2. Create a short-lived branch (do not commit directly on the default branch if it would mix unrelated work): - - Prefer \`fix/\` or \`feat/\` with a lowercase hyphenated slug from the thread (planner/coder context). - - Example: \`${branchCheckoutExample}\` -3. \`git add -A\` -4. Write a **conventional commit** message summarizing what changed and why (scope may be \`${conventionalCommitScopeHint}\` or similar). -5. \`git commit -m ""\` (use multiple \`-m\` if you need a body). Do **not** pass \`--author\` — always use the repo's local git config identity. -6. \`git push -u origin \` +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. @@ -63,24 +35,11 @@ or export function createWorkspaceCommitterRole( adapter: AgentFn, - { - extract, - nerveRoot, - workflowName, - conventionalCommitScopeHint, - branchCheckoutExample, - }: BuildWorkspaceCommitterDeps, + extract: LlmExtractorConfig, ): Role { const innerRole = createRole( adapter, - async (start: StartStep) => - workspaceCommitterPrompt({ - threadId: start.meta.threadId, - nerveRoot, - workflowName, - conventionalCommitScopeHint, - branchCheckoutExample, - }), + async (start: StartStep) => workspaceCommitterPrompt(start.meta.threadId), committerMetaSchema, extract, ); @@ -93,13 +52,8 @@ export function createWorkspaceCommitterRole( }; } - const innerStart = { - ...start, - meta: { ...start.meta, workdir: nerveRoot }, - } as StartStep; - try { - return await innerRole(innerStart, _messages); + return await innerRole(start, _messages); } catch (e) { const msg = e instanceof Error ? e.message : String(e); return { diff --git a/workflows/develop-sense/build.ts b/workflows/develop-sense/build.ts index e49b0e1..94d786a 100644 --- a/workflows/develop-sense/build.ts +++ b/workflows/develop-sense/build.ts @@ -28,13 +28,7 @@ export function createDevelopSenseWorkflow({ coder: createCoderRole(a('coder'), extract), reviewer: createReviewerRole(a('reviewer'), extract, cwd), tester: createTesterRole(a('tester'), extract, cwd), - committer: createWorkspaceCommitterRole(a('committer'), { - extract, - nerveRoot: cwd, - workflowName: "develop-sense", - conventionalCommitScopeHint: "sense", - branchCheckoutExample: "git checkout -b fix/sense-export-path", - }), + committer: createWorkspaceCommitterRole(a('committer'), extract), }; return { diff --git a/workflows/develop-sense/roles/committer.ts b/workflows/develop-sense/roles/committer.ts index 44ed5e8..c303dba 100644 --- a/workflows/develop-sense/roles/committer.ts +++ b/workflows/develop-sense/roles/committer.ts @@ -1,6 +1,5 @@ export { createWorkspaceCommitterRole, committerMetaSchema, - type BuildWorkspaceCommitterDeps, type CommitterMeta, } from "../../_shared/workspace-committer.js"; diff --git a/workflows/develop-workflow/build.ts b/workflows/develop-workflow/build.ts index c64c139..a76d394 100644 --- a/workflows/develop-workflow/build.ts +++ b/workflows/develop-workflow/build.ts @@ -28,13 +28,7 @@ export function createDevelopWorkflowWorkflow({ coder: createCoderRole(a('coder'), extract), reviewer: createReviewerRole(a('reviewer'), extract, nerveRoot), tester: createTesterRole(a('tester'), extract, nerveRoot), - committer: createWorkspaceCommitterRole(a('committer'), { - extract, - nerveRoot, - workflowName: "develop-workflow", - conventionalCommitScopeHint: "workflow", - branchCheckoutExample: "git checkout -b feat/workflow-new-step", - }), + committer: createWorkspaceCommitterRole(a('committer'), extract), }; return { diff --git a/workflows/develop-workflow/roles/committer.ts b/workflows/develop-workflow/roles/committer.ts index 44ed5e8..c303dba 100644 --- a/workflows/develop-workflow/roles/committer.ts +++ b/workflows/develop-workflow/roles/committer.ts @@ -1,6 +1,5 @@ export { createWorkspaceCommitterRole, committerMetaSchema, - type BuildWorkspaceCommitterDeps, type CommitterMeta, } from "../../_shared/workspace-committer.js"; diff --git a/workflows/solve-issue/build.ts b/workflows/solve-issue/build.ts index d43499c..3dbfecd 100644 --- a/workflows/solve-issue/build.ts +++ b/workflows/solve-issue/build.ts @@ -33,7 +33,7 @@ export function createSolveIssueWorkflow({ prepare: createPrepareRole(a("prepare"), extract), plan: createPlanRole(a("plan"), { extract, nerveRoot }), implement: createImplementRole(a("implement"), { extract, nerveRoot }), - committer: createCommitterRole(a("committer"), { extract, nerveRoot }), + committer: createCommitterRole(a("committer"), extract), review: createReviewRole(a("review"), extract, nerveRoot), test: createTestRole(a("test"), extract), publish: createPublishRole(a("publish"), { extract, nerveRoot }), diff --git a/workflows/solve-issue/roles/committer/index.ts b/workflows/solve-issue/roles/committer/index.ts index 2070cd2..fd0ecb9 100644 --- a/workflows/solve-issue/roles/committer/index.ts +++ b/workflows/solve-issue/roles/committer/index.ts @@ -3,7 +3,6 @@ import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; import { createRole, isDryRun } from "@uncaged/nerve-workflow-utils"; import { z } from "zod"; -import { resolveRepoCwd } from "../../lib/repo-context.js"; import { committerPrompt } from "./prompt.js"; export const committerMetaSchema = z.object({ @@ -13,35 +12,18 @@ export const committerMetaSchema = z.object({ }); export type CommitterMeta = z.infer; -export type CreateCommitterRoleDeps = { - extract: LlmExtractorConfig; - nerveRoot: string; -}; - export function createCommitterRole( adapter: AgentFn, - { extract, nerveRoot }: CreateCommitterRoleDeps, + extract: LlmExtractorConfig, ): Role { const innerRole = createRole( adapter, - async (start: StartStep) => - committerPrompt({ - threadId: start.meta.threadId, - nerveRoot, - }), + async (start: StartStep) => committerPrompt({ threadId: start.meta.threadId }), committerMetaSchema, extract, ); return async (start: StartStep, messages: WorkflowMessage[]): Promise> => { - const cwd = resolveRepoCwd(messages); - if (cwd === null) { - return { - content: "committer cannot run: missing repo path in thread markers", - meta: { committed: false }, - }; - } - if (isDryRun(start)) { return { content: "[dry-run] committer skipped (no git branch/commit/push)", @@ -49,13 +31,8 @@ export function createCommitterRole( }; } - const innerStart = { - ...start, - meta: { ...start.meta, workdir: cwd }, - } as StartStep; - try { - return await innerRole(innerStart, messages); + return await innerRole(start, messages); } catch (e) { const msg = e instanceof Error ? e.message : String(e); return { diff --git a/workflows/solve-issue/roles/committer/prompt.ts b/workflows/solve-issue/roles/committer/prompt.ts index 18e2a40..474e2f7 100644 --- a/workflows/solve-issue/roles/committer/prompt.ts +++ b/workflows/solve-issue/roles/committer/prompt.ts @@ -1,40 +1,21 @@ -export function committerPrompt({ - threadId, - nerveRoot, -}: { - threadId: string; - nerveRoot: string; -}): string { - return `You are the **committer** agent (Hermes). The **implement** step finished with a passing build; your job is to put those changes on a feature branch and push to **origin**. +export function committerPrompt({ threadId }: { threadId: string }): string { + return `You are the committer agent. The **implement** step finished with a passing build; your job is to branch, commit, and push. -## Context - -- Read the full workflow thread: \`nerve thread show ${threadId}\` -- Optional workspace tone: \`cat ${nerveRoot}/CONVENTIONS.md\` - -## Find repo and issue markers - -In the thread, locate \`---SOLVE_ISSUE_PARSE---\` and \`---SOLVE_ISSUE_REPO---\`. From them you need: - -- Issue **number** and **title** (from PARSE; title for the branch slug) -- Repo checkout **path** (from REPO \`path\`) — this is your working copy; your shell cwd should be this directory. - -## Steps (in order) - -1. \`cd\` to the repo **path** from SOLVE_ISSUE_REPO. -2. Run \`git rev-parse --abbrev-ref HEAD\` and compare with **defaultBranch** from SOLVE_ISSUE_REPO. Implement leaves changes **uncommitted** on the default branch — you should be on that branch with a dirty working tree. If you are not on the default branch, or the tree is clean with nothing to commit when you expected changes, set **committed** to false and explain (avoids empty PRs from wrong state). -3. Run \`git status\`. There should be **uncommitted** changes from implement. If the tree is clean with nothing to commit, set **committed** to false and explain. -4. Create a feature branch (do not work on the default branch directly): +1. Read the workflow thread: \`nerve thread show ${threadId}\` — understand what was planned, implemented, and reviewed. +2. In the thread, locate \`---SOLVE_ISSUE_PARSE---\` and \`---SOLVE_ISSUE_REPO---\`. From them you need issue **number**, **title** (for the branch slug), repo **path**, and **defaultBranch**. +3. \`cd\` to the repo **path** from the markers. Optionally read \`CONVENTIONS.md\` in that repo root if present. +4. Run \`git rev-parse --abbrev-ref HEAD\` and compare with **defaultBranch** from the markers. Implement leaves changes uncommitted on the default branch — you should be on that branch with a dirty working tree. If you are not on the default branch, or the tree is clean when you expected changes, set **committed** to false and explain. +5. Run \`git status\`. If there is nothing to commit, set **committed** to false and explain. +6. Create a feature branch (do not commit directly on the default branch if it would mix unrelated work): - Name: \`fix/-\` for fixes, or \`feat/-\` if the issue is clearly a feature. - - **number** from SOLVE_ISSUE_PARSE. - **slug**: lowercase, hyphens only, short (from issue title words). - Example: \`git checkout -b fix/42-auth-timeout\` -5. \`git add -A\` -6. Write a **conventional commit** message (e.g. \`fix(scope): summary\` or \`feat(scope): summary\`) describing **what** changed and **why**, using the thread (plan + implement context). -7. \`git commit -m ""\` — use a single \`-m\` for a one-line message, or multiple \`-m\` for body paragraphs if needed. Do **not** pass \`--author\` — always use the repo's local git config identity. -8. \`git push -u origin \` +7. \`git add -A\` +8. Write a **conventional commit** message describing what changed and why, using the thread context. +9. \`git commit -m ""\` — do NOT pass \`--author\`, use repo git config. +10. \`git push -u origin \` -**committed=true** only if you created the branch, committed successfully, and **push** succeeded. +**committed=true** only if branch was created, commit succeeded, and **push** succeeded. End your reply with a JSON line: \`\`\`json -- 2.43.0