import { describe, it, expect } from "vitest";
import { parseSkillBlock, buildSkillBlock } from "../skill-block-parser.js";
describe("parseSkillBlock", () => {
it("matches a well-formed wrapper with args", () => {
const text =
`\nReferences are relative to /x.\n\nbody\n\n\nargs here`;
const block = parseSkillBlock(text);
expect(block).not.toBeNull();
expect(block!.name).toBe("foo");
expect(block!.location).toBe("/x/SKILL.md");
expect(block!.body).toBe("body");
expect(block!.args).toBe("args here");
expect(block!.condensed).toBe("/skill:foo args here");
});
it("strips the 'References are relative to .\\n\\n' preamble from body", () => {
const text =
`\nReferences are relative to /x.\n\nfirst body line\nsecond body line\n`;
const block = parseSkillBlock(text);
expect(block!.body).toBe("first body line\nsecond body line");
expect(block!.body.startsWith("References are relative")).toBe(false);
});
it("falls back to verbatim content when preamble is absent", () => {
// Hand-crafted wrapper without the standard preamble (defensive: pi may evolve).
const text = `\nbody only\n`;
expect(parseSkillBlock(text)!.body).toBe("body only");
});
it("matches a wrapper without args (no preamble)", () => {
const text = `\nbody\n`;
const block = parseSkillBlock(text);
expect(block).not.toBeNull();
expect(block!.args).toBeUndefined();
expect(block!.condensed).toBe("/skill:foo");
expect(block!.body).toBe("body");
});
it("preserves multi-line user args verbatim", () => {
const text =
`\nbody\n\n\nline1\nline2\nline3`;
const block = parseSkillBlock(text);
expect(block!.args).toBe("line1\nline2\nline3");
expect(block!.condensed).toBe("/skill:foo line1\nline2\nline3");
});
it("returns null for plain text", () => {
expect(parseSkillBlock("Hello, this is just text.")).toBeNull();
expect(parseSkillBlock("")).toBeNull();
expect(parseSkillBlock("/skill:foo args (unexpanded)")).toBeNull();
});
it("returns null for mid-document (anchor enforcement)", () => {
const text = `prefix\n\nbody\n`;
expect(parseSkillBlock(text)).toBeNull();
});
it("returns null for trailing whitespace after ", () => {
// Anchor end-of-string: a stray newline after with no args fails the optional-args group.
const text = `\nbody\n\n`;
expect(parseSkillBlock(text)).toBeNull();
});
it("does not terminate prematurely on body containing literal text", () => {
const text =
`\nReferences are relative to /x.\n\nDocumented like: …\nThat ended.\n`;
const block = parseSkillBlock(text);
expect(block).not.toBeNull();
expect(block!.name).toBe("real");
expect(block!.body).toContain('Documented like: …');
expect(block!.body).toContain("That ended.");
// preamble was stripped — body starts with the user-visible content
expect(block!.body.startsWith("Documented")).toBe(true);
});
it("handles an empty body (no preamble)", () => {
const text = `\n\n`;
const block = parseSkillBlock(text);
expect(block).not.toBeNull();
expect(block!.body).toBe("");
});
it("condensed form has a single space between name and args", () => {
const text = `\nb\n\n\nfoo`;
expect(parseSkillBlock(text)!.condensed).toBe("/skill:x foo");
});
it("condensed form has no trailing space when args is absent", () => {
const text = `\nb\n`;
expect(parseSkillBlock(text)!.condensed).toBe("/skill:x");
});
});
describe("buildSkillBlock", () => {
it("emits pi's exact wrapper format with args", () => {
const out = buildSkillBlock({
name: "openspec-explore",
filePath: "/x/openspec-explore/SKILL.md",
baseDir: "/x/openspec-explore",
body: "Enter explore mode.",
userArgs: "continue with X",
});
expect(out).toBe(
`\n` +
`References are relative to /x/openspec-explore.\n\n` +
`Enter explore mode.\n` +
`\n\n` +
`continue with X`,
);
});
it("emits wrapper without trailing args block when userArgs is absent", () => {
const out = buildSkillBlock({
name: "foo",
filePath: "/x/foo/SKILL.md",
baseDir: "/x/foo",
body: "body line",
});
expect(out.endsWith("")).toBe(true);
expect(out).not.toContain("\n\n");
});
});
describe("buildSkillBlock + parseSkillBlock round-trip", () => {
it("round-trips name / body / args (with args)", () => {
const built = buildSkillBlock({
name: "round",
filePath: "/p/SKILL.md",
baseDir: "/p",
body: "Some body\nwith multiple lines",
userArgs: "the args here",
});
const parsed = parseSkillBlock(built);
expect(parsed).not.toBeNull();
expect(parsed!.name).toBe("round");
expect(parsed!.location).toBe("/p/SKILL.md");
expect(parsed!.body).toBe("Some body\nwith multiple lines");
expect(parsed!.args).toBe("the args here");
});
it("round-trips with no args", () => {
const built = buildSkillBlock({
name: "noargs",
filePath: "/p/SKILL.md",
baseDir: "/p",
body: "Body only",
});
const parsed = parseSkillBlock(built);
expect(parsed!.args).toBeUndefined();
expect(parsed!.condensed).toBe("/skill:noargs");
});
});