From 2dcead4c8fe4ab935e119c518eb74294a33a09cc Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Thu, 13 Aug 2026 17:01:39 -0700 Subject: [PATCH] FN-9035: prevent static computer commands in shipped skills Keep bundled computer-use guidance limited to runtime discovery so command syntax cannot drift. - Scan all shipped skill markdown files, including plugin skills - Reject static `fn computer` commands outside the computer-use discovery stub - Assert the discovery stub exposes only its supported JSON probes Files changed: packages/cli/src/commands/__tests__/computer-use-skill.test.ts | 50 ++++++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) Fusion-Task-Id: FN-9035 Fusion-Task-Lineage: 5049fd29-1b3b-4968-b09e-1bb7be9d52ff Co-authored-by: Fusion (runfusion.ai) --- .../__tests__/computer-use-skill.test.ts | 50 +++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) 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-]+/); + } }); });