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
46 changes: 46 additions & 0 deletions src/specify_cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import json
import json5
import stat
import shlex
import yaml
from pathlib import Path
from typing import Any, Optional
Expand Down Expand Up @@ -92,6 +93,36 @@ def _build_ai_assistant_help() -> str:
return base_help + " Use " + aliases_text + "."
AI_ASSISTANT_HELP = _build_ai_assistant_help()


def _build_integration_equivalent(
integration_key: str,
ai_commands_dir: str | None = None,
) -> str:
"""Build the modern --integration equivalent for legacy --ai usage."""

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:
"""Build the legacy --ai deprecation warning message."""

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 1.0.0 or later.\n\n"
f"Use [bold]{replacement}[/bold] instead."
)

SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"}

CLAUDE_LOCAL_PATH = Path.home() / ".claude" / "local" / "claude"
Expand Down Expand Up @@ -957,6 +988,7 @@ def init(
"""

show_banner()
ai_deprecation_warning: str | None = None

# Detect when option values are likely misinterpreted flags (parameter ordering issue)
if ai_assistant and ai_assistant.startswith("--"):
Expand Down Expand Up @@ -995,6 +1027,10 @@ 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,
)

# Deprecation warnings for --ai-skills and --ai-commands-dir (only when
# an integration has been resolved from --ai or --integration)
Expand Down Expand Up @@ -1428,6 +1464,16 @@ 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)

steps_lines = []
if not here:
steps_lines.append(f"1. Go to the project folder: [cyan]cd {project_name}[/cyan]")
Expand Down
61 changes: 61 additions & 0 deletions tests/integrations/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@

import yaml

from tests.conftest import strip_ansi


def _normalize_cli_output(output: str) -> str:
output = strip_ansi(output)
output = " ".join(output.split())
return output.strip()


class TestInitIntegrationFlag:
def test_integration_and_ai_mutually_exclusive(self, tmp_path):
Expand Down Expand Up @@ -77,6 +85,59 @@ 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):
from typer.testing import CliRunner
from specify_cli import app

project = tmp_path / "warn-ai"
project.mkdir()
old_cwd = os.getcwd()
try:
os.chdir(project)
runner = CliRunner()
result = runner.invoke(app, [
"init", "--here", "--ai", "copilot", "--script", "sh", "--no-git",
], catch_exceptions=False)
finally:
os.chdir(old_cwd)

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 "1.0.0" in normalized_output
assert "--integration copilot" in normalized_output
assert normalized_output.index("Deprecation Warning") < normalized_output.index("Next Steps")
assert (project / ".github" / "agents" / "speckit.plan.agent.md").exists()

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

project = tmp_path / "warn-generic"
project.mkdir()
old_cwd = os.getcwd()
try:
os.chdir(project)
runner = CliRunner()
result = runner.invoke(app, [
"init", "--here", "--ai", "generic", "--ai-commands-dir", ".myagent/commands",
"--script", "sh", "--no-git",
], catch_exceptions=False)
finally:
os.chdir(old_cwd)

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 ".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_ai_claude_here_preserves_preexisting_commands(self, tmp_path):
from typer.testing import CliRunner
from specify_cli import app
Expand Down
Loading