FN-6891: route CE review through ce-code-review

Route compound-engineering review directly through the CE code-review skill gate.

- Remove the generic review seam from the compound-engineering workflow.
- Keep ce-code-review as the single gated review stage with coding tool mode.
- Add workflow tests covering CE review ordering, layout, compiled steps, and unaffected built-in review seams.
- Add a patch changeset for the published fusion package.

Files changed:
 .changeset/fn-6891-ce-review-skill.md              |  5 ++
 .../core/src/__tests__/builtin-workflows.test.ts   | 54 +++++++++++++++++++---
 packages/core/src/builtin-workflows.ts             |  5 +-
 3 files changed, 57 insertions(+), 7 deletions(-)

Fusion-Task-Id: FN-6891

Fusion-Task-Lineage: c85e2672-5715-4328-9dcb-ba697d4ac000
This commit is contained in:
gsxdsm
2026-06-21 18:22:43 -07:00
parent f49d8c61e7
commit 91971b674b
3 changed files with 57 additions and 7 deletions

View File

@@ -0,0 +1,5 @@
---
"@runfusion/fusion": patch
---
Update the built-in compound-engineering workflow so its Review stage runs the `compound-engineering:ce-code-review` skill directly. The redundant generic reviewer seam node was removed, leaving the CE code-review gate as the sole review stage.

View File

@@ -354,13 +354,15 @@ describe("built-in workflows", () => {
}
});
it("compound-engineering compiles its skill nodes to steps", () => {
it("compound-engineering compiles exactly one ce-code-review step and no generic review seam", () => {
const ce = getBuiltinWorkflow("builtin:compound-engineering")!;
const steps = compileWorkflowToSteps(ce.ir);
// plan + execute (ce-work) + code-review (pre-merge) + document (post-merge)
// — review/merge seams are skipped.
expect(steps.length).toBeGreaterThanOrEqual(4);
// plan + execute (ce-work) + code-review (pre-merge) + commit-pr +
// resolve-feedback + document (post-merge) — merge seams are skipped.
expect(steps.length).toBeGreaterThanOrEqual(6);
expect(steps.some((s) => s.name === "Plan")).toBe(true);
expect(steps.filter((s) => s.skillName === "compound-engineering:ce-code-review")).toHaveLength(1);
expect(steps.some((s) => s.name === "Review" && !s.skillName)).toBe(false);
});
it("compound-engineering runs ce-work for the execute step in coding mode", () => {
@@ -393,6 +395,44 @@ describe("built-in workflows", () => {
expect(ids.indexOf("merge")).toBeLessThan(ids.indexOf("document"));
});
it("compound-engineering review stage is ce-code-review, with graph ordering and layout intact", () => {
const ce = getBuiltinWorkflow("builtin:compound-engineering")!;
const byId = (id: string) => ce.ir.nodes.find((n) => n.id === id);
const authoredNodeIds = ce.ir.nodes.filter((node) => node.id !== "start" && node.id !== "end").map((node) => node.id);
expect(authoredNodeIds).toEqual([
"plan",
"execute",
"code-review",
"commit-pr",
"resolve-feedback",
"merge",
"document",
]);
expect(ce.ir.nodes.some((node) => node.config?.seam === "review")).toBe(false);
const codeReview = byId("code-review");
expect(codeReview?.kind).toBe("gate");
expect(codeReview?.config?.skillName).toBe("compound-engineering:ce-code-review");
expect(codeReview?.config?.gateMode).toBe("gate");
expect(codeReview?.config?.toolMode).toBe("coding");
const layout = ce.layout ?? {};
expect(Object.keys(layout).sort()).toEqual(ce.ir.nodes.map((node) => node.id).sort());
for (let i = 1; i < ce.ir.nodes.length; i += 1) {
expect(layout[ce.ir.nodes[i].id].x - layout[ce.ir.nodes[i - 1].id].x).toBe(170);
}
expect(ce.ir.edges.some((edge) => edge.from === "execute" && edge.to === "code-review")).toBe(true);
expect(ce.ir.edges.some((edge) => edge.from === "code-review" && edge.to === "commit-pr")).toBe(true);
});
it("other built-in workflows retain their generic review nodes", () => {
const coding = getBuiltinWorkflow("builtin:coding")!;
const reviewHeavy = getBuiltinWorkflow("builtin:review-heavy")!;
expect(coding.ir.nodes.some((node) => node.id === "review" && node.config?.seam === "review")).toBe(true);
expect(reviewHeavy.ir.nodes.some((node) => node.id === "review" && node.config?.seam === "review")).toBe(true);
});
it("compound-engineering runs plan/code-review/document in coding mode and carries skillName onto compiled steps (U1/U4)", () => {
const ce = getBuiltinWorkflow("builtin:compound-engineering")!;
const byId = (id: string) => ce.ir.nodes.find((n) => n.id === id);
@@ -407,8 +447,10 @@ describe("built-in workflows", () => {
const plan = steps.find((s) => s.name === "Plan");
expect(plan?.skillName).toBe("compound-engineering:ce-plan");
expect(plan?.toolMode).toBe("coding");
const codeReview = steps.find((s) => s.skillName === "compound-engineering:ce-code-review");
expect(codeReview?.toolMode).toBe("coding");
const codeReviewSteps = steps.filter((s) => s.skillName === "compound-engineering:ce-code-review");
expect(codeReviewSteps).toHaveLength(1);
expect(codeReviewSteps[0].gateMode).toBe("gate");
expect(codeReviewSteps[0].toolMode).toBe("coding");
const document = steps.find((s) => s.skillName === "compound-engineering:ce-compound");
expect(document?.toolMode).toBe("coding");
});

View File

@@ -208,7 +208,6 @@ export const BUILTIN_WORKFLOWS: WorkflowDefinition[] = [
prompt: "Execute the plan for this task, following existing patterns and maintaining quality throughout.",
},
},
{ id: "review", kind: "prompt", config: builtinPromptConfig("review", "Review") },
{
id: "code-review",
kind: "gate",
@@ -217,6 +216,10 @@ export const BUILTIN_WORKFLOWS: WorkflowDefinition[] = [
executor: "skill",
skillName: "compound-engineering:ce-code-review",
gateMode: "gate",
/*
* FNXC:Workflows 2026-06-21-00:00:
* FN-6891 requires the compound-engineering Review stage to invoke compound-engineering:ce-code-review directly. The prior generic reviewer seam was removed so CE review runs through the CE skill and still blocks merge as a gate.
*/
// Coding mode so ce-code-review can fan out to its reviewer-persona
// subagents via fn_spawn_agent. As a gate step it still emits the
// verdict JSON (KTD-6); it is not meant to write the tree (Risk-1).