Revisión adversarial de código desde Claude Code, con Grok como revisor.
La idea es vieja y buena: quien escribe el código no debería ser el único que lo revisa. Claude implementa, Grok busca el error. Dos familias de modelos con puntos ciegos distintos.
Lo que aporta este repo es la parte aburrida: que la señal de "está limpio" se pueda verificar en vez de creerse.
Hay dos piezas dando vueltas que resuelven mitades distintas.
ignaval/skills tiene la disciplina del
loop: gestión de convergencia, un registro de descartes para que el revisor no
re-derive las mismas carreras para siempre, dos niveles de esfuerzo. Está atada
al CLI de codex, o sea a una cuenta de OpenAI.
stdevMac/grok-in-claude tiene el
revisor: Grok expuesto a Claude Code como herramientas. No tiene esa
disciplina.
Falta el pegamento, y falta algo que ninguno de los dos tiene: que el revisor no pueda tocar el código que está revisando, comprobado y no prometido.
Corriendo Grok en modo headless, la barrera de escritura no es la que parece.
Medido sobre grok 0.2.103, pidiéndole primero que escriba un archivo y después
que revise un pagar.js con un bug de dinero plantado:
| flags | ¿escribió? | ¿revisó? |
|---|---|---|
--sandbox read-only |
SÍ | n/a |
--tools read_file,grep,list_dir + --sandbox read-only |
no | 0 hallazgos |
--tools read_file,grep,list_dir |
no | 4 hallazgos |
--permission-mode plan + --sandbox read-only |
no | 3 hallazgos |
| sin flags (control) | no | n/a |
Léase despacio: el único caso donde la escritura ocurrió es el flag que suena a
protección. --sandbox read-only le avisa al revisor que está confinado y con
eso desarma la aprobación interactiva, pero el confinamiento no aplica cuando el
binario es de Windows y escribe por el share SMB de WSL. Y usado junto al
allowlist arruina la revisión: el mismo prompt que encuentra cuatro problemas
devuelve cero.
La barrera que sí funciona es quitarle la herramienta, no pedirle que no la use:
--tools "read_file,grep,list_dir" es un allowlist, así que no le queda ninguna
forma de escribir ni de ejecutar comandos.
Y aun así no se le cree. bin/revisar.sh saca una huella del árbol antes y
después, y si cambió corta con código 70. Preguntarle al revisor no sirve: en la
misma medición respondió escribio: true en casos donde no había escrito nada.
Detalle completo y cómo reproducirlo: docs/barrera-de-escritura.md.
Uno solo, y no menciona a Grok por ningún lado:
entrada un archivo markdown con el prompt de revisión
salida un JSON que valida contra $GROKLAUDE_ESQUEMA, escrito en
$GROKLAUDE_RESPUESTA. Lista de hallazgos vacía = limpio.
Que el veredicto vaya a un archivo aparte y no por stdout no es capricho: el stream de una ronda son megabytes de llamadas a shell y razonamiento, y el veredicto son unos pocos KB. Quien orquesta lee el veredicto. Si tuviera que leer el stream, cada ronda le costaría el contexto entero.
Los códigos de salida distinguen lo que hay que distinguir:
| código | qué pasó |
|---|---|
0 |
hubo revisión (con hallazgos o sin ellos) |
3 |
corrió pero no dejó una respuesta válida: ronda fallida, no limpia |
64 |
error de argumentos |
69 |
no se encontró el backend |
70 |
el revisor modificó el árbol |
124 |
se pasó del tiempo |
La distinción entre 0 con lista vacía y 3 es la que sostiene todo. "No hubo
respuesta" y "no hay hallazgos" se parecen y significan lo contrario; confundirlas
hace converger en falso.
export GROKLAUDE_ESQUEMA=esquemas/hallazgos.json
export GROKLAUDE_RESPUESTA=/tmp/ronda1.json
bin/revisar.sh mi-prompt.md medium /ruta/al/repobin/revisar.sh <archivo-prompt> [esfuerzo] [repo ...]
[esfuerzo] high (default) | medium | low
[repo ...] el primero es el directorio de trabajo del revisor
| variable | para qué |
|---|---|
GROKLAUDE_BACKEND |
grok (default) o codex |
GROKLAUDE_ESQUEMA |
el JSON Schema que debe cumplir la respuesta |
GROKLAUDE_RESPUESTA |
dónde escribirla |
GROKLAUDE_TOOLS |
allowlist de herramientas (default: read_file,grep,list_dir) |
GROKLAUDE_VERIFICAR |
si (default) compara la huella del árbol |
GROKLAUDE_TURNOS |
tope de turnos (default según esfuerzo) |
GROKLAUDE_TIMEOUT |
segundos antes de cortar (default 1800) |
GROKLAUDE_GROK_BIN |
ruta al binario, si no está en el PATH |
Si el código vive en el filesystem nativo de Linux (que es donde tiene que vivir:
sobre /mnt/c pnpm va entre 10 y 50 veces más lento y los watchers no reciben
eventos de inotify) y el binario de Grok es un .exe de Windows, el revisor no
puede abrir /root/work/repo. Para él esa ruta no existe.
revisar.sh lo detecta solo y traduce a la ruta UNC que WSL expone por SMB:
/root/work/repo -> \\wsl$\Ubuntu\root\work\repo
/mnt/c/Users/x -> C:\Users\x
Traduce en vez de copiar el árbol a propósito. Una copia se desincroniza apenas el loop escribe un archivo, y revisar una copia vieja es peor que no revisar.
Si el binario es nativo de Linux, no traduce nada.
- Bash, Python 3,
find,sha256sum - Uno de los dos revisores:
- Grok: el CLI de grok.com en el
PATHo enGROKLAUDE_GROK_BIN, con sesión iniciada (grok login). Probado con 0.2.103. - codex: el CLI de OpenAI, con
codex login.
- Grok: el CLI de grok.com en el
No hace falta que los dos estén instalados. No hace falta cuenta de OpenAI si vas por Grok.
Anda el adaptador de revisión, que es la pieza de abajo. La skill que orquesta el loop con gestión de convergencia todavía no está: es lo que sigue.
Lo que está probado hoy, de punta a punta:
revisión real sobre código con un bug plantado 4 hallazgos, la gravedad bien puesta
intento de escritura del revisor bloqueado, árbol intacto
brecha simulada de la barrera detectada, código 70
respuesta inválida del backend ronda fallida, NO "limpio"
Dos repos hicieron el trabajo difícil primero:
ignaval/skills, de donde viene la disciplina del loop: la gestión de convergencia, el registro de descartes, y la idea de que el veredicto vaya a un archivo aparte para no gastarle el contexto a quien orquesta.stdevMac/grok-in-claude(Apache-2.0), de donde viene cómo se invoca el CLI de Grok en headless con salida estructurada.
Ver NOTICE.
Apache-2.0. Ver LICENSE.