import { describe, it, expect } from "vitest"; import { splitFrontmatter, yamlStringField, yamlBoolField, leadingCommand, extNameFromPath, commandSkillName, skillAliases, expandHome, pad, fmtDate, fmtApproxTokens, fmtRelatedUsage, estimateSkillPromptChars, aggregate, section, computeModelToolStats, parseToolStatsArgs, parseWindowDays, renderModelToolSnapshot, withCanonicalName, addSkillDef, fallbackSkillDef, type UsageRecord, type Agg, type SkillDef, } from "./index.js"; // --------------------------------------------------------------------------- // splitFrontmatter // --------------------------------------------------------------------------- describe("splitFrontmatter", () => { it("returns empty frontmatter for text without frontmatter", () => { const { frontmatter, body } = splitFrontmatter("Hello world"); expect(frontmatter).toBe(""); expect(body).toBe("Hello world"); }); it("parses simple frontmatter", () => { const raw = "---\nname: foo\ndescription: bar\n---\nBody text"; const { frontmatter, body } = splitFrontmatter(raw); expect(frontmatter).toBe("name: foo\ndescription: bar"); expect(body).toBe("Body text"); }); it("handles CRLF line endings", () => { const raw = "---\r\nname: foo\r\n---\r\nBody"; const { frontmatter, body } = splitFrontmatter(raw); expect(frontmatter).toBe("name: foo"); expect(body).toBe("Body"); }); it("returns empty frontmatter if closing --- is missing", () => { const raw = "---\nname: foo\nBody text"; const { frontmatter } = splitFrontmatter(raw); expect(frontmatter).toBe(""); }); }); // --------------------------------------------------------------------------- // yamlStringField // --------------------------------------------------------------------------- describe("yamlStringField", () => { const fm = "name: my-skill\ndescription: A test skill\nempty:\nquoted: 'single'\ndquoted: \"double\""; it("finds a simple string value", () => { expect(yamlStringField(fm, "name")).toBe("my-skill"); }); it("finds description", () => { expect(yamlStringField(fm, "description")).toBe("A test skill"); }); it("returns undefined for missing key", () => { expect(yamlStringField(fm, "nonexistent")).toBeUndefined(); }); it("returns empty string for empty value", () => { expect(yamlStringField(fm, "empty")).toBe(""); }); it("strips single quotes", () => { expect(yamlStringField(fm, "quoted")).toBe("single"); }); it("strips double quotes", () => { expect(yamlStringField(fm, "dquoted")).toBe("double"); }); it("handles folded block scalar (>)", () => { const fm2 = "description: >\n line one\n line two\nname: after"; expect(yamlStringField(fm2, "description")).toBe("line one line two"); }); it("handles literal block scalar (|)", () => { const fm2 = "description: |\n line one\n line two\nname: after"; expect(yamlStringField(fm2, "description")).toBe("line one\nline two"); }); }); // --------------------------------------------------------------------------- // yamlBoolField // --------------------------------------------------------------------------- describe("yamlBoolField", () => { it("returns true for key: true", () => { expect( yamlBoolField( "disable-model-invocation: true", "disable-model-invocation", ), ).toBe(true); }); it("returns false for missing key", () => { expect(yamlBoolField("name: foo", "disable-model-invocation")).toBe(false); }); it("returns false for key: false", () => { expect( yamlBoolField( "disable-model-invocation: false", "disable-model-invocation", ), ).toBe(false); }); }); // --------------------------------------------------------------------------- // leadingCommand // --------------------------------------------------------------------------- describe("leadingCommand", () => { it("extracts leading slash command", () => { expect(leadingCommand("/tool-stats 30")).toBe("tool-stats"); }); it("returns lowercase", () => { expect(leadingCommand("/Tool-Stats")).toBe("tool-stats"); }); it("handles command with no args", () => { expect(leadingCommand("/reload")).toBe("reload"); }); it("returns undefined for non-slash input", () => { expect(leadingCommand("hello world")).toBeUndefined(); }); it("returns undefined for empty string", () => { expect(leadingCommand("")).toBeUndefined(); }); it("handles skill: prefix", () => { expect(leadingCommand("/skill:linear")).toBe("skill:linear"); }); }); // --------------------------------------------------------------------------- // extNameFromPath // --------------------------------------------------------------------------- describe("extNameFromPath", () => { it("extracts scoped package from node_modules", () => { expect( extNameFromPath( "/home/.pi/agent/npm/node_modules/pi-subagents/skills/foo/SKILL.md", ), ).toBe("pi-subagents"); }); it("extracts unscoped package from node_modules", () => { expect(extNameFromPath("/usr/lib/node_modules/my-ext/dist/index.js")).toBe( "my-ext", ); }); it("extracts from extensions dir", () => { expect( extNameFromPath("/Users/me/.pi/agent/extensions/my-cool-ext/index.ts"), ).toBe("my-cool-ext"); }); it("returns parent dir for SKILL.md (not 'SKILL')", () => { expect(extNameFromPath("/Users/me/.agents/skills/caveman/SKILL.md")).toBe( "caveman", ); }); it("returns filename for normal .ts files in generic dir", () => { expect(extNameFromPath("/Users/me/code/my-extension/src/main.ts")).toBe( "my-extension", ); }); it("returns undefined for undefined input", () => { expect(extNameFromPath(undefined)).toBeUndefined(); }); }); // --------------------------------------------------------------------------- // commandSkillName // --------------------------------------------------------------------------- describe("commandSkillName", () => { it("strips skill: prefix", () => { expect(commandSkillName("skill:linear")).toBe("linear"); }); it("strips case-insensitive prefix", () => { expect(commandSkillName("Skill:My-Skill")).toBe("My-Skill"); }); it("returns name as-is if no prefix", () => { expect(commandSkillName("foo")).toBe("foo"); }); }); // --------------------------------------------------------------------------- // skillAliases // --------------------------------------------------------------------------- describe("skillAliases", () => { it("includes name and directory basename for SKILL.md", () => { const aliases = skillAliases("caveman", "/skills/caveman/SKILL.md"); expect(aliases).toContain("caveman"); expect(aliases).toContain("caveman"); // dir basename from SKILL.md path }); it("includes extra aliases", () => { const aliases = skillAliases("foo", "/skills/bar/SKILL.md", ["baz"]); expect(aliases).toContain("foo"); expect(aliases).toContain("baz"); expect(aliases).toContain("bar"); }); it("deduplicates", () => { const aliases = skillAliases("caveman", "/skills/caveman/SKILL.md", [ "caveman", ]); const cavemanCount = aliases.filter((a) => a === "caveman").length; expect(cavemanCount).toBe(1); }); }); // --------------------------------------------------------------------------- // expandHome // --------------------------------------------------------------------------- describe("expandHome", () => { it("expands ~ to homedir", () => { expect(expandHome("~")).toBe(process.env.HOME || "/Users"); }); it("expands ~/path", () => { expect(expandHome("~/foo/bar")).toMatch(/\/foo\/bar$/); }); it("leaves absolute paths unchanged", () => { expect(expandHome("/tmp/foo")).toBe("/tmp/foo"); }); it("leaves relative paths unchanged (resolved elsewhere)", () => { expect(expandHome("foo/bar")).toBe("foo/bar"); }); }); // --------------------------------------------------------------------------- // formatting helpers // --------------------------------------------------------------------------- describe("pad", () => { it("pads short strings", () => { expect(pad("hi", 5)).toBe("hi "); }); it("does not truncate long strings", () => { expect(pad("hello world", 5)).toBe("hello world"); }); }); describe("fmtDate", () => { it("formats a timestamp", () => { expect(fmtDate(new Date("2026-01-15").getTime())).toBe("2026-01-15"); }); it("returns 'never' for 0", () => { expect(fmtDate(0)).toBe("never"); }); }); describe("fmtApproxTokens", () => { it("estimates ~chars/4 tokens", () => { expect(fmtApproxTokens(400)).toBe("~100"); }); it("returns at least 1", () => { expect(fmtApproxTokens(1)).toBe("~1"); }); it("returns ? for 0", () => { expect(fmtApproxTokens(0)).toBe("?"); }); }); describe("fmtRelatedUsage", () => { it("shows ext:N for positive count", () => { expect(fmtRelatedUsage(5)).toBe("ext:5"); }); it("shows - for zero", () => { expect(fmtRelatedUsage(0)).toBe("-"); }); }); // --------------------------------------------------------------------------- // estimateSkillPromptChars // --------------------------------------------------------------------------- describe("estimateSkillPromptChars", () => { it("returns positive length for valid input", () => { const chars = estimateSkillPromptChars( "my-skill", "A skill", "/path/to/SKILL.md", ); expect(chars).toBeGreaterThan(0); }); }); // --------------------------------------------------------------------------- // aggregate // --------------------------------------------------------------------------- describe("aggregate", () => { it("aggregates by key", () => { const records: UsageRecord[] = [ { ts: 1000, kind: "tool", name: "read" }, { ts: 2000, kind: "tool", name: "read" }, { ts: 3000, kind: "tool", name: "bash" }, ]; const agg = aggregate(records, (r) => r.kind === "tool" ? r.name : undefined, ); expect(agg.get("read")).toEqual({ count: 2, last: 2000 }); expect(agg.get("bash")).toEqual({ count: 1, last: 3000 }); }); it("skips records where keyOf returns undefined", () => { const records: UsageRecord[] = [ { ts: 1000, kind: "skill", name: "foo" }, { ts: 2000, kind: "tool", name: "bar" }, ]; const agg = aggregate(records, (r) => r.kind === "tool" ? r.name : undefined, ); expect(agg.has("foo")).toBe(false); expect(agg.get("bar")).toEqual({ count: 1, last: 2000 }); }); it("returns empty map for empty records", () => { const agg = aggregate([], () => undefined); expect(agg.size).toBe(0); }); }); // --------------------------------------------------------------------------- // model/tool stats // --------------------------------------------------------------------------- describe("computeModelToolStats", () => { it("correlates attempts and results by toolCallId", () => { const records: UsageRecord[] = [ { ts: 1000, kind: "tool", name: "edit", provider: "anthropic", model: "claude-sonnet", toolCallId: "a", session: "s1", }, { ts: 1001, kind: "tool-result", name: "edit", provider: "anthropic", model: "claude-sonnet", toolCallId: "a", session: "s1", success: false, }, { ts: 1002, kind: "tool", name: "read", provider: "anthropic", model: "claude-sonnet", toolCallId: "b", session: "s1", }, ]; const stats = computeModelToolStats(records); const model = stats.models[0]!; expect(model.calls).toBe(2); expect(model.ok).toBe(0); expect(model.fail).toBe(1); expect(model.unk).toBe(1); expect(model.toolOkCounts.get("read") ?? 0).toBe(0); expect(model.toolFailCounts.get("edit")).toBe(1); expect(model.toolUnknownCounts.get("read")).toBe(1); }); it("windows by tool-call timestamp, not result timestamp", () => { const records: UsageRecord[] = [ { ts: 900, kind: "tool", name: "bash", provider: "openai", model: "gpt", toolCallId: "old", }, { ts: 1100, kind: "tool-result", name: "bash", provider: "openai", model: "gpt", toolCallId: "old", success: true, }, { ts: 1200, kind: "tool", name: "read", provider: "openai", model: "gpt", toolCallId: "new", }, ]; const stats = computeModelToolStats(records, 1000); expect(stats.models[0]!.calls).toBe(1); expect(stats.tools.map((t) => t.name)).toEqual(["read"]); }); }); describe("tool-stats args", () => { it("parses overview days and all-time zero", () => { expect(parseToolStatsArgs("7")).toEqual({ view: "overview", days: 7 }); expect(parseToolStatsArgs("0")).toEqual({ view: "overview", days: 0 }); }); it("parses model-tool aliases", () => { expect(parseToolStatsArgs("model-tools 14")).toEqual({ view: "model-tools", days: 14, }); expect(parseToolStatsArgs("mt 0")).toEqual({ view: "model-tools", days: 0, }); }); it("falls back to 30 days", () => { expect(parseWindowDays(undefined)).toBe(30); expect(parseToolStatsArgs("models nope")).toEqual({ view: "model-tools", days: 30, }); }); }); describe("renderModelToolSnapshot", () => { it("shows detail command hint", () => { const stats = computeModelToolStats([ { ts: 1000, kind: "tool", name: "read", provider: "anthropic", model: "claude", toolCallId: "a", }, ]); expect(renderModelToolSnapshot(stats, true).join("\n")).toContain( "/tool-stats model-tools [days]", ); }); }); // --------------------------------------------------------------------------- // section // --------------------------------------------------------------------------- describe("section", () => { it("renders header and rows sorted by count desc", () => { const agg = new Map([ ["foo", { count: 2, last: 2000 }], ["bar", { count: 5, last: 3000 }], ]); const defined = new Set(["foo", "bar"]); const lines = section("TOOLS", agg, defined); expect(lines[0]).toBe("--- TOOLS ---"); // bar (5) should come before foo (2) const barLine = lines.find((l) => l.includes("bar")); const fooLine = lines.find((l) => l.includes("foo")); expect(barLine).toBeDefined(); expect(fooLine).toBeDefined(); expect(lines.indexOf(barLine!)).toBeLessThan(lines.indexOf(fooLine!)); }); it("shows prune flag for 0-count defined items", () => { const agg = new Map(); const defined = new Set(["unused-skill"]); const lines = section("SKILLS", agg, defined); const unused = lines.find((l) => l.includes("unused-skill")); expect(unused).toContain("prune?"); }); it("shows (none) for empty section", () => { const lines = section("EMPTY", new Map(), new Set()); expect(lines).toContain(" (none)"); }); }); // --------------------------------------------------------------------------- // withCanonicalName // --------------------------------------------------------------------------- describe("withCanonicalName", () => { it("sets canonical name and preserves aliases", () => { const def: SkillDef = { name: "old", filePath: "/skills/old/SKILL.md", visible: true, promptChars: 100, aliases: ["old", "SKILL"], }; const result = withCanonicalName(def, "new"); expect(result.name).toBe("new"); expect(result.aliases).toContain("new"); expect(result.aliases).toContain("old"); expect(result.aliases).toContain("SKILL"); expect(result.promptChars).toBe(100); }); }); // --------------------------------------------------------------------------- // addSkillDef // --------------------------------------------------------------------------- describe("addSkillDef", () => { it("merges aliases from duplicate definitions", () => { const map = new Map(); addSkillDef(map, fallbackSkillDef("foo", "/skills/foo/SKILL.md")); addSkillDef(map, { name: "foo", filePath: "/skills/foo/SKILL.md", visible: true, promptChars: 200, aliases: ["bar"], }); const def = map.get("foo")!; expect(def.aliases).toContain("bar"); expect(def.aliases).toContain("foo"); expect(def.promptChars).toBe(200); }); it("takes max promptChars on merge", () => { const map = new Map(); addSkillDef(map, { name: "a", filePath: "", visible: true, promptChars: 100, aliases: ["a"], }); addSkillDef(map, { name: "a", filePath: "", visible: false, promptChars: 300, aliases: ["a"], }); expect(map.get("a")!.promptChars).toBe(300); }); it("visible=true wins on merge", () => { const map = new Map(); addSkillDef(map, { name: "a", filePath: "", visible: false, promptChars: 0, aliases: ["a"], }); addSkillDef(map, { name: "a", filePath: "", visible: true, promptChars: 0, aliases: ["a"], }); expect(map.get("a")!.visible).toBe(true); }); });