Compare commits

..

4 Commits

Author SHA1 Message Date
ac47daa42b Merge pull request 'refactor: decouple adapters from workflow factories + createXxxRole' (#16) from refactor/15-decouple-adapters into master 2026-04-29 12:44:18 +00:00
a609dc2486 refactor: derive adapter keys from Meta type instead of manual union 2026-04-29 12:41:10 +00:00
eaddd88109 refactor: add defaultAdapter + typed role union, adapters becomes Partial
- Each workflow factory takes defaultAdapter: AgentFn + adapters?: Partial<Record<RoleUnion, AgentFn>>
- index.ts only overrides roles that differ from default (planner/coder use cursor, rest fallback)
- Cleaner call sites, type-safe role names

Refs #15
2026-04-29 12:38:21 +00:00
1683e41b05 refactor: decouple adapters from workflow factories, roles export createXxxRole
- Rename build* → create* workflow factories
- Workflow factories accept adapters: Record<string, AgentFn>
- Each role file exports createXxxRole(adapter, ...) factory
- _shared/workspace-committer accepts adapter as first param
- All adapter imports moved to index.ts (injection point)
- solve-issue roles also updated

Closes #15
2026-04-29 12:35:07 +00:00
25 changed files with 337 additions and 221 deletions

View File

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

View File

@ -1,75 +1,34 @@
import type { StartStep, WorkflowDefinition } from "@uncaged/nerve-core";
import { createCursorAdapter, cursorAdapter } from "@uncaged/nerve-adapter-cursor";
import { hermesAdapter } from "@uncaged/nerve-adapter-hermes";
import type { AgentFn, WorkflowDefinition } from "@uncaged/nerve-core";
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 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 = {
defaultAdapter: AgentFn;
adapters?: Partial<Record<keyof SenseMeta, AgentFn>>;
extract: LlmExtractorConfig;
cwd: string;
};
const CURSOR_TIMEOUT_MS = 300_000;
export function buildDevelopSenseWorkflow({
export function createDevelopSenseWorkflow({
defaultAdapter,
adapters,
extract,
cwd,
}: BuildDevelopSenseDeps): WorkflowDefinition<SenseMeta> {
}: CreateDevelopSenseDeps): WorkflowDefinition<SenseMeta> {
const a = (role: keyof SenseMeta) => adapters?.[role] ?? defaultAdapter;
const roles = {
planner: createRole(
createCursorAdapter({
type: "cursor",
mode: "ask",
model: "auto",
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({
planner: createPlannerRole(a('planner'), extract),
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",

View File

@ -1,5 +1,7 @@
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 NERVE_ROOT = join(HOME, ".uncaged-nerve");
@ -11,7 +13,19 @@ if (!apiKey || !baseUrl) {
throw new Error("Set DASHSCOPE_API_KEY and DASHSCOPE_BASE_URL");
}
const workflow = buildDevelopSenseWorkflow({
const CURSOR_TIMEOUT_MS = 300_000;
const workflow = createDevelopSenseWorkflow({
defaultAdapter: hermesAdapter,
adapters: {
planner: createCursorAdapter({
type: "cursor",
mode: "ask",
model: "auto",
timeout: CURSOR_TIMEOUT_MS,
}),
coder: cursorAdapter,
},
extract: { provider: { apiKey, baseUrl, model } },
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";
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.`;
}
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 {
buildWorkspaceCommitterRole,
createWorkspaceCommitterRole,
committerMetaSchema,
type BuildWorkspaceCommitterDeps,
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";
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.`;
}
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";
export const reviewerMetaSchema = z.object({
@ -43,3 +46,17 @@ or
{ "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";
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.`;
}
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,34 @@
import type { StartStep, WorkflowDefinition } from "@uncaged/nerve-core";
import { createCursorAdapter, cursorAdapter } from "@uncaged/nerve-adapter-cursor";
import { hermesAdapter } from "@uncaged/nerve-adapter-hermes";
import type { AgentFn, WorkflowDefinition } from "@uncaged/nerve-core";
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 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 = {
defaultAdapter: AgentFn;
adapters?: Partial<Record<keyof WorkflowMeta, AgentFn>>;
extract: LlmExtractorConfig;
nerveRoot: string;
};
const CURSOR_TIMEOUT_MS = 300_000;
export function buildDevelopWorkflow({
export function createDevelopWorkflowWorkflow({
defaultAdapter,
adapters,
extract,
nerveRoot,
}: BuildDevelopWorkflowDeps): WorkflowDefinition<WorkflowMeta> {
}: CreateDevelopWorkflowDeps): WorkflowDefinition<WorkflowMeta> {
const a = (role: keyof WorkflowMeta) => adapters?.[role] ?? defaultAdapter;
const roles = {
planner: createRole(
createCursorAdapter({
type: "cursor",
mode: "ask",
model: "auto",
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({
planner: createPlannerRole(a('planner'), extract),
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",

View File

@ -1,5 +1,7 @@
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 NERVE_ROOT = join(HOME, ".uncaged-nerve");
@ -12,7 +14,19 @@ if (!apiKey || !baseUrl) {
throw new Error("Set DASHSCOPE_API_KEY and DASHSCOPE_BASE_URL");
}
const workflow = buildDevelopWorkflow({
const CURSOR_TIMEOUT_MS = 300_000;
const workflow = createDevelopWorkflowWorkflow({
defaultAdapter: hermesAdapter,
adapters: {
planner: createCursorAdapter({
type: "cursor",
mode: "ask",
model: "auto",
timeout: CURSOR_TIMEOUT_MS,
}),
coder: cursorAdapter,
},
extract: { provider: { apiKey, baseUrl, model } },
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";
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.`;
}
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 {
buildWorkspaceCommitterRole,
createWorkspaceCommitterRole,
committerMetaSchema,
type BuildWorkspaceCommitterDeps,
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";
export const plannerMetaSchema = z.object({
@ -51,3 +54,12 @@ or
{ "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";
export const reviewerMetaSchema = z.object({
@ -43,3 +46,17 @@ or
{ "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";
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.`;
}
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,42 @@
import type { StartStep, WorkflowDefinition } from "@uncaged/nerve-core";
import { hermesAdapter } from "@uncaged/nerve-adapter-hermes";
import type { AgentFn, WorkflowDefinition } from "@uncaged/nerve-core";
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
import { createRole } from "@uncaged/nerve-workflow-utils";
import { moderator } from "./moderator.js";
import type { WorkflowMeta } from "./moderator.js";
import { buildCommitterRole } from "./roles/committer/index.js";
import { buildImplementRole } from "./roles/implement/index.js";
import { buildPlanRole } from "./roles/plan/index.js";
import { prepareMetaSchema } from "./roles/prepare/index.js";
import { preparePrompt } from "./roles/prepare/prompt.js";
import { buildPublishRole } from "./roles/publish/index.js";
import { readIssueMetaSchema } from "./roles/read-issue/index.js";
import { readIssuePrompt } from "./roles/read-issue/prompt.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";
import { createCommitterRole } from "./roles/committer/index.js";
import { createImplementRole } from "./roles/implement/index.js";
import { createPlanRole } from "./roles/plan/index.js";
import { createPrepareRole } from "./roles/prepare/index.js";
import { createPublishRole } from "./roles/publish/index.js";
import { createReadIssueRole } from "./roles/read-issue/index.js";
import { createReviewRole } from "./roles/review/index.js";
import { createTestRole } from "./roles/test/index.js";
export type BuildSolveIssueDeps = {
export type CreateSolveIssueDeps = {
defaultAdapter: AgentFn;
adapters?: Partial<Record<keyof WorkflowMeta, AgentFn>>;
nerveRoot: string;
extract: LlmExtractorConfig;
};
export function buildSolveIssue({
export function createSolveIssueWorkflow({
defaultAdapter,
adapters,
nerveRoot,
extract,
}: BuildSolveIssueDeps): WorkflowDefinition<WorkflowMeta> {
}: CreateSolveIssueDeps): WorkflowDefinition<WorkflowMeta> {
const a = (role: keyof WorkflowMeta) => adapters?.[role] ?? defaultAdapter;
return {
name: "solve-issue",
roles: {
"read-issue": createRole(
hermesAdapter,
async (start: StartStep) => readIssuePrompt({ threadId: start.meta.threadId }),
readIssueMetaSchema,
extract,
),
prepare: createRole(
hermesAdapter,
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 }),
"read-issue": createReadIssueRole(a("read-issue"), extract),
prepare: createPrepareRole(a("prepare"), extract),
plan: createPlanRole(a("plan"), { extract, nerveRoot }),
implement: createImplementRole(a("implement"), { extract, nerveRoot }),
committer: createCommitterRole(a("committer"), { extract, nerveRoot }),
review: createReviewRole(a("review"), extract, nerveRoot),
test: createTestRole(a("test"), extract),
publish: createPublishRole(a("publish"), { extract, nerveRoot }),
},
moderator,
};

View File

@ -1,5 +1,7 @@
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";
const HOME = process.env.HOME ?? "/home/azureuser";
@ -11,6 +13,25 @@ if (provider === null) {
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({
defaultAdapter: hermesAdapter,
adapters: {
plan: createCursorAdapter({
type: "cursor",
mode: "ask",
model: "auto",
timeout: CURSOR_TIMEOUT_MS,
}),
implement: createCursorAdapter({
type: "cursor",
model: "auto",
timeout: CURSOR_TIMEOUT_MS,
}),
},
nerveRoot: NERVE_ROOT,
extract: { provider },
});
export default workflow;

View File

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

View File

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

View File

@ -1,5 +1,4 @@
import type { Role, RoleResult, StartStep, WorkflowMessage } from "@uncaged/nerve-core";
import { createCursorAdapter } from "@uncaged/nerve-adapter-cursor";
import type { AgentFn, Role, RoleResult, StartStep, WorkflowMessage } from "@uncaged/nerve-core";
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
import { createRole } from "@uncaged/nerve-workflow-utils";
import { z } from "zod";
@ -12,14 +11,15 @@ export const planMetaSchema = z.object({
});
export type PlanMeta = z.infer<typeof planMetaSchema>;
export type BuildPlanDeps = {
export type CreatePlanRoleDeps = {
extract: LlmExtractorConfig;
nerveRoot: string;
};
const CURSOR_TIMEOUT_MS = 300_000;
export function buildPlanRole({ extract, nerveRoot }: BuildPlanDeps): Role<PlanMeta> {
export function createPlanRole(
adapter: AgentFn,
{ extract, nerveRoot }: CreatePlanRoleDeps,
): Role<PlanMeta> {
return async (start: StartStep, messages: WorkflowMessage[]): Promise<RoleResult<PlanMeta>> => {
const cwd = resolveRepoCwd(messages);
if (cwd === null) {
@ -30,12 +30,7 @@ export function buildPlanRole({ extract, nerveRoot }: BuildPlanDeps): Role<PlanM
}
const innerRole = createRole(
createCursorAdapter({
type: "cursor",
mode: "ask",
model: "auto",
timeout: CURSOR_TIMEOUT_MS,
}),
adapter,
async (innerStart: StartStep) =>
buildPlanPrompt({
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 { preparePrompt } from "./prompt.js";
export const prepareMetaSchema = z.object({
ready: z.boolean().describe("true if repo is ready and baseline build ok"),
});
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 { join } from "node:path";
import type { Role, RoleResult, StartStep, WorkflowMessage } from "@uncaged/nerve-core";
import { hermesAdapter } from "@uncaged/nerve-adapter-hermes";
import type { AgentFn, Role, RoleResult, StartStep, WorkflowMessage } from "@uncaged/nerve-core";
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
import { createRole, isDryRun } from "@uncaged/nerve-workflow-utils";
import { z } from "zod";
@ -13,7 +12,7 @@ export const publishMetaSchema = z.object({
});
export type PublishMeta = z.infer<typeof publishMetaSchema>;
export type BuildPublishDeps = {
export type CreatePublishRoleDeps = {
extract: LlmExtractorConfig;
nerveRoot: string;
};
@ -22,9 +21,12 @@ function logPath(nerveRoot: string): string {
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(
hermesAdapter,
adapter,
async (start: StartStep) =>
buildPublishPrompt({ threadId: start.meta.threadId, nerveRoot }),
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 { readIssuePrompt } from "./prompt.js";
export const readIssueMetaSchema = z.object({
ready: z.boolean().describe("true if issue content was fetched and markers are present"),
});
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 { reviewPrompt } from "./prompt.js";
export const reviewMetaSchema = z.object({
approved: z.boolean().describe("true if diff is clean and ready for tests"),
});
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 { testPrompt } from "./prompt.js";
export const testMetaSchema = z.object({
passed: z.boolean().describe("true if all test commands passed"),
});
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,
);
}