From .cursorrules to .ai/: How I Unified My Cursor and Claude Config Into One Place
Authored on
For a while, my .cursorrules file was doing fine. It was a 760-line monster that described the project, the Drupal coding standards, the git commit format, the SCSS conventions, what files to ignore, and — my personal favorite — a reminder not to run git push without checking SSH first. It worked. Cursor read it, understood the project context, and mostly did the right thing.
Then I started using Claude Code alongside Cursor. And suddenly I had a problem I didn't expect: two AI tools, two separate config systems, and zero reason why they should diverge.
The Monolith Problem
The original .cursorrules was written as one giant file. Everything in one place — which sounds like a good idea until you try to update the PHP standards section without accidentally breaking the Twig section because they were formatted slightly differently and you're editing 700 lines of plain text at 11pm.
The other issue: it loaded all the time, for every file. Cursor would get the full project context regardless of what you were working on. That's fine for small projects, but when you're building rules for Drupal PHP, Twig templates, SCSS/BEM, JavaScript, and git commit conventions, that's a lot of noise in every prompt.
I knew I needed to split it up. I just kept putting it off because it worked, and refactoring config files doesn't ship features.
First Attempt: Rules and Skills (Feb 24)
The push finally came on one of my Drupal client projects. I was context-switching a lot — some sessions were pure theme work, others were custom module PHP, others were debugging config sync. The monolithic .cursorrules was getting loaded with full PHP standards when all I needed was the SCSS rules.
So I did the split. Broke .cursorrules into:
8 rule files in .cursor/rules/:
drupal-php-standards.mdc— PHP coding standards, type hints, constructor injection, PHPCStwig-templates.mdc— Twig conventions, template overrides,{{ attach_library() }}javascript-drupal.mdc— Drupal JS behaviors,once(), ES6 in the Drupal contextscss-bem.mdc— BEM methodology, nesting rules, no magic numberscss-verify-selectors.mdc— The one rule I actually needed most: verify selectors against real HTML, never guessproblem-solving-dont-force.mdc— Don't keep pushing the same broken solutionfile-exclusions.mdc— What to never touch or suggest modifyinggit-commits.mdc— Branch prefix format, no AI attribution lines in commits
6 skill files in .cursor/skills/:
drupal-new-module— The full workflow for scaffolding a custom moduledrupal-theme-changes— Cache clearing, template suggestions, the whole dancedrupal-config-management— Export/import, UUID conflicts, the usual dramaissue-documentation-first— Don't start coding until the issue doc is writtendrupal-debugging— Where to look first,dpm(), Xdebug configdrupal-troubleshooting-commands— Lando, Drush, cache, the cheatsheet
The .cursorrules file went from 760 lines to a slim map-and-reference file that just described what each rule and skill was for. Much better. Cursor now loads only the relevant .mdc files based on what's open.
I did this, felt good about it, and then about three weeks later I broke everything by introducing Claude Code into the picture.
The Claude Problem
Claude Code has its own config system. It reads CLAUDE.md at the project root (or .claude/CLAUDE.md), loads skills from .claude/skills/, and applies rules from .claude/rules/. It's a clean system. The problem is it has nothing to do with Cursor's .cursor/ directory.
So now I had:
.cursorrules(legacy, barely maintained).cursor/rules/with 8 files.cursor/skills/with 6 filesCLAUDE.md— project overview I'd written separately.claude/— Claude-specific config
And they were already drifting. I'd update a PHP standard in the Cursor rule and forget to update the equivalent Claude context. I added a new Lando hook in the Claude config and didn't think about whether Cursor needed to know about it either.
Two AI tools. Two configs. Same project. This was going to become a maintenance nightmare.
The Real Fix: .ai/ as Single Source of Truth
The insight was simple once I stopped trying to manage both configs separately: the rules and skills are the same regardless of which AI reads them. The project doesn't care whether Cursor or Claude is asking about PHP standards. The answer is the same.
So I created .ai/ as the canonical location for everything:
.ai/
rules/
drupal-standards.md
drupal-twig-cache.md
execution-and-tools.md
incremental-development.md
skills/
my-drupal-template-cache/SKILL.md
my-task-workflow/SKILL.md
my-wrap-up-task/SKILL.md
hooks/
lando-guard-core.shThen I pointed both tools at it with symlinks:
# Claude reads .claude/rules/ and .claude/skills/
# Make those directory symlinks to .ai/
ln -s ../.ai/rules .claude/rules
ln -s ../.ai/skills .claude/skills
# Cursor reads individual .mdc files in .cursor/rules/
# Symlink each file
ln -s ../../.ai/rules/drupal-standards.md .cursor/rules/drupal-standards.mdc
# Cursor skills: symlink the whole skill directory
ln -s ../../.ai/skills/my-wrap-up-task .cursor/skills/my-wrap-up-task.claude/rules is a directory symlink to .ai/rules. So when Claude loads rules, it reads the canonical .ai/ versions. Same for skills.
Cursor is a little more work because it expects individual .mdc files in .cursor/rules/ rather than a directory symlink. So those are file-level symlinks. A bit more friction when adding a new rule, but manageable — and the CLAUDE.md documents the exact steps so I don't have to remember.
The Hooks Problem (Bonus Complexity)
There was one more layer: hooks. Both Cursor and Claude Code support hooks that run before shell commands — useful for things like preventing lando stop from running accidentally mid-session, or enforcing environment checks before deploys.
The logic is the same for both tools, but the wiring format is different:
- Claude uses
.claude/settings.jsonwith aPreToolUsehook pointing to.claude/hooks/lando-guard.sh - Cursor uses
.cursor/hooks.jsonwithbeforeShellExecution
The solution was lando-guard-core.sh in .ai/hooks/ — the shared logic — with thin wrappers in each tool's directory that call it. One place to update the guard logic, two places that wire it in.
The Final Structure
.ai/ ← Single source of truth
rules/ ← Shared rules (plain .md)
skills/ ← Shared skills (SKILL.md)
hooks/ ← Shared hook logic
.claude/
CLAUDE.md ← Project overview for Claude
settings.json ← PreToolUse hook wiring
hooks/lando-guard.sh ← Thin wrapper → .ai/hooks/
rules → ../.ai/rules ← Directory symlink
skills → ../.ai/skills ← Directory symlink
.cursor/
rules/*.mdc → .ai/rules/* ← Individual file symlinks
skills/* → .ai/skills/* ← Directory symlinks
hooks.json ← beforeShellExecution wiringAdding a new rule now means:
- Create
.ai/rules/my-rule.md ln -s ../../.ai/rules/my-rule.md .cursor/rules/my-rule.mdc- Done. Claude picks it up automatically through the directory symlink.
Adding a new skill:
- Create
.ai/skills/my-skill/SKILL.md ln -s ../../.ai/skills/my-skill .cursor/skills/my-skill- Done.
What I'd Do Differently
Honestly? I'd set up .ai/ from the start instead of going through the .cursorrules → .cursor/rules/ → .ai/ progression. The three-step journey I described happened because I was reacting to problems as they appeared rather than designing for multi-tool from day one.
But that's also kind of how Drupal development goes. You don't build the perfect abstraction until you've felt the pain of not having it. I needed to use Claude Code on real projects before I understood why the config had to be tool-agnostic.
The .cursorrules monolith was fine when there was only one AI tool reading it. The moment that assumption changed, the whole thing needed rethinking. Better to have the structure in place before that happens on your next project.
The .ai/ directory is now the first thing I set up when I start a new project. Config first, then code.
I hope it helps you somehow, happy coding!