diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index c33281e2b..eb4c306bf 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -33,6 +33,7 @@ import json import json5 import stat +import shlex import yaml from pathlib import Path from typing import Any, Optional @@ -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" @@ -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("--"): @@ -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) @@ -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]") diff --git a/tests/integrations/test_cli.py b/tests/integrations/test_cli.py index 1e23e35a7..bd73ccd66 100644 --- a/tests/integrations/test_cli.py +++ b/tests/integrations/test_cli.py @@ -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): @@ -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