feat(workflows): make coding stepwise with final review
This commit is contained in:
7
.changeset/stepwise-default-coding.md
Normal file
7
.changeset/stepwise-default-coding.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Make Coding use stepwise execution with final review and keep Legacy coding available.
|
||||
category: feature
|
||||
dev: `builtin:coding` now uses the stepwise final-review graph; the old monolithic graph is `builtin:legacy-coding`.
|
||||
@@ -130,7 +130,7 @@ You can also use expanded board controls (Refine, Deps, Attachments, model overr
|
||||
|
||||
Most tasks can use the default **Coding** workflow. When the workflow selector is visible on a task or board creation surface, choose a different workflow if the work needs a shorter path, extra review, stepwise execution, Compound Engineering skills, or a custom policy your project authored.
|
||||
|
||||
Built-ins include task-selectable Coding, Quick fix, Review-heavy, plugin-gated Compound engineering, Stepwise coding, and Design workflows, plus PR lifecycle fragments for workflow authors. For the full catalog and runtime behavior, see [Workflow Steps](./workflow-steps.md#workflow-overview). To inspect built-ins or author custom workflows, open the dashboard [Workflow Editor](./workflow-editor.md).
|
||||
Built-ins include task-selectable Coding, Legacy coding, Quick fix, Review-heavy, plugin-gated Compound engineering, Coding (per-step review), and Design workflows, plus PR lifecycle fragments for workflow authors. For the full catalog and runtime behavior, see [Workflow Steps](./workflow-steps.md#workflow-overview). To inspect built-ins or author custom workflows, open the dashboard [Workflow Editor](./workflow-editor.md).
|
||||
|
||||
## Understand the Task Lifecycle
|
||||
|
||||
|
||||
@@ -0,0 +1,328 @@
|
||||
---
|
||||
title: "feat: Add final-review stepwise workflow and fix workflow selection"
|
||||
date: 2026-06-29
|
||||
type: feature
|
||||
depth: standard
|
||||
artifact_contract: ce-unified-plan/v1
|
||||
artifact_readiness: implementation-ready
|
||||
product_contract_source: ce-plan-bootstrap
|
||||
execution: code
|
||||
---
|
||||
|
||||
# feat: Add final-review stepwise workflow and fix workflow selection
|
||||
|
||||
## Goal Capsule
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Objective | Make default Coding use stepwise execution with one final review, keep the original monolithic graph as Legacy coding, rename old Stepwise coding to Coding (per-step review), and fix workflow selection so new tasks actually attach to selected built-ins such as Coding (per-step review) and Compound engineering. |
|
||||
| Authority | User requests in this session: "create a built in stepwise coding version that doesn't review each step but just does a review of everything at the end"; "creating a new task in stepwise coding don't attach it to the workflow. it goes to the default coding built in workflow"; "and the compound engineering workflow doesn't work." |
|
||||
| Execution profile | Standard code change across `@fusion/core`, dashboard task creation surfaces/routes, docs, and focused tests. |
|
||||
| Stop conditions | Do not change existing `builtin:stepwise-coding` per-step review semantics; do not mask workflow selection bugs by only changing display labels; do not alter runtime primitives unless validation proves a missing generic capability. |
|
||||
| Tail ownership | Implementation should add focused core tests and run file-scoped verification, not the full suite. |
|
||||
|
||||
---
|
||||
|
||||
## Product Contract
|
||||
|
||||
### Summary
|
||||
|
||||
Fusion's default Coding workflow should use graph-owned step execution without the overhead of per-step AI review. The workflow should preserve the stepwise execution model (`PROMPT.md` parsing plus one-step-at-a-time execution) and then rely on the existing whole-task review path after all implementation steps complete. The original monolithic coding graph should remain available as Legacy coding, and the old per-step-review Stepwise workflow should display as Coding (per-step review).
|
||||
|
||||
Workflow selection must also be reliable. When an operator creates a task from a workflow lane or picker, the selected workflow must persist onto the task's `task_workflow_selection` row and govern execution. The reported failures are that creating a new task in Stepwise coding falls back to `builtin:coding`, and the Compound engineering workflow does not work. Those are selection/materialization bugs, not just labeling issues.
|
||||
|
||||
### Problem Frame
|
||||
|
||||
`builtin:stepwise-coding` currently demonstrates step inversion by parsing `PROMPT.md`, iterating through `Task.steps[]`, running each step, and routing each step through `step-review` with approve/revise/rethink outcomes. That is useful when every step needs independent review, but it is heavier than needed for workflows that only want a final whole-task review.
|
||||
|
||||
The default `builtin:coding` already performs a whole-task execution seam followed by optional pre-merge gates and final review, but it does not expose per-step execution as authored workflow graph structure. The requested variant fills the middle: graph-owned step execution, no per-step review, final review after the full implementation.
|
||||
|
||||
During initial investigation, the store-level explicit/default workflow paths already have tests for default `builtin:stepwise-coding` selection seeding. That makes the likely failure surface the dashboard create-task path: the UI may display a selected lane while submitting `workflowId` as `undefined`, or it may submit optional-step state in a way that suppresses `input.workflowId` in `TaskStore.createTask`. Compound engineering adds another dimension because it is plugin-gated (`fusion-plugin-compound-engineering`) and can fail either at visibility/selection time or at runtime if the required bundled plugin/skills are unavailable.
|
||||
|
||||
### Requirements
|
||||
|
||||
- R1. `builtin:coding` uses the Stepwise-derived final-review graph and remains the default coding workflow/fallback.
|
||||
- R2. The new workflow preserves stepwise planning and execution: planning produces `PROMPT.md`, a `parse-steps` node parses it, and a `foreach(source:"task-steps")` region runs one `step-execute` node per planned step.
|
||||
- R3. The new workflow must not include a `step-review` node in the foreach template and must not include per-step revise/rethink routing.
|
||||
- R4. After all planned steps complete, the workflow runs the same pre-merge optional groups as the coding built-ins: `browser-verification` default off and `code-review` default on.
|
||||
- R5. After optional pre-merge groups, the workflow runs the existing final `review` seam and the standard merge-gate / branch-group / merge-attempt region.
|
||||
- R6. The existing `builtin:stepwise-coding` graph remains unchanged for users who rely on per-step review and rework, but its user-facing name becomes Coding (per-step review).
|
||||
- R7. The original monolithic coding graph remains selectable as `builtin:legacy-coding` with user-facing name Legacy coding.
|
||||
- R8. Creating a task while Coding (per-step review) is selected must persist `workflowId: "builtin:stepwise-coding"` and not silently resolve the task to `builtin:coding`.
|
||||
- R9. Creating a task while Compound engineering is selected must either persist `workflowId: "builtin:compound-engineering"` and execute that workflow, or clearly block selection/create with a plugin-gating explanation when the required plugin is unavailable. It must not silently fall back to `builtin:coding`.
|
||||
- R10. Task creation surfaces must preserve the distinction between `workflowId: undefined` (inherit project default), `workflowId: null` (explicit no workflow), and `workflowId: string` (explicit selected workflow).
|
||||
- R11. Explicit enabled optional steps must not accidentally suppress an explicit workflow selection unless the caller intentionally opts into trusted low-level behavior. User-facing create flows must be able to submit both the selected workflow and enabled optional-group IDs.
|
||||
- R12. Board workflow lane task creation, global New Task modal creation, list view creation, quick-entry creation, planning/subtask/mission task creation, and agent/tool-created tasks must be enumerated and tested according to the workflow selection contract.
|
||||
|
||||
### Scope Boundaries
|
||||
|
||||
- In scope: a Stepwise-derived final-review IR module, built-in registry/export updates, docs catalog updates, and tests that prove default Coding has no per-step review node while final review remains.
|
||||
- In scope: diagnose and fix create-time workflow selection loss for Stepwise coding and Compound engineering across dashboard/API/store boundaries.
|
||||
- In scope: add regression coverage proving selected workflow IDs are persisted, returned, and used to resolve the task workflow IR.
|
||||
- Out of scope: runtime changes to `WorkflowGraphExecutor`, per-step parallelization, new reviewer verdict semantics, dashboard redesign, or changes to `builtin:coding` / `builtin:stepwise-coding` behavior.
|
||||
- Out of scope: removing the default-on pre-merge `code-review` optional group. The request removes per-step review only; the whole-task pre-merge code review remains the "review of everything at the end" gate alongside the final `review` seam.
|
||||
- Out of scope: redesigning plugin installation. This plan may add a clearer gating/error path for Compound engineering, but it should not rebuild the plugin manager.
|
||||
|
||||
### Acceptance Examples
|
||||
|
||||
- AE1. Given a task selects `builtin:coding`, when workflow selection resolves, then the built-in registry returns a v2 IR with `parse-steps`, `foreach`, `browser-verification`, `code-review`, `review`, and merge nodes.
|
||||
- AE2. Given the `builtin:coding` IR, when its foreach template is inspected, then it contains `step-execute` and a pass-through exit node, but no `step-review`.
|
||||
- AE3. Given optional-step defaults are resolved for `builtin:coding`, then `code-review` is seeded by default and `browser-verification` is not.
|
||||
- AE4. Given docs list built-in workflows, then the new workflow is documented separately from `builtin:stepwise-coding`.
|
||||
- AE5. Given the board/list workflow selector is set to Stepwise coding, when a new task is created from that workflow context, then `store.getTaskWorkflowSelection(task.id)?.workflowId` is `builtin:stepwise-coding`.
|
||||
- AE6. Given the board/list workflow selector is set to Compound engineering and the required plugin is available/enabled, when a new task is created from that workflow context, then `store.getTaskWorkflowSelection(task.id)?.workflowId` is `builtin:compound-engineering`.
|
||||
- AE7. Given Compound engineering is unavailable because `fusion-plugin-compound-engineering` is not installed/enabled, when a user tries to create/select a Compound engineering task, then the UI/API reports the plugin requirement instead of creating a default coding task.
|
||||
- AE8. Given a user toggles optional steps while selecting a non-default workflow, when the task is created, then the selected workflow persists and the enabled optional-group IDs reflect the user's toggles.
|
||||
|
||||
### Symptom Verification
|
||||
|
||||
- **Original symptom 1:** Creating a new task from/with Stepwise coding selected attaches the task to default `builtin:coding` instead of `builtin:stepwise-coding`.
|
||||
- **Exact reproduction 1:** In a project with built-in workflows enabled, select Stepwise coding in a task creation surface, create a task, then inspect `GET /api/tasks/:id/workflow` or `TaskStore.getTaskWorkflowSelection(task.id)`.
|
||||
- **Assertion it is gone 1:** The created task's workflow selection row has `workflowId: "builtin:stepwise-coding"` and `enabledWorkflowSteps` is seeded according to that workflow (`["code-review"]` by default).
|
||||
- **Original symptom 2:** Compound engineering workflow "doesn't work".
|
||||
- **Exact reproduction 2:** Select/create a task with `workflowId: "builtin:compound-engineering"` through the same surfaces and inspect both selection persistence and the first workflow resolution/execution failure.
|
||||
- **Assertion it is gone 2:** With the CE plugin available/enabled, the task persists `workflowId: "builtin:compound-engineering"` and resolves to the CE IR; without the plugin, creation/selection fails visibly with the required plugin ID rather than silently falling back.
|
||||
|
||||
### Surface Enumeration
|
||||
|
||||
- **Create surfaces:** New Task modal (`packages/dashboard/app/components/NewTaskModal.tsx`), TaskForm workflow picker (`packages/dashboard/app/components/TaskForm.tsx`), board/list workflow lane quick create (`packages/dashboard/app/components/Column.tsx`, `packages/dashboard/app/components/QuickEntryBox.tsx`, `packages/dashboard/app/components/ListView.tsx`), mission triage (`packages/dashboard/app/components/MissionManager.tsx` and `packages/dashboard/src/mission-routes.ts`), planning/subtask routes (`packages/dashboard/src/routes/register-planning-subtask-routes.ts`), and agent/API creation (`packages/dashboard/src/routes/register-task-workflow-routes.ts`, `packages/engine/src/agent-tools.ts`).
|
||||
- **Workflow IDs:** `builtin:coding`, `builtin:stepwise-coding`, new final-review stepwise workflow, `builtin:compound-engineering`, custom workflow IDs, `null` no-workflow, and `undefined` inherit-default.
|
||||
- **Plugin states:** Compound engineering plugin installed/enabled, installed/disabled, unavailable, and bundled path resolution failure.
|
||||
- **Optional-step states:** `enabledWorkflowSteps` omitted, empty array, default-on only, custom toggled values, and explicit values combined with non-default workflow ID.
|
||||
- **Board states:** task starts in default `triage`/workflow intake column, workflow-specific custom columns, and workflow lane selected independently from project default.
|
||||
- **Breakpoints/surfaces:** desktop and mobile New Task / inline-create affordances, since workflow selection must not be display-only on one viewport.
|
||||
|
||||
---
|
||||
|
||||
## Planning Contract
|
||||
|
||||
### Key Technical Decisions
|
||||
|
||||
- KTD-1. Make `builtin:coding` the Stepwise-derived final-review workflow and preserve the old graphs under explicit names.
|
||||
The default Coding ID is the resolver fallback, so mapping it to the Stepwise-derived final-review graph makes new ordinary coding tasks use the requested workflow. The original monolithic graph remains available as `builtin:legacy-coding`, while `builtin:stepwise-coding` keeps its graph behavior and displays as Coding (per-step review).
|
||||
|
||||
- KTD-2. Model step completion as `step-execute -> step-done` inside the foreach template.
|
||||
The existing stepwise workflow uses `step-review` as the authority that marks a step done. Without per-step review, the foreach template should let `step-execute` complete the step through the existing step-execution primitive. The template still needs a single exit, so use a config-less `gate` node as the pass-through sink, mirroring the existing `step-done` pattern.
|
||||
|
||||
- KTD-3. Keep whole-task review surfaces unchanged.
|
||||
The new workflow should keep `browser-verification`, `code-review`, final `review`, and the merge region after the foreach. This satisfies the requested "review of everything at the end" without introducing a new review mechanism.
|
||||
|
||||
- KTD-4. Register as a normal selectable built-in.
|
||||
Add the new workflow to `BUILTIN_WORKFLOWS` with `kind: "workflow"` and let `defaultEnabledBuiltinWorkflowIds()` include it by default, matching non-plugin-gated selectable built-ins.
|
||||
|
||||
- KTD-5. Focus verification on IR shape and registry behavior.
|
||||
This is primarily a built-in graph definition change. Targeted tests should validate parse/round-trip, registry presence, non-compilable built-in classification, optional-group defaults, docs-adjacent catalog expectations, and the exact absence of `step-review`.
|
||||
|
||||
- KTD-6. Fix workflow selection at the boundary where intent is lost.
|
||||
Store-level explicit workflow creation already records `task_workflow_selection` for built-ins, including Stepwise coding, when `workflowId` reaches `TaskStore.createTask` and `enabledWorkflowSteps` is omitted. The implementation must prove whether the lost value happens in the UI submit payload, API normalization, duplicate-reconcile response, or store precedence rule. The fix should be at that boundary, not by changing resolver fallback behavior.
|
||||
|
||||
- KTD-7. User-facing create flows must support workflow ID plus optional-step toggles together.
|
||||
Current store logic intentionally treats explicit `enabledWorkflowSteps` as a trusted override that can suppress `input.workflowId`. That is dangerous for UI create flows because the workflow picker and optional-step toggles are independent controls. Either the UI/API must omit `enabledWorkflowSteps` unless the user explicitly changed them, or the store/API must preserve `workflowId` while applying explicit optional IDs for that selected workflow. The chosen fix must keep low-level backward compatibility explicit and tested.
|
||||
|
||||
- KTD-8. Compound engineering must fail closed on missing plugin requirements.
|
||||
`builtin:compound-engineering` is plugin-gated by `fusion-plugin-compound-engineering`. A missing plugin should prevent selection/execution with a visible requirement, not degrade into default coding. Tests should cover both the persistence path and plugin-gated availability semantics.
|
||||
|
||||
### High-Level Technical Design
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Start[start] --> Plan[plan prompt]
|
||||
Plan --> Parse[parse-steps: PROMPT.md]
|
||||
Parse --> Steps[foreach task-steps]
|
||||
Steps --> StepExecute[step-execute]
|
||||
StepExecute --> StepDone[step-done gate]
|
||||
Steps --> Browser[browser-verification optional-group]
|
||||
Browser --> CodeReview[code-review optional-group]
|
||||
CodeReview --> FinalReview[final review seam]
|
||||
FinalReview --> MergeGate[merge-gate and merge region]
|
||||
MergeGate --> End[end]
|
||||
```
|
||||
|
||||
### Existing Patterns To Follow
|
||||
|
||||
- `packages/core/src/builtin-stepwise-coding-workflow-ir.ts` for columns, artifacts, parse/foreach structure, optional groups, and merge region.
|
||||
- `packages/core/src/builtin-coding-workflow-ir.ts` for the standard post-execute optional-group and review/merge suffix.
|
||||
- `packages/core/src/builtin-workflows.ts` for built-in registry metadata, layouts, and stable timestamp conventions.
|
||||
- `packages/core/src/__tests__/builtin-workflows.test.ts` for registry and selectable built-in assertions.
|
||||
- `packages/core/src/__tests__/workflow-optional-steps.test.ts` and `packages/core/src/__tests__/builtin-code-review-group.test.ts` for optional-group default behavior.
|
||||
- `docs/workflow-steps.md` and `docs/workflow-editor.md` for built-in catalog descriptions.
|
||||
|
||||
### Assumptions
|
||||
|
||||
- The existing `step-execute` primitive marks the active step done on success when no `step-review` node is present. This is documented in `packages/engine/src/step-runner.ts` and should be confirmed with a focused graph test if implementation exposes uncertainty.
|
||||
- The Stepwise-derived final-review graph is implemented as a reusable IR module but registered under `builtin:coding`; the original monolithic graph is registered as `builtin:legacy-coding`.
|
||||
- Pre-merge `code-review` remains the default-on final whole-task code review gate. The final `review` seam remains the lifecycle review before merge.
|
||||
|
||||
### Sequencing
|
||||
|
||||
1. Diagnose workflow selection loss with a failing test that reproduces Stepwise task creation through the affected UI/API path.
|
||||
2. Fix the selection boundary and add Compound engineering plugin-gating regression coverage.
|
||||
3. Add and export the new built-in IR.
|
||||
4. Register it in `BUILTIN_WORKFLOWS` with layout and description.
|
||||
5. Extend tests for registry, optional defaults, and IR shape.
|
||||
6. Add the new workflow to the now-fixed create/selection matrix.
|
||||
7. Update docs catalog and built-in workflow descriptions.
|
||||
8. Add the changeset and run targeted verification.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Units
|
||||
|
||||
### U1. Add the stepwise-final-review default Coding IR
|
||||
|
||||
- **Goal:** Create a Stepwise-derived workflow IR for `builtin:coding` that mirrors `builtin:stepwise-coding` except the foreach template omits `step-review` and routes `step-execute` success directly to a pass-through `step-done` exit.
|
||||
- **Requirements:** R1, R2, R3, R4, R5, R6
|
||||
- **Files:**
|
||||
- Create `packages/core/src/builtin-stepwise-final-review-coding-workflow-ir.ts`
|
||||
- Modify `packages/core/src/index.ts`
|
||||
- **Approach:** Copy the stable lifecycle skeleton from `packages/core/src/builtin-stepwise-coding-workflow-ir.ts`: same columns, `PROMPT.md` artifact declaration, planning/parse/foreach, optional groups, final review, merge region, and settings. Inside the foreach template, include only `step-execute` and `step-done` with a success edge. Remove per-step `step-review`, `outcome:revise`, `outcome:rethink`, and rework-hold routing unless validation requires a generic failure path.
|
||||
- **FNXC comment requirement:** Add or update a concise FNXC comment in the new IR file explaining that this built-in exists because operators need graph-owned step execution with one whole-task review at the end rather than per-step review.
|
||||
- **Test Scenarios:**
|
||||
- The IR parses and round-trips.
|
||||
- The top-level graph contains `parse-steps`, `foreach`, `browser-verification`, `code-review`, `review`, and merge nodes.
|
||||
- The foreach template contains `step-execute` and `step-done`.
|
||||
- The foreach template contains no `step-review`.
|
||||
- No template edge carries `kind: "rework"`.
|
||||
- **Verification:** Add assertions in `packages/core/src/__tests__/builtin-coding-workflow-ir.test.ts` or a new focused `packages/core/src/__tests__/builtin-stepwise-final-review-coding-workflow-ir.test.ts`.
|
||||
|
||||
### U2. Register and expose the new built-in workflow
|
||||
|
||||
- **Goal:** Make the workflow discoverable and selectable through the built-in workflow registry and package exports.
|
||||
- **Requirements:** R1, R6, R7
|
||||
- **Files:**
|
||||
- Modify `packages/core/src/builtin-workflows.ts`
|
||||
- Modify `packages/core/src/index.ts`
|
||||
- Possibly modify `packages/core/src/types.ts` if the enabled built-in IDs type or docs list is closed over explicit IDs.
|
||||
- **Approach:** Import the new IR, add a `BUILTIN_WORKFLOWS` entry near `builtin:stepwise-coding`, and provide a layout that includes `plan`, `parse`, `steps`, `browser-verification`, `code-review`, `review`, and merge/end nodes. Add the workflow ID to any tests or type-level inventories that enumerate selectable built-ins.
|
||||
- **Test Scenarios:**
|
||||
- `getBuiltinWorkflow("builtin:stepwise-final-review-coding")` returns the new workflow.
|
||||
- `defaultEnabledBuiltinWorkflowIds()` includes the new ID because it is selectable and not plugin-gated.
|
||||
- `NON_COMPILABLE_BUILTIN_IDS` includes the new ID in compiler tests because the graph uses interpreter-only node kinds.
|
||||
- Built-in registry tests distinguish existing `builtin:stepwise-coding` as per-step-review and the new workflow as final-review-only.
|
||||
- **Verification:** Extend `packages/core/src/__tests__/builtin-workflows.test.ts`.
|
||||
|
||||
### U3. Preserve optional-gate behavior for the new workflow
|
||||
|
||||
- **Goal:** Ensure the new workflow has the same end-of-task optional gates as existing coding built-ins.
|
||||
- **Requirements:** R4, R5, R7, AE3
|
||||
- **Files:**
|
||||
- Modify `packages/core/src/__tests__/workflow-optional-steps.test.ts`
|
||||
- Modify `packages/core/src/__tests__/builtin-code-review-group.test.ts`
|
||||
- Modify `packages/core/src/__tests__/builtin-coding-workflow-ir.test.ts` if it asserts "both coding built-ins"
|
||||
- **Approach:** Expand test matrices that currently cover `BUILTIN_CODING_WORKFLOW_IR` and `BUILTIN_STEPWISE_CODING_WORKFLOW_IR` to include the new IR where the assertion is about shared coding optional gates. Keep per-step-review-specific assertions targeted only at `BUILTIN_STEPWISE_CODING_WORKFLOW_IR`.
|
||||
- **Test Scenarios:**
|
||||
- `resolveWorkflowOptionalSteps(newIr)` returns browser verification default off and code review default on.
|
||||
- `resolveDefaultOnOptionalGroupIds(newIr)` returns `["code-review"]`.
|
||||
- The new workflow routes `browser-verification -> code-review -> review`.
|
||||
- Code review failure routes to `end` as in the other coding built-ins.
|
||||
- **Verification:** Run focused core tests listed in the Verification Contract.
|
||||
|
||||
### U4. Update operator-facing docs
|
||||
|
||||
- **Goal:** Document the new built-in so users understand when to choose it instead of `builtin:coding` or `builtin:stepwise-coding`.
|
||||
- **Requirements:** R7, AE4
|
||||
- **Files:**
|
||||
- Modify `docs/workflow-steps.md`
|
||||
- Modify `docs/workflow-editor.md`
|
||||
- Modify `docs/getting-started.md`
|
||||
- **Approach:** Add a catalog row and short runtime note. Phrase the difference plainly: `Stepwise coding` reviews each step; the new workflow executes steps one by one and reviews the full result at the end.
|
||||
- **Test Scenarios:**
|
||||
- Existing docs inventory tests, if any, still pass.
|
||||
- Docs mention the new workflow ID exactly once in the built-in catalog and use consistent naming elsewhere.
|
||||
- **Verification:** Run any focused docs/lazy inventory test only if affected by the docs change; otherwise rely on core tests plus markdown review.
|
||||
|
||||
### U5. Add a changeset
|
||||
|
||||
- **Goal:** Record the new published CLI/operator-visible built-in workflow for `@runfusion/fusion`.
|
||||
- **Requirements:** R1, R7
|
||||
- **Files:**
|
||||
- Create `.changeset/<descriptive-name>.md`
|
||||
- **Approach:** This affects published `@runfusion/fusion`, so add a minor changeset with the required labeled body format.
|
||||
- **Test Scenarios:**
|
||||
- Changeset body passes `pnpm check:changesets` format expectations.
|
||||
- **Verification:** Include the changeset in review; run `pnpm check:changesets` if available and fast enough for the focused pass.
|
||||
|
||||
### U6. Reproduce and fix Stepwise coding create-task selection loss
|
||||
|
||||
- **Goal:** Ensure a task created while Stepwise coding is selected persists `builtin:stepwise-coding` instead of falling back to `builtin:coding`.
|
||||
- **Requirements:** R8, R10, R11, R12, AE5, AE8
|
||||
- **Files:**
|
||||
- Modify `packages/dashboard/app/components/TaskForm.tsx`
|
||||
- Modify `packages/dashboard/app/components/NewTaskModal.tsx`
|
||||
- Modify `packages/dashboard/app/components/Board.tsx`
|
||||
- Modify `packages/dashboard/app/components/Column.tsx`
|
||||
- Modify `packages/dashboard/app/components/ListView.tsx`
|
||||
- Modify `packages/dashboard/app/components/QuickEntryBox.tsx`
|
||||
- Modify `packages/dashboard/src/routes/register-task-workflow-routes.ts` if payload normalization is the loss boundary
|
||||
- Modify `packages/core/src/store.ts` only if the root cause is the `workflowId` + `enabledWorkflowSteps` precedence rule
|
||||
- Add/update tests in `packages/dashboard/app/components/__tests__/TaskForm.test.tsx`, `packages/dashboard/app/components/__tests__/NewTaskModal.test.tsx`, `packages/dashboard/app/components/__tests__/ListView.test.tsx`, `packages/dashboard/app/components/__tests__/QuickEntryBox.test.tsx`, `packages/dashboard/app/components/__tests__/board-quickcreate-workflow-lane-visibility.test.tsx`, `packages/dashboard/app/__tests__/api-tasks.test.ts`, `packages/dashboard/src/routes/__tests__/task-create-workflow-route.test.ts`, and/or `packages/core/src/__tests__/builtin-workflows.test.ts` depending on the confirmed loss boundary
|
||||
- **Approach:** Start with a failing test that simulates selecting `builtin:stepwise-coding` in the same create surface the user used. Assert the request payload includes `workflowId: "builtin:stepwise-coding"` and the created task selection resolves to that workflow. Trace whether `selectedWorkflowId` is `undefined` despite the visible workflow label, whether `enabledWorkflowSteps` is always sent and suppresses `workflowId`, or whether the API response drops the workflow metadata and causes a client-side reclassification.
|
||||
- **FNXC comment requirement:** Add/update FNXC comments at the fixed boundary explaining that visible workflow lane selection is user intent and must persist as `workflowId`, while optional-group toggles are a separate control.
|
||||
- **Test Scenarios:**
|
||||
- New Task modal selecting Stepwise coding submits `workflowId: "builtin:stepwise-coding"`.
|
||||
- Board/list workflow lane create submits the lane workflow ID, not the project default.
|
||||
- Optional-step toggles do not erase the selected workflow.
|
||||
- `workflowId: undefined`, `null`, and string each preserve their distinct meanings.
|
||||
- **Verification:** Run focused dashboard/core tests for the changed create surface and store selection behavior.
|
||||
|
||||
### U7. Reproduce and fix Compound engineering workflow create/selection failure
|
||||
|
||||
- **Goal:** Make Compound engineering workflow selection either work end-to-end when the required plugin is available or fail visibly when unavailable.
|
||||
- **Requirements:** R9, R10, R12, AE6, AE7
|
||||
- **Files:**
|
||||
- Modify `packages/core/src/builtin-workflows.ts` only if plugin-gated metadata is incomplete
|
||||
- Modify `packages/dashboard/src/routes/register-workflow-routes.ts` if task workflow selection should reject unavailable plugin-gated built-ins
|
||||
- Modify `packages/dashboard/src/routes/register-task-workflow-routes.ts` if task creation accepts unavailable plugin-gated workflows silently
|
||||
- Modify dashboard picker components if disabled/gated built-ins are shown as selectable without explanation
|
||||
- Add/update tests in `packages/core/src/__tests__/builtin-workflows.test.ts`, `packages/dashboard/src/routes/__tests__/task-create-workflow-route.test.ts`, `packages/dashboard/src/routes/__tests__/workflow-design-route.test.ts`, or a new focused route test if neither existing file owns the confirmed failure boundary
|
||||
- **Approach:** First define "doesn't work" mechanically by reproducing whether CE fails at list visibility, create payload, selection persistence, workflow resolution, skill loading, or plugin runtime. Use the existing plugin-gated map (`PLUGIN_GATED_BUILTIN_WORKFLOWS`) as the source of truth. If unavailable CE workflows are currently selectable, either filter/disable them with a visible required-plugin reason or reject create/select requests with a 4xx that names `fusion-plugin-compound-engineering`. If the plugin is available but execution fails, trace skill loading through `FUSION_CE_SKILLS_DIR` and the CE skill-backed workflow nodes.
|
||||
- **FNXC comment requirement:** Add/update FNXC comments where CE plugin gating is enforced, explaining that plugin-gated built-ins must not silently degrade to coding because that hides operator workflow intent.
|
||||
- **Test Scenarios:**
|
||||
- With CE plugin available/enabled, create/select persists `builtin:compound-engineering`.
|
||||
- With CE plugin unavailable, create/select returns a clear client error or disabled option naming `fusion-plugin-compound-engineering`.
|
||||
- CE workflow resolution does not fall back to `builtin:coding` after explicit selection.
|
||||
- Skill-backed CE nodes still request CE skills by both namespaced and bare forms.
|
||||
- **Verification:** Run focused core/route tests for CE gating and selection.
|
||||
|
||||
### U8. Add the new final-review workflow to the fixed creation matrix
|
||||
|
||||
- **Goal:** Ensure the new final-review stepwise workflow benefits from the same fixed creation path and appears in workflow selectors.
|
||||
- **Requirements:** R1, R7, R10, R11, R12
|
||||
- **Files:**
|
||||
- Modify the tests added in U2, U3, U6, and U7 to include the new workflow ID once U1/U2 create it
|
||||
- **Approach:** After the selection bug is fixed for existing Stepwise and Compound engineering workflows, include the new final-review workflow in the same create/select matrix so future built-ins do not regress.
|
||||
- **Test Scenarios:**
|
||||
- Creating a task with the new workflow selected persists the new workflow ID.
|
||||
- Optional-step defaults seed `code-review` and do not erase the selected workflow.
|
||||
- **Verification:** Covered by the focused create/selection tests.
|
||||
|
||||
---
|
||||
|
||||
## Verification Contract
|
||||
|
||||
| Scope | Command | Proves |
|
||||
|---|---|---|
|
||||
| Built-in registry and IR shape | `pnpm --filter @fusion/core exec vitest run src/__tests__/builtin-workflows.test.ts src/__tests__/builtin-coding-workflow-ir.test.ts --silent=passed-only --reporter=dot` | New workflow registers, parses, round-trips, and has final-review-only stepwise shape. |
|
||||
| Optional groups | `pnpm --filter @fusion/core exec vitest run src/__tests__/workflow-optional-steps.test.ts src/__tests__/builtin-code-review-group.test.ts --silent=passed-only --reporter=dot` | Browser verification and code review defaults/wiring match other coding built-ins. |
|
||||
| Stepwise create regression | `pnpm --filter @fusion/dashboard exec vitest run app/components/__tests__/TaskForm.test.tsx app/components/__tests__/NewTaskModal.test.tsx app/components/__tests__/ListView.test.tsx app/components/__tests__/QuickEntryBox.test.tsx app/components/__tests__/board-quickcreate-workflow-lane-visibility.test.tsx app/__tests__/api-tasks.test.ts src/routes/__tests__/task-create-workflow-route.test.ts --silent=passed-only --reporter=dot` | Selected workflow ID survives picker, quick-create, UI/task-create payload, and route boundaries and does not fall back to `builtin:coding`. |
|
||||
| Store workflow selection | `pnpm --filter @fusion/core exec vitest run src/__tests__/builtin-workflows.test.ts --silent=passed-only --reporter=dot` | `TaskStore.createTask` and reserved-ID creation persist explicit/default workflow selections correctly. |
|
||||
| Compound engineering gating | `pnpm --filter @fusion/dashboard exec vitest run src/routes/__tests__/task-create-workflow-route.test.ts src/routes/__tests__/workflow-design-route.test.ts --silent=passed-only --reporter=dot` | Plugin-gated CE create/select works when available and fails visibly when unavailable. If implementation creates a more focused route test, run that file instead of the broader existing route-design file. |
|
||||
| Changeset format | `pnpm check:changesets` | Published package changeset uses required labeled fields. |
|
||||
| Type safety if exports/types changed | `pnpm --filter @fusion/core typecheck` | New exports and registry changes typecheck in core. |
|
||||
|
||||
Do not run `pnpm test:full` or `pnpm verify:workspace` for this scoped change. Use the merge gate later if the branch is being prepared for merge.
|
||||
|
||||
---
|
||||
|
||||
## Definition of Done
|
||||
|
||||
- The new workflow is available from `getBuiltinWorkflow` under a stable `builtin:` ID.
|
||||
- The new workflow executes planned steps through `parse-steps` and `foreach` without any `step-review` node.
|
||||
- The new workflow keeps browser verification, code review, final review, and merge behavior aligned with existing coding built-ins.
|
||||
- Existing `builtin:stepwise-coding` behavior and tests remain intact.
|
||||
- Creating a task from Stepwise coding persists `builtin:stepwise-coding` and never silently falls back to `builtin:coding`.
|
||||
- Creating/selecting Compound engineering either persists `builtin:compound-engineering` when available or gives a clear plugin-gating error when unavailable.
|
||||
- Workflow creation tests cover UI/API/store surfaces where workflow intent can be lost.
|
||||
- Docs describe the distinction between per-step-review stepwise coding and final-review-only stepwise coding.
|
||||
- A valid changeset exists for `@runfusion/fusion`.
|
||||
- Focused tests in the Verification Contract pass.
|
||||
@@ -165,11 +165,12 @@ Save is blocked by client-side issues such as unplaced nodes and blocking column
|
||||
|
||||
Fusion ships built-in workflows as read-only references:
|
||||
|
||||
- `builtin:coding` — the default coding lifecycle and fallback for tasks without a workflow selection.
|
||||
- `builtin:coding` — the default Stepwise-based coding lifecycle: plan steps, execute them one at a time, then review and merge the full result.
|
||||
- `builtin:legacy-coding` — the original monolithic coding lifecycle for tasks that should not use graph-owned step execution.
|
||||
- `builtin:quick-fix` — a short path for trivial or no-commit/decision work.
|
||||
- `builtin:review-heavy` — a standard execute/review/merge path with an additional gated security review.
|
||||
- `builtin:compound-engineering` — a plugin-gated Compound Engineering pipeline: `/ce-plan` writes the CE plan doc, optional `ce-doc-review` can pressure-test plans (markdown gets autofix/Open Questions write-back; HTML uses DOM-safe helper mutations, including canonical checklist repair, only when safety is proven and otherwise report-only with no write), `/ce-work` implements, `/ce-code-review` gates merge, and autoMerge-off projects route through the CE PR/feedback skills before Fusion's manual merge seam.
|
||||
- `builtin:stepwise-coding` — a graph variant that models per-step parse, execute, review, and rework structure.
|
||||
- `builtin:stepwise-coding` — Coding (per-step review): a graph variant that models per-step parse, execute, review, and rework structure.
|
||||
- `builtin:design` — a UI-heavy work path with a gated design/UX review before standard review and merge.
|
||||
|
||||
Built-ins can be viewed, exported, and used as templates, but their graph, columns, field declarations, and setting declarations are not editable. Their per-project setting **values** are editable from the Settings panel's Values tab. Selectable built-ins all use a capacity-released queue column (`todo` or a workflow-specific backlog) that dispatches to the active WIP column through the standard hold/release sweep.
|
||||
|
||||
@@ -44,12 +44,13 @@ Decision-only or investigation tasks can also declare `noCommitsExpected` / `**N
|
||||
|
||||
| Workflow | ID | Notes |
|
||||
|---|---|---|
|
||||
| Coding | `builtin:coding` | Default coding lifecycle and fallback for tasks without an explicit selection. |
|
||||
| Coding | `builtin:coding` | Default Stepwise-based coding lifecycle: plan steps, execute them one at a time, then review and merge the full result. |
|
||||
| Legacy coding | `builtin:legacy-coding` | Original monolithic coding lifecycle for tasks that should not use graph-owned step execution. |
|
||||
| Quick fix | `builtin:quick-fix` | Short path for trivial or no-commit/decision work; omits the standard review stage. |
|
||||
| Review-heavy | `builtin:review-heavy` | Standard execute/review/merge path with an additional gated security review. |
|
||||
| Marketing | `builtin:marketing` | Content pipeline with custom Ideation, Backlog, Drafting, Editorial review, Published, and Archived columns plus structured marketing brief/draft/editorial prompts; drafts are persisted as task documents for review while the workflow reuses standard lifecycle traits and merge primitives. |
|
||||
| Compound engineering | `builtin:compound-engineering` | Plugin-gated CE workflow that invokes `/ce-plan`, optional advisory `ce-doc-review` (markdown autofix; HTML DOM-safe mutation with report-only fallback), `/ce-work`, merge-blocking `/ce-code-review`, CE PR/feedback skills, Fusion merge, and learnings capture. |
|
||||
| Stepwise coding | `builtin:stepwise-coding` | Graph-executor workflow that models per-step parse/execute/review/rework explicitly. |
|
||||
| Coding (per-step review) | `builtin:stepwise-coding` | Graph-executor workflow that models per-step parse/execute/review/rework explicitly. |
|
||||
| Design | `builtin:design` | UI-heavy work path that implements, persists a user-facing design preview task document, runs a gated design/UX review, then performs the standard review and merge. |
|
||||
| PR lifecycle | `builtin:pr-workflow` | Reusable PR lifecycle graph fragment (create PR → await review → respond → gate → merge); it is a fragment, not directly selectable as a task workflow. |
|
||||
| Lead generation | `builtin:lead-generation` | Selectable business workflow for sourcing, qualifying, enriching, and contacting leads with custom lead fields, stage columns, and reviewable enrichment/outreach task documents; requires the workflow graph executor for custom board columns. |
|
||||
@@ -177,11 +178,13 @@ The workflow runtime is the authoritative execution path for task lifecycle work
|
||||
|
||||
The engine remains the substrate for scheduler dispatch, routing claims, persistence, concurrency limits, process supervision, storage, and audit plumbing. Lifecycle policy belongs in built-in or custom workflows.
|
||||
|
||||
The default built-in catalog entry `builtin:coding` is backed by the canonical `BUILTIN_CODING_WORKFLOW_IR`, which is also the resolver/runtime fallback for tasks with no workflow selection or an explicit default selection. Missing/corrupt explicit custom selections fail closed as workflow-resolution failures instead of silently running the default. The built-in IR encodes the legacy lifecycle path as graph stages, with merge represented by workflow-native policy primitives rather than a single linear merge seam:
|
||||
The default built-in catalog entry `builtin:coding` is backed by a Stepwise-derived final-review graph. It is the resolver/runtime fallback for tasks with no workflow selection or an explicit default selection. Missing/corrupt explicit custom selections fail closed as workflow-resolution failures instead of silently running the default. The built-in IR parses planned steps, executes them sequentially without per-step review, then runs the whole-task review and merge region:
|
||||
|
||||
- `triage/planning` → `execute` → `optional-group` quality gates → `review` → `merge-gate` / branch-group integration / `merge-attempt` / retry or manual hold → post-merge `optional-group` gates → `end`
|
||||
- `triage` → `plan` → `parse-steps` → `foreach(step-execute)` → `optional-group` quality gates → `review` → `merge-gate` / branch-group integration / `merge-attempt` / retry or manual hold → `end`
|
||||
|
||||
`builtin:stepwise-coding` is a separate graph variant backed by `BUILTIN_STEPWISE_CODING_WORKFLOW_IR`; it keeps the same lifecycle columns/traits while modeling per-step parse/execute/review/rework as authored graph structure.
|
||||
`builtin:legacy-coding` is backed by the original monolithic `BUILTIN_CODING_WORKFLOW_IR`: `planning` → `execute` → optional quality gates → `review` → merge region.
|
||||
|
||||
`builtin:stepwise-coding` displays as Coding (per-step review). It is backed by `BUILTIN_STEPWISE_CODING_WORKFLOW_IR`; it keeps the same lifecycle columns/traits while modeling per-step parse/execute/review/rework as authored graph structure.
|
||||
|
||||
`builtin:marketing` is a non-coding content workflow with marketing-specific columns (`ideation`, `backlog`, `drafting`, `editorial-review`, `published`, `archived`) and prompt seams for content brief, draft, and editorial review. Its draft stage saves the primary content deliverable as a task document for human review, while the workflow uses the same lifecycle traits (`intake`, `hold`, `wip`, `merge-blocker`, `human-review`, `complete`, `archived`) and the same merge-gate/branch-group/merge-attempt primitive region as coding workflows, so scheduler, capacity, review blocking, and merge orchestration behavior remain standard.
|
||||
|
||||
@@ -341,7 +344,7 @@ Node config (`WorkflowOptionalGroupConfig`): `{ name?, defaultOn?, maxRevisions?
|
||||
|
||||
Built-in optional gates ship as inlined IR builders, not as a template catalog:
|
||||
|
||||
- `builtin:coding` carries the `browser-verification` optional-group node (`builtin-browser-verification-group.ts`), opt-in by default, so browser verification runs only for tasks whose `enabledWorkflowSteps` includes `browser-verification`.
|
||||
- `builtin:coding` carries the `browser-verification` optional-group node (`builtin-browser-verification-group.ts`), default-off, so browser verification runs only for tasks whose `enabledWorkflowSteps` includes `browser-verification`.
|
||||
- The `code-review` optional-group node (`builtin-code-review-group.ts`) is the inlined code-review gate.
|
||||
- A workflow (for example compound-engineering) can add a **post-merge** optional-group node via the generic `postMergeOptionalGroupNode(...)` builder (`builtin-post-merge-group.ts`) — e.g. a `document` step that runs after merge.
|
||||
|
||||
@@ -422,7 +425,7 @@ FN-7039 (U6) DELETED the `WORKFLOW_STEP_TEMPLATES` built-in catalog array (the f
|
||||
|
||||
The built-in quality gates ship as inlined `optional-group` node builders in `@fusion/core`, not as a template catalog (the former `WORKFLOW_STEP_TEMPLATES` array was removed):
|
||||
|
||||
- **Browser Verification** (`browser-verification`, `builtin-browser-verification-group.ts`) — browser-automation-style checks for UI validation flows; an optional-group node on `builtin:coding` and `builtin:stepwise-coding`.
|
||||
- **Browser Verification** (`browser-verification`, `builtin-browser-verification-group.ts`) — browser-automation-style checks for UI validation flows; an optional-group node on `builtin:coding`, `builtin:legacy-coding`, and `builtin:stepwise-coding`.
|
||||
- **Code Review** (`code-review`, `builtin-code-review-group.ts`) — the inlined code-review gate.
|
||||
|
||||
The Browser Verification inner prompt node carries `requiresBrowser: true` while keeping `toolMode: "coding"`. When that step runs, the executor best-effort adds the `agent-browser-navigation` skill (when the agent-browser plugin is installed), runs a bounded non-fatal `agent-browser --version` preflight, and writes start, availability, and finish entries into both the task log and the task's agent log. A missing or timed-out `agent-browser` binary is logged as an actionable warning rather than failing the step solely because of the preflight; the prompt can still fast-bail or report a normal verification failure. Because Bash tool events are already streamed to the agent log, `agent-browser open ...`, `agent-browser snapshot ...`, and related commands appear as the browser-verification activity the step performed.
|
||||
|
||||
@@ -16,6 +16,7 @@ import { resolveColumnFlags } from "../trait-registry.js";
|
||||
import { compileWorkflowToSteps } from "../workflow-compiler.js";
|
||||
import { DEFAULT_WORKFLOW_COLUMN_IDS, parseWorkflowIr, serializeWorkflowIr } from "../workflow-ir.js";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { BUILTIN_STEPWISE_FINAL_REVIEW_CODING_WORKFLOW_IR } from "../builtin-stepwise-final-review-coding-workflow-ir.js";
|
||||
|
||||
const EXECUTE_NODE_MAX_RETRIES = 2;
|
||||
const LINEAR_BUILTIN_IDS = [
|
||||
@@ -43,6 +44,7 @@ describe("built-in workflows", () => {
|
||||
// linear compiler cannot lower to a step list. They still must parse as valid IR.
|
||||
const NON_COMPILABLE_BUILTIN_IDS = new Set([
|
||||
"builtin:coding",
|
||||
"builtin:legacy-coding",
|
||||
"builtin:marketing",
|
||||
"builtin:stepwise-coding",
|
||||
"builtin:pr-workflow",
|
||||
@@ -83,6 +85,37 @@ describe("built-in workflows", () => {
|
||||
expect(template.nodes.some((n) => n.config?.seam === "step-execute")).toBe(true);
|
||||
});
|
||||
|
||||
it("backs default coding with stepwise execution without per-step review", () => {
|
||||
const workflow = getBuiltinWorkflow("builtin:coding");
|
||||
expect(workflow).toBeDefined();
|
||||
expect(workflow!.ir).toBe(BUILTIN_STEPWISE_FINAL_REVIEW_CODING_WORKFLOW_IR);
|
||||
const ir = parseWorkflowIr(workflow!.ir);
|
||||
if (ir.version !== "v2") throw new Error("expected v2");
|
||||
|
||||
expect(ir.nodes.some((node) => node.kind === "parse-steps")).toBe(true);
|
||||
expect(ir.nodes.map((node) => node.id)).toEqual(
|
||||
expect.arrayContaining(["plan", "parse", "steps", "browser-verification", "code-review", "review", "merge-gate", "merge-attempt"]),
|
||||
);
|
||||
expect(ir.nodes.some((node) => node.id === "rework-hold")).toBe(false);
|
||||
|
||||
const foreach = ir.nodes.find((node) => node.kind === "foreach");
|
||||
expect(foreach).toBeDefined();
|
||||
const template = (
|
||||
foreach!.config as {
|
||||
template: {
|
||||
nodes: Array<{ id: string; kind: string }>;
|
||||
edges: Array<{ from: string; to: string; condition?: string; kind?: string }>;
|
||||
};
|
||||
}
|
||||
).template;
|
||||
expect(template.nodes.map((node) => node.id)).toEqual(["step-execute", "step-done"]);
|
||||
expect(template.nodes.some((node) => node.kind === "step-review")).toBe(false);
|
||||
expect(template.edges).toEqual([
|
||||
expect.objectContaining({ from: "step-execute", to: "step-done", condition: "success" }),
|
||||
]);
|
||||
expect(template.edges.some((edge) => edge.kind === "rework")).toBe(false);
|
||||
});
|
||||
|
||||
it("includes the PR lifecycle built-in wiring the PR nodes end to end (U9)", () => {
|
||||
const pr = getBuiltinWorkflow("builtin:pr-workflow");
|
||||
expect(pr).toBeDefined();
|
||||
@@ -142,17 +175,28 @@ describe("built-in workflows", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("builtin:coding catalog entry is backed by the canonical coding IR", () => {
|
||||
it("builtin:coding catalog entry is backed by the stepwise final-review IR", () => {
|
||||
const coding = getBuiltinWorkflow("builtin:coding");
|
||||
expect(coding).toBeDefined();
|
||||
expect(coding!.id).toBe("builtin:coding");
|
||||
expect(coding!.name).toBe("Coding (built-in)");
|
||||
expect(coding!.description).toContain("standard coding pipeline");
|
||||
expect(coding!.description).toContain("execute them one at a time");
|
||||
expect(coding!.kind).toBe("workflow");
|
||||
expect(coding!.createdAt).toBe("2026-01-01T00:00:00.000Z");
|
||||
expect(coding!.updatedAt).toBe("2026-01-01T00:00:00.000Z");
|
||||
expect(coding!.ir).toBe(BUILTIN_CODING_WORKFLOW_IR);
|
||||
expect(serializeWorkflowIr(coding!.ir)).toBe(serializeWorkflowIr(BUILTIN_CODING_WORKFLOW_IR));
|
||||
expect(coding!.ir).toBe(BUILTIN_STEPWISE_FINAL_REVIEW_CODING_WORKFLOW_IR);
|
||||
expect(serializeWorkflowIr(coding!.ir)).toBe(serializeWorkflowIr(BUILTIN_STEPWISE_FINAL_REVIEW_CODING_WORKFLOW_IR));
|
||||
});
|
||||
|
||||
it("builtin:legacy-coding catalog entry preserves the original monolithic coding IR", () => {
|
||||
const legacy = getBuiltinWorkflow("builtin:legacy-coding");
|
||||
expect(legacy).toBeDefined();
|
||||
expect(legacy!.id).toBe("builtin:legacy-coding");
|
||||
expect(legacy!.name).toBe("Legacy coding (built-in)");
|
||||
expect(legacy!.description).toContain("original monolithic coding pipeline");
|
||||
expect(legacy!.kind).toBe("workflow");
|
||||
expect(legacy!.ir).toBe(BUILTIN_CODING_WORKFLOW_IR);
|
||||
expect(serializeWorkflowIr(legacy!.ir)).toBe(serializeWorkflowIr(BUILTIN_CODING_WORKFLOW_IR));
|
||||
});
|
||||
|
||||
it("linear built-ins use the canonical trait-bearing default columns", () => {
|
||||
@@ -216,6 +260,17 @@ describe("built-in workflows", () => {
|
||||
{ id: "archived", traits: ["archived"] },
|
||||
],
|
||||
],
|
||||
[
|
||||
"builtin:legacy-coding",
|
||||
[
|
||||
{ id: "triage", traits: ["intake"] },
|
||||
{ id: "todo", traits: ["hold", "reset-on-entry"] },
|
||||
{ id: "in-progress", traits: ["wip", "abort-on-exit", "timing"] },
|
||||
{ id: "in-review", traits: ["merge-blocker", "human-review", "stall-detection", "merge"] },
|
||||
{ id: "done", traits: ["complete"] },
|
||||
{ id: "archived", traits: ["archived"] },
|
||||
],
|
||||
],
|
||||
[
|
||||
"builtin:lead-generation",
|
||||
[
|
||||
@@ -276,7 +331,9 @@ describe("built-in workflows", () => {
|
||||
]);
|
||||
|
||||
const byId = new Map(ir.nodes.map((node) => [node.id, node]));
|
||||
expect(byId.get("execute")?.column).toBe("in-progress");
|
||||
expect(byId.get("plan")?.column).toBe("in-progress");
|
||||
expect(byId.get("parse")?.column).toBe("in-progress");
|
||||
expect(byId.get("steps")?.column).toBe("in-progress");
|
||||
// U6: the legacy `workflow-step` seam is replaced by the pre-merge
|
||||
// `browser-verification` optional-group, placed in the implementation column.
|
||||
expect(byId.get("workflow-step")).toBeUndefined();
|
||||
@@ -392,9 +449,9 @@ describe("built-in workflows", () => {
|
||||
});
|
||||
|
||||
it("repeated catalog reads and listings keep builtin:coding in the enabled order", () => {
|
||||
expect(getBuiltinWorkflow("builtin:coding")?.ir).toBe(BUILTIN_CODING_WORKFLOW_IR);
|
||||
expect(getBuiltinWorkflow("builtin:coding")?.ir).toBe(BUILTIN_CODING_WORKFLOW_IR);
|
||||
expect(BUILTIN_WORKFLOWS.find((workflow) => workflow.id === "builtin:coding")?.ir).toBe(BUILTIN_CODING_WORKFLOW_IR);
|
||||
expect(getBuiltinWorkflow("builtin:coding")?.ir).toBe(BUILTIN_STEPWISE_FINAL_REVIEW_CODING_WORKFLOW_IR);
|
||||
expect(getBuiltinWorkflow("builtin:coding")?.ir).toBe(BUILTIN_STEPWISE_FINAL_REVIEW_CODING_WORKFLOW_IR);
|
||||
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),
|
||||
@@ -408,11 +465,12 @@ describe("built-in workflows", () => {
|
||||
expect(defaultEnabledBuiltinWorkflowIds().length).toBeGreaterThanOrEqual(5);
|
||||
expect(defaultEnabledBuiltinWorkflowIds().slice(0, 5)).toEqual([
|
||||
"builtin:coding",
|
||||
"builtin:legacy-coding",
|
||||
"builtin:quick-fix",
|
||||
"builtin:review-heavy",
|
||||
"builtin:marketing",
|
||||
"builtin:stepwise-coding",
|
||||
]);
|
||||
expect(defaultEnabledBuiltinWorkflowIds()).toContain("builtin:stepwise-coding");
|
||||
});
|
||||
|
||||
it("identifies plugin-gated built-in workflows", () => {
|
||||
@@ -428,8 +486,8 @@ describe("built-in workflows", () => {
|
||||
expect(getRequiredPluginIdForBuiltinWorkflow("builtin:coding")).toBeUndefined();
|
||||
expect(getRequiredPluginIdForBuiltinWorkflow("builtin:quick-fix")).toBeUndefined();
|
||||
});
|
||||
it("builtin:coding exposes execute retries after registry lookup and parse round-trip", () => {
|
||||
const coding = getBuiltinWorkflow("builtin:coding");
|
||||
it("builtin:legacy-coding exposes execute retries after registry lookup and parse round-trip", () => {
|
||||
const coding = getBuiltinWorkflow("builtin:legacy-coding");
|
||||
expect(coding).toBeDefined();
|
||||
const ir = parseWorkflowIr(coding!.ir);
|
||||
const reparsed = parseWorkflowIr(serializeWorkflowIr(ir));
|
||||
@@ -726,13 +784,18 @@ describe("built-in workflows", () => {
|
||||
expect(await store.getWorkflowDefinition("builtin:compound-engineering")).toBeDefined();
|
||||
});
|
||||
|
||||
it("shows the built-in seam prompt text in node config", () => {
|
||||
it("shows the built-in prompt text in node config", () => {
|
||||
const coding = getBuiltinWorkflow("builtin:coding");
|
||||
const execute = coding?.ir.nodes.find((node) => node.id === "execute");
|
||||
const plan = coding?.ir.nodes.find((node) => node.id === "plan");
|
||||
const steps = coding?.ir.nodes.find((node) => node.id === "steps");
|
||||
const review = coding?.ir.nodes.find((node) => node.id === "review");
|
||||
const legacy = getBuiltinWorkflow("builtin:legacy-coding");
|
||||
const legacyExecute = legacy?.ir.nodes.find((node) => node.id === "execute");
|
||||
|
||||
expect((execute?.config as { prompt?: string } | undefined)?.prompt).toContain("You are a task execution agent");
|
||||
expect((plan?.config as { prompt?: string } | undefined)?.prompt).toContain("You are a task specification agent");
|
||||
expect(steps?.kind).toBe("foreach");
|
||||
expect((review?.config as { prompt?: string } | undefined)?.prompt).toContain("You are an independent code and plan reviewer");
|
||||
expect((legacyExecute?.config as { prompt?: string } | undefined)?.prompt).toContain("You are a task execution agent");
|
||||
// No `merge` seam node post-FN-6035 — merge runs as native primitives.
|
||||
expect(coding?.ir.nodes.find((node) => node.id === "merge")).toBeUndefined();
|
||||
});
|
||||
@@ -752,10 +815,11 @@ describe("built-in workflows", () => {
|
||||
// with builtin:coding — previously select returned [] and silently skipped the gate).
|
||||
const expectedGroups: Record<string, string[]> = {
|
||||
"builtin:coding": ["code-review"],
|
||||
"builtin:legacy-coding": ["code-review"],
|
||||
"builtin:marketing": [],
|
||||
"builtin:stepwise-coding": ["code-review"],
|
||||
};
|
||||
for (const workflowId of ["builtin:coding", "builtin:marketing", "builtin:stepwise-coding"]) {
|
||||
for (const workflowId of ["builtin:coding", "builtin:legacy-coding", "builtin:marketing", "builtin:stepwise-coding"]) {
|
||||
const task = await store.createTask({ description: `select ${workflowId}`, enabledWorkflowSteps: [] });
|
||||
const expected = expectedGroups[workflowId];
|
||||
|
||||
@@ -779,8 +843,10 @@ describe("built-in workflows", () => {
|
||||
});
|
||||
|
||||
it("a task can disable code-review by creating with explicit enabledWorkflowSteps excluding it", async () => {
|
||||
// Default-on but TOGGLEABLE: an explicit (non-empty) enabledWorkflowSteps wins over
|
||||
// the workflow's default-on seeding, so omitting `code-review` disables it.
|
||||
// FNXC:WorkflowCreation 2026-06-28-23:09:
|
||||
// Default-on optional groups are toggleable, but toggling them must not erase
|
||||
// the explicit workflow selection row. User-facing create flows send workflowId
|
||||
// and enabledWorkflowSteps together.
|
||||
const task = await store.createTask({
|
||||
description: "coding without code review",
|
||||
workflowId: "builtin:coding",
|
||||
@@ -789,6 +855,55 @@ describe("built-in workflows", () => {
|
||||
const detail = await store.getTask(task.id);
|
||||
expect(detail.enabledWorkflowSteps ?? []).not.toContain("code-review");
|
||||
expect(detail.enabledWorkflowSteps ?? []).toEqual(["browser-verification"]);
|
||||
expect(store.getTaskWorkflowSelection(task.id)).toEqual({
|
||||
workflowId: "builtin:coding",
|
||||
stepIds: ["browser-verification"],
|
||||
});
|
||||
});
|
||||
|
||||
it("create-time stepwise workflowId persists when optional steps are submitted", async () => {
|
||||
const task = await store.createTask({
|
||||
description: "stepwise with toggles",
|
||||
workflowId: "builtin:stepwise-coding",
|
||||
enabledWorkflowSteps: ["code-review"],
|
||||
});
|
||||
|
||||
expect((await store.getTask(task.id)).enabledWorkflowSteps ?? []).toEqual(["code-review"]);
|
||||
expect(store.getTaskWorkflowSelection(task.id)).toEqual({
|
||||
workflowId: "builtin:stepwise-coding",
|
||||
stepIds: ["code-review"],
|
||||
});
|
||||
});
|
||||
|
||||
it("create-time workflowId with empty optional steps disables default-on groups but keeps selection", async () => {
|
||||
const task = await store.createTask({
|
||||
description: "coding with all optional groups off",
|
||||
workflowId: "builtin:coding",
|
||||
enabledWorkflowSteps: [],
|
||||
});
|
||||
|
||||
expect((await store.getTask(task.id)).enabledWorkflowSteps ?? []).toEqual([]);
|
||||
expect(store.getTaskWorkflowSelection(task.id)).toEqual({
|
||||
workflowId: "builtin:coding",
|
||||
stepIds: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("reserved-id create-time workflowId persists when optional steps are submitted", async () => {
|
||||
const task = await store.createTaskWithReservedId(
|
||||
{
|
||||
description: "reserved stepwise with toggles",
|
||||
workflowId: "builtin:stepwise-coding",
|
||||
enabledWorkflowSteps: ["code-review"],
|
||||
},
|
||||
{ taskId: "reserved-stepwise-with-toggles" },
|
||||
);
|
||||
|
||||
expect((await store.getTask(task.id)).enabledWorkflowSteps ?? []).toEqual(["code-review"]);
|
||||
expect(store.getTaskWorkflowSelection(task.id)).toEqual({
|
||||
workflowId: "builtin:stepwise-coding",
|
||||
stepIds: ["code-review"],
|
||||
});
|
||||
});
|
||||
|
||||
it("branching built-in project defaults do not throw", async () => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { BUILTIN_CODING_WORKFLOW_IR } from "../builtin-coding-workflow-ir.js";
|
||||
import { BUILTIN_STEPWISE_CODING_WORKFLOW_IR } from "../builtin-stepwise-coding-workflow-ir.js";
|
||||
import { BUILTIN_STEPWISE_FINAL_REVIEW_CODING_WORKFLOW_IR } from "../builtin-stepwise-final-review-coding-workflow-ir.js";
|
||||
import {
|
||||
resolveDefaultOnOptionalGroupIds,
|
||||
resolveWorkflowOptionalSteps,
|
||||
@@ -130,6 +131,7 @@ describe("resolveWorkflowOptionalSteps (optional-group nodes)", () => {
|
||||
];
|
||||
expect(resolveWorkflowOptionalSteps(BUILTIN_CODING_WORKFLOW_IR)).toEqual(expected);
|
||||
expect(resolveWorkflowOptionalSteps(BUILTIN_STEPWISE_CODING_WORKFLOW_IR)).toEqual(expected);
|
||||
expect(resolveWorkflowOptionalSteps(BUILTIN_STEPWISE_FINAL_REVIEW_CODING_WORKFLOW_IR)).toEqual(expected);
|
||||
});
|
||||
|
||||
it("seeds code-review (default ON) but not browser-verification (default OFF) for the built-ins", () => {
|
||||
@@ -137,6 +139,7 @@ describe("resolveWorkflowOptionalSteps (optional-group nodes)", () => {
|
||||
// default: code-review is on, browser-verification is off.
|
||||
expect(resolveDefaultOnOptionalGroupIds(BUILTIN_CODING_WORKFLOW_IR)).toEqual(["code-review"]);
|
||||
expect(resolveDefaultOnOptionalGroupIds(BUILTIN_STEPWISE_CODING_WORKFLOW_IR)).toEqual(["code-review"]);
|
||||
expect(resolveDefaultOnOptionalGroupIds(BUILTIN_STEPWISE_FINAL_REVIEW_CODING_WORKFLOW_IR)).toEqual(["code-review"]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import type { WorkflowIr } from "./workflow-ir-types.js";
|
||||
import { parseWorkflowIr } from "./workflow-ir.js";
|
||||
import { BUILTIN_STEPWISE_CODING_WORKFLOW_IR } from "./builtin-stepwise-coding-workflow-ir.js";
|
||||
|
||||
function cloneWorkflowIr(ir: WorkflowIr): WorkflowIr {
|
||||
return JSON.parse(JSON.stringify(ir)) as WorkflowIr;
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:WorkflowBuiltins 2026-06-28-23:09:
|
||||
Operators need graph-owned step execution without per-step AI review. This built-in preserves the per-step-review workflow's parse-steps and sequential foreach model, then runs the normal end-of-task browser/code-review/final-review/merge suffix after all planned steps finish.
|
||||
*/
|
||||
const RAW_BUILTIN_STEPWISE_FINAL_REVIEW_CODING_WORKFLOW_IR: WorkflowIr = (() => {
|
||||
const ir = cloneWorkflowIr(BUILTIN_STEPWISE_CODING_WORKFLOW_IR);
|
||||
ir.name = "builtin-stepwise-final-review-coding";
|
||||
|
||||
const foreach = ir.nodes.find((node) => node.id === "steps" && node.kind === "foreach");
|
||||
const template = foreach?.config?.template as
|
||||
| {
|
||||
nodes?: Array<{ id: string; kind: string; config?: Record<string, unknown> }>;
|
||||
edges?: Array<{ from: string; to: string; condition?: string; kind?: string }>;
|
||||
}
|
||||
| undefined;
|
||||
if (!template?.nodes || !template.edges) {
|
||||
throw new Error("stepwise final-review built-in requires the stepwise foreach template");
|
||||
}
|
||||
|
||||
template.nodes = template.nodes.filter((node) => node.id !== "step-review");
|
||||
template.edges = [
|
||||
{ from: "step-execute", to: "step-done", condition: "success" },
|
||||
];
|
||||
|
||||
ir.nodes = ir.nodes.filter((node) => node.id !== "rework-hold");
|
||||
ir.edges = ir.edges.filter(
|
||||
(edge) => edge.from !== "rework-hold" && edge.to !== "rework-hold",
|
||||
);
|
||||
|
||||
return ir;
|
||||
})();
|
||||
|
||||
export const BUILTIN_STEPWISE_FINAL_REVIEW_CODING_WORKFLOW_IR = parseWorkflowIr(
|
||||
RAW_BUILTIN_STEPWISE_FINAL_REVIEW_CODING_WORKFLOW_IR,
|
||||
);
|
||||
@@ -3,6 +3,7 @@ import { BUILTIN_LEAD_GENERATION_WORKFLOW_IR } from "./builtin-lead-generation-w
|
||||
import { BUILTIN_MARKETING_WORKFLOW_IR } from "./builtin-marketing-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_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 type { WorkflowDefinition } from "./workflow-definition-types.js";
|
||||
@@ -138,7 +139,33 @@ export const BUILTIN_WORKFLOWS: WorkflowDefinition[] = [
|
||||
{
|
||||
id: "builtin:coding",
|
||||
name: "Coding (built-in)",
|
||||
description: "The standard coding pipeline: implement, review, then merge. Equivalent to the default behavior.",
|
||||
description: "Default coding pipeline: plan steps, execute them one at a time, then review and merge the full result.",
|
||||
kind: "workflow",
|
||||
ir: BUILTIN_STEPWISE_FINAL_REVIEW_CODING_WORKFLOW_IR,
|
||||
layout: {
|
||||
start: { x: 60, y: 160 },
|
||||
plan: { x: 230, y: 160 },
|
||||
parse: { x: 400, y: 160 },
|
||||
steps: { x: 570, y: 160 },
|
||||
"browser-verification": { x: 740, y: 160 },
|
||||
"code-review": { x: 910, y: 160 },
|
||||
review: { x: 1080, y: 160 },
|
||||
"merge-gate": { x: 1250, y: 160 },
|
||||
"branch-group-member-integration": { x: 1420, y: 80 },
|
||||
"branch-group-promotion": { x: 1590, y: 80 },
|
||||
"merge-attempt": { x: 1760, y: 160 },
|
||||
"merge-retry": { x: 1930, y: 80 },
|
||||
"recovery-router": { x: 1930, y: 240 },
|
||||
"merge-manual-hold": { x: 1420, y: 240 },
|
||||
end: { x: 2100, y: 160 },
|
||||
},
|
||||
createdAt: BUILTIN_TS,
|
||||
updatedAt: BUILTIN_TS,
|
||||
},
|
||||
{
|
||||
id: "builtin:legacy-coding",
|
||||
name: "Legacy coding (built-in)",
|
||||
description: "The original monolithic coding pipeline: implement, review, then merge without graph-owned per-step execution.",
|
||||
kind: "workflow",
|
||||
ir: BUILTIN_CODING_WORKFLOW_IR,
|
||||
layout: {
|
||||
@@ -353,9 +380,9 @@ export const BUILTIN_WORKFLOWS: WorkflowDefinition[] = [
|
||||
// workflow graph runtime.
|
||||
{
|
||||
id: "builtin:stepwise-coding",
|
||||
name: "Stepwise coding (built-in)",
|
||||
name: "Coding (per-step review) (built-in)",
|
||||
description:
|
||||
"Per-step plan, execute, and review modeled as graph structure: each planned step runs and is reviewed (approve / revise / rethink) before the next, with bounded rework.",
|
||||
"Per-step review coding pipeline: each planned step runs and is reviewed (approve / revise / rethink) before the next, with bounded rework before final review and merge.",
|
||||
kind: "workflow",
|
||||
ir: BUILTIN_STEPWISE_CODING_WORKFLOW_IR,
|
||||
layout: {
|
||||
|
||||
@@ -165,6 +165,7 @@ export {
|
||||
} from "./workflow-prompt-overrides.js";
|
||||
export type { WorkflowPromptDefault, WorkflowPromptOverrides } from "./workflow-prompt-overrides.js";
|
||||
export { BUILTIN_STEPWISE_CODING_WORKFLOW_IR } from "./builtin-stepwise-coding-workflow-ir.js";
|
||||
export { BUILTIN_STEPWISE_FINAL_REVIEW_CODING_WORKFLOW_IR } from "./builtin-stepwise-final-review-coding-workflow-ir.js";
|
||||
export { BUILTIN_PR_WORKFLOW_IR } from "./builtin-pr-workflow-ir.js";
|
||||
export { BUILTIN_LEAD_GENERATION_WORKFLOW_IR } from "./builtin-lead-generation-workflow-ir.js";
|
||||
export {
|
||||
|
||||
@@ -4556,12 +4556,12 @@ ${TASK_UPSERT_SQL_ASSIGNMENTS}
|
||||
// When a project default workflow is configured, new tasks inherit it
|
||||
// (compiled to steps) ahead of the legacy default-on step behavior.
|
||||
let pendingWorkflowSelection: { workflowId: string; stepIds: string[] } | undefined;
|
||||
// U6/R3/KTD-4: an explicit create-time workflowId beats the project default.
|
||||
// `null` is an explicit opt-out (no workflow), `string` materializes that
|
||||
// workflow, `undefined` falls through to the default-workflow behavior below.
|
||||
// Explicit enabledWorkflowSteps still wins over workflowId for trusted callers.
|
||||
const explicitWorkflowId =
|
||||
input.enabledWorkflowSteps === undefined ? input.workflowId : undefined;
|
||||
/*
|
||||
FNXC:WorkflowCreation 2026-06-28-23:09:
|
||||
User-facing task creation can submit a selected workflowId and optional-group toggles together. The visible workflow selection is operator intent and must persist as task_workflow_selection; enabledWorkflowSteps only overrides that workflow's default optional-group seed.
|
||||
Legacy trusted callers that submit enabledWorkflowSteps without workflowId still bypass workflow selection materialization.
|
||||
*/
|
||||
const explicitWorkflowId = input.workflowId;
|
||||
if (explicitWorkflowId !== undefined) {
|
||||
if (explicitWorkflowId === null) {
|
||||
// Explicit "No workflow": skip default materialization entirely.
|
||||
@@ -4570,8 +4570,14 @@ ${TASK_UPSERT_SQL_ASSIGNMENTS}
|
||||
// Compile + materialize up front so unknown/fragment ids throw BEFORE
|
||||
// the task row is created (no orphaned steps, no half-created task).
|
||||
const selected = await this.materializeExplicitWorkflowSteps(explicitWorkflowId);
|
||||
resolvedWorkflowSteps = selected.stepIds;
|
||||
pendingWorkflowSelection = selected;
|
||||
const explicitStepIds = input.enabledWorkflowSteps !== undefined
|
||||
? (resolvedWorkflowSteps ?? [])
|
||||
: undefined;
|
||||
resolvedWorkflowSteps = explicitStepIds ?? selected.stepIds;
|
||||
pendingWorkflowSelection = {
|
||||
workflowId: selected.workflowId,
|
||||
stepIds: explicitStepIds ?? selected.stepIds,
|
||||
};
|
||||
}
|
||||
} else if (input.enabledWorkflowSteps === undefined) {
|
||||
try {
|
||||
@@ -4744,12 +4750,11 @@ ${TASK_UPSERT_SQL_ASSIGNMENTS}
|
||||
: undefined;
|
||||
|
||||
let pendingWorkflowSelection: { workflowId: string; stepIds: string[] } | undefined;
|
||||
// U6/R3/KTD-4: an explicit create-time workflowId beats the project default,
|
||||
// mirroring createTask(). `null` is an explicit opt-out, `string` materializes
|
||||
// that workflow, `undefined` falls through to the default-workflow behavior.
|
||||
// Explicit enabledWorkflowSteps still wins over workflowId for trusted callers.
|
||||
const explicitWorkflowId =
|
||||
input.enabledWorkflowSteps === undefined ? input.workflowId : undefined;
|
||||
/*
|
||||
FNXC:WorkflowCreation 2026-06-28-23:09:
|
||||
Reserved-id task creation must match normal task creation: workflowId and enabledWorkflowSteps are independent create controls, so explicit optional toggles do not erase the selected workflow row.
|
||||
*/
|
||||
const explicitWorkflowId = input.workflowId;
|
||||
if (explicitWorkflowId !== undefined) {
|
||||
if (explicitWorkflowId === null) {
|
||||
// Explicit "No workflow": skip default materialization entirely.
|
||||
@@ -4758,8 +4763,14 @@ ${TASK_UPSERT_SQL_ASSIGNMENTS}
|
||||
// Compile + materialize up front so unknown/fragment ids throw BEFORE
|
||||
// the task row is created (no orphaned steps, no half-created task).
|
||||
const selected = await this.materializeExplicitWorkflowSteps(explicitWorkflowId);
|
||||
resolvedWorkflowSteps = selected.stepIds;
|
||||
pendingWorkflowSelection = selected;
|
||||
const explicitStepIds = input.enabledWorkflowSteps !== undefined
|
||||
? (resolvedWorkflowSteps ?? [])
|
||||
: undefined;
|
||||
resolvedWorkflowSteps = explicitStepIds ?? selected.stepIds;
|
||||
pendingWorkflowSelection = {
|
||||
workflowId: selected.workflowId,
|
||||
stepIds: explicitStepIds ?? selected.stepIds,
|
||||
};
|
||||
}
|
||||
} else if (input.enabledWorkflowSteps === undefined && options.applyDefaultWorkflowSteps !== false) {
|
||||
// Mirror createTask: a configured project default workflow takes
|
||||
|
||||
@@ -2262,7 +2262,7 @@ function builtinStepwiseDef(): WorkflowDefinition {
|
||||
return {
|
||||
id: "builtin:stepwise-coding",
|
||||
kind: "workflow",
|
||||
name: "Stepwise coding (built-in)",
|
||||
name: "Coding (per-step review) (built-in)",
|
||||
description: "",
|
||||
ir: BUILTIN_STEPWISE_CODING_WORKFLOW_IR,
|
||||
layout: {},
|
||||
|
||||
@@ -80,7 +80,7 @@ describe("POST /tasks workflowId (U6/R3)", () => {
|
||||
const post = (path: string, body: unknown) =>
|
||||
REQUEST(app, "POST", path, JSON.stringify(body), { "content-type": "application/json" });
|
||||
|
||||
it("workflowId → created task has populated enabledWorkflowSteps", async () => {
|
||||
it("workflowId → created task records workflow selection", async () => {
|
||||
const wf = await store.createWorkflowDefinition({ name: "QA", ir: linearIr("qa") });
|
||||
|
||||
const res = await post("/api/tasks", { description: "with workflow", workflowId: wf.id });
|
||||
@@ -88,8 +88,100 @@ describe("POST /tasks workflowId (U6/R3)", () => {
|
||||
const created = res.body as { id: string };
|
||||
|
||||
const detail = await store.getTask(created.id);
|
||||
expect(detail.enabledWorkflowSteps).toHaveLength(2);
|
||||
expect(store.getTaskWorkflowSelection(created.id)?.workflowId).toBe(wf.id);
|
||||
expect(detail.enabledWorkflowSteps ?? []).toEqual([]);
|
||||
expect(store.getTaskWorkflowSelection(created.id)).toEqual({ workflowId: wf.id, stepIds: [] });
|
||||
});
|
||||
|
||||
it("workflowId plus enabledWorkflowSteps → created task keeps explicit workflow selection", async () => {
|
||||
const res = await post("/api/tasks", {
|
||||
description: "stepwise with toggles",
|
||||
workflowId: "builtin:stepwise-coding",
|
||||
enabledWorkflowSteps: ["code-review"],
|
||||
});
|
||||
expect(res.status).toBe(201);
|
||||
const created = res.body as { id: string };
|
||||
|
||||
const detail = await store.getTask(created.id);
|
||||
expect(detail.enabledWorkflowSteps ?? []).toEqual(["code-review"]);
|
||||
expect(store.getTaskWorkflowSelection(created.id)).toEqual({
|
||||
workflowId: "builtin:stepwise-coding",
|
||||
stepIds: ["code-review"],
|
||||
});
|
||||
});
|
||||
|
||||
it("workflowId plus empty enabledWorkflowSteps → disables default optional groups and keeps selection", async () => {
|
||||
const res = await post("/api/tasks", {
|
||||
description: "coding with optional groups off",
|
||||
workflowId: "builtin:coding",
|
||||
enabledWorkflowSteps: [],
|
||||
});
|
||||
expect(res.status).toBe(201);
|
||||
const created = res.body as { id: string };
|
||||
|
||||
const detail = await store.getTask(created.id);
|
||||
expect(detail.enabledWorkflowSteps ?? []).toEqual([]);
|
||||
expect(store.getTaskWorkflowSelection(created.id)).toEqual({
|
||||
workflowId: "builtin:coding",
|
||||
stepIds: [],
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
["default coding", "builtin:coding", ["code-review"]],
|
||||
["legacy coding", "builtin:legacy-coding", ["code-review"]],
|
||||
["coding per-step review", "builtin:stepwise-coding", ["code-review"]],
|
||||
])("%s workflow create/select/resolve works end to end", async (_label, workflowId, defaultSteps) => {
|
||||
const res = await post("/api/tasks", {
|
||||
description: `exercise ${workflowId}`,
|
||||
workflowId,
|
||||
});
|
||||
expect(res.status).toBe(201);
|
||||
const created = res.body as { id: string };
|
||||
|
||||
const detail = await store.getTask(created.id);
|
||||
expect(detail.enabledWorkflowSteps ?? []).toEqual(defaultSteps);
|
||||
expect(store.getTaskWorkflowSelection(created.id)).toEqual({
|
||||
workflowId,
|
||||
stepIds: defaultSteps,
|
||||
});
|
||||
const definition = await store.getWorkflowDefinition(workflowId);
|
||||
expect(definition?.id).toBe(workflowId);
|
||||
expect(definition?.kind).toBe("workflow");
|
||||
});
|
||||
|
||||
it("unavailable Compound engineering workflow → 4xx instead of default coding fallback", async () => {
|
||||
const before = (await store.listTasks({ includeArchived: true })).length;
|
||||
|
||||
const res = await post("/api/tasks", {
|
||||
description: "compound unavailable",
|
||||
workflowId: "builtin:compound-engineering",
|
||||
});
|
||||
|
||||
expect(res.status).toBeGreaterThanOrEqual(400);
|
||||
expect(res.status).toBeLessThan(500);
|
||||
expect(String((res.body as { error?: unknown }).error ?? "")).toContain("builtin:compound-engineering");
|
||||
const after = (await store.listTasks({ includeArchived: true })).length;
|
||||
expect(after).toBe(before);
|
||||
});
|
||||
|
||||
it("registered Compound engineering plugin → created task keeps Compound workflow selection", async () => {
|
||||
await store.getPluginStore().registerPlugin({
|
||||
manifest: {
|
||||
id: "fusion-plugin-compound-engineering",
|
||||
name: "Compound Engineering",
|
||||
version: "0.0.0",
|
||||
},
|
||||
path: rootDir,
|
||||
});
|
||||
|
||||
const res = await post("/api/tasks", {
|
||||
description: "compound available",
|
||||
workflowId: "builtin:compound-engineering",
|
||||
});
|
||||
expect(res.status).toBe(201);
|
||||
const created = res.body as { id: string };
|
||||
|
||||
expect(store.getTaskWorkflowSelection(created.id)?.workflowId).toBe("builtin:compound-engineering");
|
||||
});
|
||||
|
||||
it("workflowId: null → task created with no workflow steps", async () => {
|
||||
|
||||
Reference in New Issue
Block a user