Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions tests/tools/test_skills_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,29 @@ def test_view_existing_skill(self, tmp_path):
assert result["name"] == "my-skill"
assert "Step 1" in result["content"]

def test_view_resolves_frontmatter_name_when_directory_differs(self, tmp_path):
with patch("tools.skills_tool.SKILLS_DIR", tmp_path):
skill_dir = tmp_path / "brainstorming"
skill_dir.mkdir(parents=True)
(skill_dir / "SKILL.md").write_text(
"---\n"
"name: superpowers-brainstorming\n"
"description: Brainstorming workflow.\n"
"---\n\n"
"# Brainstorming\n\n"
"Explore options first.\n",
encoding="utf-8",
)

listed = json.loads(skills_list())
raw = skill_view("superpowers-brainstorming")

result = json.loads(raw)
assert "superpowers-brainstorming" in {s["name"] for s in listed["skills"]}
assert result["success"] is True
assert result["name"] == "superpowers-brainstorming"
assert "Explore options first" in result["content"]

def test_skill_view_applies_template_vars(self, tmp_path):
with (
patch("tools.skills_tool.SKILLS_DIR", tmp_path),
Expand Down
14 changes: 14 additions & 0 deletions tools/skills_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -999,9 +999,23 @@ def _record(sd: Optional[Path], smd: Path) -> None:

# Strategy 2: recursive by directory name (catches nested skills
# like "foundations/runtime/explore-codebase" called by bare name).
# Also match the SKILL.md frontmatter name. Discovery/listing uses
# frontmatter `name:` as the canonical skill identifier, so viewing
# must resolve the same identifier even when the directory is named
# differently (for example plugin/external skills with
# skills/brainstorming/SKILL.md declaring name: superpowers-brainstorming).
for found_skill_md in iter_skill_index_files(search_dir, "SKILL.md"):
if found_skill_md.parent.name == name:
_record(found_skill_md.parent, found_skill_md)
continue
try:
probe_content = found_skill_md.read_text(encoding="utf-8")[:4000]
probe_frontmatter, _ = _parse_frontmatter(probe_content)
probe_name = str(probe_frontmatter.get("name", ""))[:MAX_NAME_LENGTH]
except Exception:
probe_name = ""
if probe_name == name:
_record(found_skill_md.parent, found_skill_md)

# Strategy 3: legacy flat <name>.md files anywhere under the dir.
for found_md in search_dir.rglob(f"{name}.md"):
Expand Down