From 08ea320b54d750bcc251b03b7084ebc69977b669 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sat, 30 May 2026 19:12:05 +0900 Subject: [PATCH] test(builtin-skills): batch 29 (3 files) --- .../shared-skill-extraction.test.ts | 69 ++++++++++++++++ .../builtin-skills/skill-file-loader.test.ts | 81 +++++++++++++++++++ .../builtin-skills/skill-file-loader.ts | 38 +++++++++ 3 files changed, 188 insertions(+) create mode 100644 src/features/builtin-skills/shared-skill-extraction.test.ts create mode 100644 src/features/builtin-skills/skill-file-loader.test.ts create mode 100644 src/features/builtin-skills/skill-file-loader.ts diff --git a/src/features/builtin-skills/shared-skill-extraction.test.ts b/src/features/builtin-skills/shared-skill-extraction.test.ts new file mode 100644 index 000000000..f908bc558 --- /dev/null +++ b/src/features/builtin-skills/shared-skill-extraction.test.ts @@ -0,0 +1,69 @@ +/// + +import { describe, expect, test } from "bun:test" +import type { BuiltinSkill } from "./types" + +declare const Bun: { + file(path: string): { text(): Promise } +} + +const TARGET_SKILLS = ["remove-ai-slops", "review-work", "frontend-ui-ux", "init-deep"] as const + +type TargetSkill = (typeof TARGET_SKILLS)[number] + +type SkillSource = { + readonly name: TargetSkill + readonly description: string + readonly template: string +} + +function getRequiredMatch(source: string, pattern: RegExp, label: string): RegExpMatchArray { + const match = source.match(pattern) + if (!match) { + throw new Error(`missing ${label}`) + } + return match +} + +async function readSkillSource(name: TargetSkill): Promise { + let skill: BuiltinSkill + switch (name) { + case "remove-ai-slops": + skill = (await import("./skills/remove-ai-slops")).removeAiSlopsSkill + break + case "review-work": + skill = (await import("./skills/review-work")).reviewWorkSkill + break + case "frontend-ui-ux": + skill = (await import("./skills/frontend-ui-ux")).frontendUiUxSkill + break + case "init-deep": + skill = (await import("./skills/init-deep")).initDeepSkill + break + } + return { name, description: skill.description, template: skill.template } +} + +async function readSharedSkill(name: TargetSkill): Promise<{ readonly frontmatter: string; readonly body: string }> { + const content = await Bun.file(`packages/shared-skills/skills/${name}/SKILL.md`).text() + const match = getRequiredMatch(content, /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/, `${name} frontmatter`) + return { frontmatter: match[1], body: match[2] } +} + +describe("shared builtin skill extraction", () => { + test("#given extracted builtin skill markdown #when compared to TS sources #then bodies and metadata stay byte-equivalent", async () => { + // given + const sources = await Promise.all(TARGET_SKILLS.map(readSkillSource)) + + // when + const sharedSkills = await Promise.all(TARGET_SKILLS.map(readSharedSkill)) + + // then + for (const [index, source] of sources.entries()) { + const sharedSkill = sharedSkills[index] + expect(sharedSkill.frontmatter).toContain(`name: ${source.name}`) + expect(sharedSkill.frontmatter).toContain(`description: ${JSON.stringify(source.description)}`) + expect(sharedSkill.body).toBe(source.template) + } + }) +}) diff --git a/src/features/builtin-skills/skill-file-loader.test.ts b/src/features/builtin-skills/skill-file-loader.test.ts new file mode 100644 index 000000000..5fbf011d2 --- /dev/null +++ b/src/features/builtin-skills/skill-file-loader.test.ts @@ -0,0 +1,81 @@ +/// + +import { describe, expect, test } from "bun:test" +import { parseFrontmatter } from "../../shared/frontmatter" +import { createBuiltinSkills } from "./skills" +import { createSharedSkillTemplateLoader, loadSharedSkillTemplate } from "./skill-file-loader" + +declare const Bun: { + file(path: string): { text(): Promise } +} + +const SHARED_BUILTIN_SKILLS = ["remove-ai-slops", "review-work", "frontend-ui-ux", "init-deep"] as const + +describe("shared builtin skill file loader", () => { + test("#given extracted shared skill files #when builtin skills are created #then templates load from SKILL.md bodies", async () => { + // given + const skills = createBuiltinSkills() + + // when + const skillTemplates = new Map(skills.map((skill) => [skill.name, skill.template])) + + // then + for (const skillName of SHARED_BUILTIN_SKILLS) { + const content = await Bun.file(`packages/shared-skills/skills/${skillName}/SKILL.md`).text() + const { body } = parseFrontmatter(content) + expect(skillTemplates.get(skillName)).toBe(body) + expect(loadSharedSkillTemplate(skillName)).toBe(body) + } + }) + + test("#given repeated loads #when using the same loader #then it reads each shared skill file once", () => { + // given + const reads: string[] = [] + const loader = createSharedSkillTemplateLoader((path) => { + reads.push(path) + return "---\nname: cached\n---\nCached body" + }) + + // when + const first = loader("cached-skill") + const second = loader("cached-skill") + + // then + expect(first).toBe("Cached body") + expect(second).toBe("Cached body") + expect(reads).toHaveLength(1) + }) + + test("#given source and bundled layouts #when loading shared skill templates #then both package-relative paths resolve", () => { + // given + const expectedContent = "---\nname: layout\n---\nLayout body" + const createMissingFileError = (): Error => { + const error = new Error("ENOENT missing SKILL.md") + Object.defineProperty(error, "code", { value: "ENOENT" }) + return error + } + const readFile = (path: string): string => { + if (path.endsWith("/packages/shared-skills/skills/layout/SKILL.md")) { + return expectedContent + } + throw createMissingFileError() + } + + // when + const bundledLoader = createSharedSkillTemplateLoader(readFile, "/workspace/dist") + const sourceLoader = createSharedSkillTemplateLoader(readFile, "/workspace/src/features/builtin-skills") + + // then + expect(bundledLoader("layout")).toBe("Layout body") + expect(sourceLoader("layout")).toBe("Layout body") + }) + + test("#given a missing shared skill file #when loading the template #then the loader fails fast", () => { + // given + const loader = createSharedSkillTemplateLoader(() => { + throw new Error("ENOENT missing SKILL.md") + }) + + expect(() => loader("__missing__")).toThrow("ENOENT missing SKILL.md") + }) +}) diff --git a/src/features/builtin-skills/skill-file-loader.ts b/src/features/builtin-skills/skill-file-loader.ts new file mode 100644 index 000000000..64a6fc4c5 --- /dev/null +++ b/src/features/builtin-skills/skill-file-loader.ts @@ -0,0 +1,38 @@ +import { readFileSync } from "node:fs" +import { dirname, join } from "node:path" +import { fileURLToPath } from "node:url" +import { parseFrontmatter } from "../../shared/frontmatter" +type SkillFileReader = (path: string, encoding: "utf8") => string +const SHARED_SKILL_PATHS = [ + ["..", "packages", "shared-skills", "skills"], + ["..", "..", "..", "packages", "shared-skills", "skills"], +] as const +const moduleDir = typeof import.meta.dir === "string" ? import.meta.dir : dirname(fileURLToPath(import.meta.url)) +export function createSharedSkillTemplateLoader( + readFile: SkillFileReader = readFileSync, + baseDir: string = moduleDir, +): (skillName: string) => string { + const cache = new Map() + return (skillName) => { + const cached = cache.get(skillName) + if (cached !== undefined) return cached + let missingFileError: unknown + for (const segments of SHARED_SKILL_PATHS) { + try { + const { body } = parseFrontmatter(readFile(join(baseDir, ...segments, skillName, "SKILL.md"), "utf8")) + cache.set(skillName, body) + return body + } catch (error) { + if (!(error instanceof Error && Reflect.get(error, "code") === "ENOENT")) { + throw error + } + missingFileError ??= error + } + } + throw missingFileError ?? new Error(`missing shared skill template: ${skillName}`) + } +} +const loadSharedSkillTemplateFromDisk = createSharedSkillTemplateLoader() +export function loadSharedSkillTemplate(skillName: string): string { + return loadSharedSkillTemplateFromDisk(skillName) +}