fix(skills): normalize windows separators for source globs

This commit is contained in:
YeonGyu-Kim 2026-02-13 11:17:18 +09:00
parent 0001bc87c2
commit 5a83c61d77
2 changed files with 26 additions and 8 deletions

View File

@ -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")
})
})

View File

@ -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 })
})
}