mirror of
https://github.com/github/spec-kit.git
synced 2026-04-02 02:33:08 +00:00
Stage 3: Standard markdown integrations — 19 agents migrated to plugin architecture (#2038)
* 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
This commit is contained in:
@@ -1974,7 +1974,7 @@ def init(
|
||||
console.print("[yellow]Use:[/yellow] --integration for the new integration system, or --ai for the legacy path")
|
||||
raise typer.Exit(1)
|
||||
|
||||
# Auto-promote: --ai copilot → integration path with a nudge
|
||||
# Auto-promote: --ai <key> → integration path with a nudge (if registered)
|
||||
use_integration = False
|
||||
if integration:
|
||||
from .integrations import INTEGRATION_REGISTRY, get_integration
|
||||
@@ -1987,14 +1987,14 @@ def init(
|
||||
use_integration = True
|
||||
# Map integration key to the ai_assistant variable for downstream compatibility
|
||||
ai_assistant = integration
|
||||
elif ai_assistant == "copilot":
|
||||
elif ai_assistant:
|
||||
from .integrations import get_integration
|
||||
resolved_integration = get_integration("copilot")
|
||||
resolved_integration = get_integration(ai_assistant)
|
||||
if resolved_integration:
|
||||
use_integration = True
|
||||
console.print(
|
||||
"[dim]Tip: Use [bold]--integration copilot[/bold] instead of "
|
||||
"--ai copilot. The --ai flag will be deprecated in a future release.[/dim]"
|
||||
f"[dim]Tip: Use [bold]--integration {ai_assistant}[/bold] instead of "
|
||||
f"--ai {ai_assistant}. The --ai flag will be deprecated in a future release.[/dim]"
|
||||
)
|
||||
|
||||
if project_name == ".":
|
||||
|
||||
@@ -43,7 +43,7 @@ class CommandRegistrar:
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": ".agent.md"
|
||||
},
|
||||
"cursor": {
|
||||
"cursor-agent": {
|
||||
"dir": ".cursor/commands",
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
@@ -162,6 +162,12 @@ class CommandRegistrar:
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": ".md"
|
||||
},
|
||||
"vibe": {
|
||||
"dir": ".vibe/prompts",
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": ".md"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,11 +241,11 @@ class CommandRegistrar:
|
||||
|
||||
for key, script_path in scripts.items():
|
||||
if isinstance(script_path, str):
|
||||
scripts[key] = self._rewrite_project_relative_paths(script_path)
|
||||
scripts[key] = self.rewrite_project_relative_paths(script_path)
|
||||
return frontmatter
|
||||
|
||||
@staticmethod
|
||||
def _rewrite_project_relative_paths(text: str) -> str:
|
||||
def rewrite_project_relative_paths(text: str) -> str:
|
||||
"""Rewrite repo-relative paths to their generated project locations."""
|
||||
if not isinstance(text, str) or not text:
|
||||
return text
|
||||
@@ -422,7 +428,7 @@ class CommandRegistrar:
|
||||
body = body.replace("{AGENT_SCRIPT}", agent_script_command)
|
||||
|
||||
body = body.replace("{ARGS}", "$ARGUMENTS").replace("__AGENT__", agent_name)
|
||||
return CommandRegistrar._rewrite_project_relative_paths(body)
|
||||
return CommandRegistrar.rewrite_project_relative_paths(body)
|
||||
|
||||
def _convert_argument_placeholder(self, content: str, from_placeholder: str, to_placeholder: str) -> str:
|
||||
"""Convert argument placeholder format.
|
||||
|
||||
@@ -37,10 +37,57 @@ def get_integration(key: str) -> IntegrationBase | None:
|
||||
# -- Register built-in integrations --------------------------------------
|
||||
|
||||
def _register_builtins() -> None:
|
||||
"""Register all built-in integrations."""
|
||||
from .copilot import CopilotIntegration
|
||||
"""Register all built-in integrations.
|
||||
|
||||
Package directories use Python-safe identifiers (e.g. ``kiro_cli``,
|
||||
``cursor_agent``). The user-facing integration key stored in
|
||||
``IntegrationBase.key`` stays hyphenated (``"kiro-cli"``,
|
||||
``"cursor-agent"``) to match the actual CLI tool / binary name that
|
||||
users install and invoke.
|
||||
"""
|
||||
# -- Imports (alphabetical) -------------------------------------------
|
||||
from .amp import AmpIntegration
|
||||
from .auggie import AuggieIntegration
|
||||
from .bob import BobIntegration
|
||||
from .claude import ClaudeIntegration
|
||||
from .codebuddy import CodebuddyIntegration
|
||||
from .copilot import CopilotIntegration
|
||||
from .cursor_agent import CursorAgentIntegration
|
||||
from .iflow import IflowIntegration
|
||||
from .junie import JunieIntegration
|
||||
from .kilocode import KilocodeIntegration
|
||||
from .kiro_cli import KiroCliIntegration
|
||||
from .opencode import OpencodeIntegration
|
||||
from .pi import PiIntegration
|
||||
from .qodercli import QodercliIntegration
|
||||
from .qwen import QwenIntegration
|
||||
from .roo import RooIntegration
|
||||
from .shai import ShaiIntegration
|
||||
from .trae import TraeIntegration
|
||||
from .vibe import VibeIntegration
|
||||
from .windsurf import WindsurfIntegration
|
||||
|
||||
# -- Registration (alphabetical) --------------------------------------
|
||||
_register(AmpIntegration())
|
||||
_register(AuggieIntegration())
|
||||
_register(BobIntegration())
|
||||
_register(ClaudeIntegration())
|
||||
_register(CodebuddyIntegration())
|
||||
_register(CopilotIntegration())
|
||||
_register(CursorAgentIntegration())
|
||||
_register(IflowIntegration())
|
||||
_register(JunieIntegration())
|
||||
_register(KilocodeIntegration())
|
||||
_register(KiroCliIntegration())
|
||||
_register(OpencodeIntegration())
|
||||
_register(PiIntegration())
|
||||
_register(QodercliIntegration())
|
||||
_register(QwenIntegration())
|
||||
_register(RooIntegration())
|
||||
_register(ShaiIntegration())
|
||||
_register(TraeIntegration())
|
||||
_register(VibeIntegration())
|
||||
_register(WindsurfIntegration())
|
||||
|
||||
|
||||
_register_builtins()
|
||||
|
||||
21
src/specify_cli/integrations/amp/__init__.py
Normal file
21
src/specify_cli/integrations/amp/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""Amp CLI integration."""
|
||||
|
||||
from ..base import MarkdownIntegration
|
||||
|
||||
|
||||
class AmpIntegration(MarkdownIntegration):
|
||||
key = "amp"
|
||||
config = {
|
||||
"name": "Amp",
|
||||
"folder": ".agents/",
|
||||
"commands_subdir": "commands",
|
||||
"install_url": "https://ampcode.com/manual#install",
|
||||
"requires_cli": True,
|
||||
}
|
||||
registrar_config = {
|
||||
"dir": ".agents/commands",
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": ".md",
|
||||
}
|
||||
context_file = "AGENTS.md"
|
||||
23
src/specify_cli/integrations/amp/scripts/update-context.ps1
Normal file
23
src/specify_cli/integrations/amp/scripts/update-context.ps1
Normal file
@@ -0,0 +1,23 @@
|
||||
# update-context.ps1 — Amp integration: create/update AGENTS.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
# If git did not return a repo root, or the git root does not contain .specify,
|
||||
# fall back to walking up from the script directory to find the initialized project root.
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType amp
|
||||
28
src/specify_cli/integrations/amp/scripts/update-context.sh
Executable file
28
src/specify_cli/integrations/amp/scripts/update-context.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — Amp integration: create/update AGENTS.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" amp
|
||||
21
src/specify_cli/integrations/auggie/__init__.py
Normal file
21
src/specify_cli/integrations/auggie/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""Auggie CLI integration."""
|
||||
|
||||
from ..base import MarkdownIntegration
|
||||
|
||||
|
||||
class AuggieIntegration(MarkdownIntegration):
|
||||
key = "auggie"
|
||||
config = {
|
||||
"name": "Auggie CLI",
|
||||
"folder": ".augment/",
|
||||
"commands_subdir": "commands",
|
||||
"install_url": "https://docs.augmentcode.com/cli/setup-auggie/install-auggie-cli",
|
||||
"requires_cli": True,
|
||||
}
|
||||
registrar_config = {
|
||||
"dir": ".augment/commands",
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": ".md",
|
||||
}
|
||||
context_file = ".augment/rules/specify-rules.md"
|
||||
@@ -0,0 +1,23 @@
|
||||
# update-context.ps1 — Auggie CLI integration: create/update .augment/rules/specify-rules.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
# If git did not return a repo root, or the git root does not contain .specify,
|
||||
# fall back to walking up from the script directory to find the initialized project root.
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType auggie
|
||||
28
src/specify_cli/integrations/auggie/scripts/update-context.sh
Executable file
28
src/specify_cli/integrations/auggie/scripts/update-context.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — Auggie CLI integration: create/update .augment/rules/specify-rules.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" auggie
|
||||
@@ -206,6 +206,53 @@ class IntegrationBase(ABC):
|
||||
manifest.record_existing(rel)
|
||||
return dest
|
||||
|
||||
def integration_scripts_dir(self) -> Path | None:
|
||||
"""Return path to this integration's bundled ``scripts/`` directory.
|
||||
|
||||
Looks for a ``scripts/`` sibling of the module that defines the
|
||||
concrete subclass (not ``IntegrationBase`` itself).
|
||||
Returns ``None`` if the directory doesn't exist.
|
||||
"""
|
||||
import inspect
|
||||
|
||||
cls_file = inspect.getfile(type(self))
|
||||
scripts = Path(cls_file).resolve().parent / "scripts"
|
||||
return scripts if scripts.is_dir() else None
|
||||
|
||||
def install_scripts(
|
||||
self,
|
||||
project_root: Path,
|
||||
manifest: IntegrationManifest,
|
||||
) -> list[Path]:
|
||||
"""Copy integration-specific scripts into the project.
|
||||
|
||||
Copies files from this integration's ``scripts/`` directory to
|
||||
``.specify/integrations/<key>/scripts/`` in the project. Shell
|
||||
scripts are made executable. All copied files are recorded in
|
||||
*manifest*.
|
||||
|
||||
Returns the list of files created.
|
||||
"""
|
||||
scripts_src = self.integration_scripts_dir()
|
||||
if not scripts_src:
|
||||
return []
|
||||
|
||||
created: list[Path] = []
|
||||
scripts_dest = project_root / ".specify" / "integrations" / self.key / "scripts"
|
||||
scripts_dest.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
for src_script in sorted(scripts_src.iterdir()):
|
||||
if not src_script.is_file():
|
||||
continue
|
||||
dst_script = scripts_dest / src_script.name
|
||||
shutil.copy2(src_script, dst_script)
|
||||
if dst_script.suffix == ".sh":
|
||||
dst_script.chmod(dst_script.stat().st_mode | 0o111)
|
||||
self.record_file_in_manifest(dst_script, project_root, manifest)
|
||||
created.append(dst_script)
|
||||
|
||||
return created
|
||||
|
||||
@staticmethod
|
||||
def process_template(
|
||||
content: str,
|
||||
@@ -299,13 +346,11 @@ class IntegrationBase(ABC):
|
||||
# 6. Replace __AGENT__
|
||||
content = content.replace("__AGENT__", agent_name)
|
||||
|
||||
# 7. Rewrite paths (matches release script's rewrite_paths())
|
||||
content = re.sub(r"(/?)memory/", r".specify/memory/", content)
|
||||
content = re.sub(r"(/?)scripts/", r".specify/scripts/", content)
|
||||
content = re.sub(r"(/?)templates/", r".specify/templates/", content)
|
||||
# Fix double-prefix (same as release script's .specify.specify/ fix)
|
||||
content = content.replace(".specify.specify/", ".specify/")
|
||||
content = content.replace(".specify/.specify/", ".specify/")
|
||||
# 7. Rewrite paths — delegate to the shared implementation in
|
||||
# CommandRegistrar so extension-local paths are preserved and
|
||||
# boundary rules stay consistent across the codebase.
|
||||
from specify_cli.agents import CommandRegistrar
|
||||
content = CommandRegistrar.rewrite_project_relative_paths(content)
|
||||
|
||||
return content
|
||||
|
||||
@@ -405,11 +450,51 @@ class MarkdownIntegration(IntegrationBase):
|
||||
Subclasses only need to set ``key``, ``config``, ``registrar_config``
|
||||
(and optionally ``context_file``). Everything else is inherited.
|
||||
|
||||
The default ``setup()`` from ``IntegrationBase`` copies templates
|
||||
into the agent's commands directory — which is correct for the
|
||||
standard Markdown case.
|
||||
``setup()`` processes command templates (replacing ``{SCRIPT}``,
|
||||
``{ARGS}``, ``__AGENT__``, rewriting paths) and installs
|
||||
integration-specific scripts (``update-context.sh`` / ``.ps1``).
|
||||
"""
|
||||
|
||||
# MarkdownIntegration inherits IntegrationBase.setup() as-is.
|
||||
# Future stages may add markdown-specific path rewriting here.
|
||||
pass
|
||||
def setup(
|
||||
self,
|
||||
project_root: Path,
|
||||
manifest: IntegrationManifest,
|
||||
parsed_options: dict[str, Any] | None = None,
|
||||
**opts: Any,
|
||||
) -> list[Path]:
|
||||
templates = self.list_command_templates()
|
||||
if not templates:
|
||||
return []
|
||||
|
||||
project_root_resolved = project_root.resolve()
|
||||
if manifest.project_root != project_root_resolved:
|
||||
raise ValueError(
|
||||
f"manifest.project_root ({manifest.project_root}) does not match "
|
||||
f"project_root ({project_root_resolved})"
|
||||
)
|
||||
|
||||
dest = self.commands_dest(project_root).resolve()
|
||||
try:
|
||||
dest.relative_to(project_root_resolved)
|
||||
except ValueError as exc:
|
||||
raise ValueError(
|
||||
f"Integration destination {dest} escapes "
|
||||
f"project root {project_root_resolved}"
|
||||
) from exc
|
||||
dest.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
script_type = opts.get("script_type", "sh")
|
||||
arg_placeholder = self.registrar_config.get("args", "$ARGUMENTS") if self.registrar_config else "$ARGUMENTS"
|
||||
created: list[Path] = []
|
||||
|
||||
for src_file in templates:
|
||||
raw = src_file.read_text(encoding="utf-8")
|
||||
processed = self.process_template(raw, self.key, script_type, arg_placeholder)
|
||||
dst_name = self.command_filename(src_file.stem)
|
||||
dst_file = self.write_file_and_record(
|
||||
processed, dest / dst_name, project_root, manifest
|
||||
)
|
||||
created.append(dst_file)
|
||||
|
||||
created.extend(self.install_scripts(project_root, manifest))
|
||||
return created
|
||||
|
||||
21
src/specify_cli/integrations/bob/__init__.py
Normal file
21
src/specify_cli/integrations/bob/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""IBM Bob integration."""
|
||||
|
||||
from ..base import MarkdownIntegration
|
||||
|
||||
|
||||
class BobIntegration(MarkdownIntegration):
|
||||
key = "bob"
|
||||
config = {
|
||||
"name": "IBM Bob",
|
||||
"folder": ".bob/",
|
||||
"commands_subdir": "commands",
|
||||
"install_url": None,
|
||||
"requires_cli": False,
|
||||
}
|
||||
registrar_config = {
|
||||
"dir": ".bob/commands",
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": ".md",
|
||||
}
|
||||
context_file = "AGENTS.md"
|
||||
23
src/specify_cli/integrations/bob/scripts/update-context.ps1
Normal file
23
src/specify_cli/integrations/bob/scripts/update-context.ps1
Normal file
@@ -0,0 +1,23 @@
|
||||
# update-context.ps1 — IBM Bob integration: create/update AGENTS.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
# If git did not return a repo root, or the git root does not contain .specify,
|
||||
# fall back to walking up from the script directory to find the initialized project root.
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType bob
|
||||
28
src/specify_cli/integrations/bob/scripts/update-context.sh
Executable file
28
src/specify_cli/integrations/bob/scripts/update-context.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — IBM Bob integration: create/update AGENTS.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" bob
|
||||
21
src/specify_cli/integrations/claude/__init__.py
Normal file
21
src/specify_cli/integrations/claude/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""Claude Code integration."""
|
||||
|
||||
from ..base import MarkdownIntegration
|
||||
|
||||
|
||||
class ClaudeIntegration(MarkdownIntegration):
|
||||
key = "claude"
|
||||
config = {
|
||||
"name": "Claude Code",
|
||||
"folder": ".claude/",
|
||||
"commands_subdir": "commands",
|
||||
"install_url": "https://docs.anthropic.com/en/docs/claude-code/setup",
|
||||
"requires_cli": True,
|
||||
}
|
||||
registrar_config = {
|
||||
"dir": ".claude/commands",
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": ".md",
|
||||
}
|
||||
context_file = "CLAUDE.md"
|
||||
@@ -0,0 +1,23 @@
|
||||
# update-context.ps1 — Claude Code integration: create/update CLAUDE.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
# If git did not return a repo root, or the git root does not contain .specify,
|
||||
# fall back to walking up from the script directory to find the initialized project root.
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType claude
|
||||
28
src/specify_cli/integrations/claude/scripts/update-context.sh
Executable file
28
src/specify_cli/integrations/claude/scripts/update-context.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — Claude Code integration: create/update CLAUDE.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" claude
|
||||
21
src/specify_cli/integrations/codebuddy/__init__.py
Normal file
21
src/specify_cli/integrations/codebuddy/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""CodeBuddy CLI integration."""
|
||||
|
||||
from ..base import MarkdownIntegration
|
||||
|
||||
|
||||
class CodebuddyIntegration(MarkdownIntegration):
|
||||
key = "codebuddy"
|
||||
config = {
|
||||
"name": "CodeBuddy",
|
||||
"folder": ".codebuddy/",
|
||||
"commands_subdir": "commands",
|
||||
"install_url": "https://www.codebuddy.ai/cli",
|
||||
"requires_cli": True,
|
||||
}
|
||||
registrar_config = {
|
||||
"dir": ".codebuddy/commands",
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": ".md",
|
||||
}
|
||||
context_file = "CODEBUDDY.md"
|
||||
@@ -0,0 +1,23 @@
|
||||
# update-context.ps1 — CodeBuddy integration: create/update CODEBUDDY.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
# If git did not return a repo root, or the git root does not contain .specify,
|
||||
# fall back to walking up from the script directory to find the initialized project root.
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType codebuddy
|
||||
28
src/specify_cli/integrations/codebuddy/scripts/update-context.sh
Executable file
28
src/specify_cli/integrations/codebuddy/scripts/update-context.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — CodeBuddy integration: create/update CODEBUDDY.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" codebuddy
|
||||
@@ -118,19 +118,7 @@ class CopilotIntegration(IntegrationBase):
|
||||
created.append(dst_settings)
|
||||
|
||||
# 4. Install integration-specific update-context scripts
|
||||
scripts_src = Path(__file__).resolve().parent / "scripts"
|
||||
if scripts_src.is_dir():
|
||||
scripts_dest = project_root / ".specify" / "integrations" / "copilot" / "scripts"
|
||||
scripts_dest.mkdir(parents=True, exist_ok=True)
|
||||
for src_script in sorted(scripts_src.iterdir()):
|
||||
if src_script.is_file():
|
||||
dst_script = scripts_dest / src_script.name
|
||||
shutil.copy2(src_script, dst_script)
|
||||
# Make shell scripts executable
|
||||
if dst_script.suffix == ".sh":
|
||||
dst_script.chmod(dst_script.stat().st_mode | 0o111)
|
||||
self.record_file_in_manifest(dst_script, project_root, manifest)
|
||||
created.append(dst_script)
|
||||
created.extend(self.install_scripts(project_root, manifest))
|
||||
|
||||
return created
|
||||
|
||||
|
||||
@@ -14,8 +14,18 @@
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$repoRoot = git rev-parse --show-toplevel 2>$null
|
||||
if (-not $repoRoot) { $repoRoot = $PWD.Path }
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
# If git did not return a repo root, or the git root does not contain .specify,
|
||||
# fall back to walking up from the script directory to find the initialized project root.
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
# Invoke shared update-agent-context script as a separate process.
|
||||
# Dot-sourcing is unsafe until that script guards its Main call.
|
||||
|
||||
@@ -15,7 +15,22 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Invoke shared update-agent-context script as a separate process.
|
||||
# Sourcing is unsafe until that script guards its main logic.
|
||||
|
||||
21
src/specify_cli/integrations/cursor_agent/__init__.py
Normal file
21
src/specify_cli/integrations/cursor_agent/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""Cursor IDE integration."""
|
||||
|
||||
from ..base import MarkdownIntegration
|
||||
|
||||
|
||||
class CursorAgentIntegration(MarkdownIntegration):
|
||||
key = "cursor-agent"
|
||||
config = {
|
||||
"name": "Cursor",
|
||||
"folder": ".cursor/",
|
||||
"commands_subdir": "commands",
|
||||
"install_url": None,
|
||||
"requires_cli": False,
|
||||
}
|
||||
registrar_config = {
|
||||
"dir": ".cursor/commands",
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": ".md",
|
||||
}
|
||||
context_file = ".cursor/rules/specify-rules.mdc"
|
||||
@@ -0,0 +1,23 @@
|
||||
# update-context.ps1 — Cursor integration: create/update .cursor/rules/specify-rules.mdc
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
# If git did not return a repo root, or the git root does not contain .specify,
|
||||
# fall back to walking up from the script directory to find the initialized project root.
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType cursor-agent
|
||||
28
src/specify_cli/integrations/cursor_agent/scripts/update-context.sh
Executable file
28
src/specify_cli/integrations/cursor_agent/scripts/update-context.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — Cursor integration: create/update .cursor/rules/specify-rules.mdc
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" cursor-agent
|
||||
21
src/specify_cli/integrations/iflow/__init__.py
Normal file
21
src/specify_cli/integrations/iflow/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""iFlow CLI integration."""
|
||||
|
||||
from ..base import MarkdownIntegration
|
||||
|
||||
|
||||
class IflowIntegration(MarkdownIntegration):
|
||||
key = "iflow"
|
||||
config = {
|
||||
"name": "iFlow CLI",
|
||||
"folder": ".iflow/",
|
||||
"commands_subdir": "commands",
|
||||
"install_url": "https://docs.iflow.cn/en/cli/quickstart",
|
||||
"requires_cli": True,
|
||||
}
|
||||
registrar_config = {
|
||||
"dir": ".iflow/commands",
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": ".md",
|
||||
}
|
||||
context_file = "IFLOW.md"
|
||||
@@ -0,0 +1,23 @@
|
||||
# update-context.ps1 — iFlow CLI integration: create/update IFLOW.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
# If git did not return a repo root, or the git root does not contain .specify,
|
||||
# fall back to walking up from the script directory to find the initialized project root.
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType iflow
|
||||
28
src/specify_cli/integrations/iflow/scripts/update-context.sh
Executable file
28
src/specify_cli/integrations/iflow/scripts/update-context.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — iFlow CLI integration: create/update IFLOW.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" iflow
|
||||
21
src/specify_cli/integrations/junie/__init__.py
Normal file
21
src/specify_cli/integrations/junie/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""Junie integration (JetBrains)."""
|
||||
|
||||
from ..base import MarkdownIntegration
|
||||
|
||||
|
||||
class JunieIntegration(MarkdownIntegration):
|
||||
key = "junie"
|
||||
config = {
|
||||
"name": "Junie",
|
||||
"folder": ".junie/",
|
||||
"commands_subdir": "commands",
|
||||
"install_url": "https://junie.jetbrains.com/",
|
||||
"requires_cli": True,
|
||||
}
|
||||
registrar_config = {
|
||||
"dir": ".junie/commands",
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": ".md",
|
||||
}
|
||||
context_file = ".junie/AGENTS.md"
|
||||
@@ -0,0 +1,23 @@
|
||||
# update-context.ps1 — Junie integration: create/update .junie/AGENTS.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
# If git did not return a repo root, or the git root does not contain .specify,
|
||||
# fall back to walking up from the script directory to find the initialized project root.
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType junie
|
||||
28
src/specify_cli/integrations/junie/scripts/update-context.sh
Executable file
28
src/specify_cli/integrations/junie/scripts/update-context.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — Junie integration: create/update .junie/AGENTS.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" junie
|
||||
21
src/specify_cli/integrations/kilocode/__init__.py
Normal file
21
src/specify_cli/integrations/kilocode/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""Kilo Code integration."""
|
||||
|
||||
from ..base import MarkdownIntegration
|
||||
|
||||
|
||||
class KilocodeIntegration(MarkdownIntegration):
|
||||
key = "kilocode"
|
||||
config = {
|
||||
"name": "Kilo Code",
|
||||
"folder": ".kilocode/",
|
||||
"commands_subdir": "workflows",
|
||||
"install_url": None,
|
||||
"requires_cli": False,
|
||||
}
|
||||
registrar_config = {
|
||||
"dir": ".kilocode/workflows",
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": ".md",
|
||||
}
|
||||
context_file = ".kilocode/rules/specify-rules.md"
|
||||
@@ -0,0 +1,23 @@
|
||||
# update-context.ps1 — Kilo Code integration: create/update .kilocode/rules/specify-rules.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
# If git did not return a repo root, or the git root does not contain .specify,
|
||||
# fall back to walking up from the script directory to find the initialized project root.
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType kilocode
|
||||
28
src/specify_cli/integrations/kilocode/scripts/update-context.sh
Executable file
28
src/specify_cli/integrations/kilocode/scripts/update-context.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — Kilo Code integration: create/update .kilocode/rules/specify-rules.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" kilocode
|
||||
21
src/specify_cli/integrations/kiro_cli/__init__.py
Normal file
21
src/specify_cli/integrations/kiro_cli/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""Kiro CLI integration."""
|
||||
|
||||
from ..base import MarkdownIntegration
|
||||
|
||||
|
||||
class KiroCliIntegration(MarkdownIntegration):
|
||||
key = "kiro-cli"
|
||||
config = {
|
||||
"name": "Kiro CLI",
|
||||
"folder": ".kiro/",
|
||||
"commands_subdir": "prompts",
|
||||
"install_url": "https://kiro.dev/docs/cli/",
|
||||
"requires_cli": True,
|
||||
}
|
||||
registrar_config = {
|
||||
"dir": ".kiro/prompts",
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": ".md",
|
||||
}
|
||||
context_file = "AGENTS.md"
|
||||
@@ -0,0 +1,23 @@
|
||||
# update-context.ps1 — Kiro CLI integration: create/update AGENTS.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
# If git did not return a repo root, or the git root does not contain .specify,
|
||||
# fall back to walking up from the script directory to find the initialized project root.
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType kiro-cli
|
||||
28
src/specify_cli/integrations/kiro_cli/scripts/update-context.sh
Executable file
28
src/specify_cli/integrations/kiro_cli/scripts/update-context.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — Kiro CLI integration: create/update AGENTS.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" kiro-cli
|
||||
21
src/specify_cli/integrations/opencode/__init__.py
Normal file
21
src/specify_cli/integrations/opencode/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""opencode integration."""
|
||||
|
||||
from ..base import MarkdownIntegration
|
||||
|
||||
|
||||
class OpencodeIntegration(MarkdownIntegration):
|
||||
key = "opencode"
|
||||
config = {
|
||||
"name": "opencode",
|
||||
"folder": ".opencode/",
|
||||
"commands_subdir": "command",
|
||||
"install_url": "https://opencode.ai",
|
||||
"requires_cli": True,
|
||||
}
|
||||
registrar_config = {
|
||||
"dir": ".opencode/command",
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": ".md",
|
||||
}
|
||||
context_file = "AGENTS.md"
|
||||
@@ -0,0 +1,23 @@
|
||||
# update-context.ps1 — opencode integration: create/update AGENTS.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
# If git did not return a repo root, or the git root does not contain .specify,
|
||||
# fall back to walking up from the script directory to find the initialized project root.
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType opencode
|
||||
28
src/specify_cli/integrations/opencode/scripts/update-context.sh
Executable file
28
src/specify_cli/integrations/opencode/scripts/update-context.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — opencode integration: create/update AGENTS.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" opencode
|
||||
21
src/specify_cli/integrations/pi/__init__.py
Normal file
21
src/specify_cli/integrations/pi/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""Pi Coding Agent integration."""
|
||||
|
||||
from ..base import MarkdownIntegration
|
||||
|
||||
|
||||
class PiIntegration(MarkdownIntegration):
|
||||
key = "pi"
|
||||
config = {
|
||||
"name": "Pi Coding Agent",
|
||||
"folder": ".pi/",
|
||||
"commands_subdir": "prompts",
|
||||
"install_url": "https://www.npmjs.com/package/@mariozechner/pi-coding-agent",
|
||||
"requires_cli": True,
|
||||
}
|
||||
registrar_config = {
|
||||
"dir": ".pi/prompts",
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": ".md",
|
||||
}
|
||||
context_file = "AGENTS.md"
|
||||
23
src/specify_cli/integrations/pi/scripts/update-context.ps1
Normal file
23
src/specify_cli/integrations/pi/scripts/update-context.ps1
Normal file
@@ -0,0 +1,23 @@
|
||||
# update-context.ps1 — Pi Coding Agent integration: create/update AGENTS.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
# If git did not return a repo root, or the git root does not contain .specify,
|
||||
# fall back to walking up from the script directory to find the initialized project root.
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType pi
|
||||
28
src/specify_cli/integrations/pi/scripts/update-context.sh
Executable file
28
src/specify_cli/integrations/pi/scripts/update-context.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — Pi Coding Agent integration: create/update AGENTS.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" pi
|
||||
21
src/specify_cli/integrations/qodercli/__init__.py
Normal file
21
src/specify_cli/integrations/qodercli/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""Qoder CLI integration."""
|
||||
|
||||
from ..base import MarkdownIntegration
|
||||
|
||||
|
||||
class QodercliIntegration(MarkdownIntegration):
|
||||
key = "qodercli"
|
||||
config = {
|
||||
"name": "Qoder CLI",
|
||||
"folder": ".qoder/",
|
||||
"commands_subdir": "commands",
|
||||
"install_url": "https://qoder.com/cli",
|
||||
"requires_cli": True,
|
||||
}
|
||||
registrar_config = {
|
||||
"dir": ".qoder/commands",
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": ".md",
|
||||
}
|
||||
context_file = "QODER.md"
|
||||
@@ -0,0 +1,23 @@
|
||||
# update-context.ps1 — Qoder CLI integration: create/update QODER.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
# If git did not return a repo root, or the git root does not contain .specify,
|
||||
# fall back to walking up from the script directory to find the initialized project root.
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType qodercli
|
||||
28
src/specify_cli/integrations/qodercli/scripts/update-context.sh
Executable file
28
src/specify_cli/integrations/qodercli/scripts/update-context.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — Qoder CLI integration: create/update QODER.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" qodercli
|
||||
21
src/specify_cli/integrations/qwen/__init__.py
Normal file
21
src/specify_cli/integrations/qwen/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""Qwen Code integration."""
|
||||
|
||||
from ..base import MarkdownIntegration
|
||||
|
||||
|
||||
class QwenIntegration(MarkdownIntegration):
|
||||
key = "qwen"
|
||||
config = {
|
||||
"name": "Qwen Code",
|
||||
"folder": ".qwen/",
|
||||
"commands_subdir": "commands",
|
||||
"install_url": "https://github.com/QwenLM/qwen-code",
|
||||
"requires_cli": True,
|
||||
}
|
||||
registrar_config = {
|
||||
"dir": ".qwen/commands",
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": ".md",
|
||||
}
|
||||
context_file = "QWEN.md"
|
||||
23
src/specify_cli/integrations/qwen/scripts/update-context.ps1
Normal file
23
src/specify_cli/integrations/qwen/scripts/update-context.ps1
Normal file
@@ -0,0 +1,23 @@
|
||||
# update-context.ps1 — Qwen Code integration: create/update QWEN.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
# If git did not return a repo root, or the git root does not contain .specify,
|
||||
# fall back to walking up from the script directory to find the initialized project root.
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType qwen
|
||||
28
src/specify_cli/integrations/qwen/scripts/update-context.sh
Executable file
28
src/specify_cli/integrations/qwen/scripts/update-context.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — Qwen Code integration: create/update QWEN.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" qwen
|
||||
21
src/specify_cli/integrations/roo/__init__.py
Normal file
21
src/specify_cli/integrations/roo/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""Roo Code integration."""
|
||||
|
||||
from ..base import MarkdownIntegration
|
||||
|
||||
|
||||
class RooIntegration(MarkdownIntegration):
|
||||
key = "roo"
|
||||
config = {
|
||||
"name": "Roo Code",
|
||||
"folder": ".roo/",
|
||||
"commands_subdir": "commands",
|
||||
"install_url": None,
|
||||
"requires_cli": False,
|
||||
}
|
||||
registrar_config = {
|
||||
"dir": ".roo/commands",
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": ".md",
|
||||
}
|
||||
context_file = ".roo/rules/specify-rules.md"
|
||||
23
src/specify_cli/integrations/roo/scripts/update-context.ps1
Normal file
23
src/specify_cli/integrations/roo/scripts/update-context.ps1
Normal file
@@ -0,0 +1,23 @@
|
||||
# update-context.ps1 — Roo Code integration: create/update .roo/rules/specify-rules.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
# If git did not return a repo root, or the git root does not contain .specify,
|
||||
# fall back to walking up from the script directory to find the initialized project root.
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType roo
|
||||
28
src/specify_cli/integrations/roo/scripts/update-context.sh
Executable file
28
src/specify_cli/integrations/roo/scripts/update-context.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — Roo Code integration: create/update .roo/rules/specify-rules.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" roo
|
||||
21
src/specify_cli/integrations/shai/__init__.py
Normal file
21
src/specify_cli/integrations/shai/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""SHAI CLI integration."""
|
||||
|
||||
from ..base import MarkdownIntegration
|
||||
|
||||
|
||||
class ShaiIntegration(MarkdownIntegration):
|
||||
key = "shai"
|
||||
config = {
|
||||
"name": "SHAI",
|
||||
"folder": ".shai/",
|
||||
"commands_subdir": "commands",
|
||||
"install_url": "https://github.com/ovh/shai",
|
||||
"requires_cli": True,
|
||||
}
|
||||
registrar_config = {
|
||||
"dir": ".shai/commands",
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": ".md",
|
||||
}
|
||||
context_file = "SHAI.md"
|
||||
23
src/specify_cli/integrations/shai/scripts/update-context.ps1
Normal file
23
src/specify_cli/integrations/shai/scripts/update-context.ps1
Normal file
@@ -0,0 +1,23 @@
|
||||
# update-context.ps1 — SHAI integration: create/update SHAI.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
# If git did not return a repo root, or the git root does not contain .specify,
|
||||
# fall back to walking up from the script directory to find the initialized project root.
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType shai
|
||||
28
src/specify_cli/integrations/shai/scripts/update-context.sh
Executable file
28
src/specify_cli/integrations/shai/scripts/update-context.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — SHAI integration: create/update SHAI.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" shai
|
||||
21
src/specify_cli/integrations/trae/__init__.py
Normal file
21
src/specify_cli/integrations/trae/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""Trae IDE integration."""
|
||||
|
||||
from ..base import MarkdownIntegration
|
||||
|
||||
|
||||
class TraeIntegration(MarkdownIntegration):
|
||||
key = "trae"
|
||||
config = {
|
||||
"name": "Trae",
|
||||
"folder": ".trae/",
|
||||
"commands_subdir": "rules",
|
||||
"install_url": None,
|
||||
"requires_cli": False,
|
||||
}
|
||||
registrar_config = {
|
||||
"dir": ".trae/rules",
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": ".md",
|
||||
}
|
||||
context_file = ".trae/rules/AGENTS.md"
|
||||
23
src/specify_cli/integrations/trae/scripts/update-context.ps1
Normal file
23
src/specify_cli/integrations/trae/scripts/update-context.ps1
Normal file
@@ -0,0 +1,23 @@
|
||||
# update-context.ps1 — Trae integration: create/update .trae/rules/AGENTS.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
# If git did not return a repo root, or the git root does not contain .specify,
|
||||
# fall back to walking up from the script directory to find the initialized project root.
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType trae
|
||||
28
src/specify_cli/integrations/trae/scripts/update-context.sh
Executable file
28
src/specify_cli/integrations/trae/scripts/update-context.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — Trae integration: create/update .trae/rules/AGENTS.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" trae
|
||||
21
src/specify_cli/integrations/vibe/__init__.py
Normal file
21
src/specify_cli/integrations/vibe/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""Mistral Vibe CLI integration."""
|
||||
|
||||
from ..base import MarkdownIntegration
|
||||
|
||||
|
||||
class VibeIntegration(MarkdownIntegration):
|
||||
key = "vibe"
|
||||
config = {
|
||||
"name": "Mistral Vibe",
|
||||
"folder": ".vibe/",
|
||||
"commands_subdir": "prompts",
|
||||
"install_url": "https://github.com/mistralai/mistral-vibe",
|
||||
"requires_cli": True,
|
||||
}
|
||||
registrar_config = {
|
||||
"dir": ".vibe/prompts",
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": ".md",
|
||||
}
|
||||
context_file = ".vibe/agents/specify-agents.md"
|
||||
23
src/specify_cli/integrations/vibe/scripts/update-context.ps1
Normal file
23
src/specify_cli/integrations/vibe/scripts/update-context.ps1
Normal file
@@ -0,0 +1,23 @@
|
||||
# update-context.ps1 — Mistral Vibe integration: create/update .vibe/agents/specify-agents.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
# If git did not return a repo root, or the git root does not contain .specify,
|
||||
# fall back to walking up from the script directory to find the initialized project root.
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType vibe
|
||||
28
src/specify_cli/integrations/vibe/scripts/update-context.sh
Executable file
28
src/specify_cli/integrations/vibe/scripts/update-context.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — Mistral Vibe integration: create/update .vibe/agents/specify-agents.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" vibe
|
||||
21
src/specify_cli/integrations/windsurf/__init__.py
Normal file
21
src/specify_cli/integrations/windsurf/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""Windsurf IDE integration."""
|
||||
|
||||
from ..base import MarkdownIntegration
|
||||
|
||||
|
||||
class WindsurfIntegration(MarkdownIntegration):
|
||||
key = "windsurf"
|
||||
config = {
|
||||
"name": "Windsurf",
|
||||
"folder": ".windsurf/",
|
||||
"commands_subdir": "workflows",
|
||||
"install_url": None,
|
||||
"requires_cli": False,
|
||||
}
|
||||
registrar_config = {
|
||||
"dir": ".windsurf/workflows",
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": ".md",
|
||||
}
|
||||
context_file = ".windsurf/rules/specify-rules.md"
|
||||
@@ -0,0 +1,23 @@
|
||||
# update-context.ps1 — Windsurf integration: create/update .windsurf/rules/specify-rules.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
# If git did not return a repo root, or the git root does not contain .specify,
|
||||
# fall back to walking up from the script directory to find the initialized project root.
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType windsurf
|
||||
28
src/specify_cli/integrations/windsurf/scripts/update-context.sh
Executable file
28
src/specify_cli/integrations/windsurf/scripts/update-context.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — Windsurf integration: create/update .windsurf/rules/specify-rules.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" windsurf
|
||||
11
tests/integrations/test_integration_amp.py
Normal file
11
tests/integrations/test_integration_amp.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Tests for AmpIntegration."""
|
||||
|
||||
from .test_integration_base_markdown import MarkdownIntegrationTests
|
||||
|
||||
|
||||
class TestAmpIntegration(MarkdownIntegrationTests):
|
||||
KEY = "amp"
|
||||
FOLDER = ".agents/"
|
||||
COMMANDS_SUBDIR = "commands"
|
||||
REGISTRAR_DIR = ".agents/commands"
|
||||
CONTEXT_FILE = "AGENTS.md"
|
||||
11
tests/integrations/test_integration_auggie.py
Normal file
11
tests/integrations/test_integration_auggie.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Tests for AuggieIntegration."""
|
||||
|
||||
from .test_integration_base_markdown import MarkdownIntegrationTests
|
||||
|
||||
|
||||
class TestAuggieIntegration(MarkdownIntegrationTests):
|
||||
KEY = "auggie"
|
||||
FOLDER = ".augment/"
|
||||
COMMANDS_SUBDIR = "commands"
|
||||
REGISTRAR_DIR = ".augment/commands"
|
||||
CONTEXT_FILE = ".augment/rules/specify-rules.md"
|
||||
296
tests/integrations/test_integration_base_markdown.py
Normal file
296
tests/integrations/test_integration_base_markdown.py
Normal file
@@ -0,0 +1,296 @@
|
||||
"""Reusable test mixin for standard MarkdownIntegration subclasses.
|
||||
|
||||
Each per-agent test file sets ``KEY``, ``FOLDER``, ``COMMANDS_SUBDIR``,
|
||||
``REGISTRAR_DIR``, and ``CONTEXT_FILE``, then inherits all verification
|
||||
logic from ``MarkdownIntegrationTests``.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from specify_cli.integrations import INTEGRATION_REGISTRY, get_integration
|
||||
from specify_cli.integrations.base import MarkdownIntegration
|
||||
from specify_cli.integrations.manifest import IntegrationManifest
|
||||
|
||||
|
||||
class MarkdownIntegrationTests:
|
||||
"""Mixin — set class-level constants and inherit these tests.
|
||||
|
||||
Required class attrs on subclass::
|
||||
|
||||
KEY: str — integration registry key
|
||||
FOLDER: str — e.g. ".claude/"
|
||||
COMMANDS_SUBDIR: str — e.g. "commands"
|
||||
REGISTRAR_DIR: str — e.g. ".claude/commands"
|
||||
CONTEXT_FILE: str — e.g. "CLAUDE.md"
|
||||
"""
|
||||
|
||||
KEY: str
|
||||
FOLDER: str
|
||||
COMMANDS_SUBDIR: str
|
||||
REGISTRAR_DIR: str
|
||||
CONTEXT_FILE: str
|
||||
|
||||
# -- Registration -----------------------------------------------------
|
||||
|
||||
def test_registered(self):
|
||||
assert self.KEY in INTEGRATION_REGISTRY
|
||||
assert get_integration(self.KEY) is not None
|
||||
|
||||
def test_is_markdown_integration(self):
|
||||
assert isinstance(get_integration(self.KEY), MarkdownIntegration)
|
||||
|
||||
# -- Config -----------------------------------------------------------
|
||||
|
||||
def test_config_folder(self):
|
||||
i = get_integration(self.KEY)
|
||||
assert i.config["folder"] == self.FOLDER
|
||||
|
||||
def test_config_commands_subdir(self):
|
||||
i = get_integration(self.KEY)
|
||||
assert i.config["commands_subdir"] == self.COMMANDS_SUBDIR
|
||||
|
||||
def test_registrar_config(self):
|
||||
i = get_integration(self.KEY)
|
||||
assert i.registrar_config["dir"] == self.REGISTRAR_DIR
|
||||
assert i.registrar_config["format"] == "markdown"
|
||||
assert i.registrar_config["args"] == "$ARGUMENTS"
|
||||
assert i.registrar_config["extension"] == ".md"
|
||||
|
||||
def test_context_file(self):
|
||||
i = get_integration(self.KEY)
|
||||
assert i.context_file == self.CONTEXT_FILE
|
||||
|
||||
# -- Setup / teardown -------------------------------------------------
|
||||
|
||||
def test_setup_creates_files(self, tmp_path):
|
||||
i = get_integration(self.KEY)
|
||||
m = IntegrationManifest(self.KEY, tmp_path)
|
||||
created = i.setup(tmp_path, m)
|
||||
assert len(created) > 0
|
||||
cmd_files = [f for f in created if "scripts" not in f.parts]
|
||||
for f in cmd_files:
|
||||
assert f.exists()
|
||||
assert f.name.startswith("speckit.")
|
||||
assert f.name.endswith(".md")
|
||||
|
||||
def test_setup_writes_to_correct_directory(self, tmp_path):
|
||||
i = get_integration(self.KEY)
|
||||
m = IntegrationManifest(self.KEY, tmp_path)
|
||||
created = i.setup(tmp_path, m)
|
||||
expected_dir = i.commands_dest(tmp_path)
|
||||
assert expected_dir.exists(), f"Expected directory {expected_dir} was not created"
|
||||
cmd_files = [f for f in created if "scripts" not in f.parts]
|
||||
assert len(cmd_files) > 0, "No command files were created"
|
||||
for f in cmd_files:
|
||||
assert f.resolve().parent == expected_dir.resolve(), (
|
||||
f"{f} is not under {expected_dir}"
|
||||
)
|
||||
|
||||
def test_templates_are_processed(self, tmp_path):
|
||||
"""Command files must have placeholders replaced, not raw templates."""
|
||||
i = get_integration(self.KEY)
|
||||
m = IntegrationManifest(self.KEY, tmp_path)
|
||||
created = i.setup(tmp_path, m)
|
||||
cmd_files = [f for f in created if "scripts" not in f.parts]
|
||||
assert len(cmd_files) > 0
|
||||
for f in cmd_files:
|
||||
content = f.read_text(encoding="utf-8")
|
||||
assert "{SCRIPT}" not in content, f"{f.name} has unprocessed {{SCRIPT}}"
|
||||
assert "__AGENT__" not in content, f"{f.name} has unprocessed __AGENT__"
|
||||
assert "{ARGS}" not in content, f"{f.name} has unprocessed {{ARGS}}"
|
||||
assert "\nscripts:\n" not in content, f"{f.name} has unstripped scripts: block"
|
||||
assert "\nagent_scripts:\n" not in content, f"{f.name} has unstripped agent_scripts: block"
|
||||
|
||||
def test_all_files_tracked_in_manifest(self, tmp_path):
|
||||
i = get_integration(self.KEY)
|
||||
m = IntegrationManifest(self.KEY, tmp_path)
|
||||
created = i.setup(tmp_path, m)
|
||||
for f in created:
|
||||
rel = f.resolve().relative_to(tmp_path.resolve()).as_posix()
|
||||
assert rel in m.files, f"{rel} not tracked in manifest"
|
||||
|
||||
def test_install_uninstall_roundtrip(self, tmp_path):
|
||||
i = get_integration(self.KEY)
|
||||
m = IntegrationManifest(self.KEY, tmp_path)
|
||||
created = i.install(tmp_path, m)
|
||||
assert len(created) > 0
|
||||
m.save()
|
||||
for f in created:
|
||||
assert f.exists()
|
||||
removed, skipped = i.uninstall(tmp_path, m)
|
||||
assert len(removed) == len(created)
|
||||
assert skipped == []
|
||||
|
||||
def test_modified_file_survives_uninstall(self, tmp_path):
|
||||
i = get_integration(self.KEY)
|
||||
m = IntegrationManifest(self.KEY, tmp_path)
|
||||
created = i.install(tmp_path, m)
|
||||
m.save()
|
||||
modified_file = created[0]
|
||||
modified_file.write_text("user modified this", encoding="utf-8")
|
||||
removed, skipped = i.uninstall(tmp_path, m)
|
||||
assert modified_file.exists()
|
||||
assert modified_file in skipped
|
||||
|
||||
# -- Scripts ----------------------------------------------------------
|
||||
|
||||
def test_setup_installs_update_context_scripts(self, tmp_path):
|
||||
i = get_integration(self.KEY)
|
||||
m = IntegrationManifest(self.KEY, tmp_path)
|
||||
created = i.setup(tmp_path, m)
|
||||
scripts_dir = tmp_path / ".specify" / "integrations" / self.KEY / "scripts"
|
||||
assert scripts_dir.is_dir(), f"Scripts directory not created for {self.KEY}"
|
||||
assert (scripts_dir / "update-context.sh").exists()
|
||||
assert (scripts_dir / "update-context.ps1").exists()
|
||||
|
||||
def test_scripts_tracked_in_manifest(self, tmp_path):
|
||||
i = get_integration(self.KEY)
|
||||
m = IntegrationManifest(self.KEY, tmp_path)
|
||||
i.setup(tmp_path, m)
|
||||
script_rels = [k for k in m.files if "update-context" in k]
|
||||
assert len(script_rels) >= 2
|
||||
|
||||
def test_sh_script_is_executable(self, tmp_path):
|
||||
i = get_integration(self.KEY)
|
||||
m = IntegrationManifest(self.KEY, tmp_path)
|
||||
i.setup(tmp_path, m)
|
||||
sh = tmp_path / ".specify" / "integrations" / self.KEY / "scripts" / "update-context.sh"
|
||||
assert os.access(sh, os.X_OK)
|
||||
|
||||
# -- CLI auto-promote -------------------------------------------------
|
||||
|
||||
def test_ai_flag_auto_promotes(self, tmp_path):
|
||||
from typer.testing import CliRunner
|
||||
from specify_cli import app
|
||||
|
||||
project = tmp_path / f"promote-{self.KEY}"
|
||||
project.mkdir()
|
||||
old_cwd = os.getcwd()
|
||||
try:
|
||||
os.chdir(project)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(app, [
|
||||
"init", "--here", "--ai", 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 f"--integration {self.KEY}" in result.output
|
||||
|
||||
def test_integration_flag_creates_files(self, tmp_path):
|
||||
from typer.testing import CliRunner
|
||||
from specify_cli import app
|
||||
|
||||
project = tmp_path / f"int-{self.KEY}"
|
||||
project.mkdir()
|
||||
old_cwd = os.getcwd()
|
||||
try:
|
||||
os.chdir(project)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(app, [
|
||||
"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 --integration {self.KEY} failed: {result.output}"
|
||||
i = get_integration(self.KEY)
|
||||
cmd_dir = i.commands_dest(project)
|
||||
assert cmd_dir.is_dir(), f"Commands directory {cmd_dir} not created"
|
||||
commands = sorted(cmd_dir.glob("speckit.*"))
|
||||
assert len(commands) > 0, f"No command files in {cmd_dir}"
|
||||
|
||||
# -- Complete file inventory ------------------------------------------
|
||||
|
||||
COMMAND_STEMS = [
|
||||
"analyze", "checklist", "clarify", "constitution",
|
||||
"implement", "plan", "specify", "tasks", "taskstoissues",
|
||||
]
|
||||
|
||||
def _expected_files(self, script_variant: str) -> list[str]:
|
||||
"""Build the expected file list for this integration + script variant."""
|
||||
i = get_integration(self.KEY)
|
||||
cmd_dir = i.registrar_config["dir"]
|
||||
files = []
|
||||
|
||||
# Command files
|
||||
for stem in self.COMMAND_STEMS:
|
||||
files.append(f"{cmd_dir}/speckit.{stem}.md")
|
||||
|
||||
# Integration scripts
|
||||
files.append(f".specify/integrations/{self.KEY}/scripts/update-context.ps1")
|
||||
files.append(f".specify/integrations/{self.KEY}/scripts/update-context.sh")
|
||||
|
||||
# Framework files
|
||||
files.append(f".specify/integration.json")
|
||||
files.append(f".specify/init-options.json")
|
||||
files.append(f".specify/integrations/{self.KEY}.manifest.json")
|
||||
files.append(f".specify/integrations/speckit.manifest.json")
|
||||
|
||||
if script_variant == "sh":
|
||||
for name in ["check-prerequisites.sh", "common.sh", "create-new-feature.sh",
|
||||
"setup-plan.sh", "update-agent-context.sh"]:
|
||||
files.append(f".specify/scripts/bash/{name}")
|
||||
else:
|
||||
for name in ["check-prerequisites.ps1", "common.ps1", "create-new-feature.ps1",
|
||||
"setup-plan.ps1", "update-agent-context.ps1"]:
|
||||
files.append(f".specify/scripts/powershell/{name}")
|
||||
|
||||
for name in ["agent-file-template.md", "checklist-template.md",
|
||||
"constitution-template.md", "plan-template.md",
|
||||
"spec-template.md", "tasks-template.md"]:
|
||||
files.append(f".specify/templates/{name}")
|
||||
|
||||
files.append(".specify/memory/constitution.md")
|
||||
return sorted(files)
|
||||
|
||||
def test_complete_file_inventory_sh(self, tmp_path):
|
||||
"""Every file produced by specify init --integration <key> --script sh."""
|
||||
from typer.testing import CliRunner
|
||||
from specify_cli import app
|
||||
|
||||
project = tmp_path / f"inventory-sh-{self.KEY}"
|
||||
project.mkdir()
|
||||
old_cwd = os.getcwd()
|
||||
try:
|
||||
os.chdir(project)
|
||||
result = CliRunner().invoke(app, [
|
||||
"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 failed: {result.output}"
|
||||
actual = sorted(p.relative_to(project).as_posix()
|
||||
for p in project.rglob("*") if p.is_file())
|
||||
expected = self._expected_files("sh")
|
||||
assert actual == expected, (
|
||||
f"Missing: {sorted(set(expected) - set(actual))}\n"
|
||||
f"Extra: {sorted(set(actual) - set(expected))}"
|
||||
)
|
||||
|
||||
def test_complete_file_inventory_ps(self, tmp_path):
|
||||
"""Every file produced by specify init --integration <key> --script ps."""
|
||||
from typer.testing import CliRunner
|
||||
from specify_cli import app
|
||||
|
||||
project = tmp_path / f"inventory-ps-{self.KEY}"
|
||||
project.mkdir()
|
||||
old_cwd = os.getcwd()
|
||||
try:
|
||||
os.chdir(project)
|
||||
result = CliRunner().invoke(app, [
|
||||
"init", "--here", "--integration", self.KEY, "--script", "ps",
|
||||
"--no-git", "--ignore-agent-tools",
|
||||
], catch_exceptions=False)
|
||||
finally:
|
||||
os.chdir(old_cwd)
|
||||
assert result.exit_code == 0, f"init failed: {result.output}"
|
||||
actual = sorted(p.relative_to(project).as_posix()
|
||||
for p in project.rglob("*") if p.is_file())
|
||||
expected = self._expected_files("ps")
|
||||
assert actual == expected, (
|
||||
f"Missing: {sorted(set(expected) - set(actual))}\n"
|
||||
f"Extra: {sorted(set(actual) - set(expected))}"
|
||||
)
|
||||
11
tests/integrations/test_integration_bob.py
Normal file
11
tests/integrations/test_integration_bob.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Tests for BobIntegration."""
|
||||
|
||||
from .test_integration_base_markdown import MarkdownIntegrationTests
|
||||
|
||||
|
||||
class TestBobIntegration(MarkdownIntegrationTests):
|
||||
KEY = "bob"
|
||||
FOLDER = ".bob/"
|
||||
COMMANDS_SUBDIR = "commands"
|
||||
REGISTRAR_DIR = ".bob/commands"
|
||||
CONTEXT_FILE = "AGENTS.md"
|
||||
11
tests/integrations/test_integration_claude.py
Normal file
11
tests/integrations/test_integration_claude.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Tests for ClaudeIntegration."""
|
||||
|
||||
from .test_integration_base_markdown import MarkdownIntegrationTests
|
||||
|
||||
|
||||
class TestClaudeIntegration(MarkdownIntegrationTests):
|
||||
KEY = "claude"
|
||||
FOLDER = ".claude/"
|
||||
COMMANDS_SUBDIR = "commands"
|
||||
REGISTRAR_DIR = ".claude/commands"
|
||||
CONTEXT_FILE = "CLAUDE.md"
|
||||
11
tests/integrations/test_integration_codebuddy.py
Normal file
11
tests/integrations/test_integration_codebuddy.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Tests for CodebuddyIntegration."""
|
||||
|
||||
from .test_integration_base_markdown import MarkdownIntegrationTests
|
||||
|
||||
|
||||
class TestCodebuddyIntegration(MarkdownIntegrationTests):
|
||||
KEY = "codebuddy"
|
||||
FOLDER = ".codebuddy/"
|
||||
COMMANDS_SUBDIR = "commands"
|
||||
REGISTRAR_DIR = ".codebuddy/commands"
|
||||
CONTEXT_FILE = "CODEBUDDY.md"
|
||||
11
tests/integrations/test_integration_cursor_agent.py
Normal file
11
tests/integrations/test_integration_cursor_agent.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Tests for CursorAgentIntegration."""
|
||||
|
||||
from .test_integration_base_markdown import MarkdownIntegrationTests
|
||||
|
||||
|
||||
class TestCursorAgentIntegration(MarkdownIntegrationTests):
|
||||
KEY = "cursor-agent"
|
||||
FOLDER = ".cursor/"
|
||||
COMMANDS_SUBDIR = "commands"
|
||||
REGISTRAR_DIR = ".cursor/commands"
|
||||
CONTEXT_FILE = ".cursor/rules/specify-rules.mdc"
|
||||
11
tests/integrations/test_integration_iflow.py
Normal file
11
tests/integrations/test_integration_iflow.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Tests for IflowIntegration."""
|
||||
|
||||
from .test_integration_base_markdown import MarkdownIntegrationTests
|
||||
|
||||
|
||||
class TestIflowIntegration(MarkdownIntegrationTests):
|
||||
KEY = "iflow"
|
||||
FOLDER = ".iflow/"
|
||||
COMMANDS_SUBDIR = "commands"
|
||||
REGISTRAR_DIR = ".iflow/commands"
|
||||
CONTEXT_FILE = "IFLOW.md"
|
||||
11
tests/integrations/test_integration_junie.py
Normal file
11
tests/integrations/test_integration_junie.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Tests for JunieIntegration."""
|
||||
|
||||
from .test_integration_base_markdown import MarkdownIntegrationTests
|
||||
|
||||
|
||||
class TestJunieIntegration(MarkdownIntegrationTests):
|
||||
KEY = "junie"
|
||||
FOLDER = ".junie/"
|
||||
COMMANDS_SUBDIR = "commands"
|
||||
REGISTRAR_DIR = ".junie/commands"
|
||||
CONTEXT_FILE = ".junie/AGENTS.md"
|
||||
11
tests/integrations/test_integration_kilocode.py
Normal file
11
tests/integrations/test_integration_kilocode.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Tests for KilocodeIntegration."""
|
||||
|
||||
from .test_integration_base_markdown import MarkdownIntegrationTests
|
||||
|
||||
|
||||
class TestKilocodeIntegration(MarkdownIntegrationTests):
|
||||
KEY = "kilocode"
|
||||
FOLDER = ".kilocode/"
|
||||
COMMANDS_SUBDIR = "workflows"
|
||||
REGISTRAR_DIR = ".kilocode/workflows"
|
||||
CONTEXT_FILE = ".kilocode/rules/specify-rules.md"
|
||||
11
tests/integrations/test_integration_kiro_cli.py
Normal file
11
tests/integrations/test_integration_kiro_cli.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Tests for KiroCliIntegration."""
|
||||
|
||||
from .test_integration_base_markdown import MarkdownIntegrationTests
|
||||
|
||||
|
||||
class TestKiroCliIntegration(MarkdownIntegrationTests):
|
||||
KEY = "kiro-cli"
|
||||
FOLDER = ".kiro/"
|
||||
COMMANDS_SUBDIR = "prompts"
|
||||
REGISTRAR_DIR = ".kiro/prompts"
|
||||
CONTEXT_FILE = "AGENTS.md"
|
||||
11
tests/integrations/test_integration_opencode.py
Normal file
11
tests/integrations/test_integration_opencode.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Tests for OpencodeIntegration."""
|
||||
|
||||
from .test_integration_base_markdown import MarkdownIntegrationTests
|
||||
|
||||
|
||||
class TestOpencodeIntegration(MarkdownIntegrationTests):
|
||||
KEY = "opencode"
|
||||
FOLDER = ".opencode/"
|
||||
COMMANDS_SUBDIR = "command"
|
||||
REGISTRAR_DIR = ".opencode/command"
|
||||
CONTEXT_FILE = "AGENTS.md"
|
||||
11
tests/integrations/test_integration_pi.py
Normal file
11
tests/integrations/test_integration_pi.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Tests for PiIntegration."""
|
||||
|
||||
from .test_integration_base_markdown import MarkdownIntegrationTests
|
||||
|
||||
|
||||
class TestPiIntegration(MarkdownIntegrationTests):
|
||||
KEY = "pi"
|
||||
FOLDER = ".pi/"
|
||||
COMMANDS_SUBDIR = "prompts"
|
||||
REGISTRAR_DIR = ".pi/prompts"
|
||||
CONTEXT_FILE = "AGENTS.md"
|
||||
11
tests/integrations/test_integration_qodercli.py
Normal file
11
tests/integrations/test_integration_qodercli.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Tests for QodercliIntegration."""
|
||||
|
||||
from .test_integration_base_markdown import MarkdownIntegrationTests
|
||||
|
||||
|
||||
class TestQodercliIntegration(MarkdownIntegrationTests):
|
||||
KEY = "qodercli"
|
||||
FOLDER = ".qoder/"
|
||||
COMMANDS_SUBDIR = "commands"
|
||||
REGISTRAR_DIR = ".qoder/commands"
|
||||
CONTEXT_FILE = "QODER.md"
|
||||
11
tests/integrations/test_integration_qwen.py
Normal file
11
tests/integrations/test_integration_qwen.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Tests for QwenIntegration."""
|
||||
|
||||
from .test_integration_base_markdown import MarkdownIntegrationTests
|
||||
|
||||
|
||||
class TestQwenIntegration(MarkdownIntegrationTests):
|
||||
KEY = "qwen"
|
||||
FOLDER = ".qwen/"
|
||||
COMMANDS_SUBDIR = "commands"
|
||||
REGISTRAR_DIR = ".qwen/commands"
|
||||
CONTEXT_FILE = "QWEN.md"
|
||||
11
tests/integrations/test_integration_roo.py
Normal file
11
tests/integrations/test_integration_roo.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Tests for RooIntegration."""
|
||||
|
||||
from .test_integration_base_markdown import MarkdownIntegrationTests
|
||||
|
||||
|
||||
class TestRooIntegration(MarkdownIntegrationTests):
|
||||
KEY = "roo"
|
||||
FOLDER = ".roo/"
|
||||
COMMANDS_SUBDIR = "commands"
|
||||
REGISTRAR_DIR = ".roo/commands"
|
||||
CONTEXT_FILE = ".roo/rules/specify-rules.md"
|
||||
11
tests/integrations/test_integration_shai.py
Normal file
11
tests/integrations/test_integration_shai.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Tests for ShaiIntegration."""
|
||||
|
||||
from .test_integration_base_markdown import MarkdownIntegrationTests
|
||||
|
||||
|
||||
class TestShaiIntegration(MarkdownIntegrationTests):
|
||||
KEY = "shai"
|
||||
FOLDER = ".shai/"
|
||||
COMMANDS_SUBDIR = "commands"
|
||||
REGISTRAR_DIR = ".shai/commands"
|
||||
CONTEXT_FILE = "SHAI.md"
|
||||
11
tests/integrations/test_integration_trae.py
Normal file
11
tests/integrations/test_integration_trae.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Tests for TraeIntegration."""
|
||||
|
||||
from .test_integration_base_markdown import MarkdownIntegrationTests
|
||||
|
||||
|
||||
class TestTraeIntegration(MarkdownIntegrationTests):
|
||||
KEY = "trae"
|
||||
FOLDER = ".trae/"
|
||||
COMMANDS_SUBDIR = "rules"
|
||||
REGISTRAR_DIR = ".trae/rules"
|
||||
CONTEXT_FILE = ".trae/rules/AGENTS.md"
|
||||
11
tests/integrations/test_integration_vibe.py
Normal file
11
tests/integrations/test_integration_vibe.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Tests for VibeIntegration."""
|
||||
|
||||
from .test_integration_base_markdown import MarkdownIntegrationTests
|
||||
|
||||
|
||||
class TestVibeIntegration(MarkdownIntegrationTests):
|
||||
KEY = "vibe"
|
||||
FOLDER = ".vibe/"
|
||||
COMMANDS_SUBDIR = "prompts"
|
||||
REGISTRAR_DIR = ".vibe/prompts"
|
||||
CONTEXT_FILE = ".vibe/agents/specify-agents.md"
|
||||
11
tests/integrations/test_integration_windsurf.py
Normal file
11
tests/integrations/test_integration_windsurf.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Tests for WindsurfIntegration."""
|
||||
|
||||
from .test_integration_base_markdown import MarkdownIntegrationTests
|
||||
|
||||
|
||||
class TestWindsurfIntegration(MarkdownIntegrationTests):
|
||||
KEY = "windsurf"
|
||||
FOLDER = ".windsurf/"
|
||||
COMMANDS_SUBDIR = "workflows"
|
||||
REGISTRAR_DIR = ".windsurf/workflows"
|
||||
CONTEXT_FILE = ".windsurf/rules/specify-rules.md"
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Tests for INTEGRATION_REGISTRY."""
|
||||
"""Tests for INTEGRATION_REGISTRY — mechanics, completeness, and registrar alignment."""
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -11,6 +11,16 @@ 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)
|
||||
@@ -41,5 +51,26 @@ class TestRegistry:
|
||||
finally:
|
||||
INTEGRATION_REGISTRY.pop("stub", None)
|
||||
|
||||
def test_copilot_registered(self):
|
||||
assert "copilot" in INTEGRATION_REGISTRY
|
||||
|
||||
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
|
||||
|
||||
@@ -1237,24 +1237,22 @@ class TestCliValidation:
|
||||
assert "agent skills" in plain.lower()
|
||||
|
||||
def test_kiro_alias_normalized_to_kiro_cli(self, tmp_path):
|
||||
"""--ai kiro should normalize to canonical kiro-cli agent key."""
|
||||
"""--ai kiro should normalize to canonical kiro-cli and auto-promote to integration path."""
|
||||
import os
|
||||
from typer.testing import CliRunner
|
||||
|
||||
runner = CliRunner()
|
||||
target = tmp_path / "kiro-alias-proj"
|
||||
target.mkdir()
|
||||
|
||||
with patch("specify_cli.download_and_extract_template") as mock_download, \
|
||||
patch("specify_cli.scaffold_from_core_pack", create=True) as mock_scaffold, \
|
||||
patch("specify_cli.ensure_executable_scripts"), \
|
||||
patch("specify_cli.ensure_constitution_from_template"), \
|
||||
patch("specify_cli.is_git_repo", return_value=False), \
|
||||
patch("specify_cli.shutil.which", return_value="/usr/bin/git"):
|
||||
mock_scaffold.return_value = True
|
||||
old_cwd = os.getcwd()
|
||||
try:
|
||||
os.chdir(target)
|
||||
result = runner.invoke(
|
||||
app,
|
||||
[
|
||||
"init",
|
||||
str(target),
|
||||
"--here",
|
||||
"--ai",
|
||||
"kiro",
|
||||
"--ignore-agent-tools",
|
||||
@@ -1262,17 +1260,16 @@ class TestCliValidation:
|
||||
"sh",
|
||||
"--no-git",
|
||||
],
|
||||
catch_exceptions=False,
|
||||
)
|
||||
finally:
|
||||
os.chdir(old_cwd)
|
||||
|
||||
assert result.exit_code == 0
|
||||
# Without --offline, the download path should be taken.
|
||||
assert mock_download.called, (
|
||||
"Expected download_and_extract_template to be called (default non-offline path)"
|
||||
)
|
||||
assert mock_download.call_args.args[1] == "kiro-cli"
|
||||
assert not mock_scaffold.called, (
|
||||
"scaffold_from_core_pack should not be called without --offline"
|
||||
)
|
||||
# kiro alias should auto-promote to integration path with nudge
|
||||
assert "--integration kiro-cli" in result.output
|
||||
# Command files should be created via integration path
|
||||
assert (target / ".kiro" / "prompts" / "speckit.plan.md").exists()
|
||||
|
||||
def test_q_removed_from_agent_config(self):
|
||||
"""Amazon Q legacy key should not remain in AGENT_CONFIG."""
|
||||
|
||||
@@ -981,7 +981,7 @@ $ARGUMENTS
|
||||
"Run scripts/bash/setup-plan.sh\n"
|
||||
)
|
||||
|
||||
rewritten = AgentCommandRegistrar._rewrite_project_relative_paths(body)
|
||||
rewritten = AgentCommandRegistrar.rewrite_project_relative_paths(body)
|
||||
|
||||
assert ".specify/extensions/test-ext/templates/spec.md" in rewritten
|
||||
assert ".specify/scripts/bash/setup-plan.sh" in rewritten
|
||||
|
||||
Reference in New Issue
Block a user