feat: native Cursor IDE integration#301
Conversation
Add full Cursor support as the third integration target alongside
GitHub Copilot and Claude. When a .cursor/ directory exists, apm install
deploys primitives in Cursor's native formats:
- Instructions → .cursor/rules/*.mdc (applyTo: → globs: conversion)
- Agents → .cursor/agents/*.md
- Skills → .cursor/skills/{name}/SKILL.md
- Hooks → .cursor/hooks.json (merged with _apm_source tracking)
- MCP → .cursor/mcp.json (CursorClientAdapter)
Architecture:
- TargetProfile data layer (targets.py) for scalable multi-target design
- Opt-in pattern: only deploys when .cursor/ already exists
- .cursor-plugin/plugin.json detection for Cursor-native plugin repos
- BaseIntegrator updated with .cursor/ prefixes and partition buckets
- Full install/uninstall wiring across all 3 code paths
Tests: 73 new tests (18 skill, 13 agent, 8 hook, 21 rules, 13 MCP)
Docs: 8 pages updated to reflect Cursor as native integration target
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR adds native Cursor IDE integration as the third target alongside GitHub Copilot and Claude. When a .cursor/ directory exists in a project, apm install deploys primitives in Cursor's native formats (rules .mdc, agents, skills, hooks, MCP) — no compilation needed.
Changes:
- Adds
TargetProfiledata layer andCursorClientAdapterfor data-driven multi-target architecture and Cursor MCP support - Extends all integrators (instructions, agents, skills, hooks) with Cursor-specific deployment methods and wires them into install/uninstall flows
- Updates 8 documentation pages and CHANGELOG to reflect Cursor as a native integration target
Reviewed changes
Copilot reviewed 31 out of 32 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/apm_cli/integration/targets.py |
New data-driven target profile definitions (TargetProfile, PrimitiveMapping, KNOWN_TARGETS) |
src/apm_cli/adapters/client/cursor.py |
New CursorClientAdapter for .cursor/mcp.json management |
src/apm_cli/integration/base_integrator.py |
Add .cursor/ integration prefixes and partition buckets |
src/apm_cli/integration/instruction_integrator.py |
Add Cursor Rules (.mdc) conversion and deployment |
src/apm_cli/integration/agent_integrator.py |
Add Cursor agent deployment to .cursor/agents/ |
src/apm_cli/integration/skill_integrator.py |
Add Cursor skill deployment to .cursor/skills/ |
src/apm_cli/integration/hook_integrator.py |
Add Cursor hook integration to .cursor/hooks.json |
src/apm_cli/integration/mcp_integrator.py |
Add Cursor to MCP runtime detection and stale cleanup |
src/apm_cli/commands/install.py |
Wire Cursor integration into all 3 install code paths |
src/apm_cli/commands/uninstall.py |
Wire Cursor cleanup and re-integration during uninstall |
src/apm_cli/factory.py |
Register CursorClientAdapter in ClientFactory |
src/apm_cli/utils/helpers.py |
Add .cursor-plugin/plugin.json detection |
src/apm_cli/deps/github_downloader.py |
Add .cursor-plugin/plugin.json validation |
src/apm_cli/core/target_detection.py |
Update target descriptions for Cursor |
src/apm_cli/integration/__init__.py |
Export new target types |
tests/unit/test_cursor_mcp.py |
13 MCP adapter tests |
tests/unit/test_mcp_client_factory.py |
Add cursor to supported types |
tests/unit/test_helpers.py |
Add cursor plugin.json tests |
tests/unit/integration/test_skill_integrator.py |
18 Cursor skill tests |
tests/unit/integration/test_instruction_integrator.py |
21 Cursor rules tests |
tests/unit/integration/test_hook_integrator.py |
8 Cursor hook tests |
tests/unit/integration/test_agent_integrator.py |
13 Cursor agent tests |
docs/src/content/docs/*/ |
8 doc pages updated |
CHANGELOG.md |
Unreleased entry for Cursor integration |
Comments suppressed due to low confidence (1)
src/apm_cli/commands/install.py:660
- The commands integration block (lines 647–660) is indented inside the
for tp in cursor_agent_result.target_paths:loop body. This means the Claude commands integration only runs whencursor_agent_result.target_pathsis non-empty — i.e., only when Cursor agents were deployed. If there are no Cursor agent target paths (e.g.,.cursor/doesn't exist), the commands block is silently skipped entirely.
This was likely caused by inserting the Cursor agents block between the Claude agents block and the commands block without adjusting indentation. The commands block (and the closing of the if integrate_claude: guard that previously wrapped it) needs to be dedented back to the function's top indentation level.
deployed.append(tp.relative_to(project_root).as_posix())
# --- commands (.claude) ---
command_result = command_integrator.integrate_package_commands(
package_info, project_root,
force=force, managed_files=managed_files,
diagnostics=diagnostics,
)
if command_result.files_integrated > 0:
result["commands"] += command_result.files_integrated
_rich_info(f" └─ {command_result.files_integrated} commands integrated → .claude/commands/")
if command_result.files_updated > 0:
_rich_info(f" └─ {command_result.files_updated} commands updated")
result["links_resolved"] += command_result.links_resolved
for tp in command_result.target_paths:
deployed.append(tp.relative_to(project_root).as_posix())
src/apm_cli/commands/uninstall.py
Outdated
| managed_files=_buckets["agents_cursor"] if _buckets else None) | ||
| agents_cleaned += result.get("files_removed", 0) | ||
|
|
||
| if Path(".github/skills").exists() or Path(".claude/skills").exists(): |
| # Known integration prefixes that APM is allowed to deploy/remove under. | ||
| # Derived from ``targets.KNOWN_TARGETS`` so the allow-list stays in sync. | ||
| @staticmethod | ||
| def _get_integration_prefixes() -> tuple: | ||
| from apm_cli.integration.targets import get_integration_prefixes | ||
| return get_integration_prefixes() | ||
|
|
||
| INTEGRATION_PREFIXES = (".github/", ".claude/", ".cursor/") |
| | GitHub Copilot | `.github/instructions/`, `.github/prompts/`, agents, hooks, plugins, MCP | `AGENTS.md` (optional) | **Full** | | ||
| | Claude | `.claude/` commands, skills, MCP | `CLAUDE.md` | **Full** | | ||
| | Cursor | — | `.cursor/rules/` | Instructions via compile | | ||
| | Cursor | `.cursor/mcp.json` (MCP) | `.cursor/rules/` | MCP + Instructions | |
| #### Cursor (`.cursor/`) | ||
|
|
||
| | Location | Purpose | | ||
| |----------|---------| | ||
| | `.cursor/rules/*.mdc` | Instructions converted to Cursor rules format | | ||
| | `.cursor/agents/*.md` | Sub-agents from installed packages | | ||
| | `.cursor/hooks.json` (hooks key) | Hooks from installed packages (merged into config) | | ||
| | `.cursor/hooks/{pkg}/` | Referenced hook scripts | |
| # Copy the entire skill folder (identical to github copy) | ||
| shutil.copytree(source_path, cursor_skill_dir) | ||
|
|
||
| return (github_skill_dir, claude_skill_dir) |
…alability
- Fix critical indentation bug: commands block was nested inside cursor
agents for-loop, causing Claude commands to silently skip when .cursor/
didn't exist (install.py:647-660)
- Derive INTEGRATION_PREFIXES from KNOWN_TARGETS: removed hardcoded class
attribute, validate_deploy_path() now calls _get_integration_prefixes()
so adding a target auto-propagates the allow-list (base_integrator.py)
- Change copy_skill_to_target() return from tuple[Path|None, Path|None]
to list[Path]: scales to N targets without fixed-width tuples. Refactored
opt-in target loop to iterate ('.claude', '.cursor') (skill_integrator.py)
- Add .cursor/skills guard in uninstall cleanup (uninstall.py:425)
- Fix docs: Cursor feature table now shows 'Full' support, added missing
skills and MCP rows to ide-tool-integration.md Cursor table
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Summary
Adds full Cursor IDE support as the third native integration target alongside GitHub Copilot and Claude. When a
.cursor/directory exists in a project,apm installdeploys primitives in Cursor's native formats — no compilation step needed.Closes #302
Primitive Mapping
.instructions.md).cursor/rules/*.mdcapplyTo:→globs:frontmatter conversion.agent.md).cursor/agents/*.mdSKILL.md).cursor/skills/{name}/SKILL.md.json).cursor/hooks.json+.cursor/hooks/{pkg}/_apm_sourcetracking.cursor/mcp.jsonmcpServersJSONArchitecture
TargetProfiledata layer (targets.py): Data-driven target definitions — adding a new target = adding a dict entry toKNOWN_TARGETS, not creating new classes.cursor/directory exists (same as Claude).cursor-plugin/plugin.jsondetection for consuming Cursor-native plugin repositoriesBaseIntegratorupdated with.cursor/prefixes and partition buckets for install/uninstallinstall.py(3 code paths) anduninstall.py(sync + re-integration)New Files
src/apm_cli/integration/targets.pyTargetProfile,PrimitiveMapping,KNOWN_TARGETSsrc/apm_cli/adapters/client/cursor.pyCursorClientAdapterfor.cursor/mcp.jsontests/unit/test_cursor_mcp.pyTests
73 new tests across 5 test files:
.mdcconversion, frontmatter mapping, collision detection)1758 total tests passing.
Docs
8 pages updated to reflect Cursor as a native integration target (no longer "compile-only"):
how-it-works.md,compilation.md,teams.md,adoption-playbook.mdskills.md,plugins.md,ide-tool-integration.md,what-is-apm.md