From cb19a1f06f61858208b62f3df206d21c69e86598 Mon Sep 17 00:00:00 2001 From: Gabriel Henrique Date: Fri, 10 Apr 2026 10:52:42 -0300 Subject: [PATCH] feat(cursor-agent): migrate from .cursor/commands to .cursor/skills Use SkillsIntegration so workflows ship as speckit-*/SKILL.md. Update init next-steps, extension hook invocation, docs, and tests. Made-with: Cursor --- docs/upgrade.md | 2 +- src/specify_cli/__init__.py | 10 +++++-- src/specify_cli/extensions.py | 3 ++ .../integrations/cursor_agent/__init__.py | 30 +++++++++++++++---- .../test_integration_cursor_agent.py | 25 +++++++++++++--- 5 files changed, 57 insertions(+), 13 deletions(-) diff --git a/docs/upgrade.md b/docs/upgrade.md index cd5cc124fe..aecbb7879b 100644 --- a/docs/upgrade.md +++ b/docs/upgrade.md @@ -292,7 +292,7 @@ This tells Spec Kit which feature directory to use when creating specs, plans, a ```bash ls -la .claude/commands/ # Claude Code ls -la .gemini/commands/ # Gemini - ls -la .cursor/commands/ # Cursor + ls -la .cursor/skills/ # Cursor ls -la .pi/prompts/ # Pi Coding Agent ``` diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 11b6e0eda5..7f343f7a14 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -1338,7 +1338,7 @@ def init( step_num = 2 # Determine skill display mode for the next-steps panel. - # Skills integrations (codex, kimi, agy, trae) should show skill invocation syntax. + # Skills integrations (codex, kimi, agy, trae, cursor-agent) should show skill invocation syntax. from .integrations.base import SkillsIntegration as _SkillsInt _is_skills_integration = isinstance(resolved_integration, _SkillsInt) @@ -1347,7 +1347,8 @@ def init( kimi_skill_mode = selected_ai == "kimi" agy_skill_mode = selected_ai == "agy" and _is_skills_integration trae_skill_mode = selected_ai == "trae" - native_skill_mode = codex_skill_mode or claude_skill_mode or kimi_skill_mode or agy_skill_mode or trae_skill_mode + cursor_agent_skill_mode = selected_ai == "cursor-agent" and (ai_skills or _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 if codex_skill_mode and not ai_skills: # Integration path installed skills; show the helpful notice @@ -1356,6 +1357,9 @@ def init( if claude_skill_mode and not ai_skills: steps_lines.append(f"{step_num}. Start Claude in this project directory; spec-kit skills were installed to [cyan].claude/skills[/cyan]") step_num += 1 + if cursor_agent_skill_mode and not ai_skills: + steps_lines.append(f"{step_num}. Start Cursor Agent in this project directory; spec-kit skills were installed to [cyan].cursor/skills[/cyan]") + step_num += 1 usage_label = "skills" if native_skill_mode else "slash commands" def _display_cmd(name: str) -> str: @@ -1365,6 +1369,8 @@ def _display_cmd(name: str) -> str: return f"/speckit-{name}" if kimi_skill_mode: return f"/skill:speckit-{name}" + if cursor_agent_skill_mode: + return f"/speckit-{name}" return f"/speckit.{name}" steps_lines.append(f"{step_num}. Start using {usage_label} with your AI agent:") diff --git a/src/specify_cli/extensions.py b/src/specify_cli/extensions.py index da1a5f4472..d03018b024 100644 --- a/src/specify_cli/extensions.py +++ b/src/specify_cli/extensions.py @@ -2170,6 +2170,7 @@ def _render_hook_invocation(self, command: Any) -> str: codex_skill_mode = selected_ai == "codex" and bool(init_options.get("ai_skills")) 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")) skill_name = self._skill_name_from_command(command_id) if codex_skill_mode and skill_name: @@ -2178,6 +2179,8 @@ def _render_hook_invocation(self, command: Any) -> str: 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}" return f"/{command_id}" diff --git a/src/specify_cli/integrations/cursor_agent/__init__.py b/src/specify_cli/integrations/cursor_agent/__init__.py index c244a7c01a..a5472654fa 100644 --- a/src/specify_cli/integrations/cursor_agent/__init__.py +++ b/src/specify_cli/integrations/cursor_agent/__init__.py @@ -1,21 +1,39 @@ -"""Cursor IDE integration.""" +"""Cursor IDE integration. -from ..base import MarkdownIntegration +Cursor Agent uses the ``.cursor/skills/speckit-/SKILL.md`` layout. +Commands are deprecated; ``--skills`` defaults to ``True``. +""" +from __future__ import annotations -class CursorAgentIntegration(MarkdownIntegration): +from ..base import IntegrationOption, SkillsIntegration + + +class CursorAgentIntegration(SkillsIntegration): key = "cursor-agent" config = { "name": "Cursor", "folder": ".cursor/", - "commands_subdir": "commands", + "commands_subdir": "skills", "install_url": None, "requires_cli": False, } registrar_config = { - "dir": ".cursor/commands", + "dir": ".cursor/skills", "format": "markdown", "args": "$ARGUMENTS", - "extension": ".md", + "extension": "/SKILL.md", } + context_file = ".cursor/rules/specify-rules.mdc" + + @classmethod + def options(cls) -> list[IntegrationOption]: + return [ + IntegrationOption( + "--skills", + is_flag=True, + default=True, + help="Install as agent skills (recommended for Cursor)", + ), + ] diff --git a/tests/integrations/test_integration_cursor_agent.py b/tests/integrations/test_integration_cursor_agent.py index 71b7db1c98..3384fdc14f 100644 --- a/tests/integrations/test_integration_cursor_agent.py +++ b/tests/integrations/test_integration_cursor_agent.py @@ -1,11 +1,28 @@ """Tests for CursorAgentIntegration.""" -from .test_integration_base_markdown import MarkdownIntegrationTests +from .test_integration_base_skills import SkillsIntegrationTests -class TestCursorAgentIntegration(MarkdownIntegrationTests): +class TestCursorAgentIntegration(SkillsIntegrationTests): KEY = "cursor-agent" FOLDER = ".cursor/" - COMMANDS_SUBDIR = "commands" - REGISTRAR_DIR = ".cursor/commands" + COMMANDS_SUBDIR = "skills" + REGISTRAR_DIR = ".cursor/skills" CONTEXT_FILE = ".cursor/rules/specify-rules.mdc" + + +class TestCursorAgentAutoPromote: + """--ai cursor-agent auto-promotes to integration path.""" + + def test_ai_cursor_agent_without_ai_skills_auto_promotes(self, tmp_path): + """--ai cursor-agent should work the same as --integration cursor-agent.""" + from typer.testing import CliRunner + from specify_cli import app + + runner = CliRunner() + target = tmp_path / "test-proj" + result = runner.invoke(app, ["init", str(target), "--ai", "cursor-agent", "--no-git", "--ignore-agent-tools", "--script", "sh"]) + + assert result.exit_code == 0, f"init --ai cursor-agent failed: {result.output}" + assert (target / ".cursor" / "skills" / "speckit-plan" / "SKILL.md").exists() +