import { beforeEach, describe, expect, mock, test } from "bun:test"; // --------------------------------------------------------------------------- // Mock state // --------------------------------------------------------------------------- const mockResolveSkillStates = mock( (): Array<{ summary: { id: string; displayName: string; description: string; emoji?: string; source: | "bundled" | "managed" | "workspace" | "extra" | "catalog" | "plugin"; directoryPath: string; owner?: { kind: "skill" | "mcp" | "plugin" | "workspace"; id: string }; }; state: "enabled" | "disabled"; }> => [], ); const mockReadInstallMeta = mock( ( _dir: string, ): { origin: string; slug?: string; sourceRepo?: string; installedAt?: string; author?: "assistant" | "user"; sourceConversationId?: string; } | null => null, ); const mockFetchSkillAudits = mock( async ( _source: string, _skillSlugs: string[], ): Promise< Record< string, Record< string, { risk: string; alerts?: number; score?: number; analyzedAt: string } > > > => ({}), ); // --------------------------------------------------------------------------- // Mock modules — before importing module under test // --------------------------------------------------------------------------- mock.module("../config/skills.js", () => ({ loadSkillCatalog: () => [], })); mock.module("../config/skill-state.js", () => ({ resolveSkillStates: mockResolveSkillStates, skillFlagKey: () => null, })); mock.module("../config/assistant-feature-flags.js", () => ({ isAssistantFeatureFlagEnabled: () => true, })); mock.module("../skills/install-meta.js", () => ({ readInstallMeta: mockReadInstallMeta, })); mock.module("../skills/skillssh-registry.js", () => ({ searchSkillsRegistry: mock(async () => []), fetchSkillAudits: mockFetchSkillAudits, // resolveSkillSource uses the real implementation — its pure parsing // logic is reliable and doesn't need mocking. The handler calls it to // derive owner/repo/skillSlug from the slug string before calling // fetchSkillAudits, so as long as fetchSkillAudits receives the right // args (asserted below), we know resolveSkillSource did its job. resolveSkillSource: (source: string) => { const parts = source.split("/"); return { owner: parts[0], repo: parts[1], skillSlug: parts[2] }; }, installExternalSkill: mock(async () => {}), })); mock.module("../skills/clawhub.js", () => ({ clawhubSearch: mock(async () => ({ skills: [] })), clawhubCheckUpdates: mock(async () => []), clawhubInspect: mock(async () => ({})), clawhubInstall: mock(async () => ({ success: true })), clawhubUpdate: mock(async () => ({ success: true })), })); mock.module("../skills/catalog-search.js", () => ({ filterByQuery: () => [], })); mock.module("../providers/provider-send-message.js", () => ({ createTimeout: () => ({ signal: AbortSignal.timeout(1000), cleanup: () => {}, }), extractText: () => "", getConfiguredProvider: async () => null, userMessage: () => ({}), })); mock.module("../runtime/routes/workspace-utils.js", () => ({ isTextMimeType: () => true, })); mock.module("../skills/catalog-cache.js", () => ({ getCatalog: async () => [], })); mock.module("../skills/catalog-files.js", () => ({ catalogSkillToSlim: () => ({}), createVellumCatalogProvider: () => ({ canHandle: () => false, listFiles: async () => null, toSlimSkill: async () => null, readFileContent: async () => null, }), hasHiddenOrSkippedSegment: () => false, sanitizeRelativePath: (p: string) => p, SKIP_DIRS: new Set(), })); mock.module("../skills/skillssh-files.js", () => ({ createSkillsShProvider: () => ({ canHandle: () => false, listFiles: async () => null, toSlimSkill: async () => null, readFileContent: async () => null, }), })); mock.module("../skills/clawhub-files.js", () => ({ createClawhubProvider: () => ({ canHandle: () => false, listFiles: async () => null, toSlimSkill: async () => null, readFileContent: async () => null, }), })); mock.module("../skills/catalog-install.js", () => ({ installSkillLocally: async () => {}, })); mock.module("../skills/managed-store.js", () => ({ createManagedSkill: () => ({ created: true }), deleteManagedSkill: () => ({ deleted: true }), validateManagedSkillId: () => null, })); mock.module("../plugins/defaults/memory/graph/capability-seed.js", () => ({ deleteSkillCapabilityNode: () => {}, seedSkillGraphNodes: () => {}, seedUninstalledCatalogSkillMemories: async () => {}, })); mock.module("../util/platform.js", () => ({ getWorkspaceSkillsDir: () => "/tmp/test-skills", })); mock.module("../daemon/handlers/shared.js", () => ({ CONFIG_RELOAD_DEBOUNCE_MS: 100, ensureSkillEntry: () => ({}), log: { info: () => {}, warn: () => {}, error: () => {}, debug: () => {}, }, })); // Import after mocking import { getSkill } from "../daemon/handlers/skills.js"; // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- describe("getSkill — skillssh audit enrichment", () => { beforeEach(() => { mockResolveSkillStates.mockReset(); mockReadInstallMeta.mockReset(); mockFetchSkillAudits.mockReset(); // Default: no skills resolved mockResolveSkillStates.mockReturnValue([]); mockReadInstallMeta.mockReturnValue(null); mockFetchSkillAudits.mockResolvedValue({}); }); test("enriches skillssh skill detail with audit data on success", async () => { // Set up a skillssh installed skill mockResolveSkillStates.mockReturnValue([ { summary: { id: "acme/tools/lint", displayName: "Lint", description: "A lint skill", source: "extra" as const, directoryPath: "/tmp/test-skills/lint", }, state: "enabled" as const, }, ]); // Return skillssh origin from install-meta mockReadInstallMeta.mockReturnValue({ origin: "skillssh", slug: "acme/tools/lint", sourceRepo: "acme/tools", }); // fetchSkillAudits returns audit data keyed by skill slug mockFetchSkillAudits.mockResolvedValue({ lint: { ath: { risk: "safe", score: 100, analyzedAt: "2025-06-01T00:00:00Z" }, socket: { risk: "low", alerts: 1, analyzedAt: "2025-06-01T00:00:00Z", }, }, }); const result = await getSkill("acme/tools/lint"); // Should succeed expect("skill" in result).toBe(true); if (!("skill" in result)) { throw new Error("Expected skill response"); } const detail = result.skill; expect(detail.origin).toBe("skillssh"); expect(detail.id).toBe("acme/tools/lint"); expect(detail.name).toBe("Lint"); // Verify audit data is attached if (detail.origin === "skillssh") { expect(detail.audit).toBeDefined(); expect(detail.audit!.ath).toEqual({ risk: "safe", score: 100, analyzedAt: "2025-06-01T00:00:00Z", }); expect(detail.audit!.socket).toEqual({ risk: "low", alerts: 1, analyzedAt: "2025-06-01T00:00:00Z", }); } // Verify fetchSkillAudits was called with the correct source repo and slug expect(mockFetchSkillAudits).toHaveBeenCalledTimes(1); expect(mockFetchSkillAudits).toHaveBeenCalledWith("acme/tools", ["lint"]); }); test("returns detail without audit data when fetchSkillAudits throws", async () => { // Set up a skillssh installed skill mockResolveSkillStates.mockReturnValue([ { summary: { id: "org/repo/my-tool", displayName: "My Tool", description: "A community tool", source: "extra" as const, directoryPath: "/tmp/test-skills/my-tool", }, state: "enabled" as const, }, ]); // Return skillssh origin from install-meta mockReadInstallMeta.mockReturnValue({ origin: "skillssh", slug: "org/repo/my-tool", sourceRepo: "org/repo", }); // fetchSkillAudits throws an error mockFetchSkillAudits.mockRejectedValue( new Error("audit service unavailable"), ); const result = await getSkill("org/repo/my-tool"); // Should still succeed — audit failure is non-fatal expect("skill" in result).toBe(true); if (!("skill" in result)) { throw new Error("Expected skill response"); } const detail = result.skill; expect(detail.origin).toBe("skillssh"); expect(detail.id).toBe("org/repo/my-tool"); expect(detail.name).toBe("My Tool"); // Audit data should not be present if (detail.origin === "skillssh") { expect(detail.audit).toBeUndefined(); } // fetchSkillAudits was still called (just failed) expect(mockFetchSkillAudits).toHaveBeenCalledTimes(1); }); test("attributes a plugin-resident skill to its owning plugin on the detail response", async () => { // GIVEN a skill shipped inside an installed plugin, resolved with a // `plugin` source and a plugin owner descriptor mockResolveSkillStates.mockReturnValue([ { summary: { id: "caveman", displayName: "Caveman", description: "Ultra-compressed communication mode", source: "plugin" as const, directoryPath: "/tmp/test-skills/plugins/caveman/skills/caveman", owner: { kind: "plugin", id: "caveman" }, }, state: "enabled" as const, }, ]); // WHEN we fetch its detail const result = await getSkill("caveman"); // THEN it resolves to the vellum origin (plugin skills are mapped to // bundled/vellum for older clients) while preserving plugin attribution // on the owner descriptor expect("skill" in result).toBe(true); if (!("skill" in result)) { throw new Error("Expected skill response"); } const detail = result.skill; expect(detail.origin).toBe("vellum"); expect(detail.id).toBe("caveman"); // AND the owner identifies the plugin so detail consumers can attribute it if (detail.origin === "vellum") { expect(detail.owner).toEqual({ kind: "plugin", id: "caveman" }); } }); }); describe("getSkill — assistant-memory conversation lineage", () => { beforeEach(() => { mockResolveSkillStates.mockReset(); mockReadInstallMeta.mockReset(); mockFetchSkillAudits.mockReset(); mockResolveSkillStates.mockReturnValue([ { summary: { id: "triage-inbox", displayName: "Triage Inbox", description: "Distilled inbox triage procedure", source: "managed" as const, directoryPath: "/tmp/test-skills/triage-inbox", }, state: "enabled" as const, }, ]); mockFetchSkillAudits.mockResolvedValue({}); }); test("includes sourceConversationId when install-meta records it", async () => { // GIVEN a retrospective-authored skill whose install-meta carries lineage mockReadInstallMeta.mockReturnValue({ origin: "custom", author: "assistant", sourceConversationId: "conv-123", installedAt: "2026-01-01T00:00:00Z", }); const result = await getSkill("triage-inbox"); expect("skill" in result).toBe(true); if (!("skill" in result)) { throw new Error("Expected skill response"); } const detail = result.skill; expect(detail.origin).toBe("assistant-memory"); if (detail.origin === "assistant-memory") { expect(detail.sourceConversationId).toBe("conv-123"); } }); test("omits sourceConversationId when install-meta has none", async () => { // GIVEN a retrospective-authored skill without recorded lineage mockReadInstallMeta.mockReturnValue({ origin: "custom", author: "assistant", installedAt: "2026-01-01T00:00:00Z", }); const result = await getSkill("triage-inbox"); expect("skill" in result).toBe(true); if (!("skill" in result)) { throw new Error("Expected skill response"); } const detail = result.skill; expect(detail.origin).toBe("assistant-memory"); expect("sourceConversationId" in detail).toBe(false); }); test("never exposes lineage on non-assistant-memory origins", async () => { // GIVEN a user-installed custom skill that (unexpectedly) carries a // sourceConversationId in install-meta mockReadInstallMeta.mockReturnValue({ origin: "custom", sourceConversationId: "conv-123", installedAt: "2026-01-01T00:00:00Z", }); const result = await getSkill("triage-inbox"); expect("skill" in result).toBe(true); if (!("skill" in result)) { throw new Error("Expected skill response"); } const detail = result.skill; expect(detail.origin).toBe("custom"); expect("sourceConversationId" in detail).toBe(false); }); });