FN-7144: complete compound engineering workflow audit

Adds the missing CE document-review stage and tightens workflow docs, compilation, and tests.

- Add optional advisory ce-doc-review support to the compound engineering built-in workflow.
- Register and document the compound-engineering ce-doc-review skill for markdown CE plan review.
- Preserve optional-group workflow semantics when compiling legacy steps and cover the CE workflow seams with tests.
- Update operator docs and add a published package changeset.

Files changed:
 .changeset/fn-7144-compound-engineering-audit.md   |  7 +++
 docs/workflow-editor.md                            |  2 +-
 docs/workflow-steps.md                             |  4 +-
 .../core/src/__tests__/builtin-workflows.test.ts   | 29 +++++++++-
 packages/core/src/builtin-workflows.ts             | 62 ++++++++++++++++++++--
 packages/core/src/workflow-compiler.ts             |  3 +-
 .../__tests__/ce-workflow-step-conventions.test.ts |  8 +++
 .../src/__tests__/interpreter-merge-seam.test.ts   |  3 +-
 .../src/__tests__/manifest.test.ts                 |  1 +
 .../src/skills.ts                                  | 13 +++++
 .../src/skills/ce-doc-review/SKILL.md              | 54 +++++++++++++++++++
 11 files changed, 175 insertions(+), 11 deletions(-)

Fusion-Task-Id: FN-7144
Fusion-Task-Lineage: bedbfad4-d6e8-489e-a555-803991a1ad54
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-06-27 17:40:42 -07:00
parent c468c161c2
commit d4137f1abc
11 changed files with 175 additions and 11 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": minor
---
summary: Add optional Compound Engineering document review to the built-in CE workflow.
category: feature
dev: Bundles ce-doc-review and documents autoMerge-off CE PR routing before Fusion's merge seam.

View File

@@ -134,7 +134,7 @@ Fusion ships built-in workflows as read-only references:
- `builtin:coding` — the default coding lifecycle and fallback for tasks without a workflow selection.
- `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 workflow for Compound Engineering skill lanes.
- `builtin:compound-engineering` — a plugin-gated Compound Engineering pipeline: `/ce-plan` writes the CE plan doc, optional `ce-doc-review` can pressure-test markdown plans, `/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:design` — a UI-heavy work path with a gated design/UX review before standard review and merge.

View File

@@ -45,7 +45,7 @@ Decision-only or investigation tasks can also declare `noCommitsExpected` / `**N
| 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 workflow that invokes Compound Engineering skills for planning, work, review, PR/feedback, and learnings capture. |
| Compound engineering | `builtin:compound-engineering` | Plugin-gated CE workflow that invokes `/ce-plan`, optional advisory `ce-doc-review`, `/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. |
| 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. |
@@ -140,6 +140,8 @@ The default built-in catalog entry `builtin:coding` is backed by the canonical `
`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.
`builtin:compound-engineering` is plugin-gated by `fusion-plugin-compound-engineering`. Its graph runs `/ce-plan` first and expects the CE plan document artifact under `docs/plans/`; an optional default-off `ce-doc-review` advisory step can then review markdown plans without blocking merge. Implementation runs `/ce-work`, merge-blocking code review runs `/ce-code-review`, and the PR lane runs `/ce-commit-push-pr` then `/ce-resolve-pr-feedback` before Fusion's native merge seam. When project `autoMerge` is off, that merge seam no-ops into manual review instead of forcing an unattended board merge, so the CE-created pull request remains the human merge path.
During triage/planning sessions, agents can call `fn_workflow_list` to discover available built-in and custom workflows and read their descriptions before routing work. They can call `fn_workflow_select` only when the user explicitly requested a workflow or when selecting a workflow for a task they created, and they can pass `workflow_id` when creating child tasks with `fn_task_create`; decision-only or investigation tasks can also set `noCommitsExpected` / `**No commits expected:** true` when no code changes are expected. The built-in triage thresholds, decision-only verb list, and default routing IDs are workflow-native typed settings resolved from the selected workflow.
#### Runtime invariant criterion

View File

@@ -407,10 +407,12 @@ describe("built-in workflows", () => {
const ce = getBuiltinWorkflow("builtin:compound-engineering")!;
const steps = compileWorkflowToSteps(ce.ir);
// plan + execute (ce-work) + code-review (pre-merge) + commit-pr +
// resolve-feedback + document (post-merge) — merge seams are skipped.
// resolve-feedback + document (post-merge) — merge seams and graph-native
// optional-groups are skipped by the legacy step compiler.
expect(steps.length).toBeGreaterThanOrEqual(6);
expect(steps.some((s) => s.name === "Plan")).toBe(true);
expect(steps.filter((s) => s.skillName === "compound-engineering:ce-code-review")).toHaveLength(1);
expect(steps.some((s) => s.name === "CE Doc Review")).toBe(false);
expect(steps.some((s) => s.name === "Review" && !s.skillName)).toBe(false);
});
@@ -443,6 +445,8 @@ describe("built-in workflows", () => {
for (const [nodeId, slashCommand] of expectedPrompts) {
expect(String(byId(nodeId)?.config?.prompt ?? "")).toContain(slashCommand);
}
const docReviewTemplate = byId("ce-doc-review")?.config?.template as { nodes?: Array<{ config?: Record<string, unknown> }> } | undefined;
expect(String(docReviewTemplate?.nodes?.[0]?.config?.prompt ?? "")).toContain("/ce-doc-review");
expect(String(byId("merge")?.config?.prompt ?? "")).not.toContain("/ce-");
});
@@ -452,8 +456,11 @@ describe("built-in workflows", () => {
expect(byId("commit-pr")?.config?.skillName).toBe("compound-engineering:ce-commit-push-pr");
expect(byId("commit-pr")?.config?.toolMode).toBe("coding");
expect(byId("resolve-feedback")?.config?.skillName).toBe("compound-engineering:ce-resolve-pr-feedback");
expect(String(byId("commit-pr")?.config?.prompt ?? "")).toContain("When project autoMerge is off");
expect(String(byId("commit-pr")?.config?.prompt ?? "")).toContain("do not perform the Fusion board-state merge");
// KTD-6: the Fusion board-merge seam is preserved (CE prepares the PR, Fusion
// owns the merge transition).
// owns the merge transition). With autoMerge:false, the runtime seam no-ops
// into manual review; the CE PR skills are still ordered before this seam.
expect(byId("merge")?.config?.seam).toBe("merge");
// Ordering: commit-pr → resolve-feedback → merge → document.
const ids = ce.ir.nodes.map((n) => n.id);
@@ -468,6 +475,7 @@ describe("built-in workflows", () => {
const authoredNodeIds = ce.ir.nodes.filter((node) => node.id !== "start" && node.id !== "end").map((node) => node.id);
expect(authoredNodeIds).toEqual([
"plan",
"ce-doc-review",
"execute",
"code-review",
"commit-pr",
@@ -477,6 +485,21 @@ describe("built-in workflows", () => {
]);
expect(ce.ir.nodes.some((node) => node.config?.seam === "review")).toBe(false);
const docReview = byId("ce-doc-review");
expect(docReview?.kind).toBe("optional-group");
expect(docReview?.config?.name).toBe("CE Doc Review");
expect(docReview?.config?.defaultOn).toBe(false);
const docReviewTemplate = docReview?.config?.template as { nodes?: Array<{ id: string; kind: string; config?: Record<string, unknown> }> } | undefined;
expect(docReviewTemplate?.nodes?.[0]).toMatchObject({
id: "ce-doc-review-step",
kind: "prompt",
config: {
skillName: "compound-engineering:ce-doc-review",
toolMode: "coding",
gateMode: "advisory",
},
});
const codeReview = byId("code-review");
expect(codeReview?.kind).toBe("gate");
expect(codeReview?.config?.name).toBe("Code Review");
@@ -489,6 +512,8 @@ describe("built-in workflows", () => {
for (let i = 1; i < ce.ir.nodes.length; i += 1) {
expect(layout[ce.ir.nodes[i].id].x - layout[ce.ir.nodes[i - 1].id].x).toBe(170);
}
expect(ce.ir.edges.some((edge) => edge.from === "plan" && edge.to === "ce-doc-review")).toBe(true);
expect(ce.ir.edges.some((edge) => edge.from === "ce-doc-review" && edge.to === "execute")).toBe(true);
expect(ce.ir.edges.some((edge) => edge.from === "execute" && edge.to === "code-review")).toBe(true);
expect(ce.ir.edges.some((edge) => edge.from === "code-review" && edge.to === "commit-pr")).toBe(true);
});

View File

@@ -6,8 +6,8 @@ import { BUILTIN_STEPWISE_CODING_WORKFLOW_IR } from "./builtin-stepwise-coding-w
import { BUILTIN_WORKFLOW_SETTINGS } from "./builtin-workflow-settings.js";
import { builtinPromptConfig } from "./builtin-workflow-prompts.js";
import type { WorkflowDefinition } from "./workflow-definition-types.js";
import type { WorkflowIr } from "./workflow-ir-types.js";
import { parseWorkflowIr } from "./workflow-ir.js";
import type { WorkflowIr, WorkflowIrNode } from "./workflow-ir-types.js";
import { DEFAULT_WORKFLOW_COLUMN_IDS, parseWorkflowIr } from "./workflow-ir.js";
/** Prefix marking a workflow as a read-only built-in template. */
export const BUILTIN_WORKFLOW_ID_PREFIX = "builtin:";
@@ -51,6 +51,16 @@ interface BuiltinSpec {
nodes: Array<{ id: string; kind: WorkflowIr["nodes"][number]["kind"]; config?: Record<string, unknown> }>;
}
const V1_LINEAR_NODE_KINDS = new Set<WorkflowIrNode["kind"]>(["start", "prompt", "script", "gate", "end"]);
function defaultColumnForLinearNode(node: WorkflowIrNode): string {
const seam = node.config?.seam;
if (seam === "execute") return "in-progress";
if (seam === "review") return "in-review";
if (seam === "merge") return "in-review";
return "todo";
}
/** Build a linear IR (start → nodes… → end) with simple x-spaced layout. */
function linear(spec: BuiltinSpec): WorkflowDefinition {
const nodes: WorkflowIr["nodes"] = [
@@ -72,7 +82,16 @@ function linear(spec: BuiltinSpec): WorkflowDefinition {
nodes.forEach((node, i) => {
layout[node.id] = { x: 60 + i * 170, y: 160 };
});
const ir = parseWorkflowIr({ version: "v1", name: spec.name, nodes, edges });
const hasV2OnlyNode = nodes.some((node) => !V1_LINEAR_NODE_KINDS.has(node.kind));
const ir = hasV2OnlyNode
? parseWorkflowIr({
version: "v2",
name: spec.name,
columns: DEFAULT_WORKFLOW_COLUMN_IDS.map((id) => ({ id, name: id, traits: [] })),
nodes: nodes.map((node) => (node.column ? node : { ...node, column: defaultColumnForLinearNode(node) })),
edges,
})
: parseWorkflowIr({ version: "v1", name: spec.name, nodes, edges });
// Attach the moved-key settings catalog (U1/U3, R4) so every built-in workflow
// carries its declarations through the resolver path (resolveWorkflowIrById →
// resolveEffectiveSettings). v1 graphs upgrade to v2 on parse, so the parsed IR
@@ -195,7 +214,36 @@ export const BUILTIN_WORKFLOWS: WorkflowDefinition[] = [
// fn_spawn_agent (registered only for coding-mode steps). It is not
// meant to write — see the accepted write-capability posture (Risk-1).
toolMode: "coding",
prompt: "Run /ce-plan to produce a short implementation plan for this task before any code is written.",
prompt: "Run /ce-plan to produce the CE plan document artifact for this task under docs/plans/ before any code is written.",
},
},
{
/*
* FNXC:Workflows 2026-06-27-00:00:
* FN-7144 requires compound-engineering to expose the markdown-only ce-doc-review pass as an optional non-blocking stage after planning. This preserves the WHAT/HOW boundary: ce-plan writes the HOW plan doc, ce-doc-review checks coherence and scope alignment, and merge-blocking code review remains the later ce-code-review gate.
*/
id: "ce-doc-review",
kind: "optional-group",
config: {
name: "CE Doc Review",
defaultOn: false,
template: {
nodes: [
{
id: "ce-doc-review-step",
kind: "prompt",
config: {
name: "CE Doc Review",
executor: "skill",
skillName: "compound-engineering:ce-doc-review",
toolMode: "coding",
gateMode: "advisory",
prompt: "Run /ce-doc-review in headless advisory mode against the markdown CE plan document produced by /ce-plan (normally the latest docs/plans artifact). Apply only safe markdown fixes; if no markdown CE plan is available, report the skip as notes without blocking execution.",
},
},
],
edges: [],
},
},
},
{
@@ -246,8 +294,12 @@ export const BUILTIN_WORKFLOWS: WorkflowDefinition[] = [
// push / PR creation; it does NOT perform the board-state merge — that
// stays with Fusion's merge seam below (workflow-owned merge), so the
// two never race the same branch state.
/*
* FNXC:Workflows 2026-06-27-00:00:
* FN-7144 confirms the autoMerge-off CE route: ce-commit-push-pr and ce-resolve-pr-feedback prepare the human PR flow, while the later Fusion merge seam no-ops into manual review instead of forcing an unattended board merge.
*/
toolMode: "coding",
prompt: "Run /ce-commit-push-pr to commit the work in logical commits, push the branch, and open a pull request with a value-first description.",
prompt: "Run /ce-commit-push-pr to commit the work in logical commits, push the branch, and open a pull request with a value-first description. When project autoMerge is off, this PR is the human merge/review path; do not perform the Fusion board-state merge here.",
},
},
{

View File

@@ -312,7 +312,8 @@ export function compileWorkflowToSteps(ir: WorkflowIr): WorkflowStepInput[] {
if (seam === "merge") {
phase = "post-merge";
} else if (!seam && node.kind !== "start" && node.kind !== "end") {
} else if (!seam && node.kind !== "start" && node.kind !== "end" && node.kind !== "optional-group") {
// FNXC:WorkflowOptionalGroup 2026-06-27-00:00: Optional-group nodes are graph-native enable containers keyed by node id; compiling them into legacy WorkflowStepInput rows would create an empty duplicate step and bypass the per-task toggle semantics.
steps.push(nodeToStepInput(node, phase));
}

View File

@@ -148,6 +148,7 @@ describe("U1: normalized CE skill request-form resolution", () => {
mkdirSync(projectRootDir, { recursive: true });
mkdirSync(agentDir, { recursive: true });
materialize("ce-work");
materialize("ce-doc-review");
});
afterEach(() => {
@@ -179,6 +180,13 @@ describe("U1: normalized CE skill request-form resolution", () => {
expect(resolved.filter((n) => n === "ce-work")).toHaveLength(1);
});
it("optional ce-doc-review resolves from bare and namespaced workflow-step requests", () => {
expect(resolveFor(["ce-doc-review"])).toContain("ce-doc-review");
expect(resolveFor(["compound-engineering:ce-doc-review"])).toContain("ce-doc-review");
const resolved = resolveFor(["compound-engineering:ce-doc-review", "ce-doc-review"]);
expect(resolved.filter((n) => n === "ce-doc-review")).toHaveLength(1);
});
it("without the install dir on the discovery path, the request does NOT resolve", () => {
// Repoint discovery away from the install root: name alone is insufficient
// for every normalized request form.

View File

@@ -67,12 +67,13 @@ describe("interpreter merge seam", () => {
};
}
it("returns merged:false and does NOT force a merge when autoMerge is off", async () => {
it("returns merged:false/noOp and does NOT force a merge when autoMerge is off", async () => {
const onMerge = vi.fn();
const fakeEngine = fakeEngineWith({ autoEligible: false, onMerge });
const result = await (ProjectEngine.prototype as any).requestInterpreterMerge.call(fakeEngine, "FN-3");
expect(result.merged).toBe(false);
expect(result.noOp).toBe(true);
expect(onMerge).not.toHaveBeenCalled(); // the human "merge now" bypass is never invoked
});

View File

@@ -57,6 +57,7 @@ describe("compound engineering plugin manifest", () => {
"ce-brainstorm",
"ce-plan",
"ce-work",
"ce-doc-review",
"ce-code-review",
"ce-debug",
"ce-compound",

View File

@@ -58,6 +58,19 @@ export const COMPOUND_ENGINEERING_SKILLS: PluginSkillContribution[] = [
enabled: true,
triggerPatterns: ["do the work", "implement", "execute the plan", "finish this feature"],
},
{
/*
* FNXC:CompoundEngineering 2026-06-27-00:00:
* FN-7144 bundles ce-doc-review because ce-plan and ce-brainstorm invoke it as a named markdown document-review skill, and the built-in CE workflow now exposes it as an optional advisory stage after planning.
*/
skillId: "ce-doc-review",
name: "ce-doc-review",
description:
"Review CE markdown plan documents for coherence, feasibility, scope alignment, and safe markdown-only fixes.",
skillFiles: ["skills/ce-doc-review/SKILL.md"],
enabled: true,
triggerPatterns: ["doc review", "document review", "review the plan", "pressure-test the requirements"],
},
{
skillId: "ce-code-review",
name: "ce-code-review",

View File

@@ -0,0 +1,54 @@
---
name: ce-doc-review
description: "Review Compound Engineering markdown plan documents for coherence, feasibility, scope alignment, and safe markdown-only fixes. Use headless mode for automated plan handoff review."
argument-hint: "[mode:headless] <markdown-plan-path>"
---
# CE Document Review
Review a Compound Engineering markdown plan or requirements document. This skill is intentionally markdown-only: do not mutate HTML artifacts. If the target is HTML, missing, or not a CE plan/requirements document, report a non-blocking skip with notes.
## Modes
- `mode:headless <path>`: run an automated advisory pass, apply only safe markdown fixes, and return a concise review envelope.
- `<path>` without `mode:headless`: run the same review, but include actionable findings clearly enough for an interactive caller to decide what to apply.
## Review boundary
`ce-brainstorm` owns WHAT: product requirements, actors, flows, acceptance examples, and scope boundaries.
`ce-plan` owns HOW: technical decisions, implementation units, dependencies, risks, verification contract, and handoff posture.
`ce-doc-review` checks whether the document preserves that boundary and is usable by downstream `ce-work`, PR reviewers, and humans. Do not turn document review into code review; `ce-code-review` owns implementation diff review later in the pipeline.
## Procedure
1. Resolve the target path from the arguments. Prefer an explicit path. If no path is provided, inspect `docs/plans/` for the most recent markdown plan-like artifact and use that; if none exists, skip non-blockingly.
2. Confirm the target is markdown (`.md`). If it is not markdown, skip and explain that HTML document review is not supported yet.
3. Read the document enough to evaluate structure and consistency. For long documents, scan headings first, then read the Goal Capsule/Product Contract/Plan/Implementation Units/Verification/Definition of Done sections as present.
4. Check for:
- Product scope drift: requirements or Product Contract rewritten without a clear preservation note.
- HOW gaps: implementation units lacking files, dependencies, risks, or verification scenarios.
- Coherence gaps: contradictory decisions, stale handoff instructions, duplicated or inconsistent artifact readiness metadata.
- Feasibility gaps: sequencing that cannot work, missing prerequisite decisions, or verification that cannot prove the stated Definition of Done.
- Markdown-only hygiene that is safe to fix automatically: broken heading levels, obvious duplicate blank lines, malformed checklists, or typo-level wording that does not change meaning.
5. In headless mode, apply only `safe_auto` fixes directly to the markdown file. Do not apply changes that alter product scope, technical decisions, acceptance criteria, or verification obligations; report those as findings.
6. If findings remain, classify each as:
- `proposed_fix`: a safe but non-trivial improvement the user may accept.
- `decision`: a scope/technical judgment that needs human or planner choice.
- `fyi`: useful observation that does not need routing.
7. End with a concise summary and exactly one trailing JSON object on the final line.
## Output contract
Use this shape for the final line:
```json
{"verdict":"APPROVE|APPROVE_WITH_NOTES|REVISE","fixes_applied":0,"proposed_fixes_count":0,"decisions_count":0,"fyi_count":0,"notes":"short summary"}
```
- `APPROVE`: no actionable issues remain.
- `APPROVE_WITH_NOTES`: non-blocking observations or skipped review; this is the normal result for optional workflow use.
- `REVISE`: only for severe document issues that make downstream work unsafe or impossible. In the Fusion built-in workflow this skill is advisory/non-blocking, but the verdict still helps humans see severity.
Do not wrap the final JSON in markdown fences.