Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/specify_cli/extensions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1596,7 +1596,7 @@ def _replacement(match: re.Match[str]) -> str:
)

return re.sub(
r"__SPECKIT_COMMAND_([A-Z][A-Z0-9_]*)__", _replacement, body
r"__SPECKIT_COMMAND_([A-Z][A-Z0-9_-]*)__", _replacement, body
)

for cmd_info in manifest.commands:
Expand Down
6 changes: 5 additions & 1 deletion src/specify_cli/integrations/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -636,11 +636,15 @@ def resolve_command_refs(
* ``separator="."`` → ``/speckit.plan``, ``/speckit.git.commit``
* ``separator="-"`` → ``/speckit-plan``, ``/speckit-git-commit``

A hyphen belongs to the segment it sits in rather than separating
segments, so ``__SPECKIT_COMMAND_AGENT-CONTEXT_UPDATE__`` resolves to
``/speckit.agent-context.update``.

*prefix* defaults to ``"/"`` but may be ``"$"`` for agents whose
native skills invocation uses dollar-prefixed chat commands.
"""
return re.sub(
r"__SPECKIT_COMMAND_([A-Z][A-Z0-9_]*)__",
r"__SPECKIT_COMMAND_([A-Z][A-Z0-9_-]*)__",
lambda m: prefix
+ "speckit"
+ separator
Expand Down
14 changes: 14 additions & 0 deletions tests/integrations/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,16 @@ def test_extension_command_hyphen(self):
result = IntegrationBase.resolve_command_refs(text, "-")
assert result == "Run /speckit-git-commit to commit."

def test_hyphenated_command_dot(self):
text = "Run __SPECKIT_COMMAND_AGENT-CONTEXT_UPDATE__ to refresh."
result = IntegrationBase.resolve_command_refs(text, ".")
assert result == "Run /speckit.agent-context.update to refresh."

def test_hyphenated_command_hyphen(self):
text = "Run __SPECKIT_COMMAND_AGENT-CONTEXT_UPDATE__ to refresh."
result = IntegrationBase.resolve_command_refs(text, "-")
assert result == "Run /speckit-agent-context-update to refresh."

def test_no_placeholders_unchanged(self):
text = "No placeholders here."
assert IntegrationBase.resolve_command_refs(text, ".") == text
Expand Down Expand Up @@ -400,6 +410,10 @@ def test_lowercase_placeholder_not_matched(self):
text = "Run __SPECKIT_COMMAND_plan__ to plan."
assert IntegrationBase.resolve_command_refs(text, ".") == text

def test_leading_hyphen_not_matched(self):
text = "Run __SPECKIT_COMMAND_-PLAN__ to plan."
assert IntegrationBase.resolve_command_refs(text, ".") == text

def test_placeholder_adjacent_to_text(self):
text = "foo__SPECKIT_COMMAND_PLAN__bar"
result = IntegrationBase.resolve_command_refs(text, ".")
Expand Down
46 changes: 46 additions & 0 deletions tests/test_extension_skills.py
Original file line number Diff line number Diff line change
Expand Up @@ -1166,6 +1166,52 @@ def test_skill_registration_resolves_command_ref_tokens(
assert "__SPECKIT_COMMAND_PLAN__" not in content
assert expected_invocation in content

def test_skill_registration_resolves_hyphenated_command_ref_tokens(
self, project_dir, temp_dir
):
"""Command names containing a hyphen resolve like any other name."""
_create_init_options(project_dir, ai="claude", ai_skills=True)
skills_dir = _create_skills_dir(project_dir, ai="claude")

ext_dir = temp_dir / "hyphen-ref-ext"
ext_dir.mkdir()
manifest_data = {
"schema_version": "1.0",
"extension": {
"id": "hyphen-ref-ext",
"name": "Hyphen Ref Extension",
"version": "1.0.0",
"description": "Test",
},
"requires": {"speckit_version": ">=0.1.0"},
"provides": {
"commands": [
{
"name": "speckit.hyphen-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_AGENT-CONTEXT_UPDATE__ before proceeding.\n"
)

manager = ExtensionManager(project_dir)
manager.install_from_directory(ext_dir, "0.1.0", register_commands=False)

content = (skills_dir / "speckit-hyphen-ref-ext-run" / "SKILL.md").read_text()
assert "__SPECKIT_COMMAND_AGENT-CONTEXT_UPDATE__" not in content
assert "/speckit-agent-context-update" in content

def test_skill_registration_does_not_rewrite_literal_speckit_text(
self, project_dir, temp_dir
):
Expand Down