Phase 3: add detect tests — slash, blocks, read match, trackReadPaths gate.
Cover detection helpers from SPEC §6.2 for regression safety. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,85 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import {
|
||||||
|
detectSlashSkill,
|
||||||
|
matchReadPathToSkill,
|
||||||
|
matchReadPathToSkillWhenEnabled,
|
||||||
|
parseSkillBlocksFromText,
|
||||||
|
type SkillPathMeta,
|
||||||
|
} from "../src/detect";
|
||||||
|
|
||||||
|
const sampleSkills: SkillPathMeta[] = [
|
||||||
|
{
|
||||||
|
name: "brave-search",
|
||||||
|
filePath: "/home/user/.pi/skills/brave-search/SKILL.md",
|
||||||
|
baseDir: "/home/user/.pi/skills/brave-search",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pdf-tools",
|
||||||
|
filePath: "/proj/.pi/skills/pdf-tools/SKILL.md",
|
||||||
|
baseDir: "/proj/.pi/skills/pdf-tools",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe("detectSlashSkill", () => {
|
||||||
|
it("detects slash command at start of text", () => {
|
||||||
|
expect(detectSlashSkill("/skill:brave-search")).toBe("brave-search");
|
||||||
|
expect(detectSlashSkill("/skill:pdf-tools extract pages")).toBe("pdf-tools");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns null for non-slash or invalid names", () => {
|
||||||
|
expect(detectSlashSkill("hello")).toBeNull();
|
||||||
|
expect(detectSlashSkill(" /skill:foo")).toBeNull();
|
||||||
|
expect(detectSlashSkill("/skill:Bad_Name")).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("parseSkillBlocksFromText", () => {
|
||||||
|
const block =
|
||||||
|
'<skill name="brave-search" location="/home/user/.pi/skills/brave-search/SKILL.md">\nbody\n</skill>';
|
||||||
|
|
||||||
|
it("parses one or more skill blocks", () => {
|
||||||
|
expect(parseSkillBlocksFromText(block)).toEqual([
|
||||||
|
{
|
||||||
|
name: "brave-search",
|
||||||
|
location: "/home/user/.pi/skills/brave-search/SKILL.md",
|
||||||
|
content: "body",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(parseSkillBlocksFromText(`${block}\n\n${block.replace("brave-search", "pdf-tools")}`)).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns empty array when no blocks", () => {
|
||||||
|
expect(parseSkillBlocksFromText("plain text")).toEqual([]);
|
||||||
|
expect(parseSkillBlocksFromText('<skill name="x"')).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("matchReadPathToSkill", () => {
|
||||||
|
it("matches absolute skill filePath", () => {
|
||||||
|
expect(matchReadPathToSkill("/home/user/.pi/skills/brave-search/SKILL.md", sampleSkills)?.name).toBe(
|
||||||
|
"brave-search",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("matches relative path via skill baseDir", () => {
|
||||||
|
expect(matchReadPathToSkill("SKILL.md", [sampleSkills[0]])?.name).toBe("brave-search");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns null for unrelated paths", () => {
|
||||||
|
expect(matchReadPathToSkill("/tmp/README.md", sampleSkills)).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("matchReadPathToSkillWhenEnabled", () => {
|
||||||
|
it("skips read detection when trackReadPaths is false", () => {
|
||||||
|
expect(
|
||||||
|
matchReadPathToSkillWhenEnabled("/home/user/.pi/skills/brave-search/SKILL.md", sampleSkills, false),
|
||||||
|
).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("delegates to matchReadPathToSkill when enabled", () => {
|
||||||
|
expect(
|
||||||
|
matchReadPathToSkillWhenEnabled("/home/user/.pi/skills/brave-search/SKILL.md", sampleSkills, true)?.name,
|
||||||
|
).toBe("brave-search");
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user