chore: deprecate --ai flag in favor of --integration on specify init (#2218)

* chore: deprecate --ai flag in favor of --integration on specify init

- Adds deprecation warning when --ai is used
- Shows equivalent --integration command replacement
- Handles generic integration with --commands-dir mapping
- Adds comprehensive test coverage for deprecation behavior
- Warning displays as prominent red panel above Next Steps
- --ai flag continues to function (non-breaking change)

Fixes #2169

* Address PR review feedback for issue #2169

- Use existing strip_ansi helper from conftest instead of duplicating ANSI escape pattern
- Properly escape ai_commands_dir with shlex.quote() to handle paths with spaces
- Add shlex import to support proper command-line argument escaping
This commit is contained in:
Manfred Riem
2026-04-14 15:12:27 -05:00
committed by GitHub
parent 3467d26b1c
commit 39c7b04e5e
2 changed files with 107 additions and 0 deletions

View File

@@ -33,6 +33,7 @@ import shutil
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]")

View File

@@ -5,6 +5,14 @@ import os
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 @@ class TestInitIntegrationFlag:
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