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
This commit is contained in:
5
.changeset/fn-6025-auto-merge-toggle-flags.md
Normal file
5
.changeset/fn-6025-auto-merge-toggle-flags.md
Normal file
@@ -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.
|
||||||
@@ -2,6 +2,7 @@ import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|||||||
|
|
||||||
import { BUILTIN_WORKFLOWS, getBuiltinWorkflow, isBuiltinWorkflowId } from "../builtin-workflows.js";
|
import { BUILTIN_WORKFLOWS, getBuiltinWorkflow, isBuiltinWorkflowId } from "../builtin-workflows.js";
|
||||||
import { BUILTIN_CODING_WORKFLOW_IR } from "../builtin-coding-workflow-ir.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 { compileWorkflowToSteps } from "../workflow-compiler.js";
|
||||||
import { DEFAULT_WORKFLOW_COLUMN_IDS, parseWorkflowIr, serializeWorkflowIr } from "../workflow-ir.js";
|
import { DEFAULT_WORKFLOW_COLUMN_IDS, parseWorkflowIr, serializeWorkflowIr } from "../workflow-ir.js";
|
||||||
import { createTaskStoreTestHarness } from "./store-test-helpers.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", () => {
|
it("includes a coding and a compound-engineering workflow", () => {
|
||||||
expect(getBuiltinWorkflow("builtin:coding")).toBeDefined();
|
expect(getBuiltinWorkflow("builtin:coding")).toBeDefined();
|
||||||
expect(getBuiltinWorkflow("builtin:compound-engineering")).toBeDefined();
|
expect(getBuiltinWorkflow("builtin:compound-engineering")).toBeDefined();
|
||||||
|
|||||||
@@ -89,6 +89,20 @@ describe("resolveWorkflowIrForTask", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("resolveWorkflowIrById", () => {
|
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 () => {
|
it("parses a raw-string IR from the definition", async () => {
|
||||||
const raw = JSON.stringify(CUSTOM_IR);
|
const raw = JSON.stringify(CUSTOM_IR);
|
||||||
const store = makeStore({ defs: { "wf-raw": { ir: raw } } });
|
const store = makeStore({ defs: { "wf-raw": { ir: raw } } });
|
||||||
|
|||||||
@@ -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_PR_WORKFLOW_IR } from "./builtin-pr-workflow-ir.js";
|
||||||
import { BUILTIN_STEPWISE_CODING_WORKFLOW_IR } from "./builtin-stepwise-coding-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";
|
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).
|
* seams are honored only by the graph interpreter (flag on).
|
||||||
*/
|
*/
|
||||||
export const BUILTIN_WORKFLOWS: WorkflowDefinition[] = [
|
export const BUILTIN_WORKFLOWS: WorkflowDefinition[] = [
|
||||||
linear({
|
{
|
||||||
id: "builtin:coding",
|
id: "builtin:coding",
|
||||||
name: "Coding (built-in)",
|
name: "Coding (built-in)",
|
||||||
description: "The standard coding pipeline: implement, review, then merge. Equivalent to the default behavior.",
|
description: "The standard coding pipeline: implement, review, then merge. Equivalent to the default behavior.",
|
||||||
nodes: [
|
kind: "workflow",
|
||||||
{ id: "execute", kind: "prompt", config: builtinPromptConfig("execute", "Execute") },
|
ir: BUILTIN_CODING_WORKFLOW_IR,
|
||||||
{ id: "review", kind: "prompt", config: builtinPromptConfig("review", "Review") },
|
layout: {
|
||||||
{ id: "merge", kind: "prompt", config: builtinPromptConfig("merge", "Merge boundary") },
|
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({
|
linear({
|
||||||
id: "builtin:quick-fix",
|
id: "builtin:quick-fix",
|
||||||
name: "Quick fix (built-in)",
|
name: "Quick fix (built-in)",
|
||||||
|
|||||||
@@ -112,7 +112,11 @@ describe("GET /tasks/board-workflows", () => {
|
|||||||
const body = res.body as {
|
const body = res.body as {
|
||||||
flagEnabled: boolean;
|
flagEnabled: boolean;
|
||||||
defaultWorkflowId: string;
|
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<string, string>;
|
taskWorkflowIds: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -149,6 +153,9 @@ describe("GET /tasks/board-workflows", () => {
|
|||||||
const defaultLane = body.workflows.find((w) => w.id === DEFAULT_LANE);
|
const defaultLane = body.workflows.find((w) => w.id === DEFAULT_LANE);
|
||||||
expect(Array.isArray(defaultLane?.columns)).toBe(true);
|
expect(Array.isArray(defaultLane?.columns)).toBe(true);
|
||||||
expect((defaultLane?.columns.length ?? 0)).toBeGreaterThan(0);
|
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 () => {
|
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);
|
const ids = payload.workflows.map((w) => w.id);
|
||||||
expect(new Set(ids).size).toBe(ids.length); // deduped
|
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);
|
const customLane = payload.workflows.find((w) => w.id === custom.id);
|
||||||
expect(customLane?.name).toBe("Custom");
|
expect(customLane?.name).toBe("Custom");
|
||||||
expect((customLane?.columns.length ?? 0)).toBeGreaterThan(0);
|
expect((customLane?.columns.length ?? 0)).toBeGreaterThan(0);
|
||||||
|
|||||||
@@ -82,6 +82,10 @@ describe("buildBoardWorkflowsPayload", () => {
|
|||||||
"Done",
|
"Done",
|
||||||
"Archived",
|
"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 () => {
|
it("includes user-defined workflows even when no visible task references them", async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user