Files
vercel__vercel-plugin/hooks/skill-map-frontmatter.test.ts
John Lindquist 1d5f084780 Add bodyPath to skill manifest entries, golden fixture tests, fuzz tests for glob and YAML parsers, and vercel-config key routing improvements. Bump version to 0.3.0.
Update build-manifest.ts to include bodyPath in each skill manifest entry pointing to the SKILL.md file. Update all SKILL.md frontmatter across all skills to ensure consistent formatting. Add vercel-config.mjs improvements for key-aware skill routing.

Add tests/build-skill-map.test.ts for skill map construction. Add golden fixture JSON files (golden-bash-cap-collision, golden-bash-next-dev, golden-edit-middleware, golden-read-env-local, golden-read-vercel-json, golden-vercel-json-crons, golden-vercel-json-rewrites) for deterministic match engine validation. Add tests/fuzz-glob.test.ts and tests/fuzz-yaml.test.ts for property-based testing of the glob-to-regex and YAML parsers.

Update snapshot files for injection output. Update validate.ts and validate.test.ts with command convention checks. Update HOOKS.md documentation. Update pretooluse-skill-inject.mjs, skill-map-frontmatter.mjs, and vercel-config.mjs hook modules. Bump plugin.json version from 0.2.0 to 0.3.0.
2026-03-06 08:29:02 -07:00

105 lines
3.3 KiB
TypeScript

import { describe, expect, it } from "bun:test";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { buildSkillMap, extractFrontmatter, parseSkillFrontmatter, scanSkillsDir } from "./skill-map-frontmatter.mjs";
describe("skill-map frontmatter regression coverage", () => {
it("extractFrontmatter should strip BOM and split frontmatter/body", () => {
const markdown = "\uFEFF---\nname: ai-sdk\ndescription: AI SDK skill\n---\n# Markdown body\n";
const { yaml, body } = extractFrontmatter(markdown);
expect(yaml).toBe("name: ai-sdk\ndescription: AI SDK skill");
expect(body).toBe("# Markdown body\n");
});
it("parseSkillFrontmatter should parse nested metadata with block-list arrays", () => {
const yaml = [
"name: ai-sdk",
"description: AI SDK skill",
"metadata:",
" priority: 8",
" pathPatterns:",
" - app/api/chat/**",
" - src/lib/ai/**",
].join("\n");
const parsed = parseSkillFrontmatter(yaml);
expect(parsed.name).toBe("ai-sdk");
expect(parsed.metadata.priority).toBe(8);
expect(typeof parsed.metadata.priority).toBe("number");
expect(parsed.metadata.pathPatterns).toEqual(["app/api/chat/**", "src/lib/ai/**"]);
expect(parsed.metadata.pathPatterns?.every((item: string) => typeof item === "string")).toBe(true);
});
it("parseSkillFrontmatter should parse inline arrays and preserve regex-like strings", () => {
const yaml = [
"name: ai-sdk",
"metadata:",
" bashPatterns: ['^bun test$', '^pnpm lint$']",
].join("\n");
const parsed = parseSkillFrontmatter(yaml);
expect(parsed.metadata.bashPatterns).toEqual(["^bun test$", "^pnpm lint$"]);
});
it("buildSkillMap should accept deprecated filePattern with deprecation warning", () => {
const root = mkdtempSync(join(tmpdir(), "vercel-plugin-skillmap-"));
try {
const fooDir = join(root, "foo");
mkdirSync(fooDir, { recursive: true });
writeFileSync(
join(fooDir, "SKILL.md"),
[
"---",
"name: foo",
"metadata:",
" filePattern: app/api/chat/**",
"---",
"# Foo",
].join("\n"),
);
const result = buildSkillMap(root);
expect(result.skills.foo.pathPatterns).toEqual(["app/api/chat/**"]);
expect(
result.warnings.some((warning: string) => warning.includes("filePattern is deprecated")),
).toBe(true);
} finally {
rmSync(root, { recursive: true, force: true });
}
});
it("scanSkillsDir should report invalid YAML via diagnostics instead of throwing", () => {
const root = mkdtempSync(join(tmpdir(), "vercel-plugin-skillmap-"));
try {
const brokenDir = join(root, "broken");
mkdirSync(brokenDir, { recursive: true });
writeFileSync(
join(brokenDir, "SKILL.md"),
[
"---",
"name: broken",
"metadata:",
"\tpriority: 3",
"---",
"# Broken",
].join("\n"),
);
const result = scanSkillsDir(root);
expect(result.diagnostics.length).toBe(1);
expect(result.skills.length).toBe(0);
} finally {
rmSync(root, { recursive: true, force: true });
}
});
});