Skip to content

Commit 64ccaa8

Browse files
Fix command-token-hyphen-gap: add verbatim token form for hyphenated command names
Apply the remediation from the bug assessment on issue #4198. Extends resolve_command_refs() with a new verbatim token form __SPECKIT_COMMAND(speckit.agent-context.update)__ that carries the command ID verbatim. Dots are replaced by the invoke_separator; hyphens within segments are preserved (and collapse naturally to the separator when separator is '-'). The existing uppercase __SPECKIT_COMMAND_NAME__ form is unchanged. Refs #4198 Assisted-by: GitHub Copilot (model: claude-sonnet-4.6, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 6b7f4aa commit 64ccaa8

2 files changed

Lines changed: 64 additions & 8 deletions

File tree

‎src/specify_cli/integrations/base.py‎

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -620,25 +620,42 @@ def install_scripts(
620620
def resolve_command_refs(
621621
content: str, separator: str = ".", prefix: str = "/"
622622
) -> str:
623-
"""Replace ``__SPECKIT_COMMAND_<NAME>__`` placeholders with invocations.
623+
"""Replace ``__SPECKIT_COMMAND_*__`` placeholders with invocations.
624624
625-
Each placeholder encodes a command name in upper-case with
625+
Two forms are supported:
626+
627+
**Uppercase form** — encodes a command name in upper-case with
626628
underscores (e.g. ``__SPECKIT_COMMAND_PLAN__``,
627-
``__SPECKIT_COMMAND_GIT_COMMIT__``). The replacement uses
629+
``__SPECKIT_COMMAND_GIT_COMMIT__``). Each underscore is replaced by
628630
*separator* to join the segments:
629631
630632
* ``separator="."`` → ``/speckit.plan``, ``/speckit.git.commit``
631633
* ``separator="-"`` → ``/speckit-plan``, ``/speckit-git-commit``
632634
635+
**Verbatim form** — carries the canonical command ID verbatim inside
636+
parentheses (e.g. ``__SPECKIT_COMMAND(speckit.agent-context.update)__``).
637+
This form correctly handles command names that contain hyphens, which
638+
the uppercase form cannot represent unambiguously. The canonical dots
639+
are replaced by *separator*; hyphens within a segment are kept as-is
640+
(they become *separator* when *separator* is ``"-"``):
641+
642+
* ``separator="."`` → ``/speckit.agent-context.update``
643+
* ``separator="-"`` → ``/speckit-agent-context-update``
644+
633645
*prefix* defaults to ``"/"`` but may be ``"$"`` for agents whose
634646
native skills invocation uses dollar-prefixed chat commands.
635647
"""
648+
649+
def _replace(m: re.Match) -> str:
650+
if m.group(1) is not None:
651+
# Uppercase form: replace underscores with separator.
652+
return prefix + "speckit" + separator + m.group(1).lower().replace("_", separator)
653+
# Verbatim form: replace dots with separator; hyphens are kept.
654+
return prefix + m.group(2).replace(".", separator)
655+
636656
return re.sub(
637-
r"__SPECKIT_COMMAND_([A-Z][A-Z0-9_]*)__",
638-
lambda m: prefix
639-
+ "speckit"
640-
+ separator
641-
+ m.group(1).lower().replace("_", separator),
657+
r"__SPECKIT_COMMAND_([A-Z][A-Z0-9_]*)__|__SPECKIT_COMMAND\(([^)]+)\)__",
658+
_replace,
642659
content,
643660
)
644661

‎tests/integrations/test_base.py‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,45 @@ def test_placeholder_with_digits(self):
410410
result = IntegrationBase.resolve_command_refs(text, ".")
411411
assert result == "/speckit.v2.plan"
412412

413+
# -- Verbatim form tests --------------------------------------------------
414+
415+
def test_verbatim_dot_separator_with_hyphen(self):
416+
"""Verbatim form correctly handles a hyphen in command name with dot separator."""
417+
text = "__SPECKIT_COMMAND(speckit.agent-context.update)__"
418+
result = IntegrationBase.resolve_command_refs(text, ".")
419+
assert result == "/speckit.agent-context.update"
420+
421+
def test_verbatim_hyphen_separator_with_hyphen(self):
422+
"""Verbatim form correctly handles a hyphen in command name with hyphen separator."""
423+
text = "__SPECKIT_COMMAND(speckit.agent-context.update)__"
424+
result = IntegrationBase.resolve_command_refs(text, "-")
425+
assert result == "/speckit-agent-context-update"
426+
427+
def test_verbatim_dollar_prefix(self):
428+
text = "__SPECKIT_COMMAND(speckit.agent-context.update)__"
429+
result = IntegrationBase.resolve_command_refs(text, "-", "$")
430+
assert result == "$speckit-agent-context-update"
431+
432+
def test_verbatim_simple_command_no_hyphens(self):
433+
"""Verbatim form works for commands without hyphens too."""
434+
text = "__SPECKIT_COMMAND(speckit.git.commit)__"
435+
result = IntegrationBase.resolve_command_refs(text, ".")
436+
assert result == "/speckit.git.commit"
437+
438+
def test_verbatim_and_uppercase_mixed(self):
439+
"""Both forms can coexist in the same string."""
440+
text = "__SPECKIT_COMMAND_PLAN__ and __SPECKIT_COMMAND(speckit.agent-context.update)__"
441+
result = IntegrationBase.resolve_command_refs(text, ".")
442+
assert result == "/speckit.plan and /speckit.agent-context.update"
443+
444+
def test_uppercase_form_unaffected_by_verbatim_support(self):
445+
"""Regression: existing uppercase form still works correctly."""
446+
text = "__SPECKIT_COMMAND_AGENT_CONTEXT_UPDATE__"
447+
result_dot = IntegrationBase.resolve_command_refs(text, ".")
448+
result_hyphen = IntegrationBase.resolve_command_refs(text, "-")
449+
assert result_dot == "/speckit.agent.context.update"
450+
assert result_hyphen == "/speckit-agent-context-update"
451+
413452

414453
class TestResolvePythonInterpreter:
415454
def test_returns_python_on_path(self, monkeypatch):

0 commit comments

Comments
 (0)