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

@@ -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"