Skip to content

Commit 9801d2b

Browse files
authored
fix(integrations): show hyphenated command names for docker-agent and junie (#4910)
* test(integrations): cover command names shown for docker-agent and junie Assisted-by: Claude Code (model: Claude Opus 5, autonomous) * fix(integrations): show hyphenated command names for docker-agent and junie Both agents register `/speckit-<name>` commands, and their own `build_command_invocation` returns that form, but Spec Kit showed users the dotted `/speckit.<name>` names in two places: - docker-agent was missing from `ALWAYS_SLASH_AGENTS`, so the `init` next-steps banner and extension hook invocations used dotted names (#4137 fixed the same gap for alquimia). - The `init` banner special-cased cline and forge but not junie, which installs hyphenated commands the same way. Assisted-by: Claude Code (model: Claude Opus 5, autonomous) * refactor(init): derive next-steps command names from the integration The next-steps banner kept its own list of agents that use /speckit-<name>, which is how junie went missing from it. Build the names from the integration's invoke prefix and separator instead, the values init already uses to render the installed commands. The banner is unchanged for every registered integration in each of its modes. Assisted-by: Claude Code (model: Claude Opus 5, autonomous)
1 parent d941cbf commit 9801d2b

4 files changed

Lines changed: 69 additions & 22 deletions

File tree

‎src/specify_cli/_invocation_style.py‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,18 @@
1313

1414
# Agents that always render /speckit-<name>, regardless of ai_skills.
1515
ALWAYS_SLASH_AGENTS: frozenset[str] = frozenset(
16-
{"devin", "droid", "dsh", "grok", "mcode", "muse", "qodercli", "trae", "zed"}
16+
{
17+
"devin",
18+
"docker-agent",
19+
"droid",
20+
"dsh",
21+
"grok",
22+
"mcode",
23+
"muse",
24+
"qodercli",
25+
"trae",
26+
"zed",
27+
}
1728
)
1829

1930
# Agents that render /speckit-<name> only when ai_skills is enabled.

‎src/specify_cli/command_init.py‎

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1077,8 +1077,6 @@ def init(
10771077
muse_skill_mode = selected_ai == "muse" and _is_skills_integration
10781078
grok_skill_mode = selected_ai == "grok" and _is_skills_integration
10791079
dsh_skill_mode = selected_ai == "dsh" and _is_skills_integration
1080-
cline_skill_mode = selected_ai == "cline"
1081-
forge_skill_mode = selected_ai == "forge"
10821080
bob_skill_mode = selected_ai == "bob" and _is_skills_integration
10831081
native_skill_mode = (
10841082
codex_skill_mode
@@ -1155,28 +1153,18 @@ def init(
11551153
step_num += 1
11561154
usage_label = "skills" if native_skill_mode else "slash commands"
11571155

1158-
from ._invocation_style import (
1159-
is_dollar_skills_agent as _is_dollar_skills_agent,
1160-
is_slash_skills_agent as _is_slash_skills_agent,
1156+
invoke_prefix = _invoke_prefix_for_integration(
1157+
resolved_integration,
1158+
resolved_integration.key,
1159+
integration_parsed_options or None,
1160+
project_path,
1161+
)
1162+
invoke_separator = resolved_integration.effective_invoke_separator(
1163+
integration_parsed_options or None, project_root=project_path
11611164
)
1162-
1163-
# `_is_skills_integration` means the integration is installed in
1164-
# skills mode, which is the semantic equivalent of `ai_skills_enabled`
1165-
# used by `is_slash_skills_agent()`.
1166-
_ai_skills_enabled = _is_skills_integration
11671165

11681166
def _display_cmd(name: str) -> str:
1169-
if _is_dollar_skills_agent(selected_ai, _ai_skills_enabled):
1170-
return f"$speckit-{name}"
1171-
if kimi_skill_mode:
1172-
return f"/skill:speckit-{name}"
1173-
if (
1174-
_is_slash_skills_agent(selected_ai, _ai_skills_enabled)
1175-
or cline_skill_mode
1176-
or forge_skill_mode
1177-
):
1178-
return f"/speckit-{name}"
1179-
return f"/speckit.{name}"
1167+
return f"{invoke_prefix}speckit{invoke_separator}{name}"
11801168

11811169
steps_lines.append(
11821170
f"{step_num}. Start using {usage_label} with your coding agent:"

‎tests/integrations/test_integration_docker_agent.py‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,30 @@ class TestDockerAgentIntegration(SkillsIntegrationTests):
1616
def test_multi_install_is_opt_in(self):
1717
assert DockerAgentIntegration().multi_install_safe is False
1818

19+
def test_is_slash_skills_agent(self):
20+
"""Docker Agent only installs `speckit-<name>/SKILL.md` skills, so the
21+
helper must agree with its own renderer with or without `ai_skills`
22+
(always slash, like Zed and Muse Code, which share that layout)."""
23+
from specify_cli._invocation_style import is_slash_skills_agent
24+
25+
assert DockerAgentIntegration().build_command_invocation("plan") == "/speckit-plan"
26+
assert is_slash_skills_agent("docker-agent", True) is True
27+
assert is_slash_skills_agent("docker-agent", False) is True
28+
29+
def test_hook_invocation_renders_the_hyphenated_form(self, tmp_path, monkeypatch):
30+
import specify_cli
31+
from specify_cli.extensions import HookExecutor
32+
33+
monkeypatch.setattr(
34+
specify_cli,
35+
"load_init_options",
36+
lambda _root: {"ai": "docker-agent", "ai_skills": True},
37+
)
38+
39+
rendered = HookExecutor(tmp_path)._render_hook_invocation("speckit.git.commit")
40+
41+
assert rendered == "/speckit-git-commit"
42+
1943

2044
def test_extra_args_are_applied_to_build_exec_args(monkeypatch):
2145
monkeypatch.setenv(

‎tests/integrations/test_integration_junie.py‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,30 @@ def test_junie_hook_instruction_injection_no_trailing_newline(self):
109109
# Instruction stays on its own line rather than being mashed onto the note.
110110
assert "\n- For each executable hook, output the following:" in injected
111111

112+
def test_init_next_steps_show_hyphenated_commands(self, tmp_path):
113+
"""Next Steps must list the `/speckit-<name>` commands Junie installs."""
114+
from typer.testing import CliRunner
115+
116+
from specify_cli import app
117+
118+
result = CliRunner().invoke(
119+
app,
120+
[
121+
"init",
122+
str(tmp_path / "junie-next-steps"),
123+
"--integration",
124+
self.KEY,
125+
"--script",
126+
"sh",
127+
"--ignore-agent-tools",
128+
],
129+
catch_exceptions=False,
130+
)
131+
132+
assert result.exit_code == 0, result.output
133+
assert "/speckit-plan" in result.output
134+
assert "/speckit.plan" not in result.output
135+
112136
# -- Overrides for MarkdownIntegrationTests ---------------------------
113137

114138
def test_setup_creates_files(self, tmp_path):

0 commit comments

Comments
 (0)