mirror of
https://github.com/github/spec-kit.git
synced 2026-07-11 02:22:14 +08:00
fix: resolve __SPECKIT_COMMAND_*__ refs in preset skill rendering (#2717)
The preset skill layer mirrors command templates into SKILL.md files but only ran resolve_skill_placeholders(), leaving command cross-references as raw __SPECKIT_COMMAND_<NAME>__ placeholders instead of rendering them as /speckit-<cmd> the way CommandRegistrar.register_commands() does. As a result, presets that override core commands under the agent skill layer (e.g. Claude --ai-skills) leaked the raw tokens into SKILL.md. Add a shared PresetManager._resolve_skill_command_refs() helper that maps the agent's invoke separator to IntegrationBase.resolve_command_refs(), and call it right after resolve_skill_placeholders() in every preset skill-rendering path: _register_skills() (install), the _reconcile_skills() override-restoration block, and both _unregister_skills() restore paths. This mirrors register_commands() and addresses the path divergence flagged in #1976. Add regression tests covering the install and restore paths. AI assistance: authored with Claude Code (Anthropic) — analysis, patch, and tests. Verified via the existing pytest suite plus a manual CLI install and remove cycle on a Claude --ai-skills project.
This commit is contained in:
@@ -28,6 +28,7 @@ from packaging import version as pkg_version
|
||||
from packaging.specifiers import SpecifierSet, InvalidSpecifier
|
||||
|
||||
from .extensions import REINSTALL_COMMAND, ExtensionRegistry, normalize_priority
|
||||
from .integrations.base import IntegrationBase
|
||||
|
||||
|
||||
def _substitute_core_template(
|
||||
@@ -1058,6 +1059,9 @@ class PresetManager:
|
||||
body = registrar.resolve_skill_placeholders(
|
||||
selected_ai, fm, body, self.project_root
|
||||
)
|
||||
body = self._resolve_skill_command_refs(
|
||||
body, registrar, selected_ai
|
||||
)
|
||||
fm_data = registrar.build_skill_frontmatter(
|
||||
selected_ai if isinstance(selected_ai, str) else "",
|
||||
skill_name, desc,
|
||||
@@ -1134,6 +1138,23 @@ class PresetManager:
|
||||
title_name = title_name[len("speckit."):]
|
||||
return title_name.replace(".", " ").replace("-", " ").title()
|
||||
|
||||
@staticmethod
|
||||
def _resolve_skill_command_refs(
|
||||
body: str, registrar: "CommandRegistrar", selected_ai: str
|
||||
) -> str:
|
||||
"""Render ``__SPECKIT_COMMAND_*__`` tokens in a skill body as invocations.
|
||||
|
||||
Looks up the agent's invoke separator and rewrites each
|
||||
``__SPECKIT_COMMAND_<NAME>__`` placeholder into the matching
|
||||
slash-command invocation — ``/speckit-<cmd>`` for a ``-`` separator,
|
||||
``/speckit.<cmd>`` for ``.`` — the same rendering the command layer
|
||||
applies via ``CommandRegistrar.register_commands()``.
|
||||
"""
|
||||
separator = registrar.AGENT_CONFIGS.get(selected_ai, {}).get(
|
||||
"invoke_separator", "."
|
||||
)
|
||||
return IntegrationBase.resolve_command_refs(body, separator)
|
||||
|
||||
def _build_extension_skill_restore_index(self) -> Dict[str, Dict[str, Any]]:
|
||||
"""Index extension-backed skill restore data by skill directory name."""
|
||||
from .extensions import ExtensionManifest, ValidationError
|
||||
@@ -1310,6 +1331,7 @@ class PresetManager:
|
||||
body = registrar.resolve_skill_placeholders(
|
||||
selected_ai, frontmatter, body, self.project_root
|
||||
)
|
||||
body = self._resolve_skill_command_refs(body, registrar, selected_ai)
|
||||
|
||||
for target_skill_name in target_skill_names:
|
||||
skill_subdir = skills_dir / target_skill_name
|
||||
@@ -1402,6 +1424,9 @@ class PresetManager:
|
||||
body = registrar.resolve_skill_placeholders(
|
||||
selected_ai, frontmatter, body, self.project_root
|
||||
)
|
||||
body = self._resolve_skill_command_refs(
|
||||
body, registrar, selected_ai
|
||||
)
|
||||
|
||||
original_desc = frontmatter.get("description", "")
|
||||
enhanced_desc = original_desc or SKILL_DESCRIPTIONS.get(
|
||||
@@ -1439,6 +1464,9 @@ class PresetManager:
|
||||
body = registrar.resolve_skill_placeholders(
|
||||
selected_ai, frontmatter, body, self.project_root
|
||||
)
|
||||
body = self._resolve_skill_command_refs(
|
||||
body, registrar, selected_ai
|
||||
)
|
||||
|
||||
command_name = extension_restore["command_name"]
|
||||
title_name = self._skill_title_from_command(command_name)
|
||||
|
||||
Reference in New Issue
Block a user