From 1c02e683b743d252347cde44834fd5d17ce5b98f Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Wed, 15 Jul 2026 16:24:23 -0700 Subject: [PATCH] FN-7970: deprecate unused builtin:brainstorming from new selection Hide the built-in Brainstorming workflow from new selection after occupancy preflight, while keeping it resolvable for existing tasks. - Add DEPRECATED_BUILTIN_WORKFLOW_IDS and isBuiltinWorkflowDeprecated helper - Exclude deprecated built-ins from defaults and selection listings - Hide deprecated built-ins from Settings workflow enablement toggles - Update docs/tests and add a minor changeset for the operator-facing change Files changed: .changeset/fn-7970-deprecate-brainstorming.md | 7 ++++ docs/workflow-steps.md | 2 +- .../core/src/__tests__/builtin-workflows.test.ts | 40 ++++++++++++---------- packages/core/src/builtin-workflows.ts | 17 ++++++++- packages/core/src/index.gate.ts | 2 ++ packages/core/src/index.ts | 2 ++ packages/core/src/task-store/remaining-ops-8.ts | 10 ++++-- packages/core/src/types.ts | 9 +++++ .../app/__tests__/settings-sections.test.tsx | 28 ++++++++++++++- .../settings/sections/GeneralSection.tsx | 9 +++-- 10 files changed, 101 insertions(+), 25 deletions(-) Fusion-Task-Id: FN-7970 Fusion-Task-Lineage: 47f9cd6e-d843-4c14-b197-447ff2072e3b Co-authored-by: Fusion (runfusion.ai) --- .changeset/fn-7970-deprecate-brainstorming.md | 7 ++++ docs/workflow-steps.md | 2 +- .../src/__tests__/builtin-workflows.test.ts | 40 ++++++++++--------- packages/core/src/builtin-workflows.ts | 17 +++++++- packages/core/src/index.gate.ts | 2 + packages/core/src/index.ts | 2 + .../core/src/task-store/remaining-ops-8.ts | 10 ++++- packages/core/src/types.ts | 9 +++++ .../app/__tests__/settings-sections.test.tsx | 28 ++++++++++++- .../settings/sections/GeneralSection.tsx | 9 ++++- 10 files changed, 101 insertions(+), 25 deletions(-) create mode 100644 .changeset/fn-7970-deprecate-brainstorming.md diff --git a/.changeset/fn-7970-deprecate-brainstorming.md b/.changeset/fn-7970-deprecate-brainstorming.md new file mode 100644 index 0000000000..edeea718f2 --- /dev/null +++ b/.changeset/fn-7970-deprecate-brainstorming.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": minor +--- + +summary: Deprecate the built-in Brainstorming workflow — it no longer appears for new task selection. +category: internal +dev: builtin:brainstorming is excluded from defaults and listWorkflowDefinitions through the deprecation registry/helper, but remains resolvable by id. Applied after a successful live-store query verified no active task selects it. diff --git a/docs/workflow-steps.md b/docs/workflow-steps.md index 93aba19ae5..3609319715 100644 --- a/docs/workflow-steps.md +++ b/docs/workflow-steps.md @@ -386,7 +386,7 @@ start → ask (ask-user: "Anything to refine?") the PR review loop) ``` -Each turn, the user is asked to refine; once they reply "looks good" (or whatever the condition matches), the exit-gate routes the task out of the brainstorm loop. This composition is also available as a discoverable built-in: `builtin:brainstorming` (FN-7584) registers exactly this shape — `ask-user` → a refine prompt step → `exit-gate`-on-approval — ahead of the unmodified default Coding plan/execute/review/merge spine, selectable directly from the workflow picker. Copy the shape into a custom workflow's IR via `fn_workflow_create`/`fn_workflow_update` when you need a different downstream pipeline than the standard coding one. +Each turn, the user is asked to refine; once they reply "looks good" (or whatever the condition matches), the exit-gate routes the task out of the brainstorm loop. The former `builtin:brainstorming` composition (FN-7584) is deprecated and hidden from new workflow selection, but remains resolvable for tasks that already use it. Its `ask-user` → refine prompt → `exit-gate`-on-approval shape can be copied into a custom workflow's IR via `fn_workflow_create`/`fn_workflow_update` when you need this behavior or a different downstream pipeline than the standard coding one. #### Workflow-defined custom task fields diff --git a/packages/core/src/__tests__/builtin-workflows.test.ts b/packages/core/src/__tests__/builtin-workflows.test.ts index 3d7aadfb93..8b96cd291d 100644 --- a/packages/core/src/__tests__/builtin-workflows.test.ts +++ b/packages/core/src/__tests__/builtin-workflows.test.ts @@ -7,6 +7,7 @@ import { getRequiredPluginIdForBuiltinWorkflow, isBuiltinWorkflowId, isBuiltinWorkflowPluginGated, + isBuiltinWorkflowDeprecated, } from "../builtin-workflows.js"; import { BUILTIN_CODING_WORKFLOW_IR } from "../builtin-coding-workflow-ir.js"; import { BUILTIN_STEPWISE_CODING_WORKFLOW_IR } from "../builtin-stepwise-coding-workflow-ir.js"; @@ -344,29 +345,16 @@ describe("built-in workflows", () => { ); }); - /* - * FNXC:WorkflowBrainstorming 2026-07-05-00:00: - * FN-7584 parity coverage for the registered builtin:brainstorming built-in - * (FN-7579's ask-user -> refine -> exit-gate-on-approval composition, - * discoverable from the workflow picker ahead of the normal coding spine). - */ - it("registers builtin:brainstorming as a default-enabled workflow ordered after the existing built-ins", () => { + it("keeps deprecated builtin:brainstorming resolvable while excluding it from defaults", () => { const brainstorming = getBuiltinWorkflow("builtin:brainstorming"); expect(brainstorming).toBeDefined(); expect(brainstorming!.kind).toBe("workflow"); expect(() => parseWorkflowIr(brainstorming!.ir)).not.toThrow(); - expect(defaultEnabledBuiltinWorkflowIds()).toContain("builtin:brainstorming"); + expect(isBuiltinWorkflowDeprecated("builtin:brainstorming")).toBe(true); + expect(defaultEnabledBuiltinWorkflowIds()).not.toContain("builtin:brainstorming"); expect(BUILTIN_WORKFLOWS.findIndex((workflow) => workflow.id === "builtin:brainstorming")).toBeGreaterThan( BUILTIN_WORKFLOWS.findIndex((workflow) => workflow.id === "builtin:lead-generation"), ); - // Ordering assertion the suite pins elsewhere (`.slice(0, 5)`) must stay untouched. - expect(defaultEnabledBuiltinWorkflowIds().slice(0, 5)).toEqual([ - "builtin:coding", - "builtin:coding-ideas", - "builtin:legacy-coding", - "builtin:quick-fix", - "builtin:review-heavy", - ]); }); it("orders builtin:brainstorming's ask-user/exit-gate loop ahead of the plan/execute spine", () => { @@ -739,12 +727,15 @@ describe("built-in workflows", () => { expect(BUILTIN_WORKFLOWS.find((workflow) => workflow.id === "builtin:coding")?.ir).toBe(BUILTIN_STEPWISE_FINAL_REVIEW_CODING_WORKFLOW_IR); expect(defaultEnabledBuiltinWorkflowIds()).toEqual( BUILTIN_WORKFLOWS.filter( - (workflow) => workflow.kind !== "fragment" && !isBuiltinWorkflowPluginGated(workflow.id), + (workflow) => workflow.kind !== "fragment" + && !isBuiltinWorkflowPluginGated(workflow.id) + && !isBuiltinWorkflowDeprecated(workflow.id), ).map((workflow) => workflow.id), ); expect(defaultEnabledBuiltinWorkflowIds()).toContain("builtin:design"); expect(defaultEnabledBuiltinWorkflowIds()).toContain("builtin:marketing"); expect(defaultEnabledBuiltinWorkflowIds()).not.toContain("builtin:compound-engineering"); + expect(defaultEnabledBuiltinWorkflowIds()).not.toContain("builtin:brainstorming"); expect(defaultEnabledBuiltinWorkflowIds()).not.toContain("builtin:pr-workflow"); expect(getBuiltinWorkflow("builtin:pr-workflow")!.kind).toBe("fragment"); expect(defaultEnabledBuiltinWorkflowIds().length).toBeGreaterThanOrEqual(5); @@ -758,10 +749,12 @@ describe("built-in workflows", () => { expect(defaultEnabledBuiltinWorkflowIds()).toContain("builtin:stepwise-coding"); }); - it("identifies plugin-gated built-in workflows", () => { + it("identifies plugin-gated and deprecated built-in workflows", () => { expect(isBuiltinWorkflowPluginGated("builtin:compound-engineering")).toBe(true); expect(isBuiltinWorkflowPluginGated("builtin:coding")).toBe(false); expect(isBuiltinWorkflowPluginGated("builtin:quick-fix")).toBe(false); + expect(isBuiltinWorkflowDeprecated("builtin:brainstorming")).toBe(true); + expect(isBuiltinWorkflowDeprecated("builtin:coding")).toBe(false); }); it("resolves required plugin ids for plugin-gated built-in workflows", () => { @@ -1064,6 +1057,17 @@ describe("built-in workflows", () => { expect(managementList.some((workflow) => workflow.id === "builtin:compound-engineering")).toBe(false); }); + it("hides deprecated brainstorming from selection listings while preserving management and direct resolution", async () => { + const selectionList = await store.listWorkflowDefinitions(); + expect(selectionList.some((workflow) => workflow.id === "builtin:brainstorming")).toBe(false); + + const managementList = await store.listWorkflowDefinitions({ includeDisabledBuiltins: true }); + expect(managementList.some((workflow) => workflow.id === "builtin:brainstorming")).toBe(true); + expect(await store.getWorkflowDefinition("builtin:brainstorming")).toMatchObject({ + id: "builtin:brainstorming", + }); + }); + it("hides the compound-engineering built-in when its plugin is not installed", async () => { const list = await store.listWorkflowDefinitions(); expect(list.some((workflow) => workflow.id === "builtin:compound-engineering")).toBe(false); diff --git a/packages/core/src/builtin-workflows.ts b/packages/core/src/builtin-workflows.ts index 1a0cb882df..4fe5ace238 100644 --- a/packages/core/src/builtin-workflows.ts +++ b/packages/core/src/builtin-workflows.ts @@ -18,6 +18,7 @@ import { codeReviewRemediationNode, planReplanNode, } from "./builtin-workflow-remediation-nodes.js"; +import { DEPRECATED_BUILTIN_WORKFLOW_IDS } from "./types.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"; @@ -33,17 +34,31 @@ const PLUGIN_GATED_BUILTIN_WORKFLOWS: ReadonlyMap = new Map([ ["builtin:compound-engineering", "fusion-plugin-compound-engineering"], ]); +/* + * FNXC:WorkflowBrainstorming 2026-07-15-15:49: + * FN-7970 deprecates builtin:brainstorming after its occupancy preflight: hide it + * from new selection while retaining its definition for existing task resolution. + * Keep this generic registry so later deprecated built-ins share the same policy. + */ +const DEPRECATED_BUILTIN_WORKFLOWS: ReadonlySet = DEPRECATED_BUILTIN_WORKFLOW_IDS; + export function isBuiltinWorkflowPluginGated(id: string): boolean { return PLUGIN_GATED_BUILTIN_WORKFLOWS.has(id); } +export function isBuiltinWorkflowDeprecated(id: string): boolean { + return DEPRECATED_BUILTIN_WORKFLOWS.has(id); +} + export function getRequiredPluginIdForBuiltinWorkflow(id: string): string | undefined { return PLUGIN_GATED_BUILTIN_WORKFLOWS.get(id); } export function defaultEnabledBuiltinWorkflowIds(): string[] { return BUILTIN_WORKFLOWS.filter( - (workflow) => workflow.kind !== "fragment" && !PLUGIN_GATED_BUILTIN_WORKFLOWS.has(workflow.id), + (workflow) => workflow.kind !== "fragment" + && !isBuiltinWorkflowPluginGated(workflow.id) + && !isBuiltinWorkflowDeprecated(workflow.id), ).map((workflow) => workflow.id); } diff --git a/packages/core/src/index.gate.ts b/packages/core/src/index.gate.ts index 87bea3be27..7454094b5a 100644 --- a/packages/core/src/index.gate.ts +++ b/packages/core/src/index.gate.ts @@ -473,6 +473,7 @@ export { stepToFragmentIr, layoutForIr, } from "./workflow-steps-to-ir.js"; +export { DEPRECATED_BUILTIN_WORKFLOW_IDS } from "./types.js"; export { BUILTIN_WORKFLOWS, BUILTIN_WORKFLOW_ID_PREFIX, @@ -480,6 +481,7 @@ export { getRequiredPluginIdForBuiltinWorkflow, isBuiltinWorkflowId, isBuiltinWorkflowPluginGated, + isBuiltinWorkflowDeprecated, } from "./builtin-workflows.js"; export { COMPLETION_SUMMARY_NODE_ID, diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 54337d474b..6f175f5bec 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -462,6 +462,7 @@ export { stepToFragmentIr, layoutForIr, } from "./workflow-steps-to-ir.js"; +export { DEPRECATED_BUILTIN_WORKFLOW_IDS } from "./types.js"; export { BUILTIN_WORKFLOWS, BUILTIN_WORKFLOW_ID_PREFIX, @@ -469,6 +470,7 @@ export { getRequiredPluginIdForBuiltinWorkflow, isBuiltinWorkflowId, isBuiltinWorkflowPluginGated, + isBuiltinWorkflowDeprecated, } from "./builtin-workflows.js"; export { COMPLETION_SUMMARY_NODE_ID, diff --git a/packages/core/src/task-store/remaining-ops-8.ts b/packages/core/src/task-store/remaining-ops-8.ts index e529e3a85a..53dfeb026b 100644 --- a/packages/core/src/task-store/remaining-ops-8.ts +++ b/packages/core/src/task-store/remaining-ops-8.ts @@ -11,7 +11,7 @@ import { TaskStore } from "../store.js"; import {resolveEntryColumnId} from "../workflow-reconciliation.js"; import { pruneAgentLogFiles as pruneAgentLogFileEntries, readAgentLogEntriesByTimeRange } from "../agent-log-file-store.js"; import { BUILTIN_CODING_WORKFLOW_IR } from "../builtin-coding-workflow-ir.js"; -import { BUILTIN_WORKFLOWS, getBuiltinWorkflow, getRequiredPluginIdForBuiltinWorkflow, isBuiltinWorkflowEnabled, isBuiltinWorkflowId, isBuiltinWorkflowPluginGated } from "../builtin-workflows.js"; +import { BUILTIN_WORKFLOWS, getBuiltinWorkflow, getRequiredPluginIdForBuiltinWorkflow, isBuiltinWorkflowDeprecated, isBuiltinWorkflowEnabled, isBuiltinWorkflowId, isBuiltinWorkflowPluginGated } from "../builtin-workflows.js"; import { CentralCore } from "../central-core.js"; import { fromJson } from "../db.js"; import { type DistributedTaskIdAllocator, createDistributedTaskIdAllocator } from "../distributed-task-id.js"; @@ -235,8 +235,14 @@ export async function listWorkflowDefinitionsImpl(store: TaskStore, const enabledVisible = options?.includeDisabledBuiltins ? all : all.filter((wf) => isBuiltinWorkflowEnabled(wf.id, enabledBuiltinWorkflowIds)); + // FNXC:WorkflowBrainstorming 2026-07-15-15:49: + // FN-7970 removes deprecated built-ins only from new-selection listings. + // Management listings retain them, and direct id resolution remains unconditional. + const selectionVisible = options?.includeDisabledBuiltins + ? enabledVisible + : enabledVisible.filter((wf) => !isBuiltinWorkflowDeprecated(wf.id)); const visible = await Promise.all( - enabledVisible.map(async (wf) => { + selectionVisible.map(async (wf) => { const requiredPluginId = getRequiredPluginIdForBuiltinWorkflow(wf.id); if (!requiredPluginId) return wf; return (await store.isPluginInstalled(requiredPluginId)) ? wf : undefined; diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 657b37364a..539090579d 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -32,6 +32,15 @@ export { export type { GitlabConfigSettingsSource, ResolvedGitlabConfig, ResolveGitlabConfigInput } from "./gitlab-config.js"; export { validateMcpServerDefinitionDetailed, validateMcpServerDefinitionsDetailed } from "./settings-validation.js"; +/* + * FNXC:WorkflowBrainstorming 2026-07-15-15:49: + * Keep deprecation IDs browser-safe because Settings renders the management list + * (including disabled built-ins) and must not offer deprecated entries again. + */ +export const DEPRECATED_BUILTIN_WORKFLOW_IDS: ReadonlySet = new Set([ + "builtin:brainstorming", +]); + /* FNXC:CodeOrganization 2026-07-15-00:00: diff --git a/packages/dashboard/app/__tests__/settings-sections.test.tsx b/packages/dashboard/app/__tests__/settings-sections.test.tsx index b9eb86edf4..b380987d14 100644 --- a/packages/dashboard/app/__tests__/settings-sections.test.tsx +++ b/packages/dashboard/app/__tests__/settings-sections.test.tsx @@ -24,7 +24,7 @@ import { PromptsSection } from "../components/settings/sections/PromptsSection"; import { SecretsSection } from "../components/settings/sections/SecretsSection"; import { WorktreesSection } from "../components/settings/sections/WorktreesSection"; import type { SettingsFormState } from "../components/settings/sections/context"; -import { fetchWorkflow, fetchWorkflowSettingValues, updateWorkflowSettingValues } from "../api"; +import { fetchWorkflow, fetchWorkflows, fetchWorkflowSettingValues, updateWorkflowSettingValues } from "../api"; import type { WorkflowSettingValuesPayload } from "../api"; vi.mock("../components/AgentPromptsManager", () => ({ @@ -75,9 +75,11 @@ vi.mock("../components/CustomModelDropdown", () => ({ expect.extend(jestDomMatchers); beforeEach(() => { + vi.mocked(fetchWorkflows).mockReset(); vi.mocked(fetchWorkflow).mockReset(); vi.mocked(fetchWorkflowSettingValues).mockReset(); vi.mocked(updateWorkflowSettingValues).mockReset(); + vi.mocked(fetchWorkflows).mockResolvedValue([]); vi.mocked(fetchWorkflow).mockResolvedValue({ id: "builtin:coding", name: "Coding", ir: {} } as never); vi.mocked(fetchWorkflowSettingValues).mockResolvedValue({ stored: {}, effective: {}, orphaned: [] }); vi.mocked(updateWorkflowSettingValues).mockResolvedValue({ stored: {}, effective: {}, orphaned: [] }); @@ -117,6 +119,30 @@ describe("AppearanceSection", () => { }); describe("GeneralSection", () => { + it("hides deprecated built-ins from the workflow enablement toggles", async () => { + vi.mocked(fetchWorkflows).mockResolvedValue([ + { id: "builtin:coding", name: "Coding", kind: "workflow", ir: {} }, + { id: "builtin:brainstorming", name: "Brainstorming", kind: "workflow", ir: {} }, + ] as never); + + render( + , + ); + + await waitFor(() => expect(screen.getByLabelText("Coding")).toBeInTheDocument()); + expect(screen.queryByLabelText("Brainstorming")).not.toBeInTheDocument(); + }); + it("emits the absolute file-browser path toggle via setForm", () => { function GeneralHost() { const [form, setForm] = useState({ allowAbsoluteFileBrowserPaths: false } as SettingsFormState); diff --git a/packages/dashboard/app/components/settings/sections/GeneralSection.tsx b/packages/dashboard/app/components/settings/sections/GeneralSection.tsx index ff83621642..8ba1b14c28 100644 --- a/packages/dashboard/app/components/settings/sections/GeneralSection.tsx +++ b/packages/dashboard/app/components/settings/sections/GeneralSection.tsx @@ -1,5 +1,5 @@ import { useEffect, useMemo, useState, type ReactNode } from "react"; -import { isLocale, SUPPORTED_LOCALES, type WorkflowDefinition } from "@fusion/core"; +import { DEPRECATED_BUILTIN_WORKFLOW_IDS, isLocale, SUPPORTED_LOCALES, type WorkflowDefinition } from "@fusion/core"; import { SettingsToggleRow } from "../SettingsToggleRow"; import { SettingsSelectRow } from "../SettingsSelectRow"; /* @@ -35,7 +35,12 @@ export function GeneralSection({ scopeBanner, form, setForm, projectId, addToast fetchWorkflows(projectId, { includeDisabledBuiltins: true }) .then((workflows) => { if (!cancelled) { - setBuiltinWorkflows(workflows.filter((workflow) => workflow.id.startsWith("builtin:") && workflow.kind !== "fragment")); + // FNXC:WorkflowBrainstorming 2026-07-15-15:49: FN-7970 keeps deprecated built-ins out of Settings toggles, which are a new-selection surface. + setBuiltinWorkflows(workflows.filter( + (workflow) => workflow.id.startsWith("builtin:") + && workflow.kind !== "fragment" + && !DEPRECATED_BUILTIN_WORKFLOW_IDS.has(workflow.id), + )); } }) .catch(() => {