e9e4960714
- Verify createWorkflow in runtime has zero I/O imports - Migrate agent-cursor, agent-hermes to pure workflow-runtime dependency - Migrate agent-llm, util-agent, templates to dual dependency (runtime for types, engine for CAS/merkle/buildDescriptor) - All 377 tests passing Refs #121, relates #123 #124
90 lines
2.1 KiB
TypeScript
90 lines
2.1 KiB
TypeScript
import type { Moderator, ModeratorContext } from "@uncaged/workflow-runtime";
|
|
import { END } from "@uncaged/workflow-runtime";
|
|
|
|
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;
|
|
};
|