diff --git a/.changeset/fn-7159-canonical-html-checklist.md b/.changeset/fn-7159-canonical-html-checklist.md new file mode 100644 index 0000000000..345b8ed391 --- /dev/null +++ b/.changeset/fn-7159-canonical-html-checklist.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": minor +--- + +summary: CE HTML docs can define and safely repair malformed checklists in ce-doc-review. +category: feature +dev: Adds parse5-backed canonical checklist repair with validation, atomic writes, and report-only fallback. diff --git a/docs/workflow-editor.md b/docs/workflow-editor.md index 2fdc28d68b..d013ab6216 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 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 only when safety is proven, 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: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: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 5801dbbe71..a7a0c11a02 100644 --- a/docs/workflow-steps.md +++ b/docs/workflow-steps.md @@ -149,9 +149,12 @@ The default built-in catalog entry `builtin:coding` is backed by the canonical ` -`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 plans without blocking merge. Markdown plans retain safe autofix and Append-to-Open-Questions behavior, while HTML plans support DOM-safe in-place mutation only after parse/anchor/visible-text/protected-region validation with atomic, idempotent writes; any safety failure falls back to report-only with no write. 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. +`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 plans without blocking merge. Markdown plans retain safe autofix and Append-to-Open-Questions behavior, while HTML plans support DOM-safe in-place mutation only after parse/anchor/visible-text/protected-region validation with atomic, idempotent writes. HTML checklist hygiene additionally supports canonical checklist repair when the helper proves the stable CE HTML checklist representation deterministically; any safety failure falls back to report-only with no write. 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. diff --git a/plugins/fusion-plugin-compound-engineering/src/__tests__/ce-doc-review-html-mode.test.ts b/plugins/fusion-plugin-compound-engineering/src/__tests__/ce-doc-review-html-mode.test.ts index 910ec1e1a0..ff0c32d253 100644 --- a/plugins/fusion-plugin-compound-engineering/src/__tests__/ce-doc-review-html-mode.test.ts +++ b/plugins/fusion-plugin-compound-engineering/src/__tests__/ce-doc-review-html-mode.test.ts @@ -44,8 +44,10 @@ describe("ce-doc-review HTML report-only mode", () => { expect(docReviewSkill).toMatch(/same document-quality and persona-lens checks/i); expect(docReviewSkill).toMatch(/fall back to report-only \(`fixes_applied = 0`\)/i); expect(docReviewSkill).toMatch(/Append-to-Open-Questions write-back that inserts markdown/i); - expect(docReviewSkill).toMatch(/Malformed-checklist HTML repair remains report-only/i); - expect(docReviewSkill).toMatch(/successful DOM-safe helper fixes may increment `fixes_applied` only for the allowlisted operations/i); + expect(docReviewSkill).toMatch(/malformed checklist repair to the canonical HTML checklist representation/i); + expect(docReviewSkill).toMatch(/including malformed-checklist repair when the helper can prove the canonical checklist form deterministically/i); + expect(docReviewSkill).toMatch(/Ambiguous or non-canonical checklist-like HTML that cannot be proven safe stays report-only/i); + expect(docReviewSkill).toMatch(/successful DOM-safe helper fixes may increment `fixes_applied` only for the allowlisted operations, including canonical checklist repair/i); }); it("routes ce-plan HTML artifacts through report-only review instead of a skipped envelope", () => { diff --git a/plugins/fusion-plugin-compound-engineering/src/artifacts/__tests__/html-mutation.test.ts b/plugins/fusion-plugin-compound-engineering/src/artifacts/__tests__/html-mutation.test.ts index 2034639df3..a8e0acf0d0 100644 --- a/plugins/fusion-plugin-compound-engineering/src/artifacts/__tests__/html-mutation.test.ts +++ b/plugins/fusion-plugin-compound-engineering/src/artifacts/__tests__/html-mutation.test.ts @@ -160,7 +160,7 @@ describe("HTML mutation helper", () => { expect(result).toMatchObject({ ok: false, fixesApplied: 0 }); }); - it("refuses unsafe fragments and unsupported checklist repair without writing", () => { + it("refuses unsafe fragments and unknown checklist operations without writing", () => { root = makeRepo(); const file = planPath(root); writeFileSync(file, BASE_HTML); @@ -213,6 +213,139 @@ describe("HTML mutation helper", () => { expect(readdirSync(root).filter((entry) => entry.includes("html-mutation"))).toEqual([]); }); + it("leaves empty and populated canonical checklists unchanged", () => { + const canonical = 'Plan

Implementation Units

'; + + const result = applyHtmlMutations(canonical, [{ type: "checklist-repair" }]); + + expect(result).toEqual({ ok: true, html: canonical, fixesApplied: 0 }); + }); + + it("repairs raw markdown checklist text to the canonical mixed-state shape", () => { + const html = 'Plan

Definition of Done

\n- [ ] Ship alpha\n- [x] Verify beta\n- [X] Archive gamma\n
'; + + const result = applyHtmlMutations(html, [{ type: "checklist-repair" }]); + + expect(result).toMatchObject({ ok: true, fixesApplied: 1 }); + if (!result.ok) throw new Error(result.reason); + expect(result.html).toContain('

Definition of Done

'); + expect(result.html).not.toContain("- [ ] Ship alpha"); + + const second = applyHtmlMutations(result.html, [{ type: "checklist-repair" }]); + expect(second).toMatchObject({ ok: true, fixesApplied: 0, html: result.html }); + }); + + it("repairs marker lists without adding, dropping, reordering, or rewording items", () => { + const html = 'Plan

Verification Contract

  1. [x] First & ready
  2. [ ] Second pending
'; + + const result = applyHtmlMutations(html, [{ type: "checklist-repair" }]); + + expect(result).toMatchObject({ ok: true, fixesApplied: 1 }); + if (!result.ok) throw new Error(result.reason); + expect(result.html).toContain('
  • [x] First & ready
  • [ ] Second pending
  • '); + expect(result.html.indexOf("First & ready")).toBeLessThan(result.html.indexOf("Second pending")); + }); + + it("repairs a single checked item to the canonical shape", () => { + const html = 'Plan

    Definition of Done

    '; + + const result = applyHtmlMutations(html, [{ type: "checklist-repair" }]); + + expect(result).toMatchObject({ ok: true, fixesApplied: 1 }); + if (!result.ok) throw new Error(result.reason); + expect(result.html).toContain(''); + }); + + it("repairs all-unchecked and all-checked checklists without changing state", () => { + const unchecked = 'Plan

    Verification Contract

    '; + const checked = 'Plan

    Verification Contract

    '; + + const uncheckedResult = applyHtmlMutations(unchecked, [{ type: "checklist-repair" }]); + const checkedResult = applyHtmlMutations(checked, [{ type: "checklist-repair" }]); + + expect(uncheckedResult).toMatchObject({ ok: true, fixesApplied: 1 }); + expect(checkedResult).toMatchObject({ ok: true, fixesApplied: 1 }); + if (!uncheckedResult.ok) throw new Error(uncheckedResult.reason); + if (!checkedResult.ok) throw new Error(checkedResult.reason); + expect(uncheckedResult.html.match(/ce-checklist-state">\[ \]/g)).toHaveLength(2); + expect(uncheckedResult.html).not.toContain('ce-checklist-state">[x]'); + expect(checkedResult.html.match(/ce-checklist-state">\[x\]/g)).toHaveLength(2); + expect(checkedResult.html).not.toContain('ce-checklist-state">[ ]'); + }); + + it("repairs input-checkbox lists while preserving checked state and label text", () => { + const html = 'Plan

    Implementation Units

    '; + + const result = applyHtmlMutations(html, [{ type: "checklist-repair" }]); + + expect(result).toMatchObject({ ok: true, fixesApplied: 1 }); + if (!result.ok) throw new Error(result.reason); + expect(result.html).toContain(''); + expect(result.html).not.toContain(' { + const html = 'Plan

    Implementation Units

    - [ ] code checklist stays raw
    '; + + const result = applyHtmlMutations(html, [{ type: "checklist-repair" }]); + + expect(result).toMatchObject({ ok: true, fixesApplied: 1 }); + if (!result.ok) throw new Error(result.reason); + expect(result.html).toContain(''); + expect(result.html).toContain(''); + expect(result.html).toContain('
    - [ ] code checklist stays raw
    '); + expect(result.html).toContain('Render <script> as text'); + expect(result.html).toContain('Render <style> as text'); + expect(result.html).not.toContain('Render