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>
This commit is contained in:
Copilot
2026-06-05 14:56:28 -05:00
committed by GitHub
parent 072b32cba0
commit 7106858c4e
29 changed files with 141 additions and 345 deletions

View File

@@ -277,7 +277,7 @@
"id": "generic",
"name": "Generic (bring your own agent)",
"version": "1.0.0",
"description": "Generic integration for any agent via --ai-commands-dir",
"description": "Generic integration for any agent via --integration-options=\"--commands-dir <dir>\"",
"author": "spec-kit-core",
"repository": "https://github.com/github/spec-kit",
"tags": ["generic"]

View File

@@ -82,8 +82,6 @@ from ._version import (
)
from ._agent_config import (
AGENT_CONFIG as AGENT_CONFIG,
AI_ASSISTANT_ALIASES as AI_ASSISTANT_ALIASES,
AI_ASSISTANT_HELP as AI_ASSISTANT_HELP,
DEFAULT_INIT_INTEGRATION as DEFAULT_INIT_INTEGRATION,
SCRIPT_TYPE_CHOICES as SCRIPT_TYPE_CHOICES,
)

View File

@@ -17,29 +17,4 @@ AGENT_CONFIG: dict[str, dict[str, Any]] = _build_agent_config()
DEFAULT_INIT_INTEGRATION = "copilot"
AI_ASSISTANT_ALIASES: dict[str, str] = {
"kiro": "kiro-cli",
}
def _build_ai_assistant_help() -> str:
non_generic_agents = sorted(agent for agent in AGENT_CONFIG if agent != "generic")
base_help = (
f"AI assistant to use: {', '.join(non_generic_agents)}, "
"or generic (requires --ai-commands-dir)."
)
if not AI_ASSISTANT_ALIASES:
return base_help
alias_phrases = []
for alias, target in sorted(AI_ASSISTANT_ALIASES.items()):
alias_phrases.append(f"'{alias}' as an alias for '{target}'")
if len(alias_phrases) == 1:
aliases_text = alias_phrases[0]
else:
aliases_text = ", ".join(alias_phrases[:-1]) + " and " + alias_phrases[-1]
return base_help + " Use " + aliases_text + "."
AI_ASSISTANT_HELP: str = _build_ai_assistant_help()
SCRIPT_TYPE_CHOICES: dict[str, str] = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"}

View File

@@ -2,7 +2,6 @@
from __future__ import annotations
import os
import shlex
import shutil
import sys
from pathlib import Path
@@ -14,8 +13,6 @@ from rich.panel import Panel
from .._agent_config import (
AGENT_CONFIG,
AI_ASSISTANT_ALIASES,
AI_ASSISTANT_HELP,
DEFAULT_INIT_INTEGRATION,
SCRIPT_TYPE_CHOICES,
)
@@ -28,31 +25,6 @@ from .._assets import (
from .._console import StepTracker, console, select_with_arrows, show_banner
from .._utils import check_tool, init_git_repo, is_git_repo
def _build_integration_equivalent(
integration_key: str,
ai_commands_dir: str | None = None,
) -> str:
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:
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 0.10.0 or later.\n\n"
f"Use [bold]{replacement}[/bold] instead."
)
def _stdin_is_interactive() -> bool:
return sys.stdin.isatty()
@@ -97,8 +69,6 @@ def register(app: typer.Typer) -> None:
@app.command()
def init(
project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here, or use '.' for current directory)"),
ai_assistant: str = typer.Option(None, "--ai", help=AI_ASSISTANT_HELP),
ai_commands_dir: str = typer.Option(None, "--ai-commands-dir", help="Directory for agent command files (required with --ai generic, e.g. .myagent/commands/)"),
script_type: str = typer.Option(None, "--script", help="Script type to use: sh or ps"),
ignore_agent_tools: bool = typer.Option(False, "--ignore-agent-tools", help="Skip checks for coding agent tools like Claude Code"),
no_git: bool = typer.Option(False, "--no-git", help="Skip git repository initialization"),
@@ -107,11 +77,10 @@ def register(app: typer.Typer) -> None:
skip_tls: bool = typer.Option(False, "--skip-tls", help="Deprecated (no-op). Previously: skip SSL/TLS verification.", hidden=True),
debug: bool = typer.Option(False, "--debug", help="Deprecated. Previously: show verbose diagnostic output; currently only prints additional diagnostic details on failure.", hidden=True),
github_token: str = typer.Option(None, "--github-token", help="Deprecated (no-op). Previously: GitHub token for API requests.", hidden=True),
ai_skills: bool = typer.Option(False, "--ai-skills", help="Install Prompt.MD templates as agent skills (requires --ai)"),
offline: bool = typer.Option(False, "--offline", help="Deprecated (no-op). All scaffolding now uses bundled assets.", hidden=True),
preset: str = typer.Option(None, "--preset", help="Install a preset during initialization (by preset ID)"),
branch_numbering: str = typer.Option(None, "--branch-numbering", help="Branch numbering strategy: 'sequential' (001, 002, …, 1000, … — expands past 999 automatically) or 'timestamp' (YYYYMMDD-HHMMSS)"),
integration: str = typer.Option(None, "--integration", help="Use the new integration system (e.g. --integration copilot). Mutually exclusive with --ai."),
integration: str = typer.Option(None, "--integration", help="AI coding agent integration to use (e.g. --integration copilot). See 'specify check' for available integrations."),
integration_options: str = typer.Option(None, "--integration-options", help='Options for the integration (e.g. --integration-options="--commands-dir .myagent/cmds")'),
):
"""
@@ -163,27 +132,6 @@ def register(app: typer.Typer) -> None:
from ..integration_runtime import with_integration_setting as _with_integration_setting
show_banner()
ai_deprecation_warning: str | None = None
if ai_assistant and ai_assistant.startswith("--"):
console.print(f"[red]Error:[/red] Invalid value for --ai: '{ai_assistant}'")
console.print("[yellow]Hint:[/yellow] Did you forget to provide a value for --ai?")
console.print("[yellow]Example:[/yellow] specify init --integration claude --here")
console.print(f"[yellow]Available agents:[/yellow] {', '.join(AGENT_CONFIG.keys())}")
raise typer.Exit(1)
if ai_commands_dir and ai_commands_dir.startswith("--"):
console.print(f"[red]Error:[/red] Invalid value for --ai-commands-dir: '{ai_commands_dir}'")
console.print("[yellow]Hint:[/yellow] Did you forget to provide a value for --ai-commands-dir?")
console.print("[yellow]Example:[/yellow] specify init --integration generic --integration-options=\"--commands-dir .myagent/commands/\"")
raise typer.Exit(1)
if ai_assistant:
ai_assistant = AI_ASSISTANT_ALIASES.get(ai_assistant, ai_assistant)
if integration and ai_assistant:
console.print("[red]Error:[/red] --integration and --ai are mutually exclusive")
raise typer.Exit(1)
from ..integrations import INTEGRATION_REGISTRY, get_integration
if integration:
@@ -193,35 +141,6 @@ def register(app: typer.Typer) -> None:
available = ", ".join(sorted(INTEGRATION_REGISTRY))
console.print(f"[yellow]Available integrations:[/yellow] {available}")
raise typer.Exit(1)
ai_assistant = integration
elif ai_assistant:
resolved_integration = get_integration(ai_assistant)
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,
)
if ai_assistant or integration:
if ai_skills:
from ..integrations.base import SkillsIntegration as _SkillsCheck
if isinstance(resolved_integration, _SkillsCheck):
console.print(
"[dim]Note: --ai-skills is not needed; "
"skills are the default for this integration.[/dim]"
)
else:
console.print(
"[dim]Note: --ai-skills has no effect with "
f"{resolved_integration.key}; this integration uses commands, not skills.[/dim]"
)
if ai_commands_dir and resolved_integration.key != "generic":
console.print(
"[dim]Note: --ai-commands-dir is deprecated; "
'use [bold]--integration generic --integration-options="--commands-dir <dir>"[/bold] instead.[/dim]'
)
if no_git:
console.print(
@@ -242,11 +161,6 @@ def register(app: typer.Typer) -> None:
console.print("[red]Error:[/red] Must specify either a project name, use '.' for current directory, or use --here flag")
raise typer.Exit(1)
if ai_skills and not ai_assistant:
console.print("[red]Error:[/red] --ai-skills requires --ai to be specified")
console.print("[yellow]Usage:[/yellow] specify init <project> --ai <agent> --ai-skills")
raise typer.Exit(1)
BRANCH_NUMBERING_CHOICES = {"sequential", "timestamp"}
if branch_numbering and branch_numbering not in BRANCH_NUMBERING_CHOICES:
console.print(f"[red]Error:[/red] Invalid --branch-numbering value '{branch_numbering}'. Choose from: {', '.join(sorted(BRANCH_NUMBERING_CHOICES))}")
@@ -295,11 +209,11 @@ def register(app: typer.Typer) -> None:
console.print(error_panel)
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())}")
if integration:
if integration not in AGENT_CONFIG:
console.print(f"[red]Error:[/red] Invalid integration '{integration}'. Choose from: {', '.join(AGENT_CONFIG.keys())}")
raise typer.Exit(1)
selected_ai = ai_assistant
selected_ai = integration
elif not _stdin_is_interactive():
console.print(
f"[dim]Non-interactive session detected: defaulting to '{DEFAULT_INIT_INTEGRATION}'. "
@@ -314,17 +228,16 @@ def register(app: typer.Typer) -> None:
DEFAULT_INIT_INTEGRATION,
)
if not ai_assistant:
if not integration:
resolved_integration = get_integration(selected_ai)
if not resolved_integration:
console.print(f"[red]Error:[/red] Unknown agent '{selected_ai}'")
raise typer.Exit(1)
if selected_ai == "generic" and not integration_options:
if not ai_commands_dir:
console.print("[red]Error:[/red] --ai-commands-dir is required when using --ai generic or --integration generic")
console.print('[dim]Example: specify init my-project --integration generic --integration-options="--commands-dir .myagent/commands/"[/dim]')
raise typer.Exit(1)
console.print("[red]Error:[/red] --integration generic requires --integration-options with --commands-dir")
console.print('[dim]Example: specify init my-project --integration generic --integration-options="--commands-dir .myagent/commands/"[/dim]')
raise typer.Exit(1)
current_dir = Path.cwd()
@@ -414,10 +327,6 @@ def register(app: typer.Typer) -> None:
)
integration_parsed_options: dict[str, Any] = {}
if ai_commands_dir:
integration_parsed_options["commands_dir"] = ai_commands_dir
if ai_skills:
integration_parsed_options["skills"] = True
if integration_options:
extra = _parse_integration_options(resolved_integration, integration_options)
if extra:
@@ -675,7 +584,7 @@ def register(app: typer.Typer) -> None:
agent_config = AGENT_CONFIG.get(selected_ai)
if agent_config:
agent_folder = ai_commands_dir if selected_ai == "generic" else agent_config["folder"]
agent_folder = agent_config["folder"] or integration_parsed_options.get("commands_dir")
if agent_folder:
security_notice = Panel(
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 register(app: typer.Typer) -> None:
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)
if git_default_notice:
default_change_notice = Panel(
"The git extension is currently enabled by default during [bold]specify init[/bold].\n"
@@ -720,24 +619,24 @@ def register(app: typer.Typer) -> None:
from ..integrations.base import SkillsIntegration as _SkillsInt
_is_skills_integration = isinstance(resolved_integration, _SkillsInt) or getattr(resolved_integration, "_skills_mode", False)
codex_skill_mode = selected_ai == "codex" and (ai_skills or _is_skills_integration)
claude_skill_mode = selected_ai == "claude" and (ai_skills or _is_skills_integration)
codex_skill_mode = selected_ai == "codex" and _is_skills_integration
claude_skill_mode = selected_ai == "claude" and _is_skills_integration
kimi_skill_mode = selected_ai == "kimi"
agy_skill_mode = selected_ai == "agy" and _is_skills_integration
trae_skill_mode = selected_ai == "trae"
cursor_agent_skill_mode = selected_ai == "cursor-agent" and (ai_skills or _is_skills_integration)
cursor_agent_skill_mode = selected_ai == "cursor-agent" and _is_skills_integration
copilot_skill_mode = selected_ai == "copilot" and _is_skills_integration
devin_skill_mode = selected_ai == "devin"
cline_skill_mode = selected_ai == "cline"
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
if codex_skill_mode and not ai_skills:
if codex_skill_mode:
steps_lines.append(f"{step_num}. Start Codex in this project directory; spec-kit skills were installed to [cyan].agents/skills[/cyan]")
step_num += 1
if claude_skill_mode and not ai_skills:
if claude_skill_mode:
steps_lines.append(f"{step_num}. Start Claude in this project directory; spec-kit skills were installed to [cyan].claude/skills[/cyan]")
step_num += 1
if cursor_agent_skill_mode and not ai_skills:
if cursor_agent_skill_mode:
steps_lines.append(f"{step_num}. Start Cursor Agent in this project directory; spec-kit skills were installed to [cyan].cursor/skills[/cyan]")
step_num += 1
if devin_skill_mode:

View File

@@ -889,7 +889,7 @@ class ExtensionManager:
For every command in the extension manifest, creates a SKILL.md
file in the agent's skills directory following the agentskills.io
specification. This is only done when ``--ai-skills`` was used
specification. This is only done when skills mode was used
during project initialisation.
Args:
@@ -1295,7 +1295,7 @@ class ExtensionManager:
create_missing_active_skills_dir=True,
)
# Auto-register extension commands as agent skills when --ai-skills
# Auto-register extension commands as agent skills when skills mode
# was used during project initialisation (feature parity).
registered_skills = self._register_extension_skills(
manifest, dest_dir, link_outputs=link_commands

View File

@@ -22,7 +22,7 @@ class CursorAgentIntegration(SkillsIntegration):
"folder": ".cursor/",
"commands_subdir": "skills",
"install_url": "https://docs.cursor.com/en/cli/overview",
# IDE-first integration: ``specify init --ai cursor-agent`` must
# IDE-first integration: ``specify init --integration cursor-agent`` must
# work without the ``cursor-agent`` CLI installed (the IDE flow
# uses skills directly). Workflow dispatch additionally requires
# the CLI on PATH, but that's enforced at dispatch time via

View File

@@ -7,7 +7,7 @@ AI agent framework by Nous Research. It stores skills in
Usage::
specify init my-project --integration hermes
specify init --here --ai hermes
specify init --here --integration hermes
"""
from __future__ import annotations

View File

@@ -1219,7 +1219,7 @@ class PresetManager:
directory. If so, the skill is overwritten with content derived
from the preset's command file. This ensures that presets that
override commands also propagate to the agentskills.io skill
layer when ``--ai-skills`` was used during project initialisation.
layer when skills mode was used during project initialisation.
Args:
manifest: Preset manifest.
@@ -1559,7 +1559,7 @@ class PresetManager:
"registered_commands": registered_commands,
})
# Update corresponding skills when --ai-skills was previously used
# Update corresponding skills when skills mode was previously used
# and persist that result as well.
registered_skills = self._register_skills(manifest, dest_dir)
self.registry.update(manifest.id, {

View File

@@ -43,16 +43,6 @@ class TestCliDiagnosticFormatting:
class TestInitIntegrationFlag:
def test_integration_and_ai_mutually_exclusive(self, tmp_path):
from typer.testing import CliRunner
from specify_cli import app
runner = CliRunner()
result = runner.invoke(app, [
"init", str(tmp_path / "test-project"), "--ai", "claude", "--integration", "copilot",
])
assert result.exit_code != 0
assert "mutually exclusive" in result.output
def test_unknown_integration_rejected(self, tmp_path):
from typer.testing import CliRunner
from specify_cli import app
@@ -131,7 +121,7 @@ class TestInitIntegrationFlag:
data = json.loads((project / ".specify" / "integration.json").read_text(encoding="utf-8"))
assert data["integration"] == specify_cli.DEFAULT_INIT_INTEGRATION
def test_ai_copilot_auto_promotes(self, tmp_path):
def test_integration_copilot_auto_promotes(self, tmp_path):
from typer.testing import CliRunner
from specify_cli import app
project = tmp_path / "promote-test"
@@ -141,66 +131,13 @@ class TestInitIntegrationFlag:
os.chdir(project)
runner = CliRunner()
result = runner.invoke(app, [
"init", "--here", "--ai", "copilot", "--script", "sh", "--no-git",
"init", "--here", "--integration", "copilot", "--script", "sh", "--no-git",
], catch_exceptions=False)
finally:
os.chdir(old_cwd)
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 "0.10.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_init_optional_preset_failure_reports_target_and_continues(
self, tmp_path, monkeypatch
):
@@ -237,7 +174,7 @@ class TestInitIntegrationFlag:
assert "Continuing without the optional preset" in normalized
assert "Project ready" in normalized
def test_ai_claude_here_preserves_preexisting_commands(self, tmp_path):
def test_integration_claude_here_preserves_preexisting_commands(self, tmp_path):
from typer.testing import CliRunner
from specify_cli import app
@@ -255,7 +192,7 @@ class TestInitIntegrationFlag:
os.chdir(project)
runner = CliRunner()
result = runner.invoke(app, [
"init", "--here", "--force", "--ai", "claude", "--ai-skills", "--script", "sh", "--no-git", "--ignore-agent-tools",
"init", "--here", "--force", "--integration", "claude", "--script", "sh", "--no-git", "--ignore-agent-tools",
], catch_exceptions=False)
finally:
os.chdir(old_cwd)
@@ -800,7 +737,7 @@ class TestGitExtensionAutoInstall:
os.chdir(project)
runner = CliRunner()
result = runner.invoke(app, [
"init", "--here", "--ai", "claude", "--script", "sh",
"init", "--here", "--integration", "claude", "--script", "sh",
"--ignore-agent-tools",
], catch_exceptions=False)
finally:
@@ -838,7 +775,7 @@ class TestGitExtensionAutoInstall:
os.chdir(project)
runner = CliRunner()
result = runner.invoke(app, [
"init", "--here", "--ai", "claude", "--script", "sh",
"init", "--here", "--integration", "claude", "--script", "sh",
"--no-git", "--ignore-agent-tools",
], catch_exceptions=False)
finally:
@@ -862,7 +799,7 @@ class TestGitExtensionAutoInstall:
os.chdir(project)
runner = CliRunner()
result = runner.invoke(app, [
"init", "--here", "--ai", "claude", "--script", "sh",
"init", "--here", "--integration", "claude", "--script", "sh",
"--no-git", "--ignore-agent-tools",
], catch_exceptions=False)
finally:
@@ -889,7 +826,7 @@ class TestGitExtensionAutoInstall:
os.chdir(project)
runner = CliRunner()
result = runner.invoke(app, [
"init", "--here", "--ai", "claude", "--script", "sh",
"init", "--here", "--integration", "claude", "--script", "sh",
"--ignore-agent-tools",
], catch_exceptions=False)
finally:
@@ -915,7 +852,7 @@ class TestGitExtensionAutoInstall:
os.chdir(project)
runner = CliRunner()
result = runner.invoke(app, [
"init", "--here", "--ai", "claude", "--script", "sh",
"init", "--here", "--integration", "claude", "--script", "sh",
"--ignore-agent-tools",
], catch_exceptions=False)
finally:

View File

@@ -29,19 +29,19 @@ class TestAgyIntegration(SkillsIntegrationTests):
assert i.config["install_url"] == "https://antigravity.google/"
class TestAgyAutoPromote:
"""--ai agy auto-promotes to integration path."""
class TestAgyInitFlow:
"""--integration agy creates expected files."""
def test_ai_agy_without_ai_skills_auto_promotes(self, tmp_path):
"""--ai agy should work the same as --integration agy."""
def test_integration_agy_creates_skills(self, tmp_path):
"""--integration agy should create skills directory."""
from typer.testing import CliRunner
from specify_cli import app
runner = CliRunner()
target = tmp_path / "test-proj"
result = runner.invoke(app, ["init", str(target), "--ai", "agy", "--no-git", "--script", "sh", "--ignore-agent-tools"])
result = runner.invoke(app, ["init", str(target), "--integration", "agy", "--no-git", "--script", "sh", "--ignore-agent-tools"])
assert result.exit_code == 0, f"init --ai agy failed: {result.output}"
assert result.exit_code == 0, f"init --integration agy failed: {result.output}"
assert (target / ".agents" / "skills" / "speckit-plan" / "SKILL.md").exists()
def test_agy_setup_warning(self, tmp_path):
@@ -52,7 +52,7 @@ class TestAgyAutoPromote:
# Click >= 8.2 separates stdout and stderr natively
runner = CliRunner()
target = tmp_path / "test-proj2"
result = runner.invoke(app, ["init", str(target), "--ai", "agy", "--no-git", "--script", "sh", "--ignore-agent-tools"])
result = runner.invoke(app, ["init", str(target), "--integration", "agy", "--no-git", "--script", "sh", "--ignore-agent-tools"])
assert result.exit_code == 0
assert "Warning: The .agents/ layout requires Antigravity v1.20.5 or newer" in result.stderr

View File

@@ -179,9 +179,9 @@ class MarkdownIntegrationTests:
assert "<!-- SPECKIT END -->" not in remaining
assert "# My Rules" in remaining
# -- CLI auto-promote -------------------------------------------------
# -- CLI integration flag -------------------------------------------------
def test_ai_flag_auto_promotes(self, tmp_path):
def test_integration_flag_auto_promotes(self, tmp_path):
from typer.testing import CliRunner
from specify_cli import app
@@ -192,15 +192,15 @@ class MarkdownIntegrationTests:
os.chdir(project)
runner = CliRunner()
result = runner.invoke(app, [
"init", "--here", "--ai", self.KEY, "--script", "sh", "--no-git",
"init", "--here", "--integration", self.KEY, "--script", "sh", "--no-git",
"--ignore-agent-tools",
], catch_exceptions=False)
finally:
os.chdir(old_cwd)
assert result.exit_code == 0, f"init --ai {self.KEY} failed: {result.output}"
assert result.exit_code == 0, f"init --integration {self.KEY} failed: {result.output}"
i = get_integration(self.KEY)
cmd_dir = i.commands_dest(project)
assert cmd_dir.is_dir(), f"--ai {self.KEY} did not create commands directory"
assert cmd_dir.is_dir(), f"--integration {self.KEY} did not create commands directory"
def test_integration_flag_creates_files(self, tmp_path):
from typer.testing import CliRunner

View File

@@ -312,9 +312,9 @@ class SkillsIntegrationTests:
assert "<!-- SPECKIT END -->" not in remaining
assert "# My Rules" in remaining
# -- CLI auto-promote -------------------------------------------------
# -- CLI integration flag -------------------------------------------------
def test_ai_flag_auto_promotes(self, tmp_path):
def test_integration_flag_auto_promotes(self, tmp_path):
from typer.testing import CliRunner
from specify_cli import app
@@ -325,15 +325,15 @@ class SkillsIntegrationTests:
os.chdir(project)
runner = CliRunner()
result = runner.invoke(app, [
"init", "--here", "--ai", self.KEY, "--script", "sh", "--no-git",
"init", "--here", "--integration", self.KEY, "--script", "sh", "--no-git",
"--ignore-agent-tools",
], catch_exceptions=False)
finally:
os.chdir(old_cwd)
assert result.exit_code == 0, f"init --ai {self.KEY} failed: {result.output}"
assert result.exit_code == 0, f"init --integration {self.KEY} failed: {result.output}"
i = get_integration(self.KEY)
skills_dir = i.skills_dest(project)
assert skills_dir.is_dir(), f"--ai {self.KEY} did not create skills directory"
assert skills_dir.is_dir(), f"--integration {self.KEY} did not create skills directory"
def test_integration_flag_creates_files(self, tmp_path):
from typer.testing import CliRunner

View File

@@ -388,9 +388,9 @@ class TomlIntegrationTests:
assert "<!-- SPECKIT END -->" not in remaining
assert "# My Rules" in remaining
# -- CLI auto-promote -------------------------------------------------
# -- CLI integration flag -------------------------------------------------
def test_ai_flag_auto_promotes(self, tmp_path):
def test_integration_flag_auto_promotes(self, tmp_path):
from typer.testing import CliRunner
from specify_cli import app
@@ -405,7 +405,7 @@ class TomlIntegrationTests:
[
"init",
"--here",
"--ai",
"--integration",
self.KEY,
"--script",
"sh",
@@ -416,10 +416,10 @@ class TomlIntegrationTests:
)
finally:
os.chdir(old_cwd)
assert result.exit_code == 0, f"init --ai {self.KEY} failed: {result.output}"
assert result.exit_code == 0, f"init --integration {self.KEY} failed: {result.output}"
i = get_integration(self.KEY)
cmd_dir = i.commands_dest(project)
assert cmd_dir.is_dir(), f"--ai {self.KEY} did not create commands directory"
assert cmd_dir.is_dir(), f"--integration {self.KEY} did not create commands directory"
def test_integration_flag_creates_files(self, tmp_path):
from typer.testing import CliRunner

View File

@@ -267,9 +267,9 @@ class YamlIntegrationTests:
assert "<!-- SPECKIT END -->" not in remaining
assert "# My Rules" in remaining
# -- CLI auto-promote -------------------------------------------------
# -- CLI integration flag -------------------------------------------------
def test_ai_flag_auto_promotes(self, tmp_path):
def test_integration_flag_auto_promotes(self, tmp_path):
from typer.testing import CliRunner
from specify_cli import app
@@ -284,7 +284,7 @@ class YamlIntegrationTests:
[
"init",
"--here",
"--ai",
"--integration",
self.KEY,
"--script",
"sh",
@@ -295,10 +295,10 @@ class YamlIntegrationTests:
)
finally:
os.chdir(old_cwd)
assert result.exit_code == 0, f"init --ai {self.KEY} failed: {result.output}"
assert result.exit_code == 0, f"init --integration {self.KEY} failed: {result.output}"
i = get_integration(self.KEY)
cmd_dir = i.commands_dest(project)
assert cmd_dir.is_dir(), f"--ai {self.KEY} did not create commands directory"
assert cmd_dir.is_dir(), f"--integration {self.KEY} did not create commands directory"
def test_integration_flag_creates_files(self, tmp_path):
from typer.testing import CliRunner

View File

@@ -118,7 +118,7 @@ class TestClaudeIntegration:
assert b"<!-- SPECKIT" not in remaining
assert b"# CLAUDE.md" in remaining
def test_ai_flag_auto_promotes_and_enables_skills(self, tmp_path):
def test_integration_flag_creates_skill_files_cli(self, tmp_path):
from typer.testing import CliRunner
from specify_cli import app
@@ -133,7 +133,7 @@ class TestClaudeIntegration:
[
"init",
"--here",
"--ai",
"--integration",
"claude",
"--script",
"sh",
@@ -234,7 +234,7 @@ class TestClaudeIntegration:
assert init_options["integration"] == "claude"
def test_claude_init_remains_usable_when_converter_fails(self, tmp_path):
"""Claude init should succeed even without install_ai_skills."""
"""Claude init should succeed even without install_skills."""
from typer.testing import CliRunner
from specify_cli import app
@@ -243,7 +243,7 @@ class TestClaudeIntegration:
result = runner.invoke(
app,
["init", str(target), "--ai", "claude", "--script", "sh", "--no-git", "--ignore-agent-tools"],
["init", str(target), "--integration", "claude", "--script", "sh", "--no-git", "--ignore-agent-tools"],
)
assert result.exit_code == 0

View File

@@ -14,19 +14,19 @@ class TestCodexIntegration(SkillsIntegrationTests):
CONTEXT_FILE = "AGENTS.md"
class TestCodexAutoPromote:
"""--ai codex auto-promotes to integration path."""
class TestCodexInitFlow:
"""--integration codex creates expected files."""
def test_ai_codex_without_ai_skills_auto_promotes(self, tmp_path):
"""--ai codex should work the same as --integration codex."""
def test_integration_codex_creates_skills(self, tmp_path):
"""--integration codex should create skills in .agents/skills."""
from typer.testing import CliRunner
from specify_cli import app
runner = CliRunner()
target = tmp_path / "test-proj"
result = runner.invoke(app, ["init", str(target), "--ai", "codex", "--no-git", "--ignore-agent-tools", "--script", "sh"])
result = runner.invoke(app, ["init", str(target), "--integration", "codex", "--no-git", "--ignore-agent-tools", "--script", "sh"])
assert result.exit_code == 0, f"init --ai codex failed: {result.output}"
assert result.exit_code == 0, f"init --integration codex failed: {result.output}"
assert (target / ".agents" / "skills" / "speckit-plan" / "SKILL.md").exists()

View File

@@ -92,19 +92,19 @@ class TestCursorMdcFrontmatter:
assert not ctx_path.exists()
class TestCursorAgentAutoPromote:
"""--ai cursor-agent auto-promotes to integration path."""
class TestCursorAgentInitFlow:
"""--integration cursor-agent creates expected files."""
def test_ai_cursor_agent_without_ai_skills_auto_promotes(self, tmp_path):
"""--ai cursor-agent should work the same as --integration cursor-agent."""
def test_integration_cursor_agent_creates_skills(self, tmp_path):
"""--integration cursor-agent should create skills in .cursor/skills."""
from typer.testing import CliRunner
from specify_cli import app
runner = CliRunner()
target = tmp_path / "test-proj"
result = runner.invoke(app, ["init", str(target), "--ai", "cursor-agent", "--no-git", "--ignore-agent-tools", "--script", "sh"])
result = runner.invoke(app, ["init", str(target), "--integration", "cursor-agent", "--no-git", "--ignore-agent-tools", "--script", "sh"])
assert result.exit_code == 0, f"init --ai cursor-agent failed: {result.output}"
assert result.exit_code == 0, f"init --integration cursor-agent failed: {result.output}"
assert (target / ".cursor" / "skills" / "speckit-plan" / "SKILL.md").exists()
@@ -120,7 +120,7 @@ class TestCursorAgentCliDispatch:
def test_requires_cli_is_false_for_ide_first_flow(self):
"""``requires_cli`` must stay False so the IDE-only flow keeps working.
``specify init --ai cursor-agent`` (without ``--ignore-agent-tools``)
``specify init --integration cursor-agent`` (without ``--ignore-agent-tools``)
treats ``requires_cli=True`` as a hard precheck and fails when the
``cursor-agent`` CLI isn't on PATH — even though the Cursor IDE
/ skills flow can run without it. Workflow dispatch support is

View File

@@ -56,11 +56,11 @@ class TestDevinBuildExecArgs:
assert args == ["devin", "-p", "hi", "--model", "claude-sonnet-4"]
class TestDevinAutoPromote:
"""--ai devin auto-promotes to integration path."""
class TestDevinInitFlow:
"""--integration devin creates expected files."""
def test_ai_devin_without_ai_skills_auto_promotes(self, tmp_path):
"""--ai devin should work the same as --integration devin."""
def test_integration_devin_creates_skills(self, tmp_path):
"""--integration devin should create skills directory."""
from typer.testing import CliRunner
from specify_cli import app
@@ -68,8 +68,8 @@ class TestDevinAutoPromote:
target = tmp_path / "test-proj"
result = runner.invoke(
app,
["init", str(target), "--ai", "devin", "--no-git", "--ignore-agent-tools", "--script", "sh"],
["init", str(target), "--integration", "devin", "--no-git", "--ignore-agent-tools", "--script", "sh"],
)
assert result.exit_code == 0, f"init --ai devin failed: {result.output}"
assert result.exit_code == 0, f"init --integration devin failed: {result.output}"
assert (target / ".devin" / "skills" / "speckit-plan" / "SKILL.md").exists()

View File

@@ -245,7 +245,7 @@ class TestGenericIntegration:
# -- CLI --------------------------------------------------------------
def test_cli_generic_without_commands_dir_fails(self, tmp_path):
"""--integration generic without --ai-commands-dir should fail."""
"""--integration generic without --integration-options should fail."""
from typer.testing import CliRunner
from specify_cli import app
runner = CliRunner()
@@ -253,8 +253,7 @@ class TestGenericIntegration:
"init", str(tmp_path / "test-generic"), "--integration", "generic",
"--script", "sh", "--no-git",
])
# Generic requires --commands-dir / --ai-commands-dir
# The integration path validates via setup()
# Generic requires --commands-dir via --integration-options
assert result.exit_code != 0
def test_init_options_includes_context_file(self, tmp_path):
@@ -270,7 +269,7 @@ class TestGenericIntegration:
os.chdir(project)
result = CliRunner().invoke(app, [
"init", "--here", "--integration", "generic",
"--ai-commands-dir", ".myagent/commands",
"--integration-options=--commands-dir .myagent/commands",
"--script", "sh", "--no-git",
], catch_exceptions=False)
finally:
@@ -281,7 +280,7 @@ class TestGenericIntegration:
assert ext_cfg.get("context_file") == "AGENTS.md"
def test_complete_file_inventory_sh(self, tmp_path):
"""Every file produced by specify init --integration generic --ai-commands-dir ... --script sh."""
"""Every file produced by specify init --integration generic --integration-options=--commands-dir ... --script sh."""
from typer.testing import CliRunner
from specify_cli import app
@@ -292,7 +291,7 @@ class TestGenericIntegration:
os.chdir(project)
result = CliRunner().invoke(app, [
"init", "--here", "--integration", "generic",
"--ai-commands-dir", ".myagent/commands",
"--integration-options=--commands-dir .myagent/commands",
"--script", "sh", "--no-git",
], catch_exceptions=False)
finally:
@@ -345,7 +344,7 @@ class TestGenericIntegration:
)
def test_complete_file_inventory_ps(self, tmp_path):
"""Every file produced by specify init --integration generic --ai-commands-dir ... --script ps."""
"""Every file produced by specify init --integration generic --integration-options=--commands-dir ... --script ps."""
from typer.testing import CliRunner
from specify_cli import app
@@ -356,7 +355,7 @@ class TestGenericIntegration:
os.chdir(project)
result = CliRunner().invoke(app, [
"init", "--here", "--integration", "generic",
"--ai-commands-dir", ".myagent/commands",
"--integration-options=--commands-dir .myagent/commands",
"--script", "ps", "--no-git",
], catch_exceptions=False)
finally:

View File

@@ -326,12 +326,11 @@ class TestHermesIntegration(SkillsIntegrationTests):
)
class TestHermesAutoPromote:
"""--ai hermes auto-promotes to integration path."""
class TestHermesInitFlow:
"""--integration hermes creates expected files."""
def test_ai_hermes_without_ai_skills_auto_promotes(self, tmp_path, monkeypatch):
"""--ai hermes should work the same as --integration hermes,
creating global skills and a local marker."""
def test_integration_hermes_creates_global_skills(self, tmp_path, monkeypatch):
"""--integration hermes should create global skills and a local marker."""
home = _fake_home(tmp_path)
monkeypatch.setattr(Path, "home", lambda: home)
@@ -342,13 +341,13 @@ class TestHermesAutoPromote:
target = tmp_path / "test-proj"
result = runner.invoke(app, [
"init", str(target),
"--ai", "hermes",
"--integration", "hermes",
"--no-git",
"--ignore-agent-tools",
"--script", "sh",
])
assert result.exit_code == 0, f"init --ai hermes failed: {result.output}"
assert result.exit_code == 0, f"init --integration hermes failed: {result.output}"
# Skills should be in global ~/.hermes/skills/
assert (home / ".hermes" / "skills" / "speckit-plan" / "SKILL.md").exists()
# Local marker should exist

View File

@@ -137,7 +137,7 @@ class TestKimiNextSteps:
os.chdir(project)
runner = CliRunner()
result = runner.invoke(app, [
"init", "--here", "--ai", "kimi", "--no-git",
"init", "--here", "--integration", "kimi", "--no-git",
"--ignore-agent-tools", "--script", "sh",
], catch_exceptions=False)
finally:

View File

@@ -123,15 +123,15 @@ class TestKiroCliIntegration(MarkdownIntegrationTests):
)
class TestKiroAlias:
"""--ai kiro alias normalizes to kiro-cli and auto-promotes."""
class TestKiroIntegration:
"""--integration kiro-cli creates expected files."""
def test_kiro_alias_normalized_to_kiro_cli(self, tmp_path):
"""--ai kiro should normalize to canonical kiro-cli and auto-promote."""
def test_integration_kiro_cli_creates_files(self, tmp_path):
"""--integration kiro-cli should create files in .kiro/prompts."""
from typer.testing import CliRunner
from specify_cli import app
target = tmp_path / "kiro-alias-proj"
target = tmp_path / "kiro-proj"
target.mkdir()
old_cwd = os.getcwd()
@@ -139,7 +139,7 @@ class TestKiroAlias:
os.chdir(target)
runner = CliRunner()
result = runner.invoke(app, [
"init", "--here", "--ai", "kiro",
"init", "--here", "--integration", "kiro-cli",
"--ignore-agent-tools", "--script", "sh", "--no-git",
], catch_exceptions=False)
finally:

View File

@@ -294,11 +294,11 @@ class TestRovodevIntegration:
assert init_options.get("ai_skills") is True
assert init_options.get("script") == "sh"
def test_ai_flag_auto_promotes_to_integration(self, tmp_path):
"""``--ai rovodev`` should reach the same end-state as ``--integration rovodev``."""
project = tmp_path / "rovodev-ai"
def test_integration_flag_creates_expected_files(self, tmp_path):
"""``--integration rovodev`` should create all expected rovodev files."""
project = tmp_path / "rovodev-int"
project.mkdir()
result = _run_init(project, "--ai", "rovodev")
result = _run_init(project, "--integration", "rovodev")
assert result.exit_code == 0, result.output
assert (project / ".rovodev" / "skills" / "speckit-plan" / "SKILL.md").exists()
assert (project / ".rovodev" / "prompts.yml").exists()

View File

@@ -2,7 +2,7 @@
from pathlib import Path
from specify_cli import AGENT_CONFIG, AI_ASSISTANT_ALIASES, AI_ASSISTANT_HELP
from specify_cli import AGENT_CONFIG
from specify_cli.extensions import CommandRegistrar
REPO_ROOT = Path(__file__).resolve().parent.parent
@@ -39,13 +39,6 @@ class TestAgentConfigConsistency:
assert AGENT_CONFIG["codex"]["folder"] == ".agents/"
assert AGENT_CONFIG["codex"]["commands_subdir"] == "skills"
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."""
assert "roo" in AI_ASSISTANT_HELP
for alias, target in AI_ASSISTANT_ALIASES.items():
assert alias in AI_ASSISTANT_HELP
assert target in AI_ASSISTANT_HELP
def test_devcontainer_kiro_installer_uses_pinned_checksum(self):
"""Devcontainer installer should always verify Kiro installer via pinned SHA256."""
post_create_text = (REPO_ROOT / ".devcontainer" / "post-create.sh").read_text(
@@ -80,9 +73,9 @@ class TestAgentConfigConsistency:
assert cfg["args"] == "{{args}}"
assert cfg["extension"] == ".toml"
def test_ai_help_includes_tabnine(self):
"""CLI help text for --ai should include tabnine."""
assert "tabnine" in AI_ASSISTANT_HELP
def test_agent_config_includes_tabnine(self):
"""AGENT_CONFIG should include tabnine."""
assert "tabnine" in AGENT_CONFIG
# --- Kimi Code CLI consistency checks ---
@@ -102,9 +95,9 @@ class TestAgentConfigConsistency:
assert kimi_cfg["dir"] == ".kimi/skills"
assert kimi_cfg["extension"] == "/SKILL.md"
def test_ai_help_includes_kimi(self):
"""CLI help text for --ai should include kimi."""
assert "kimi" in AI_ASSISTANT_HELP
def test_agent_config_includes_kimi(self):
"""AGENT_CONFIG should include kimi."""
assert "kimi" in AGENT_CONFIG
# --- Trae IDE consistency checks ---
@@ -126,9 +119,9 @@ class TestAgentConfigConsistency:
assert trae_cfg["args"] == "$ARGUMENTS"
assert trae_cfg["extension"] == "/SKILL.md"
def test_ai_help_includes_trae(self):
"""CLI help text for --ai should include trae."""
assert "trae" in AI_ASSISTANT_HELP
def test_agent_config_includes_trae(self):
"""AGENT_CONFIG should include trae."""
assert "trae" in AGENT_CONFIG
# --- Pi Coding Agent consistency checks ---
@@ -151,9 +144,9 @@ class TestAgentConfigConsistency:
assert pi_cfg["args"] == "$ARGUMENTS"
assert pi_cfg["extension"] == ".md"
def test_ai_help_includes_pi(self):
"""CLI help text for --ai should include pi."""
assert "pi" in AI_ASSISTANT_HELP
def test_agent_config_includes_pi(self):
"""AGENT_CONFIG should include pi."""
assert "pi" in AGENT_CONFIG
# --- iFlow CLI consistency checks ---
@@ -173,9 +166,9 @@ class TestAgentConfigConsistency:
assert cfg["iflow"]["format"] == "markdown"
assert cfg["iflow"]["args"] == "$ARGUMENTS"
def test_ai_help_includes_iflow(self):
"""CLI help text for --ai should include iflow."""
assert "iflow" in AI_ASSISTANT_HELP
def test_agent_config_includes_iflow(self):
"""AGENT_CONFIG should include iflow."""
assert "iflow" in AGENT_CONFIG
# --- Goose consistency checks ---
@@ -195,9 +188,9 @@ class TestAgentConfigConsistency:
assert cfg["goose"]["format"] == "yaml"
assert cfg["goose"]["args"] == "{{args}}"
def test_ai_help_includes_goose(self):
"""CLI help text for --ai should include goose."""
assert "goose" in AI_ASSISTANT_HELP
def test_agent_config_includes_goose(self):
"""AGENT_CONFIG should include goose."""
assert "goose" in AGENT_CONFIG
# --- invoke_separator propagation checks ---
@@ -304,6 +297,6 @@ class TestAgentConfigConsistency:
assert rovodev_cfg["args"] == "$ARGUMENTS"
assert rovodev_cfg["extension"] == "/SKILL.md"
def test_ai_help_includes_rovodev(self):
"""CLI help text for --ai should include rovodev."""
assert "rovodev" in AI_ASSISTANT_HELP
def test_agent_config_includes_rovodev(self):
"""AGENT_CONFIG should include rovodev."""
assert "rovodev" in AGENT_CONFIG

View File

@@ -36,7 +36,7 @@ class TestSaveBranchNumbering:
project_dir = tmp_path / "proj"
runner = CliRunner()
result = runner.invoke(app, ["init", str(project_dir), "--ai", "claude", "--ignore-agent-tools", "--no-git", "--script", "sh"])
result = runner.invoke(app, ["init", str(project_dir), "--integration", "claude", "--ignore-agent-tools", "--no-git", "--script", "sh"])
assert result.exit_code == 0
saved = json.loads((project_dir / ".specify/init-options.json").read_text())
@@ -51,7 +51,7 @@ class TestBranchNumberingValidation:
from specify_cli import app
runner = CliRunner()
result = runner.invoke(app, ["init", str(tmp_path / "proj"), "--ai", "claude", "--branch-numbering", "foobar", "--ignore-agent-tools"])
result = runner.invoke(app, ["init", str(tmp_path / "proj"), "--integration", "claude", "--branch-numbering", "foobar", "--ignore-agent-tools"])
assert result.exit_code == 1
assert "Invalid --branch-numbering" in result.output
@@ -60,7 +60,7 @@ class TestBranchNumberingValidation:
from specify_cli import app
runner = CliRunner()
result = runner.invoke(app, ["init", str(tmp_path / "proj"), "--ai", "claude", "--branch-numbering", "sequential", "--ignore-agent-tools", "--no-git", "--script", "sh"])
result = runner.invoke(app, ["init", str(tmp_path / "proj"), "--integration", "claude", "--branch-numbering", "sequential", "--ignore-agent-tools", "--no-git", "--script", "sh"])
assert result.exit_code == 0
assert "Invalid --branch-numbering" not in (result.output or "")
@@ -69,6 +69,6 @@ class TestBranchNumberingValidation:
from specify_cli import app
runner = CliRunner()
result = runner.invoke(app, ["init", str(tmp_path / "proj"), "--ai", "claude", "--branch-numbering", "timestamp", "--ignore-agent-tools", "--no-git", "--script", "sh"])
result = runner.invoke(app, ["init", str(tmp_path / "proj"), "--integration", "claude", "--branch-numbering", "timestamp", "--ignore-agent-tools", "--no-git", "--script", "sh"])
assert result.exit_code == 0
assert "Invalid --branch-numbering" not in (result.output or "")

View File

@@ -16,14 +16,10 @@ def test_commands_init_importable():
def test_agent_config_importable():
from specify_cli._agent_config import (
AGENT_CONFIG,
AI_ASSISTANT_ALIASES,
AI_ASSISTANT_HELP,
DEFAULT_INIT_INTEGRATION,
SCRIPT_TYPE_CHOICES,
)
assert isinstance(AGENT_CONFIG, dict)
assert isinstance(AI_ASSISTANT_ALIASES, dict)
assert isinstance(AI_ASSISTANT_HELP, str)
assert DEFAULT_INIT_INTEGRATION == "copilot"
assert "sh" in SCRIPT_TYPE_CHOICES

View File

@@ -2,7 +2,7 @@
Unit tests for extension skill auto-registration.
Tests cover:
- SKILL.md generation when --ai-skills was used during init
- SKILL.md generation when skills mode was used during init
- No skills created when ai_skills not active
- SKILL.md content correctness
- Existing user-modified skills not overwritten
@@ -162,7 +162,7 @@ def extension_dir(temp_dir):
@pytest.fixture
def skills_project(project_dir):
"""Create a project with --ai-skills enabled and skills directory."""
"""Create a project with skills mode enabled and skills directory."""
_create_init_options(project_dir, ai="claude", ai_skills=True)
skills_dir = _create_skills_dir(project_dir, ai="claude")
return project_dir, skills_dir
@@ -170,7 +170,7 @@ def skills_project(project_dir):
@pytest.fixture
def no_skills_project(project_dir):
"""Create a project without --ai-skills."""
"""Create a project without skills mode."""
_create_init_options(project_dir, ai="claude", ai_skills=False)
return project_dir

View File

@@ -4932,7 +4932,7 @@ class TestHookInvocationRendering:
assert "EXECUTE_COMMAND_INVOCATION: /skill:speckit-plan" in message
def test_codex_hooks_render_dollar_skill_invocation(self, project_dir):
"""Codex projects with --ai-skills should render $speckit-* invocations."""
"""Codex projects with skills mode should render $speckit-* invocations."""
init_options = project_dir / ".specify" / "init-options.json"
init_options.parent.mkdir(parents=True, exist_ok=True)
init_options.write_text(json.dumps({"ai": "codex", "ai_skills": True}))

View File

@@ -2557,8 +2557,8 @@ class TestPresetSkills:
return preset_dir
def test_skill_overridden_on_preset_install(self, project_dir, temp_dir):
"""When --ai-skills was used, a preset command override should update the skill."""
# Simulate --ai-skills having been used: write init-options + create skill
"""When skills mode was used, a preset command override should update the skill."""
# Simulate skills mode having been used: write init-options + create skill
self._write_init_options(project_dir, ai="claude")
skills_dir = project_dir / ".claude" / "skills"
self._create_skill(skills_dir, "speckit-specify")
@@ -2843,7 +2843,7 @@ class TestPresetSkills:
assert "override taskstoissues body" in content
def test_skill_not_updated_when_ai_skills_disabled(self, project_dir, temp_dir):
"""When --ai-skills was NOT used, preset install should not touch skills."""
"""When skills mode was NOT used, preset install should not touch skills."""
self._write_init_options(project_dir, ai="qwen", ai_skills=False)
skills_dir = project_dir / ".qwen" / "skills"
self._create_skill(skills_dir, "speckit-specify", body="untouched")
@@ -2962,7 +2962,7 @@ class TestPresetSkills:
def test_no_skills_registered_when_no_skill_dir_exists(self, project_dir, temp_dir):
"""Skills should not be created when no existing skill dir is found."""
self._write_init_options(project_dir, ai="claude")
# Don't create skills dir — simulate --ai-skills never created them
# Don't create skills dir — simulate skills mode never created them
manager = PresetManager(project_dir)
install_self_test_preset(manager)
@@ -4123,7 +4123,7 @@ class TestWrapStrategy:
"---\ndescription: core wrap-test\n---\n\n# Core Wrap-Test Body\n"
)
# Set up skills dir (simulating --ai claude)
# Set up skills dir (simulating --integration claude)
skills_dir = project_dir / ".claude" / "skills"
skills_dir.mkdir(parents=True, exist_ok=True)
skill_subdir = skills_dir / "speckit-wrap-test"