Skip to content

Commit 7106858

Browse files
CopilotmnriemCopilot
authored
feat!: remove legacy --ai, --ai-commands-dir, and --ai-skills flags (0.10.0) (#2872)
* Initial plan * feat!: remove legacy --ai, --ai-commands-dir, and --ai-skills flags at 0.10.0 * refactor(tests): rename stale test_ai_help_* methods to test_agent_config_* * fix: address review — derive agent folder for generic integration and remove redundant test - Security notice now falls back to integration_parsed_options['commands_dir'] when AGENT_CONFIG folder is None (generic integration). - Remove test_agent_config_includes_kiro_cli which duplicates the assertion in test_runtime_config_uses_kiro_cli_and_removes_q. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: scrub all remaining --ai flag references from source and tests - Remove dead AI_ASSISTANT_ALIASES, AI_ASSISTANT_HELP, and _build_ai_assistant_help() from _agent_config.py - Update comments/docstrings in extensions.py, presets.py, and integration subpackages to reference 'skills mode' or '--integration' instead of the removed flags - Fix catalog.json generic integration description - Update test docstrings/comments in test_extension_skills.py, test_extensions.py, and test_presets.py Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test: remove legacy --ai flag rejection tests The flags are fully removed from the CLI; typer handles unknown options generically. No custom rejection logic exists to test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * revert: remove manual CHANGELOG.md entry CHANGELOG is generated automatically; manual edits should not be made. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: make generic catalog description self-explanatory Include the required --commands-dir sub-option in the description so readers don't need to look up integration docs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(tests): rename duplicate test classes to avoid shadowing The rename from Test*AutoPromote to Test*Integration collided with the existing Test*Integration(SkillsIntegrationTests) base classes, causing the shared test suites to be silently overwritten. Rename the CLI init flow classes to Test*InitFlow instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Manfred Riem <mnriem@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 072b32c commit 7106858

29 files changed

Lines changed: 141 additions & 345 deletions

integrations/catalog.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@
277277
"id": "generic",
278278
"name": "Generic (bring your own agent)",
279279
"version": "1.0.0",
280-
"description": "Generic integration for any agent via --ai-commands-dir",
280+
"description": "Generic integration for any agent via --integration-options=\"--commands-dir <dir>\"",
281281
"author": "spec-kit-core",
282282
"repository": "https://github.com/github/spec-kit",
283283
"tags": ["generic"]

src/specify_cli/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,6 @@
8282
)
8383
from ._agent_config import (
8484
AGENT_CONFIG as AGENT_CONFIG,
85-
AI_ASSISTANT_ALIASES as AI_ASSISTANT_ALIASES,
86-
AI_ASSISTANT_HELP as AI_ASSISTANT_HELP,
8785
DEFAULT_INIT_INTEGRATION as DEFAULT_INIT_INTEGRATION,
8886
SCRIPT_TYPE_CHOICES as SCRIPT_TYPE_CHOICES,
8987
)

src/specify_cli/_agent_config.py

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,4 @@ def _build_agent_config() -> dict[str, dict[str, Any]]:
1717

1818
DEFAULT_INIT_INTEGRATION = "copilot"
1919

20-
AI_ASSISTANT_ALIASES: dict[str, str] = {
21-
"kiro": "kiro-cli",
22-
}
23-
24-
25-
def _build_ai_assistant_help() -> str:
26-
non_generic_agents = sorted(agent for agent in AGENT_CONFIG if agent != "generic")
27-
base_help = (
28-
f"AI assistant to use: {', '.join(non_generic_agents)}, "
29-
"or generic (requires --ai-commands-dir)."
30-
)
31-
if not AI_ASSISTANT_ALIASES:
32-
return base_help
33-
alias_phrases = []
34-
for alias, target in sorted(AI_ASSISTANT_ALIASES.items()):
35-
alias_phrases.append(f"'{alias}' as an alias for '{target}'")
36-
if len(alias_phrases) == 1:
37-
aliases_text = alias_phrases[0]
38-
else:
39-
aliases_text = ", ".join(alias_phrases[:-1]) + " and " + alias_phrases[-1]
40-
return base_help + " Use " + aliases_text + "."
41-
42-
43-
AI_ASSISTANT_HELP: str = _build_ai_assistant_help()
44-
4520
SCRIPT_TYPE_CHOICES: dict[str, str] = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"}

src/specify_cli/commands/init.py

Lines changed: 16 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from __future__ import annotations
33

44
import os
5-
import shlex
65
import shutil
76
import sys
87
from pathlib import Path
@@ -14,8 +13,6 @@
1413

1514
from .._agent_config import (
1615
AGENT_CONFIG,
17-
AI_ASSISTANT_ALIASES,
18-
AI_ASSISTANT_HELP,
1916
DEFAULT_INIT_INTEGRATION,
2017
SCRIPT_TYPE_CHOICES,
2118
)
@@ -28,31 +25,6 @@
2825
from .._console import StepTracker, console, select_with_arrows, show_banner
2926
from .._utils import check_tool, init_git_repo, is_git_repo
3027

31-
def _build_integration_equivalent(
32-
integration_key: str,
33-
ai_commands_dir: str | None = None,
34-
) -> str:
35-
parts = [f"--integration {integration_key}"]
36-
if integration_key == "generic" and ai_commands_dir:
37-
parts.append(
38-
f'--integration-options="--commands-dir {shlex.quote(ai_commands_dir)}"'
39-
)
40-
return " ".join(parts)
41-
42-
43-
def _build_ai_deprecation_warning(
44-
integration_key: str,
45-
ai_commands_dir: str | None = None,
46-
) -> str:
47-
replacement = _build_integration_equivalent(
48-
integration_key,
49-
ai_commands_dir=ai_commands_dir,
50-
)
51-
return (
52-
"[bold]--ai[/bold] is deprecated and will no longer be available in version 0.10.0 or later.\n\n"
53-
f"Use [bold]{replacement}[/bold] instead."
54-
)
55-
5628

5729
def _stdin_is_interactive() -> bool:
5830
return sys.stdin.isatty()
@@ -97,8 +69,6 @@ def register(app: typer.Typer) -> None:
9769
@app.command()
9870
def init(
9971
project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here, or use '.' for current directory)"),
100-
ai_assistant: str = typer.Option(None, "--ai", help=AI_ASSISTANT_HELP),
101-
ai_commands_dir: str = typer.Option(None, "--ai-commands-dir", help="Directory for agent command files (required with --ai generic, e.g. .myagent/commands/)"),
10272
script_type: str = typer.Option(None, "--script", help="Script type to use: sh or ps"),
10373
ignore_agent_tools: bool = typer.Option(False, "--ignore-agent-tools", help="Skip checks for coding agent tools like Claude Code"),
10474
no_git: bool = typer.Option(False, "--no-git", help="Skip git repository initialization"),
@@ -107,11 +77,10 @@ def init(
10777
skip_tls: bool = typer.Option(False, "--skip-tls", help="Deprecated (no-op). Previously: skip SSL/TLS verification.", hidden=True),
10878
debug: bool = typer.Option(False, "--debug", help="Deprecated. Previously: show verbose diagnostic output; currently only prints additional diagnostic details on failure.", hidden=True),
10979
github_token: str = typer.Option(None, "--github-token", help="Deprecated (no-op). Previously: GitHub token for API requests.", hidden=True),
110-
ai_skills: bool = typer.Option(False, "--ai-skills", help="Install Prompt.MD templates as agent skills (requires --ai)"),
11180
offline: bool = typer.Option(False, "--offline", help="Deprecated (no-op). All scaffolding now uses bundled assets.", hidden=True),
11281
preset: str = typer.Option(None, "--preset", help="Install a preset during initialization (by preset ID)"),
11382
branch_numbering: str = typer.Option(None, "--branch-numbering", help="Branch numbering strategy: 'sequential' (001, 002, …, 1000, … — expands past 999 automatically) or 'timestamp' (YYYYMMDD-HHMMSS)"),
114-
integration: str = typer.Option(None, "--integration", help="Use the new integration system (e.g. --integration copilot). Mutually exclusive with --ai."),
83+
integration: str = typer.Option(None, "--integration", help="AI coding agent integration to use (e.g. --integration copilot). See 'specify check' for available integrations."),
11584
integration_options: str = typer.Option(None, "--integration-options", help='Options for the integration (e.g. --integration-options="--commands-dir .myagent/cmds")'),
11685
):
11786
"""
@@ -163,27 +132,6 @@ def init(
163132
from ..integration_runtime import with_integration_setting as _with_integration_setting
164133

165134
show_banner()
166-
ai_deprecation_warning: str | None = None
167-
168-
if ai_assistant and ai_assistant.startswith("--"):
169-
console.print(f"[red]Error:[/red] Invalid value for --ai: '{ai_assistant}'")
170-
console.print("[yellow]Hint:[/yellow] Did you forget to provide a value for --ai?")
171-
console.print("[yellow]Example:[/yellow] specify init --integration claude --here")
172-
console.print(f"[yellow]Available agents:[/yellow] {', '.join(AGENT_CONFIG.keys())}")
173-
raise typer.Exit(1)
174-
175-
if ai_commands_dir and ai_commands_dir.startswith("--"):
176-
console.print(f"[red]Error:[/red] Invalid value for --ai-commands-dir: '{ai_commands_dir}'")
177-
console.print("[yellow]Hint:[/yellow] Did you forget to provide a value for --ai-commands-dir?")
178-
console.print("[yellow]Example:[/yellow] specify init --integration generic --integration-options=\"--commands-dir .myagent/commands/\"")
179-
raise typer.Exit(1)
180-
181-
if ai_assistant:
182-
ai_assistant = AI_ASSISTANT_ALIASES.get(ai_assistant, ai_assistant)
183-
184-
if integration and ai_assistant:
185-
console.print("[red]Error:[/red] --integration and --ai are mutually exclusive")
186-
raise typer.Exit(1)
187135

188136
from ..integrations import INTEGRATION_REGISTRY, get_integration
189137
if integration:
@@ -193,35 +141,6 @@ def init(
193141
available = ", ".join(sorted(INTEGRATION_REGISTRY))
194142
console.print(f"[yellow]Available integrations:[/yellow] {available}")
195143
raise typer.Exit(1)
196-
ai_assistant = integration
197-
elif ai_assistant:
198-
resolved_integration = get_integration(ai_assistant)
199-
if not resolved_integration:
200-
console.print(f"[red]Error:[/red] Unknown agent '{ai_assistant}'. Choose from: {', '.join(sorted(INTEGRATION_REGISTRY))}")
201-
raise typer.Exit(1)
202-
ai_deprecation_warning = _build_ai_deprecation_warning(
203-
resolved_integration.key,
204-
ai_commands_dir=ai_commands_dir,
205-
)
206-
207-
if ai_assistant or integration:
208-
if ai_skills:
209-
from ..integrations.base import SkillsIntegration as _SkillsCheck
210-
if isinstance(resolved_integration, _SkillsCheck):
211-
console.print(
212-
"[dim]Note: --ai-skills is not needed; "
213-
"skills are the default for this integration.[/dim]"
214-
)
215-
else:
216-
console.print(
217-
"[dim]Note: --ai-skills has no effect with "
218-
f"{resolved_integration.key}; this integration uses commands, not skills.[/dim]"
219-
)
220-
if ai_commands_dir and resolved_integration.key != "generic":
221-
console.print(
222-
"[dim]Note: --ai-commands-dir is deprecated; "
223-
'use [bold]--integration generic --integration-options="--commands-dir <dir>"[/bold] instead.[/dim]'
224-
)
225144

226145
if no_git:
227146
console.print(
@@ -242,11 +161,6 @@ def init(
242161
console.print("[red]Error:[/red] Must specify either a project name, use '.' for current directory, or use --here flag")
243162
raise typer.Exit(1)
244163

245-
if ai_skills and not ai_assistant:
246-
console.print("[red]Error:[/red] --ai-skills requires --ai to be specified")
247-
console.print("[yellow]Usage:[/yellow] specify init <project> --ai <agent> --ai-skills")
248-
raise typer.Exit(1)
249-
250164
BRANCH_NUMBERING_CHOICES = {"sequential", "timestamp"}
251165
if branch_numbering and branch_numbering not in BRANCH_NUMBERING_CHOICES:
252166
console.print(f"[red]Error:[/red] Invalid --branch-numbering value '{branch_numbering}'. Choose from: {', '.join(sorted(BRANCH_NUMBERING_CHOICES))}")
@@ -295,11 +209,11 @@ def init(
295209
console.print(error_panel)
296210
raise typer.Exit(1)
297211

298-
if ai_assistant:
299-
if ai_assistant not in AGENT_CONFIG:
300-
console.print(f"[red]Error:[/red] Invalid AI assistant '{ai_assistant}'. Choose from: {', '.join(AGENT_CONFIG.keys())}")
212+
if integration:
213+
if integration not in AGENT_CONFIG:
214+
console.print(f"[red]Error:[/red] Invalid integration '{integration}'. Choose from: {', '.join(AGENT_CONFIG.keys())}")
301215
raise typer.Exit(1)
302-
selected_ai = ai_assistant
216+
selected_ai = integration
303217
elif not _stdin_is_interactive():
304218
console.print(
305219
f"[dim]Non-interactive session detected: defaulting to '{DEFAULT_INIT_INTEGRATION}'. "
@@ -314,17 +228,16 @@ def init(
314228
DEFAULT_INIT_INTEGRATION,
315229
)
316230

317-
if not ai_assistant:
231+
if not integration:
318232
resolved_integration = get_integration(selected_ai)
319233
if not resolved_integration:
320234
console.print(f"[red]Error:[/red] Unknown agent '{selected_ai}'")
321235
raise typer.Exit(1)
322236

323237
if selected_ai == "generic" and not integration_options:
324-
if not ai_commands_dir:
325-
console.print("[red]Error:[/red] --ai-commands-dir is required when using --ai generic or --integration generic")
326-
console.print('[dim]Example: specify init my-project --integration generic --integration-options="--commands-dir .myagent/commands/"[/dim]')
327-
raise typer.Exit(1)
238+
console.print("[red]Error:[/red] --integration generic requires --integration-options with --commands-dir")
239+
console.print('[dim]Example: specify init my-project --integration generic --integration-options="--commands-dir .myagent/commands/"[/dim]')
240+
raise typer.Exit(1)
328241

329242
current_dir = Path.cwd()
330243

@@ -414,10 +327,6 @@ def init(
414327
)
415328

416329
integration_parsed_options: dict[str, Any] = {}
417-
if ai_commands_dir:
418-
integration_parsed_options["commands_dir"] = ai_commands_dir
419-
if ai_skills:
420-
integration_parsed_options["skills"] = True
421330
if integration_options:
422331
extra = _parse_integration_options(resolved_integration, integration_options)
423332
if extra:
@@ -675,7 +584,7 @@ def init(
675584

676585
agent_config = AGENT_CONFIG.get(selected_ai)
677586
if agent_config:
678-
agent_folder = ai_commands_dir if selected_ai == "generic" else agent_config["folder"]
587+
agent_folder = agent_config["folder"] or integration_parsed_options.get("commands_dir")
679588
if agent_folder:
680589
security_notice = Panel(
681590
f"Some agents may store credentials, auth tokens, or other identifying and private artifacts in the agent folder within your project.\n"
@@ -687,16 +596,6 @@ def init(
687596
console.print()
688597
console.print(security_notice)
689598

690-
if ai_deprecation_warning:
691-
deprecation_notice = Panel(
692-
ai_deprecation_warning,
693-
title="[bold red]Deprecation Warning[/bold red]",
694-
border_style="red",
695-
padding=(1, 2),
696-
)
697-
console.print()
698-
console.print(deprecation_notice)
699-
700599
if git_default_notice:
701600
default_change_notice = Panel(
702601
"The git extension is currently enabled by default during [bold]specify init[/bold].\n"
@@ -720,24 +619,24 @@ def init(
720619
from ..integrations.base import SkillsIntegration as _SkillsInt
721620
_is_skills_integration = isinstance(resolved_integration, _SkillsInt) or getattr(resolved_integration, "_skills_mode", False)
722621

723-
codex_skill_mode = selected_ai == "codex" and (ai_skills or _is_skills_integration)
724-
claude_skill_mode = selected_ai == "claude" and (ai_skills or _is_skills_integration)
622+
codex_skill_mode = selected_ai == "codex" and _is_skills_integration
623+
claude_skill_mode = selected_ai == "claude" and _is_skills_integration
725624
kimi_skill_mode = selected_ai == "kimi"
726625
agy_skill_mode = selected_ai == "agy" and _is_skills_integration
727626
trae_skill_mode = selected_ai == "trae"
728-
cursor_agent_skill_mode = selected_ai == "cursor-agent" and (ai_skills or _is_skills_integration)
627+
cursor_agent_skill_mode = selected_ai == "cursor-agent" and _is_skills_integration
729628
copilot_skill_mode = selected_ai == "copilot" and _is_skills_integration
730629
devin_skill_mode = selected_ai == "devin"
731630
cline_skill_mode = selected_ai == "cline"
732631
native_skill_mode = codex_skill_mode or claude_skill_mode or kimi_skill_mode or agy_skill_mode or trae_skill_mode or cursor_agent_skill_mode or copilot_skill_mode or devin_skill_mode
733632

734-
if codex_skill_mode and not ai_skills:
633+
if codex_skill_mode:
735634
steps_lines.append(f"{step_num}. Start Codex in this project directory; spec-kit skills were installed to [cyan].agents/skills[/cyan]")
736635
step_num += 1
737-
if claude_skill_mode and not ai_skills:
636+
if claude_skill_mode:
738637
steps_lines.append(f"{step_num}. Start Claude in this project directory; spec-kit skills were installed to [cyan].claude/skills[/cyan]")
739638
step_num += 1
740-
if cursor_agent_skill_mode and not ai_skills:
639+
if cursor_agent_skill_mode:
741640
steps_lines.append(f"{step_num}. Start Cursor Agent in this project directory; spec-kit skills were installed to [cyan].cursor/skills[/cyan]")
742641
step_num += 1
743642
if devin_skill_mode:

src/specify_cli/extensions.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -889,7 +889,7 @@ def _register_extension_skills(
889889
890890
For every command in the extension manifest, creates a SKILL.md
891891
file in the agent's skills directory following the agentskills.io
892-
specification. This is only done when ``--ai-skills`` was used
892+
specification. This is only done when skills mode was used
893893
during project initialisation.
894894
895895
Args:
@@ -1295,7 +1295,7 @@ def install_from_directory(
12951295
create_missing_active_skills_dir=True,
12961296
)
12971297

1298-
# Auto-register extension commands as agent skills when --ai-skills
1298+
# Auto-register extension commands as agent skills when skills mode
12991299
# was used during project initialisation (feature parity).
13001300
registered_skills = self._register_extension_skills(
13011301
manifest, dest_dir, link_outputs=link_commands

src/specify_cli/integrations/cursor_agent/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class CursorAgentIntegration(SkillsIntegration):
2222
"folder": ".cursor/",
2323
"commands_subdir": "skills",
2424
"install_url": "https://docs.cursor.com/en/cli/overview",
25-
# IDE-first integration: ``specify init --ai cursor-agent`` must
25+
# IDE-first integration: ``specify init --integration cursor-agent`` must
2626
# work without the ``cursor-agent`` CLI installed (the IDE flow
2727
# uses skills directly). Workflow dispatch additionally requires
2828
# the CLI on PATH, but that's enforced at dispatch time via

src/specify_cli/integrations/hermes/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
Usage::
88
99
specify init my-project --integration hermes
10-
specify init --here --ai hermes
10+
specify init --here --integration hermes
1111
"""
1212

1313
from __future__ import annotations

src/specify_cli/presets.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1219,7 +1219,7 @@ def _register_skills(
12191219
directory. If so, the skill is overwritten with content derived
12201220
from the preset's command file. This ensures that presets that
12211221
override commands also propagate to the agentskills.io skill
1222-
layer when ``--ai-skills`` was used during project initialisation.
1222+
layer when skills mode was used during project initialisation.
12231223
12241224
Args:
12251225
manifest: Preset manifest.
@@ -1559,7 +1559,7 @@ def install_from_directory(
15591559
"registered_commands": registered_commands,
15601560
})
15611561

1562-
# Update corresponding skills when --ai-skills was previously used
1562+
# Update corresponding skills when skills mode was previously used
15631563
# and persist that result as well.
15641564
registered_skills = self._register_skills(manifest, dest_dir)
15651565
self.registry.update(manifest.id, {

0 commit comments

Comments
 (0)