From 3af6ed9bda7f7de411ac0b28b6ff5bfaf44863bd Mon Sep 17 00:00:00 2001 From: Quratulain-bilal Date: Sun, 12 Jul 2026 03:06:31 +0500 Subject: [PATCH] fix(extensions): hyphenate in-body command refs when rendering skills extension command bodies were copied verbatim into generated SKILL.md files for skills-based integrations (codex, claude, etc.), so an in-body reference like /speckit.foo.bar kept its slash-dot spelling. skills agents dispatch speckit-foo-bar skills, not /speckit.foo.bar slash commands, so the reference was not runnable and the agent fell back to a markdown-only path. the skill dir name and frontmatter hook refs were already hyphenated; the body was the missing piece. _register_extension_skills only ran resolve_skill_placeholders (script/args/paths) and post_process_skill_content (a hook-note only), never _hyphenate_body_refs. that helper already exists and is applied on the cline extension path; apply it here too. added a regression test that installs an extension whose command body references a sibling command and asserts the dotted ref is rewritten to the hyphenated skill name. confirmed it fails on the pre-fix code. --- src/specify_cli/extensions/__init__.py | 7 +++++++ tests/test_extension_skills.py | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/specify_cli/extensions/__init__.py b/src/specify_cli/extensions/__init__.py index b84b836545..5a9bea0a7b 100644 --- a/src/specify_cli/extensions/__init__.py +++ b/src/specify_cli/extensions/__init__.py @@ -1081,6 +1081,13 @@ def _register_extension_skills( body = registrar.resolve_skill_placeholders( selected_ai, frontmatter, body, self.project_root, extension_id=manifest.id ) + # Rewrite in-body command references (`speckit.foo.bar`) to the + # hyphenated skill spelling. Skills-based integrations dispatch + # commands as `speckit-foo-bar` skills, not `/speckit.foo.bar` + # slash commands, so a verbatim dotted reference is not runnable + # (see #3451). The skill dir name and frontmatter hook refs are + # already hyphenated elsewhere; the body was the missing piece. + body = registrar._hyphenate_body_refs(body) original_desc = frontmatter.get("description", "") description = original_desc or f"Extension command: {cmd_name}" diff --git a/tests/test_extension_skills.py b/tests/test_extension_skills.py index c2d9bb439e..db1a121bf2 100644 --- a/tests/test_extension_skills.py +++ b/tests/test_extension_skills.py @@ -103,6 +103,7 @@ def _create_extension_dir(temp_dir: Path, ext_id: str = "test-ext") -> Path: "# Hello Command\n" "\n" "Run this to say hello.\n" + f"First run /speckit.{ext_id}.world to set up.\n" "$ARGUMENTS\n" ) @@ -327,6 +328,28 @@ def test_skill_md_content_correct(self, skills_project, extension_dir): assert "compatibility:" in content assert "Run this to say hello." in content + def test_skill_body_command_refs_are_hyphenated(self, skills_project, extension_dir): + """In-body `speckit.x.y` references must be rewritten to the hyphenated + skill spelling for skills-based integrations (#3451). + + Skills agents dispatch `speckit-x-y` skills, not `/speckit.x.y` slash + commands, so a verbatim dotted reference copied into the skill body is + not runnable. The skill dir name and frontmatter hook refs are already + hyphenated; the body was the missing piece.""" + project_dir, skills_dir = skills_project + manager = ExtensionManager(project_dir) + manager.install_from_directory( + extension_dir, "0.1.0", register_commands=False + ) + + skill_file = skills_dir / "speckit-test-ext-hello" / "SKILL.md" + content = skill_file.read_text() + + # The dotted cross-command reference must be gone from the body... + assert "speckit.test-ext.world" not in content + # ...rewritten to the hyphenated skill name. + assert "speckit-test-ext-world" in content + def test_skill_md_has_parseable_yaml(self, skills_project, extension_dir): """Generated SKILL.md should contain valid, parseable YAML frontmatter.""" project_dir, skills_dir = skills_project