Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Define what to build before building it. Rich templates, quality checklists, and

### Use any coding agent

<span class="pillar-stat">30 integrations</span> — Copilot, Gemini, Codex, Windsurf, Claude, Forge, Kiro, and more. Switch freely between agents with a single command. No lock-in.
<span class="pillar-stat">31 integrations</span> — Copilot, Gemini, Codex, Windsurf, Zed, Claude, Forge, Kiro, and more. Switch freely between agents with a single command. No lock-in.
Comment thread
arrrrny marked this conversation as resolved.
Outdated
Comment thread
arrrrny marked this conversation as resolved.
Outdated

Run `specify init` with your agent of choice and Spec Kit sets up the right command files, context rules, and directory structures automatically. If your agent isn't listed, the `generic` integration is an escape hatch for any tool.

Expand Down
1 change: 1 addition & 0 deletions docs/reference/integrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ The Specify CLI supports a wide range of AI coding agents. When you run `specify
| [Tabnine CLI](https://docs.tabnine.com/main/getting-started/tabnine-cli) | `tabnine` | |
| [Trae](https://www.trae.ai/) | `trae` | Skills-based integration; skills are installed automatically |
| [Windsurf](https://windsurf.com/) | `windsurf` | |
| [Zed](https://zed.dev/) | `zed` | Skills-based integration; installs skills into `.agents/skills` and invokes them as `/speckit-<command>` |
| Generic | `generic` | Bring your own agent — use `--integration generic --integration-options="--commands-dir <path>"` for AI coding agents not listed above |

## List Available Integrations
Expand Down
8 changes: 6 additions & 2 deletions src/specify_cli/commands/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -726,7 +726,8 @@ def init(
cursor_agent_skill_mode = selected_ai == "cursor-agent" and (ai_skills or _is_skills_integration)
copilot_skill_mode = selected_ai == "copilot" and _is_skills_integration
devin_skill_mode = selected_ai == "devin"
native_skill_mode = codex_skill_mode or claude_skill_mode or kimi_skill_mode or agy_skill_mode or trae_skill_mode or cursor_agent_skill_mode or copilot_skill_mode or devin_skill_mode
zed_skill_mode = selected_ai == "zed" and _is_skills_integration
native_skill_mode = codex_skill_mode or claude_skill_mode or kimi_skill_mode or agy_skill_mode or trae_skill_mode or cursor_agent_skill_mode or copilot_skill_mode or devin_skill_mode or zed_skill_mode

if codex_skill_mode and not ai_skills:
steps_lines.append(f"{step_num}. Start Codex in this project directory; spec-kit skills were installed to [cyan].agents/skills[/cyan]")
Expand All @@ -740,6 +741,9 @@ def init(
if devin_skill_mode:
steps_lines.append(f"{step_num}. Start Devin in this project directory; spec-kit skills were installed to [cyan].devin/skills[/cyan]")
step_num += 1
if zed_skill_mode:
steps_lines.append(f"{step_num}. Start Zed in this project directory; spec-kit skills were installed to [cyan].agents/skills[/cyan]")
step_num += 1
Comment thread
arrrrny marked this conversation as resolved.
usage_label = "skills" if native_skill_mode else "slash commands"

def _display_cmd(name: str) -> str:
Expand All @@ -749,7 +753,7 @@ def _display_cmd(name: str) -> str:
return f"/speckit-{name}"
if kimi_skill_mode:
return f"/skill:speckit-{name}"
if cursor_agent_skill_mode or copilot_skill_mode or devin_skill_mode:
if cursor_agent_skill_mode or copilot_skill_mode or devin_skill_mode or zed_skill_mode:
return f"/speckit-{name}"
return f"/speckit.{name}"

Expand Down
3 changes: 3 additions & 0 deletions src/specify_cli/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2413,6 +2413,7 @@ def _render_hook_invocation(self, command: Any) -> str:
claude_skill_mode = selected_ai == "claude" and bool(init_options.get("ai_skills"))
kimi_skill_mode = selected_ai == "kimi"
cursor_skill_mode = selected_ai == "cursor-agent" and bool(init_options.get("ai_skills"))
zed_skill_mode = selected_ai == "zed" and bool(init_options.get("ai_skills"))
Comment thread
arrrrny marked this conversation as resolved.
Outdated

skill_name = self._skill_name_from_command(command_id)
if codex_skill_mode and skill_name:
Expand All @@ -2423,6 +2424,8 @@ def _render_hook_invocation(self, command: Any) -> str:
return f"/skill:{skill_name}"
if cursor_skill_mode and skill_name:
return f"/{skill_name}"
if zed_skill_mode and skill_name:
return f"/{skill_name}"
Comment thread
arrrrny marked this conversation as resolved.
Outdated
Comment thread
arrrrny marked this conversation as resolved.
Comment thread
arrrrny marked this conversation as resolved.

return f"/{command_id}"

Expand Down
2 changes: 2 additions & 0 deletions src/specify_cli/integrations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ def _register_builtins() -> None:
from .trae import TraeIntegration
from .vibe import VibeIntegration
from .windsurf import WindsurfIntegration
from .zed import ZedIntegration

# -- Registration (alphabetical) --------------------------------------
_register(AgyIntegration())
Expand Down Expand Up @@ -111,6 +112,7 @@ def _register_builtins() -> None:
_register(TraeIntegration())
_register(VibeIntegration())
_register(WindsurfIntegration())
_register(ZedIntegration())


_register_builtins()
41 changes: 41 additions & 0 deletions src/specify_cli/integrations/zed/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Zed editor integration. — skills-based agent.
Comment thread
arrrrny marked this conversation as resolved.
Outdated

Zed uses the ``.agents/skills/speckit-<name>/SKILL.md`` layout so Spec Kit
commands are exposed as project-local skills that can be invoked from Zed's
slash-command menu.
"""

from __future__ import annotations

from ..base import IntegrationOption, SkillsIntegration


class ZedIntegration(SkillsIntegration):
"""Integration for Zed editor skills."""

key = "zed"
config = {
"name": "Zed",
"folder": ".agents/",
"commands_subdir": "skills",
"install_url": None,
"requires_cli": False,
}
registrar_config = {
"dir": ".agents/skills",
"format": "markdown",
"args": "$ARGUMENTS",
"extension": "/SKILL.md",
}
context_file = "AGENTS.md"

@classmethod
def options(cls) -> list[IntegrationOption]:
return [
IntegrationOption(
"--skills",
is_flag=True,
default=True,
help="Install as agent skills (default for Zed)",
),
]
Comment thread
arrrrny marked this conversation as resolved.
Outdated
1 change: 1 addition & 0 deletions tests/integrations/test_integration_subcommand.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ def test_list_shows_available_integrations(self, tmp_path):
# Should show multiple integrations
assert "claude" in result.output
assert "gemini" in result.output
assert "zed" in result.output

def test_list_shows_multi_install_safe_status(self, tmp_path):
project = _init_project(tmp_path, "claude")
Expand Down
51 changes: 51 additions & 0 deletions tests/integrations/test_integration_zed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""Tests for ZedIntegration."""

import json

from specify_cli.integrations import get_integration

from .test_integration_base_skills import SkillsIntegrationTests


class TestZedIntegration(SkillsIntegrationTests):
KEY = "zed"
FOLDER = ".agents/"
COMMANDS_SUBDIR = "skills"
REGISTRAR_DIR = ".agents/skills"
CONTEXT_FILE = "AGENTS.md"

def test_requires_cli_is_false(self):
"""Zed is IDE-based; requires_cli must remain False."""
i = get_integration(self.KEY)
assert i is not None
assert i.config is not None
assert i.config["requires_cli"] is False


class TestZedHookInvocations:
"""Zed hook messages should reference slash-invokable skills."""

def test_hooks_render_skill_invocation(self, tmp_path):
from specify_cli.extensions import HookExecutor

project = tmp_path / "zed-hooks"
project.mkdir()
init_options = project / ".specify" / "init-options.json"
init_options.parent.mkdir(parents=True, exist_ok=True)
init_options.write_text(json.dumps({"ai": "zed", "ai_skills": True}))

hook_executor = HookExecutor(project)
message = hook_executor.format_hook_message(
"before_plan",
[
{
"extension": "test-ext",
"command": "speckit.plan",
"optional": False,
}
],
)

assert "Executing: `/speckit-plan`" in message
Comment thread
arrrrny marked this conversation as resolved.
assert "EXECUTE_COMMAND: speckit.plan" in message
assert "EXECUTE_COMMAND_INVOCATION: /speckit-plan" in message
2 changes: 1 addition & 1 deletion tests/integrations/test_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
# Stage 4 — TOML integrations
"gemini", "tabnine",
# Stage 5 — skills, generic & option-driven integrations
"codex", "kimi", "agy", "generic",
"codex", "kimi", "agy", "zed", "generic",
]


Expand Down