tools/skills: rebase search hits from either path form

A skill root can live inside the workspace (the project-scope .claude/skills),
and grep reports those hits relative to the workspace root while outside ones
come back absolute. The attribution only stripped an absolute skill prefix, so
an in-workspace root produced alpha/alpha/SKILL.md - and, worse, a path that no
longer started with the skill name when the root itself was the workspace.
Normalizing the hit to an absolute path before relativizing to the skill covers
both, and the hit line is now matched with a pattern instead of a colon split so
a Windows drive letter cannot split it wrongly.

Found by running the REPL against a workspace-local skill root; the tool tests
only covered roots outside the workspace, so they get that case now.
This commit is contained in:
2026-09-22 22:59:57 +08:00
parent b967a000dc
commit 20fc493c47
2 changed files with 50 additions and 11 deletions
+16
View File
@@ -195,6 +195,22 @@ async def test_skill_search_single_skill_and_errors(tmp_path: Path) -> None:
assert (await skill_search.handler("[")).startswith("error: invalid regex")
async def test_skill_search_inside_the_workspace_rebases_the_hit(tmp_path: Path) -> None:
"""A project-scope skill root lives inside the workspace: one path, once.
Grep reports in-workspace files relative to the workspace root and outside
ones absolutely, so the skill name must never be glued onto a path that
already carries the root.
"""
root = tmp_path / "project" / ".claude" / "skills"
_ = _write_skill(root, "alpha", body="Use pdftotext here.")
with _bound(tmp_path, root):
out = await skill_search.handler("pdftotext")
assert out.count("alpha/SKILL.md:") == 1
assert "project/.claude/skills/alpha" not in out
assert "alpha/alpha/SKILL.md" not in out
async def test_skill_search_without_skills(tmp_path: Path) -> None:
empty = tmp_path / "empty"
empty.mkdir()