From 705ce80b4fc9b7a8ea90d653d1f04bbf78316524 Mon Sep 17 00:00:00 2001 From: Yiorgis Gozadinos Date: Tue, 24 Mar 2026 17:27:56 +0200 Subject: [PATCH] Allow all spec-compliant names --- .../haiku/rag/skill_generator/__init__.py | 10 +-- .../templates/pyproject.toml.j2 | 2 +- tests/test_skill_generator.py | 64 +++++++------------ 3 files changed, 29 insertions(+), 47 deletions(-) diff --git a/haiku_rag_slim/haiku/rag/skill_generator/__init__.py b/haiku_rag_slim/haiku/rag/skill_generator/__init__.py index 241925d4..37a3a247 100644 --- a/haiku_rag_slim/haiku/rag/skill_generator/__init__.py +++ b/haiku_rag_slim/haiku/rag/skill_generator/__init__.py @@ -36,9 +36,6 @@ def _get_env() -> Environment: def validate_metadata(name: str, description: str) -> None: - if not name.isidentifier(): - raise ValueError(f"{name!r} is not a valid Python identifier") - from haiku.skills import SkillMetadata SkillMetadata(name=name, description=description) @@ -80,16 +77,18 @@ def render_templates( if preamble is None: preamble = DEFAULT_PREAMBLE + pkg_name = name.replace("-", "_") env = _get_env() context = { "name": name, + "pkg_name": pkg_name, "description": description, "tool_names": tool_names, "preamble": preamble, } result_dir = output_dir / f"{name}-skill" - pkg_dir = result_dir / f"{name}_skill" + pkg_dir = result_dir / f"{pkg_name}_skill" assets_dir = pkg_dir / "assets" assets_dir.mkdir(parents=True) @@ -130,7 +129,8 @@ def generate_skill( preamble=preamble, ) - assets_dir = result / f"{name}_skill" / "assets" + pkg_name = name.replace("-", "_") + assets_dir = result / f"{pkg_name}_skill" / "assets" shutil.copytree(db_path, assets_dir / f"{name}.lancedb") if config_path is not None: diff --git a/haiku_rag_slim/haiku/rag/skill_generator/templates/pyproject.toml.j2 b/haiku_rag_slim/haiku/rag/skill_generator/templates/pyproject.toml.j2 index 29616669..915d1c16 100644 --- a/haiku_rag_slim/haiku/rag/skill_generator/templates/pyproject.toml.j2 +++ b/haiku_rag_slim/haiku/rag/skill_generator/templates/pyproject.toml.j2 @@ -13,4 +13,4 @@ dependencies = [ ] [project.entry-points."haiku.skills"] -{{ name }} = "{{ name }}_skill:create_skill" +{{ name }} = "{{ pkg_name }}_skill:create_skill" diff --git a/tests/test_skill_generator.py b/tests/test_skill_generator.py index db79217f..8aa4c439 100644 --- a/tests/test_skill_generator.py +++ b/tests/test_skill_generator.py @@ -25,46 +25,11 @@ class TestAvailableTools: class TestValidateMetadata: def test_valid(self): - validate_metadata("recipes", "A skill.") + validate_metadata("my-recipes", "A skill.") - def test_valid_with_numbers(self): - validate_metadata("recipes123", "A skill.") - - def test_rejects_underscores(self): - with pytest.raises(ValueError, match="name"): - validate_metadata("my_recipes", "A skill.") - - def test_rejects_hyphens(self): - with pytest.raises(ValueError, match="identifier"): - validate_metadata("my-recipes", "A skill.") - - def test_rejects_uppercase(self): - with pytest.raises(ValueError, match="lowercase"): - validate_metadata("Recipes", "A skill.") - - def test_rejects_empty_name(self): - with pytest.raises(ValueError, match="identifier"): - validate_metadata("", "A skill.") - - def test_rejects_not_identifier(self): - with pytest.raises(ValueError, match="identifier"): - validate_metadata("123abc", "A skill.") - - def test_rejects_spaces_in_name(self): - with pytest.raises(ValueError, match="identifier"): - validate_metadata("my recipes", "A skill.") - - def test_rejects_special_chars(self): - with pytest.raises(ValueError, match="identifier"): - validate_metadata("my@recipes", "A skill.") - - def test_rejects_empty_description(self): - with pytest.raises(ValueError, match="description"): - validate_metadata("recipes", "") - - def test_rejects_too_long_description(self): - with pytest.raises(ValueError, match="description"): - validate_metadata("recipes", "x" * 1025) + def test_rejects_invalid_name(self): + with pytest.raises(ValueError): + validate_metadata("Bad_Name!", "A skill.") class TestValidateTools: @@ -135,6 +100,23 @@ class TestRenderTemplates: assert (pkg / "SKILL.md").is_file() assert (pkg / "assets").is_dir() + def test_dashed_name_uses_underscores_for_python(self, tmp_path): + result = render_templates( + output_dir=tmp_path, + name="my-recipes", + description="A recipe skill.", + tool_names=["search", "ask"], + ) + assert result == tmp_path / "my-recipes-skill" + pkg = result / "my_recipes_skill" + assert (pkg / "__init__.py").is_file() + init = (pkg / "__init__.py").read_text() + assert '"my-recipes.lancedb"' in init + assert 'state_namespace="my-recipes"' in init + toml = (result / "pyproject.toml").read_text() + assert 'name = "my-recipes-skill"' in toml + assert 'my-recipes = "my_recipes_skill:create_skill"' in toml + def test_tool_names_list_matches_selection(self, tmp_path): render_templates( output_dir=tmp_path, @@ -313,11 +295,11 @@ class TestGenerateSkill: def test_rejects_invalid_name(self, tmp_path): db_path = _make_fake_lancedb(tmp_path / "test.lancedb") - with pytest.raises(ValueError, match="identifier"): + with pytest.raises(ValueError): generate_skill( db_path=db_path, output_dir=tmp_path, - name="Bad-Name", + name="Bad_Name!", description="A skill.", tool_names=["search"], )