Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b122303
feat: add Zed integration
arrrrny May 31, 2026
1d20a63
fix: address Copilot review - fix docstring punctuation, merge upstre…
arrrrny Jun 1, 2026
d6df1e8
fix: update integrations stats grid to 31 for consistency
arrrrny Jun 2, 2026
d2538e7
Merge remote-tracking branch 'upstream/main' into feat/2779-zed-integ…
arrrrny Jun 2, 2026
2e99b19
fix: address Copilot review feedback
arrrrny Jun 2, 2026
e4f3b4c
Potential fix for pull request finding
arrrrny Jun 3, 2026
2b9b482
fix: address Copilot review round 2
arrrrny Jun 3, 2026
52b6d0b
Potential fix for pull request finding
arrrrny Jun 4, 2026
2dbbe75
fix: address copilot review feedback for zed integration
arrrrny Jun 4, 2026
48f7e94
Merge branch 'main' into feat/2779-zed-integration
arrrrny Jun 4, 2026
429ce89
fix: refine slash-skill logic and ai-skills validation
arrrrny Jun 4, 2026
04d3eb3
fix: remove unused variables and update ai-skills help text
arrrrny Jun 4, 2026
3d2c70b
fix: add trae_skill_mode to hook invocation for consistency
arrrrny Jun 4, 2026
cb89f7d
fix: make Agy always skills-based for consistency
arrrrny Jun 4, 2026
8a2c278
fix: gate agy_skill_mode and refactor _render_hook_invocation to use …
arrrrny Jun 4, 2026
e27ce85
Merge upstream/main into feat/2779-zed-integration
arrrrny Jun 4, 2026
5466d62
fix: address latest Copilot review comments
arrrrny Jun 4, 2026
a96d596
fix: address latest Copilot review comments
arrrrny Jun 5, 2026
0ced30a
Merge branch 'main' into feat/2779-zed-integration
arrrrny Jun 5, 2026
8a1d8fd
fix: re-add _is_skills_integration definition lost in merge
arrrrny Jun 5, 2026
c6a0b31
fix: gate zed_skill_mode on _is_skills_integration for consistency
arrrrny Jun 5, 2026
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 @@ -36,6 +36,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,8 +726,9 @@ 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"
zed_skill_mode = selected_ai == "zed" and _is_skills_integration
cline_skill_mode = selected_ai == "cline"
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
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
Comment thread
arrrrny marked this conversation as resolved.
Outdated

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 @@ -741,6 +742,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 @@ -750,7 +754,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 or cline_skill_mode:
if cursor_agent_skill_mode or copilot_skill_mode or devin_skill_mode or zed_skill_mode or cline_skill_mode:
return f"/speckit-{name}"
Comment thread
arrrrny marked this conversation as resolved.
Outdated
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
cline_mode = selected_ai == "cline"
Comment thread
arrrrny marked this conversation as resolved.

skill_name = self._skill_name_from_command(command_id)
Expand All @@ -2424,6 +2425,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
if cline_mode:
from .integrations.cline import format_cline_command_name

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 @@ -79,6 +79,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 @@ -113,6 +114,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.

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
Comment on lines +57 to +59
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
Loading