Compare commits

..

No commits in common. "f6e29a5cae57b24cdf28f2db454e0d8b610467ae" and "85fac3158d84809a66ce013477bf90e54d851436" have entirely different histories.

9 changed files with 202 additions and 127 deletions

View File

@ -1,109 +0,0 @@
import type { Role, RoleResult, StartStep } from "@uncaged/nerve-core";
import { hermesAdapter } from "@uncaged/nerve-adapter-hermes";
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<typeof committerMetaSchema>;
export type BuildWorkspaceCommitterDeps = {
extract: LlmExtractorConfig;
nerveRoot: string;
workflowName: string;
conventionalCommitScopeHint: string;
branchCheckoutExample: string;
};
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/<short-slug>\` or \`feat/<short-slug>\` 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 "<message>"\` (use multiple \`-m\` if you need a body).
6. \`git push -u origin <branch-name>\`
**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 buildWorkspaceCommitterRole({
extract,
nerveRoot,
workflowName,
conventionalCommitScopeHint,
branchCheckoutExample,
}: BuildWorkspaceCommitterDeps): Role<CommitterMeta> {
const innerRole = createRole(
hermesAdapter,
async (start: StartStep) =>
workspaceCommitterPrompt({
threadId: start.meta.threadId,
nerveRoot,
workflowName,
conventionalCommitScopeHint,
branchCheckoutExample,
}),
committerMetaSchema,
extract,
);
return async (start, _messages): Promise<RoleResult<CommitterMeta>> => {
if (isDryRun(start)) {
return {
content: "[dry-run] committer skipped (no git branch/commit/push)",
meta: { committed: true },
};
}
const innerStart = {
...start,
meta: { ...start.meta, workdir: nerveRoot },
} as StartStep;
try {
return await innerRole(innerStart, _messages);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
return {
content: `committer failed: ${msg}`,
meta: { committed: false },
};
}
};
}

View File

@ -63,8 +63,6 @@ export function buildDevelopSenseWorkflow({
extract,
nerveRoot: cwd,
workflowName: "develop-sense",
conventionalCommitScopeHint: "sense",
branchCheckoutExample: "git checkout -b fix/sense-export-path",
}),
};

View File

@ -1,6 +1,62 @@
export {
buildWorkspaceCommitterRole,
committerMetaSchema,
type BuildWorkspaceCommitterDeps,
type CommitterMeta,
} from "../../../_shared/workspace-committer.js";
import type { Role, RoleResult, StartStep } from "@uncaged/nerve-core";
import { hermesAdapter } from "@uncaged/nerve-adapter-hermes";
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
import { createRole, isDryRun } from "@uncaged/nerve-workflow-utils";
import { z } from "zod";
import { workspaceCommitterPrompt } from "./prompt.js";
export const committerMetaSchema = z.object({
committed: z
.boolean()
.describe("true if branch created, changes committed, and pushed successfully"),
});
export type CommitterMeta = z.infer<typeof committerMetaSchema>;
export type BuildWorkspaceCommitterDeps = {
extract: LlmExtractorConfig;
nerveRoot: string;
workflowName: string;
};
export function buildWorkspaceCommitterRole({
extract,
nerveRoot,
workflowName,
}: BuildWorkspaceCommitterDeps): Role<CommitterMeta> {
const innerRole = createRole(
hermesAdapter,
async (start: StartStep) =>
workspaceCommitterPrompt({
threadId: start.meta.threadId,
nerveRoot,
workflowName,
}),
committerMetaSchema,
extract,
);
return async (start, _messages): Promise<RoleResult<CommitterMeta>> => {
if (isDryRun(start)) {
return {
content: "[dry-run] committer skipped (no git branch/commit/push)",
meta: { committed: true },
};
}
const innerStart = {
...start,
meta: { ...start.meta, workdir: nerveRoot },
} as StartStep;
try {
return await innerRole(innerStart, _messages);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
return {
content: `committer failed: ${msg}`,
meta: { committed: false },
};
}
};
}

View File

@ -0,0 +1,38 @@
export function workspaceCommitterPrompt({
threadId,
nerveRoot,
workflowName,
}: {
threadId: string;
nerveRoot: string;
workflowName: 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/<short-slug>\` or \`feat/<short-slug>\` with a lowercase hyphenated slug from the thread (planner/coder context).
- Example: \`git checkout -b fix/sense-export-path\`
3. \`git add -A\`
4. Write a **conventional commit** message summarizing what changed and why (scope may be \`sense\` or similar).
5. \`git commit -m "<message>"\` (use multiple \`-m\` if you need a body).
6. \`git push -u origin <branch-name>\`
**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 }
\`\`\``;
}

View File

@ -10,5 +10,5 @@
"declaration": false,
"types": ["node"]
},
"include": ["./**/*.ts", "../_shared/**/*.ts"]
"include": ["./**/*.ts"]
}

View File

@ -63,8 +63,6 @@ export function buildDevelopWorkflow({
extract,
nerveRoot,
workflowName: "develop-workflow",
conventionalCommitScopeHint: "workflow",
branchCheckoutExample: "git checkout -b feat/workflow-new-step",
}),
};

View File

@ -1,6 +1,62 @@
export {
buildWorkspaceCommitterRole,
committerMetaSchema,
type BuildWorkspaceCommitterDeps,
type CommitterMeta,
} from "../../../_shared/workspace-committer.js";
import type { Role, RoleResult, StartStep } from "@uncaged/nerve-core";
import { hermesAdapter } from "@uncaged/nerve-adapter-hermes";
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
import { createRole, isDryRun } from "@uncaged/nerve-workflow-utils";
import { z } from "zod";
import { workspaceCommitterPrompt } from "./prompt.js";
export const committerMetaSchema = z.object({
committed: z
.boolean()
.describe("true if branch created, changes committed, and pushed successfully"),
});
export type CommitterMeta = z.infer<typeof committerMetaSchema>;
export type BuildWorkspaceCommitterDeps = {
extract: LlmExtractorConfig;
nerveRoot: string;
workflowName: string;
};
export function buildWorkspaceCommitterRole({
extract,
nerveRoot,
workflowName,
}: BuildWorkspaceCommitterDeps): Role<CommitterMeta> {
const innerRole = createRole(
hermesAdapter,
async (start: StartStep) =>
workspaceCommitterPrompt({
threadId: start.meta.threadId,
nerveRoot,
workflowName,
}),
committerMetaSchema,
extract,
);
return async (start, _messages): Promise<RoleResult<CommitterMeta>> => {
if (isDryRun(start)) {
return {
content: "[dry-run] committer skipped (no git branch/commit/push)",
meta: { committed: true },
};
}
const innerStart = {
...start,
meta: { ...start.meta, workdir: nerveRoot },
} as StartStep;
try {
return await innerRole(innerStart, _messages);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
return {
content: `committer failed: ${msg}`,
meta: { committed: false },
};
}
};
}

View File

@ -0,0 +1,38 @@
export function workspaceCommitterPrompt({
threadId,
nerveRoot,
workflowName,
}: {
threadId: string;
nerveRoot: string;
workflowName: 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/<short-slug>\` or \`feat/<short-slug>\` with a lowercase hyphenated slug from the thread (planner/coder context).
- Example: \`git checkout -b feat/workflow-new-step\`
3. \`git add -A\`
4. Write a **conventional commit** message summarizing what changed and why (scope may be \`workflow\` or similar).
5. \`git commit -m "<message>"\` (use multiple \`-m\` if you need a body).
6. \`git push -u origin <branch-name>\`
**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 }
\`\`\``;
}

View File

@ -9,5 +9,5 @@
"noEmit": true,
"types": ["node"]
},
"include": ["./**/*.ts", "../_shared/**/*.ts"]
"include": ["./**/*.ts"]
}