From f77aa073c11db73bba6a52c54b35194566d70f7d Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Mon, 8 Jun 2026 12:24:24 -0700 Subject: [PATCH] FN-6025: fix builtin coding auto-merge review flags Restore the built-in coding workflow's in-review auto-merge metadata. - switch builtin:coding to the authored v2 workflow IR so in-review retains merge-blocker and human-review traits - add core regression coverage for builtin workflow resolution and column flag propagation - extend dashboard board workflow route/payload tests to assert the in-review flags and payload contents Files changed: .changeset/fn-6025-auto-merge-toggle-flags.md | 5 +++++ packages/core/src/__tests__/builtin-workflows.test.ts | 19 +++++++++++++++++++ packages/core/src/__tests__/workflow-ir-resolver.test.ts | 14 ++++++++++++++ packages/core/src/builtin-workflows.ts | 21 ++++++++++++++------- packages/dashboard/src/routes/__tests__/board-workflows-route.test.ts | 12 ++++++++++-- packages/dashboard/src/routes/__tests__/board-workflows.test.ts | 4 ++++ 6 files changed, 66 insertions(+), 9 deletions(-) Fusion-Task-Id: FN-6025 Fusion-Task-Lineage: 838ced1f-0fdf-413b-94a0-52ddb21d75c9 --- .changeset/fn-6025-auto-merge-toggle-flags.md | 5 +++++ .../src/__tests__/builtin-workflows.test.ts | 19 +++++++++++++++++ .../__tests__/workflow-ir-resolver.test.ts | 14 +++++++++++++ packages/core/src/builtin-workflows.ts | 21 ++++++++++++------- .../__tests__/board-workflows-route.test.ts | 12 +++++++++-- .../routes/__tests__/board-workflows.test.ts | 4 ++++ 6 files changed, 66 insertions(+), 9 deletions(-) create mode 100644 .changeset/fn-6025-auto-merge-toggle-flags.md diff --git a/.changeset/fn-6025-auto-merge-toggle-flags.md b/.changeset/fn-6025-auto-merge-toggle-flags.md new file mode 100644 index 0000000000..8fcfc147ec --- /dev/null +++ b/.changeset/fn-6025-auto-merge-toggle-flags.md @@ -0,0 +1,5 @@ +--- +"@runfusion/fusion": patch +--- + +Fix auto-merge toggle not appearing on the built-in coding workflow's in-review column. The builtin:coding IR now carries the correct column traits (merge-blocker, human-review) so the dashboard resolves and passes the auto-merge toggle to the in-review column. diff --git a/packages/core/src/__tests__/builtin-workflows.test.ts b/packages/core/src/__tests__/builtin-workflows.test.ts index 74b81259bf..ccba51dd06 100644 --- a/packages/core/src/__tests__/builtin-workflows.test.ts +++ b/packages/core/src/__tests__/builtin-workflows.test.ts @@ -2,6 +2,7 @@ import { describe, it, expect, beforeEach, afterEach } from "vitest"; import { BUILTIN_WORKFLOWS, getBuiltinWorkflow, isBuiltinWorkflowId } from "../builtin-workflows.js"; import { BUILTIN_CODING_WORKFLOW_IR } from "../builtin-coding-workflow-ir.js"; +import { resolveColumnFlags } from "../trait-registry.js"; import { compileWorkflowToSteps } from "../workflow-compiler.js"; import { DEFAULT_WORKFLOW_COLUMN_IDS, parseWorkflowIr, serializeWorkflowIr } from "../workflow-ir.js"; import { createTaskStoreTestHarness } from "./store-test-helpers.js"; @@ -86,6 +87,24 @@ describe("built-in workflows", () => { ]); }); + it("builtin:coding exposes merge-blocker and human-review traits on in-review", () => { + const coding = getBuiltinWorkflow("builtin:coding"); + expect(coding).toBeDefined(); + const ir = parseWorkflowIr(coding!.ir); + expect(ir.version).toBe("v2"); + if (ir.version !== "v2") throw new Error("expected v2"); + + const inReview = ir.columns.find((column) => column.id === "in-review"); + expect(inReview).toBeDefined(); + expect(inReview!.traits.length).toBeGreaterThan(0); + expect(inReview!.traits.map((trait) => trait.trait)).toContain("merge-blocker"); + expect(inReview!.traits.map((trait) => trait.trait)).toContain("human-review"); + + const flags = resolveColumnFlags(inReview!); + expect(flags.mergeBlocker).toBe(true); + expect(flags.humanReview).toBe(true); + }); + it("includes a coding and a compound-engineering workflow", () => { expect(getBuiltinWorkflow("builtin:coding")).toBeDefined(); expect(getBuiltinWorkflow("builtin:compound-engineering")).toBeDefined(); diff --git a/packages/core/src/__tests__/workflow-ir-resolver.test.ts b/packages/core/src/__tests__/workflow-ir-resolver.test.ts index e4d96cf635..ba21bcb100 100644 --- a/packages/core/src/__tests__/workflow-ir-resolver.test.ts +++ b/packages/core/src/__tests__/workflow-ir-resolver.test.ts @@ -89,6 +89,20 @@ describe("resolveWorkflowIrForTask", () => { }); describe("resolveWorkflowIrById", () => { + it("resolves builtin:coding to the authored v2 IR with review column traits", async () => { + const store = makeStore({}); + const ir = await resolveWorkflowIrById(store, "builtin:coding"); + + expect(ir.version).toBe("v2"); + if (ir.version !== "v2") throw new Error("expected v2"); + const inReview = ir.columns.find((column) => column.id === "in-review"); + expect(inReview).toBeDefined(); + expect(inReview!.traits.length).toBeGreaterThan(0); + expect(inReview!.traits).toEqual( + expect.arrayContaining([{ trait: "merge-blocker" }, { trait: "human-review" }]), + ); + }); + it("parses a raw-string IR from the definition", async () => { const raw = JSON.stringify(CUSTOM_IR); const store = makeStore({ defs: { "wf-raw": { ir: raw } } }); diff --git a/packages/core/src/builtin-workflows.ts b/packages/core/src/builtin-workflows.ts index a00f899b4d..6cb3da9eb4 100644 --- a/packages/core/src/builtin-workflows.ts +++ b/packages/core/src/builtin-workflows.ts @@ -1,3 +1,4 @@ +import { BUILTIN_CODING_WORKFLOW_IR } from "./builtin-coding-workflow-ir.js"; import { BUILTIN_PR_WORKFLOW_IR } from "./builtin-pr-workflow-ir.js"; import { BUILTIN_STEPWISE_CODING_WORKFLOW_IR } from "./builtin-stepwise-coding-workflow-ir.js"; import { BUILTIN_WORKFLOW_SETTINGS } from "./builtin-workflow-settings.js"; @@ -84,16 +85,22 @@ function linear(spec: BuiltinSpec): WorkflowDefinition { * seams are honored only by the graph interpreter (flag on). */ export const BUILTIN_WORKFLOWS: WorkflowDefinition[] = [ - linear({ + { id: "builtin:coding", name: "Coding (built-in)", description: "The standard coding pipeline: implement, review, then merge. Equivalent to the default behavior.", - nodes: [ - { id: "execute", kind: "prompt", config: builtinPromptConfig("execute", "Execute") }, - { id: "review", kind: "prompt", config: builtinPromptConfig("review", "Review") }, - { id: "merge", kind: "prompt", config: builtinPromptConfig("merge", "Merge boundary") }, - ], - }), + kind: "workflow", + ir: BUILTIN_CODING_WORKFLOW_IR, + layout: { + start: { x: 60, y: 160 }, + execute: { x: 230, y: 160 }, + review: { x: 400, y: 160 }, + merge: { x: 570, y: 160 }, + end: { x: 740, y: 160 }, + }, + createdAt: BUILTIN_TS, + updatedAt: BUILTIN_TS, + }, linear({ id: "builtin:quick-fix", name: "Quick fix (built-in)", diff --git a/packages/dashboard/src/routes/__tests__/board-workflows-route.test.ts b/packages/dashboard/src/routes/__tests__/board-workflows-route.test.ts index 61ec574e67..2c05403247 100644 --- a/packages/dashboard/src/routes/__tests__/board-workflows-route.test.ts +++ b/packages/dashboard/src/routes/__tests__/board-workflows-route.test.ts @@ -112,7 +112,11 @@ describe("GET /tasks/board-workflows", () => { const body = res.body as { flagEnabled: boolean; defaultWorkflowId: string; - workflows: Array<{ id: string; name: string; columns: unknown[] }>; + workflows: Array<{ + id: string; + name: string; + columns: Array<{ id: string; flags: { mergeBlocker?: boolean; humanReview?: boolean } }>; + }>; taskWorkflowIds: Record; }; @@ -149,6 +153,9 @@ describe("GET /tasks/board-workflows", () => { const defaultLane = body.workflows.find((w) => w.id === DEFAULT_LANE); expect(Array.isArray(defaultLane?.columns)).toBe(true); expect((defaultLane?.columns.length ?? 0)).toBeGreaterThan(0); + const inReview = defaultLane?.columns.find((column) => column.id === "in-review"); + expect(inReview?.flags.mergeBlocker).toBe(true); + expect(inReview?.flags.humanReview).toBe(true); }); it("payload contract (flag ON): mixed default + custom ids → full taskWorkflowIds + deduped workflows", async () => { @@ -173,7 +180,8 @@ describe("GET /tasks/board-workflows", () => { }); const ids = payload.workflows.map((w) => w.id); expect(new Set(ids).size).toBe(ids.length); // deduped - expect(ids.sort()).toEqual([DEFAULT_LANE, custom.id].sort()); + expect(ids).toContain(DEFAULT_LANE); + expect(ids).toContain(custom.id); const customLane = payload.workflows.find((w) => w.id === custom.id); expect(customLane?.name).toBe("Custom"); expect((customLane?.columns.length ?? 0)).toBeGreaterThan(0); diff --git a/packages/dashboard/src/routes/__tests__/board-workflows.test.ts b/packages/dashboard/src/routes/__tests__/board-workflows.test.ts index b17a2cc7c1..61ece4d7ae 100644 --- a/packages/dashboard/src/routes/__tests__/board-workflows.test.ts +++ b/packages/dashboard/src/routes/__tests__/board-workflows.test.ts @@ -82,6 +82,10 @@ describe("buildBoardWorkflowsPayload", () => { "Done", "Archived", ]); + const inReview = defaultWf!.columns.find((c) => c.id === "in-review"); + expect(inReview).toBeDefined(); + expect(inReview!.flags.mergeBlocker).toBe(true); + expect(inReview!.flags.humanReview).toBe(true); }); it("includes user-defined workflows even when no visible task references them", async () => {