.agents: reglas y skills centralizados para cualquier editor de IA
Cuando cada dev usa un editor de IA distinto, las rules se desincronizan entre .cursor, .agent y .claude. Centralizamos todo en .agents/ con symlinks y escondimos el cableado en VS Code para que el repo no distraiga.
Cómo llegamos a esto
En el equipo estábamos todos metidos con asistentes de código, pero cada uno con el suyo. Yo llevaba un buen rato en Cursor; un compañero usaba Antigravity; otro probaba Pi. Las herramientas andaban bien — el lío empezó cuando quisimos compartir reglas.
Cursor las busca en .cursor/rules. Antigravity, en .agent (hoy ya prefiere .agents, pero en ese momento no lo sabíamos). Pi tiene lo suyo en .pi. Claude Code mira .claude. Skills, curiosamente, ya se portaban mejor: los instalas una vez y el editor resuelve. Las rules no: eran carpetas paralelas que se desincronizaban en el primer PR.
Alguien tocaba la regla de TypeScript en .cursor, otro la actualizaba en .agent, y aparecía la misma duda: ¿cuál manda? No era pelea de herramientas. Simplemente no habíamos elegido un solo lugar.
En algún momento miré cómo funcionaban los Skills — el linkeo, la instalación compartida — y pensé: si eso ya anda, ¿por qué no hacer lo mismo con las rules? Terminamos centralizando todo en .agents.
Qué hicimos (en pocas palabras)
.agents/ es la carpeta donde vive de verdad la configuración del agente: rules, skills, y un AGENTS.md con el scope del repo. Los editores no duplican nada; apuntan ahí con symlinks que un script crea solo al hacer pnpm install.
.agents/ ← aquí editas, aquí commiteas
├── AGENTS.md
├── rules/
├── skills/
└── scripts/init.js
.cursor/ ──► .agents/ (Cursor todavía pide su carpeta para rules)
.claude/ ──► .agents/ (Claude Code igual)
.agent/ ──► .agents/ (legacy de Antigravity; hoy AGY lee .agents directo)
.pi/ ──► .agents/ (Pi ya descubre skills en .agents; rules no usa carpeta)La regla de oro del equipo: si quieres cambiar una rule o un skill, lo haces en `.agents/`. Nunca en .cursor/ ni en .claude/. Esas carpetas existen para que cada tool las encuentre, no para escribir ahí.
Cómo quedó armado
Un script chico que corre solo
En .agents/scripts/init.js hay un script de unas cincuenta líneas, sin dependencias raras. Lo enganchamos a prepare en el package.json, junto con el init de git hooks:
"setup:ai:init": "node .agents/scripts/init.js",
"prepare": "pnpm run setup:githooks:init && pnpm run setup:ai:init"Clonas el repo, corres pnpm install, y listo: symlinks creados. No hace falta acordarse de un paso extra que nadie va a leer en el README.
El script recorre .cursor, .agent, .pi y .claude, y en cada uno deja rules y skills apuntando a .agents. Si mañana sumamos otro editor, agregamos el nombre al array y listo.
Solo versionamos .agents
Las carpetas de editor están en .gitignore. En git viaja la configuración real; los symlinks se generan en cada máquina, parecido a cómo core.hooksPath apunta a .githooks sin versionar el enlace de Git.
Eso nos salvó de commits del estilo “arreglé la rule en Cursor pero me olvidé de Antigravity”.
Ocultamos el ruido en VS Code
¿Qué hace esto en VS Code? Cursor y VS Code leen la configuración del workspace en .vscode/settings.json. La clave files.exclude solo oculta carpetas del explorador de archivos — no las borra del disco, no afecta a Git ni a los editores de IA que las necesitan. Es ergonomía: menos carpetas que parecen duplicadas (.cursor, .claude, .githooks) y una sola fuente visible para editar (.agents/, src/). Si necesitas abrir algo oculto: Ctrl/Cmd+P, escribe la ruta (p. ej. .githooks/pre-commit), o quita temporalmente la entrada en files.exclude.
Aunque los symlinks estén bien, en el explorador de archivos se veían cuatro carpetas con el mismo contenido adentro. Para alguien nuevo parecía que había cuatro copias de las rules. En pair programming, en code review, siempre la misma duda: ¿dónde edito?
En .vscode/settings.json escondimos .cursor, .agent, .pi, .claude y también .githooks y .vscode mismo. Lo que queda visible en el árbol es .agents/. Las carpetas ocultas siguen en disco — los tools las necesitan — pero dejan de competir por atención.
Es la misma idea que con los hooks: la fuente de verdad se ve; el cableado, no.
¿Sigue teniendo sentido hoy?
Lo investigamos después de armarlo, y la respuesta corta es sí — de hecho el ecosistema nos alcanzó un poco.
Antigravity documenta .agents/rules como ubicación oficial (.agent quedó en compatibilidad). Pi ya descubre skills en .agents/skills/ sin symlink. Cursor también lee skills desde .agents/, aunque las rules todavía las pide en .cursor/rules/ — ahí el symlink sigue siendo necesario. Claude Code, por ahora, igual: necesita .claude/.
O sea: la carpeta canónica ya no es un invento nuestro. Varios tools convergen en .agents. Lo que hicimos con symlinks + init automático sigue siendo el puente para los que todavía exigen su propia carpeta.
Hay matices que aprendimos en el camino:
- Pi no tiene carpeta `rules`. Usa AGENTS.md en el root del proyecto. Nosotros lo tenemos dentro de .agents/AGENTS.md; conviene un symlink AGENTS.md → .agents/AGENTS.md en la raíz para que Pi y Cursor lo encuentren sin drama.
- El frontmatter no es idéntico en todos lados. Cursor usa .mdc con globs y alwaysApply; Antigravity tiene sus propios modos de activación; Claude Code usa paths. El contenido se comparte, pero el scoping condicional no es 100% portable — hay que saberlo.
- Cuidado al editar por el symlink. Algunos editores, al guardar, reemplazan el link con un archivo real. Si editas desde .cursor/rules/ puedes romper el enlace sin darte cuenta. Por eso insistimos: editar siempre en .agents/.
Pasos para implementarlo en tu repo
Antes de empezar: mismo repo donde ya configuraste Git hooks (o al menos tienes package.json con prepare). Necesitas Node.js 18+. Las carpetas .agents/rules y .agents/skills deben existir — aunque estén vacías — o el init fallará.
1. Crear .agents/ como fuente de verdad
En la raíz del repo:
mkdir -p .agents/rules .agents/skills .agents/scripts
touch .agents/AGENTS.mdEstructura mínima:
.agents/
├── AGENTS.md # scope del repo (puede empezar con un párrafo)
├── rules/ # vacío al inicio, o mueve aquí tus .cursor/rules
├── skills/ # vacío al inicio (cada skill = carpeta con SKILL.md)
└── scripts/
└── init.js # ← copia el archivo de abajoEjemplo mínimo de .agents/AGENTS.md:
# Mi proyecto
Stack: Node.js, tu framework.
Rules en `.agents/rules/`. Skills en `.agents/skills/`.
Edita solo aquí — no en `.cursor/` ni `.claude/`.Si hoy tienes rules en .cursor/rules, muévelas a .agents/rules/ una sola vez. Borra duplicados viejos después del PR de migración.
2. Crear el init — copia este archivo completo
Crea .agents/scripts/init.js y pega todo este contenido:
#!/usr/bin/env node
'use strict';
/**
* .agents init — symlinks rules y skills hacia carpetas de editores de IA.
* Requiere: .agents/rules y .agents/skills (pueden estar vacías).
*/
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)');Qué hace (en simple): crea carpetas .cursor, .claude, etc., y dentro deja rules y skills como enlaces a .agents/. Cada editor cree que lee su carpeta; en realidad todos apuntan al mismo sitio.
Para agregar un editor nuevo más adelante, suma su nombre al array EDITORS.
3. Conectar el init a package.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"
}
}Si aún no tienes Git hooks, puedes usar solo:
"setup:ai:init": "node .agents/scripts/init.js",
"prepare": "pnpm run setup:ai:init"Prueba manual:
pnpm run setup:ai:init
ls -la .cursorDeberías ver líneas como rules -> ../.agents/rules y skills -> ../.agents/skills. Si aparecen, el init funcionó.
4. Gitignore de las carpetas de editor
En .gitignore:
.cursor/
.agent/
.pi/
.claude/Solo .agents/ se versiona. Los symlinks son artefactos locales — evitas commits del estilo “actualicé .cursor pero olvidé .agent”.
5. Ocultar el ruido en .vscode/settings.json
"files.exclude": {
".cursor": true,
".agent": true,
".pi": true,
".claude": true,
".githooks": true,
".vscode": true
}Lo visible en el árbol: .agents/ (y tu código). Regla de equipo: editar solo ahí, nunca en las carpetas linkeadas — algunos editores rompen el symlink al guardar por encima del link.
6. AGENTS.md accesible para Pi y Cursor
Pi y Cursor buscan AGENTS.md en el root del proyecto. Si lo tienes dentro de .agents/AGENTS.md, conviene un symlink en la raíz:
ln -s .agents/AGENTS.md AGENTS.md(O moverlo al root y referenciarlo desde la doc del equipo.)
7. Verificar en cada editor del equipo
pnpm run setup:ai:init
ls -la .cursor # rules → ../.agents/rules
ls -la .claude # idemEdita una línea en .agents/rules/..., abre el editor y confirma que la ve. Si un compañero usa Antigravity nuevo, puede leer .agents/ directo — el symlink en .agent/ queda por compatibilidad.
8. Acordar proceso
- Cambios en .agents/ se revisan como código — una mala rule escala a todos los editores.
- Commits descriptivos: feat(agents/rules/ui): …
- Documentar en AGENTS.md que .agents/ es el único lugar donde se escribe.
Qué nos falta pulir
Lo básico ya corre: un solo lugar, onboarding automático, extensible a editores nuevos. Lo que todavía estamos definiendo es más de proceso que de código.
Las rules ahora escalan a todo el equipo de golpe — una mala rule es un PR que hay que revisar con la misma seriedad que código de producción. Los commits que tocan .agents conviene que se entiendan en el historial (feat(agents/rules/ui): … ayuda). Y si alguien usa un editor que no está en la lista, hay que mirar dónde busca su config y sumarlo al init.
En Windows los junctions funcionan, pero conviene que alguien lo pruebe cuando toquemos el script. Y si usas agentes en la nube que no corren pnpm install, hay que verificar que lean .agents directo o que el entorno remoto también ejecute el init.
Estamos evaluando un template del script para otros repos del equipo y un check liviano en CI que confirme que .agents/rules existe y está en forma.
Referencia rápida
Fuente de verdad: .agents/
Activación: pnpm install → prepare → setup:ai:init
Init: .agents/scripts/init.js
Symlinks necesarios: sobre todo .cursor/rules y .claude/ (rules + skills)
Ocultar en VS Code: .vscode/settings.json → files.exclude
Regla de equipo: editar solo en .agents/, nunca en las carpetas linkeadaspnpm run setup:ai:init
ls -la .cursor # rules → ../.agents/rules