diff --git a/src/features/opencode-skill-loader/project-skill-discovery.test.ts b/src/features/opencode-skill-loader/project-skill-discovery.test.ts new file mode 100644 index 000000000..0d34da8ea --- /dev/null +++ b/src/features/opencode-skill-loader/project-skill-discovery.test.ts @@ -0,0 +1,86 @@ +import { execFileSync } from "node:child_process" +import { afterEach, beforeEach, describe, expect, it } from "bun:test" +import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs" +import { tmpdir } from "node:os" +import { join } from "node:path" +import { + discoverOpencodeProjectSkills, + discoverProjectAgentsSkills, + discoverProjectClaudeSkills, +} from "./loader" + +function writeSkill(directory: string, name: string, description: string): void { + mkdirSync(directory, { recursive: true }) + writeFileSync( + join(directory, "SKILL.md"), + `---\nname: ${name}\ndescription: ${description}\n---\nBody\n`, + ) +} + +describe("project skill discovery", () => { + let tempDir = "" + + beforeEach(() => { + tempDir = mkdtempSync(join(tmpdir(), "omo-project-skill-discovery-")) + }) + + afterEach(() => { + rmSync(tempDir, { recursive: true, force: true }) + }) + + it("discovers ancestor project skill directories up to the worktree root", async () => { + // given + const repositoryDir = join(tempDir, "repo") + const nestedDirectory = join(repositoryDir, "packages", "app", "src") + + mkdirSync(nestedDirectory, { recursive: true }) + execFileSync("git", ["init"], { + cwd: repositoryDir, + stdio: ["ignore", "ignore", "ignore"], + }) + + writeSkill( + join(repositoryDir, ".claude", "skills", "repo-claude"), + "repo-claude", + "Discovered from the repository root", + ) + writeSkill( + join(repositoryDir, ".agents", "skills", "repo-agents"), + "repo-agents", + "Discovered from the repository root", + ) + writeSkill( + join(repositoryDir, ".opencode", "skill", "repo-opencode"), + "repo-opencode", + "Discovered from the repository root", + ) + + writeSkill( + join(tempDir, ".claude", "skills", "outside-claude"), + "outside-claude", + "Should stay outside the worktree", + ) + writeSkill( + join(tempDir, ".agents", "skills", "outside-agents"), + "outside-agents", + "Should stay outside the worktree", + ) + writeSkill( + join(tempDir, ".opencode", "skills", "outside-opencode"), + "outside-opencode", + "Should stay outside the worktree", + ) + + // when + const [claudeSkills, agentSkills, opencodeSkills] = await Promise.all([ + discoverProjectClaudeSkills(nestedDirectory), + discoverProjectAgentsSkills(nestedDirectory), + discoverOpencodeProjectSkills(nestedDirectory), + ]) + + // then + expect(claudeSkills.map(skill => skill.name)).toEqual(["repo-claude"]) + expect(agentSkills.map(skill => skill.name)).toEqual(["repo-agents"]) + expect(opencodeSkills.map(skill => skill.name)).toEqual(["repo-opencode"]) + }) +})