From d044286838501eb38ea2eef178482eed669e9a5b Mon Sep 17 00:00:00 2001 From: swithek <52840391+swithek@users.noreply.github.com> Date: Wed, 22 Apr 2026 16:25:24 +0200 Subject: [PATCH 1/2] fix: resolve skill placeholders for all SKILL.md agents, not just codex/kimi --- src/specify_cli/agents.py | 3 +- tests/test_extensions.py | 73 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/specify_cli/agents.py b/src/specify_cli/agents.py index ef4e879c03..31dc6abf0e 100644 --- a/src/specify_cli/agents.py +++ b/src/specify_cli/agents.py @@ -282,7 +282,8 @@ def render_skill_command( if not isinstance(frontmatter, dict): frontmatter = {} - if agent_name in {"codex", "kimi"}: + agent_config = self.AGENT_CONFIGS.get(agent_name, {}) + if agent_config.get("extension") == "/SKILL.md": body = self.resolve_skill_placeholders( agent_name, frontmatter, body, project_root ) diff --git a/tests/test_extensions.py b/tests/test_extensions.py index 5379178afe..1ae2883cba 100644 --- a/tests/test_extensions.py +++ b/tests/test_extensions.py @@ -1361,6 +1361,79 @@ def test_codex_skill_registration_resolves_script_placeholders(self, project_dir assert "{ARGS}" not in content assert '.specify/scripts/bash/setup-plan.sh --json "$ARGUMENTS"' in content + @pytest.mark.parametrize("agent_name,skills_path", [ + ("codex", ".agents/skills"), + ("kimi", ".kimi/skills"), + ("claude", ".claude/skills"), + ("cursor-agent", ".cursor/skills"), + ("trae", ".trae/skills"), + ("agy", ".agents/skills"), + ]) + def test_all_skill_agents_register_commands_with_resolved_placeholders( + self, project_dir, temp_dir, agent_name, skills_path + ): + """All SKILL.md agents must produce fully resolved SKILL.md files when commands are registered.""" + import yaml + + ext_dir = temp_dir / f"ext-{agent_name}" + ext_dir.mkdir() + (ext_dir / "commands").mkdir() + + manifest_data = { + "schema_version": "1.0", + "extension": { + "id": f"ext-{agent_name}", + "name": "Scripted Extension", + "version": "1.0.0", + "description": "Test", + }, + "requires": {"speckit_version": ">=0.1.0"}, + "provides": { + "commands": [ + { + "name": f"speckit.ext-{agent_name}.run", + "file": "commands/run.md", + "description": "Scripted command", + } + ] + }, + } + with open(ext_dir / "extension.yml", "w") as f: + yaml.dump(manifest_data, f) + + (ext_dir / "commands" / "run.md").write_text( + "---\n" + "description: Scripted command\n" + "scripts:\n" + ' sh: ../../scripts/bash/setup-plan.sh --json "{ARGS}"\n' + "---\n\n" + "Run {SCRIPT}\n" + "Agent is __AGENT__.\n" + ) + + init_options = project_dir / ".specify" / "init-options.json" + init_options.parent.mkdir(parents=True, exist_ok=True) + init_options.write_text(f'{{"ai":"{agent_name}","script":"sh"}}') + + skills_dir = project_dir + for part in skills_path.split("/"): + skills_dir = skills_dir / part + skills_dir.mkdir(parents=True) + + manifest = ExtensionManifest(ext_dir / "extension.yml") + registrar = CommandRegistrar() + registrar.register_commands_for_agent(agent_name, manifest, ext_dir, project_dir) + + skill_dir_name = f"speckit-ext-{agent_name}-run" + skill_file = skills_dir / skill_dir_name / "SKILL.md" + assert skill_file.exists(), f"SKILL.md not created for {agent_name}" + + content = skill_file.read_text() + assert "{SCRIPT}" not in content, f"{{SCRIPT}} not resolved for {agent_name}" + assert "__AGENT__" not in content, f"__AGENT__ not resolved for {agent_name}" + assert "{ARGS}" not in content, f"{{ARGS}} not resolved for {agent_name}" + assert '.specify/scripts/bash/setup-plan.sh' in content + def test_codex_skill_alias_frontmatter_matches_alias_name(self, project_dir, temp_dir): """Codex alias skills should render their own matching `name:` frontmatter.""" import yaml From f9e47aa377c77bef38abd908bbf41cf4050d6a40 Mon Sep 17 00:00:00 2001 From: swithek <52840391+swithek@users.noreply.github.com> Date: Wed, 22 Apr 2026 18:36:34 +0200 Subject: [PATCH 2/2] chore: remove unused NATIVE_SKILLS_AGENTS constant --- src/specify_cli/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 8c6fd02b9f..8d68348fb2 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -920,7 +920,6 @@ def _get_skills_dir(project_path: Path, selected_ai: str) -> Path: # Constants kept for backward compatibility with presets and extensions. DEFAULT_SKILLS_DIR = ".agents/skills" -NATIVE_SKILLS_AGENTS = {"codex", "kimi"} SKILL_DESCRIPTIONS = { "specify": "Create or update feature specifications from natural language descriptions.", "plan": "Generate technical implementation plans from feature specifications.",