v2.6.1
2.6.1 - 2026-04-15
Bug Fixed
search fails to locate SKILL.md when the marketplace API's skillId has different punctuation from the on-disk directory name or the YAML name field (#126)
search fails to locate SKILL.md when the marketplace API's skillId has different punctuation from the on-disk directory name or the YAML name field, producing warnings like:
SKILL.md not found for 'pdf-ocr-extraction' in claude-office-skills/skills
SKILL.md not found for 'pdf-merge-&-split' in claude-office-skills/skills
Problem
When running search against the marketplace, some skills that do exist in the cloned repository are reported as missing. Concrete reproduction against skills.sh with query pdf ocr / pdf merge:
API skillId |
API name |
Actual repo directory | YAML name field |
|---|---|---|---|
pdf-ocr-extraction |
pdf ocr extraction |
pdf-ocr/ |
PDF OCR Extraction |
pdf-merge-&-split |
pdf merge & split |
pdf-merge-split/ |
PDF Merge & Split |
Both skills exist and have valid SKILL.md files at claude-office-skills/skills (https://github.com/claude-office-skills/skills), but search fails to find them and skips the read/install flow for them.
Solution
It's solved with the following five-tier solution:
Tier 1 — Direct path under skillId (unchanged)
- Check
repoDir / skillId / "SKILL.md". If exists -> return it.
Tier 2 — Direct path under skills/skillId (unchanged)
- Check
repoDir / "skills" / skillId / "SKILL.md". If exists -> return it.
Tier 3 — Directory name exact match (widened)
- Collect all
SKILL.mdfiles viacollectSkillMds. - Match against either
skillIdordisplayName(string===, case-sensitive as today). skillIdtakes precedence overdisplayNameif both match different files (deterministic:.find { md => (md / os.up).last === skillId }.orElse { … displayName }).
Tier 4 — YAML name field exact match (case-insensitive, trimmed) (widened)
- Normalize via
s => s.toLowerCase.trim. - For each SKILL.md: read its YAML
nameand normalize. - Match against the normalized
skillIdor normalizeddisplayName. skillIdcandidate is tried first,displayNamesecond (same.find { … }.orElse { … }ordering).
Tier 5 — Alphanumeric-only fuzzy match (new, last resort)
- Normalization:
def alnumNormalize(s: String): String = s.toLowerCase.filter(_.isLetterOrDigit). - Build normalized
skillIdAlnumanddisplayNameAlnum. - Candidate is a
SKILL.mdif the alphanumeric normalization of either its parent directory name or its YAMLnameequals eitherskillIdAlnumordisplayNameAlnum. - If exactly one SKILL.md matches -> return it.
- If zero SKILL.md files match -> return
None. - If two or more distinct SKILL.md files match -> return
None(ambiguity policy; avoid guessing). - Guard against empty normalized strings: if both
skillIdAlnumanddisplayNameAlnumare empty, skip tier 5 entirely (returnNonewithout scanning).
What's Changed
- Update: README.md by @kevin-lee in #125
- Fix #126: Fix marketplace
searchfailing to findSKILL.mdwhen the APIskillIddiffers in punctuation from the repo directory or YAML name by @kevin-lee in #127 - Refactor to follow 2020 Hindsight Scala practices by @kevin-lee in #128
- ai-skills v2.6.1 by @kevin-lee in #129
Full Changelog: v2.6.0...v2.6.1