From 68c1a08ad114e6d1cfdcecd09880b5b594230b77 Mon Sep 17 00:00:00 2001 From: LeonSGP43 Date: Mon, 4 May 2026 09:51:00 +0800 Subject: [PATCH] fix(curator): protect hub skills by frontmatter name --- tests/tools/test_skill_usage.py | 41 +++++++++++++++++++++++++++++++++ tools/skill_usage.py | 21 ++++++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/tests/tools/test_skill_usage.py b/tests/tools/test_skill_usage.py index b66e2bba76..232a17a9cd 100644 --- a/tests/tools/test_skill_usage.py +++ b/tests/tools/test_skill_usage.py @@ -225,6 +225,47 @@ def test_agent_created_excludes_hub_installed(skills_home): assert "hub-skill" not in names +def test_agent_created_excludes_hub_installed_frontmatter_name(skills_home): + from tools.skill_usage import is_agent_created, list_agent_created_skill_names + + skills_dir = skills_home / "skills" + hub_skill = skills_dir / "productivity" / "getnote" + hub_skill.mkdir(parents=True) + (hub_skill / "SKILL.md").write_text( + """--- +name: Get笔记 +description: test skill +--- + +# body +""", + encoding="utf-8", + ) + _write_skill(skills_dir, "my-skill") + hub_dir = skills_dir / ".hub" + hub_dir.mkdir() + (hub_dir / "lock.json").write_text( + json.dumps( + { + "version": 1, + "installed": { + "getnote": { + "source": "taps/main", + "install_path": "productivity/getnote", + } + }, + } + ), + encoding="utf-8", + ) + + names = list_agent_created_skill_names() + assert "my-skill" in names + assert "Get笔记" not in names + assert is_agent_created("Get笔记") is False + assert is_agent_created("getnote") is False + + def test_is_agent_created(skills_home): from tools.skill_usage import is_agent_created skills_dir = skills_home / "skills" diff --git a/tools/skill_usage.py b/tools/skill_usage.py index 0491f1d8b1..053f27b224 100644 --- a/tools/skill_usage.py +++ b/tools/skill_usage.py @@ -143,7 +143,26 @@ def _read_hub_installed_names() -> Set[str]: if isinstance(data, dict): installed = data.get("installed") or {} if isinstance(installed, dict): - return {str(k) for k in installed.keys()} + names = {str(k) for k in installed.keys()} + skills_dir = _skills_dir() + for entry in installed.values(): + if not isinstance(entry, dict): + continue + install_path = entry.get("install_path") + if not isinstance(install_path, str) or not install_path.strip(): + continue + skill_dir = Path(install_path) + if not skill_dir.is_absolute(): + skill_dir = skills_dir / skill_dir + try: + resolved = skill_dir.resolve() + resolved.relative_to(skills_dir.resolve()) + except (OSError, ValueError): + continue + skill_md = resolved / "SKILL.md" + if skill_md.exists(): + names.add(_read_skill_name(skill_md, fallback=resolved.name)) + return names except (OSError, json.JSONDecodeError) as e: logger.debug("Failed to read hub lock file: %s", e) return set()