feat: tester role + develop workflow template
- New workflow-role-tester: runs tests/build/lint, reports pass/fail - Committer: removed push, only creates branch and commits - New workflow-template-develop: planner → coder ⟲ → reviewer ⟲ → tester → committer - 173 tests passing Fixes #58
This commit is contained in:
@@ -21,12 +21,12 @@ export const committerMetaSchema = z.discriminatedUnion("status", [
|
|||||||
|
|
||||||
export type CommitterMeta = z.infer<typeof committerMetaSchema>;
|
export type CommitterMeta = z.infer<typeof committerMetaSchema>;
|
||||||
|
|
||||||
const COMMITTER_SYSTEM = `You are the git committer. Create a branch, commit the changes, and push.
|
const COMMITTER_SYSTEM = `You are the git committer. Create a branch and commit the changes.
|
||||||
Report the branch name and commit SHA. On failure, classify as recoverable or unrecoverable.
|
Report the branch name and commit SHA. On failure, classify as recoverable or unrecoverable.
|
||||||
Do not attempt to fix failures yourself.`;
|
Do not attempt to fix failures yourself.`;
|
||||||
|
|
||||||
export const committerRole: RoleDefinition<CommitterMeta> = {
|
export const committerRole: RoleDefinition<CommitterMeta> = {
|
||||||
description: "Creates branch, commits, and pushes when review passes.",
|
description: "Creates a branch and commits changes.",
|
||||||
systemPrompt: COMMITTER_SYSTEM,
|
systemPrompt: COMMITTER_SYSTEM,
|
||||||
extractPrompt:
|
extractPrompt:
|
||||||
"Extract the commit result: committed (with branch and SHA), recoverable failure, or unrecoverable failure. Include error details and log references if applicable.",
|
"Extract the commit result: committed (with branch and SHA), recoverable failure, or unrecoverable failure. Include error details and log references if applicable.",
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "@uncaged/workflow-role-tester",
|
||||||
|
"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:*",
|
||||||
|
"zod": "^4.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { type TesterMeta, testerMetaSchema, testerRole } from "./tester.js";
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import type { RoleDefinition } from "@uncaged/workflow";
|
||||||
|
import * as z from "zod/v4";
|
||||||
|
|
||||||
|
export const testerMetaSchema = z.discriminatedUnion("status", [
|
||||||
|
z.object({
|
||||||
|
status: z.literal("passed"),
|
||||||
|
details: z.string(),
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
status: z.literal("failed"),
|
||||||
|
details: z.string(),
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export type TesterMeta = z.infer<typeof testerMetaSchema>;
|
||||||
|
|
||||||
|
const TESTER_SYSTEM = `You are a tester. Run the project's test suite, build, and lint commands. Check what commands are available from the preparer's output in the thread. Report pass/fail with details of what failed.`;
|
||||||
|
|
||||||
|
export const testerRole: RoleDefinition<TesterMeta> = {
|
||||||
|
description: "Runs test, build, and lint commands and reports pass or fail with details.",
|
||||||
|
systemPrompt: TESTER_SYSTEM,
|
||||||
|
extractPrompt:
|
||||||
|
"Extract the verification result: passed with summary details, or failed with details of what broke.",
|
||||||
|
schema: testerMetaSchema,
|
||||||
|
extractRefs: null,
|
||||||
|
extractMode: "single",
|
||||||
|
};
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "src",
|
||||||
|
"outDir": "dist",
|
||||||
|
"composite": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts"],
|
||||||
|
"references": [{ "path": "../workflow" }]
|
||||||
|
}
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
import { describe, expect, test } from "bun:test";
|
||||||
|
import {
|
||||||
|
END,
|
||||||
|
type ModeratorContext,
|
||||||
|
type RoleStep,
|
||||||
|
START,
|
||||||
|
validateWorkflowDescriptor,
|
||||||
|
} from "@uncaged/workflow";
|
||||||
|
|
||||||
|
import type { CommitterMeta } from "@uncaged/workflow-role-committer";
|
||||||
|
import type { PlannerMeta } from "@uncaged/workflow-role-planner";
|
||||||
|
|
||||||
|
import { buildDevelopDescriptor } from "../src/descriptor.js";
|
||||||
|
import { developModerator } from "../src/index.js";
|
||||||
|
import type { DevelopMeta } from "../src/roles.js";
|
||||||
|
|
||||||
|
const DEFAULT_PHASES: PlannerMeta["phases"] = [
|
||||||
|
{
|
||||||
|
hash: "4KNMR2PX",
|
||||||
|
title: "Do the work",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function makeStart(maxRounds: number): ModeratorContext<DevelopMeta>["start"] {
|
||||||
|
return {
|
||||||
|
role: START,
|
||||||
|
content: "Implement the feature",
|
||||||
|
meta: { maxRounds },
|
||||||
|
timestamp: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeCtx(
|
||||||
|
maxRounds: number,
|
||||||
|
steps: ModeratorContext<DevelopMeta>["steps"],
|
||||||
|
): ModeratorContext<DevelopMeta> {
|
||||||
|
return {
|
||||||
|
threadId: "01TEST000000000000000000TR",
|
||||||
|
depth: 0,
|
||||||
|
start: makeStart(maxRounds),
|
||||||
|
steps,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function plannerStep(phases: PlannerMeta["phases"] = DEFAULT_PHASES): RoleStep<DevelopMeta> {
|
||||||
|
return {
|
||||||
|
role: "planner",
|
||||||
|
contentHash: "STUBHASHPLANNER001",
|
||||||
|
meta: { phases },
|
||||||
|
refs: phases.map((p) => p.hash),
|
||||||
|
timestamp: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function coderStep(completedPhase = "4KNMR2PX"): RoleStep<DevelopMeta> {
|
||||||
|
return {
|
||||||
|
role: "coder",
|
||||||
|
contentHash: "STUBHASHCODER00001",
|
||||||
|
meta: { completedPhase, filesChanged: ["a.ts"], summary: "implemented" },
|
||||||
|
refs: [completedPhase],
|
||||||
|
timestamp: 2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function reviewerStep(approved: boolean): RoleStep<DevelopMeta> {
|
||||||
|
return {
|
||||||
|
role: "reviewer",
|
||||||
|
contentHash: "STUBHASHREVIEWER01",
|
||||||
|
meta: approved
|
||||||
|
? { status: "approved" as const }
|
||||||
|
: { status: "rejected" as const, issues: ["needs fix"] },
|
||||||
|
refs: [],
|
||||||
|
timestamp: 3,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function testerStep(passed: boolean): RoleStep<DevelopMeta> {
|
||||||
|
return {
|
||||||
|
role: "tester",
|
||||||
|
contentHash: "STUBHASHTESTER01",
|
||||||
|
meta: passed
|
||||||
|
? { status: "passed" as const, details: "all checks passed" }
|
||||||
|
: { status: "failed" as const, details: "lint failed" },
|
||||||
|
refs: [],
|
||||||
|
timestamp: 4,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function committerStep(meta: CommitterMeta): RoleStep<DevelopMeta> {
|
||||||
|
return {
|
||||||
|
role: "committer",
|
||||||
|
contentHash: "STUBHASHCOMMITTER1",
|
||||||
|
meta,
|
||||||
|
refs: [],
|
||||||
|
timestamp: 5,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("developModerator", () => {
|
||||||
|
test("routes initial → planner → coder → reviewer → tester → committer → END", () => {
|
||||||
|
expect(developModerator(makeCtx(20, []))).toBe("planner");
|
||||||
|
expect(developModerator(makeCtx(20, [plannerStep()]))).toBe("coder");
|
||||||
|
expect(developModerator(makeCtx(20, [plannerStep(), coderStep()]))).toBe("reviewer");
|
||||||
|
expect(developModerator(makeCtx(20, [plannerStep(), coderStep(), reviewerStep(true)]))).toBe(
|
||||||
|
"tester",
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
developModerator(
|
||||||
|
makeCtx(20, [plannerStep(), coderStep(), reviewerStep(true), testerStep(true)]),
|
||||||
|
),
|
||||||
|
).toBe("committer");
|
||||||
|
expect(
|
||||||
|
developModerator(
|
||||||
|
makeCtx(20, [
|
||||||
|
plannerStep(),
|
||||||
|
coderStep(),
|
||||||
|
reviewerStep(true),
|
||||||
|
testerStep(true),
|
||||||
|
committerStep({ status: "committed", branch: "feat/x", commitSha: "abc1234" }),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
).toBe(END);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("reviewer rejects → coder retry when budget allows", () => {
|
||||||
|
const steps: ModeratorContext<DevelopMeta>["steps"] = [
|
||||||
|
plannerStep(),
|
||||||
|
coderStep(),
|
||||||
|
reviewerStep(false),
|
||||||
|
];
|
||||||
|
expect(developModerator(makeCtx(20, steps))).toBe("coder");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("reviewer rejects → END when max rounds exhausted", () => {
|
||||||
|
const steps: ModeratorContext<DevelopMeta>["steps"] = [
|
||||||
|
plannerStep(),
|
||||||
|
coderStep(),
|
||||||
|
reviewerStep(false),
|
||||||
|
];
|
||||||
|
expect(developModerator(makeCtx(4, steps))).toBe(END);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("tester failed → coder retry when budget allows", () => {
|
||||||
|
const steps: ModeratorContext<DevelopMeta>["steps"] = [
|
||||||
|
plannerStep(),
|
||||||
|
coderStep(),
|
||||||
|
reviewerStep(true),
|
||||||
|
testerStep(false),
|
||||||
|
];
|
||||||
|
expect(developModerator(makeCtx(20, steps))).toBe("coder");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("tester failed → END when max rounds exhausted", () => {
|
||||||
|
const steps: ModeratorContext<DevelopMeta>["steps"] = [
|
||||||
|
plannerStep(),
|
||||||
|
coderStep(),
|
||||||
|
reviewerStep(true),
|
||||||
|
testerStep(false),
|
||||||
|
];
|
||||||
|
expect(developModerator(makeCtx(5, steps))).toBe(END);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("multiple planner phases → coder until all complete, then reviewer", () => {
|
||||||
|
const phases: PlannerMeta["phases"] = [
|
||||||
|
{ hash: "AA000001", title: "first phase" },
|
||||||
|
{ hash: "AA000002", title: "second phase" },
|
||||||
|
];
|
||||||
|
expect(developModerator(makeCtx(20, [plannerStep(phases)]))).toBe("coder");
|
||||||
|
expect(developModerator(makeCtx(20, [plannerStep(phases), coderStep("AA000001")]))).toBe(
|
||||||
|
"coder",
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
developModerator(
|
||||||
|
makeCtx(20, [plannerStep(phases), coderStep("AA000001"), coderStep("AA000002")]),
|
||||||
|
),
|
||||||
|
).toBe("reviewer");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("one-shot coder reports only last phase hash → reviewer (moderator treats as all phases done)", () => {
|
||||||
|
const phases: PlannerMeta["phases"] = [
|
||||||
|
{ hash: "BB000001", title: "setup branch" },
|
||||||
|
{ hash: "BB000002", title: "write tests" },
|
||||||
|
{ hash: "BB000003", title: "verify" },
|
||||||
|
{ hash: "BB000004", title: "polish" },
|
||||||
|
];
|
||||||
|
expect(developModerator(makeCtx(20, [plannerStep(phases), coderStep("BB000004")]))).toBe(
|
||||||
|
"reviewer",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("unrecognised completedPhase hash → coder retry when budget allows", () => {
|
||||||
|
const phases: PlannerMeta["phases"] = [
|
||||||
|
{ hash: "CC000001", title: "first phase" },
|
||||||
|
{ hash: "CC000002", title: "second phase" },
|
||||||
|
];
|
||||||
|
expect(developModerator(makeCtx(20, [plannerStep(phases), coderStep("all-done")]))).toBe(
|
||||||
|
"coder",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("incomplete phases → END when max rounds exhausted", () => {
|
||||||
|
const phases: PlannerMeta["phases"] = [
|
||||||
|
{ hash: "DD000001", title: "first phase" },
|
||||||
|
{ hash: "DD000002", title: "second phase" },
|
||||||
|
];
|
||||||
|
const steps: ModeratorContext<DevelopMeta>["steps"] = [
|
||||||
|
plannerStep(phases),
|
||||||
|
coderStep("DD000001"),
|
||||||
|
];
|
||||||
|
expect(developModerator(makeCtx(3, steps))).toBe(END);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("committer → END for any committer meta status", () => {
|
||||||
|
const committed = committerStep({ status: "committed", branch: "f", commitSha: "x" });
|
||||||
|
const recoverable = committerStep({
|
||||||
|
status: "recoverable",
|
||||||
|
error: "merge conflict",
|
||||||
|
logRef: null,
|
||||||
|
});
|
||||||
|
const unrecoverable = committerStep({
|
||||||
|
status: "unrecoverable",
|
||||||
|
error: "repo missing",
|
||||||
|
logRef: "log1",
|
||||||
|
});
|
||||||
|
const base: ModeratorContext<DevelopMeta>["steps"] = [
|
||||||
|
plannerStep(),
|
||||||
|
coderStep(),
|
||||||
|
reviewerStep(true),
|
||||||
|
testerStep(true),
|
||||||
|
];
|
||||||
|
expect(developModerator(makeCtx(20, [...base, committed]))).toBe(END);
|
||||||
|
expect(developModerator(makeCtx(20, [...base, recoverable]))).toBe(END);
|
||||||
|
expect(developModerator(makeCtx(20, [...base, unrecoverable]))).toBe(END);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("buildDevelopDescriptor", () => {
|
||||||
|
test("lists all roles with schemas that validate", () => {
|
||||||
|
const descriptor = buildDevelopDescriptor();
|
||||||
|
const validated = validateWorkflowDescriptor(descriptor);
|
||||||
|
expect(validated.ok).toBe(true);
|
||||||
|
if (!validated.ok) {
|
||||||
|
throw new Error(validated.error);
|
||||||
|
}
|
||||||
|
expect(Object.keys(validated.value.roles).sort()).toEqual([
|
||||||
|
"coder",
|
||||||
|
"committer",
|
||||||
|
"planner",
|
||||||
|
"reviewer",
|
||||||
|
"tester",
|
||||||
|
]);
|
||||||
|
for (const key of ["planner", "coder", "reviewer", "tester", "committer"] as const) {
|
||||||
|
const role = validated.value.roles[key];
|
||||||
|
expect(role).toBeDefined();
|
||||||
|
expect(typeof role.schema).toBe("object");
|
||||||
|
expect(role.schema).not.toBeNull();
|
||||||
|
expect(Array.isArray(role.schema)).toBe(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "@uncaged/workflow-template-develop",
|
||||||
|
"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-role-coder": "workspace:*",
|
||||||
|
"@uncaged/workflow-role-committer": "workspace:*",
|
||||||
|
"@uncaged/workflow-role-planner": "workspace:*",
|
||||||
|
"@uncaged/workflow-role-reviewer": "workspace:*",
|
||||||
|
"@uncaged/workflow-role-tester": "workspace:*"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { buildDescriptor } from "@uncaged/workflow";
|
||||||
|
|
||||||
|
import { developModerator } from "./moderator.js";
|
||||||
|
import { DEVELOP_WORKFLOW_DESCRIPTION, developRoles } from "./roles.js";
|
||||||
|
|
||||||
|
export function buildDevelopDescriptor() {
|
||||||
|
return buildDescriptor({
|
||||||
|
description: DEVELOP_WORKFLOW_DESCRIPTION,
|
||||||
|
roles: developRoles,
|
||||||
|
moderator: developModerator,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import {
|
||||||
|
type AgentBinding,
|
||||||
|
createWorkflow,
|
||||||
|
type ExtractFn,
|
||||||
|
type LlmProvider,
|
||||||
|
type WorkflowDefinition,
|
||||||
|
type WorkflowFn,
|
||||||
|
} from "@uncaged/workflow";
|
||||||
|
|
||||||
|
import { developModerator } from "./moderator.js";
|
||||||
|
import { DEVELOP_WORKFLOW_DESCRIPTION, type DevelopMeta, developRoles } from "./roles.js";
|
||||||
|
|
||||||
|
export {
|
||||||
|
type CoderMeta,
|
||||||
|
coderMetaSchema,
|
||||||
|
coderRole,
|
||||||
|
} from "@uncaged/workflow-role-coder";
|
||||||
|
export {
|
||||||
|
type CommitterMeta,
|
||||||
|
committerMetaSchema,
|
||||||
|
committerRole,
|
||||||
|
} from "@uncaged/workflow-role-committer";
|
||||||
|
export {
|
||||||
|
type PlannerMeta,
|
||||||
|
phaseSchema,
|
||||||
|
plannerMetaSchema,
|
||||||
|
plannerRole,
|
||||||
|
} from "@uncaged/workflow-role-planner";
|
||||||
|
export {
|
||||||
|
type ReviewerMeta,
|
||||||
|
reviewerMetaSchema,
|
||||||
|
reviewerRole,
|
||||||
|
} from "@uncaged/workflow-role-reviewer";
|
||||||
|
export {
|
||||||
|
type TesterMeta,
|
||||||
|
testerMetaSchema,
|
||||||
|
testerRole,
|
||||||
|
} from "@uncaged/workflow-role-tester";
|
||||||
|
export { buildDevelopDescriptor } from "./descriptor.js";
|
||||||
|
export { developModerator } from "./moderator.js";
|
||||||
|
export {
|
||||||
|
DEVELOP_WORKFLOW_DESCRIPTION,
|
||||||
|
type DevelopMeta,
|
||||||
|
type DevelopRoles,
|
||||||
|
developRoles,
|
||||||
|
} from "./roles.js";
|
||||||
|
|
||||||
|
export const developWorkflowDefinition: WorkflowDefinition<DevelopMeta> = {
|
||||||
|
description: DEVELOP_WORKFLOW_DESCRIPTION,
|
||||||
|
roles: developRoles,
|
||||||
|
moderator: developModerator,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function createDevelopRun(
|
||||||
|
binding: AgentBinding,
|
||||||
|
extract: ExtractFn,
|
||||||
|
llmProvider: LlmProvider | null,
|
||||||
|
): WorkflowFn {
|
||||||
|
return createWorkflow(developWorkflowDefinition, binding, extract, llmProvider);
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import type { Moderator, ModeratorContext } from "@uncaged/workflow";
|
||||||
|
import { END } from "@uncaged/workflow";
|
||||||
|
|
||||||
|
import type { DevelopMeta } from "./roles.js";
|
||||||
|
|
||||||
|
function coderFinishedAllPlannedPhases(
|
||||||
|
phases: ReadonlyArray<{ hash: string }>,
|
||||||
|
coderCompletedPhases: ReadonlyArray<string>,
|
||||||
|
): boolean {
|
||||||
|
if (phases.length === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const plannedHashes = new Set(phases.map((p) => p.hash));
|
||||||
|
const lastHash = phases[phases.length - 1].hash;
|
||||||
|
const explicit = new Set(coderCompletedPhases.filter((h) => plannedHashes.has(h)));
|
||||||
|
if (phases.every((p) => explicit.has(p.hash))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (coderCompletedPhases.some((h) => h === lastHash)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextAfterCoder(
|
||||||
|
ctx: ModeratorContext<DevelopMeta>,
|
||||||
|
maxRounds: number,
|
||||||
|
): (keyof DevelopMeta & string) | typeof END {
|
||||||
|
const plannerStep = ctx.steps.find((s) => s.role === "planner");
|
||||||
|
if (plannerStep === undefined) {
|
||||||
|
return "reviewer";
|
||||||
|
}
|
||||||
|
const phases = plannerStep.meta.phases;
|
||||||
|
const coderCompletedPhases = ctx.steps
|
||||||
|
.filter((s) => s.role === "coder")
|
||||||
|
.map((s) => s.meta.completedPhase);
|
||||||
|
const allDone = coderFinishedAllPlannedPhases(phases, coderCompletedPhases);
|
||||||
|
if (allDone) {
|
||||||
|
return "reviewer";
|
||||||
|
}
|
||||||
|
if (ctx.steps.length < maxRounds - 1) {
|
||||||
|
return "coder";
|
||||||
|
}
|
||||||
|
return END;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const developModerator: Moderator<DevelopMeta> = (ctx) => {
|
||||||
|
const maxRounds = ctx.start.meta.maxRounds;
|
||||||
|
|
||||||
|
if (ctx.steps.length === 0) {
|
||||||
|
return "planner";
|
||||||
|
}
|
||||||
|
|
||||||
|
const last = ctx.steps[ctx.steps.length - 1];
|
||||||
|
|
||||||
|
if (last.role === "planner") {
|
||||||
|
return "coder";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (last.role === "coder") {
|
||||||
|
return nextAfterCoder(ctx, maxRounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (last.role === "reviewer") {
|
||||||
|
if (last.meta.status === "approved") {
|
||||||
|
return "tester";
|
||||||
|
}
|
||||||
|
if (ctx.steps.length < maxRounds - 1) {
|
||||||
|
return "coder";
|
||||||
|
}
|
||||||
|
return END;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (last.role === "tester") {
|
||||||
|
if (last.meta.status === "passed") {
|
||||||
|
return "committer";
|
||||||
|
}
|
||||||
|
if (ctx.steps.length < maxRounds - 1) {
|
||||||
|
return "coder";
|
||||||
|
}
|
||||||
|
return END;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (last.role === "committer") {
|
||||||
|
return END;
|
||||||
|
}
|
||||||
|
|
||||||
|
return END;
|
||||||
|
};
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import type { RoleDefinition } from "@uncaged/workflow";
|
||||||
|
import { type CoderMeta, coderRole } from "@uncaged/workflow-role-coder";
|
||||||
|
import { type CommitterMeta, committerRole } from "@uncaged/workflow-role-committer";
|
||||||
|
import { type PlannerMeta, plannerRole } from "@uncaged/workflow-role-planner";
|
||||||
|
import { type ReviewerMeta, reviewerRole } from "@uncaged/workflow-role-reviewer";
|
||||||
|
import { type TesterMeta, testerRole } from "@uncaged/workflow-role-tester";
|
||||||
|
|
||||||
|
export const DEVELOP_WORKFLOW_DESCRIPTION =
|
||||||
|
"Plan phases, implement incrementally, review, verify with tests/build/lint, and commit (planner → coder [repeat per phase] → reviewer → tester → committer).";
|
||||||
|
|
||||||
|
export type DevelopMeta = {
|
||||||
|
planner: PlannerMeta;
|
||||||
|
coder: CoderMeta;
|
||||||
|
reviewer: ReviewerMeta;
|
||||||
|
tester: TesterMeta;
|
||||||
|
committer: CommitterMeta;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DevelopRoles = {
|
||||||
|
[K in keyof DevelopMeta]: RoleDefinition<DevelopMeta[K]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const developRoles: DevelopRoles = {
|
||||||
|
planner: plannerRole,
|
||||||
|
coder: coderRole,
|
||||||
|
reviewer: reviewerRole,
|
||||||
|
tester: testerRole,
|
||||||
|
committer: committerRole,
|
||||||
|
};
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "src",
|
||||||
|
"outDir": "dist",
|
||||||
|
"composite": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts"],
|
||||||
|
"references": [
|
||||||
|
{ "path": "../workflow" },
|
||||||
|
{ "path": "../workflow-role-coder" },
|
||||||
|
{ "path": "../workflow-role-committer" },
|
||||||
|
{ "path": "../workflow-role-planner" },
|
||||||
|
{ "path": "../workflow-role-reviewer" },
|
||||||
|
{ "path": "../workflow-role-tester" }
|
||||||
|
]
|
||||||
|
}
|
||||||
+3
-1
@@ -23,10 +23,12 @@
|
|||||||
{ "path": "packages/workflow-role-coder" },
|
{ "path": "packages/workflow-role-coder" },
|
||||||
{ "path": "packages/workflow-role-planner" },
|
{ "path": "packages/workflow-role-planner" },
|
||||||
{ "path": "packages/workflow-role-reviewer" },
|
{ "path": "packages/workflow-role-reviewer" },
|
||||||
|
{ "path": "packages/workflow-role-tester" },
|
||||||
{ "path": "packages/workflow-agent-cursor" },
|
{ "path": "packages/workflow-agent-cursor" },
|
||||||
{ "path": "packages/workflow-agent-hermes" },
|
{ "path": "packages/workflow-agent-hermes" },
|
||||||
{ "path": "packages/workflow-util-agent" },
|
{ "path": "packages/workflow-util-agent" },
|
||||||
{ "path": "packages/cli-workflow" },
|
{ "path": "packages/cli-workflow" },
|
||||||
{ "path": "packages/workflow-template-solve-issue" }
|
{ "path": "packages/workflow-template-solve-issue" },
|
||||||
|
{ "path": "packages/workflow-template-develop" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user