FN-7194: verify Compound Engineering workflow skills
Add regression coverage proving the built-in Compound Engineering workflow resolves and installs every referenced skill. - Collect skill-bearing nodes from the built-in Compound Engineering workflow, including optional-group template children.\n- Assert ce-plan, ce-work, ce-code-review, and follow-on workflow skills map to bundled SKILL.md files.\n- Verify bundled CE skill installation places each workflow-named skill on the discovery root used by sessions.\n\nFiles changed:\n .../__tests__/ce-workflow-skill-alignment.test.ts | 106 +++++++++++++++++++++\n 1 file changed, 106 insertions(+) Fusion-Task-Id: FN-7194 Fusion-Task-Lineage: 8461ebae-1484-4e77-a9ae-e18531e617b5 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { BUILTIN_WORKFLOWS, type WorkflowIrNode } from "@fusion/core";
|
||||
import { installBundledCeSkills, resolveBundledSkillsRoot } from "../skill-installation.js";
|
||||
|
||||
/*
|
||||
FNXC:CompoundEngineering 2026-06-28-08:58:
|
||||
FN-7145 requires workflow-named Compound Engineering skills to load as real resources, not only appear in prompt text. This regression test ties every `builtin:compound-engineering` skill node, including optional-group children, to a bundled and installable `SKILL.md` so ce-plan, ce-work, ce-code-review, and follow-on CE skills remain discoverable by the step session.
|
||||
*/
|
||||
|
||||
type CeWorkflowSkillNode = {
|
||||
nodePath: string;
|
||||
skillName: string;
|
||||
bareSkillId: string;
|
||||
};
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null;
|
||||
}
|
||||
|
||||
function bareSkillId(skillName: string): string {
|
||||
return skillName.includes(":") ? skillName.slice(skillName.lastIndexOf(":") + 1) : skillName;
|
||||
}
|
||||
|
||||
function templateNodes(node: WorkflowIrNode): WorkflowIrNode[] {
|
||||
const template = isRecord(node.config?.template) ? node.config.template : undefined;
|
||||
const nodes = isRecord(template) ? template.nodes : undefined;
|
||||
return Array.isArray(nodes) ? nodes.filter((candidate): candidate is WorkflowIrNode => isRecord(candidate) && typeof candidate.id === "string" && typeof candidate.kind === "string") : [];
|
||||
}
|
||||
|
||||
function collectSkillNodes(nodes: readonly WorkflowIrNode[], parentPath: readonly string[] = []): CeWorkflowSkillNode[] {
|
||||
return nodes.flatMap((node) => {
|
||||
const nodePathParts = [...parentPath, node.id];
|
||||
const skillName = typeof node.config?.skillName === "string" ? node.config.skillName.trim() : "";
|
||||
const direct = skillName
|
||||
? [
|
||||
{
|
||||
nodePath: nodePathParts.join(" > "),
|
||||
skillName,
|
||||
bareSkillId: bareSkillId(skillName),
|
||||
},
|
||||
]
|
||||
: [];
|
||||
return [...direct, ...collectSkillNodes(templateNodes(node), nodePathParts)];
|
||||
});
|
||||
}
|
||||
|
||||
function compoundEngineeringWorkflowSkillNodes(): CeWorkflowSkillNode[] {
|
||||
const workflow = BUILTIN_WORKFLOWS.find((candidate) => candidate.id === "builtin:compound-engineering");
|
||||
if (!workflow) throw new Error("builtin:compound-engineering workflow not found");
|
||||
return collectSkillNodes(workflow.ir.nodes);
|
||||
}
|
||||
|
||||
function frontmatterName(content: string): string | undefined {
|
||||
const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/m)?.[1] ?? "";
|
||||
const rawName = frontmatter.match(/^name:\s*(.+?)\s*$/m)?.[1];
|
||||
return rawName?.replace(/^['\"]|['\"]$/g, "");
|
||||
}
|
||||
|
||||
const workflowSkillNodes = compoundEngineeringWorkflowSkillNodes();
|
||||
const workflowBareSkillIds = workflowSkillNodes.map((node) => node.bareSkillId);
|
||||
|
||||
describe("built-in Compound Engineering workflow skill alignment", () => {
|
||||
const tmpTargets: string[] = [];
|
||||
|
||||
afterEach(() => {
|
||||
for (const target of tmpTargets) rmSync(target, { recursive: true, force: true });
|
||||
tmpTargets.length = 0;
|
||||
});
|
||||
|
||||
it("derives every skill-bearing node, including optional-group template children", () => {
|
||||
expect(workflowSkillNodes.map((node) => [node.nodePath, node.skillName])).toEqual([
|
||||
["plan", "compound-engineering:ce-plan"],
|
||||
["ce-doc-review > ce-doc-review-step", "compound-engineering:ce-doc-review"],
|
||||
["execute", "compound-engineering:ce-work"],
|
||||
["code-review", "compound-engineering:ce-code-review"],
|
||||
["commit-pr", "compound-engineering:ce-commit-push-pr"],
|
||||
["resolve-feedback", "compound-engineering:ce-resolve-pr-feedback"],
|
||||
["document", "compound-engineering:ce-compound"],
|
||||
]);
|
||||
expect(workflowBareSkillIds).toEqual(expect.arrayContaining(["ce-plan", "ce-work", "ce-code-review"]));
|
||||
});
|
||||
|
||||
it.each(workflowSkillNodes)("$nodePath resolves $bareSkillId to a bundled SKILL.md source", ({ bareSkillId }) => {
|
||||
const skillMd = join(resolveBundledSkillsRoot(), bareSkillId, "SKILL.md");
|
||||
expect(existsSync(skillMd)).toBe(true);
|
||||
expect(frontmatterName(readFileSync(skillMd, "utf-8"))).toBe(bareSkillId);
|
||||
});
|
||||
|
||||
it("installs every workflow-named skill onto the discovery root scanned by CE sessions", () => {
|
||||
const targetRoot = mkdtempSync(join(tmpdir(), "ce-workflow-skill-alignment-"));
|
||||
tmpTargets.push(targetRoot);
|
||||
|
||||
const { results } = installBundledCeSkills({ targetRoot });
|
||||
|
||||
for (const { bareSkillId } of workflowSkillNodes) {
|
||||
const result = results.find((candidate) => candidate.skillId === bareSkillId);
|
||||
expect(result).toMatchObject({ skillId: bareSkillId, outcome: "installed" });
|
||||
const installedSkillMd = join(targetRoot, bareSkillId, "SKILL.md");
|
||||
expect(existsSync(installedSkillMd)).toBe(true);
|
||||
expect(frontmatterName(readFileSync(installedSkillMd, "utf-8"))).toBe(bareSkillId);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user