refactor: decouple adapters from workflow factories + createXxxRole #16

Merged
xiaoju merged 3 commits from refactor/15-decouple-adapters into master 2026-04-29 12:44:19 +00:00
25 changed files with 337 additions and 221 deletions
Showing only changes of commit 1683e41b05 - Show all commits

View File

@ -1,5 +1,4 @@
import type { Role, RoleResult, StartStep } from "@uncaged/nerve-core"; import type { AgentFn, Role, RoleResult, StartStep } from "@uncaged/nerve-core";
import { hermesAdapter } from "@uncaged/nerve-adapter-hermes";
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
import { createRole, isDryRun } from "@uncaged/nerve-workflow-utils"; import { createRole, isDryRun } from "@uncaged/nerve-workflow-utils";
import { z } from "zod"; import { z } from "zod";
@ -62,15 +61,18 @@ or
\`\`\``; \`\`\``;
} }
export function buildWorkspaceCommitterRole({ export function createWorkspaceCommitterRole(
adapter: AgentFn,
{
extract, extract,
nerveRoot, nerveRoot,
workflowName, workflowName,
conventionalCommitScopeHint, conventionalCommitScopeHint,
branchCheckoutExample, branchCheckoutExample,
}: BuildWorkspaceCommitterDeps): Role<CommitterMeta> { }: BuildWorkspaceCommitterDeps,
): Role<CommitterMeta> {
const innerRole = createRole( const innerRole = createRole(
hermesAdapter, adapter,
async (start: StartStep) => async (start: StartStep) =>
workspaceCommitterPrompt({ workspaceCommitterPrompt({
threadId: start.meta.threadId, threadId: start.meta.threadId,

View File

@ -1,75 +1,31 @@
import type { StartStep, WorkflowDefinition } from "@uncaged/nerve-core"; import type { AgentFn, WorkflowDefinition } from "@uncaged/nerve-core";
import { createCursorAdapter, cursorAdapter } from "@uncaged/nerve-adapter-cursor";
import { hermesAdapter } from "@uncaged/nerve-adapter-hermes";
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
import { createRole } from "@uncaged/nerve-workflow-utils";
import {
coderMetaSchema,
coderPrompt,
} from "./roles/coder.js";
import {
buildWorkspaceCommitterRole,
} from "./roles/committer.js";
import {
plannerMetaSchema,
plannerPrompt,
} from "./roles/planner.js";
import {
reviewerMetaSchema,
reviewerPrompt,
} from "./roles/reviewer.js";
import {
testerMetaSchema,
testerPrompt,
} from "./roles/tester.js";
import { moderator } from "./moderator.js"; import { moderator } from "./moderator.js";
import type { SenseMeta } from "./moderator.js"; import type { SenseMeta } from "./moderator.js";
import { createCoderRole } from "./roles/coder.js";
import { createWorkspaceCommitterRole } from "./roles/committer.js";
import { createPlannerRole } from "./roles/planner.js";
import { createReviewerRole } from "./roles/reviewer.js";
import { createTesterRole } from "./roles/tester.js";
export type BuildDevelopSenseDeps = { export type CreateDevelopSenseDeps = {
adapters: Record<string, AgentFn>;
extract: LlmExtractorConfig; extract: LlmExtractorConfig;
cwd: string; cwd: string;
}; };
const CURSOR_TIMEOUT_MS = 300_000; export function createDevelopSenseWorkflow({
adapters,
export function buildDevelopSenseWorkflow({
extract, extract,
cwd, cwd,
}: BuildDevelopSenseDeps): WorkflowDefinition<SenseMeta> { }: CreateDevelopSenseDeps): WorkflowDefinition<SenseMeta> {
const roles = { const roles = {
planner: createRole( planner: createPlannerRole(adapters.planner, extract),
createCursorAdapter({ coder: createCoderRole(adapters.coder, extract),
type: "cursor", reviewer: createReviewerRole(adapters.reviewer, extract, cwd),
mode: "ask", tester: createTesterRole(adapters.tester, extract, cwd),
model: "auto", committer: createWorkspaceCommitterRole(adapters.committer, {
timeout: CURSOR_TIMEOUT_MS,
}),
async (start: StartStep) => plannerPrompt({ threadId: start.meta.threadId }),
plannerMetaSchema,
extract,
),
coder: createRole(
cursorAdapter,
async (start: StartStep) => coderPrompt({ threadId: start.meta.threadId }),
coderMetaSchema,
extract,
),
reviewer: createRole(
hermesAdapter,
async (start: StartStep) =>
reviewerPrompt({ threadId: start.meta.threadId, nerveRoot: cwd }),
reviewerMetaSchema,
extract,
),
tester: createRole(
hermesAdapter,
async (start: StartStep) =>
testerPrompt({ threadId: start.meta.threadId, nerveRoot: cwd }),
testerMetaSchema,
extract,
),
committer: buildWorkspaceCommitterRole({
extract, extract,
nerveRoot: cwd, nerveRoot: cwd,
workflowName: "develop-sense", workflowName: "develop-sense",

View File

@ -1,5 +1,7 @@
import { join } from "node:path"; import { join } from "node:path";
import { buildDevelopSenseWorkflow } from "./build.js"; import { createCursorAdapter, cursorAdapter } from "@uncaged/nerve-adapter-cursor";
import { hermesAdapter } from "@uncaged/nerve-adapter-hermes";
import { createDevelopSenseWorkflow } from "./build.js";
const HOME = process.env.HOME ?? "/home/azureuser"; const HOME = process.env.HOME ?? "/home/azureuser";
const NERVE_ROOT = join(HOME, ".uncaged-nerve"); const NERVE_ROOT = join(HOME, ".uncaged-nerve");
@ -11,7 +13,21 @@ if (!apiKey || !baseUrl) {
throw new Error("Set DASHSCOPE_API_KEY and DASHSCOPE_BASE_URL"); throw new Error("Set DASHSCOPE_API_KEY and DASHSCOPE_BASE_URL");
} }
const workflow = buildDevelopSenseWorkflow({ const CURSOR_TIMEOUT_MS = 300_000;
const workflow = createDevelopSenseWorkflow({
adapters: {
planner: createCursorAdapter({
type: "cursor",
mode: "ask",
model: "auto",
timeout: CURSOR_TIMEOUT_MS,
}),
coder: cursorAdapter,
reviewer: hermesAdapter,
tester: hermesAdapter,
committer: hermesAdapter,
},
extract: { provider: { apiKey, baseUrl, model } }, extract: { provider: { apiKey, baseUrl, model } },
cwd: NERVE_ROOT, cwd: NERVE_ROOT,
}); });

View File

@ -1,3 +1,6 @@
import type { AgentFn, Role, StartStep } from "@uncaged/nerve-core";
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
import { createRole } from "@uncaged/nerve-workflow-utils";
import { z } from "zod"; import { z } from "zod";
export const coderMetaSchema = z.object({ export const coderMetaSchema = z.object({
@ -36,3 +39,12 @@ Return \`done: true\` ONLY when ALL of the following are true:
Return \`done: false\` if you made progress but there is still work to do.`; Return \`done: false\` if you made progress but there is still work to do.`;
} }
export function createCoderRole(adapter: AgentFn, extract: LlmExtractorConfig): Role<CoderMeta> {
return createRole(
adapter,
async (start: StartStep) => coderPrompt({ threadId: start.meta.threadId }),
coderMetaSchema,
extract,
);
}

View File

@ -1,5 +1,5 @@
export { export {
buildWorkspaceCommitterRole, createWorkspaceCommitterRole,
committerMetaSchema, committerMetaSchema,
type BuildWorkspaceCommitterDeps, type BuildWorkspaceCommitterDeps,
type CommitterMeta, type CommitterMeta,

View File

@ -1,3 +1,6 @@
import type { AgentFn, Role, StartStep } from "@uncaged/nerve-core";
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
import { createRole } from "@uncaged/nerve-workflow-utils";
import { z } from "zod"; import { z } from "zod";
export const plannerMetaSchema = z.object({ export const plannerMetaSchema = z.object({
@ -22,3 +25,12 @@ Pick a good kebab-case name for this sense. Produce a PLAN (not code) in markdow
Output ONLY the plan. Be precise and implementation-ready.`; Output ONLY the plan. Be precise and implementation-ready.`;
} }
export function createPlannerRole(adapter: AgentFn, extract: LlmExtractorConfig): Role<PlannerMeta> {
return createRole(
adapter,
async (start: StartStep) => plannerPrompt({ threadId: start.meta.threadId }),
plannerMetaSchema,
extract,
);
}

View File

@ -1,3 +1,6 @@
import type { AgentFn, Role, StartStep } from "@uncaged/nerve-core";
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
import { createRole } from "@uncaged/nerve-workflow-utils";
import { z } from "zod"; import { z } from "zod";
export const reviewerMetaSchema = z.object({ export const reviewerMetaSchema = z.object({
@ -43,3 +46,17 @@ or
{ "approved": false } { "approved": false }
\`\`\``; \`\`\``;
} }
export function createReviewerRole(
adapter: AgentFn,
extract: LlmExtractorConfig,
nerveRoot: string,
): Role<ReviewerMeta> {
return createRole(
adapter,
async (start: StartStep) =>
reviewerPrompt({ threadId: start.meta.threadId, nerveRoot }),
reviewerMetaSchema,
extract,
);
}

View File

@ -1,3 +1,6 @@
import type { AgentFn, Role, StartStep } from "@uncaged/nerve-core";
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
import { createRole } from "@uncaged/nerve-workflow-utils";
import { z } from "zod"; import { z } from "zod";
export const testerMetaSchema = z.object({ export const testerMetaSchema = z.object({
@ -39,3 +42,17 @@ If any step fails, include the relevant error output.
Output a clear summary: what you checked, what passed, what failed, and why.`; Output a clear summary: what you checked, what passed, what failed, and why.`;
} }
export function createTesterRole(
adapter: AgentFn,
extract: LlmExtractorConfig,
nerveRoot: string,
): Role<TesterMeta> {
return createRole(
adapter,
async (start: StartStep) =>
testerPrompt({ threadId: start.meta.threadId, nerveRoot }),
testerMetaSchema,
extract,
);
}

View File

@ -1,75 +1,31 @@
import type { StartStep, WorkflowDefinition } from "@uncaged/nerve-core"; import type { AgentFn, WorkflowDefinition } from "@uncaged/nerve-core";
import { createCursorAdapter, cursorAdapter } from "@uncaged/nerve-adapter-cursor";
import { hermesAdapter } from "@uncaged/nerve-adapter-hermes";
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
import { createRole } from "@uncaged/nerve-workflow-utils";
import {
coderMetaSchema,
coderPrompt,
} from "./roles/coder.js";
import {
buildWorkspaceCommitterRole,
} from "./roles/committer.js";
import {
plannerMetaSchema,
plannerPrompt,
} from "./roles/planner.js";
import {
reviewerMetaSchema,
reviewerPrompt,
} from "./roles/reviewer.js";
import {
testerMetaSchema,
testerPrompt,
} from "./roles/tester.js";
import { moderator } from "./moderator.js"; import { moderator } from "./moderator.js";
import type { WorkflowMeta } from "./moderator.js"; import type { WorkflowMeta } from "./moderator.js";
import { createCoderRole } from "./roles/coder.js";
import { createWorkspaceCommitterRole } from "./roles/committer.js";
import { createPlannerRole } from "./roles/planner.js";
import { createReviewerRole } from "./roles/reviewer.js";
import { createTesterRole } from "./roles/tester.js";
export type BuildDevelopWorkflowDeps = { export type CreateDevelopWorkflowDeps = {
adapters: Record<string, AgentFn>;
extract: LlmExtractorConfig; extract: LlmExtractorConfig;
nerveRoot: string; nerveRoot: string;
}; };
const CURSOR_TIMEOUT_MS = 300_000; export function createDevelopWorkflowWorkflow({
adapters,
export function buildDevelopWorkflow({
extract, extract,
nerveRoot, nerveRoot,
}: BuildDevelopWorkflowDeps): WorkflowDefinition<WorkflowMeta> { }: CreateDevelopWorkflowDeps): WorkflowDefinition<WorkflowMeta> {
const roles = { const roles = {
planner: createRole( planner: createPlannerRole(adapters.planner, extract),
createCursorAdapter({ coder: createCoderRole(adapters.coder, extract),
type: "cursor", reviewer: createReviewerRole(adapters.reviewer, extract, nerveRoot),
mode: "ask", tester: createTesterRole(adapters.tester, extract, nerveRoot),
model: "auto", committer: createWorkspaceCommitterRole(adapters.committer, {
timeout: CURSOR_TIMEOUT_MS,
}),
async (start: StartStep) => plannerPrompt({ threadId: start.meta.threadId }),
plannerMetaSchema,
extract,
),
coder: createRole(
cursorAdapter,
async (start: StartStep) => coderPrompt({ threadId: start.meta.threadId }),
coderMetaSchema,
extract,
),
reviewer: createRole(
hermesAdapter,
async (start: StartStep) =>
reviewerPrompt({ threadId: start.meta.threadId, nerveRoot }),
reviewerMetaSchema,
extract,
),
tester: createRole(
hermesAdapter,
async (start: StartStep) =>
testerPrompt({ threadId: start.meta.threadId, nerveRoot }),
testerMetaSchema,
extract,
),
committer: buildWorkspaceCommitterRole({
extract, extract,
nerveRoot, nerveRoot,
workflowName: "develop-workflow", workflowName: "develop-workflow",

View File

@ -1,5 +1,7 @@
import { join } from "node:path"; import { join } from "node:path";
import { buildDevelopWorkflow } from "./build.js"; import { createCursorAdapter, cursorAdapter } from "@uncaged/nerve-adapter-cursor";
import { hermesAdapter } from "@uncaged/nerve-adapter-hermes";
import { createDevelopWorkflowWorkflow } from "./build.js";
const HOME = process.env.HOME ?? "/home/azureuser"; const HOME = process.env.HOME ?? "/home/azureuser";
const NERVE_ROOT = join(HOME, ".uncaged-nerve"); const NERVE_ROOT = join(HOME, ".uncaged-nerve");
@ -12,7 +14,21 @@ if (!apiKey || !baseUrl) {
throw new Error("Set DASHSCOPE_API_KEY and DASHSCOPE_BASE_URL"); throw new Error("Set DASHSCOPE_API_KEY and DASHSCOPE_BASE_URL");
} }
const workflow = buildDevelopWorkflow({ const CURSOR_TIMEOUT_MS = 300_000;
const workflow = createDevelopWorkflowWorkflow({
adapters: {
planner: createCursorAdapter({
type: "cursor",
mode: "ask",
model: "auto",
timeout: CURSOR_TIMEOUT_MS,
}),
coder: cursorAdapter,
reviewer: hermesAdapter,
tester: hermesAdapter,
committer: hermesAdapter,
},
extract: { provider: { apiKey, baseUrl, model } }, extract: { provider: { apiKey, baseUrl, model } },
nerveRoot: NERVE_ROOT, nerveRoot: NERVE_ROOT,
}); });

View File

@ -1,3 +1,6 @@
import type { AgentFn, Role, StartStep } from "@uncaged/nerve-core";
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
import { createRole } from "@uncaged/nerve-workflow-utils";
import { z } from "zod"; import { z } from "zod";
export const coderMetaSchema = z.object({ export const coderMetaSchema = z.object({
@ -55,3 +58,12 @@ Return \`done: true\` ONLY when ALL of the following are true:
Return \`done: false\` if you made progress but there is still work to do, or if build/lint has errors you plan to fix in the next iteration.`; Return \`done: false\` if you made progress but there is still work to do, or if build/lint has errors you plan to fix in the next iteration.`;
} }
export function createCoderRole(adapter: AgentFn, extract: LlmExtractorConfig): Role<CoderMeta> {
return createRole(
adapter,
async (start: StartStep) => coderPrompt({ threadId: start.meta.threadId }),
coderMetaSchema,
extract,
);
}

View File

@ -1,5 +1,5 @@
export { export {
buildWorkspaceCommitterRole, createWorkspaceCommitterRole,
committerMetaSchema, committerMetaSchema,
type BuildWorkspaceCommitterDeps, type BuildWorkspaceCommitterDeps,
type CommitterMeta, type CommitterMeta,

View File

@ -1,3 +1,6 @@
import type { AgentFn, Role, StartStep } from "@uncaged/nerve-core";
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
import { createRole } from "@uncaged/nerve-workflow-utils";
import { z } from "zod"; import { z } from "zod";
export const plannerMetaSchema = z.object({ export const plannerMetaSchema = z.object({
@ -51,3 +54,12 @@ or
{ "ready": false } { "ready": false }
\`\`\``; \`\`\``;
} }
export function createPlannerRole(adapter: AgentFn, extract: LlmExtractorConfig): Role<PlannerMeta> {
return createRole(
adapter,
async (start: StartStep) => plannerPrompt({ threadId: start.meta.threadId }),
plannerMetaSchema,
extract,
);
}

View File

@ -1,3 +1,6 @@
import type { AgentFn, Role, StartStep } from "@uncaged/nerve-core";
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
import { createRole } from "@uncaged/nerve-workflow-utils";
import { z } from "zod"; import { z } from "zod";
export const reviewerMetaSchema = z.object({ export const reviewerMetaSchema = z.object({
@ -43,3 +46,17 @@ or
{ "approved": false } { "approved": false }
\`\`\``; \`\`\``;
} }
export function createReviewerRole(
adapter: AgentFn,
extract: LlmExtractorConfig,
nerveRoot: string,
): Role<ReviewerMeta> {
return createRole(
adapter,
async (start: StartStep) =>
reviewerPrompt({ threadId: start.meta.threadId, nerveRoot }),
reviewerMetaSchema,
extract,
);
}

View File

@ -1,3 +1,6 @@
import type { AgentFn, Role, StartStep } from "@uncaged/nerve-core";
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
import { createRole } from "@uncaged/nerve-workflow-utils";
import { z } from "zod"; import { z } from "zod";
export const testerMetaSchema = z.object({ export const testerMetaSchema = z.object({
@ -40,3 +43,17 @@ If any step fails, include the relevant error output.
Output a clear summary: what you checked, what passed, what failed, and why.`; Output a clear summary: what you checked, what passed, what failed, and why.`;
} }
export function createTesterRole(
adapter: AgentFn,
extract: LlmExtractorConfig,
nerveRoot: string,
): Role<TesterMeta> {
return createRole(
adapter,
async (start: StartStep) =>
testerPrompt({ threadId: start.meta.threadId, nerveRoot }),
testerMetaSchema,
extract,
);
}

View File

@ -1,64 +1,39 @@
import type { StartStep, WorkflowDefinition } from "@uncaged/nerve-core"; import type { AgentFn, WorkflowDefinition } from "@uncaged/nerve-core";
import { hermesAdapter } from "@uncaged/nerve-adapter-hermes";
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
import { createRole } from "@uncaged/nerve-workflow-utils";
import { moderator } from "./moderator.js"; import { moderator } from "./moderator.js";
import type { WorkflowMeta } from "./moderator.js"; import type { WorkflowMeta } from "./moderator.js";
import { buildCommitterRole } from "./roles/committer/index.js"; import { createCommitterRole } from "./roles/committer/index.js";
import { buildImplementRole } from "./roles/implement/index.js"; import { createImplementRole } from "./roles/implement/index.js";
import { buildPlanRole } from "./roles/plan/index.js"; import { createPlanRole } from "./roles/plan/index.js";
import { prepareMetaSchema } from "./roles/prepare/index.js"; import { createPrepareRole } from "./roles/prepare/index.js";
import { preparePrompt } from "./roles/prepare/prompt.js"; import { createPublishRole } from "./roles/publish/index.js";
import { buildPublishRole } from "./roles/publish/index.js"; import { createReadIssueRole } from "./roles/read-issue/index.js";
import { readIssueMetaSchema } from "./roles/read-issue/index.js"; import { createReviewRole } from "./roles/review/index.js";
import { readIssuePrompt } from "./roles/read-issue/prompt.js"; import { createTestRole } from "./roles/test/index.js";
import { reviewMetaSchema } from "./roles/review/index.js";
import { reviewPrompt } from "./roles/review/prompt.js";
import { testMetaSchema } from "./roles/test/index.js";
import { testPrompt } from "./roles/test/prompt.js";
export type BuildSolveIssueDeps = { export type CreateSolveIssueDeps = {
adapters: Record<string, AgentFn>;
nerveRoot: string; nerveRoot: string;
extract: LlmExtractorConfig; extract: LlmExtractorConfig;
}; };
export function buildSolveIssue({ export function createSolveIssueWorkflow({
adapters,
nerveRoot, nerveRoot,
extract, extract,
}: BuildSolveIssueDeps): WorkflowDefinition<WorkflowMeta> { }: CreateSolveIssueDeps): WorkflowDefinition<WorkflowMeta> {
return { return {
name: "solve-issue", name: "solve-issue",
roles: { roles: {
"read-issue": createRole( "read-issue": createReadIssueRole(adapters["read-issue"], extract),
hermesAdapter, prepare: createPrepareRole(adapters.prepare, extract),
async (start: StartStep) => readIssuePrompt({ threadId: start.meta.threadId }), plan: createPlanRole(adapters.plan, { extract, nerveRoot }),
readIssueMetaSchema, implement: createImplementRole(adapters.implement, { extract, nerveRoot }),
extract, committer: createCommitterRole(adapters.committer, { extract, nerveRoot }),
), review: createReviewRole(adapters.review, extract, nerveRoot),
prepare: createRole( test: createTestRole(adapters.test, extract),
hermesAdapter, publish: createPublishRole(adapters.publish, { extract, nerveRoot }),
async (start: StartStep) => preparePrompt({ threadId: start.meta.threadId }),
prepareMetaSchema,
extract,
),
plan: buildPlanRole({ extract, nerveRoot }),
implement: buildImplementRole({ extract, nerveRoot }),
committer: buildCommitterRole({ extract, nerveRoot }),
review: createRole(
hermesAdapter,
async (start: StartStep) =>
reviewPrompt({ threadId: start.meta.threadId, nerveRoot }),
reviewMetaSchema,
extract,
),
test: createRole(
hermesAdapter,
async (start: StartStep) => testPrompt({ threadId: start.meta.threadId }),
testMetaSchema,
extract,
),
publish: buildPublishRole({ extract, nerveRoot }),
}, },
moderator, moderator,
}; };

View File

@ -1,5 +1,7 @@
import { join } from "node:path"; import { join } from "node:path";
import { buildSolveIssue } from "./build.js"; import { createCursorAdapter } from "@uncaged/nerve-adapter-cursor";
import { hermesAdapter } from "@uncaged/nerve-adapter-hermes";
import { createSolveIssueWorkflow } from "./build.js";
import { resolveDashScopeProvider } from "./lib/provider.js"; import { resolveDashScopeProvider } from "./lib/provider.js";
const HOME = process.env.HOME ?? "/home/azureuser"; const HOME = process.env.HOME ?? "/home/azureuser";
@ -11,6 +13,30 @@ if (provider === null) {
throw new Error("Set DASHSCOPE_API_KEY and DASHSCOPE_BASE_URL (or cfg get equivalents)"); throw new Error("Set DASHSCOPE_API_KEY and DASHSCOPE_BASE_URL (or cfg get equivalents)");
} }
const workflow = buildSolveIssue({ nerveRoot: NERVE_ROOT, extract: { provider } }); const CURSOR_TIMEOUT_MS = 300_000;
const workflow = createSolveIssueWorkflow({
adapters: {
"read-issue": hermesAdapter,
prepare: hermesAdapter,
plan: createCursorAdapter({
type: "cursor",
mode: "ask",
model: "auto",
timeout: CURSOR_TIMEOUT_MS,
}),
implement: createCursorAdapter({
type: "cursor",
model: "auto",
timeout: CURSOR_TIMEOUT_MS,
}),
committer: hermesAdapter,
review: hermesAdapter,
test: hermesAdapter,
publish: hermesAdapter,
},
nerveRoot: NERVE_ROOT,
extract: { provider },
});
export default workflow; export default workflow;

View File

@ -1,5 +1,4 @@
import type { Role, RoleResult, StartStep, WorkflowMessage } from "@uncaged/nerve-core"; import type { AgentFn, Role, RoleResult, StartStep, WorkflowMessage } from "@uncaged/nerve-core";
import { hermesAdapter } from "@uncaged/nerve-adapter-hermes";
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
import { createRole, isDryRun } from "@uncaged/nerve-workflow-utils"; import { createRole, isDryRun } from "@uncaged/nerve-workflow-utils";
import { z } from "zod"; import { z } from "zod";
@ -14,14 +13,17 @@ export const committerMetaSchema = z.object({
}); });
export type CommitterMeta = z.infer<typeof committerMetaSchema>; export type CommitterMeta = z.infer<typeof committerMetaSchema>;
export type BuildCommitterDeps = { export type CreateCommitterRoleDeps = {
extract: LlmExtractorConfig; extract: LlmExtractorConfig;
nerveRoot: string; nerveRoot: string;
}; };
export function buildCommitterRole({ extract, nerveRoot }: BuildCommitterDeps): Role<CommitterMeta> { export function createCommitterRole(
adapter: AgentFn,
{ extract, nerveRoot }: CreateCommitterRoleDeps,
): Role<CommitterMeta> {
const innerRole = createRole( const innerRole = createRole(
hermesAdapter, adapter,
async (start: StartStep) => async (start: StartStep) =>
committerPrompt({ committerPrompt({
threadId: start.meta.threadId, threadId: start.meta.threadId,

View File

@ -1,5 +1,4 @@
import type { Role, RoleResult, StartStep, WorkflowMessage } from "@uncaged/nerve-core"; import type { AgentFn, Role, RoleResult, StartStep, WorkflowMessage } from "@uncaged/nerve-core";
import { createCursorAdapter } from "@uncaged/nerve-adapter-cursor";
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
import { createRole } from "@uncaged/nerve-workflow-utils"; import { createRole } from "@uncaged/nerve-workflow-utils";
import { z } from "zod"; import { z } from "zod";
@ -12,17 +11,15 @@ export const implementMetaSchema = z.object({
}); });
export type ImplementMeta = z.infer<typeof implementMetaSchema>; export type ImplementMeta = z.infer<typeof implementMetaSchema>;
export type BuildImplementDeps = { export type CreateImplementRoleDeps = {
extract: LlmExtractorConfig; extract: LlmExtractorConfig;
nerveRoot: string; nerveRoot: string;
}; };
const CURSOR_TIMEOUT_MS = 300_000; export function createImplementRole(
adapter: AgentFn,
export function buildImplementRole({ { extract, nerveRoot }: CreateImplementRoleDeps,
extract, ): Role<ImplementMeta> {
nerveRoot,
}: BuildImplementDeps): Role<ImplementMeta> {
return async (start: StartStep, messages: WorkflowMessage[]): Promise<RoleResult<ImplementMeta>> => { return async (start: StartStep, messages: WorkflowMessage[]): Promise<RoleResult<ImplementMeta>> => {
const cwd = resolveRepoCwd(messages); const cwd = resolveRepoCwd(messages);
if (cwd === null) { if (cwd === null) {
@ -33,11 +30,7 @@ export function buildImplementRole({
} }
const innerRole = createRole( const innerRole = createRole(
createCursorAdapter({ adapter,
type: "cursor",
model: "auto",
timeout: CURSOR_TIMEOUT_MS,
}),
async (innerStart: StartStep) => async (innerStart: StartStep) =>
buildImplementPrompt({ buildImplementPrompt({
threadId: innerStart.meta.threadId, threadId: innerStart.meta.threadId,

View File

@ -1,5 +1,4 @@
import type { Role, RoleResult, StartStep, WorkflowMessage } from "@uncaged/nerve-core"; import type { AgentFn, Role, RoleResult, StartStep, WorkflowMessage } from "@uncaged/nerve-core";
import { createCursorAdapter } from "@uncaged/nerve-adapter-cursor";
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
import { createRole } from "@uncaged/nerve-workflow-utils"; import { createRole } from "@uncaged/nerve-workflow-utils";
import { z } from "zod"; import { z } from "zod";
@ -12,14 +11,15 @@ export const planMetaSchema = z.object({
}); });
export type PlanMeta = z.infer<typeof planMetaSchema>; export type PlanMeta = z.infer<typeof planMetaSchema>;
export type BuildPlanDeps = { export type CreatePlanRoleDeps = {
extract: LlmExtractorConfig; extract: LlmExtractorConfig;
nerveRoot: string; nerveRoot: string;
}; };
const CURSOR_TIMEOUT_MS = 300_000; export function createPlanRole(
adapter: AgentFn,
export function buildPlanRole({ extract, nerveRoot }: BuildPlanDeps): Role<PlanMeta> { { extract, nerveRoot }: CreatePlanRoleDeps,
): Role<PlanMeta> {
return async (start: StartStep, messages: WorkflowMessage[]): Promise<RoleResult<PlanMeta>> => { return async (start: StartStep, messages: WorkflowMessage[]): Promise<RoleResult<PlanMeta>> => {
const cwd = resolveRepoCwd(messages); const cwd = resolveRepoCwd(messages);
if (cwd === null) { if (cwd === null) {
@ -30,12 +30,7 @@ export function buildPlanRole({ extract, nerveRoot }: BuildPlanDeps): Role<PlanM
} }
const innerRole = createRole( const innerRole = createRole(
createCursorAdapter({ adapter,
type: "cursor",
mode: "ask",
model: "auto",
timeout: CURSOR_TIMEOUT_MS,
}),
async (innerStart: StartStep) => async (innerStart: StartStep) =>
buildPlanPrompt({ buildPlanPrompt({
threadId: innerStart.meta.threadId, threadId: innerStart.meta.threadId,

View File

@ -1,6 +1,20 @@
import type { AgentFn, Role, StartStep } from "@uncaged/nerve-core";
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
import { createRole } from "@uncaged/nerve-workflow-utils";
import { z } from "zod"; import { z } from "zod";
import { preparePrompt } from "./prompt.js";
export const prepareMetaSchema = z.object({ export const prepareMetaSchema = z.object({
ready: z.boolean().describe("true if repo is ready and baseline build ok"), ready: z.boolean().describe("true if repo is ready and baseline build ok"),
}); });
export type PrepareMeta = z.infer<typeof prepareMetaSchema>; export type PrepareMeta = z.infer<typeof prepareMetaSchema>;
export function createPrepareRole(adapter: AgentFn, extract: LlmExtractorConfig): Role<PrepareMeta> {
return createRole(
adapter,
async (start: StartStep) => preparePrompt({ threadId: start.meta.threadId }),
prepareMetaSchema,
extract,
);
}

View File

@ -1,7 +1,6 @@
import { mkdirSync, writeFileSync } from "node:fs"; import { mkdirSync, writeFileSync } from "node:fs";
import { join } from "node:path"; import { join } from "node:path";
import type { Role, RoleResult, StartStep, WorkflowMessage } from "@uncaged/nerve-core"; import type { AgentFn, Role, RoleResult, StartStep, WorkflowMessage } from "@uncaged/nerve-core";
import { hermesAdapter } from "@uncaged/nerve-adapter-hermes";
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
import { createRole, isDryRun } from "@uncaged/nerve-workflow-utils"; import { createRole, isDryRun } from "@uncaged/nerve-workflow-utils";
import { z } from "zod"; import { z } from "zod";
@ -13,7 +12,7 @@ export const publishMetaSchema = z.object({
}); });
export type PublishMeta = z.infer<typeof publishMetaSchema>; export type PublishMeta = z.infer<typeof publishMetaSchema>;
export type BuildPublishDeps = { export type CreatePublishRoleDeps = {
extract: LlmExtractorConfig; extract: LlmExtractorConfig;
nerveRoot: string; nerveRoot: string;
}; };
@ -22,9 +21,12 @@ function logPath(nerveRoot: string): string {
return join(nerveRoot, "logs", `solve-issue-publish-${Date.now()}.log`); return join(nerveRoot, "logs", `solve-issue-publish-${Date.now()}.log`);
} }
export function buildPublishRole({ extract, nerveRoot }: BuildPublishDeps): Role<PublishMeta> { export function createPublishRole(
adapter: AgentFn,
{ extract, nerveRoot }: CreatePublishRoleDeps,
): Role<PublishMeta> {
const innerRole = createRole( const innerRole = createRole(
hermesAdapter, adapter,
async (start: StartStep) => async (start: StartStep) =>
buildPublishPrompt({ threadId: start.meta.threadId, nerveRoot }), buildPublishPrompt({ threadId: start.meta.threadId, nerveRoot }),
publishMetaSchema, publishMetaSchema,

View File

@ -1,6 +1,20 @@
import type { AgentFn, Role, StartStep } from "@uncaged/nerve-core";
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
import { createRole } from "@uncaged/nerve-workflow-utils";
import { z } from "zod"; import { z } from "zod";
import { readIssuePrompt } from "./prompt.js";
export const readIssueMetaSchema = z.object({ export const readIssueMetaSchema = z.object({
ready: z.boolean().describe("true if issue content was fetched and markers are present"), ready: z.boolean().describe("true if issue content was fetched and markers are present"),
}); });
export type ReadIssueMeta = z.infer<typeof readIssueMetaSchema>; export type ReadIssueMeta = z.infer<typeof readIssueMetaSchema>;
export function createReadIssueRole(adapter: AgentFn, extract: LlmExtractorConfig): Role<ReadIssueMeta> {
return createRole(
adapter,
async (start: StartStep) => readIssuePrompt({ threadId: start.meta.threadId }),
readIssueMetaSchema,
extract,
);
}

View File

@ -1,6 +1,25 @@
import type { AgentFn, Role, StartStep } from "@uncaged/nerve-core";
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
import { createRole } from "@uncaged/nerve-workflow-utils";
import { z } from "zod"; import { z } from "zod";
import { reviewPrompt } from "./prompt.js";
export const reviewMetaSchema = z.object({ export const reviewMetaSchema = z.object({
approved: z.boolean().describe("true if diff is clean and ready for tests"), approved: z.boolean().describe("true if diff is clean and ready for tests"),
}); });
export type ReviewMeta = z.infer<typeof reviewMetaSchema>; export type ReviewMeta = z.infer<typeof reviewMetaSchema>;
export function createReviewRole(
adapter: AgentFn,
extract: LlmExtractorConfig,
nerveRoot: string,
): Role<ReviewMeta> {
return createRole(
adapter,
async (start: StartStep) =>
reviewPrompt({ threadId: start.meta.threadId, nerveRoot }),
reviewMetaSchema,
extract,
);
}

View File

@ -1,6 +1,20 @@
import type { AgentFn, Role, StartStep } from "@uncaged/nerve-core";
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
import { createRole } from "@uncaged/nerve-workflow-utils";
import { z } from "zod"; import { z } from "zod";
import { testPrompt } from "./prompt.js";
export const testMetaSchema = z.object({ export const testMetaSchema = z.object({
passed: z.boolean().describe("true if all test commands passed"), passed: z.boolean().describe("true if all test commands passed"),
}); });
export type TestMeta = z.infer<typeof testMetaSchema>; export type TestMeta = z.infer<typeof testMetaSchema>;
export function createTestRole(adapter: AgentFn, extract: LlmExtractorConfig): Role<TestMeta> {
return createRole(
adapter,
async (start: StartStep) => testPrompt({ threadId: start.meta.threadId }),
testMetaSchema,
extract,
);
}