import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; import type { ResolvedSkillInfo, ToolInfo } from "@selesai/code"; import { buildSkillCatalog, buildToolCatalog, firstSentence, route, toolSummary, type CatalogEntry, } from "./catalog.ts"; import { capabilityInstruction, describePick, invocationContract, schemaShape } from "./index.ts"; const tool = (overrides: Partial): ToolInfo => ({ name: "grep_app_search", description: "Search public GitHub code through grep.app. Returns one page.", parameters: {} as never, sourceInfo: { path: "/ext/index.ts", source: "local", scope: "user", origin: "top-level" }, ...overrides, }) as ToolInfo; const skill = (overrides: Partial): ResolvedSkillInfo => ({ name: "research", description: "Investigate a question against high-trust primary sources.", filePath: "/skills/research/SKILL.md", scope: "user", disableModelInvocation: false, ...overrides, }); describe("catalog metadata", () => { it("uses explicit discovery summary over the description first sentence", () => { const t = tool({ discovery: { summary: "Search public GitHub code" } }); expect(toolSummary(t)).toBe("Search public GitHub code"); }); it("falls back to the first sentence of the description", () => { expect(toolSummary(tool({}))).toBe("Search public GitHub code through grep.app."); }); it("firstSentence handles short text without punctuation", () => { expect(firstSentence("no punctuation here")).toBe("no punctuation here"); }); it("builds a tool catalog excluding built-ins and gateway tools", () => { const tools = [ tool({ name: "read" }), tool({ name: "capability_catalog" }), tool({ name: "grep_app_search", discovery: { aliases: ["github-search"], category: "web" } }), ]; const entries = buildToolCatalog(tools, new Set(["capability_catalog"])); expect(entries.map((e) => e.name)).toEqual(["grep_app_search"]); expect(entries[0]!.aliases).toEqual(["github-search"]); expect(entries[0]!.category).toBe("web"); expect(entries[0]!.kind).toBe("tool"); }); it("builds a skill catalog excluding disable-model-invocation skills", () => { const entries = buildSkillCatalog([ skill({ name: "research" }), skill({ name: "secret", disableModelInvocation: true }), ]); expect(entries.map((e) => e.name)).toEqual(["research"]); expect(entries[0]!.kind).toBe("skill"); }); it("lists a skill resolved from several sources once, keeping the first", () => { const entries = buildSkillCatalog([ skill({ name: "to-prd", description: "Turn the conversation into a PRD.", category: "docs" }), skill({ name: "research" }), skill({ name: "to-prd", description: "Older copy." }), ]); expect(entries.map((e) => e.name)).toEqual(["to-prd", "research"]); expect(entries[0]!.category).toBe("docs"); }); it("falls back to the file frontmatter description when the resolved description is empty", () => { const dir = mkdtempSync(join(tmpdir(), "gw-skill-")); const filePath = join(dir, "SKILL.md"); writeFileSync( filePath, "---\nname: brandkit\ndescription: Premium brand-kit image generation skill for creating high-end brand-guidelines boards.\n---\nbody\n", ); try { const entries = buildSkillCatalog([skill({ name: "brandkit", description: "", filePath })]); expect(entries[0]!.summary).toBe("Premium brand-kit image generation skill for creating high-end brand-guidelines boards."); } finally { rmSync(dir, { recursive: true, force: true }); } }); }); describe("deterministic routing", () => { const catalog: CatalogEntry[] = [ { name: "grep_app_search", kind: "tool", summary: "Search public GitHub code", aliases: ["github-search"], category: "web", eligible: true }, { name: "grep_app_fetch", kind: "tool", summary: "Fetch a public GitHub file", aliases: [], category: "web", eligible: true }, { name: "research", kind: "skill", summary: "Investigate a question against primary sources", aliases: [], category: "research", eligible: true }, ]; it("auto-activates a unique high-confidence tool match", () => { const result = route("search github code with grep_app_search", catalog); expect(result.action).toBe("activate"); expect(result.entry?.name).toBe("grep_app_search"); }); it("matches by alias", () => { const result = route("use github-search", catalog); expect(result.action).toBe("activate"); expect(result.entry?.name).toBe("grep_app_search"); }); it("auto-activates a uniquely identifiable one-character tool typo", () => { const result = route("activate inercom", [ ...catalog, { name: "intercom", kind: "tool", summary: "Coordinate with other local sessions", aliases: [], category: "coordination", eligible: true }, ]); expect(result.action).toBe("activate"); expect(result.entry?.name).toBe("intercom"); }); it("recommends a unique high-confidence skill without auto-loading", () => { const result = route("do research on this topic", catalog); expect(result.action).toBe("recommend"); expect(result.entry?.name).toBe("research"); }); it("returns a hint for ambiguous matches", () => { const result = route("grep app", catalog); expect(result.action).toBe("hint"); expect(result.candidates?.length).toBeGreaterThan(1); }); it("returns none for unrelated prompts", () => { expect(route("refactor the auth module", catalog).action).toBe("none"); expect(route("", catalog).action).toBe("none"); expect(route(" ", catalog).action).toBe("none"); }); it("is deterministic for the same input", () => { const a = route("search github code", catalog); const b = route("search github code", catalog); expect(a).toEqual(b); }); }); describe("invocation contract", () => { const toolWith = (parameters: unknown, overrides: Partial = {}) => tool({ name: "graft_find_code", description: "Find code by question.", parameters: parameters as never, ...overrides }); it("lists each parameter with its shape and required flag", () => { const contract = invocationContract( toolWith({ type: "object", properties: { question: { type: "string", description: "What you want to understand, in plain words." }, limit: { type: "number" }, }, required: ["question"], }), ); expect(contract).toContain("graft_find_code — Find code by question."); expect(contract).toContain("- question (string, required): What you want to understand, in plain words."); expect(contract).toContain("- limit (number, optional)"); }); it("names nested shapes instead of expanding them", () => { expect(schemaShape({ type: "array", items: { type: "object" } })).toBe("array"); expect(schemaShape({ anyOf: [{ type: "string" }, { type: "number" }] })).toBe("one of several"); expect(schemaShape({ enum: ["a", "b"] })).toBe('"a" | "b"'); expect(schemaShape({})).toBe("value"); }); it("says so when a tool takes no parameters", () => { expect(invocationContract(toolWith({ type: "object", properties: {} }))).toContain("Parameters: none"); }); it("clips long text and never carries the schema itself", () => { const contract = invocationContract( toolWith({ type: "object", properties: { question: { type: "string", description: "x".repeat(500) } }, required: ["question"], }), ); expect(contract).toContain("…"); expect(contract).not.toContain('"type"'); expect(contract.split("\n").every((line) => line.length <= 200)).toBe(true); }); }); describe("capability instruction", () => { it("mentions the job route only when Jev can answer it", () => { expect(capabilityInstruction(true)).toContain("capability_discover with `job`"); const offline = capabilityInstruction(false); expect(offline).not.toContain("`job`"); expect(offline).toContain("capability_catalog"); expect(offline).toContain("exact `name`"); }); }); describe("describePick", () => { const side = (pick: Parameters[1]["pick"], offered = 3, dropped = 0) => ({ pick, offered, dropped }); it("reads a hesitant Jev as no confident pick rather than a failure", () => { expect(describePick("Skill", side({ selected: false, reason: "low-confidence" }), undefined, "x")).toBe( "Skill: no confident pick.", ); expect(describePick("Tool", side({ selected: false, reason: "unquantified" }), undefined, "x")).toBe( "Tool: no confident pick.", ); }); it("keeps real failures, picks, and an honest none distinct", () => { expect(describePick("Tool", side({ selected: false, reason: "timeout" }), undefined, "x")).toBe("Tool: no decision (timeout)."); expect(describePick("Tool", side({ selected: true, name: "t", confidence: 0.9 }), "t", "x")).toBe( 'Tool: Jev picked "t" (confidence 0.90).', ); expect(describePick("Skill", side({ selected: false, reason: "none" }, 40, 9), undefined, "no procedure")).toContain( "among 40 of 49 offered", ); }); });