From f7fbda53d2f35f5850e00b0757081482fdf97f0c Mon Sep 17 00:00:00 2001 From: Manfred Riem <15701806+mnriem@users.noreply.github.com> Date: Fri, 13 Mar 2026 09:36:26 -0500 Subject: [PATCH] Applying review recommendations --- src/specify_cli/agents.py | 2 ++ src/specify_cli/presets.py | 33 ++++++++++++++++++++++++++++----- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/specify_cli/agents.py b/src/specify_cli/agents.py index 8876f018..5054cb7d 100644 --- a/src/specify_cli/agents.py +++ b/src/specify_cli/agents.py @@ -324,6 +324,7 @@ class CommandRegistrar: raise ValueError(f"Unsupported format: {agent_config['format']}") dest_file = commands_dir / f"{cmd_name}{agent_config['extension']}" + dest_file.parent.mkdir(parents=True, exist_ok=True) dest_file.write_text(output, encoding="utf-8") if agent_name == "copilot": @@ -333,6 +334,7 @@ class CommandRegistrar: for alias in cmd_info.get("aliases", []): alias_file = commands_dir / f"{alias}{agent_config['extension']}" + alias_file.parent.mkdir(parents=True, exist_ok=True) alias_file.write_text(output, encoding="utf-8") if agent_name == "copilot": self.write_copilot_prompt(project_root, alias) diff --git a/src/specify_cli/presets.py b/src/specify_cli/presets.py index 1c291894..4a210227 100644 --- a/src/specify_cli/presets.py +++ b/src/specify_cli/presets.py @@ -364,7 +364,7 @@ class PresetManager: Scans the preset's templates for type "command", reads each command file, and writes it to every detected agent directory using the - CommandRegistrar from the extensions module. + CommandRegistrar from the agents module. Args: manifest: Preset manifest @@ -424,7 +424,7 @@ class PresetManager: Reads ``.specify/init-options.json`` to determine whether skills are enabled and which agent was selected, then delegates to - ``_get_skills_dir()`` for the concrete path. + the module-level ``_get_skills_dir()`` helper for the concrete path. Returns: The skills directory ``Path``, or ``None`` if skills were not @@ -473,15 +473,33 @@ class PresetManager: if not command_templates: return [] + # Filter out extension command overrides if the extension isn't installed, + # matching the same logic used by _register_commands(). + extensions_dir = self.project_root / ".specify" / "extensions" + filtered = [] + for cmd in command_templates: + parts = cmd["name"].split(".") + if len(parts) >= 3 and parts[0] == "speckit": + ext_id = parts[1] + if not (extensions_dir / ext_id).is_dir(): + continue + filtered.append(cmd) + + if not filtered: + return [] + skills_dir = self._get_skills_dir() if not skills_dir: return [] - from . import SKILL_DESCRIPTIONS + from . import SKILL_DESCRIPTIONS, load_init_options + + opts = load_init_options(self.project_root) + selected_ai = opts.get("ai", "") written: List[str] = [] - for cmd_tmpl in command_templates: + for cmd_tmpl in filtered: cmd_name = cmd_tmpl["name"] cmd_file_rel = cmd_tmpl["file"] source_file = preset_dir / cmd_file_rel @@ -492,7 +510,12 @@ class PresetManager: short_name = cmd_name if short_name.startswith("speckit."): short_name = short_name[len("speckit."):] - skill_name = f"speckit-{short_name}" + # Kimi CLI discovers skills by directory name and invokes them as + # /skill: — use dot separator to match packaging convention. + if selected_ai == "kimi": + skill_name = f"speckit.{short_name}" + else: + skill_name = f"speckit-{short_name}" # Only overwrite if the skill already exists (i.e. --ai-skills was used) skill_subdir = skills_dir / skill_name