/// import { describe, it, expect, beforeEach, afterEach } from "bun:test" import { join } from "node:path" import { tmpdir } from "node:os" import { resolveSkillContent, resolveMultipleSkills, resolveSkillContentAsync, resolveMultipleSkillsAsync } from "./skill-content" let originalEnv: Record let testConfigDir: string beforeEach(() => { originalEnv = { CLAUDE_CONFIG_DIR: process.env.CLAUDE_CONFIG_DIR, OPENCODE_CONFIG_DIR: process.env.OPENCODE_CONFIG_DIR, } const unique = `skill-content-test-${Date.now()}-${Math.random().toString(16).slice(2)}` testConfigDir = join(tmpdir(), unique) process.env.CLAUDE_CONFIG_DIR = testConfigDir process.env.OPENCODE_CONFIG_DIR = testConfigDir }) afterEach(() => { for (const [key, value] of Object.entries(originalEnv)) { if (value !== undefined) { process.env[key] = value } else { delete process.env[key] } } }) describe("resolveSkillContent", () => { it("should return template for existing skill", () => { // given: builtin skills with 'frontend-ui-ux' skill // when: resolving content for 'frontend-ui-ux' const result = resolveSkillContent("frontend-ui-ux") // then: returns template string expect(result).not.toBeNull() expect(typeof result).toBe("string") expect(result).toContain("Role: Designer-Turned-Developer") }) it("should return template for 'playwright' skill", () => { // given: builtin skills with 'playwright' skill // when: resolving content for 'playwright' const result = resolveSkillContent("playwright") // then: returns template string expect(result).not.toBeNull() expect(typeof result).toBe("string") expect(result).toContain("Playwright Browser Automation") }) it("should return null for non-existent skill", () => { // given: builtin skills without 'nonexistent' skill // when: resolving content for 'nonexistent' const result = resolveSkillContent("nonexistent") // then: returns null expect(result).toBeNull() }) it("should return null for disabled skill", () => { // given: frontend-ui-ux skill disabled const options = { disabledSkills: new Set(["frontend-ui-ux"]) } // when: resolving content for disabled skill const result = resolveSkillContent("frontend-ui-ux", options) // then: returns null expect(result).toBeNull() }) }) describe("resolveMultipleSkills", () => { it("should resolve all existing skills", () => { // given: list of existing skill names const skillNames = ["frontend-ui-ux", "playwright"] // when: resolving multiple skills const result = resolveMultipleSkills(skillNames) // then: all skills resolved, none not found expect(result.resolved.size).toBe(2) expect(result.notFound).toEqual([]) expect(result.resolved.get("frontend-ui-ux")).toContain("Designer-Turned-Developer") expect(result.resolved.get("playwright")).toContain("Playwright Browser Automation") }) it("should handle partial success - some skills not found", () => { // given: list with existing and non-existing skills const skillNames = ["frontend-ui-ux", "nonexistent", "playwright", "another-missing"] // when: resolving multiple skills const result = resolveMultipleSkills(skillNames) // then: resolves existing skills, lists not found skills expect(result.resolved.size).toBe(2) expect(result.notFound).toEqual(["nonexistent", "another-missing"]) expect(result.resolved.get("frontend-ui-ux")).toContain("Designer-Turned-Developer") expect(result.resolved.get("playwright")).toContain("Playwright Browser Automation") }) it("should handle empty array", () => { // given: empty skill names list const skillNames: string[] = [] // when: resolving multiple skills const result = resolveMultipleSkills(skillNames) // then: returns empty resolved and notFound expect(result.resolved.size).toBe(0) expect(result.notFound).toEqual([]) }) it("should handle all skills not found", () => { // given: list of non-existing skills const skillNames = ["skill-one", "skill-two", "skill-three"] // when: resolving multiple skills const result = resolveMultipleSkills(skillNames) // then: no skills resolved, all in notFound expect(result.resolved.size).toBe(0) expect(result.notFound).toEqual(["skill-one", "skill-two", "skill-three"]) }) it("should treat disabled skills as not found", () => { // #given: frontend-ui-ux disabled, playwright not disabled const skillNames = ["frontend-ui-ux", "playwright"] const options = { disabledSkills: new Set(["frontend-ui-ux"]) } // #when: resolving multiple skills with disabled one const result = resolveMultipleSkills(skillNames, options) // #then: frontend-ui-ux in notFound, playwright resolved expect(result.resolved.size).toBe(1) expect(result.resolved.has("playwright")).toBe(true) expect(result.notFound).toEqual(["frontend-ui-ux"]) }) it("should preserve skill order in resolved map", () => { // given: list of skill names in specific order const skillNames = ["playwright", "frontend-ui-ux"] // when: resolving multiple skills const result = resolveMultipleSkills(skillNames) // then: map contains skills with expected keys expect(result.resolved.has("playwright")).toBe(true) expect(result.resolved.has("frontend-ui-ux")).toBe(true) expect(result.resolved.size).toBe(2) }) }) describe("resolveSkillContentAsync", () => { it("should return template for builtin skill async", async () => { // given: builtin skill 'frontend-ui-ux' // when: resolving content async const options = { disabledSkills: new Set(["frontend-ui-ux"]) } const result = await resolveSkillContentAsync("git-master", options) // then: returns template string expect(result).not.toBeNull() expect(typeof result).toBe("string") expect(result).toContain("Git Master Agent") }) it("should return null for disabled skill async", async () => { // given: frontend-ui-ux disabled const options = { disabledSkills: new Set(["frontend-ui-ux"]) } // when: resolving content async for disabled skill const result = await resolveSkillContentAsync("frontend-ui-ux", options) // then: returns null expect(result).toBeNull() }) }) describe("resolveMultipleSkillsAsync", () => { it("should resolve builtin skills async", async () => { // given: builtin skill names const skillNames = ["playwright", "git-master"] // when: resolving multiple skills async const result = await resolveMultipleSkillsAsync(skillNames) // then: all builtin skills resolved expect(result.resolved.size).toBe(2) expect(result.notFound).toEqual([]) expect(result.resolved.get("playwright")).toContain("Playwright Browser Automation") expect(result.resolved.get("git-master")).toContain("Git Master Agent") }) it("should handle partial success with non-existent skills async", async () => { // given: mix of existing and non-existing skills const skillNames = ["playwright", "nonexistent-skill-12345"] // when: resolving multiple skills async const result = await resolveMultipleSkillsAsync(skillNames) // then: existing skills resolved, non-existing in notFound expect(result.resolved.size).toBe(1) expect(result.notFound).toEqual(["nonexistent-skill-12345"]) expect(result.resolved.get("playwright")).toContain("Playwright Browser Automation") }) it("should treat disabled skills as not found async", async () => { // #given: frontend-ui-ux disabled const skillNames = ["frontend-ui-ux", "playwright"] const options = { disabledSkills: new Set(["frontend-ui-ux"]) } // #when: resolving multiple skills async with disabled one const result = await resolveMultipleSkillsAsync(skillNames, options) // #then: frontend-ui-ux in notFound, playwright resolved expect(result.resolved.size).toBe(1) expect(result.resolved.has("playwright")).toBe(true) expect(result.notFound).toEqual(["frontend-ui-ux"]) }) it("should NOT inject watermark when both options are disabled", async () => { // given: git-master skill with watermark disabled const skillNames = ["git-master"] const options = { gitMasterConfig: { commit_footer: false, include_co_authored_by: false, }, } // when: resolving with git-master config const result = await resolveMultipleSkillsAsync(skillNames, options) // then: no watermark section injected expect(result.resolved.size).toBe(1) expect(result.notFound).toEqual([]) const gitMasterContent = result.resolved.get("git-master") expect(gitMasterContent).not.toContain("Ultraworked with") expect(gitMasterContent).not.toContain("Co-authored-by: Sisyphus") }) it("should inject watermark when enabled (default)", async () => { // given: git-master skill with default config (watermark enabled) const skillNames = ["git-master"] const options = { gitMasterConfig: { commit_footer: true, include_co_authored_by: true, }, } // when: resolving with git-master config const result = await resolveMultipleSkillsAsync(skillNames, options) // then: watermark section is injected expect(result.resolved.size).toBe(1) const gitMasterContent = result.resolved.get("git-master") expect(gitMasterContent).toContain("Ultraworked with [Sisyphus]") expect(gitMasterContent).toContain("Co-authored-by: Sisyphus") }) it("should inject only footer when co-author is disabled", async () => { // given: git-master skill with only footer enabled const skillNames = ["git-master"] const options = { gitMasterConfig: { commit_footer: true, include_co_authored_by: false, }, } // when: resolving with git-master config const result = await resolveMultipleSkillsAsync(skillNames, options) // then: only footer is injected const gitMasterContent = result.resolved.get("git-master") expect(gitMasterContent).toContain("Ultraworked with [Sisyphus]") expect(gitMasterContent).not.toContain("Co-authored-by: Sisyphus") }) it("should inject watermark by default when no config provided", async () => { // given: git-master skill with NO config (default behavior) const skillNames = ["git-master"] // when: resolving without any gitMasterConfig const result = await resolveMultipleSkillsAsync(skillNames) // then: watermark is injected (default is ON) expect(result.resolved.size).toBe(1) const gitMasterContent = result.resolved.get("git-master") expect(gitMasterContent).toContain("Ultraworked with [Sisyphus]") expect(gitMasterContent).toContain("Co-authored-by: Sisyphus") }) it("should inject only co-author when footer is disabled", async () => { // given: git-master skill with only co-author enabled const skillNames = ["git-master"] const options = { gitMasterConfig: { commit_footer: false, include_co_authored_by: true, }, } // when: resolving with git-master config const result = await resolveMultipleSkillsAsync(skillNames, options) // then: only co-author is injected const gitMasterContent = result.resolved.get("git-master") expect(gitMasterContent).not.toContain("Ultraworked with [Sisyphus]") expect(gitMasterContent).toContain("Co-authored-by: Sisyphus") }) it("should inject custom string footer when commit_footer is a string", async () => { // given: git-master skill with custom string footer const skillNames = ["git-master"] const customFooter = "Custom footer from my team" const options = { gitMasterConfig: { commit_footer: customFooter, include_co_authored_by: false, }, } // when: resolving with custom footer config const result = await resolveMultipleSkillsAsync(skillNames, options) // then: custom footer is injected instead of default const gitMasterContent = result.resolved.get("git-master") expect(gitMasterContent).toContain(customFooter) expect(gitMasterContent).not.toContain("Ultraworked with [Sisyphus]") }) it("should use default Sisyphus footer when commit_footer is boolean true", async () => { // given: git-master skill with boolean true footer const skillNames = ["git-master"] const options = { gitMasterConfig: { commit_footer: true, include_co_authored_by: false, }, } // when: resolving with boolean true footer config const result = await resolveMultipleSkillsAsync(skillNames, options) // then: default Sisyphus footer is injected const gitMasterContent = result.resolved.get("git-master") expect(gitMasterContent).toContain("Ultraworked with [Sisyphus]") }) it("should handle empty array", async () => { // given: empty skill names const skillNames: string[] = [] // when: resolving multiple skills async const result = await resolveMultipleSkillsAsync(skillNames) // then: empty results expect(result.resolved.size).toBe(0) expect(result.notFound).toEqual([]) }) }) describe("resolveSkillContent with browserProvider", () => { it("should resolve agent-browser skill when browserProvider is 'agent-browser'", () => { // given: browserProvider set to agent-browser const options = { browserProvider: "agent-browser" as const } // when: resolving content for 'agent-browser' const result = resolveSkillContent("agent-browser", options) // then: returns agent-browser template expect(result).not.toBeNull() expect(result).toContain("agent-browser") }) it("should return null for agent-browser when browserProvider is default", () => { // given: no browserProvider (defaults to playwright) // when: resolving content for 'agent-browser' const result = resolveSkillContent("agent-browser") // then: returns null because agent-browser is not in default builtin skills expect(result).toBeNull() }) it("should return null for playwright when browserProvider is agent-browser", () => { // given: browserProvider set to agent-browser const options = { browserProvider: "agent-browser" as const } // when: resolving content for 'playwright' const result = resolveSkillContent("playwright", options) // then: returns null because playwright is replaced by agent-browser expect(result).toBeNull() }) }) describe("resolveMultipleSkills with browserProvider", () => { it("should resolve agent-browser when browserProvider is set", () => { // given: agent-browser and git-master requested with browserProvider const skillNames = ["agent-browser", "git-master"] const options = { browserProvider: "agent-browser" as const } // when: resolving multiple skills const result = resolveMultipleSkills(skillNames, options) // then: both resolved expect(result.resolved.has("agent-browser")).toBe(true) expect(result.resolved.has("git-master")).toBe(true) expect(result.notFound).toHaveLength(0) }) it("should not resolve agent-browser without browserProvider option", () => { // given: agent-browser requested without browserProvider const skillNames = ["agent-browser"] // when: resolving multiple skills const result = resolveMultipleSkills(skillNames) // then: agent-browser not found expect(result.resolved.has("agent-browser")).toBe(false) expect(result.notFound).toContain("agent-browser") }) }) describe("resolveMultipleSkillsAsync with browserProvider filtering", () => { it("should exclude discovered agent-browser when browserProvider is playwright", async () => { // given: playwright is the selected browserProvider (default) const skillNames = ["playwright", "git-master"] const options = { browserProvider: "playwright" as const } // when: resolving multiple skills const result = await resolveMultipleSkillsAsync(skillNames, options) // then: playwright resolved, agent-browser would be excluded if discovered expect(result.resolved.has("playwright")).toBe(true) expect(result.resolved.has("git-master")).toBe(true) expect(result.notFound).not.toContain("playwright") }) it("should exclude discovered playwright when browserProvider is agent-browser", async () => { // given: agent-browser is the selected browserProvider const skillNames = ["agent-browser", "git-master"] const options = { browserProvider: "agent-browser" as const } // when: resolving multiple skills const result = await resolveMultipleSkillsAsync(skillNames, options) // then: agent-browser resolved, playwright would be excluded if discovered expect(result.resolved.has("agent-browser")).toBe(true) expect(result.resolved.has("git-master")).toBe(true) expect(result.notFound).not.toContain("agent-browser") }) })