Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,12 +319,13 @@ specify init --here --force

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

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:
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` or the shorter `--ai` alias. You can also proactively specify the integration directly in the terminal:

```bash
specify init <project_name> --integration copilot
specify init <project_name> --integration gemini
specify init <project_name> --integration codex
specify init <project_name> --ai codex

# Or in current directory:
specify init . --integration copilot
Expand Down
6 changes: 4 additions & 2 deletions docs/reference/core.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ specify init [<project_name>]

| Option | Description |
| ------------------------ | ------------------------------------------------------------------------ |
| `--integration <key>` | AI coding agent integration to use (e.g. `copilot`, `claude`, `gemini`). See the [Integrations reference](integrations.md) for all available keys |
| `--integration <key>` | AI coding agent integration to use (e.g. `copilot`, `claude`, `gemini`). Equivalent to `--ai`; do not combine them. See the [Integrations reference](integrations.md) for all available keys |
| `--ai <key>` | Short alias for `--integration <key>`; do not combine with `--integration` |
| `--integration-options` | Options for the integration (e.g. `--integration-options="--commands-dir .myagent/cmds"`) |
| `--script sh\|ps` | Script type: `sh` (bash/zsh) or `ps` (PowerShell) |
| `--here` | Initialize in the current directory instead of creating a new one |
Expand All @@ -28,13 +29,14 @@ Creates a new Spec Kit project with the necessary directory structure, templates

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.

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.
When `--integration` and `--ai` are 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>` or the equivalent shorter `--ai <key>` alias to choose a different integration explicitly. The two options select the same integration setting and may not be combined in the same invocation.

### Examples

```bash
# Create a new project with an integration
specify init my-project --integration copilot
specify init my-project --ai copilot

# Initialize in the current directory
specify init --here --integration copilot
Expand Down
7 changes: 5 additions & 2 deletions src/specify_cli/_agent_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ def _build_agent_config() -> dict[str, dict[str, Any]]:
def _build_ai_assistant_help() -> str:
non_generic_agents = sorted(agent for agent in AGENT_CONFIG if agent != "generic")
base_help = (
f"AI assistant to use: {', '.join(non_generic_agents)}, "
"or generic (requires --ai-commands-dir)."
f"Short alias for --integration. Choose: {', '.join(non_generic_agents)}, "
"or generic. For generic, provide a commands directory with "
"--ai-commands-dir; the --integration generic form can use "
'--integration-options="--commands-dir <dir>". Do not combine '
"--ai with --integration."
)
if not AI_ASSISTANT_ALIASES:
return base_help
Expand Down
44 changes: 1 addition & 43 deletions src/specify_cli/commands/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from __future__ import annotations

import os
import shlex
import shutil
import sys
from pathlib import Path
Expand All @@ -28,32 +27,6 @@
from .._console import StepTracker, console, select_with_arrows, show_banner
from .._utils import check_tool, init_git_repo, is_git_repo

def _build_integration_equivalent(
integration_key: str,
ai_commands_dir: str | None = None,
) -> str:
parts = [f"--integration {integration_key}"]
if integration_key == "generic" and ai_commands_dir:
parts.append(
f'--integration-options="--commands-dir {shlex.quote(ai_commands_dir)}"'
)
return " ".join(parts)


def _build_ai_deprecation_warning(
integration_key: str,
ai_commands_dir: str | None = None,
) -> str:
replacement = _build_integration_equivalent(
integration_key,
ai_commands_dir=ai_commands_dir,
)
return (
"[bold]--ai[/bold] is deprecated and will no longer be available in version 0.10.0 or later.\n\n"
f"Use [bold]{replacement}[/bold] instead."
)


def _stdin_is_interactive() -> bool:
return sys.stdin.isatty()

Expand Down Expand Up @@ -111,7 +84,7 @@ def init(
offline: bool = typer.Option(False, "--offline", help="Deprecated (no-op). All scaffolding now uses bundled assets.", hidden=True),
preset: str = typer.Option(None, "--preset", help="Install a preset during initialization (by preset ID)"),
branch_numbering: str = typer.Option(None, "--branch-numbering", help="Branch numbering strategy: 'sequential' (001, 002, …, 1000, … — expands past 999 automatically) or 'timestamp' (YYYYMMDD-HHMMSS)"),
integration: str = typer.Option(None, "--integration", help="Use the new integration system (e.g. --integration copilot). Mutually exclusive with --ai."),
integration: str = typer.Option(None, "--integration", help="Coding agent integration to use (e.g. --integration copilot). --ai is a short alias for this option; do not combine them."),
integration_options: str = typer.Option(None, "--integration-options", help='Options for the integration (e.g. --integration-options="--commands-dir .myagent/cmds")'),
):
"""
Expand Down Expand Up @@ -160,7 +133,6 @@ def init(
from ..integration_runtime import with_integration_setting as _with_integration_setting

show_banner()
ai_deprecation_warning: str | None = None

if ai_assistant and ai_assistant.startswith("--"):
console.print(f"[red]Error:[/red] Invalid value for --ai: '{ai_assistant}'")
Expand Down Expand Up @@ -196,10 +168,6 @@ def init(
if not resolved_integration:
console.print(f"[red]Error:[/red] Unknown agent '{ai_assistant}'. Choose from: {', '.join(sorted(INTEGRATION_REGISTRY))}")
raise typer.Exit(1)
ai_deprecation_warning = _build_ai_deprecation_warning(
resolved_integration.key,
ai_commands_dir=ai_commands_dir,
)

if ai_assistant or integration:
if ai_skills:
Expand Down Expand Up @@ -645,16 +613,6 @@ def init(
console.print()
console.print(security_notice)

if ai_deprecation_warning:
deprecation_notice = Panel(
ai_deprecation_warning,
title="[bold red]Deprecation Warning[/bold red]",
border_style="red",
padding=(1, 2),
)
console.print()
console.print(deprecation_notice)

if git_default_notice:
default_change_notice = Panel(
"The git extension is currently enabled by default during [bold]specify init[/bold].\n"
Expand Down
55 changes: 40 additions & 15 deletions tests/integrations/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io
import json
import os
import re

import pytest
import yaml
Expand Down Expand Up @@ -42,6 +43,25 @@ def test_cli_phase_label_includes_target(self):
)


def _assert_no_ai_deprecation_output(output: str) -> None:
lower_output = output.lower()
assert "deprecation warning" not in lower_output
assert "no longer be available" not in lower_output
assert "commands will no longer be available" not in lower_output
assert "--ai is deprecated" not in lower_output
assert "deprecated --ai" not in lower_output

ai_related_blocks = [
block
for block in re.split(r"\n\s*\n", lower_output)
if "--ai" in block
]
for block in ai_related_blocks:
assert "deprecated" not in block
assert "0.10.0" not in block
assert "next steps" not in block
Comment thread
puneetdixit200 marked this conversation as resolved.


class TestInitIntegrationFlag:
def test_integration_and_ai_mutually_exclusive(self, tmp_path):
from typer.testing import CliRunner
Expand All @@ -53,6 +73,20 @@ def test_integration_and_ai_mutually_exclusive(self, tmp_path):
assert result.exit_code != 0
assert "mutually exclusive" in result.output

def test_init_help_explains_ai_alias_and_generic_options(self):
from typer.testing import CliRunner
from specify_cli import app

runner = CliRunner()
result = runner.invoke(app, ["init", "--help"])

normalized_output = _normalize_cli_output(result.output)
assert result.exit_code == 0, result.output
assert "Short alias for --integration" in normalized_output
assert "--ai-commands-dir" in normalized_output
assert "--integration generic" in normalized_output
assert "--integration-options" in normalized_output

def test_unknown_integration_rejected(self, tmp_path):
from typer.testing import CliRunner
from specify_cli import app
Expand Down Expand Up @@ -141,11 +175,11 @@ def test_ai_copilot_auto_promotes(self, tmp_path):
assert result.exit_code == 0
assert (project / ".github" / "agents" / "speckit.plan.agent.md").exists()

def test_ai_emits_deprecation_warning_with_integration_replacement(self, tmp_path):
def test_ai_copilot_does_not_emit_deprecation_warning(self, tmp_path):
from typer.testing import CliRunner
from specify_cli import app

project = tmp_path / "warn-ai"
project = tmp_path / "keep-ai"
project.mkdir()
old_cwd = os.getcwd()
try:
Expand All @@ -159,20 +193,14 @@ def test_ai_emits_deprecation_warning_with_integration_replacement(self, tmp_pat

normalized_output = _normalize_cli_output(result.output)
assert result.exit_code == 0, result.output
assert "Deprecation Warning" in normalized_output
assert "--ai" in normalized_output
assert "deprecated" in normalized_output
assert "no longer be available" in normalized_output
assert "0.10.0" in normalized_output
assert "--integration copilot" in normalized_output
assert normalized_output.index("Deprecation Warning") < normalized_output.index("Next Steps")
_assert_no_ai_deprecation_output(normalized_output)
assert (project / ".github" / "agents" / "speckit.plan.agent.md").exists()

def test_ai_generic_warning_suggests_integration_options_equivalent(self, tmp_path):
def test_ai_generic_alias_does_not_emit_deprecation_warning(self, tmp_path):
from typer.testing import CliRunner
from specify_cli import app

project = tmp_path / "warn-generic"
project = tmp_path / "generic-ai"
project.mkdir()
old_cwd = os.getcwd()
try:
Expand All @@ -187,11 +215,8 @@ def test_ai_generic_warning_suggests_integration_options_equivalent(self, tmp_pa

normalized_output = _normalize_cli_output(result.output)
assert result.exit_code == 0, result.output
assert "Deprecation Warning" in normalized_output
assert "--integration generic" in normalized_output
assert "--integration-options" in normalized_output
_assert_no_ai_deprecation_output(normalized_output)
assert ".myagent/commands" in normalized_output
assert normalized_output.index("Deprecation Warning") < normalized_output.index("Next Steps")
assert (project / ".myagent" / "commands" / "speckit.plan.md").exists()

def test_init_optional_preset_failure_reports_target_and_continues(
Expand Down