El equipo de Aviator pasó a integrar cuatro veces más pull requests con los mismos nueve ingenieros, según contó en The New Stack. La mediana del tiempo hasta la integración bajó de 2,1 días a 3,4 horas y los reverts cayeron un 40%. El cambio no fue dejar la revisión en manos de una IA: una persona siguió aprobando cada PR. Cambió qué examinaba.
La propuesta es revisar intención, criterios de aceptación, invariantes, decisiones y pruebas, en vez de intentar leer cada línea del diff. Vamos a implementar una versión pequeña y ejecutable en Python: un informe que comprueba que cada criterio tiene evidencia, que las rutas indicadas existen y que las decisiones pendientes quedan visibles. No decide si el cambio está bien; prepara una revisión humana más enfocada.
El problema: un diff no explica el propósito
En el caso descrito por Aviator, los PR semanales pasaron de unos 100 a más de 400, y el PR mediano generado por IA llegó a 640 líneas. La empresa también probó un revisor automático, un plazo de cuatro horas y una habilidad de autorrevisión. El bot encontró un problema real entre muchos comentarios menores; el plazo aceleró las primeras revisiones, pero vació las aprobaciones; y la autorrevisión seguía mirando el diff después de escribir el código.
El flujo que vamos a construir recoge criterios y decisiones en un archivo, comprueba cada criterio frente a evidencia local y genera un resultado legible. En el ejemplo, la evidencia será una ruta a un archivo y una cita de línea. En un proyecto real, esa prueba podría ser también un resultado de API, una captura o un registro de ejecución: Aviator menciona esas formas de evidencia en su descripción del proceso.
flowchart TD
A["Criterios y decisiones"] --> B["Verificador local"]
C["Evidencias del cambio"] --> B
D["Invariantes del equipo"] --> B
B --> E["Informe para la persona"]
E --> F["Resolver decisiones pendientes"]Preparar criterios y evidencias
Usaremos solo la biblioteca estándar de Python. Guarda el siguiente archivo como review.py y ejecútalo con python review.py. El programa crea un caso de demostración en un directorio temporal, así que no depende de archivos preexistentes. Cada criterio tiene una descripción y una prueba concreta; una decisión registra su estado y la persona que la resolvió.
El ejemplo deliberadamente mantiene los datos junto al verificador para que se pueda ejecutar de inmediato. En un repositorio, conviene separar la configuración de la herramienta y guardar los criterios en un artefacto asociado al PR.
from pathlib import Path
from tempfile import TemporaryDirectory
criteria = [
{
"id": "AC-1",
"description": "La función de cálculo está presente",
"evidence": "src/pricing.py",
"line": 1,
},
{
"id": "AC-2",
"description": "La prueba de cálculo está presente",
"evidence": "tests/test_pricing.py",
"line": 1,
},
]
def main():
with TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
(root / "src").mkdir()
(root / "tests").mkdir()
(root / "src/pricing.py").write_text("def total(price, tax):\n return price + tax\n")
(root / "tests/test_pricing.py").write_text("def test_total():\n assert 2 + 1 == 3\n")
for item in criteria:
path = root / item["evidence"]
print(f"{item['id']}: {'OK' if path.is_file() else 'FALTA'} — {item['description']}")
if __name__ == "__main__":
main()
Comprobar cada criterio contra su evidencia
Que exista un archivo no demuestra que el criterio esté satisfecho. El siguiente paso comprueba también que la línea citada existe. Sustituye el contenido anterior por este programa completo: conserva los datos de muestra, pero ahora produce un resultado estructurado. El estado solo significa que la evidencia indicada está disponible; no equivale a una aprobación semántica.
Esta distinción es importante. En el experimento de Aviator con una función de configuración, por ejemplo, la verificación contemplaba guardar un ajuste y comprobar el registro de auditoría, y luego guardar sin cambios para confirmar que no aparecía otra entrada. La segunda comprobación aporta una evidencia distinta: no basta con probar el caso favorable.
from pathlib import Path
from tempfile import TemporaryDirectory
criteria = [
{"id": "AC-1", "description": "Función presente", "evidence": "src/pricing.py", "line": 1},
{"id": "AC-2", "description": "Prueba presente", "evidence": "tests/test_pricing.py", "line": 1},
{"id": "AC-3", "description": "Ruta inexistente", "evidence": "docs/pricing.md", "line": 1},
]
def check(root, item):
path = root / item["evidence"]
if not path.is_file():
return {"id": item["id"], "status": "MISSING_FILE", "evidence": item["evidence"]}
lines = path.read_text(encoding="utf-8").splitlines()
line = item["line"]
if line < 1 or line > len(lines):
return {"id": item["id"], "status": "MISSING_LINE", "evidence": item["evidence"]}
return {"id": item["id"], "status": "EVIDENCE_PRESENT", "evidence": item["evidence"], "line": line}
def main():
with TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
(root / "src").mkdir()
(root / "tests").mkdir()
(root / "src/pricing.py").write_text("def total(price, tax):\n return price + tax\n", encoding="utf-8")
(root / "tests/test_pricing.py").write_text("def test_total():\n assert 2 + 1 == 3\n", encoding="utf-8")
for item in criteria:
print(check(root, item))
if __name__ == "__main__":
main()
Hacer visibles invariantes y decisiones
Las reglas repetidas en revisiones pueden convertirse en invariantes. The New Stack cuenta que Aviator analizó 11.400 comentarios, obtuvo 212 reglas candidatas y aprobó 64 invariantes. Algunas se ejecutaban como reglas deterministas; otras seguían necesitando comprobaciones adicionales. El ejemplo siguiente añade una invariante simple: todo criterio debe incluir evidencia. Añade también decisiones sin resolver al informe, en lugar de ocultarlas entre comentarios.
Guarda el código completo como review.py. Para este caso didáctico, la invariante se evalúa sobre los datos, no sobre el código del proyecto. En un repositorio real, las reglas de estilo o patrones pueden aplicarse con herramientas adecuadas al lenguaje; una decisión de diseño, en cambio, debe seguir a la vista de una persona.
from pathlib import Path
from tempfile import TemporaryDirectory
criteria = [
{"id": "AC-1", "description": "Función presente", "evidence": "src/pricing.py", "line": 1},
{"id": "AC-2", "description": "Prueba presente", "evidence": "tests/test_pricing.py", "line": 1},
{"id": "AC-3", "description": "Ruta inexistente", "evidence": "docs/pricing.md", "line": 1},
]
decisions = [
{"question": "¿Debe redondearse el total?", "status": "pending", "owner": None},
{"question": "¿Se mantiene la firma actual?", "status": "settled", "owner": "revisor"},
]
def check(root, item):
if not item.get("evidence"):
return {"id": item["id"], "status": "NO_EVIDENCE"}
path = root / item["evidence"]
if not path.is_file():
return {"id": item["id"], "status": "MISSING_FILE", "evidence": item["evidence"]}
lines = path.read_text(encoding="utf-8").splitlines()
line = item["line"]
if line < 1 or line > len(lines):
return {"id": item["id"], "status": "MISSING_LINE", "evidence": item["evidence"]}
return {"id": item["id"], "status": "EVIDENCE_PRESENT", "evidence": item["evidence"], "line": line}
def main():
with TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
(root / "src").mkdir()
(root / "tests").mkdir()
(root / "src/pricing.py").write_text("def total(price, tax):\n return price + tax\n", encoding="utf-8")
(root / "tests/test_pricing.py").write_text("def test_total():\n assert 2 + 1 == 3\n", encoding="utf-8")
print("Criterios:")
for item in criteria:
print(check(root, item))
print("Decisiones pendientes:")
for decision in decisions:
if decision["status"] == "pending":
print(f"- {decision['question']}")
if __name__ == "__main__":
main()
Convertir el resultado en una puerta de revisión
El último ajuste hace que el programa termine con código de error si falta evidencia o hay una decisión pendiente. Así puede ejecutarse desde una comprobación local o integrarse más adelante en CI. La persona revisora sigue teniendo que comprobar si la prueba responde al criterio y resolver las decisiones de producto o diseño.
En la experiencia que Aviator describió, los revisores examinaban lo ya resuelto, atendían lo que los agentes no podían decidir y reabrían aquello que juzgarían de otra manera. La automatización ordena el trabajo; no sustituye ese juicio. El propio equipo situó el coste de transición en trabajo duplicado durante el primer mes, antes de que las invariantes empezaran a reducir la carga en el segundo.
from pathlib import Path
from tempfile import TemporaryDirectory
criteria = [
{"id": "AC-1", "description": "Función presente", "evidence": "src/pricing.py", "line": 1},
{"id": "AC-2", "description": "Prueba presente", "evidence": "tests/test_pricing.py", "line": 1},
]
decisions = [
{"question": "¿Debe redondearse el total?", "status": "pending", "owner": None},
]
def check(root, item):
if not item.get("evidence"):
return False, f"{item['id']}: falta la ruta de evidencia"
path = root / item["evidence"]
if not path.is_file():
return False, f"{item['id']}: no existe {item['evidence']}"
lines = path.read_text(encoding="utf-8").splitlines()
line = item["line"]
if line < 1 or line > len(lines):
return False, f"{item['id']}: no existe la línea {line}"
return True, f"{item['id']}: evidencia disponible en {item['evidence']}:{line}"
def main():
with TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
(root / "src").mkdir()
(root / "tests").mkdir()
(root / "src/pricing.py").write_text("def total(price, tax):\n return price + tax\n", encoding="utf-8")
(root / "tests/test_pricing.py").write_text("def test_total():\n assert 2 + 1 == 3\n", encoding="utf-8")
failed = False
for item in criteria:
ok, message = check(root, item)
print(message)
failed |= not ok
for decision in decisions:
if decision["status"] != "settled":
print(f"PENDIENTE: {decision['question']}")
failed = True
raise SystemExit(1 if failed else 0)
if __name__ == "__main__":
main()
Con la decisión de muestra sin resolver, la ejecución termina con error; cambia su estado a settled y asigna una persona para que la puerta pase. Ese cambio no certifica el código: deja claro qué falta para que alguien pueda aprobarlo con contexto.
Fuentes: The New Stack, Aviator, Aviator Blog, Aviator Blog