mirror of
https://github.com/github/spec-kit.git
synced 2026-04-02 10:43:08 +00:00
* Stage 3: Standard markdown integrations — 19 agents migrated to plugin architecture
Migrate all standard markdown integrations to self-contained subpackages
under integrations/. Each subclasses MarkdownIntegration with config-only
overrides (~10 lines per __init__.py).
Integrations migrated (19):
claude, qwen, opencode, junie, kilocode, auggie, roo, codebuddy,
qodercli, amp, shai, bob, trae, pi, iflow, kiro-cli, windsurf,
vibe, cursor-agent
Changes:
- Create integrations/<key>/ subpackage with __init__.py and scripts/
(update-context.sh, update-context.ps1) for each integration
- Register all 19 in INTEGRATION_REGISTRY (20 total with copilot)
- MarkdownIntegration.setup() processes templates (replaces {SCRIPT},
{ARGS}, __AGENT__; strips frontmatter blocks; rewrites paths)
- Extract install_scripts() to IntegrationBase; refactor copilot to use it
- Generalize --ai auto-promote from copilot-only to registry-driven:
any integration registered in INTEGRATION_REGISTRY auto-promotes.
Unregistered agents (gemini, tabnine, codex, kimi, agy, generic)
continue through the legacy --ai path unchanged.
- Fix cursor/cursor-agent key mismatch in CommandRegistrar.AGENT_CONFIGS
- Add missing vibe entry to CommandRegistrar.AGENT_CONFIGS
- Update kiro alias test to reflect auto-promote behavior
Testing:
- Per-agent test files (test_integration_<agent>.py) with shared mixin
- 1316 tests passing, 0 failures
- Complete file inventory tests for both sh and ps variants
- Byte-for-byte validated against v0.4.3 release packages (684 files)
* Address PR review: fix repo root detection and no-op test
- Fix repo root fallback in all 20 update-context.sh scripts: walk up
from script location to find .specify/ instead of falling back to pwd
- Fix repo root fallback in all 20 update-context.ps1 scripts: walk up
from script location to find .specify/ instead of falling back to $PWD
- Add assertions to test_setup_writes_to_correct_directory: verify
expected_dir exists and all command files reside under it
* Fix REPO_ROOT priority: prefer .specify walk-up over git root
In monorepos the git toplevel may differ from the project root that
contains .specify/. The previous fix still preferred git rev-parse
over the walk-up result.
Bash scripts (20): prefer the discovered _root when it contains
.specify/; only accept git root if it also contains .specify/.
PowerShell scripts (20): validate git root contains .specify/ before
using it; fall back to walking up from script directory otherwise.
* Guard git call with try/catch in PowerShell scripts
With $ErrorActionPreference = 'Stop', an unguarded git rev-parse
throws a terminating CommandNotFoundException when git is not
installed, preventing the .specify walk-up fallback from running.
Wrap the git call in try/catch across all 20 update-context.ps1
scripts so the fallback works reliably without git.
* Rename hyphenated package dirs to valid Python identifiers
Rename kiro-cli → kiro_cli and cursor-agent → cursor_agent so the
packages can be imported with normal Python syntax instead of
importlib. The user-facing integration key (IntegrationBase.key)
stays hyphenated to match the actual CLI tool / binary name.
Also reorganize _register_builtins(): imports and registrations
are now grouped alphabetically with clear section comments.
* Reuse CommandRegistrar path rewriting in process_template()
Replace the duplicated regex-based path rewriting in
MarkdownIntegration.process_template() with a call to the shared
CommandRegistrar._rewrite_project_relative_paths() implementation.
This ensures extension-local paths are preserved and boundary rules
stay consistent across the codebase.
* Promote _rewrite_project_relative_paths to public API
Rename CommandRegistrar._rewrite_project_relative_paths() to
rewrite_project_relative_paths() (drop leading underscore) so
integrations can call it without reaching into a private method
across subsystem boundaries.
Addresses PR review feedback:
https://github.com/github/spec-kit/pull/2038#discussion_r3022105627
* Broaden TestRegistrarKeyAlignment to cover all integration keys
Parametrize across ALL_INTEGRATION_KEYS instead of only checking
cursor-agent and vibe. Keeps a separate negative test for the
stale 'cursor' shorthand.
Addresses PR review feedback:
https://github.com/github/spec-kit/pull/2038#discussion_r3022269032
77 lines
2.5 KiB
Python
77 lines
2.5 KiB
Python
"""Tests for INTEGRATION_REGISTRY — mechanics, completeness, and registrar alignment."""
|
|
|
|
import pytest
|
|
|
|
from specify_cli.integrations import (
|
|
INTEGRATION_REGISTRY,
|
|
_register,
|
|
get_integration,
|
|
)
|
|
from specify_cli.integrations.base import MarkdownIntegration
|
|
from .conftest import StubIntegration
|
|
|
|
|
|
# Every integration key that must be registered (Stage 2 + Stage 3).
|
|
ALL_INTEGRATION_KEYS = [
|
|
"copilot",
|
|
# Stage 3 — standard markdown integrations
|
|
"claude", "qwen", "opencode", "junie", "kilocode", "auggie",
|
|
"roo", "codebuddy", "qodercli", "amp", "shai", "bob", "trae",
|
|
"pi", "iflow", "kiro-cli", "windsurf", "vibe", "cursor-agent",
|
|
]
|
|
|
|
|
|
class TestRegistry:
|
|
def test_registry_is_dict(self):
|
|
assert isinstance(INTEGRATION_REGISTRY, dict)
|
|
|
|
def test_register_and_get(self):
|
|
stub = StubIntegration()
|
|
_register(stub)
|
|
try:
|
|
assert get_integration("stub") is stub
|
|
finally:
|
|
INTEGRATION_REGISTRY.pop("stub", None)
|
|
|
|
def test_get_missing_returns_none(self):
|
|
assert get_integration("nonexistent-xyz") is None
|
|
|
|
def test_register_empty_key_raises(self):
|
|
class EmptyKey(MarkdownIntegration):
|
|
key = ""
|
|
with pytest.raises(ValueError, match="empty key"):
|
|
_register(EmptyKey())
|
|
|
|
def test_register_duplicate_raises(self):
|
|
stub = StubIntegration()
|
|
_register(stub)
|
|
try:
|
|
with pytest.raises(KeyError, match="already registered"):
|
|
_register(StubIntegration())
|
|
finally:
|
|
INTEGRATION_REGISTRY.pop("stub", None)
|
|
|
|
|
|
class TestRegistryCompleteness:
|
|
"""Every expected integration must be registered."""
|
|
|
|
@pytest.mark.parametrize("key", ALL_INTEGRATION_KEYS)
|
|
def test_key_registered(self, key):
|
|
assert key in INTEGRATION_REGISTRY, f"{key} missing from registry"
|
|
|
|
|
|
class TestRegistrarKeyAlignment:
|
|
"""Every integration key must have a matching AGENT_CONFIGS entry."""
|
|
|
|
@pytest.mark.parametrize("key", ALL_INTEGRATION_KEYS)
|
|
def test_integration_key_in_registrar(self, key):
|
|
from specify_cli.agents import CommandRegistrar
|
|
assert key in CommandRegistrar.AGENT_CONFIGS, (
|
|
f"Integration '{key}' is registered but has no AGENT_CONFIGS entry"
|
|
)
|
|
|
|
def test_no_stale_cursor_shorthand(self):
|
|
"""The old 'cursor' shorthand must not appear in AGENT_CONFIGS."""
|
|
from specify_cli.agents import CommandRegistrar
|
|
assert "cursor" not in CommandRegistrar.AGENT_CONFIGS
|