test(issue-42): .claude/skills 링크 검사가 두 로컬 배치를 모두 받는다 #47

Merged
Godopu merged 3 commits from fix/issue-42-skills-link into main 2026-10-04 04:54:59 +00:00
Showing only changes of commit 150b4937f8 - Show all commits
+59 -12
View File
@@ -237,17 +237,64 @@ def test_frontmatter_name_matches_directory(skill):
assert m and m.group(1) == skill
def test_claude_skills_link_resolves_to_agents_skills():
""".claude/skills is local (git-ignored) and comes in two layouts: one link
to .agents/skills, or a directory with one link per skill. Either way each
skill Claude Code loads must be the .agents/skills one."""
link = REPO / ".claude" / "skills"
if not link.exists():
pytest.skip(".claude/skills is untracked; absent in a worktree")
def claude_skills_problems(link, skills_dir, names):
"""None when ``link`` (.claude/skills) is absent; else the list of problems.
.claude/skills is local (git-ignored) and comes in two layouts: one link to
.agents/skills, or a directory with one link per skill. Either way each
skill Claude Code loads must be the .agents/skills one. A dangling
top-level link is a broken layout, not an absent one: Path.exists()
follows the link, so absence is "neither exists nor is a symlink"."""
if not link.exists() and not link.is_symlink():
return None
if link.is_symlink():
assert link.resolve() == SKILLS.resolve()
return
for skill in SKILL_NAMES:
ok = link.exists() and link.resolve() == skills_dir.resolve()
return [] if ok else [f"{link} -> {link.resolve()} is not {skills_dir}"]
problems = []
for skill in names:
entry = link / skill
assert entry.exists(), f".claude/skills/{skill} is missing"
assert entry.resolve() == (SKILLS / skill).resolve(), f".claude/skills/{skill} -> {entry.resolve()}"
if not entry.exists():
problems.append(f"{skill} is missing or dangling")
elif entry.resolve() != (skills_dir / skill).resolve():
problems.append(f"{skill} -> {entry.resolve()}")
return problems
def test_claude_skills_link_resolves_to_agents_skills():
problems = claude_skills_problems(REPO / ".claude" / "skills", SKILLS, SKILL_NAMES)
if problems is None:
pytest.skip(".claude/skills is untracked; absent in a worktree")
assert problems == []
@pytest.mark.parametrize("layout,ok", [("absent", None), ("single", True), ("per_skill", True),
("wrong_skill", False), ("broken_top", False), ("broken_entry", False)])
def test_claude_skills_layouts(tmp_path, layout, ok):
"""The check on synthetic layouts, so it is exercised in a worktree too."""
skills = tmp_path / ".agents" / "skills"
names = ["multi-agent-mux-a", "multi-agent-mux-b"]
for n in names:
(skills / n).mkdir(parents=True)
link = tmp_path / ".claude" / "skills"
link.parent.mkdir()
if layout == "single":
link.symlink_to("../.agents/skills")
elif layout == "broken_top":
link.symlink_to("../.agents/no-such-dir")
elif layout in ("per_skill", "wrong_skill", "broken_entry"):
link.mkdir()
for n in names:
(link / n).symlink_to(f"../../.agents/skills/{n}")
if layout == "wrong_skill":
(link / names[1]).unlink()
(link / names[1]).symlink_to(f"../../.agents/skills/{names[0]}")
if layout == "broken_entry":
(link / names[1]).unlink()
(link / names[1]).symlink_to("../../.agents/skills/gone")
problems = claude_skills_problems(link, skills, names)
if ok is None:
assert problems is None
elif ok:
assert problems == []
else:
assert problems, layout