Skip to content
Merged
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
28 changes: 28 additions & 0 deletions src/specify_cli/integrations/vibe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from __future__ import annotations

from collections.abc import Mapping, Sequence
from pathlib import Path
from typing import Any

Expand Down Expand Up @@ -79,6 +80,33 @@ def options(cls) -> list[IntegrationOption]:
)
return opts

def build_exec_args(
self,
prompt: str,
*,
model: str | None = None,
output_json: bool = True,
integration_args: Sequence[str] | None = None,
integration_options: Mapping[str, Any] | None = None,
project_root: Path | None = None,
) -> list[str] | None:
"""Build CLI arguments for non-interactive ``vibe`` execution.

``SkillsIntegration``'s default appends ``--model`` and
``--output-format``, neither of which exists in the Vibe CLI, so a
dispatched step exits 2 at argument parsing whenever either flag ends
up appended (a configured ``model``, or ``output_json=True``). Vibe's
structured output is ``--output json``. ``model`` is deliberately
dropped: Vibe has no per-invocation model flag and selects its model
out of band via ``active_model`` in its config (or ``VIBE_ACTIVE_MODEL``).
"""
self.validate_runtime_config(integration_args, integration_options)
args = [self._resolve_executable(), "-p", prompt]
self._apply_extra_args_env_var(args)
if output_json:
args.extend(["--output", "json"])
return args

def _render_skill(self, template_name: str, frontmatter: dict[str, Any], body: str) -> str:
"""Render a processed command template as a Vibe skill."""
skill_name = f"speckit-{template_name.replace('.', '-')}"
Expand Down
48 changes: 48 additions & 0 deletions tests/integrations/test_integration_vibe.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,54 @@ def test_skills_do_not_have_argument_hint(self, tmp_path):
f"{f.parent.name}/SKILL.md unexpectedly has argument-hint frontmatter"
)

def test_build_exec_args_uses_prompt_mode(self):
integration = get_integration("vibe")

args = integration.build_exec_args(
"/speckit-specify build a login page",
output_json=False,
)

assert args == ["vibe", "-p", "/speckit-specify build a login page"]

def test_build_exec_args_requests_json_output(self):
"""Vibe's structured output is `--output json`; it has no `--output-format`."""
integration = get_integration("vibe")

args = integration.build_exec_args("/speckit-plan add OAuth", output_json=True)

assert args == ["vibe", "-p", "/speckit-plan add OAuth", "--output", "json"]
assert "--output-format" not in args

def test_build_exec_args_omits_model_flag(self):
"""Vibe has no model flag; passing `--model` exits 2 at argument parsing."""
integration = get_integration("vibe")

args = integration.build_exec_args(
"explain this repository",
model="mistral-medium-latest",
output_json=False,
)

assert args == ["vibe", "-p", "explain this repository"]
assert "--model" not in args
assert "mistral-medium-latest" not in args

def test_build_exec_args_applies_extra_args(self, monkeypatch):
monkeypatch.setenv("SPECKIT_INTEGRATION_VIBE_EXTRA_ARGS", "--auto-approve")
integration = get_integration("vibe")

args = integration.build_exec_args("check the build", output_json=True)

assert args == [
"vibe",
"-p",
"check the build",
"--auto-approve",
"--output",
"json",
]


class TestVibeTomlMerging:
"""Behavioral tests for the toml-vibe hooks.toml generation and cleanup."""
Expand Down
Loading