mirror of
https://github.com/github/spec-kit.git
synced 2026-03-17 02:43:08 +00:00
* fix(cli): deprecate explicit command support for agy (#1798) * docs(cli): add tests and docs for agy deprecation (#1798) * fix: address review comments for agy deprecation * fix: address round 2 review comments for agy deprecation * fix: address round 3 review comments for agy deprecation * fix: address round 4 review comments for agy deprecation * fix: address round 5 review comments for agy deprecation * docs: add inline contextual comments to explain agy deprecation * docs: clarify historical context in agy deprecation docstring * fix: correct skills path in deprecation comment and make test mock fully deterministic
This commit is contained in:
@@ -442,7 +442,7 @@ function Build-Variant {
|
|||||||
if (Test-Path $tabnineTemplate) { Copy-Item $tabnineTemplate (Join-Path $baseDir 'TABNINE.md') }
|
if (Test-Path $tabnineTemplate) { Copy-Item $tabnineTemplate (Join-Path $baseDir 'TABNINE.md') }
|
||||||
}
|
}
|
||||||
'agy' {
|
'agy' {
|
||||||
$cmdDir = Join-Path $baseDir ".agent/workflows"
|
$cmdDir = Join-Path $baseDir ".agent/commands"
|
||||||
Generate-Commands -Agent 'agy' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
Generate-Commands -Agent 'agy' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
||||||
}
|
}
|
||||||
'vibe' {
|
'vibe' {
|
||||||
|
|||||||
@@ -280,8 +280,8 @@ build_variant() {
|
|||||||
mkdir -p "$base_dir/.kiro/prompts"
|
mkdir -p "$base_dir/.kiro/prompts"
|
||||||
generate_commands kiro-cli md "\$ARGUMENTS" "$base_dir/.kiro/prompts" "$script" ;;
|
generate_commands kiro-cli md "\$ARGUMENTS" "$base_dir/.kiro/prompts" "$script" ;;
|
||||||
agy)
|
agy)
|
||||||
mkdir -p "$base_dir/.agent/workflows"
|
mkdir -p "$base_dir/.agent/commands"
|
||||||
generate_commands agy md "\$ARGUMENTS" "$base_dir/.agent/workflows" "$script" ;;
|
generate_commands agy md "\$ARGUMENTS" "$base_dir/.agent/commands" "$script" ;;
|
||||||
bob)
|
bob)
|
||||||
mkdir -p "$base_dir/.bob/commands"
|
mkdir -p "$base_dir/.bob/commands"
|
||||||
generate_commands bob md "\$ARGUMENTS" "$base_dir/.bob/commands" "$script" ;;
|
generate_commands bob md "\$ARGUMENTS" "$base_dir/.bob/commands" "$script" ;;
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ This eliminates the need for special-case mappings throughout the codebase.
|
|||||||
- `folder`: Directory where agent-specific files are stored (relative to project root)
|
- `folder`: Directory where agent-specific files are stored (relative to project root)
|
||||||
- `commands_subdir`: Subdirectory name within the agent folder where command/prompt files are stored (default: `"commands"`)
|
- `commands_subdir`: Subdirectory name within the agent folder where command/prompt files are stored (default: `"commands"`)
|
||||||
- Most agents use `"commands"` (e.g., `.claude/commands/`)
|
- Most agents use `"commands"` (e.g., `.claude/commands/`)
|
||||||
- Some agents use alternative names: `"agents"` (copilot), `"workflows"` (windsurf, kilocode, agy), `"prompts"` (codex, kiro-cli), `"command"` (opencode - singular)
|
- Some agents use alternative names: `"agents"` (copilot), `"workflows"` (windsurf, kilocode), `"prompts"` (codex, kiro-cli), `"command"` (opencode - singular)
|
||||||
- This field enables `--ai-skills` to locate command templates correctly for skill generation
|
- This field enables `--ai-skills` to locate command templates correctly for skill generation
|
||||||
- `install_url`: Installation documentation URL (set to `None` for IDE-based agents)
|
- `install_url`: Installation documentation URL (set to `None` for IDE-based agents)
|
||||||
- `requires_cli`: Whether the agent requires a CLI tool check during initialization
|
- `requires_cli`: Whether the agent requires a CLI tool check during initialization
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ See Spec-Driven Development in action across different scenarios with these comm
|
|||||||
| [Mistral Vibe](https://github.com/mistralai/mistral-vibe) | ✅ | |
|
| [Mistral Vibe](https://github.com/mistralai/mistral-vibe) | ✅ | |
|
||||||
| [Kimi Code](https://code.kimi.com/) | ✅ | |
|
| [Kimi Code](https://code.kimi.com/) | ✅ | |
|
||||||
| [Windsurf](https://windsurf.com/) | ✅ | |
|
| [Windsurf](https://windsurf.com/) | ✅ | |
|
||||||
| [Antigravity (agy)](https://antigravity.google/) | ✅ | |
|
| [Antigravity (agy)](https://antigravity.google/) | ✅ | Requires `--ai-skills` |
|
||||||
| Generic | ✅ | Bring your own agent — use `--ai generic --ai-commands-dir <path>` for unsupported agents |
|
| Generic | ✅ | Bring your own agent — use `--ai generic --ai-commands-dir <path>` for unsupported agents |
|
||||||
|
|
||||||
## 🔧 Specify CLI Reference
|
## 🔧 Specify CLI Reference
|
||||||
@@ -248,7 +248,7 @@ specify init my-project --ai vibe
|
|||||||
specify init my-project --ai bob
|
specify init my-project --ai bob
|
||||||
|
|
||||||
# Initialize with Antigravity support
|
# Initialize with Antigravity support
|
||||||
specify init my-project --ai agy
|
specify init my-project --ai agy --ai-skills
|
||||||
|
|
||||||
# Initialize with an unsupported agent (generic / bring your own agent)
|
# Initialize with an unsupported agent (generic / bring your own agent)
|
||||||
specify init my-project --ai generic --ai-commands-dir .myagent/commands/
|
specify init my-project --ai generic --ai-commands-dir .myagent/commands/
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ AGENT_CONFIG = {
|
|||||||
"agy": {
|
"agy": {
|
||||||
"name": "Antigravity",
|
"name": "Antigravity",
|
||||||
"folder": ".agent/",
|
"folder": ".agent/",
|
||||||
"commands_subdir": "workflows", # Special: uses workflows/ not commands/
|
"commands_subdir": "commands",
|
||||||
"install_url": None, # IDE-based
|
"install_url": None, # IDE-based
|
||||||
"requires_cli": False,
|
"requires_cli": False,
|
||||||
},
|
},
|
||||||
@@ -1270,6 +1270,22 @@ def install_ai_skills(project_path: Path, selected_ai: str, tracker: StepTracker
|
|||||||
return installed_count > 0 or skipped_count > 0
|
return installed_count > 0 or skipped_count > 0
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_agy_deprecation(console: Console) -> None:
|
||||||
|
"""
|
||||||
|
Print the deprecation error for the Antigravity (agy) agent and exit.
|
||||||
|
|
||||||
|
Context for future AI reviewers (e.g., GitHub Copilot):
|
||||||
|
- Prior to Antigravity v1.20.5, users could rely on explicit agent command definitions generated by this tool.
|
||||||
|
- v1.20.5 removed this "command support" in favor of "Agent Skills".
|
||||||
|
- `specify_cli` handles this by throwing a fail-fast deprecation error if `--ai agy` is
|
||||||
|
invoked without the accompanying `--ai-skills` flag. Users are instructed to add
|
||||||
|
the skills flag to generate agent skills templates instead.
|
||||||
|
"""
|
||||||
|
console.print("\n[red]Error:[/red] Explicit command support was deprecated in Antigravity version 1.20.5.")
|
||||||
|
console.print("Please use [cyan]--ai-skills[/cyan] when initializing to install templates as agent skills instead.")
|
||||||
|
console.print("[yellow]Usage:[/yellow] specify init <project> --ai agy --ai-skills")
|
||||||
|
raise typer.Exit(1)
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def init(
|
def init(
|
||||||
project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here, or use '.' for current directory)"),
|
project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here, or use '.' for current directory)"),
|
||||||
@@ -1379,6 +1395,49 @@ def init(
|
|||||||
console.print(error_panel)
|
console.print(error_panel)
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
|
if ai_assistant:
|
||||||
|
if ai_assistant not in AGENT_CONFIG:
|
||||||
|
console.print(f"[red]Error:[/red] Invalid AI assistant '{ai_assistant}'. Choose from: {', '.join(AGENT_CONFIG.keys())}")
|
||||||
|
raise typer.Exit(1)
|
||||||
|
selected_ai = ai_assistant
|
||||||
|
else:
|
||||||
|
# Create options dict for selection (agent_key: display_name)
|
||||||
|
ai_choices = {key: config["name"] for key, config in AGENT_CONFIG.items()}
|
||||||
|
selected_ai = select_with_arrows(
|
||||||
|
ai_choices,
|
||||||
|
"Choose your AI assistant:",
|
||||||
|
"copilot"
|
||||||
|
)
|
||||||
|
|
||||||
|
# [DEPRECATION NOTICE: Antigravity (agy)]
|
||||||
|
# As of Antigravity v1.20.5, traditional CLI "command" support was fully removed
|
||||||
|
# in favor of "Agent Skills" (SKILL.md files under <agent_folder>/skills/<skill_name>/).
|
||||||
|
# Because 'specify_cli' historically populated .agent/commands/, we now must explicitly
|
||||||
|
# enforce the `--ai-skills` flag for `agy` to ensure valid template generation.
|
||||||
|
if selected_ai == "agy" and not ai_skills:
|
||||||
|
# If agy was selected interactively (no --ai provided), automatically enable
|
||||||
|
# ai_skills so the agent remains usable without requiring an extra flag.
|
||||||
|
# Preserve deprecation behavior only for explicit '--ai agy' without skills.
|
||||||
|
if ai_assistant:
|
||||||
|
_handle_agy_deprecation(console)
|
||||||
|
else:
|
||||||
|
ai_skills = True
|
||||||
|
console.print(
|
||||||
|
"\n[yellow]Note:[/yellow] 'agy' was selected interactively; "
|
||||||
|
"enabling [cyan]--ai-skills[/cyan] automatically for compatibility "
|
||||||
|
"(explicit .agent/commands usage is deprecated)."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Validate --ai-commands-dir usage
|
||||||
|
if selected_ai == "generic":
|
||||||
|
if not ai_commands_dir:
|
||||||
|
console.print("[red]Error:[/red] --ai-commands-dir is required when using --ai generic")
|
||||||
|
console.print("[dim]Example: specify init my-project --ai generic --ai-commands-dir .myagent/commands/[/dim]")
|
||||||
|
raise typer.Exit(1)
|
||||||
|
elif ai_commands_dir:
|
||||||
|
console.print(f"[red]Error:[/red] --ai-commands-dir can only be used with --ai generic (not '{selected_ai}')")
|
||||||
|
raise typer.Exit(1)
|
||||||
|
|
||||||
current_dir = Path.cwd()
|
current_dir = Path.cwd()
|
||||||
|
|
||||||
setup_lines = [
|
setup_lines = [
|
||||||
@@ -1399,30 +1458,6 @@ def init(
|
|||||||
if not should_init_git:
|
if not should_init_git:
|
||||||
console.print("[yellow]Git not found - will skip repository initialization[/yellow]")
|
console.print("[yellow]Git not found - will skip repository initialization[/yellow]")
|
||||||
|
|
||||||
if ai_assistant:
|
|
||||||
if ai_assistant not in AGENT_CONFIG:
|
|
||||||
console.print(f"[red]Error:[/red] Invalid AI assistant '{ai_assistant}'. Choose from: {', '.join(AGENT_CONFIG.keys())}")
|
|
||||||
raise typer.Exit(1)
|
|
||||||
selected_ai = ai_assistant
|
|
||||||
else:
|
|
||||||
# Create options dict for selection (agent_key: display_name)
|
|
||||||
ai_choices = {key: config["name"] for key, config in AGENT_CONFIG.items()}
|
|
||||||
selected_ai = select_with_arrows(
|
|
||||||
ai_choices,
|
|
||||||
"Choose your AI assistant:",
|
|
||||||
"copilot"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Validate --ai-commands-dir usage
|
|
||||||
if selected_ai == "generic":
|
|
||||||
if not ai_commands_dir:
|
|
||||||
console.print("[red]Error:[/red] --ai-commands-dir is required when using --ai generic")
|
|
||||||
console.print("[dim]Example: specify init my-project --ai generic --ai-commands-dir .myagent/commands/[/dim]")
|
|
||||||
raise typer.Exit(1)
|
|
||||||
elif ai_commands_dir:
|
|
||||||
console.print(f"[red]Error:[/red] --ai-commands-dir can only be used with --ai generic (not '{selected_ai}')")
|
|
||||||
raise typer.Exit(1)
|
|
||||||
|
|
||||||
if not ignore_agent_tools:
|
if not ignore_agent_tools:
|
||||||
agent_config = AGENT_CONFIG.get(selected_ai)
|
agent_config = AGENT_CONFIG.get(selected_ai)
|
||||||
if agent_config and agent_config["requires_cli"]:
|
if agent_config and agent_config["requires_cli"]:
|
||||||
|
|||||||
@@ -62,7 +62,14 @@ class TestAgentConfigConsistency:
|
|||||||
ps_text = (REPO_ROOT / ".github" / "workflows" / "scripts" / "create-release-packages.ps1").read_text(encoding="utf-8")
|
ps_text = (REPO_ROOT / ".github" / "workflows" / "scripts" / "create-release-packages.ps1").read_text(encoding="utf-8")
|
||||||
|
|
||||||
assert re.search(r"'shai'\s*\{.*?\.shai/commands", ps_text, re.S) is not None
|
assert re.search(r"'shai'\s*\{.*?\.shai/commands", ps_text, re.S) is not None
|
||||||
assert re.search(r"'agy'\s*\{.*?\.agent/workflows", ps_text, re.S) is not None
|
assert re.search(r"'agy'\s*\{.*?\.agent/commands", ps_text, re.S) is not None
|
||||||
|
|
||||||
|
def test_release_sh_switch_has_shai_and_agy_generation(self):
|
||||||
|
"""Bash release builder must generate files for shai and agy agents."""
|
||||||
|
sh_text = (REPO_ROOT / ".github" / "workflows" / "scripts" / "create-release-packages.sh").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert re.search(r"shai\)\s*\n.*?\.shai/commands", sh_text, re.S) is not None
|
||||||
|
assert re.search(r"agy\)\s*\n.*?\.agent/commands", sh_text, re.S) is not None
|
||||||
|
|
||||||
def test_init_ai_help_includes_roo_and_kiro_alias(self):
|
def test_init_ai_help_includes_roo_and_kiro_alias(self):
|
||||||
"""CLI help text for --ai should stay in sync with agent config and alias guidance."""
|
"""CLI help text for --ai should stay in sync with agent config and alias guidance."""
|
||||||
|
|||||||
@@ -661,6 +661,59 @@ class TestCliValidation:
|
|||||||
assert "Usage:" in result.output
|
assert "Usage:" in result.output
|
||||||
assert "--ai" in result.output
|
assert "--ai" in result.output
|
||||||
|
|
||||||
|
def test_agy_without_ai_skills_fails(self):
|
||||||
|
"""--ai agy without --ai-skills should fail with exit code 1."""
|
||||||
|
from typer.testing import CliRunner
|
||||||
|
|
||||||
|
runner = CliRunner()
|
||||||
|
result = runner.invoke(app, ["init", "test-proj", "--ai", "agy"])
|
||||||
|
|
||||||
|
assert result.exit_code == 1
|
||||||
|
assert "Explicit command support was deprecated in Antigravity version 1.20.5." in result.output
|
||||||
|
assert "--ai-skills" in result.output
|
||||||
|
|
||||||
|
def test_interactive_agy_without_ai_skills_prompts_skills(self, monkeypatch):
|
||||||
|
"""Interactive selector returning agy without --ai-skills should automatically enable --ai-skills."""
|
||||||
|
from typer.testing import CliRunner
|
||||||
|
|
||||||
|
# Mock select_with_arrows to simulate the user picking 'agy' for AI,
|
||||||
|
# and return a deterministic default for any other prompts to avoid
|
||||||
|
# calling the real interactive implementation.
|
||||||
|
def _fake_select_with_arrows(*args, **kwargs):
|
||||||
|
options = kwargs.get("options")
|
||||||
|
if options is None and len(args) >= 1:
|
||||||
|
options = args[0]
|
||||||
|
|
||||||
|
# If the options include 'agy', simulate selecting it.
|
||||||
|
if isinstance(options, dict) and "agy" in options:
|
||||||
|
return "agy"
|
||||||
|
if isinstance(options, (list, tuple)) and "agy" in options:
|
||||||
|
return "agy"
|
||||||
|
|
||||||
|
# For any other prompt, return a deterministic, non-interactive default:
|
||||||
|
# pick the first option if available.
|
||||||
|
if isinstance(options, dict) and options:
|
||||||
|
return next(iter(options.keys()))
|
||||||
|
if isinstance(options, (list, tuple)) and options:
|
||||||
|
return options[0]
|
||||||
|
|
||||||
|
# If no options are provided, fall back to None (should not occur in normal use).
|
||||||
|
return None
|
||||||
|
|
||||||
|
monkeypatch.setattr("specify_cli.select_with_arrows", _fake_select_with_arrows)
|
||||||
|
|
||||||
|
# Mock download_and_extract_template to prevent real HTTP downloads during testing
|
||||||
|
monkeypatch.setattr("specify_cli.download_and_extract_template", lambda *args, **kwargs: None)
|
||||||
|
# We need to bypass the `git init` step, wait, it has `--no-git` by default in tests maybe?
|
||||||
|
runner = CliRunner()
|
||||||
|
# Create temp dir to avoid directory already exists errors or whatever
|
||||||
|
with runner.isolated_filesystem():
|
||||||
|
result = runner.invoke(app, ["init", "test-proj", "--no-git"])
|
||||||
|
|
||||||
|
# Interactive selection should NOT raise the deprecation error!
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "Explicit command support was deprecated" not in result.output
|
||||||
|
|
||||||
def test_ai_skills_flag_appears_in_help(self):
|
def test_ai_skills_flag_appears_in_help(self):
|
||||||
"""--ai-skills should appear in init --help output."""
|
"""--ai-skills should appear in init --help output."""
|
||||||
from typer.testing import CliRunner
|
from typer.testing import CliRunner
|
||||||
|
|||||||
Reference in New Issue
Block a user