diff --git a/tests/tools/test_skills_sync.py b/tests/tools/test_skills_sync.py index e3469c805..5d6ce1d54 100644 --- a/tests/tools/test_skills_sync.py +++ b/tests/tools/test_skills_sync.py @@ -6,6 +6,7 @@ from unittest.mock import patch from tools.skills_sync import ( _get_bundled_dir, _read_manifest, + _read_skill_name, _write_manifest, _discover_bundled_skills, _compute_relative_dest, @@ -132,6 +133,37 @@ class TestDiscoverBundledSkills: assert skills == [] +class TestReadSkillName: + def test_reads_name_from_frontmatter(self, tmp_path): + skill_md = tmp_path / "SKILL.md" + skill_md.write_text("---\nname: audiocraft-audio-generation\n---\n# Skill") + assert _read_skill_name(skill_md, "audiocraft") == "audiocraft-audio-generation" + + def test_falls_back_to_dir_name_without_frontmatter(self, tmp_path): + skill_md = tmp_path / "SKILL.md" + skill_md.write_text("# Just a heading\nNo frontmatter here") + assert _read_skill_name(skill_md, "my-skill") == "my-skill" + + def test_falls_back_when_name_field_empty(self, tmp_path): + skill_md = tmp_path / "SKILL.md" + skill_md.write_text("---\nname:\n---\n") + assert _read_skill_name(skill_md, "fallback") == "fallback" + + def test_handles_quoted_name(self, tmp_path): + skill_md = tmp_path / "SKILL.md" + skill_md.write_text('---\nname: "serving-llms-vllm"\n---\n') + assert _read_skill_name(skill_md, "vllm") == "serving-llms-vllm" + + def test_discover_uses_frontmatter_name(self, tmp_path): + skill_dir = tmp_path / "category" / "audiocraft" + skill_dir.mkdir(parents=True) + (skill_dir / "SKILL.md").write_text( + "---\nname: audiocraft-audio-generation\n---\n# Skill" + ) + skills = _discover_bundled_skills(tmp_path) + assert skills[0][0] == "audiocraft-audio-generation" + + class TestComputeRelativeDest: def test_preserves_category_structure(self): bundled = Path("/repo/skills") diff --git a/tools/skills_sync.py b/tools/skills_sync.py index 9877afc2f..18ce1e3ff 100644 --- a/tools/skills_sync.py +++ b/tools/skills_sync.py @@ -109,6 +109,27 @@ def _write_manifest(entries: Dict[str, str]): logger.debug("Failed to write skills manifest %s: %s", MANIFEST_FILE, e, exc_info=True) +def _read_skill_name(skill_md: Path, fallback: str) -> str: + """Read the name field from SKILL.md YAML frontmatter, falling back to *fallback*.""" + try: + content = skill_md.read_text(encoding="utf-8", errors="replace")[:4000] + except OSError: + return fallback + in_frontmatter = False + for line in content.split("\n"): + stripped = line.strip() + if stripped == "---": + if in_frontmatter: + break + in_frontmatter = True + continue + if in_frontmatter and stripped.startswith("name:"): + value = stripped.split(":", 1)[1].strip().strip("\"'") + if value: + return value + return fallback + + def _discover_bundled_skills(bundled_dir: Path) -> List[Tuple[str, Path]]: """ Find all SKILL.md files in the bundled directory. @@ -123,7 +144,7 @@ def _discover_bundled_skills(bundled_dir: Path) -> List[Tuple[str, Path]]: if "/.git/" in path_str or "/.github/" in path_str or "/.hub/" in path_str: continue skill_dir = skill_md.parent - skill_name = skill_dir.name + skill_name = _read_skill_name(skill_md, skill_dir.name) skills.append((skill_name, skill_dir)) return skills