Skip to content

Commit 657ccbd

Browse files
zhujian0805claude
andcommitted
feat: Support SKILL.md files in repository root directories
- Modified skill discovery logic to handle root-level SKILL.md files - Updated tests to reflect new behavior and fix mock paths - Root-level skills now use key format 'owner/repo:.' instead of being skipped - Installation works correctly for repositories with SKILL.md at root level This enables support for repositories like seo-geo-blog-writer that organize skills directly in the root directory rather than in subdirectories. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 9c2fdf6 commit 657ccbd

2 files changed

Lines changed: 35 additions & 16 deletions

File tree

code_assistant_manager/skills/manager.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -377,11 +377,14 @@ def _fetch_skills_from_repo(
377377
rel_path = skill_dir.relative_to(scan_dir)
378378
source_directory = str(rel_path).replace("\\", "/")
379379

380-
# Skip root level SKILL.md
380+
# Handle root level SKILL.md
381381
if source_directory == ".":
382-
continue
383-
384-
directory = skill_dir.name
382+
source_directory = "." # Keep as "." for root-level skills
383+
directory = (
384+
skill_dir.name if skill_dir != scan_dir else repo.name
385+
)
386+
else:
387+
directory = skill_dir.name
385388
except ValueError:
386389
continue
387390

tests/test_skills_recursive.py

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def skill_manager(self):
1414
with tempfile.TemporaryDirectory() as tmp_config:
1515
return SkillManager(config_dir=Path(tmp_config))
1616

17-
@patch("code_assistant_manager.skills.SkillManager._download_repo")
17+
@patch("code_assistant_manager.skills.manager.BaseSkillHandler._download_repo")
1818
def test_fetch_skills_recursive(self, mock_download, skill_manager):
1919
# Setup mock repo structure
2020
# temp_dir/
@@ -48,7 +48,9 @@ def test_fetch_skills_recursive(self, mock_download, skill_manager):
4848

4949
repo = SkillRepo(owner="owner", name="repo", skills_path="skills")
5050

51-
skills = skill_manager._fetch_skills_from_repo(repo)
51+
# Get a handler for the test
52+
handler = skill_manager.get_handler("claude")
53+
skills = skill_manager._fetch_skills_from_repo(repo, handler)
5254

5355
# Verify results
5456
assert len(skills) == 2
@@ -73,15 +75,15 @@ def test_fetch_skills_recursive(self, mock_download, skill_manager):
7375
assert s2.source_directory == "category/skill2"
7476
assert s2.readme_url.endswith("/skills/category/skill2")
7577

76-
@patch("code_assistant_manager.skills.SkillManager._download_repo")
78+
@patch("code_assistant_manager.skills.manager.BaseSkillHandler._download_repo")
7779
def test_fetch_skills_root_structure(self, mock_download, skill_manager):
7880
# Test when skills_path is root ("/") or None
7981

8082
with tempfile.TemporaryDirectory() as temp_dir_str:
8183
temp_dir = Path(temp_dir_str)
8284
mock_download.return_value = (temp_dir, "main")
8385

84-
# Skill at root (should be skipped based on my implementation "directory == '.'")
86+
# Skill at root (now supported)
8587
(temp_dir / "SKILL.md").write_text(
8688
"---\nname: Root Skill\n---\n", encoding="utf-8"
8789
)
@@ -94,12 +96,26 @@ def test_fetch_skills_root_structure(self, mock_download, skill_manager):
9496

9597
repo = SkillRepo(owner="owner", name="repo", skills_path=None)
9698

97-
skills = skill_manager._fetch_skills_from_repo(repo)
99+
# Get a handler for the test
100+
handler = skill_manager.get_handler("claude")
101+
skills = skill_manager._fetch_skills_from_repo(repo, handler)
98102

99-
# Should find nested skill but skip root skill
100-
assert len(skills) == 1
101-
# directory is now just the folder name for installation
102-
assert skills[0].directory == "skill"
103-
# source_directory has the full path
104-
assert skills[0].source_directory == "nested/skill"
105-
assert skills[0].name == "Nested"
103+
# Should find both root skill and nested skill
104+
assert len(skills) == 2
105+
skill_map = {s.key: s for s in skills}
106+
107+
# Check root skill
108+
root_key = "owner/repo:."
109+
assert root_key in skill_map
110+
root_skill = skill_map[root_key]
111+
assert root_skill.name == "Root Skill"
112+
assert root_skill.directory == "repo" # Uses repo name for root skills
113+
assert root_skill.source_directory == "."
114+
115+
# Check nested skill
116+
nested_key = "owner/repo:nested/skill"
117+
assert nested_key in skill_map
118+
nested_skill = skill_map[nested_key]
119+
assert nested_skill.name == "Nested"
120+
assert nested_skill.directory == "skill"
121+
assert nested_skill.source_directory == "nested/skill"

0 commit comments

Comments
 (0)