Skip to content

Commit ecb3b94

Browse files
authored
fix: resolve skill placeholders for all SKILL.md agents, not just codex/kimi (#2313)
* fix: resolve skill placeholders for all SKILL.md agents, not just codex/kimi * chore: remove unused NATIVE_SKILLS_AGENTS constant
1 parent c5c2013 commit ecb3b94

3 files changed

Lines changed: 75 additions & 2 deletions

File tree

src/specify_cli/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -928,7 +928,6 @@ def _get_skills_dir(project_path: Path, selected_ai: str) -> Path:
928928

929929
# Constants kept for backward compatibility with presets and extensions.
930930
DEFAULT_SKILLS_DIR = ".agents/skills"
931-
NATIVE_SKILLS_AGENTS = {"codex", "kimi"}
932931
SKILL_DESCRIPTIONS = {
933932
"specify": "Create or update feature specifications from natural language descriptions.",
934933
"plan": "Generate technical implementation plans from feature specifications.",

src/specify_cli/agents.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,8 @@ def render_skill_command(
282282
if not isinstance(frontmatter, dict):
283283
frontmatter = {}
284284

285-
if agent_name in {"codex", "kimi"}:
285+
agent_config = self.AGENT_CONFIGS.get(agent_name, {})
286+
if agent_config.get("extension") == "/SKILL.md":
286287
body = self.resolve_skill_placeholders(
287288
agent_name, frontmatter, body, project_root
288289
)

tests/test_extensions.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1369,6 +1369,79 @@ def test_codex_skill_registration_resolves_script_placeholders(self, project_dir
13691369
assert "{ARGS}" not in content
13701370
assert '.specify/scripts/bash/setup-plan.sh --json "$ARGUMENTS"' in content
13711371

1372+
@pytest.mark.parametrize("agent_name,skills_path", [
1373+
("codex", ".agents/skills"),
1374+
("kimi", ".kimi/skills"),
1375+
("claude", ".claude/skills"),
1376+
("cursor-agent", ".cursor/skills"),
1377+
("trae", ".trae/skills"),
1378+
("agy", ".agents/skills"),
1379+
])
1380+
def test_all_skill_agents_register_commands_with_resolved_placeholders(
1381+
self, project_dir, temp_dir, agent_name, skills_path
1382+
):
1383+
"""All SKILL.md agents must produce fully resolved SKILL.md files when commands are registered."""
1384+
import yaml
1385+
1386+
ext_dir = temp_dir / f"ext-{agent_name}"
1387+
ext_dir.mkdir()
1388+
(ext_dir / "commands").mkdir()
1389+
1390+
manifest_data = {
1391+
"schema_version": "1.0",
1392+
"extension": {
1393+
"id": f"ext-{agent_name}",
1394+
"name": "Scripted Extension",
1395+
"version": "1.0.0",
1396+
"description": "Test",
1397+
},
1398+
"requires": {"speckit_version": ">=0.1.0"},
1399+
"provides": {
1400+
"commands": [
1401+
{
1402+
"name": f"speckit.ext-{agent_name}.run",
1403+
"file": "commands/run.md",
1404+
"description": "Scripted command",
1405+
}
1406+
]
1407+
},
1408+
}
1409+
with open(ext_dir / "extension.yml", "w") as f:
1410+
yaml.dump(manifest_data, f)
1411+
1412+
(ext_dir / "commands" / "run.md").write_text(
1413+
"---\n"
1414+
"description: Scripted command\n"
1415+
"scripts:\n"
1416+
' sh: ../../scripts/bash/setup-plan.sh --json "{ARGS}"\n'
1417+
"---\n\n"
1418+
"Run {SCRIPT}\n"
1419+
"Agent is __AGENT__.\n"
1420+
)
1421+
1422+
init_options = project_dir / ".specify" / "init-options.json"
1423+
init_options.parent.mkdir(parents=True, exist_ok=True)
1424+
init_options.write_text(f'{{"ai":"{agent_name}","script":"sh"}}')
1425+
1426+
skills_dir = project_dir
1427+
for part in skills_path.split("/"):
1428+
skills_dir = skills_dir / part
1429+
skills_dir.mkdir(parents=True)
1430+
1431+
manifest = ExtensionManifest(ext_dir / "extension.yml")
1432+
registrar = CommandRegistrar()
1433+
registrar.register_commands_for_agent(agent_name, manifest, ext_dir, project_dir)
1434+
1435+
skill_dir_name = f"speckit-ext-{agent_name}-run"
1436+
skill_file = skills_dir / skill_dir_name / "SKILL.md"
1437+
assert skill_file.exists(), f"SKILL.md not created for {agent_name}"
1438+
1439+
content = skill_file.read_text()
1440+
assert "{SCRIPT}" not in content, f"{{SCRIPT}} not resolved for {agent_name}"
1441+
assert "__AGENT__" not in content, f"__AGENT__ not resolved for {agent_name}"
1442+
assert "{ARGS}" not in content, f"{{ARGS}} not resolved for {agent_name}"
1443+
assert '.specify/scripts/bash/setup-plan.sh' in content
1444+
13721445
def test_codex_skill_alias_frontmatter_matches_alias_name(self, project_dir, temp_dir):
13731446
"""Codex alias skills should render their own matching `name:` frontmatter."""
13741447
import yaml

0 commit comments

Comments
 (0)