diff --git a/packages/cli/src/commands/__tests__/computer-use-skill.test.ts b/packages/cli/src/commands/__tests__/computer-use-skill.test.ts index 0d47803a5e..a4265ce34d 100644 --- a/packages/cli/src/commands/__tests__/computer-use-skill.test.ts +++ b/packages/cli/src/commands/__tests__/computer-use-skill.test.ts @@ -1,12 +1,42 @@ -import { readFileSync } from "node:fs"; +import { existsSync, readdirSync, readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; -import { dirname, join } from "node:path"; +import { dirname, join, relative } from "node:path"; import { describe, expect, it } from "vitest"; import { parse } from "yaml"; import { COMPUTER_COMMAND_SURFACE } from "../computer/contract.js"; -const skill = readFileSync(join(dirname(fileURLToPath(import.meta.url)), "../../../skill/computer-use/SKILL.md"), "utf8"); + +const testDir = dirname(fileURLToPath(import.meta.url)); +const cliRoot = join(testDir, "../../.."); +const repoRoot = join(cliRoot, "../.."); +const computerUseSkillPath = join(cliRoot, "skill/computer-use/SKILL.md"); +const skillRoots = [join(cliRoot, "skill"), join(repoRoot, "plugins")]; + +function findShippedSkillMarkdown(root: string): string[] { + if (!existsSync(root)) return []; + return readdirSync(root, { withFileTypes: true }).flatMap((entry) => { + const path = join(root, entry.name); + if (entry.isDirectory()) return findShippedSkillMarkdown(path); + return entry.name === "SKILL.md" ? [path] : []; + }); +} + +/** + * FNXC:ComputerUseSkill 2026-08-13-23:35: + * The original stub-only ratchet let sibling shipped skills reintroduce static computer commands and + * flags that drift from the installed binary. Discover every bundled SKILL.md so the binary remains + * the only version-matched command guide, while preserving the three read-only fallback probes. + */ +function readShippedSkills(): Array<{ path: string; content: string }> { + return skillRoots + .flatMap(findShippedSkillMarkdown) + .filter((path) => path.includes("/skill/") || /\/plugins\/[^/]+\/src\/skills\//.test(path)) + .sort() + .map((path) => ({ path, content: readFileSync(path, "utf8") })); +} + describe("computer-use shipped skill", () => { it("has parseable frontmatter and keeps its anti-drift stub thin", () => { + const skill = readFileSync(computerUseSkillPath, "utf8"); const frontmatter = skill.match(/^---\n([\s\S]*?)\n---/)?.[1]; expect(parse(frontmatter ?? "")).toMatchObject({ name: "computer-use" }); expect(skill).toContain("fn skills get computer-use"); @@ -16,5 +46,19 @@ describe("computer-use shipped skill", () => { expect(COMPUTER_COMMAND_SURFACE[name as keyof typeof COMPUTER_COMMAND_SURFACE]).toBeDefined(); expect(tail.replace(/`$/, "")).toBe(" --json"); } + expect([...skill.matchAll(/--[a-z][a-z-]*/g)].map(([flag]) => flag)).toEqual(["--json", "--json", "--json"]); + }); + + it("keeps computer commands out of every other shipped skill", () => { + const skills = readShippedSkills(); + const skillPaths = skills.map(({ path }) => relative(repoRoot, path)); + expect(skillPaths).not.toHaveLength(0); + expect(skillPaths).toContain("packages/cli/skill/computer-use/SKILL.md"); + expect(skillPaths).toContain("packages/cli/skill/fusion/SKILL.md"); + + for (const { path, content } of skills) { + if (path === computerUseSkillPath) continue; + expect(content, relative(repoRoot, path)).not.toMatch(/fn computer [a-z-]+/); + } }); });