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
2 changes: 1 addition & 1 deletion docs/integrations/junie.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ hyphenated names.

## Workflow Dispatch

Workflow steps use `junie -p <prompt>`, with `--model` when configured and
Workflow steps use `junie --task <prompt>`, with `--model` when configured and
`--output-format json` when structured output is requested.
There are no integration-specific setup options.
29 changes: 29 additions & 0 deletions src/specify_cli/integrations/junie/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@


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

Expand Down Expand Up @@ -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 <prompt>``,
but Junie's ``-p`` is ``--project <path>``, 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
61 changes: 61 additions & 0 deletions tests/integrations/test_integration_junie.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <path>`; 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):
Expand Down
Loading