Skip to content

Commit 5732de6

Browse files
feat(cursor-agent): migrate from .cursor/commands to .cursor/skills (#2156)
Use SkillsIntegration so workflows ship as speckit-*/SKILL.md. Update init next-steps, extension hook invocation, docs, and tests. Made-with: Cursor
1 parent b6e19b4 commit 5732de6

File tree

5 files changed

+57
-13
lines changed

5 files changed

+57
-13
lines changed

docs/upgrade.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ This tells Spec Kit which feature directory to use when creating specs, plans, a
292292
```bash
293293
ls -la .claude/commands/ # Claude Code
294294
ls -la .gemini/commands/ # Gemini
295-
ls -la .cursor/commands/ # Cursor
295+
ls -la .cursor/skills/ # Cursor
296296
ls -la .pi/prompts/ # Pi Coding Agent
297297
```
298298

src/specify_cli/__init__.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1338,7 +1338,7 @@ def init(
13381338
step_num = 2
13391339

13401340
# Determine skill display mode for the next-steps panel.
1341-
# Skills integrations (codex, kimi, agy, trae) should show skill invocation syntax.
1341+
# Skills integrations (codex, kimi, agy, trae, cursor-agent) should show skill invocation syntax.
13421342
from .integrations.base import SkillsIntegration as _SkillsInt
13431343
_is_skills_integration = isinstance(resolved_integration, _SkillsInt)
13441344

@@ -1347,7 +1347,8 @@ def init(
13471347
kimi_skill_mode = selected_ai == "kimi"
13481348
agy_skill_mode = selected_ai == "agy" and _is_skills_integration
13491349
trae_skill_mode = selected_ai == "trae"
1350-
native_skill_mode = codex_skill_mode or claude_skill_mode or kimi_skill_mode or agy_skill_mode or trae_skill_mode
1350+
cursor_agent_skill_mode = selected_ai == "cursor-agent" and (ai_skills or _is_skills_integration)
1351+
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
13511352

13521353
if codex_skill_mode and not ai_skills:
13531354
# Integration path installed skills; show the helpful notice
@@ -1356,6 +1357,9 @@ def init(
13561357
if claude_skill_mode and not ai_skills:
13571358
steps_lines.append(f"{step_num}. Start Claude in this project directory; spec-kit skills were installed to [cyan].claude/skills[/cyan]")
13581359
step_num += 1
1360+
if cursor_agent_skill_mode and not ai_skills:
1361+
steps_lines.append(f"{step_num}. Start Cursor Agent in this project directory; spec-kit skills were installed to [cyan].cursor/skills[/cyan]")
1362+
step_num += 1
13591363
usage_label = "skills" if native_skill_mode else "slash commands"
13601364

13611365
def _display_cmd(name: str) -> str:
@@ -1365,6 +1369,8 @@ def _display_cmd(name: str) -> str:
13651369
return f"/speckit-{name}"
13661370
if kimi_skill_mode:
13671371
return f"/skill:speckit-{name}"
1372+
if cursor_agent_skill_mode:
1373+
return f"/speckit-{name}"
13681374
return f"/speckit.{name}"
13691375

13701376
steps_lines.append(f"{step_num}. Start using {usage_label} with your AI agent:")

src/specify_cli/extensions.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2170,6 +2170,7 @@ def _render_hook_invocation(self, command: Any) -> str:
21702170
codex_skill_mode = selected_ai == "codex" and bool(init_options.get("ai_skills"))
21712171
claude_skill_mode = selected_ai == "claude" and bool(init_options.get("ai_skills"))
21722172
kimi_skill_mode = selected_ai == "kimi"
2173+
cursor_skill_mode = selected_ai == "cursor-agent" and bool(init_options.get("ai_skills"))
21732174

21742175
skill_name = self._skill_name_from_command(command_id)
21752176
if codex_skill_mode and skill_name:
@@ -2178,6 +2179,8 @@ def _render_hook_invocation(self, command: Any) -> str:
21782179
return f"/{skill_name}"
21792180
if kimi_skill_mode and skill_name:
21802181
return f"/skill:{skill_name}"
2182+
if cursor_skill_mode and skill_name:
2183+
return f"/{skill_name}"
21812184

21822185
return f"/{command_id}"
21832186

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,39 @@
1-
"""Cursor IDE integration."""
1+
"""Cursor IDE integration.
22
3-
from ..base import MarkdownIntegration
3+
Cursor Agent uses the ``.cursor/skills/speckit-<name>/SKILL.md`` layout.
4+
Commands are deprecated; ``--skills`` defaults to ``True``.
5+
"""
46

7+
from __future__ import annotations
58

6-
class CursorAgentIntegration(MarkdownIntegration):
9+
from ..base import IntegrationOption, SkillsIntegration
10+
11+
12+
class CursorAgentIntegration(SkillsIntegration):
713
key = "cursor-agent"
814
config = {
915
"name": "Cursor",
1016
"folder": ".cursor/",
11-
"commands_subdir": "commands",
17+
"commands_subdir": "skills",
1218
"install_url": None,
1319
"requires_cli": False,
1420
}
1521
registrar_config = {
16-
"dir": ".cursor/commands",
22+
"dir": ".cursor/skills",
1723
"format": "markdown",
1824
"args": "$ARGUMENTS",
19-
"extension": ".md",
25+
"extension": "/SKILL.md",
2026
}
27+
2128
context_file = ".cursor/rules/specify-rules.mdc"
29+
30+
@classmethod
31+
def options(cls) -> list[IntegrationOption]:
32+
return [
33+
IntegrationOption(
34+
"--skills",
35+
is_flag=True,
36+
default=True,
37+
help="Install as agent skills (recommended for Cursor)",
38+
),
39+
]
Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,28 @@
11
"""Tests for CursorAgentIntegration."""
22

3-
from .test_integration_base_markdown import MarkdownIntegrationTests
3+
from .test_integration_base_skills import SkillsIntegrationTests
44

55

6-
class TestCursorAgentIntegration(MarkdownIntegrationTests):
6+
class TestCursorAgentIntegration(SkillsIntegrationTests):
77
KEY = "cursor-agent"
88
FOLDER = ".cursor/"
9-
COMMANDS_SUBDIR = "commands"
10-
REGISTRAR_DIR = ".cursor/commands"
9+
COMMANDS_SUBDIR = "skills"
10+
REGISTRAR_DIR = ".cursor/skills"
1111
CONTEXT_FILE = ".cursor/rules/specify-rules.mdc"
12+
13+
14+
class TestCursorAgentAutoPromote:
15+
"""--ai cursor-agent auto-promotes to integration path."""
16+
17+
def test_ai_cursor_agent_without_ai_skills_auto_promotes(self, tmp_path):
18+
"""--ai cursor-agent should work the same as --integration cursor-agent."""
19+
from typer.testing import CliRunner
20+
from specify_cli import app
21+
22+
runner = CliRunner()
23+
target = tmp_path / "test-proj"
24+
result = runner.invoke(app, ["init", str(target), "--ai", "cursor-agent", "--no-git", "--ignore-agent-tools", "--script", "sh"])
25+
26+
assert result.exit_code == 0, f"init --ai cursor-agent failed: {result.output}"
27+
assert (target / ".cursor" / "skills" / "speckit-plan" / "SKILL.md").exists()
28+

0 commit comments

Comments
 (0)