fix(extensions): resolve __SPECKIT_COMMAND tokens in auto-registered skills (#3544)

* fix(extensions): resolve command ref tokens in extension skills

* fix(extensions): render skill command refs by invocation style

Resolve extension skill command-reference tokens with the active skill invocation style so Codex and ZCode use $speckit-* while slash-style agents keep their native forms. Preserve literal command-looking text.

* fix(extensions): resolve slash skill command refs from init options

---------

Co-authored-by: root <kinsonnee@gmail.com>
This commit is contained in:
WOLIKIMCHENG
2026-07-16 20:21:14 +08:00
committed by GitHub
parent 459f483f57
commit 4fed84a08d
2 changed files with 133 additions and 0 deletions

View File

@@ -970,6 +970,113 @@ class TestExtensionSkillRegistration:
assert "Read agents/control" not in content
assert "and knowledge-base/" not in content
@pytest.mark.parametrize(
("ai", "expected_invocation"),
[
("claude", "/speckit-plan"),
("copilot", "/speckit-plan"),
("codex", "$speckit-plan"),
("kimi", "/skill:speckit-plan"),
("zcode", "$speckit-plan"),
],
)
def test_skill_registration_resolves_command_ref_tokens(
self, project_dir, temp_dir, ai, expected_invocation
):
"""Auto-registered skills should resolve explicit command ref tokens."""
_create_init_options(project_dir, ai=ai, ai_skills=True)
skills_dir = _create_skills_dir(project_dir, ai=ai)
ext_dir = temp_dir / "command-ref-ext"
ext_dir.mkdir()
manifest_data = {
"schema_version": "1.0",
"extension": {
"id": "command-ref-ext",
"name": "Command Ref Extension",
"version": "1.0.0",
"description": "Test",
},
"requires": {"speckit_version": ">=0.1.0"},
"provides": {
"commands": [
{
"name": "speckit.command-ref-ext.run",
"file": "commands/run.md",
"description": "Run command",
}
]
},
}
with open(ext_dir / "extension.yml", "w") as f:
yaml.safe_dump(manifest_data, f)
(ext_dir / "commands").mkdir()
(ext_dir / "commands" / "run.md").write_text(
"---\n"
"description: Run command\n"
"---\n\n"
"Use __SPECKIT_COMMAND_PLAN__ before proceeding.\n"
)
manager = ExtensionManager(project_dir)
manager.install_from_directory(ext_dir, "0.1.0", register_commands=False)
content = (skills_dir / "speckit-command-ref-ext-run" / "SKILL.md").read_text()
assert "__SPECKIT_COMMAND_PLAN__" not in content
assert expected_invocation in content
def test_skill_registration_does_not_rewrite_literal_speckit_text(
self, project_dir, temp_dir
):
"""Auto-registered skills should leave literal speckit text untouched."""
_create_init_options(project_dir, ai="codex", ai_skills=True)
skills_dir = _create_skills_dir(project_dir, ai="codex")
ext_dir = temp_dir / "literal-ref-ext"
ext_dir.mkdir()
manifest_data = {
"schema_version": "1.0",
"extension": {
"id": "literal-ref-ext",
"name": "Literal Ref Extension",
"version": "1.0.0",
"description": "Test",
},
"requires": {"speckit_version": ">=0.1.0"},
"provides": {
"commands": [
{
"name": "speckit.literal-ref-ext.run",
"file": "commands/run.md",
"description": "Run command",
}
]
},
}
with open(ext_dir / "extension.yml", "w") as f:
yaml.safe_dump(manifest_data, f)
(ext_dir / "commands").mkdir()
(ext_dir / "commands" / "run.md").write_text(
"---\n"
"description: Run command\n"
"---\n\n"
"Literal slash form: /speckit.foo.bar\n"
"Literal skill form: /speckit-plan\n"
"Literal bare form: speckit.foo.bar\n"
)
manager = ExtensionManager(project_dir)
manager.install_from_directory(ext_dir, "0.1.0", register_commands=False)
content = (skills_dir / "speckit-literal-ref-ext-run" / "SKILL.md").read_text()
assert "/speckit.foo.bar" in content
assert "/speckit-plan" in content
assert "speckit.foo.bar" in content
assert "/speckit-foo-bar" not in content
assert "$speckit-plan" not in content
def test_missing_command_file_skipped(self, skills_project, temp_dir):
"""Commands with missing source files should be skipped gracefully."""
project_dir, skills_dir = skills_project