fix(FN-7224): normalize built-in workflow review toggles

Expose Plan Review, Code Review, and Browser Verification optional groups across engineering built-ins, with Quick Fix defaulting all three off and Compound Engineering keeping CE code review inside the shared optional toggle.

Fusion-Task-Id: FN-7224
This commit is contained in:
gsxdsm
2026-06-29 08:28:29 -07:00
parent 57d8065778
commit 64518280f7
8 changed files with 186 additions and 89 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Add consistent Plan Review, Code Review, and Browser Verification toggles to engineering workflows.
category: fix
dev: Quick Fix seeds all three optional groups off; other engineering built-ins seed plan/code review on and browser verification off.

View File

@@ -10,6 +10,7 @@ import {
} from "../builtin-workflows.js";
import { BUILTIN_CODING_WORKFLOW_IR } from "../builtin-coding-workflow-ir.js";
import { BROWSER_VERIFICATION_GROUP_ID, BROWSER_VERIFICATION_STEP_NODE_ID } from "../builtin-browser-verification-group.js";
import { CODE_REVIEW_STEP_NODE_ID } from "../builtin-code-review-group.js";
import { PLAN_REVIEW_GROUP_ID, PLAN_REVIEW_STEP_NODE_ID } from "../builtin-plan-review-group.js";
import { builtinPromptConfig, BUILTIN_SEAM_PROMPTS } from "../builtin-workflow-prompts.js";
import { BUILTIN_WORKFLOW_SETTINGS } from "../builtin-workflow-settings.js";
@@ -52,7 +53,11 @@ describe("built-in workflows", () => {
const NON_COMPILABLE_BUILTIN_IDS = new Set([
"builtin:coding",
"builtin:legacy-coding",
"builtin:quick-fix",
"builtin:review-heavy",
"builtin:design",
"builtin:marketing",
"builtin:compound-engineering",
"builtin:stepwise-coding",
"builtin:pr-workflow",
]);
@@ -68,6 +73,34 @@ describe("built-in workflows", () => {
}
});
it("engineering built-ins expose plan, code, and browser optional groups with expected defaults", () => {
const expectedDefaults: Record<string, Record<string, boolean>> = {
"builtin:coding": { "plan-review": true, "code-review": true, "browser-verification": false },
"builtin:legacy-coding": { "plan-review": true, "code-review": true, "browser-verification": false },
"builtin:quick-fix": { "plan-review": false, "code-review": false, "browser-verification": false },
"builtin:review-heavy": { "plan-review": true, "code-review": true, "browser-verification": false },
"builtin:design": { "plan-review": true, "code-review": true, "browser-verification": false },
"builtin:compound-engineering": { "plan-review": true, "code-review": true, "browser-verification": false },
"builtin:stepwise-coding": { "plan-review": true, "code-review": true, "browser-verification": false },
};
for (const [workflowId, defaults] of Object.entries(expectedDefaults)) {
const workflow = getBuiltinWorkflow(workflowId)!;
const byId = new Map(workflow.ir.nodes.map((node) => [node.id, node]));
for (const [groupId, defaultOn] of Object.entries(defaults)) {
const group = byId.get(groupId);
expect(group?.kind, `${workflowId}:${groupId}`).toBe("optional-group");
expect(group?.config?.defaultOn, `${workflowId}:${groupId}`).toBe(defaultOn);
}
const nodeOrder = workflow.ir.nodes.map((node) => node.id);
const executionBoundary = nodeOrder.includes("execute") ? nodeOrder.indexOf("execute") : nodeOrder.indexOf("steps");
expect(executionBoundary, workflowId).toBeGreaterThanOrEqual(0);
expect(nodeOrder.indexOf("plan-review"), workflowId).toBeLessThan(executionBoundary);
expect(nodeOrder.indexOf("browser-verification"), workflowId).toBeGreaterThan(executionBoundary);
expect(nodeOrder.indexOf("code-review"), workflowId).toBeGreaterThan(nodeOrder.indexOf("browser-verification"));
}
});
it("does not expose lowercase Code review step names in built-in workflow nodes", () => {
for (const workflow of BUILTIN_WORKFLOWS) {
for (const node of workflow.ir.nodes) {
@@ -446,10 +479,9 @@ describe("built-in workflows", () => {
expect(design).toBeDefined();
expect(design!.kind).toBe("workflow");
expect(() => parseWorkflowIr(design!.ir)).not.toThrow();
expect(() => compileWorkflowToSteps(design!.ir)).not.toThrow();
const authoredNodeIds = design!.ir.nodes.filter((node) => node.id !== "start" && node.id !== "end").map((node) => node.id);
expect(authoredNodeIds).toEqual(["execute", "design-review", "review", "merge"]);
expect(authoredNodeIds).toEqual(["plan-review", "execute", "browser-verification", "code-review", "design-review", "review", "merge"]);
const execute = design!.ir.nodes.find((node) => node.id === "execute");
expect(execute?.config?.seam).toBe("execute");
@@ -605,17 +637,13 @@ describe("built-in workflows", () => {
}
});
it("compound-engineering compiles exactly one ce-code-review step and no generic review seam", () => {
it("compound-engineering exposes ce-code-review as the optional Code Review group and no generic review seam", () => {
const ce = getBuiltinWorkflow("builtin:compound-engineering")!;
const steps = compileWorkflowToSteps(ce.ir);
// plan + execute (ce-work) + code-review (pre-merge) + commit-pr +
// resolve-feedback + document (post-merge) — merge seams and graph-native
// optional-groups are skipped by the legacy step compiler.
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 === "CE Doc Review")).toBe(false);
expect(steps.some((s) => s.name === "Review" && !s.skillName)).toBe(false);
const codeReview = ce.ir.nodes.find((node) => node.id === "code-review");
const template = codeReview?.config?.template as { nodes?: Array<{ id: string; config?: Record<string, unknown> }> } | undefined;
expect(codeReview?.kind).toBe("optional-group");
expect(template?.nodes?.filter((node) => node.config?.skillName === "compound-engineering:ce-code-review")).toHaveLength(1);
expect(ce.ir.nodes.some((node) => node.config?.seam === "review")).toBe(false);
});
it("compound-engineering runs ce-work for the execute step in coding mode", () => {
@@ -625,11 +653,7 @@ describe("built-in workflows", () => {
const executeNode = ce.ir.nodes.find((n) => n.id === "execute");
expect(executeNode?.config?.executor).toBe("skill");
expect(executeNode?.config?.skillName).toBe("compound-engineering:ce-work");
// The compiled step runs in coding mode so write/spawn tools are available.
const steps = compileWorkflowToSteps(ce.ir);
const execute = steps.find((s) => s.name === "Execute");
expect(execute).toBeDefined();
expect(execute!.toolMode).toBe("coding");
expect(executeNode?.config?.toolMode).toBe("coding");
});
it("compound-engineering skill-node prompts name their /ce- slash commands", () => {
@@ -638,7 +662,6 @@ describe("built-in workflows", () => {
const expectedPrompts = new Map([
["plan", "/ce-plan"],
["execute", "/ce-work"],
["code-review", "/ce-code-review"],
["commit-pr", "/ce-commit-push-pr"],
["resolve-feedback", "/ce-resolve-pr-feedback"],
["document", "/ce-compound"],
@@ -649,6 +672,8 @@ describe("built-in workflows", () => {
}
const docReviewTemplate = byId("ce-doc-review")?.config?.template as { nodes?: Array<{ config?: Record<string, unknown> }> } | undefined;
expect(String(docReviewTemplate?.nodes?.[0]?.config?.prompt ?? "")).toContain("/ce-doc-review");
const codeReviewTemplate = byId("code-review")?.config?.template as { nodes?: Array<{ config?: Record<string, unknown> }> } | undefined;
expect(String(codeReviewTemplate?.nodes?.[0]?.config?.prompt ?? "")).toContain("/ce-code-review");
expect(String(byId("merge")?.config?.prompt ?? "")).not.toContain("/ce-");
});
@@ -678,7 +703,9 @@ describe("built-in workflows", () => {
expect(authoredNodeIds).toEqual([
"plan",
"ce-doc-review",
"plan-review",
"execute",
"browser-verification",
"code-review",
"commit-pr",
"resolve-feedback",
@@ -703,11 +730,19 @@ describe("built-in workflows", () => {
});
const codeReview = byId("code-review");
expect(codeReview?.kind).toBe("gate");
expect(codeReview?.kind).toBe("optional-group");
expect(codeReview?.config?.name).toBe("Code Review");
expect(codeReview?.config?.skillName).toBe("compound-engineering:ce-code-review");
expect(codeReview?.config?.gateMode).toBe("gate");
expect(codeReview?.config?.toolMode).toBe("coding");
expect(codeReview?.config?.defaultOn).toBe(true);
const codeReviewTemplate = codeReview?.config?.template as { nodes?: Array<{ id: string; kind: string; config?: Record<string, unknown> }> } | undefined;
expect(codeReviewTemplate?.nodes?.[0]).toMatchObject({
id: CODE_REVIEW_STEP_NODE_ID,
kind: "gate",
config: {
skillName: "compound-engineering:ce-code-review",
gateMode: "gate",
toolMode: "coding",
},
});
const layout = ce.layout ?? {};
expect(Object.keys(layout).sort()).toEqual(ce.ir.nodes.map((node) => node.id).sort());
@@ -715,8 +750,10 @@ describe("built-in workflows", () => {
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 === "plan" && edge.to === "ce-doc-review")).toBe(true);
expect(ce.ir.edges.some((edge) => edge.from === "ce-doc-review" && edge.to === "execute")).toBe(true);
expect(ce.ir.edges.some((edge) => edge.from === "execute" && edge.to === "code-review")).toBe(true);
expect(ce.ir.edges.some((edge) => edge.from === "ce-doc-review" && edge.to === "plan-review")).toBe(true);
expect(ce.ir.edges.some((edge) => edge.from === "plan-review" && edge.to === "execute")).toBe(true);
expect(ce.ir.edges.some((edge) => edge.from === "execute" && edge.to === "browser-verification")).toBe(true);
expect(ce.ir.edges.some((edge) => edge.from === "browser-verification" && edge.to === "code-review")).toBe(true);
expect(ce.ir.edges.some((edge) => edge.from === "code-review" && edge.to === "commit-pr")).toBe(true);
});
@@ -738,20 +775,12 @@ describe("built-in workflows", () => {
// U4: fan-out steps (plan, code-review) need coding so fn_spawn_agent is
// available for persona fan-out; document needs coding to WRITE docs/solutions.
expect(byId("plan")?.config?.toolMode).toBe("coding");
expect(byId("code-review")?.config?.toolMode).toBe("coding");
expect(byId("document")?.config?.toolMode).toBe("coding");
// U1: the compiler carries each node's skillName onto the materialized step so
// the step session can actually LOAD the skill (not just name it in prompt text).
const steps = compileWorkflowToSteps(ce.ir);
const plan = steps.find((s) => s.name === "Plan");
expect(plan?.skillName).toBe("compound-engineering:ce-plan");
expect(plan?.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");
const codeReview = byId("code-review");
const template = codeReview?.config?.template as { nodes?: Array<{ config?: Record<string, unknown> }> } | undefined;
expect(template?.nodes?.[0]?.config?.skillName).toBe("compound-engineering:ce-code-review");
expect(template?.nodes?.[0]?.config?.gateMode).toBe("gate");
expect(template?.nodes?.[0]?.config?.toolMode).toBe("coding");
});
describe("store integration", () => {
@@ -855,7 +884,7 @@ describe("built-in workflows", () => {
// skipped the gate).
const expectedGroups: Record<string, string[]> = {
"builtin:coding": ["plan-review", "code-review"],
"builtin:legacy-coding": ["code-review"],
"builtin:legacy-coding": ["plan-review", "code-review"],
"builtin:marketing": [],
"builtin:stepwise-coding": ["plan-review", "code-review"],
};

View File

@@ -110,10 +110,17 @@ describe("resolveWorkflowOptionalSteps (optional-group nodes)", () => {
});
it("resolves the built-in coding/stepwise optional-groups in execution order", () => {
// Legacy coding carries two optional-group toggles on the pre-merge path:
// `browser-verification` (default OFF) then `code-review` (default ON — runs by default
// but is toggleable off per task).
const legacyExpected = [
// Legacy/default/stepwise engineering workflows expose the same three toggles
// in execution order: plan review before execution, then browser verification
// and code review after implementation.
const engineeringExpected = [
{
templateId: "plan-review",
name: "Plan Review",
description: "",
phase: "pre-merge" as const,
defaultOn: true,
},
{
templateId: "browser-verification",
name: "Browser Verification",
@@ -129,19 +136,9 @@ describe("resolveWorkflowOptionalSteps (optional-group nodes)", () => {
defaultOn: true,
},
];
const stepwiseExpected = [
{
templateId: "plan-review",
name: "Plan Review",
description: "",
phase: "pre-merge" as const,
defaultOn: true,
},
...legacyExpected,
];
expect(resolveWorkflowOptionalSteps(BUILTIN_CODING_WORKFLOW_IR)).toEqual(legacyExpected);
expect(resolveWorkflowOptionalSteps(BUILTIN_STEPWISE_CODING_WORKFLOW_IR)).toEqual(stepwiseExpected);
expect(resolveWorkflowOptionalSteps(BUILTIN_STEPWISE_FINAL_REVIEW_CODING_WORKFLOW_IR)).toEqual(stepwiseExpected);
expect(resolveWorkflowOptionalSteps(BUILTIN_CODING_WORKFLOW_IR)).toEqual(engineeringExpected);
expect(resolveWorkflowOptionalSteps(BUILTIN_STEPWISE_CODING_WORKFLOW_IR)).toEqual(engineeringExpected);
expect(resolveWorkflowOptionalSteps(BUILTIN_STEPWISE_FINAL_REVIEW_CODING_WORKFLOW_IR)).toEqual(engineeringExpected);
});
it("orders Plan Review before execution even when the node is appended after Code Review", () => {
@@ -169,7 +166,7 @@ describe("resolveWorkflowOptionalSteps (optional-group nodes)", () => {
it("seeds default-on optional groups but not browser-verification for the built-ins", () => {
// resolveDefaultOnOptionalGroupIds drives which groups a new task gets enabled by
// default: review groups are on, browser-verification is off.
expect(resolveDefaultOnOptionalGroupIds(BUILTIN_CODING_WORKFLOW_IR)).toEqual(["code-review"]);
expect(resolveDefaultOnOptionalGroupIds(BUILTIN_CODING_WORKFLOW_IR)).toEqual(["plan-review", "code-review"]);
expect(resolveDefaultOnOptionalGroupIds(BUILTIN_STEPWISE_CODING_WORKFLOW_IR)).toEqual(["plan-review", "code-review"]);
expect(resolveDefaultOnOptionalGroupIds(BUILTIN_STEPWISE_FINAL_REVIEW_CODING_WORKFLOW_IR)).toEqual(["plan-review", "code-review"]);
});

View File

@@ -84,14 +84,17 @@ Note: Refs (@e1, @e2) are invalidated after page navigation. Re-snapshot after c
* Mirrors `stepTemplateToNode(browser-verification)`: a single `prompt` node whose
* config carries the inlined prompt + `toolMode: "coding"` + `gateMode: "advisory"`.
*/
export function browserVerificationOptionalGroupNode(column: string): WorkflowIrNode {
export function browserVerificationOptionalGroupNode(
column: string,
options: { defaultOn?: boolean } = {},
): WorkflowIrNode {
return {
id: BROWSER_VERIFICATION_GROUP_ID,
kind: "optional-group",
column,
config: {
name: BROWSER_VERIFICATION_NAME,
defaultOn: false,
defaultOn: options.defaultOn ?? false,
template: {
nodes: [
{

View File

@@ -73,7 +73,10 @@ Be specific: cite \`file:line\` for every finding and explain the concrete failu
* Mirrors `stepTemplateToNode(code-review)`: a single `prompt` node whose config carries
* the inlined prompt + `toolMode: "readonly"` + `gateMode: "advisory"`.
*/
export function codeReviewOptionalGroupNode(column: string): WorkflowIrNode {
export function codeReviewOptionalGroupNode(
column: string,
options: { defaultOn?: boolean } = {},
): WorkflowIrNode {
return {
id: CODE_REVIEW_GROUP_ID,
kind: "optional-group",
@@ -82,7 +85,7 @@ export function codeReviewOptionalGroupNode(column: string): WorkflowIrNode {
name: CODE_REVIEW_NAME,
// Default-ON: runs for every coding task by default, but operators can toggle it
// off per task (remove `code-review` from enabledWorkflowSteps).
defaultOn: true,
defaultOn: options.defaultOn ?? true,
template: {
nodes: [
{

View File

@@ -4,6 +4,7 @@ import { BUILTIN_WORKFLOW_SETTINGS } from "./builtin-workflow-settings.js";
import { builtinPromptConfig } from "./builtin-workflow-prompts.js";
import { browserVerificationOptionalGroupNode } from "./builtin-browser-verification-group.js";
import { codeReviewOptionalGroupNode } from "./builtin-code-review-group.js";
import { planReviewOptionalGroupNode } from "./builtin-plan-review-group.js";
/**
* The built-in default workflow as a v2 IR. Its six columns have ids that are
@@ -68,6 +69,7 @@ const RAW_BUILTIN_CODING_WORKFLOW_IR: WorkflowIr = {
column: "triage",
config: builtinPromptConfig("planning", "Plan / specify"),
},
planReviewOptionalGroupNode("in-progress"),
{
id: "execute",
kind: "prompt",
@@ -104,7 +106,8 @@ const RAW_BUILTIN_CODING_WORKFLOW_IR: WorkflowIr = {
],
edges: [
{ from: "start", to: "planning" },
{ from: "planning", to: "execute", condition: "success" },
{ from: "planning", to: "plan-review", condition: "success" },
{ from: "plan-review", to: "execute", condition: "success" },
// execute → browser-verification (optional-group) → review. When the group is
// disabled it passes through with outcome=success and routes straight to review.
{ from: "execute", to: "browser-verification", condition: "success" },
@@ -126,6 +129,7 @@ const RAW_BUILTIN_CODING_WORKFLOW_IR: WorkflowIr = {
{ from: "merge-attempt", to: "merge-manual-hold", condition: "outcome:manual-required" },
{ from: "recovery-router", to: "merge-attempt", condition: "outcome:wake-merge", kind: "rework" },
{ from: "planning", to: "end", condition: "failure" },
{ from: "plan-review", to: "end", condition: "failure" },
{ from: "execute", to: "end", condition: "failure" },
{ from: "browser-verification", to: "end", condition: "failure" },
{ from: "code-review", to: "end", condition: "failure" },

View File

@@ -39,14 +39,17 @@ Be specific: cite the plan section or file path for every finding and explain th
{"verdict":"APPROVE|APPROVE_WITH_NOTES|REVISE","notes":"..."}`;
/** Build the `plan-review` optional-group node placed between planning and execution. */
export function planReviewOptionalGroupNode(column: string): WorkflowIrNode {
export function planReviewOptionalGroupNode(
column: string,
options: { defaultOn?: boolean } = {},
): WorkflowIrNode {
return {
id: PLAN_REVIEW_GROUP_ID,
kind: "optional-group",
column,
config: {
name: PLAN_REVIEW_NAME,
defaultOn: true,
defaultOn: options.defaultOn ?? true,
template: {
nodes: [
{

View File

@@ -6,6 +6,9 @@ import { BUILTIN_STEPWISE_CODING_WORKFLOW_IR } from "./builtin-stepwise-coding-w
import { BUILTIN_STEPWISE_FINAL_REVIEW_CODING_WORKFLOW_IR } from "./builtin-stepwise-final-review-coding-workflow-ir.js";
import { BUILTIN_WORKFLOW_SETTINGS } from "./builtin-workflow-settings.js";
import { builtinPromptConfig } from "./builtin-workflow-prompts.js";
import { browserVerificationOptionalGroupNode } from "./builtin-browser-verification-group.js";
import { codeReviewOptionalGroupNode } from "./builtin-code-review-group.js";
import { planReviewOptionalGroupNode } from "./builtin-plan-review-group.js";
import type { WorkflowDefinition } from "./workflow-definition-types.js";
import type { WorkflowIr, WorkflowIrColumn, WorkflowIrNode } from "./workflow-ir-types.js";
import { parseWorkflowIr } from "./workflow-ir.js";
@@ -35,6 +38,39 @@ export function defaultEnabledBuiltinWorkflowIds(): string[] {
).map((workflow) => workflow.id);
}
function ceCodeReviewOptionalGroupNode(column: string): WorkflowIrNode {
return {
id: "code-review",
kind: "optional-group",
column,
config: {
/*
* FNXC:Workflows 2026-06-29-10:24:
* Compound Engineering uses the same optional Code Review toggle as other engineering built-ins, but the inner reviewer remains the CE skill. Keep the stable `code-review` group id so per-task toggles work uniformly while preserving the CE review implementation.
*/
name: "Code Review",
defaultOn: true,
template: {
nodes: [
{
id: "code-review-step",
kind: "gate",
config: {
name: "Code Review",
executor: "skill",
skillName: "compound-engineering:ce-code-review",
gateMode: "gate",
toolMode: "coding",
prompt: "Run /ce-code-review to perform a structured code review of the changes. Block merge on P0/P1 findings.",
},
},
],
edges: [],
},
},
};
}
export function isBuiltinWorkflowEnabled(id: string, enabledIds?: readonly string[]): boolean {
if (!isBuiltinWorkflowId(id)) return true;
if (!enabledIds) return true;
@@ -50,6 +86,11 @@ interface BuiltinSpec {
description: string;
/** Ordered node specs between start and end; seams use {seam}. */
nodes: Array<{ id: string; kind: WorkflowIr["nodes"][number]["kind"]; config?: Record<string, unknown> }>;
engineeringOptionalGroups?: {
planReviewDefaultOn?: boolean;
codeReviewDefaultOn?: boolean;
browserVerificationDefaultOn?: boolean;
};
}
function defaultColumnForLinearNode(node: WorkflowIrNode): string {
@@ -75,9 +116,12 @@ function canonicalBuiltinWorkflowColumns(): WorkflowIrColumn[] {
/** Build a linear IR (start → nodes… → end) with simple x-spaced layout. */
function linear(spec: BuiltinSpec): WorkflowDefinition {
const specNodes = spec.engineeringOptionalGroups
? withEngineeringOptionalGroups(spec.nodes, spec.engineeringOptionalGroups)
: spec.nodes;
const nodes: WorkflowIr["nodes"] = [
{ id: "start", kind: "start" },
...spec.nodes,
...specNodes,
{ id: "end", kind: "end" },
];
const edges: WorkflowIr["edges"] = [];
@@ -85,8 +129,8 @@ function linear(spec: BuiltinSpec): WorkflowDefinition {
edges.push({ from: nodes[i].id, to: nodes[i + 1].id, condition: "success" });
}
// Seam nodes also fail straight to end (mirrors the legacy pipeline).
for (const node of spec.nodes) {
if (typeof node.config?.seam === "string") {
for (const node of specNodes) {
if (typeof node.config?.seam === "string" || node.kind === "optional-group") {
edges.push({ from: node.id, to: "end", condition: "failure" });
}
}
@@ -129,6 +173,26 @@ function linear(spec: BuiltinSpec): WorkflowDefinition {
};
}
function withEngineeringOptionalGroups(
nodes: BuiltinSpec["nodes"],
options: NonNullable<BuiltinSpec["engineeringOptionalGroups"]>,
): BuiltinSpec["nodes"] {
const executeIndex = nodes.findIndex((node) => node.id === "execute");
if (executeIndex < 0) return nodes;
/*
* FNXC:WorkflowBuiltins 2026-06-29-10:17:
* Engineering built-ins must expose the same operator toggles: Plan Review before execution, then Browser Verification and Code Review after implementation. Quick Fix keeps all three default-off; other engineering built-ins keep plan/code review default-on and browser verification default-off.
*/
return [
...nodes.slice(0, executeIndex),
planReviewOptionalGroupNode("in-progress", { defaultOn: options.planReviewDefaultOn ?? true }),
nodes[executeIndex],
browserVerificationOptionalGroupNode("in-progress", { defaultOn: options.browserVerificationDefaultOn ?? false }),
codeReviewOptionalGroupNode("in-progress", { defaultOn: options.codeReviewDefaultOn ?? true }),
...nodes.slice(executeIndex + 1),
];
}
/**
* Read-only built-in workflow templates. Selectable like any workflow; they
* cannot be edited or deleted. In compile mode (flag off) only the custom
@@ -188,6 +252,11 @@ export const BUILTIN_WORKFLOWS: WorkflowDefinition[] = [
id: "builtin:quick-fix",
name: "Quick fix (built-in)",
description: "Implement and merge with no review step — for trivial, low-risk changes.",
engineeringOptionalGroups: {
planReviewDefaultOn: false,
codeReviewDefaultOn: false,
browserVerificationDefaultOn: false,
},
nodes: [
{ id: "execute", kind: "prompt", config: builtinPromptConfig("execute", "Execute") },
{ id: "merge", kind: "prompt", config: builtinPromptConfig("merge", "Merge boundary") },
@@ -197,6 +266,7 @@ export const BUILTIN_WORKFLOWS: WorkflowDefinition[] = [
id: "builtin:review-heavy",
name: "Review-heavy (built-in)",
description: "Adds an extra security pass before merge, on top of the standard review.",
engineeringOptionalGroups: {},
nodes: [
{ id: "execute", kind: "prompt", config: builtinPromptConfig("execute", "Execute") },
{ id: "review", kind: "prompt", config: builtinPromptConfig("review", "Review") },
@@ -287,6 +357,7 @@ export const BUILTIN_WORKFLOWS: WorkflowDefinition[] = [
},
},
},
planReviewOptionalGroupNode("in-progress"),
{
id: "execute",
kind: "prompt",
@@ -301,29 +372,8 @@ export const BUILTIN_WORKFLOWS: WorkflowDefinition[] = [
prompt: "Run /ce-work to execute the plan for this task, following existing patterns and maintaining quality throughout.",
},
},
{
id: "code-review",
kind: "gate",
config: {
/*
* FNXC:Workflows 2026-06-25-00:00:
* FN-7045 requires every built-in code-review step display name to be title-case "Code Review" so compound-engineering matches WORKFLOW_STEP_TEMPLATES and the optional Code Review group.
*/
name: "Code Review",
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).
toolMode: "coding",
prompt: "Run /ce-code-review to perform a structured code review of the changes. Block merge on P0/P1 findings.",
},
},
browserVerificationOptionalGroupNode("in-progress"),
ceCodeReviewOptionalGroupNode("in-progress"),
{
id: "commit-pr",
kind: "prompt",
@@ -418,6 +468,7 @@ export const BUILTIN_WORKFLOWS: WorkflowDefinition[] = [
id: "builtin:design",
name: "Design (built-in)",
description: "Implement, then run a design/UX review gate before the standard review and merge — for UI-heavy work.",
engineeringOptionalGroups: {},
nodes: [
{
id: "execute",