From 789ed4ae25a37058bdf274c9bd53870a9cfff97a Mon Sep 17 00:00:00 2001 From: Denton Social Date: Wed, 23 Sep 2026 23:45:52 -0500 Subject: [PATCH] Allow dotted skill titles (category.skill) in create_skill drafts The model follows the repo's existing dotted naming (email.compose, tracking.check) and proposed titles like tracking.status_lookup, which the strict alnum-only check rejected. Accept dotted snake_case names. --- semif_agent/skills.py | 4 ++-- tests/test_skills.py | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/semif_agent/skills.py b/semif_agent/skills.py index 4e2a0e8..68d1752 100644 --- a/semif_agent/skills.py +++ b/semif_agent/skills.py @@ -430,8 +430,8 @@ def parse_skill_draft(raw: str) -> SkillDraft: if not title or not description: raise ValueError(f"skill draft missing title/description: {raw!r}") name = re.sub(r"\s+", "_", title.lower()) - if not name.replace("_", "").isalnum(): - raise ValueError(f"skill title must be snake_case alnum: {title!r}") + if not re.fullmatch(r"[a-z0-9_]+(?:\.[a-z0-9_]+)*", name): + raise ValueError(f"skill title must be snake_case alnum (dots allowed): {title!r}") return SkillDraft(name=name, description=description) diff --git a/tests/test_skills.py b/tests/test_skills.py index a8d6ab4..ef92742 100644 --- a/tests/test_skills.py +++ b/tests/test_skills.py @@ -140,6 +140,13 @@ def test_parse_skill_draft_normalizes_title(): assert draft.name == "live_tracking" +def test_parse_skill_draft_allows_dotted_name(): + draft = parse_skill_draft( + '{"title": "tracking.status_lookup", "description": "Look up a package status."}' + ) + assert draft.name == "tracking.status_lookup" + + def test_parse_skill_draft_missing_fields_raises(): with pytest.raises(ValueError): parse_skill_draft('{"title": "only_title"}')