diff --git a/src/specify_cli/_invocation_style.py b/src/specify_cli/_invocation_style.py index 3c867cc026..86926e23ba 100644 --- a/src/specify_cli/_invocation_style.py +++ b/src/specify_cli/_invocation_style.py @@ -13,7 +13,18 @@ # Agents that always render /speckit-, regardless of ai_skills. ALWAYS_SLASH_AGENTS: frozenset[str] = frozenset( - {"devin", "droid", "dsh", "grok", "mcode", "muse", "qodercli", "trae", "zed"} + { + "devin", + "docker-agent", + "droid", + "dsh", + "grok", + "mcode", + "muse", + "qodercli", + "trae", + "zed", + } ) # Agents that render /speckit- only when ai_skills is enabled. diff --git a/src/specify_cli/command_init.py b/src/specify_cli/command_init.py index 605dd7ed92..d89835cc5f 100644 --- a/src/specify_cli/command_init.py +++ b/src/specify_cli/command_init.py @@ -1051,6 +1051,7 @@ def init( dsh_skill_mode = selected_ai == "dsh" and _is_skills_integration cline_skill_mode = selected_ai == "cline" forge_skill_mode = selected_ai == "forge" + junie_skill_mode = selected_ai == "junie" bob_skill_mode = selected_ai == "bob" and _is_skills_integration native_skill_mode = ( codex_skill_mode @@ -1146,6 +1147,7 @@ def _display_cmd(name: str) -> str: _is_slash_skills_agent(selected_ai, _ai_skills_enabled) or cline_skill_mode or forge_skill_mode + or junie_skill_mode ): return f"/speckit-{name}" return f"/speckit.{name}" diff --git a/tests/integrations/test_integration_docker_agent.py b/tests/integrations/test_integration_docker_agent.py index fa2207927f..173f75efda 100644 --- a/tests/integrations/test_integration_docker_agent.py +++ b/tests/integrations/test_integration_docker_agent.py @@ -16,6 +16,30 @@ class TestDockerAgentIntegration(SkillsIntegrationTests): def test_multi_install_is_opt_in(self): assert DockerAgentIntegration().multi_install_safe is False + def test_is_slash_skills_agent(self): + """Docker Agent only installs `speckit-/SKILL.md` skills, so the + helper must agree with its own renderer with or without `ai_skills` + (always slash, like Zed and Muse Code, which share that layout).""" + from specify_cli._invocation_style import is_slash_skills_agent + + assert DockerAgentIntegration().build_command_invocation("plan") == "/speckit-plan" + assert is_slash_skills_agent("docker-agent", True) is True + assert is_slash_skills_agent("docker-agent", False) is True + + def test_hook_invocation_renders_the_hyphenated_form(self, tmp_path, monkeypatch): + import specify_cli + from specify_cli.extensions import HookExecutor + + monkeypatch.setattr( + specify_cli, + "load_init_options", + lambda _root: {"ai": "docker-agent", "ai_skills": True}, + ) + + rendered = HookExecutor(tmp_path)._render_hook_invocation("speckit.git.commit") + + assert rendered == "/speckit-git-commit" + def test_extra_args_are_applied_to_build_exec_args(monkeypatch): monkeypatch.setenv( diff --git a/tests/integrations/test_integration_junie.py b/tests/integrations/test_integration_junie.py index a6234ba734..e6fae0f69e 100644 --- a/tests/integrations/test_integration_junie.py +++ b/tests/integrations/test_integration_junie.py @@ -109,6 +109,30 @@ def test_junie_hook_instruction_injection_no_trailing_newline(self): # Instruction stays on its own line rather than being mashed onto the note. assert "\n- For each executable hook, output the following:" in injected + def test_init_next_steps_show_hyphenated_commands(self, tmp_path): + """Next Steps must list the `/speckit-` commands Junie installs.""" + from typer.testing import CliRunner + + from specify_cli import app + + result = CliRunner().invoke( + app, + [ + "init", + str(tmp_path / "junie-next-steps"), + "--integration", + self.KEY, + "--script", + "sh", + "--ignore-agent-tools", + ], + catch_exceptions=False, + ) + + assert result.exit_code == 0, result.output + assert "/speckit-plan" in result.output + assert "/speckit.plan" not in result.output + # -- Overrides for MarkdownIntegrationTests --------------------------- def test_setup_creates_files(self, tmp_path):