refactor: extract planner and coder into standalone role packages
- 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 小橘 <xiaoju@shazhou.work>
This commit is contained in:
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<typeof coderMetaSchema>;
|
||||||
|
|
||||||
|
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<CoderMeta> {
|
||||||
|
return createRole({
|
||||||
|
name: "coder",
|
||||||
|
schema: coderMetaSchema,
|
||||||
|
systemPrompt: async (_ctx: ThreadContext) => coderSystemPrompt(config),
|
||||||
|
agent: adapter,
|
||||||
|
extract: {
|
||||||
|
provider: extract.provider,
|
||||||
|
dryRun: extract.dryRun,
|
||||||
|
dryRunMeta: extract.dryRunMeta,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export {
|
||||||
|
type CoderConfig,
|
||||||
|
type CoderMeta,
|
||||||
|
coderMetaSchema,
|
||||||
|
createCoderRole,
|
||||||
|
DEFAULT_CODER_CONFIG,
|
||||||
|
} from "./coder.js";
|
||||||
@@ -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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export {
|
||||||
|
createPlannerRole,
|
||||||
|
DEFAULT_PLANNER_CONFIG,
|
||||||
|
type PlannerConfig,
|
||||||
|
type PlannerMeta,
|
||||||
|
phaseSchema,
|
||||||
|
plannerMetaSchema,
|
||||||
|
} from "./planner.js";
|
||||||
@@ -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<typeof plannerMetaSchema>;
|
||||||
|
|
||||||
|
/** Reserved for future planner options; empty for now. */
|
||||||
|
export type PlannerConfig = Record<string, never>;
|
||||||
|
|
||||||
|
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<PlannerMeta> {
|
||||||
|
return createRole({
|
||||||
|
name: "planner",
|
||||||
|
schema: plannerMetaSchema,
|
||||||
|
systemPrompt: async (_ctx: ThreadContext) => plannerSystemPrompt(config),
|
||||||
|
agent: adapter,
|
||||||
|
extract: {
|
||||||
|
provider: extract.provider,
|
||||||
|
dryRun: extract.dryRun,
|
||||||
|
dryRunMeta: extract.dryRunMeta,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -8,9 +8,11 @@ import {
|
|||||||
validateWorkflowDescriptor,
|
validateWorkflowDescriptor,
|
||||||
} from "@uncaged/workflow";
|
} from "@uncaged/workflow";
|
||||||
|
|
||||||
|
import type { PlannerMeta } from "@uncaged/workflow-role-planner";
|
||||||
|
|
||||||
import { buildSolveIssueDescriptor } from "../src/descriptor.js";
|
import { buildSolveIssueDescriptor } from "../src/descriptor.js";
|
||||||
import { solveIssueModerator } from "../src/moderator.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"] = [
|
const DEFAULT_PHASES: PlannerMeta["phases"] = [
|
||||||
{ name: "phase-a", description: "Do the work", acceptance: "Done" },
|
{ name: "phase-a", description: "Do the work", acceptance: "Done" },
|
||||||
|
|||||||
@@ -12,9 +12,9 @@
|
|||||||
"@uncaged/workflow": "workspace:*",
|
"@uncaged/workflow": "workspace:*",
|
||||||
"@uncaged/workflow-agent-cursor": "workspace:*",
|
"@uncaged/workflow-agent-cursor": "workspace:*",
|
||||||
"@uncaged/workflow-role-committer": "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-role-reviewer": "workspace:*",
|
||||||
"@uncaged/workflow-util-role": "workspace:*",
|
"@uncaged/workflow-util-role": "workspace:*"
|
||||||
"zod": "^4.0.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,15 +9,21 @@ import {
|
|||||||
} from "./roles.js";
|
} from "./roles.js";
|
||||||
|
|
||||||
export { type CursorAgentConfig, createCursorAgent } from "@uncaged/workflow-agent-cursor";
|
export { type CursorAgentConfig, createCursorAgent } from "@uncaged/workflow-agent-cursor";
|
||||||
export { buildSolveIssueDescriptor } from "./descriptor.js";
|
|
||||||
export { solveIssueModerator } from "./moderator.js";
|
|
||||||
export {
|
export {
|
||||||
type CoderMeta,
|
type CoderMeta,
|
||||||
coderMetaSchema,
|
coderMetaSchema,
|
||||||
createSolveIssueRoles,
|
createCoderRole,
|
||||||
|
} from "@uncaged/workflow-role-coder";
|
||||||
|
export {
|
||||||
|
createPlannerRole,
|
||||||
type PlannerMeta,
|
type PlannerMeta,
|
||||||
phaseSchema,
|
phaseSchema,
|
||||||
plannerMetaSchema,
|
plannerMetaSchema,
|
||||||
|
} from "@uncaged/workflow-role-planner";
|
||||||
|
export { buildSolveIssueDescriptor } from "./descriptor.js";
|
||||||
|
export { solveIssueModerator } from "./moderator.js";
|
||||||
|
export {
|
||||||
|
createSolveIssueRoles,
|
||||||
SOLVE_ISSUE_WORKFLOW_DESCRIPTION,
|
SOLVE_ISSUE_WORKFLOW_DESCRIPTION,
|
||||||
type SolveIssueMeta,
|
type SolveIssueMeta,
|
||||||
type SolveIssueRoles,
|
type SolveIssueRoles,
|
||||||
|
|||||||
@@ -1,17 +1,21 @@
|
|||||||
import type { AgentFn, RoleDefinition } from "@uncaged/workflow";
|
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 {
|
import {
|
||||||
type CommitterMeta,
|
type CommitterMeta,
|
||||||
committerMetaSchema,
|
committerMetaSchema,
|
||||||
createCommitterRole,
|
createCommitterRole,
|
||||||
} from "@uncaged/workflow-role-committer";
|
} from "@uncaged/workflow-role-committer";
|
||||||
|
import {
|
||||||
|
createPlannerRole,
|
||||||
|
type PlannerMeta,
|
||||||
|
plannerMetaSchema,
|
||||||
|
} from "@uncaged/workflow-role-planner";
|
||||||
import {
|
import {
|
||||||
createReviewerRole,
|
createReviewerRole,
|
||||||
type ReviewerMeta,
|
type ReviewerMeta,
|
||||||
reviewerMetaSchema,
|
reviewerMetaSchema,
|
||||||
} from "@uncaged/workflow-role-reviewer";
|
} from "@uncaged/workflow-role-reviewer";
|
||||||
import type { LlmProvider } from "@uncaged/workflow-util-role";
|
import type { LlmProvider } from "@uncaged/workflow-util-role";
|
||||||
import * as z from "zod/v4";
|
|
||||||
|
|
||||||
const DRY_RUN_PROVIDER: LlmProvider = {
|
const DRY_RUN_PROVIDER: LlmProvider = {
|
||||||
baseUrl: "http://127.0.0.1:9",
|
baseUrl: "http://127.0.0.1:9",
|
||||||
@@ -19,40 +23,15 @@ const DRY_RUN_PROVIDER: LlmProvider = {
|
|||||||
model: "template-dry-run",
|
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 =
|
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).";
|
"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({
|
export type SolveIssueMeta = {
|
||||||
name: z.string(),
|
planner: PlannerMeta;
|
||||||
description: z.string(),
|
coder: CoderMeta;
|
||||||
acceptance: z.string(),
|
reviewer: ReviewerMeta;
|
||||||
});
|
committer: CommitterMeta;
|
||||||
|
};
|
||||||
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<typeof plannerMetaSchema>;
|
|
||||||
|
|
||||||
export type CoderMeta = z.infer<typeof coderMetaSchema>;
|
|
||||||
|
|
||||||
const PLANNER_DRY_RUN_META: PlannerMeta = {
|
const PLANNER_DRY_RUN_META: PlannerMeta = {
|
||||||
phases: [
|
phases: [
|
||||||
@@ -80,13 +59,6 @@ const COMMITTER_DRY_RUN_META: CommitterMeta = {
|
|||||||
commitSha: "0000000",
|
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). */
|
/** Wiring for workflow-role LLM structured extraction. Use `null` for stub extract (dry-run meta from built-in placeholders). */
|
||||||
export type SolveIssueRolesConfig = {
|
export type SolveIssueRolesConfig = {
|
||||||
agent: AgentFn;
|
agent: AgentFn;
|
||||||
@@ -132,35 +104,30 @@ export function createSolveIssueRoles(config: SolveIssueRolesConfig): SolveIssue
|
|||||||
const committerConfig = {
|
const committerConfig = {
|
||||||
cwd: config.workdir,
|
cwd: config.workdir,
|
||||||
};
|
};
|
||||||
|
const coderConfig = {
|
||||||
|
cwd: config.workdir,
|
||||||
|
};
|
||||||
|
|
||||||
const plannerAgent = resolveRoleAgent(config, "planner");
|
const plannerAgent = resolveRoleAgent(config, "planner");
|
||||||
const coderAgent = resolveRoleAgent(config, "coder");
|
const coderAgent = resolveRoleAgent(config, "coder");
|
||||||
const reviewerAgent = resolveRoleAgent(config, "reviewer");
|
const reviewerAgent = resolveRoleAgent(config, "reviewer");
|
||||||
const committerAgent = resolveRoleAgent(config, "committer");
|
const committerAgent = resolveRoleAgent(config, "committer");
|
||||||
|
|
||||||
const plannerRun = createRole({
|
const plannerRun = createPlannerRole(plannerAgent, {
|
||||||
name: "planner",
|
provider: extract.provider,
|
||||||
schema: plannerMetaSchema,
|
dryRun: extract.dryRun,
|
||||||
systemPrompt: PLANNER_SYSTEM,
|
dryRunMeta: PLANNER_DRY_RUN_META,
|
||||||
agent: plannerAgent,
|
|
||||||
extract: {
|
|
||||||
provider: extract.provider,
|
|
||||||
dryRun: extract.dryRun,
|
|
||||||
dryRunMeta: PLANNER_DRY_RUN_META,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const coderRun = createRole({
|
const coderRun = createCoderRole(
|
||||||
name: "coder",
|
coderAgent,
|
||||||
schema: coderMetaSchema,
|
{
|
||||||
systemPrompt: CODER_SYSTEM,
|
|
||||||
agent: coderAgent,
|
|
||||||
extract: {
|
|
||||||
provider: extract.provider,
|
provider: extract.provider,
|
||||||
dryRun: extract.dryRun,
|
dryRun: extract.dryRun,
|
||||||
dryRunMeta: CODER_DRY_RUN_META,
|
dryRunMeta: CODER_DRY_RUN_META,
|
||||||
},
|
},
|
||||||
});
|
coderConfig,
|
||||||
|
);
|
||||||
|
|
||||||
const reviewerRun = createReviewerRole(
|
const reviewerRun = createReviewerRole(
|
||||||
reviewerAgent,
|
reviewerAgent,
|
||||||
|
|||||||
@@ -8,7 +8,10 @@
|
|||||||
"include": ["src/**/*.ts"],
|
"include": ["src/**/*.ts"],
|
||||||
"references": [
|
"references": [
|
||||||
{ "path": "../workflow" },
|
{ "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" }
|
{ "path": "../workflow-util-role" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,8 @@
|
|||||||
{ "path": "packages/workflow-util-role" },
|
{ "path": "packages/workflow-util-role" },
|
||||||
{ "path": "packages/workflow-agent-llm" },
|
{ "path": "packages/workflow-agent-llm" },
|
||||||
{ "path": "packages/workflow-role-committer" },
|
{ "path": "packages/workflow-role-committer" },
|
||||||
|
{ "path": "packages/workflow-role-coder" },
|
||||||
|
{ "path": "packages/workflow-role-planner" },
|
||||||
{ "path": "packages/workflow-role-reviewer" },
|
{ "path": "packages/workflow-role-reviewer" },
|
||||||
{ "path": "packages/workflow-agent-cursor" },
|
{ "path": "packages/workflow-agent-cursor" },
|
||||||
{ "path": "packages/workflow-agent-hermes" },
|
{ "path": "packages/workflow-agent-hermes" },
|
||||||
|
|||||||
Reference in New Issue
Block a user