diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ab1ace..9da014f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,7 @@ jobs: - run: pnpm install --frozen-lockfile - run: pnpm test - run: python -m pip install -r skills/skillhone/assets/requirements.txt + - run: python .test/test_static_check_name.py - run: python skills/skillhone/scripts/quality/static_check.py skills/skillhone - run: python skills/skillhone/scripts/quality/static_check.py skills/skillhone-auto-optimization - run: node dist/cli.js --help diff --git a/.test/test_static_check_name.py b/.test/test_static_check_name.py new file mode 100644 index 0000000..9bfe2a4 --- /dev/null +++ b/.test/test_static_check_name.py @@ -0,0 +1,50 @@ +"""Regression test for Agent Skills name validation.""" +from __future__ import annotations + +import importlib.util +import tempfile +import unittest +from pathlib import Path + + +REPOSITORY_ROOT = Path(__file__).resolve().parents[1] +CHECKER_PATH = ( + REPOSITORY_ROOT + / "skills" + / "skillhone" + / "scripts" + / "quality" + / "static_check.py" +) +SPEC = importlib.util.spec_from_file_location("skillhone_static_check", CHECKER_PATH) +assert SPEC is not None and SPEC.loader is not None +STATIC_CHECK = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(STATIC_CHECK) + + +class SkillNameValidationTest(unittest.TestCase): + def check_name(self, name: str) -> set[str]: + with tempfile.TemporaryDirectory() as temporary_directory: + skill_directory = Path(temporary_directory) / name + skill_directory.mkdir() + (skill_directory / "SKILL.md").write_text( + "---\n" + f"name: {name}\n" + "description: Minimal Skill name fixture.\n" + "---\n", + encoding="utf-8", + ) + + report = STATIC_CHECK.check(skill_directory) + + return {error["kind"] for error in report.errors} + + def test_consecutive_hyphens_are_rejected(self) -> None: + self.assertIn("invalid_name", self.check_name("pdf--processing")) + + def test_single_hyphens_remain_valid(self) -> None: + self.assertNotIn("invalid_name", self.check_name("pdf-processing")) + + +if __name__ == "__main__": + unittest.main() diff --git a/skills/skillhone/scripts/quality/static_check.py b/skills/skillhone/scripts/quality/static_check.py index 83da416..33afe3f 100644 --- a/skills/skillhone/scripts/quality/static_check.py +++ b/skills/skillhone/scripts/quality/static_check.py @@ -38,7 +38,7 @@ MAX_DESC_LEN = 1024 MAX_COMPAT_LEN = 500 RECOMMENDED_BODY_LINES = 500 -NAME_RE = re.compile(r"^[a-z0-9]([a-z0-9-]*[a-z0-9])?$") +NAME_RE = re.compile(r"^(?!.*--)[a-z0-9]([a-z0-9-]*[a-z0-9])?$") ALLOWED_FRONTMATTER = { "name", "description", "license", "allowed-tools", "metadata", "compatibility",