小橘 a469f30b42 refactor(workflow-generator): multi-file DIP + Role Factory + esbuild bundle
- Split 500-line monolith into roles/{planner,coder,tester,committer}/
- Each role: index.ts (build function) + prompt.ts (pure function)
- Use createCursorRole/createLlmRole/createHermesRole factories
- DIP: env vars read in index.ts, injected via build.ts
- esbuild bundle to dist/index.js (24kb)
- Moderator logic preserved: planner→coder→tester→committer with retries

Fixes xiaoju/nerve-workspace#3
2026-04-28 08:48:23 +00:00

46 lines
1.2 KiB
TypeScript

import { END } from "@uncaged/nerve-core";
import type { Moderator } from "@uncaged/nerve-core";
import type { PlannerMeta } from "./roles/planner/index.js";
import type { CoderMeta } from "./roles/coder/index.js";
import type { TesterMeta } from "./roles/tester/index.js";
import type { CommitterMeta } from "./roles/committer/index.js";
export type WorkflowMeta = {
planner: PlannerMeta;
coder: CoderMeta;
tester: TesterMeta;
committer: CommitterMeta;
};
export const moderator: Moderator<WorkflowMeta> = (context) => {
if (context.steps.length === 0) {
return "planner";
}
const last = context.steps[context.steps.length - 1];
if (last.role === "planner") {
if (last.meta.workflowName.trim().length > 0) return "coder";
const plannerAttempts = context.steps.filter((s) => s.role === "planner").length;
return plannerAttempts < 3 ? "planner" : END;
}
if (last.role === "coder") {
if (last.meta.lintPassed && last.meta.buildPassed) {
return "tester";
}
if (last.meta.attempt < 3) {
return "coder";
}
return END;
}
if (last.role === "tester") {
if (last.meta.passed) {
return "committer";
}
if (last.meta.attempt < 3) {
return "coder";
}
return END;
}
return END;
};