diff --git a/src/features/opencode-skill-loader/config-source-discovery.test.ts b/src/features/opencode-skill-loader/config-source-discovery.test.ts index 98a1eb80..d10303ce 100644 --- a/src/features/opencode-skill-loader/config-source-discovery.test.ts +++ b/src/features/opencode-skill-loader/config-source-discovery.test.ts @@ -2,7 +2,8 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test" import { mkdirSync, rmSync, writeFileSync } from "fs" import { join } from "path" import { tmpdir } from "os" -import { discoverConfigSourceSkills } from "./config-source-discovery" +import { SkillsConfigSchema } from "../../config/schema/skills" +import { discoverConfigSourceSkills, normalizePathForGlob } from "./config-source-discovery" const TEST_DIR = join(tmpdir(), `config-source-discovery-test-${Date.now()}`) @@ -28,12 +29,13 @@ describe("config source discovery", () => { const configDir = join(TEST_DIR, "config") const sourceDir = join(configDir, "custom-skills") writeSkill(join(sourceDir, "local-skill"), "local-skill", "Loaded from local source") + const config = SkillsConfigSchema.parse({ + sources: [{ path: "./custom-skills", recursive: true }], + }) // when const skills = await discoverConfigSourceSkills({ - config: { - sources: [{ path: "./custom-skills", recursive: true }], - }, + config, configDir, }) @@ -51,12 +53,13 @@ describe("config source discovery", () => { writeSkill(join(sourceDir, "keep", "kept"), "kept-skill", "Should be kept") writeSkill(join(sourceDir, "skip", "skipped"), "skipped-skill", "Should be skipped") + const config = SkillsConfigSchema.parse({ + sources: [{ path: "./custom-skills", recursive: true, glob: "keep/**" }], + }) // when const skills = await discoverConfigSourceSkills({ - config: { - sources: [{ path: "./custom-skills", recursive: true, glob: "keep/**" }], - }, + config, configDir, }) @@ -65,4 +68,15 @@ describe("config source discovery", () => { expect(names).toContain("keep/kept-skill") expect(names).not.toContain("skip/skipped-skill") }) + + it("normalizes windows separators before glob matching", () => { + // given + const windowsPath = "keep\\nested\\SKILL.md" + + // when + const normalized = normalizePathForGlob(windowsPath) + + // then + expect(normalized).toBe("keep/nested/SKILL.md") + }) }) diff --git a/src/features/opencode-skill-loader/config-source-discovery.ts b/src/features/opencode-skill-loader/config-source-discovery.ts index 198bce85..df3ee653 100644 --- a/src/features/opencode-skill-loader/config-source-discovery.ts +++ b/src/features/opencode-skill-loader/config-source-discovery.ts @@ -25,12 +25,16 @@ function isMarkdownPath(path: string): boolean { return extname(path).toLowerCase() === ".md" } +export function normalizePathForGlob(path: string): string { + return path.split("\\").join("/") +} + function filterByGlob(skills: LoadedSkill[], sourceBaseDir: string, globPattern?: string): LoadedSkill[] { if (!globPattern) return skills return skills.filter((skill) => { if (!skill.path) return false - const rel = relative(sourceBaseDir, skill.path) + const rel = normalizePathForGlob(relative(sourceBaseDir, skill.path)) return picomatch.isMatch(rel, globPattern, { dot: true, bash: true }) }) }