.agents: Centralized Rules and Skills for Any AI Editor

When everyone on the team uses a different AI editor, rules drift between .cursor, .agent, and .claude. We centralized everything in .agents/ with symlinks and tucked the wiring into VS Code so the repo stays focused.

How we got here

We were all using AI coding assistants — just not the same ones. I had been on Cursor for a while; a teammate used Antigravity; another was trying Pi. Each tool worked fine. The friction showed up when we tried to share rules.

Cursor looks in .cursor/rules. Antigravity used .agent (it now prefers .agents, which we didn't know at the time). Pi has .pi. Claude Code uses .claude. Skills were already easier to share; rules were parallel folders that drifted on the first PR.

Someone would edit the TypeScript rule in .cursor, someone else in .agent, and the same question kept coming up: which one is canonical? We hadn't picked a single place.

We looked at how Skills handled linking and asked: why not do the same for rules? We ended up centralizing everything in .agents/.

What we did

.agents/ is where agent config actually lives — rules, skills, and AGENTS.md for project scope. Editors don't duplicate; they symlink there via a script that runs on pnpm install.

Team rule: edit in `.agents/` only. Never in .cursor/ or .claude/. Those folders exist so tools can find the files, not so humans write there.

Setup in brief

A ~50-line init script hooks into prepare. Editor folders are gitignored; only .agents/ is versioned. VS Code hides the symlink folders so the file tree shows one obvious place to edit.

Does it still make sense?

Yes — the ecosystem moved toward us. Antigravity documents .agents/rules as official. Pi and Cursor already discover skills in .agents/skills/. Symlinks are still needed for Cursor rules and Claude Code.

Caveats: Pi uses root AGENTS.md, not a rules folder. Frontmatter differs across tools. Editing through a symlink can break the link — always edit the source in .agents/.

Steps to implement in your repo

Before you start: Node.js 18+, a root package.json, and both .agents/rules and .agents/skills folders (can be empty) or init exits with an error.

1. Create .agents/ as source of truth

bash
mkdir -p .agents/rules .agents/skills .agents/scripts
touch .agents/AGENTS.md
text
.agents/
├── AGENTS.md
├── rules/
├── skills/
└── scripts/init.js   # ← copy the file below

Minimal .agents/AGENTS.md:

markdown
# My project

Stack and scope here.
Edit rules only in `.agents/rules/`, skills in `.agents/skills/`.
Do not edit `.cursor/` or `.claude/` directly.

Move existing rules from .cursor/rules into .agents/rules/ once.

2. Create the init — copy this entire file

Create .agents/scripts/init.js:

javascript
#!/usr/bin/env node
'use strict';

/**
 * .agents init — symlinks rules and skills into AI editor folders.
 * Requires: .agents/rules and .agents/skills (can be empty).
 */

const fs = require('fs');
const path = require('path');

const root = path.resolve(__dirname, '../..');
const EDITORS = ['.cursor', '.agent', '.pi', '.claude'];
const agentsRules = path.join(root, '.agents', 'rules');
const agentsSkills = path.join(root, '.agents', 'skills');

function linkDir(linkPath, targetPath) {
  if (process.platform === 'win32') {
    fs.symlinkSync(targetPath, linkPath, 'junction');
    return;
  }

  const relativeTarget = path.relative(path.dirname(linkPath), targetPath);
  fs.symlinkSync(relativeTarget, linkPath, 'dir');
}

function setupEditor(editorName) {
  const editorPath = path.join(root, editorName);

  fs.mkdirSync(editorPath, { recursive: true });

  for (const [name, targetPath] of [
    ['rules', agentsRules],
    ['skills', agentsSkills],
  ]) {
    const linkPath = path.join(editorPath, name);
    fs.rmSync(linkPath, { recursive: true, force: true });
    linkDir(linkPath, targetPath);
  }
}

if (!fs.existsSync(agentsRules) || !fs.existsSync(agentsSkills)) {
  console.error('⚠️  .agents/rules or .agents/skills not found');
  console.error('   Run: mkdir -p .agents/rules .agents/skills');
  process.exit(1);
}

console.log('🔧 Initializing AI editor symlinks...');

for (const editor of EDITORS) {
  setupEditor(editor);
  console.log(`${editor}/rules → .agents/rules`);
  console.log(`${editor}/skills → .agents/skills`);
}

console.log('');
console.log('✅ AI editor symlinks ready (.cursor, .agent, .pi, .claude)');

Add new editors by extending the EDITORS array.

3. Wire init into package.json

json
{
  "scripts": {
    "setup:ai:init": "node .agents/scripts/init.js",
    "setup:githooks:init": "node .githooks/scripts/init.js",
    "prepare": "pnpm run setup:githooks:init && pnpm run setup:ai:init"
  }
}

Or without Git hooks:

json
"setup:ai:init": "node .agents/scripts/init.js",
"prepare": "pnpm run setup:ai:init"

Manual test:

bash
pnpm run setup:ai:init
ls -la .cursor

You should see rules -> ../.agents/rules and skills -> ../.agents/skills.

4. Gitignore editor symlink folders

Only .agents/ is versioned.

5. Hide symlink folders in .vscode/settings.json (files.exclude)

What does this do in VS Code? Cursor and VS Code read workspace settings from .vscode/settings.json. The files.exclude key only hides folders in the file explorer — it does not delete them from disk, and it does not affect Git or AI editors that still need those paths. It is ergonomics: fewer folders that look like duplicates (.cursor, .claude, .githooks) and one obvious place to edit (.agents/, src/). To open something hidden: Ctrl/Cmd+P, type the path (e.g. .githooks/pre-commit), or temporarily remove the entry from files.exclude.

Show .agents/ in the tree; hide .cursor, .claude, .agent, .pi, .githooks.

6. Symlink AGENTS.md to repo root if it lives under .agents/ (Pi and Cursor look there).

7. Verify: pnpm run setup:ai:init, check symlinks, edit a rule in .agents/, confirm editors see it.

8. Team process: review rule changes like code; edit only in .agents/.

Quick reference

text
Source of truth:  .agents/
Activation:       pnpm install → prepare → setup:ai:init
Symlinks:         mainly .cursor/rules and .claude/
Team rule:        edit only in .agents/

More notes on AI Engineering.