diff --git a/docs/integrations/junie.md b/docs/integrations/junie.md index 5a0d141b97..470e300193 100644 --- a/docs/integrations/junie.md +++ b/docs/integrations/junie.md @@ -29,6 +29,6 @@ hyphenated names. ## Workflow Dispatch -Workflow steps use `junie -p `, with `--model` when configured and +Workflow steps use `junie --task `, with `--model` when configured and `--output-format json` when structured output is requested. There are no integration-specific setup options. diff --git a/src/specify_cli/integrations/junie/__init__.py b/src/specify_cli/integrations/junie/__init__.py index 21ecac1f83..8aa589d8ea 100644 --- a/src/specify_cli/integrations/junie/__init__.py +++ b/src/specify_cli/integrations/junie/__init__.py @@ -5,6 +5,7 @@ import re +from collections.abc import Mapping, Sequence from pathlib import Path from typing import Any @@ -178,3 +179,31 @@ def setup( self.record_file_in_manifest(path, project_root, manifest) return created + + 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 ``junie`` execution. + + ``MarkdownIntegration``'s default passes the prompt as ``-p ``, + but Junie's ``-p`` is ``--project ``, so the prompt would be + taken as the project directory and no task would be given. Junie + takes a non-interactive task as a positional argument or ``--task``. + """ + self.validate_runtime_config(integration_args, integration_options) + if not self.config or not self.config.get("requires_cli"): + return None + args = [self._resolve_executable(), "--task", prompt] + self._apply_extra_args_env_var(args) + if model: + args.extend(["--model", model]) + if output_json: + args.extend(["--output-format", "json"]) + return args diff --git a/tests/integrations/test_integration_junie.py b/tests/integrations/test_integration_junie.py index e6fae0f69e..8fb0d1dd9d 100644 --- a/tests/integrations/test_integration_junie.py +++ b/tests/integrations/test_integration_junie.py @@ -133,6 +133,67 @@ def test_init_next_steps_show_hyphenated_commands(self, tmp_path): assert "/speckit-plan" in result.output assert "/speckit.plan" not in result.output + def test_build_exec_args_passes_prompt_as_task(self): + """Junie's `-p` is `--project `; the prompt goes to `--task`.""" + integration = get_integration(self.KEY) + + args = integration.build_exec_args( + "/speckit-specify build a login page", + output_json=False, + ) + + assert args == ["junie", "--task", "/speckit-specify build a login page"] + assert "-p" not in args + + def test_build_exec_args_requests_json_output(self): + integration = get_integration(self.KEY) + + args = integration.build_exec_args("/speckit-plan add OAuth", output_json=True) + + assert args == [ + "junie", + "--task", + "/speckit-plan add OAuth", + "--output-format", + "json", + ] + + def test_build_exec_args_forwards_model(self): + integration = get_integration(self.KEY) + + args = integration.build_exec_args( + "/speckit-specify build a login page", + model="sonnet", + output_json=False, + ) + + assert args == [ + "junie", + "--task", + "/speckit-specify build a login page", + "--model", + "sonnet", + ] + + def test_build_exec_args_applies_extra_args(self, monkeypatch): + monkeypatch.setenv("SPECKIT_INTEGRATION_JUNIE_EXTRA_ARGS", "--skip-update-check") + integration = get_integration(self.KEY) + + args = integration.build_exec_args( + "check the build", model="sonnet", output_json=True + ) + + assert args == [ + "junie", + "--task", + "check the build", + "--skip-update-check", + "--model", + "sonnet", + "--output-format", + "json", + ] + # -- Overrides for MarkdownIntegrationTests --------------------------- def test_setup_creates_files(self, tmp_path):