Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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
6 changes: 3 additions & 3 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

**Define what to build before building it — with any AI coding agent.**

Spec Kit is a toolkit for [Spec-Driven Development](concepts/sdd.md) (SDD), a methodology that puts specifications at the center of AI-assisted software development. Instead of jumping straight to code, you describe *what* to build, refine it through structured phases, and let your AI coding agent implement it.
Spec Kit is a toolkit for [Spec-Driven Development](concepts/sdd.md) (SDD), a methodology that puts specifications at the center of AI-assisted software development. Instead of jumping straight to code, you describe _what_ to build, refine it through structured phases, and let your AI coding agent implement it.

<a href="installation.md" class="btn btn-primary btn-lg">Install Spec Kit</a>&nbsp;
<a href="quickstart.md" class="btn btn-outline-primary btn-lg">Quick Start</a>
Expand All @@ -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">33 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
Comment thread
arrrrny marked this conversation as resolved.
Outdated
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 Expand Up @@ -90,7 +90,7 @@ Community extensions like CI Guard and Architecture Guard add compliance gates a
<span class="stat-label">Contributors</span>
</div>
<div class="stat-item">
<span class="stat-number">30</span>
<span class="stat-number">31</span>
Comment thread
arrrrny marked this conversation as resolved.
Outdated
<span class="stat-label">Integrations</span>
Comment thread
arrrrny marked this conversation as resolved.
Outdated
</div>
<div class="stat-item">
Expand Down
1 change: 1 addition & 0 deletions docs/reference/integrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,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 @@ -728,8 +728,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 (ai_skills or _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

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 @@ -743,6 +744,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 @@ -752,7 +756,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
9 changes: 5 additions & 4 deletions src/specify_cli/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2486,22 +2486,23 @@ 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"
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)
if codex_skill_mode and skill_name:
return f"${skill_name}"
if claude_skill_mode and skill_name:
return f"/{skill_name}"
if kimi_skill_mode and skill_name:
return f"/skill:{skill_name}"
if cursor_skill_mode and skill_name:
return f"/{skill_name}"
if cline_mode:
from .integrations.cline import format_cline_command_name

return f"/{format_cline_command_name(command_id)}"

# Slash-skill integrations (Claude, Cursor, Zed…)
if skill_name and (claude_skill_mode or cursor_skill_mode or zed_skill_mode):
return f"/{skill_name}"
Comment thread
arrrrny marked this conversation as resolved.
Comment thread
arrrrny marked this conversation as resolved.

return f"/{command_id}"

def get_project_config(self) -> Dict[str, Any]:
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()
34 changes: 34 additions & 0 deletions src/specify_cli/integrations/zed/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""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 []
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
120 changes: 120 additions & 0 deletions tests/integrations/test_integration_zed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
"""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_options_include_skills_flag(self):
"""Zed is always skills-based; no --skills option needed."""
i = get_integration(self.KEY)
skills_opts = [o for o in i.options() if o.name == "--skills"]
assert len(skills_opts) == 0
Comment thread
arrrrny marked this conversation as resolved.
Outdated

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

def test_init_persists_ai_skills_for_zed(self, tmp_path):
"""specify init --integration zed must persist ai_skills: true,
so HookExecutor renders slash-skill invocations without manual
init-options manipulation."""
from typer.testing import CliRunner

from specify_cli import app
from specify_cli.extensions import HookExecutor

project = tmp_path / "zed-init-test"
project.mkdir()
old_cwd = None
try:
import os

old_cwd = os.getcwd()
os.chdir(project)
runner = CliRunner()
result = runner.invoke(
app,
[
"init",
"--here",
"--integration",
"zed",
"--script",
"sh",
"--no-git",
"--ignore-agent-tools",
],
catch_exceptions=False,
)
finally:
if old_cwd is not None:
os.chdir(old_cwd)
Comment thread
arrrrny marked this conversation as resolved.
Outdated

assert result.exit_code == 0, f"init failed: {result.output}"

opts_path = project / ".specify" / "init-options.json"
assert opts_path.exists()
opts = json.loads(opts_path.read_text(encoding="utf-8"))
assert opts.get("ai") == "zed"
assert opts.get("ai_skills") is True, (
f"init must persist ai_skills=true for Zed, got: {opts.get('ai_skills')}"
)

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, (
"Hook rendering must produce /speckit-plan for Zed without hint injection\n"
f"Got message: {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