Una fuente de verdad, tres harnesses que la leen
El repositorio corre igual en Claude Code, OpenCode y Codex (CLI y Desktop). Existe una sola copia de cada instrucción y de cada skill. Donde los harnesses difieren de verdad —formato de MCP, API de hook— cada uno conserva un adaptador fino, versionado y verificado. Nada más está duplicado.
CLAUDE.mdNingún nombre de archivo lo descubren los tres
Claude Code carga CLAUDE.md. OpenCode y Codex cargan
AGENTS.md. Mantener dos documentos completos y compararlos en CI no elimina
el drift: lo detecta tarde, después de que alguien ya trabajó con la copia vieja.
Por eso la relación se invirtió.
AGENTS.md es el único cuerpo de instrucciones y
CLAUDE.md quedó en once bytes. Ese @ es la sintaxis de import
documentada de Claude Code: trae el archivo al contexto de la sesión. Se eligió por
encima de un symlink justamente porque un checkout de Windows lo trata igual que uno de
macOS o Linux.
# CLAUDE.md — el archivo entero, sin recortar
@AGENTS.md
Escribir prosa operativa dentro de CLAUDE.md es drift estructural. La
skill sync-ai-context se detiene y lo reporta en vez de propagarlo, y
bun run agents:compat:check falla si el archivo deja de ser exactamente
el shim.
Qué consume cada harness, superficie por superficie
Cinco superficies por tres harnesses. Lo marcado como nativo lo descubre el harness por su cuenta; lo marcado como generado es salida de un script y no se edita a mano.
| Superficie | Claude Code | OpenCode | Codex CLI + Desktop |
|---|---|---|---|
| Instrucciones |
generadoCLAUDE.md
un import: @AGENTS.md
|
nativoAGENTS.md |
nativoAGENTS.md |
| Skills |
generado.claude/skills
symlink en POSIX, junction en Windows
|
nativo.agents/skills/ |
nativo.agents/skills/ |
| Comandos |
generado.claude/commands/*.md
10 wrappers de 7 líneas
|
generado.opencode/commands/*.md
10 wrappers de 7 líneas
|
ninguno invoca la skill directamente |
| Hook |
.claude/settings.json
UserPromptSubmit
|
.opencode/plugins/
plugin de proyecto
|
.codex/hooks.json
UserPromptSubmit + commandWindows
|
| MCP | .mcp.jsonJSON |
opencode.jsoncJSONC |
.codex/config.tomlTOML |
Codex Desktop no agrega convención
CLI y Desktop consumen exactamente la misma configuración del repositorio. Desktop agrega interfaz, no un segundo contrato ni un directorio extra.
Los adaptadores son delgados a propósito
Solo dos superficies tienen adaptador real: el hook y el MCP. Ahí los harnesses difieren en la API, no en la intención. Las otras tres son la misma fuente vista desde tres lugares.
Lo generado nunca se edita a mano ni se commitea
Hay un solo store de skills, .agents/skills/, versionado, con las 19 skills
adentro. Todo lo demás que un harness necesita ver son punteros hacia ahí. Si un puntero
se rompe, se regenera; nunca se parchea.
| Artefacto generado | Su fuente | Se repara con |
|---|---|---|
.claude/skills |
.agents/skills/symlink o junction
|
bun run agents:compat |
| 10 wrappers Claude + 10 OpenCode |
.agents/compatibility/command-aliases.json
|
bun run agents:compat |
CLAUDE.md |
AGENTS.mdun import de 11 bytes |
bun run agents:compat |
La reparación del alias tiene una salvaguarda: si .claude/skills es un
directorio real con contenido propio, se niega a borrarlo y nombra el conflicto. La
única excepción es la forma que escribe la CLI de skills —un directorio cuyas entradas
son todas punteros hacia .agents/skills— porque ahí no hay nada que
perder.
Los slash commands son transporte, no workflow
Antes cada comando llevaba el workflow entero escrito adentro.
adapt-framework solo tenía 590 líneas. Eso era instrucción duplicada que
ningún harness fuera de Claude podía descubrir, y que se desincronizaba de su skill en
cuanto alguien editaba una de las dos copias.
Los diez nombres sobreviven como alias. El wrapper solo selecciona skill y modo, y
reenvía $ARGUMENTS sin tocarlo. Este es un archivo completo, no un
fragmento:
# .claude/commands/business-data-map.md — las 7 líneas, enteras --- description: Generate or refresh the business data and flow map. argument-hint: [project-path] --- Invoke skill `project-context` in mode `data`. Forward `$ARGUMENTS` unchanged.
| Alias | Skill destino | Modo |
|---|---|---|
/adapt-framework |
adapt-framework |
adapt |
/break-down-tests |
test-automation |
explainsellado, solo lectura |
/business-data-map |
project-context |
data |
/business-feature-map |
project-context |
features |
/business-api-map |
project-context |
api |
/master-test-plan |
project-context |
test-plan |
/fix-traceability |
test-documentation |
repair-traceability
conserva el gate de aprobación
|
/jira-components |
jira-administration |
components |
/jira-instance-migration |
jira-administration |
instance-migration |
/sync-ai-memory |
sync-ai-context |
sync |
Codex no usa wrappers
No hay una tercera copia de los diez archivos. Codex invoca la skill directamente, que es exactamente lo que el wrapper hace por vos en los otros dos.
La verificación no es cosmética
agents:compat:check rechaza un alias cuya skill destino no exista o
cuyo modo no esté declarado. Y a un wrapper al que le crece un cuerpo lo falla con
el mensaje contains workflow prose.
Un emisor, tres adaptadores
El contrato de personalidad que se reinyecta en cada prompt vive una sola vez, en
.agents/hooks/personality-reinject.mjs. El archivo exporta la constante y
además la escribe a stdout cuando se lo ejecuta como script. Esas dos
formas de consumirlo son las que necesitan los tres harnesses.
El validador de contrato rechaza, con nombre y línea:
- Rutas absolutas personales dentro de cualquier comando de hook, que romperían en la máquina de cualquier otra persona.
-
Un emisor compartido que trate
CLAUDE.mdcomo el archivo canónico, que es exactamente la inversión que este release deshizo. -
Un plugin de OpenCode que reemplace
output.systemen vez de mutarlo, lo que descartaría el system prompt del harness. - La reaparición de los archivos de hook duplicados que se eliminaron.
El adaptador de Codex además carga commandWindows para PowerShell y
resuelve la raíz del repositorio con git rev-parse --show-toplevel, en vez
de asumir el directorio de trabajo.
Seis servidores, tres formatos, paridad semántica
Los seis servidores canónicos existen en las tres configuraciones. La comparación no puede ser textual: los archivos ni siquiera comparten lenguaje.
Si venís de la versión anterior
Un proyecto creado cuando las instrucciones vivían en CLAUDE.md y las
skills en .claude/skills/ recibe una migración preflight la primera vez que
corre bun run up — antes de que se sincronice un solo componente, porque
sincronizar primero sobre una estructura vieja es exactamente cómo se pierde trabajo.
Nada se borra
Todo se mueve o se respalda. Lo que no se puede mover a su lugar canónico queda en
.template/pre-agents-migration/, con su contenido intacto.
Es idempotente
Correrla dos veces no hace nada la segunda vez. Un proyecto ya migrado pasa de largo sin escribir.
Lo que todavía no está cerrado
Cuatro cosas que conviene saber antes de apoyarse en esto, dichas como límites y no como notas al pie.
-
El adaptador de OpenCode usa
experimental.chat.system.transform. Es una API oficial, pero marcada como experimental por el proyecto. Claude y Codex están sobre APIs de hook estables. Hay que re-verificar al actualizar OpenCode. - El junction de Windows está cubierto por tests unitarios de construcción de ruta y de comando. La verificación en una máquina Windows real sigue pendiente.
-
Codex necesita que el repositorio esté marcado como confiable. La configuración
de proyecto bajo
.codex/y sus hooks no cargan si no lo está.bun run setup:doctorreporta esa confianza por separado de la corrección de los archivos, porque es estado de runtime que no se puede verificar leyendo el disco. -
Lanzá siempre con
bun run claude,bun run opencodeobun run codex. Cada uno pasa pordotenv -o, que fuerza que.envgane sobre una variable heredada del proceso padre. Ejecutar el binario pelado se saltea eso y puede dejar un valor viejo tapando el del archivo.