From d4137f1abcfa1dd75025f75f275bf739ef1ed5a9 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sat, 27 Jun 2026 17:40:42 -0700 Subject: [PATCH] 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) --- .../fn-7144-compound-engineering-audit.md | 7 +++ docs/workflow-editor.md | 2 +- docs/workflow-steps.md | 4 +- .../src/__tests__/builtin-workflows.test.ts | 29 ++++++++- packages/core/src/builtin-workflows.ts | 62 +++++++++++++++++-- packages/core/src/workflow-compiler.ts | 3 +- .../ce-workflow-step-conventions.test.ts | 8 +++ .../__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(-) create mode 100644 .changeset/fn-7144-compound-engineering-audit.md create mode 100644 plugins/fusion-plugin-compound-engineering/src/skills/ce-doc-review/SKILL.md diff --git a/.changeset/fn-7144-compound-engineering-audit.md b/.changeset/fn-7144-compound-engineering-audit.md new file mode 100644 index 0000000000..a3027069cf --- /dev/null +++ b/.changeset/fn-7144-compound-engineering-audit.md @@ -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. diff --git a/docs/workflow-editor.md b/docs/workflow-editor.md index b8e2448d80..93a6320232 100644 --- a/docs/workflow-editor.md +++ b/docs/workflow-editor.md @@ -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. diff --git a/docs/workflow-steps.md b/docs/workflow-steps.md index f992a5bb8f..aaf602c0c0 100644 --- a/docs/workflow-steps.md +++ b/docs/workflow-steps.md @@ -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 diff --git a/packages/core/src/__tests__/builtin-workflows.test.ts b/packages/core/src/__tests__/builtin-workflows.test.ts index 203b4c540b..4838a986c8 100644 --- a/packages/core/src/__tests__/builtin-workflows.test.ts +++ b/packages/core/src/__tests__/builtin-workflows.test.ts @@ -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 }> } | 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 }> } | 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); }); diff --git a/packages/core/src/builtin-workflows.ts b/packages/core/src/builtin-workflows.ts index d0e2844c49..0c4bff00f0 100644 --- a/packages/core/src/builtin-workflows.ts +++ b/packages/core/src/builtin-workflows.ts @@ -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 }>; } +const V1_LINEAR_NODE_KINDS = new Set(["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.", }, }, { diff --git a/packages/core/src/workflow-compiler.ts b/packages/core/src/workflow-compiler.ts index bb0762ee8b..7e1b991dd3 100644 --- a/packages/core/src/workflow-compiler.ts +++ b/packages/core/src/workflow-compiler.ts @@ -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)); } diff --git a/packages/engine/src/__tests__/ce-workflow-step-conventions.test.ts b/packages/engine/src/__tests__/ce-workflow-step-conventions.test.ts index ed46a5b2e5..00e2e1c688 100644 --- a/packages/engine/src/__tests__/ce-workflow-step-conventions.test.ts +++ b/packages/engine/src/__tests__/ce-workflow-step-conventions.test.ts @@ -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. diff --git a/packages/engine/src/__tests__/interpreter-merge-seam.test.ts b/packages/engine/src/__tests__/interpreter-merge-seam.test.ts index 8f84833fd0..1ecfcb6211 100644 --- a/packages/engine/src/__tests__/interpreter-merge-seam.test.ts +++ b/packages/engine/src/__tests__/interpreter-merge-seam.test.ts @@ -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 }); diff --git a/plugins/fusion-plugin-compound-engineering/src/__tests__/manifest.test.ts b/plugins/fusion-plugin-compound-engineering/src/__tests__/manifest.test.ts index f4bc64c6b5..8c021bef77 100644 --- a/plugins/fusion-plugin-compound-engineering/src/__tests__/manifest.test.ts +++ b/plugins/fusion-plugin-compound-engineering/src/__tests__/manifest.test.ts @@ -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", diff --git a/plugins/fusion-plugin-compound-engineering/src/skills.ts b/plugins/fusion-plugin-compound-engineering/src/skills.ts index 1382c58d4f..b037649717 100644 --- a/plugins/fusion-plugin-compound-engineering/src/skills.ts +++ b/plugins/fusion-plugin-compound-engineering/src/skills.ts @@ -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", diff --git a/plugins/fusion-plugin-compound-engineering/src/skills/ce-doc-review/SKILL.md b/plugins/fusion-plugin-compound-engineering/src/skills/ce-doc-review/SKILL.md new file mode 100644 index 0000000000..cade8fb2c2 --- /dev/null +++ b/plugins/fusion-plugin-compound-engineering/src/skills/ce-doc-review/SKILL.md @@ -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] " +--- + +# 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 `: run an automated advisory pass, apply only safe markdown fixes, and return a concise review envelope. +- `` 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.