ponytail: menos código, ¿mejor código?
Agent Report
- Herramienta
- ponytail (76,5k ★)
- Tipo
- Crítica
- Contexto
- full-stack-fastapi-template (mediano) + Django (grande, 522k LOC) — 4 tareas A/B, modelo fuerte
- Coste
- 8 sesiones de agente (baseline vs. ruleset); LOC medido con git diff
ponytail lleva días en tendencia de GitHub: 76,5k estrellas, un
eslogan perfecto («dice nada, escribe una línea, funciona») y una
promesa concreta — que tu agente de IA escriba ~54% menos código
(hasta 94%), ~20% más barato, 100% seguro. Es un plugin que inyecta
un ruleset —una «escalera de pereza»— en 16+ agentes (Claude Code, Cursor,
Codex…): antes de escribir código, el agente para en el primer peldaño que
aguante — ¿hace falta? ¿ya existe en el repo? ¿lo hace la stdlib? ¿una
feature nativa? ¿una dependencia ya instalada? ¿cabe en una línea? — y solo
entonces escribe el mínimo.
Lo raro: su benchmark es honesto
Casi todo proyecto viral infla sus números. ponytail hace lo contrario. Su benchmark reconoce que la cifra original «80-94% menos código» estaba inflada por un baseline charlatán (lo señaló Colin Eberhardt en #126), encontró y publicó un bug de contaminación en su propia medición (un hook que activaba ponytail también en el baseline), rehízo el test para poder refutarse, y corrigió la cifra a un -54% agéntico defendible, mostrando dónde no gana. Es lo contrario de la norma. No hay «gotcha» que hacer aquí.
Pero ese benchmark tiene límites que ellos mismos listan: un solo
modelo (Haiku 4.5, pequeño, que sobre-construye más y necesita más
guía), un repo plantilla mediano
(full-stack-fastapi-template) y features greenfield
donde el truco estrella —«date picker de 404 líneas → <input type="date">»—
domina la media. Su propia tabla ya muestra que el CRUD de backend converge a
~0%. Ahí es donde entra este estudio.
Lo que probé que ellos no
Sus límites son mi test: un modelo fuerte (no Haiku), un repo grande y maduro de verdad (Django, 522k líneas de Python, 7.072 archivos) y ejes más difíciles que añadir un widget — reusar un helper que ya existe en un codebase enorme, escribir una utilidad nueva, y una tarea «irreducible». Trazado largo, no dos ejemplos.
Método (el suyo, extendido): A/B del mismo agente
con el ruleset de ponytail inyectado vs. sin él (baseline =
el agente haciendo el trabajo bien, no un modelo charlatán — la corrección
justa que ellos mismos hicieron). Cuatro tickets reales sobre repos reales,
cada brazo en su propia copia del repo, LOC medido como líneas añadidas del
git diff — su misma métrica.
Tarea 1 — el color picker (mediano, sobre-construcción)
Ticket: «añade un selector de color al formulario de items». Es su tipo de tarea estrella. Resultado: baseline 168 líneas, ponytail 41 — un -76%. Pero el detalle importa: ninguno instaló una librería. Los dos fueron directos al input nativo del navegador:
<FormField
control={form.control}
name="color"
render={({ field }) => (
<FormItem>
<FormLabel>Color</FormLabel>
<FormControl>
<Input type="color" className="h-9 w-16 p-1" {...field} />
</FormControl>
</FormItem>
)}
/>
En un modelo fuerte, la trampa «instala flatpickr y monta un componente
wrapper» —el origen del -94% en Haiku— casi no se dispara:
el baseline también usó <input type="color">. Entonces,
¿de dónde salió el -76%? De scope. El baseline llevó el
color de punta a punta: modelo de backend, migración Alembic, columna en la
tabla, validación del hex, campo de texto además del swatch. ponytail hizo
solo el formulario frontend — y el color nunca se guarda en la base
de datos (su propio informe lo admite: «backend fuera del scope del
ticket»). El ticket era ambiguo; ponytail eligió la lectura más estrecha.
Más limpio, sí — pero el recorte dejó un hueco real.
Tarea 2 — reusar en 522k líneas (Django, la claim difícil)
El peldaño 2 («¿ya existe en el codebase? reúsalo») es fácil de escribir en
un ruleset y difícil de cumplir: exige que el agente encuentre el
helper en un repo enorme. Ticket: «una función title_to_slug que
convierta un título en slug». Django tiene django.utils.text.slugify
enterrado entre 522k líneas. ¿Lo encuentra?
Sí — y funciona. ponytail lo localizó y escribió un wrapper de una línea:
from django.utils.text import slugify
def title_to_slug(title):
"""
>>> title_to_slug("Hello, World!")
'hello-world'
"""
# ponytail: django.utils.text.slugify already does the work; just wrap it.
return slugify(title)
baseline 18 líneas, ponytail 10 (-44%). Pero aquí está el
matiz: el baseline también reusó slugify. La
decisión importante —no reinventar el slug con un regex— la tomaron los dos.
El recorte de ponytail fue ceremonia (docstring de módulo +
bloque __main__ vs. one-liner + doctest), no una decisión más
lista. Un agente competente ya sube al peldaño 2 sin que se lo digan.
Tarea 3 — utilidad nueva en el repo grande
Ticket: una función retry con backoff exponencial. No hay retry
público en Django ni en la stdlib, así que los dos la escriben desde cero —
terreno fértil para sobre-construir (¿decorador configurable? ¿jitter?
¿clase de política?). Resultado: baseline 43, ponytail 32
(-26%). Casi convergen. Ninguno
sobre-construyó: ambos escribieron una función escueta con time.sleep
de la stdlib y dejaron un check ejecutable. La diferencia real:
def retry(fn, attempts=3, base_delay=0.1):
if attempts < 1:
raise ValueError("attempts must be at least 1.") # baseline lo puso
...
El baseline añadió una validación de entrada (attempts < 1)
que ponytail omitió. Irónico: el propio ruleset dice «no
perezoso con la validación en fronteras de confianza». Aquí, el recorte de
ponytail fue en parte ese guard.
Tarea 4 — el endpoint «trivial»
Ticket: «un endpoint que devuelva cuántos items tiene el usuario». Esperaba
convergencia (es CRUD). Fue lo contrario: baseline 71 líneas,
ponytail 12 — un -83%. ponytail escribió un
endpoint limpio, reusó la query de conteo que ya existía y hasta ordenó bien
la ruta (/count antes de /{id}, un detalle
real de routing). Pero:
Endpoint + modelo de respuesta tipado (OwnedItemsCount) + 3 tests: cuenta 0→3, no cuenta los de otros, exige auth.
Endpoint que devuelve un dict pelado. Sin modelo tipado, sin ningún test.
ponytail devolvió un dict sin tipar —rompiendo la convención de
respuestas tipadas que usa todo el resto del repo (su propio peldaño
2)— y no dejó ni un test, aunque su regla dice «lógica no
trivial deja UN check ejecutable». Juzgó el endpoint trivial. El baseline
escribió tres tests que verifican cosas reales (que no cuenta los items de
otros usuarios, que exige autenticación). ¿Sobre-testeó el baseline, o
sub-testeó ponytail? Personas razonables discrepan — pero el recorte de -83%
no fue bloat, fue cobertura y tipos.
El patrón
Media de los cuatro recortes: ~57% — casi clavado a su -54%, incluso en modelo fuerte y repo grande. La cifra de LOC se reproduce. Lo que cambia es de dónde sale el recorte:
No es un veredicto en contra. ponytail nunca escribió más
(su claim central se sostiene), reusó bien en todos los casos —incluido
encontrar slugify en medio millón de líneas— y su equipo es de
los pocos honestos midiendo. Pero «menos código» no es automáticamente «mejor
código»: en un modelo capaz, la ganancia es modesta y a veces recorta scope,
tests o un guard.
Lo que ya sabían otros
No es solo mi medición. Un benchmark independiente
(#236,
KuldeepB19: 480 builds, 24 jobs, Claude Opus 4.8, cuatro
niveles) encontró ~44% menos código sin pérdida general de
correctness ni seguridad — pero un coste de robustez real en 5 jobs
con edge-cases no dichos: «un build crasheó con input malo donde la
versión sin plugin aguantó», y el nivel ultra degrada en
vez de mejorar. Coincide exacto con lo que vi: el recorte a veces se lleva un
guard. Y la pregunta de fondo —¿la restricción always-on empeora el
razonamiento en tareas duras (SWE-bench, Terminal-bench)?— sigue
abierta y sin datos
(16 👍, sin respuesta).
Veredicto
ponytail es un buen valor por defecto: no infla, reúsa bien, y su equipo mide
con una honestidad que casi nadie tiene. Pero el titular «-54%» es una cifra
de Haiku; en un modelo capaz el recorte es modesto y, en la mitad de mis
pruebas, se llevó por delante algo que valía la pena — la persistencia de un
campo, un puñado de tests, un guard de validación. La propia herramienta trae
un comando /ponytail-review que te devuelve una lista de borrado:
asume que revisas el diff. Ese es justo el punto medio de este sitio — usar
los agentes con criterio, medir antes de creer, revisar antes de confiar. Ni
rechazar la pereza por reflejo, ni tragarse el «-94%» sin mirar qué se recortó.