Files
github-spec-kit/tests/test_agent_config_consistency.py
Copilot 7106858c4e 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>
2026-06-05 14:56:28 -05:00

303 lines
12 KiB
Python

"""Consistency checks for agent configuration across runtime surfaces."""
from pathlib import Path
from specify_cli import AGENT_CONFIG
from specify_cli.extensions import CommandRegistrar
REPO_ROOT = Path(__file__).resolve().parent.parent
class TestAgentConfigConsistency:
"""Ensure kiro-cli migration stays synchronized across key surfaces."""
def test_runtime_config_uses_kiro_cli_and_removes_q(self):
"""AGENT_CONFIG should include kiro-cli and exclude legacy q."""
assert "kiro-cli" in AGENT_CONFIG
assert AGENT_CONFIG["kiro-cli"]["folder"] == ".kiro/"
assert AGENT_CONFIG["kiro-cli"]["commands_subdir"] == "prompts"
assert "q" not in AGENT_CONFIG
def test_extension_registrar_uses_kiro_cli_and_removes_q(self):
"""Extension command registrar should target .kiro/prompts."""
cfg = CommandRegistrar.AGENT_CONFIGS
assert "kiro-cli" in cfg
assert cfg["kiro-cli"]["dir"] == ".kiro/prompts"
assert "q" not in cfg
def test_extension_registrar_includes_codex(self):
"""Extension command registrar should include codex targeting .agents/skills."""
cfg = CommandRegistrar.AGENT_CONFIGS
assert "codex" in cfg
assert cfg["codex"]["dir"] == ".agents/skills"
assert cfg["codex"]["extension"] == "/SKILL.md"
def test_runtime_codex_uses_native_skills(self):
"""Codex runtime config should point at .agents/skills."""
assert AGENT_CONFIG["codex"]["folder"] == ".agents/"
assert AGENT_CONFIG["codex"]["commands_subdir"] == "skills"
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(
encoding="utf-8"
)
assert (
'KIRO_INSTALLER_SHA256="7487a65cf310b7fb59b357c4b5e6e3f3259d383f4394ecedb39acf70f307cffb"'
in post_create_text
)
assert "sha256sum -c -" in post_create_text
assert "KIRO_SKIP_KIRO_INSTALLER_VERIFY" not in post_create_text
# --- Tabnine CLI consistency checks ---
def test_runtime_config_includes_tabnine(self):
"""AGENT_CONFIG should include tabnine with correct folder and subdir."""
assert "tabnine" in AGENT_CONFIG
assert AGENT_CONFIG["tabnine"]["folder"] == ".tabnine/agent/"
assert AGENT_CONFIG["tabnine"]["commands_subdir"] == "commands"
assert AGENT_CONFIG["tabnine"]["requires_cli"] is True
assert AGENT_CONFIG["tabnine"]["install_url"] is not None
def test_extension_registrar_includes_tabnine(self):
"""CommandRegistrar.AGENT_CONFIGS should include tabnine with correct TOML config."""
from specify_cli.extensions import CommandRegistrar
assert "tabnine" in CommandRegistrar.AGENT_CONFIGS
cfg = CommandRegistrar.AGENT_CONFIGS["tabnine"]
assert cfg["dir"] == ".tabnine/agent/commands"
assert cfg["format"] == "toml"
assert cfg["args"] == "{{args}}"
assert cfg["extension"] == ".toml"
def test_agent_config_includes_tabnine(self):
"""AGENT_CONFIG should include tabnine."""
assert "tabnine" in AGENT_CONFIG
# --- Kimi Code CLI consistency checks ---
def test_kimi_in_agent_config(self):
"""AGENT_CONFIG should include kimi with correct folder and commands_subdir."""
assert "kimi" in AGENT_CONFIG
assert AGENT_CONFIG["kimi"]["folder"] == ".kimi/"
assert AGENT_CONFIG["kimi"]["commands_subdir"] == "skills"
assert AGENT_CONFIG["kimi"]["requires_cli"] is True
def test_kimi_in_extension_registrar(self):
"""Extension command registrar should include kimi using .kimi/skills and SKILL.md."""
cfg = CommandRegistrar.AGENT_CONFIGS
assert "kimi" in cfg
kimi_cfg = cfg["kimi"]
assert kimi_cfg["dir"] == ".kimi/skills"
assert kimi_cfg["extension"] == "/SKILL.md"
def test_agent_config_includes_kimi(self):
"""AGENT_CONFIG should include kimi."""
assert "kimi" in AGENT_CONFIG
# --- Trae IDE consistency checks ---
def test_trae_in_agent_config(self):
"""AGENT_CONFIG should include trae with correct folder and commands_subdir."""
assert "trae" in AGENT_CONFIG
assert AGENT_CONFIG["trae"]["folder"] == ".trae/"
assert AGENT_CONFIG["trae"]["commands_subdir"] == "skills"
assert AGENT_CONFIG["trae"]["requires_cli"] is False
assert AGENT_CONFIG["trae"]["install_url"] is None
def test_trae_in_extension_registrar(self):
"""Extension command registrar should include trae using .trae/rules and markdown, if present."""
cfg = CommandRegistrar.AGENT_CONFIGS
assert "trae" in cfg
trae_cfg = cfg["trae"]
assert trae_cfg["format"] == "markdown"
assert trae_cfg["args"] == "$ARGUMENTS"
assert trae_cfg["extension"] == "/SKILL.md"
def test_agent_config_includes_trae(self):
"""AGENT_CONFIG should include trae."""
assert "trae" in AGENT_CONFIG
# --- Pi Coding Agent consistency checks ---
def test_pi_in_agent_config(self):
"""AGENT_CONFIG should include pi with correct folder and commands_subdir."""
assert "pi" in AGENT_CONFIG
assert AGENT_CONFIG["pi"]["folder"] == ".pi/"
assert AGENT_CONFIG["pi"]["commands_subdir"] == "prompts"
assert AGENT_CONFIG["pi"]["requires_cli"] is True
assert AGENT_CONFIG["pi"]["install_url"] is not None
def test_pi_in_extension_registrar(self):
"""Extension command registrar should include pi using .pi/prompts."""
cfg = CommandRegistrar.AGENT_CONFIGS
assert "pi" in cfg
pi_cfg = cfg["pi"]
assert pi_cfg["dir"] == ".pi/prompts"
assert pi_cfg["format"] == "markdown"
assert pi_cfg["args"] == "$ARGUMENTS"
assert pi_cfg["extension"] == ".md"
def test_agent_config_includes_pi(self):
"""AGENT_CONFIG should include pi."""
assert "pi" in AGENT_CONFIG
# --- iFlow CLI consistency checks ---
def test_iflow_in_agent_config(self):
"""AGENT_CONFIG should include iflow with correct folder and commands_subdir."""
assert "iflow" in AGENT_CONFIG
assert AGENT_CONFIG["iflow"]["folder"] == ".iflow/"
assert AGENT_CONFIG["iflow"]["commands_subdir"] == "commands"
assert AGENT_CONFIG["iflow"]["requires_cli"] is True
def test_iflow_in_extension_registrar(self):
"""Extension command registrar should include iflow targeting .iflow/commands."""
cfg = CommandRegistrar.AGENT_CONFIGS
assert "iflow" in cfg
assert cfg["iflow"]["dir"] == ".iflow/commands"
assert cfg["iflow"]["format"] == "markdown"
assert cfg["iflow"]["args"] == "$ARGUMENTS"
def test_agent_config_includes_iflow(self):
"""AGENT_CONFIG should include iflow."""
assert "iflow" in AGENT_CONFIG
# --- Goose consistency checks ---
def test_goose_in_agent_config(self):
"""AGENT_CONFIG should include goose with correct folder and commands_subdir."""
assert "goose" in AGENT_CONFIG
assert AGENT_CONFIG["goose"]["folder"] == ".goose/"
assert AGENT_CONFIG["goose"]["commands_subdir"] == "recipes"
assert AGENT_CONFIG["goose"]["requires_cli"] is True
def test_goose_in_extension_registrar(self):
"""Extension command registrar should include goose targeting .goose/recipes."""
cfg = CommandRegistrar.AGENT_CONFIGS
assert "goose" in cfg
assert cfg["goose"]["dir"] == ".goose/recipes"
assert cfg["goose"]["format"] == "yaml"
assert cfg["goose"]["args"] == "{{args}}"
def test_agent_config_includes_goose(self):
"""AGENT_CONFIG should include goose."""
assert "goose" in AGENT_CONFIG
# --- invoke_separator propagation checks ---
def test_skills_agents_have_hyphen_invoke_separator_in_agent_configs(self):
"""Skills-based agents must expose invoke_separator='-' in AGENT_CONFIGS.
SkillsIntegration sets ``invoke_separator = "-"`` as a class attribute,
but individual skills integrations (claude, codex, …) do not repeat it in
their ``registrar_config`` dicts. ``_build_agent_configs()`` must
propagate the class attribute so that ``register_commands()`` resolves
``__SPECKIT_COMMAND_*__`` tokens with the correct hyphen separator.
"""
cfg = CommandRegistrar.AGENT_CONFIGS
skills_agents = [
key for key, c in cfg.items() if c.get("extension") == "/SKILL.md"
]
assert skills_agents, (
"Expected at least one skills-based agent in AGENT_CONFIGS"
)
for agent in skills_agents:
assert cfg[agent].get("invoke_separator") == "-", (
f"Skills agent '{agent}' has invoke_separator="
f"{cfg[agent].get('invoke_separator')!r} in AGENT_CONFIGS; "
"expected '-' (propagated from SkillsIntegration.invoke_separator)"
)
def test_skills_agent_command_token_resolves_with_hyphen(self, tmp_path):
"""__SPECKIT_COMMAND_*__ tokens in extension commands resolve to /speckit-<cmd>
when registered for a skills-based agent (e.g. claude).
Regression guard: before the fix, _build_agent_configs() did not
propagate invoke_separator from the integration class, so
register_commands() fell back to '.' and emitted /speckit.specify instead
of /speckit-specify for skills agents.
"""
import re
from pathlib import Path
from specify_cli.agents import CommandRegistrar
repo_root = Path(__file__).resolve().parent.parent
ext_dir = repo_root / "extensions" / "git"
cmd_source = ext_dir / "commands" / "speckit.git.feature.md"
assert cmd_source.exists(), (
f"Git extension command source not found at {cmd_source}"
)
assert "__SPECKIT_COMMAND_SPECIFY__" in cmd_source.read_text(
encoding="utf-8"
), (
"Expected __SPECKIT_COMMAND_SPECIFY__ token in speckit.git.feature.md; "
"check that the file uses the token rather than a hard-coded ref."
)
registrar = CommandRegistrar()
commands = [
{"name": "speckit.git.feature", "file": "commands/speckit.git.feature.md"}
]
registered = registrar.register_commands(
"claude",
commands,
"git",
ext_dir,
tmp_path,
)
assert "speckit.git.feature" in registered
skill_file = (
tmp_path / ".claude" / "skills" / "speckit-git-feature" / "SKILL.md"
)
assert skill_file.exists(), (
f"Expected Claude skill file not found at {skill_file}"
)
content = skill_file.read_text(encoding="utf-8")
assert "/speckit-specify" in content, (
"Expected '/speckit-specify' (hyphen) in generated Claude skill for git.feature; "
"__SPECKIT_COMMAND_SPECIFY__ was not resolved with the correct separator."
)
# Negative lookbehind (?<![a-zA-Z0-9_]) excludes file-path occurrences
# such as 'source: git:commands/speckit.git.feature.md' in frontmatter,
# where the '/' is a path separator preceded by a word character.
assert not re.search(r"(?<![a-zA-Z0-9_])/speckit\.[a-z]", content), (
"Found dot-notation command ref (/speckit.<cmd>) in generated Claude skill. "
"Skills agents must use hyphen notation."
)
# --- RovoDev consistency checks ---
def test_rovodev_in_agent_config(self):
"""AGENT_CONFIG should include rovodev with skills-based scaffold metadata."""
assert "rovodev" in AGENT_CONFIG
assert AGENT_CONFIG["rovodev"]["folder"] == ".rovodev/"
assert AGENT_CONFIG["rovodev"]["commands_subdir"] == "skills"
assert AGENT_CONFIG["rovodev"]["requires_cli"] is True
def test_rovodev_in_extension_registrar(self):
"""CommandRegistrar.AGENT_CONFIGS should include rovodev skill scaffold metadata."""
cfg = CommandRegistrar.AGENT_CONFIGS
assert "rovodev" in cfg
rovodev_cfg = cfg["rovodev"]
assert rovodev_cfg["dir"] == ".rovodev/skills"
assert rovodev_cfg["format"] == "markdown"
assert rovodev_cfg["args"] == "$ARGUMENTS"
assert rovodev_cfg["extension"] == "/SKILL.md"
def test_agent_config_includes_rovodev(self):
"""AGENT_CONFIG should include rovodev."""
assert "rovodev" in AGENT_CONFIG