Cursor (cursor)
Set up this tool
Cursor reads its project configuration from .cursor/, including .cursor/rules/.
First rule
Install the client using the official setup guide, then open your project in it.
Select cursor in agnostic-ai.yaml, then follow First rule to add the shared commit-prefix rule and sync it. With the default paths, it reaches .cursor/rules/conventional-commits.mdc.
The IDE and CLI share rules, but the permission settings below guard only the Cursor CLI.
For existing files, check what import cursor reads before syncing.
Verify
Run agnostic-ai sync --check and inspect .cursor/rules/conventional-commits.mdc for the commit-prefix rule. Open the project in Cursor. Confirm the rule is Always Apply in the Rules panel, then ask for a commit subject for a small fix; it should use fix:.
Next, add a skill.
Output
.cursor/rules/<name>.mdc
.cursor/agents/<name>.md # one native subagent per agent spec
.cursor/skills/<name>/SKILL.md # one folder per skill, bundled assets included
.cursor/commands/<name>.md # one per command spec
.cursor/hooks.json # when hook specs exist (sync updates its entries, keeps yours)
.cursor/hooks/agnostic-ai-portable-hook.sh # when a portable before-tool hook exists
.cursor/mcp.json # when MCP entries exist
.cursor/cli.json # from settings permissions and deny protected paths (merged)
Native mappings
Field mappings, discovery, and compatibility
Rules
alwaysApply: true, or false when the spec sets globs without alwaysApply.
- A catch-all such as
**/*stays always-on (override in frontmatter). An always-apply rule omitsglobs. - A non-always rule without
globsfalls back to thepathslist (comma-joined). - With neither,
globsis omitted, so Cursor treats the rule as "Apply Intelligently" (description-driven) or "Apply Manually".
Per-file check
agnostic-ai explain --file <path> --target cursor checks each planned .mdc rule and every AGENTS.md Cursor reads against one file, using the Rules matrix.
Agents
Cursor subagents (Cursor 2.4+). Frontmatter has name, description, and any declared model, readonly, and is_background. The portable background is written as is_background, and an is_background the spec sets wins. The body is the system prompt.
- Cursor has no
toolsfield, so sync drops atoolslist with a sync note. Usereadonly: trueinstead. - Cursor has no effort field, so a portable
effortraises a sync note. Put it in the model id:model: {cursor: "claude-opus-5[effort=high]"}. See per-targetmodelandeffort.
Commands
Markdown files under .cursor/commands/, body as the prompt. Cursor no longer documents this path; see its migrate commands to skills FAQ.
Old copies
Sync removes older flattened .mdc, agent-as-command, and skill-<name>.mdc files.
Skills
.cursor/skills/<name>/SKILL.md, the Agent Skills layout (Cursor 2.4+), bundled files copied unchanged.
- Frontmatter has
name,description, and any declaredpaths,disable-model-invocation,icon,color, andmetadata.iconandcolorstyle the badge when the skill backs a Custom Mode. - A directory scope from the source folder moves the folder under that directory.
- Cursor loads skills only from the workspace it opens, so a skill's
workspaceslist adds a<dir>/.cursor/skills/<name>/copy per directory, beside the root copy. - Skill
modelandeffortare omitted with a sync note when a value resolves for Cursor.
Review
Bugbot files, root .cursor/BUGBOT.md for specs with no directory scope and <scope>/.cursor/BUGBOT.md for scoped ones.
- Same-scope specs are joined into one file.
- Rule limits: Bugbot truncates each BUGBOT.md at 30,000 characters and caps one review's rules at 100,000 in total.
- agnostic-ai never truncates. An over-cap file is written in full, with a sync note. The size counts the whole file, header included, so several small specs joined can pass the cap. Split the review across sibling scopes.
syncalso notes a scope whose chain (the scope and its parent directories) passes 100,000 characters. Team and repository rules share that budget, so a shorter chain can still lose rules. To see what Bugbot dropped, runbugbot run verbose=trueorcursor review verbose=trueon a pull request.
Environment
.cursor/environment.json (background-agent setup).
- Spec keys pass through as written, except fields that select a tool or directory and
cleanup, which gets a no-effect note. Specs merge by top-level key. setupandsetup-windowsgo to.cursor/worktrees.jsonassetup-worktree(every OS) andsetup-worktree-windows(Windows only) (worktrees).- A script path, which Cursor resolves from
.cursor/, or a unix-onlysetup-worktree-unix, goes underx-cursor:with its native key, such assetup-worktree-unix: setup.sh.
Ignore
.cursorignore (gitignore syntax). Specs are joined into one file.
Hooks
Cursor Hooks in a managed .cursor/hooks.json (version + per-event arrays). With builtins: [memory], a sessionStart hook adds the shared memory index to the session.
- A
type: prompthook writespromptand optionalmodel. Command and prompt hooks keeptimeout,loop_limit(includingnull),failClosed, andmatcher. - camelCase event names (
beforeShellExecution,afterFileEdit, ...) pass through.validateflags unknown ones. - Fifteen events take a
matcher:preToolUse,postToolUse,postToolUseFailure(tool name),subagentStart,subagentStop(subagent type),beforeShellExecution,afterShellExecution(full command string),beforeReadFile,afterFileEdit(tool name), andbeforeTabFileRead,afterTabFileEdit,beforeSubmitPrompt,stop,afterAgentResponse,afterAgentThought(one fixed value each).lintflags a matcher on any other event, exceptbeforeMCPExecutionandafterMCPExecution. - When a command hook exists, sync adds a
sessionStarthook that prints{"env":{"AGNOSTIC_AI_TARGET":"cursor"}}. Cursor passes thatenvto every later hook in the session, including loaded Claude Code hooks, and on Windows too.sessionStarthooks, and any hook that fires before this one returns, do not see the variable. See which target ran a hook. - A hook has only
command, run through$SHELL -c(PowerShell on Windows). A spec'sargsfold intocommand, each in single quotes (node 'guard.js'). - A portable
before-toolhook lands onpreToolUseand aprompt-submithook onbeforeSubmitPrompt, each through.cursor/hooks/agnostic-ai-portable-hook.sh. Exit 2 becomes a deny reply with stderr as the message, and exit 0 an allow reply.session-startandsession-endland onsessionStartandsessionEnd.sync --globalwrites the wrapper to~/.cursor/hooks/.
MCP
.cursor/mcp.json under mcpServers. Each sync replaces the whole file from MCP specs. Cursor-only fields (cursor.com/docs/mcp):
- A stdio server accepts
envFile, a path to an env file with extra variables. - A
${NAME}reference inenv,headers,url, orargsis written as Cursor's${env:NAME};${workspaceFolder}and Cursor's other variables stay as written. See environment references. - A remote (
url) server accepts a static-OAuthauth,{CLIENT_ID, CLIENT_SECRET, scopes}withCLIENT_IDrequired, for providers without OAuth Dynamic Client Registration. - A stdio server carries an explicit
"type": "stdio", which Cursor requires. - A
rootslist passes through, though Cursor documents no per-serverrootskey. disabled: truehas no effect here; seedisabledsupport by target.
Hook args with apostrophes or spaces
PowerShell reads that quoting the same way unless an arg has an apostrophe. A command path with a space is quoted too, and PowerShell does not run it. Use a shell-form command in those cases.
Third-party reads
The Include Third-Party Plugins, Skills, and Other Configs setting (Cursor Settings → Agents → Third-Party Imports, on by default) turns on Claude and Codex compatibility reads. Turn it off to stop those reads, or give the spec a single target:. Cursor reads .agents/skills/ (written by codex) either way.
- Skills (docs): Native project roots are
.cursor/skills/and.agents/skills/. Compatibility roots include.claude/skills/,.codex/skills/, and their home equivalents. Cursor does not document which root wins on a duplicate. - Subagents (docs): Cursor reads
.claude/agents/and.codex/agents/(current project only) beside.cursor/agents/. On a name clash,.cursor/wins. Cursor documents only Markdown agents, so it may not parse thecodextarget's TOML files. - Hooks (docs): Cursor merges seven hook sources, including
.cursor/hooks.jsonand.claude/settings.json, runs every matching hook, and maps Claude event names to Cursor ones. A repo syncingclaudeandcursorruns every hook twice, except a portable hook, whose Claude Code copy exits under Cursor (details). The setting is the only way to stop the rest. - Hook
args: Cursor's third-party hooks docs do not listargs, so a claude hook spec withargsmay run as a bare interpreter that reads the JSON event data as its program.syncnotes this whenclauderuns andcursoris configured. Use a shell-formcommand, or turn off the setting.
Config keys
| Key | Default |
|---|---|
outputs.cursor.rules-dir | .cursor/rules |
outputs.cursor.agents-dir | .cursor/agents |
outputs.cursor.skills-dir | .cursor/skills |
outputs.cursor.commands-dir | .cursor/commands |
outputs.cursor.mcp-file | .cursor/mcp.json |
outputs.cursor.review-file | BUGBOT.md |
outputs.cursor.environment-file | .cursor/environment.json |
outputs.cursor.ignore-file | .cursorignore |
outputs.cursor.hooks-file | .cursor/hooks.json |
Permissions
A settings spec's portable allow and deny lists become Cursor CLI rules in permissions.allow and permissions.deny of .cursor/cli.json, beside the protected-path rules. Sync writes only spellings the CLI permissions page documents, and never widens a rule. The rules guard the Cursor CLI only, not the IDE agent.
| Portable rule | Cursor rule | Notes |
|---|---|---|
Bash(git:*), Bash(git *) | Shell(git) | One command word only. Shell(git) covers bare git too. |
Read(<path>) | Read(<path>) | /path loses its /, since Cursor scopes a relative path to the workspace. //path becomes the absolute /path. On deny, a path with no * or ? also gets Read(<path>/**). |
Edit(<path>), Write(<path>) | Write(<path>) | Same path rules as Read. |
WebFetch(domain:<host>) | WebFetch(<host>) | An exact host, *.host, or *. |
mcp__<server>__<tool> | Mcp(<server>:<tool>) | mcp__<server> becomes Mcp(<server>:*). On deny only, mcp__* becomes Mcp(*:*) and mcp__*__<tool> becomes Mcp(*:<tool>). |
These stay out:
- A multi-word command such as
Bash(go test:*)orBash(git push:*).Shelltakes one command word, andShell(go)would cover everygocommand. - An exact command such as
Bash(ls)orBash(rm), sinceShell(rm)also matchesrmwith arguments. - A path starting with
~or!, or using[],{}, or\. - A
WebFetchhost with a path, a port, or a wildcard other than a leading*.. askrules. The CLI has no ask list and already prompts before a call noallowrule covers.- Other tools, such as
WebSearch,Grep, or a bareBash.
A dropped allow rule raises a sync note. A dropped deny rule loosens the policy, so sync names each one: deny rule not enforced on cursor: Bash(git push:*). Block those with a Cursor hook on beforeShellExecution.
Cursor does not say how Shell matches a chained command such as git status && rm -rf build, or a pipe. Claude Code checks each subcommand of a chain, so an allow rule may approve more in the Cursor CLI.
A relative path means the current directory in Claude Code and the workspace in Cursor.
A rule you remove from a spec leaves cli.json on the next sync. A rule you wrote there yourself stays. Sync always writes both lists, since the Cursor CLI rejects a permissions object without one, and removes the file it created once no rule is left. With memory.personal: repo, sync also allows writes to the personal memory folder. A folder path with pattern characters, including parentheses, gets no rule and a note; move AGNOSTIC_AI_HOME or set memory.personal: checkout.
Import
agnostic-ai import cursor reads:
| Source | Becomes |
|---|---|
.cursor/rules/<name>.mdc | <rules>/<name>.md, frontmatter kept as written |
.cursor/rules/<sub>/<name>.mdc | <rules>/<sub>/<name>.md |
(no name: in frontmatter) | name: added from the filename |
.cursor/agents/<name>.md | <agents>/<name>.md, generated header stripped |
.cursor/skills/<name>/ and .agents/skills/<name>/ | <skills>/<name>/, full folder and all its files, SKILL.md merged onto the existing spec |
.cursor/commands/<name>.md | <commands>/<name>.md, generated header stripped |
.cursor/BUGBOT.md | <reviews>/review.md |
<scope>/.cursor/BUGBOT.md | <reviews>/<scope-slug>.md with scope: <scope>, such as services-api.md for services/api |
Import skips a BUGBOT.md that sync wrote.
Both skill directories are project-level, at the root and in nested subdirectories. The nesting becomes the spec scope. .cursor/skills wins a same-name clash at the same scope.
- Cursor writes no
argument-hintorallowed-toolson a skill, so import leaves both on the spec. - Keys Cursor does write, such as a skill's
iconor an agent'smodel, follow the native file. - A rule's frontmatter comes from the
.mdcalone, so wideningglobsto**/*unscopes the spec. - A rule with neither
alwaysApplynorglobsgetsalwaysApply: false, since Cursor does not load it on every request. A rule scoped to a directory gets no flag; it already loads only there.
import cursor also reads environment files:
- A hand-written
worktrees.jsonbecomesenvironments/worktree.yaml, mapped the same way sync writes it. - A hand-written
environment.json(comments allowed) becomesenvironments/cursor.yaml, keeping every key and turning//comments into YAML comments. A file that sets a key the spec reads itself, such asnameorsetup, stays as written with a note.
Protected paths
Enforced (permission). In the Cursor CLI, each path of a decision: deny block becomes Write(<path>) and Write(<path>/**) rules in permissions.deny of .cursor/cli.json, the project CLI config (configuration). A path that already ends in ** gets the first rule only. The rules carry no leading /: Cursor scopes a relative path to the workspace and reads a leading / as an absolute path (CLI permissions).
- IDE agent: not covered (permissions). State the paths in a rule if the IDE agent should know about them.
decision: ask: not enforced, with a sync note. The CLI has no ask list and already prompts before a write noallowrule covers. Usedecision: denyto block.reason: not written. A CLI permission rule has no message field.- Other settings fields:
model,effort, andx-cursorraise a sync note, since a projectcli.jsontakespermissionsonly.
cli.json merges as Permissions describes: your own rules and other keys stay, and removing a path or its block removes its rules on the next sync. A matching rule that was in cli.json before sync added one stays yours. See Protected paths.