Skip to content

Commit 2d5e630

Browse files
authored
fix: default non-interactive init to copilot integration (#2414)
* fix: default non-interactive init integration * chore: clarify non-interactive init default integration * Address non-interactive init review feedback * Fix interactive init test after fallback
1 parent 7936320 commit 2d5e630

6 files changed

Lines changed: 46 additions & 5 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ specify init --here --force
485485

486486
![Specify CLI bootstrapping a new project in the terminal](./media/specify_cli.gif)
487487

488-
You will be prompted to select the coding agent integration you are using. You can also proactively specify it directly in the terminal:
488+
In an interactive terminal, you will be prompted to select the coding agent integration you are using. In non-interactive sessions, such as CI or piped runs, `specify init` defaults to GitHub Copilot unless you pass `--integration`. You can also proactively specify the integration directly in the terminal:
489489

490490
```bash
491491
specify init <project_name> --integration copilot

docs/installation.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init --here
4141
4242
### Specify Integration
4343

44+
Interactive terminals prompt you to choose a coding agent integration during initialization. Non-interactive sessions, such as CI or piped runs, default to GitHub Copilot unless you pass `--integration`.
45+
4446
You can proactively specify your coding agent integration during initialization:
4547

4648
```bash

docs/reference/core.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ Creates a new Spec Kit project with the necessary directory structure, templates
2828
2929
Use `<project_name>` to create a new directory, or `--here` (or `.`) to initialize in the current directory. If the directory already has files, use `--force` to merge without confirmation.
3030

31+
When `--integration` is omitted, interactive terminals prompt you to choose an integration. Non-interactive sessions, such as CI or piped runs, default to GitHub Copilot; pass `--integration <key>` to choose a different integration explicitly.
32+
3133
### Examples
3234

3335
```bash

src/specify_cli/__init__.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ def _build_agent_config() -> dict[str, dict[str, Any]]:
9090
return config
9191

9292
AGENT_CONFIG = _build_agent_config()
93+
DEFAULT_INIT_INTEGRATION = "copilot"
9394

9495
AI_ASSISTANT_ALIASES = {
9596
"kiro": "kiro-cli",
@@ -152,6 +153,9 @@ def _build_ai_deprecation_warning(
152153
f"Use [bold]{replacement}[/bold] instead."
153154
)
154155

156+
def _stdin_is_interactive() -> bool:
157+
return sys.stdin.isatty()
158+
155159
SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"}
156160

157161
CLAUDE_LOCAL_PATH = Path.home() / ".claude" / "local" / "claude"
@@ -995,7 +999,8 @@ def init(
995999
9961000
This command will:
9971001
1. Check that required tools are installed (git is optional)
998-
2. Let you choose your coding agent integration
1002+
2. Let you choose your coding agent integration, or default to Copilot
1003+
in non-interactive sessions
9991004
3. Download template from GitHub (or use bundled assets with --offline)
10001005
4. Initialize a fresh git repository (if not --no-git and no existing repo)
10011006
5. Optionally set up coding agent integration commands
@@ -1162,13 +1167,19 @@ def init(
11621167
console.print(f"[red]Error:[/red] Invalid AI assistant '{ai_assistant}'. Choose from: {', '.join(AGENT_CONFIG.keys())}")
11631168
raise typer.Exit(1)
11641169
selected_ai = ai_assistant
1170+
elif not _stdin_is_interactive():
1171+
console.print(
1172+
f"[dim]Non-interactive session detected: defaulting to '{DEFAULT_INIT_INTEGRATION}'. "
1173+
"Use --integration to choose a different agent.[/dim]"
1174+
)
1175+
selected_ai = DEFAULT_INIT_INTEGRATION
11651176
else:
11661177
# Create options dict for selection (agent_key: display_name)
11671178
ai_choices = {key: config["name"] for key, config in AGENT_CONFIG.items()}
11681179
selected_ai = select_with_arrows(
11691180
ai_choices,
11701181
"Choose your coding agent integration:",
1171-
"copilot"
1182+
DEFAULT_INIT_INTEGRATION,
11721183
)
11731184

11741185
# Auto-promote interactively selected agents to the integration path
@@ -1233,7 +1244,7 @@ def init(
12331244
else:
12341245
default_script = "ps" if os.name == "nt" else "sh"
12351246

1236-
if sys.stdin.isatty():
1247+
if _stdin_is_interactive():
12371248
selected_script = select_with_arrows(SCRIPT_TYPE_CHOICES, "Choose script type (or press Enter)", default_script)
12381249
else:
12391250
selected_script = default_script

tests/integrations/test_cli.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,29 @@ def test_integration_copilot_creates_files(self, tmp_path):
8181
shared_manifest = project / ".specify" / "integrations" / "speckit.manifest.json"
8282
assert shared_manifest.exists()
8383

84+
def test_noninteractive_init_defaults_to_copilot(self, tmp_path, monkeypatch):
85+
from typer.testing import CliRunner
86+
from specify_cli import app
87+
import specify_cli
88+
89+
def fail_select(*_args, **_kwargs):
90+
raise AssertionError("non-interactive init should not open the integration picker")
91+
92+
monkeypatch.setattr(specify_cli, "select_with_arrows", fail_select)
93+
94+
runner = CliRunner()
95+
project = tmp_path / "noninteractive"
96+
result = runner.invoke(app, [
97+
"init", str(project), "--script", "sh", "--no-git", "--ignore-agent-tools",
98+
], catch_exceptions=False)
99+
100+
assert result.exit_code == 0, result.output
101+
assert f"defaulting to '{specify_cli.DEFAULT_INIT_INTEGRATION}'" in result.output
102+
assert (project / ".github" / "agents" / "speckit.plan.agent.md").exists()
103+
104+
data = json.loads((project / ".specify" / "integration.json").read_text(encoding="utf-8"))
105+
assert data["integration"] == specify_cli.DEFAULT_INIT_INTEGRATION
106+
84107
def test_ai_copilot_auto_promotes(self, tmp_path):
85108
from typer.testing import CliRunner
86109
from specify_cli import app

tests/integrations/test_integration_claude.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,10 @@ def test_interactive_claude_selection_uses_integration_path(self, tmp_path):
196196
try:
197197
os.chdir(project)
198198
runner = CliRunner()
199-
with patch("specify_cli.select_with_arrows", return_value="claude"):
199+
with (
200+
patch("specify_cli._stdin_is_interactive", return_value=True),
201+
patch("specify_cli.select_with_arrows", return_value="claude"),
202+
):
200203
result = runner.invoke(
201204
app,
202205
[

0 commit comments

Comments
 (0)