Skip to content

[Bug]: __SPECKIT_COMMAND_*__ tokens can't reference commands whose names contain hyphens #4198

Description

@minzzang144

Bug Description

__SPECKIT_COMMAND_<NAME>__ tokens encode a command name in upper case with underscores, and at render time each underscore is replaced with the agent's separator (. or -):

# src/specify_cli/integrations/base.py
r"__SPECKIT_COMMAND_([A-Z][A-Z0-9_]*)__"
... + m.group(1).lower().replace("_", separator)

Command names themselves, however, are allowed to contain hyphens:

# src/specify_cli/extensions/__init__.py
EXTENSION_COMMAND_NAME_PATTERN = re.compile(r"^speckit\.([a-z0-9-]+)\.([a-z0-9-]+)$")

Since the token grammar [A-Z][A-Z0-9_]* has no character available to represent a hyphen, a command whose name contains one cannot be referenced by a token. What makes this hard to notice is that it doesn't fail — it silently renders as a different command name.

It is especially easy to miss because in skills mode, where invoke_separator is -, the result happens to come out correct. The problem only surfaces once you switch agents or modes.

The extension submission checklist asks for IDs in lowercase-with-hyphens, so following that convention appears to make an extension's own commands unreferenceable by token.

The same condition applies to speckit.agent-context.update, from the agent-context extension bundled in core_pack/extensions/. I could not find any place in core_pack that references it through a token, and every token used in core_pack refers to a name without hyphens. I have no way of knowing whether that reflects a deliberate workaround or simply a case that has not come up yet, so I am mentioning it rather than assuming.

Some options that came to mind as a user (I don't know the design intent)

I don't have a deep understanding of speckit's internals, so please read the following as ideas from someone who ran into this rather than as proposals. If any of them were already considered and rejected, I would be genuinely interested in the reasoning.

  • A token form that carries the command id verbatim — something like __SPECKIT_COMMAND(speckit.agent-context.update)__ — would seem to make the round trip safe regardless of which characters a name uses. Whether that can coexist with the current form, I can't tell.
  • Resolving against the list of installed commands instead of transforming the string might also work, though that depends on what information is available at resolution time, which I'm not in a position to judge.
  • Some check that reports a token which fails to resolve would help. That said, since substitution happens during integration use, it would only be discoverable at that point. Checking at install time seems like it would produce false positives, since a referenced extension may not be installed yet.

If none of these are practical, even a single line in the extension/preset development guide saying that command names intended to be token-referenced should avoid hyphens would have been enough for me. Right now the naming rules point toward hyphens, so there was no way to anticipate the constraint.

Steps to Reproduce

from specify_cli.integrations.base import IntegrationBase as I

# actual bundled command: speckit.agent-context.update
I.resolve_command_refs("__SPECKIT_COMMAND_AGENT_CONTEXT_UPDATE__", ".")
I.resolve_command_refs("__SPECKIT_COMMAND_AGENT_CONTEXT_UPDATE__", "-")

In a real project:

  1. Install an extension that provides a command with a hyphen in its name (e.g. the bundled agent-context, or an extension providing speckit.<id>.my-command).
  2. Reference that command from a preset or extension command body as __SPECKIT_COMMAND_MY_COMMAND__.
  3. Run specify integration use <agent> for an integration whose invoke_separator is the default ..
  4. Check the rendered command file.

Expected Behavior

The token resolves to the command id that is actually installed — or, if it cannot be resolved, something tells me about it instead of silently emitting a wrong string.

Actual Behavior

The hyphen is interpreted as a segment separator, so a three-segment command renders as four segments. No warning is emitted.

dot separator (default):
  __SPECKIT_COMMAND_AGENT_CONTEXT_UPDATE__ → /speckit.agent.context.update   ← no such command

hyphen separator (skills mode):
  __SPECKIT_COMMAND_AGENT_CONTEXT_UPDATE__ → /speckit-agent-context-update   ← correct by coincidence

I checked the v0.16.4 sources and both places (the substitution logic and the name pattern) are unchanged.

Specify CLI Version

0.16.0

AI Agent

Claude Code

Operating System

macOS 15.3.1

Python Version

3.13.9 (specify CLI venv)

Error Logs

Additional Context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions