From fe2d883cf7b88853187704bd40bf0db319c75a31 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 14 Feb 2026 16:31:08 +0000 Subject: [PATCH] perf(test): remove fs skill scanning from skill-commands tests --- src/auto-reply/skill-commands.test.ts | 123 +++++++++++--------------- 1 file changed, 54 insertions(+), 69 deletions(-) diff --git a/src/auto-reply/skill-commands.test.ts b/src/auto-reply/skill-commands.test.ts index cfec484f88..999ee9f84f 100644 --- a/src/auto-reply/skill-commands.test.ts +++ b/src/auto-reply/skill-commands.test.ts @@ -1,48 +1,65 @@ -import fsSync from "node:fs"; import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { beforeAll, describe, expect, it, vi } from "vitest"; -// Avoid importing/parsing the full skills loader + user home skills during unit tests. -vi.mock("@mariozechner/pi-coding-agent", () => ({ - formatSkillsForPrompt: () => "", - loadSkillsFromDir: ({ dir, source }: { dir: string; source: string }) => { - try { - const entries = fsSync.readdirSync(dir, { withFileTypes: true }); - const skills = entries - .filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")) - .map((entry) => { - const baseDir = path.join(dir, entry.name); - const filePath = path.join(baseDir, "SKILL.md"); - if (!fsSync.existsSync(filePath)) { - return null; - } - let raw = ""; - try { - raw = fsSync.readFileSync(filePath, "utf-8"); - } catch { - return null; - } - const nameMatch = raw.match(/^\s*name:\s*(.+)\s*$/m); - const descriptionMatch = raw.match(/^\s*description:\s*(.+)\s*$/m); - const name = (nameMatch?.[1] ?? entry.name).trim(); - const description = (descriptionMatch?.[1] ?? "").trim(); - return { name, description, source, filePath, baseDir }; - }) - .filter(Boolean); - return { skills }; - } catch { - return { skills: [] }; - } - }, -})); - // Avoid importing the full chat command registry for reserved-name calculation. vi.mock("./commands-registry.js", () => ({ listChatCommands: () => [], })); +vi.mock("../infra/skills-remote.js", () => ({ + getRemoteSkillEligibility: () => ({}), +})); + +// Avoid filesystem-driven skill scanning for these unit tests; we only need command naming semantics. +vi.mock("../agents/skills.js", () => { + function resolveUniqueName(base: string, used: Set): string { + let name = base; + let suffix = 2; + while (used.has(name.toLowerCase())) { + name = `${base}_${suffix}`; + suffix += 1; + } + used.add(name.toLowerCase()); + return name; + } + + function resolveWorkspaceSkills( + workspaceDir: string, + ): Array<{ skillName: string; description: string }> { + const dirName = path.basename(workspaceDir); + if (dirName === "main") { + return [{ skillName: "demo-skill", description: "Demo skill" }]; + } + if (dirName === "research") { + return [ + { skillName: "demo-skill", description: "Demo skill 2" }, + { skillName: "extra-skill", description: "Extra skill" }, + ]; + } + return []; + } + + return { + buildWorkspaceSkillCommandSpecs: ( + workspaceDir: string, + opts?: { reservedNames?: Set }, + ) => { + const used = new Set(); + for (const reserved of opts?.reservedNames ?? []) { + used.add(String(reserved).toLowerCase()); + } + + return resolveWorkspaceSkills(workspaceDir).map((entry) => { + const base = entry.skillName.replace(/-/g, "_"); + const name = resolveUniqueName(base, used); + return { name, skillName: entry.skillName, description: entry.description }; + }); + }, + }; +}); + let listSkillCommandsForAgents: typeof import("./skill-commands.js").listSkillCommandsForAgents; let resolveSkillCommandInvocation: typeof import("./skill-commands.js").resolveSkillCommandInvocation; @@ -51,22 +68,6 @@ beforeAll(async () => { await import("./skill-commands.js")); }); -async function writeSkill(params: { - workspaceDir: string; - dirName: string; - name: string; - description: string; -}) { - const { workspaceDir, dirName, name, description } = params; - const skillDir = path.join(workspaceDir, "skills", dirName); - await fs.mkdir(skillDir, { recursive: true }); - await fs.writeFile( - path.join(skillDir, "SKILL.md"), - `---\nname: ${name}\ndescription: ${description}\n---\n\n# ${name}\n`, - "utf-8", - ); -} - describe("resolveSkillCommandInvocation", () => { it("matches skill commands and parses args", () => { const invocation = resolveSkillCommandInvocation({ @@ -109,24 +110,8 @@ describe("listSkillCommandsForAgents", () => { const baseDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-skills-")); const mainWorkspace = path.join(baseDir, "main"); const researchWorkspace = path.join(baseDir, "research"); - await writeSkill({ - workspaceDir: mainWorkspace, - dirName: "demo", - name: "demo-skill", - description: "Demo skill", - }); - await writeSkill({ - workspaceDir: researchWorkspace, - dirName: "demo2", - name: "demo-skill", - description: "Demo skill 2", - }); - await writeSkill({ - workspaceDir: researchWorkspace, - dirName: "extra", - name: "extra-skill", - description: "Extra skill", - }); + await fs.mkdir(mainWorkspace, { recursive: true }); + await fs.mkdir(researchWorkspace, { recursive: true }); const commands = listSkillCommandsForAgents({ cfg: {