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
13 changes: 12 additions & 1 deletion src/specify_cli/_invocation_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,18 @@

# Agents that always render /speckit-<name>, 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-<name> only when ai_skills is enabled.
Expand Down
2 changes: 2 additions & 0 deletions src/specify_cli/command_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}"
Expand Down
24 changes: 24 additions & 0 deletions tests/integrations/test_integration_docker_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -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-<name>/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(
Expand Down
24 changes: 24 additions & 0 deletions tests/integrations/test_integration_junie.py
Original file line number Diff line number Diff line change
Expand Up @@ -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-<name>` 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):
Expand Down