From c9cdfe37dbfea461e8990601c77b13143b0ce2e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Wed, 6 May 2026 11:40:19 +0000 Subject: [PATCH] refactor: extract planner and coder into standalone role packages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New @uncaged/workflow-role-planner (phaseSchema, createPlannerRole) - New @uncaged/workflow-role-coder (coderMetaSchema, createCoderRole) - solve-issue template imports from new packages, keeps dry-run defaults 小橘 --- packages/workflow-role-coder/package.json | 17 ++++ packages/workflow-role-coder/src/coder.ts | 51 ++++++++++++ packages/workflow-role-coder/src/index.ts | 7 ++ packages/workflow-role-coder/tsconfig.json | 14 ++++ packages/workflow-role-planner/package.json | 17 ++++ packages/workflow-role-planner/src/index.ts | 8 ++ packages/workflow-role-planner/src/planner.ts | 52 ++++++++++++ packages/workflow-role-planner/tsconfig.json | 14 ++++ .../__tests__/solve-issue-template.test.ts | 4 +- .../package.json | 6 +- .../src/index.ts | 12 ++- .../src/roles.ts | 81 ++++++------------- .../tsconfig.json | 5 +- tsconfig.json | 2 + 14 files changed, 225 insertions(+), 65 deletions(-) create mode 100644 packages/workflow-role-coder/package.json create mode 100644 packages/workflow-role-coder/src/coder.ts create mode 100644 packages/workflow-role-coder/src/index.ts create mode 100644 packages/workflow-role-coder/tsconfig.json create mode 100644 packages/workflow-role-planner/package.json create mode 100644 packages/workflow-role-planner/src/index.ts create mode 100644 packages/workflow-role-planner/src/planner.ts create mode 100644 packages/workflow-role-planner/tsconfig.json diff --git a/packages/workflow-role-coder/package.json b/packages/workflow-role-coder/package.json new file mode 100644 index 0000000..9a35ac2 --- /dev/null +++ b/packages/workflow-role-coder/package.json @@ -0,0 +1,17 @@ +{ + "name": "@uncaged/workflow-role-coder", + "version": "0.1.0", + "type": "module", + "main": "src/index.ts", + "types": "src/index.ts", + "scripts": { + "build": "echo 'TODO'", + "test": "bun test" + }, + "dependencies": { + "@uncaged/workflow": "workspace:*", + "@uncaged/workflow-agent-llm": "workspace:*", + "@uncaged/workflow-util-role": "workspace:*", + "zod": "^4.0.0" + } +} diff --git a/packages/workflow-role-coder/src/coder.ts b/packages/workflow-role-coder/src/coder.ts new file mode 100644 index 0000000..f0753c9 --- /dev/null +++ b/packages/workflow-role-coder/src/coder.ts @@ -0,0 +1,51 @@ +import type { AgentFn, Role, ThreadContext } from "@uncaged/workflow"; +import { createRole } from "@uncaged/workflow-agent-llm"; +import type { LlmProvider } from "@uncaged/workflow-util-role"; +import * as z from "zod/v4"; + +export const coderMetaSchema = z.object({ + completedPhase: z.string(), + filesChanged: z.array(z.string()), + summary: z.string(), +}); + +export type CoderMeta = z.infer; + +export type CoderConfig = { + cwd: string; +}; + +export const DEFAULT_CODER_CONFIG: CoderConfig = { + cwd: ".", +}; + +function coderSystemPrompt(config: CoderConfig): string { + return `You are a **coder**. The project is at \`${config.cwd}\`. + +Read the thread: the planner produced ordered **phases**. Identify the **next** phase that is not yet completed according to prior coder steps (each coder step reports a completedPhase). + +Implement **only that phase** — do not tackle multiple phases in one turn unless the planner defined a single phase. Follow project conventions; summarize what changed and list touched files. + +When done with the phase you worked on, set **completedPhase** to that phase's **name** exactly as given by the planner.`; +} + +/** + * Coder role: implements the next incomplete planner phase and reports structured completion metadata. + */ +export function createCoderRole( + adapter: AgentFn, + extract: { provider: LlmProvider; dryRun: boolean | null; dryRunMeta: CoderMeta }, + config: CoderConfig = DEFAULT_CODER_CONFIG, +): Role { + return createRole({ + name: "coder", + schema: coderMetaSchema, + systemPrompt: async (_ctx: ThreadContext) => coderSystemPrompt(config), + agent: adapter, + extract: { + provider: extract.provider, + dryRun: extract.dryRun, + dryRunMeta: extract.dryRunMeta, + }, + }); +} diff --git a/packages/workflow-role-coder/src/index.ts b/packages/workflow-role-coder/src/index.ts new file mode 100644 index 0000000..a4d7498 --- /dev/null +++ b/packages/workflow-role-coder/src/index.ts @@ -0,0 +1,7 @@ +export { + type CoderConfig, + type CoderMeta, + coderMetaSchema, + createCoderRole, + DEFAULT_CODER_CONFIG, +} from "./coder.js"; diff --git a/packages/workflow-role-coder/tsconfig.json b/packages/workflow-role-coder/tsconfig.json new file mode 100644 index 0000000..ff7d45c --- /dev/null +++ b/packages/workflow-role-coder/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "composite": true + }, + "include": ["src/**/*.ts"], + "references": [ + { "path": "../workflow" }, + { "path": "../workflow-agent-llm" }, + { "path": "../workflow-util-role" } + ] +} diff --git a/packages/workflow-role-planner/package.json b/packages/workflow-role-planner/package.json new file mode 100644 index 0000000..43e70e2 --- /dev/null +++ b/packages/workflow-role-planner/package.json @@ -0,0 +1,17 @@ +{ + "name": "@uncaged/workflow-role-planner", + "version": "0.1.0", + "type": "module", + "main": "src/index.ts", + "types": "src/index.ts", + "scripts": { + "build": "echo 'TODO'", + "test": "bun test" + }, + "dependencies": { + "@uncaged/workflow": "workspace:*", + "@uncaged/workflow-agent-llm": "workspace:*", + "@uncaged/workflow-util-role": "workspace:*", + "zod": "^4.0.0" + } +} diff --git a/packages/workflow-role-planner/src/index.ts b/packages/workflow-role-planner/src/index.ts new file mode 100644 index 0000000..20f12b4 --- /dev/null +++ b/packages/workflow-role-planner/src/index.ts @@ -0,0 +1,8 @@ +export { + createPlannerRole, + DEFAULT_PLANNER_CONFIG, + type PlannerConfig, + type PlannerMeta, + phaseSchema, + plannerMetaSchema, +} from "./planner.js"; diff --git a/packages/workflow-role-planner/src/planner.ts b/packages/workflow-role-planner/src/planner.ts new file mode 100644 index 0000000..eb508e3 --- /dev/null +++ b/packages/workflow-role-planner/src/planner.ts @@ -0,0 +1,52 @@ +import type { AgentFn, Role, ThreadContext } from "@uncaged/workflow"; +import { createRole } from "@uncaged/workflow-agent-llm"; +import type { LlmProvider } from "@uncaged/workflow-util-role"; +import * as z from "zod/v4"; + +export const phaseSchema = z.object({ + name: z.string(), + description: z.string(), + acceptance: z.string(), +}); + +export const plannerMetaSchema = z.object({ + phases: z.array(phaseSchema), +}); + +export type PlannerMeta = z.infer; + +/** Reserved for future planner options; empty for now. */ +export type PlannerConfig = Record; + +export const DEFAULT_PLANNER_CONFIG: PlannerConfig = {}; + +const PLANNER_SYSTEM = `You are a **planner** for a software task. Break the work into **sequential phases** the coder will execute one at a time. + +Each phase must have: a short **name** (stable identifier), a **description** of what to do in that phase, and **acceptance** criteria for when that phase is done. + +Order phases so earlier steps unblock later ones. Cover root cause, edge cases, and verification across the phases. Do not emit separate file lists or a free-form "approach" field — put that detail inside phase descriptions.`; + +function plannerSystemPrompt(_config: PlannerConfig): string { + return PLANNER_SYSTEM; +} + +/** + * Planner role: produces ordered implementation phases for the coder to execute sequentially. + */ +export function createPlannerRole( + adapter: AgentFn, + extract: { provider: LlmProvider; dryRun: boolean | null; dryRunMeta: PlannerMeta }, + config: PlannerConfig = DEFAULT_PLANNER_CONFIG, +): Role { + return createRole({ + name: "planner", + schema: plannerMetaSchema, + systemPrompt: async (_ctx: ThreadContext) => plannerSystemPrompt(config), + agent: adapter, + extract: { + provider: extract.provider, + dryRun: extract.dryRun, + dryRunMeta: extract.dryRunMeta, + }, + }); +} diff --git a/packages/workflow-role-planner/tsconfig.json b/packages/workflow-role-planner/tsconfig.json new file mode 100644 index 0000000..ff7d45c --- /dev/null +++ b/packages/workflow-role-planner/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "composite": true + }, + "include": ["src/**/*.ts"], + "references": [ + { "path": "../workflow" }, + { "path": "../workflow-agent-llm" }, + { "path": "../workflow-util-role" } + ] +} diff --git a/packages/workflow-template-solve-issue/__tests__/solve-issue-template.test.ts b/packages/workflow-template-solve-issue/__tests__/solve-issue-template.test.ts index 3ec2160..44a5d03 100644 --- a/packages/workflow-template-solve-issue/__tests__/solve-issue-template.test.ts +++ b/packages/workflow-template-solve-issue/__tests__/solve-issue-template.test.ts @@ -8,9 +8,11 @@ import { validateWorkflowDescriptor, } from "@uncaged/workflow"; +import type { PlannerMeta } from "@uncaged/workflow-role-planner"; + import { buildSolveIssueDescriptor } from "../src/descriptor.js"; import { solveIssueModerator } from "../src/moderator.js"; -import { createSolveIssueRoles, type PlannerMeta, type SolveIssueMeta } from "../src/roles.js"; +import { createSolveIssueRoles, type SolveIssueMeta } from "../src/roles.js"; const DEFAULT_PHASES: PlannerMeta["phases"] = [ { name: "phase-a", description: "Do the work", acceptance: "Done" }, diff --git a/packages/workflow-template-solve-issue/package.json b/packages/workflow-template-solve-issue/package.json index fcc124d..ca2d697 100644 --- a/packages/workflow-template-solve-issue/package.json +++ b/packages/workflow-template-solve-issue/package.json @@ -12,9 +12,9 @@ "@uncaged/workflow": "workspace:*", "@uncaged/workflow-agent-cursor": "workspace:*", "@uncaged/workflow-role-committer": "workspace:*", - "@uncaged/workflow-agent-llm": "workspace:*", + "@uncaged/workflow-role-coder": "workspace:*", + "@uncaged/workflow-role-planner": "workspace:*", "@uncaged/workflow-role-reviewer": "workspace:*", - "@uncaged/workflow-util-role": "workspace:*", - "zod": "^4.0.0" + "@uncaged/workflow-util-role": "workspace:*" } } diff --git a/packages/workflow-template-solve-issue/src/index.ts b/packages/workflow-template-solve-issue/src/index.ts index 220093d..ce2393b 100644 --- a/packages/workflow-template-solve-issue/src/index.ts +++ b/packages/workflow-template-solve-issue/src/index.ts @@ -9,15 +9,21 @@ import { } from "./roles.js"; export { type CursorAgentConfig, createCursorAgent } from "@uncaged/workflow-agent-cursor"; -export { buildSolveIssueDescriptor } from "./descriptor.js"; -export { solveIssueModerator } from "./moderator.js"; export { type CoderMeta, coderMetaSchema, - createSolveIssueRoles, + createCoderRole, +} from "@uncaged/workflow-role-coder"; +export { + createPlannerRole, type PlannerMeta, phaseSchema, plannerMetaSchema, +} from "@uncaged/workflow-role-planner"; +export { buildSolveIssueDescriptor } from "./descriptor.js"; +export { solveIssueModerator } from "./moderator.js"; +export { + createSolveIssueRoles, SOLVE_ISSUE_WORKFLOW_DESCRIPTION, type SolveIssueMeta, type SolveIssueRoles, diff --git a/packages/workflow-template-solve-issue/src/roles.ts b/packages/workflow-template-solve-issue/src/roles.ts index b0cd2e5..2091114 100644 --- a/packages/workflow-template-solve-issue/src/roles.ts +++ b/packages/workflow-template-solve-issue/src/roles.ts @@ -1,17 +1,21 @@ import type { AgentFn, RoleDefinition } from "@uncaged/workflow"; -import { createRole } from "@uncaged/workflow-agent-llm"; +import { type CoderMeta, coderMetaSchema, createCoderRole } from "@uncaged/workflow-role-coder"; import { type CommitterMeta, committerMetaSchema, createCommitterRole, } from "@uncaged/workflow-role-committer"; +import { + createPlannerRole, + type PlannerMeta, + plannerMetaSchema, +} from "@uncaged/workflow-role-planner"; import { createReviewerRole, type ReviewerMeta, reviewerMetaSchema, } from "@uncaged/workflow-role-reviewer"; import type { LlmProvider } from "@uncaged/workflow-util-role"; -import * as z from "zod/v4"; const DRY_RUN_PROVIDER: LlmProvider = { baseUrl: "http://127.0.0.1:9", @@ -19,40 +23,15 @@ const DRY_RUN_PROVIDER: LlmProvider = { model: "template-dry-run", }; -const PLANNER_SYSTEM = `You are a **planner** for a software task. Break the work into **sequential phases** the coder will execute one at a time. - -Each phase must have: a short **name** (stable identifier), a **description** of what to do in that phase, and **acceptance** criteria for when that phase is done. - -Order phases so earlier steps unblock later ones. Cover root cause, edge cases, and verification across the phases. Do not emit separate file lists or a free-form "approach" field — put that detail inside phase descriptions.`; - -const CODER_SYSTEM = `You are a **coder**. Read the thread: the planner produced ordered **phases**. Identify the **next** phase that is not yet completed according to prior coder steps (each coder step reports a completedPhase). - -Implement **only that phase** — do not tackle multiple phases in one turn unless the planner defined a single phase. Follow project conventions; summarize what changed and list touched files. - -When done with the phase you worked on, set **completedPhase** to that phase's **name** exactly as given by the planner.`; - export const SOLVE_ISSUE_WORKFLOW_DESCRIPTION = "Phased plan, incremental implementation per phase, review, and commit to resolve an issue end-to-end (planner → coder [repeat per phase] → reviewer → committer)."; -export const phaseSchema = z.object({ - name: z.string(), - description: z.string(), - acceptance: z.string(), -}); - -export const plannerMetaSchema = z.object({ - phases: z.array(phaseSchema), -}); - -export const coderMetaSchema = z.object({ - completedPhase: z.string(), - filesChanged: z.array(z.string()), - summary: z.string(), -}); - -export type PlannerMeta = z.infer; - -export type CoderMeta = z.infer; +export type SolveIssueMeta = { + planner: PlannerMeta; + coder: CoderMeta; + reviewer: ReviewerMeta; + committer: CommitterMeta; +}; const PLANNER_DRY_RUN_META: PlannerMeta = { phases: [ @@ -80,13 +59,6 @@ const COMMITTER_DRY_RUN_META: CommitterMeta = { commitSha: "0000000", }; -export type SolveIssueMeta = { - planner: PlannerMeta; - coder: CoderMeta; - reviewer: ReviewerMeta; - committer: CommitterMeta; -}; - /** Wiring for workflow-role LLM structured extraction. Use `null` for stub extract (dry-run meta from built-in placeholders). */ export type SolveIssueRolesConfig = { agent: AgentFn; @@ -132,35 +104,30 @@ export function createSolveIssueRoles(config: SolveIssueRolesConfig): SolveIssue const committerConfig = { cwd: config.workdir, }; + const coderConfig = { + cwd: config.workdir, + }; const plannerAgent = resolveRoleAgent(config, "planner"); const coderAgent = resolveRoleAgent(config, "coder"); const reviewerAgent = resolveRoleAgent(config, "reviewer"); const committerAgent = resolveRoleAgent(config, "committer"); - const plannerRun = createRole({ - name: "planner", - schema: plannerMetaSchema, - systemPrompt: PLANNER_SYSTEM, - agent: plannerAgent, - extract: { - provider: extract.provider, - dryRun: extract.dryRun, - dryRunMeta: PLANNER_DRY_RUN_META, - }, + const plannerRun = createPlannerRole(plannerAgent, { + provider: extract.provider, + dryRun: extract.dryRun, + dryRunMeta: PLANNER_DRY_RUN_META, }); - const coderRun = createRole({ - name: "coder", - schema: coderMetaSchema, - systemPrompt: CODER_SYSTEM, - agent: coderAgent, - extract: { + const coderRun = createCoderRole( + coderAgent, + { provider: extract.provider, dryRun: extract.dryRun, dryRunMeta: CODER_DRY_RUN_META, }, - }); + coderConfig, + ); const reviewerRun = createReviewerRole( reviewerAgent, diff --git a/packages/workflow-template-solve-issue/tsconfig.json b/packages/workflow-template-solve-issue/tsconfig.json index ff7d45c..8cf3619 100644 --- a/packages/workflow-template-solve-issue/tsconfig.json +++ b/packages/workflow-template-solve-issue/tsconfig.json @@ -8,7 +8,10 @@ "include": ["src/**/*.ts"], "references": [ { "path": "../workflow" }, - { "path": "../workflow-agent-llm" }, + { "path": "../workflow-role-coder" }, + { "path": "../workflow-role-committer" }, + { "path": "../workflow-role-planner" }, + { "path": "../workflow-role-reviewer" }, { "path": "../workflow-util-role" } ] } diff --git a/tsconfig.json b/tsconfig.json index ea74343..6d30bf1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,6 +21,8 @@ { "path": "packages/workflow-util-role" }, { "path": "packages/workflow-agent-llm" }, { "path": "packages/workflow-role-committer" }, + { "path": "packages/workflow-role-coder" }, + { "path": "packages/workflow-role-planner" }, { "path": "packages/workflow-role-reviewer" }, { "path": "packages/workflow-agent-cursor" }, { "path": "packages/workflow-agent-hermes" },