ponytail: meno codice, codice migliore?
Agent Report
- Strumento
- ponytail (76,5k ★)
- Tipo
- Critica
- Contesto
- full-stack-fastapi-template (medio) + Django (grande, 522k LOC) — 4 task A/B, modello forte
- Costo
- 8 sessioni di agente (baseline vs. ruleset); LOC misurato con git diff
ponytail è in tendenza su GitHub da giorni: 76,5k stelle, uno
slogan perfetto (« non dice nulla, scrive una riga, funziona ») e una
promessa concreta — che il tuo agente IA scriva ~54% di codice in meno
(fino al 94%), ~20% più economico, 100% sicuro. È un plugin che
inietta un ruleset —una « scala della pigrizia »— in 16+ agenti (Claude Code,
Cursor, Codex…): prima di scrivere codice, l'agente si ferma al primo piolo che
regge — serve davvero? esiste già nel repo? lo fa la stdlib? una feature
nativa? una dipendenza già installata? sta in una riga? — e solo allora scrive
il minimo.
La cosa rara: il suo benchmark è onesto
Quasi ogni progetto virale gonfia i propri numeri. ponytail fa l'opposto. Il suo benchmark ammette che la cifra originale « 80-94% di codice in meno » era gonfiata da un baseline chiacchierone (segnalato da Colin Eberhardt in #126), ha trovato e pubblicato un bug di contaminazione nella propria misura (un hook che attivava ponytail anche sul baseline), ha rifatto il test per poter confutare se stesso, e ha corretto la cifra a un -54% agentico difendibile, mostrando dove non vince. È l'opposto della norma. Qui non c'è nessun « gotcha » da fare.
Ma quel benchmark ha limiti che elencano loro stessi: un solo
modello (Haiku 4.5, piccolo, che sovra-costruisce di più e ha bisogno
di più guida), un repo template medio
(full-stack-fastapi-template) e ticket di feature
greenfield dove il colpo a effetto —« un date picker di 404 righe →
<input type="date"> »— domina la media. La loro stessa
tabella mostra già il CRUD backend convergere a ~0%. È qui che entra questo
studio.
Cosa ho provato che loro no
I loro limiti sono il mio test: un modello forte (non Haiku), un repo davvero grande e maturo (Django, 522k righe di Python, 7.072 file) e assi più difficili che aggiungere un widget — riusare un helper che esiste già in un codebase enorme, scrivere un'utility nuova, e un task « irriducibile ». Una traccia lunga, non due esempi.
Metodo (il loro, esteso): A/B dello stesso agente con
il ruleset di ponytail iniettato vs. senza (baseline = l'agente che fa
il lavoro per bene, non un modello chiacchierone — la correzione giusta che
hanno fatto loro stessi). Quattro ticket reali su repo reali, ogni braccio in
una propria copia del repo, LOC misurato come righe aggiunte del
git diff — la loro stessa metrica.
Task 1 — il color picker (medio, sovra-costruzione)
Ticket: « aggiungi un selettore di colore al form degli item ». È il loro tipo di task simbolo. Risultato: baseline 168 righe, ponytail 41 — un taglio del -76%. Ma il dettaglio conta: nessuno dei due ha installato una libreria. Entrambi sono andati dritti all'input nativo del browser:
<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>
)}
/>
Su un modello forte, la trappola « installa flatpickr e monta un componente
wrapper » —la fonte del -94% su Haiku— quasi non scatta: anche
il baseline ha usato <input type="color">. Allora da dove
viene il -76%? Dallo scope. Il baseline ha portato il colore
da capo a fondo: modello backend, migrazione Alembic, colonna nella tabella,
validazione dell'hex, campo di testo accanto allo swatch. ponytail ha fatto
solo il form frontend — e il colore non viene mai salvato nel
database (il suo stesso report lo ammette: « backend fuori dallo scope
del ticket »). Il ticket era ambiguo; ponytail ha scelto la lettura più
stretta. Più pulito, sì — ma il taglio ha lasciato un buco reale.
Task 2 — riusare in 522k righe (Django, la claim difficile)
Il piolo 2 (« già nel codebase? riusalo ») è facile da scrivere in un ruleset e
difficile da onorare: richiede che l'agente trovi l'helper in un repo
enorme. Ticket: « una funzione title_to_slug che trasforma un
titolo in slug ». Django ha django.utils.text.slugify sepolto tra
522k righe. Lo trova?
Sì — e funziona. ponytail lo ha localizzato e ha scritto un wrapper di una riga:
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 righe, ponytail 10 (-44%). Ma ecco la sfumatura:
anche il baseline ha riusato slugify. La decisione
importante —non reinventare lo slug con una regex— l'hanno presa entrambi. Il
taglio di ponytail è stato cerimonia (docstring di modulo +
blocco __main__ vs. one-liner + doctest), non una decisione più
furba. Un agente competente sale già al piolo 2 senza che glielo si dica.
Task 3 — un'utility nuova nel repo grande
Ticket: una funzione retry con backoff esponenziale. Non c'è retry
pubblico in Django né nella stdlib, quindi entrambi la scrivono da zero —
terreno fertile per sovra-costruire (un decoratore configurabile? jitter? una
classe di policy?). Risultato: baseline 43, ponytail 32
(-26%). Quasi convergono. Nessuno ha
sovra-costruito: entrambi hanno scritto una funzione asciutta con
time.sleep della stdlib e lasciato un check eseguibile. La vera
differenza:
def retry(fn, attempts=3, base_delay=0.1):
if attempts < 1:
raise ValueError("attempts must be at least 1.") # baseline l'ha messo
...
Il baseline ha aggiunto una validazione dell'input (attempts < 1)
che ponytail ha saltato. Ironico: il ruleset di ponytail dice
lui stesso « non pigro sulla validazione ai confini di fiducia ». Qui, parte del
taglio di ponytail è stato proprio quel guard.
Task 4 — l'endpoint « banale »
Ticket: « un endpoint che restituisce quanti item ha l'utente corrente ». Mi
aspettavo convergenza (è CRUD). È stato l'opposto: baseline 71
righe, ponytail 12 — un taglio del -83%.
ponytail ha scritto un endpoint pulito, riusato la query di conteggio esistente,
e persino ordinato bene la route (/count prima di
/{id}, un vero dettaglio di routing). Ma:
Endpoint + un modello di risposta tipizzato (OwnedItemsCount) + 3 test: conta 0→3, non conta gli item altrui, richiede l'auth.
Endpoint che restituisce un dict nudo. Nessun modello tipizzato, nessun test.
ponytail ha restituito un dict non tipizzato —rompendo la
convenzione di risposte tipizzate che usa tutto il resto del repo (il
suo stesso piolo 2)— e non ha lasciato alcun test, anche se la
sua regola dice « la logica non banale lascia UN check eseguibile ». Ha giudicato
l'endpoint banale. Il baseline ha scritto tre test che verificano cose reali
(che non conta gli item di altri utenti, che richiede l'autenticazione). Il
baseline ha sovra-testato, o ponytail sotto-testato? Le persone ragionevoli
divergono — ma il taglio del -83% non era bloat, era copertura e tipi.
Il pattern
Media dei quattro tagli: ~57% — quasi identico al loro -54%, anche su un modello forte e un repo grande. La cifra di LOC si riproduce. Ciò che cambia è da dove viene il taglio:
Non è un verdetto contro. ponytail non ha mai scritto di più
(la sua claim centrale regge), ha riusato bene in ogni caso —incluso trovare
slugify in mezzo milione di righe— e il suo team è tra i pochi che
misura onestamente. Ma « meno codice » non è automaticamente « codice migliore »:
su un modello capace il guadagno è modesto e a volte taglia scope, test o un
guard.
Quello che altri sapevano già
Non è solo la mia misura. Un benchmark indipendente
(#236,
KuldeepB19: 480 build, 24 job, Claude Opus 4.8, quattro
livelli) ha trovato ~44% di codice in meno senza perdita
generale di correttezza né sicurezza — ma un costo di robustezza reale
su 5 job con edge-case non detti: « un build è crashato su input
cattivo dove la versione senza plugin ha retto », e il livello ultra degrada invece di migliorare. Coincide esattamente con ciò che ho
visto: il taglio a volte si porta via un guard. E la domanda di fondo —il
vincolo always-on peggiora il ragionamento su task difficili (SWE-bench,
Terminal-bench)?— resta
aperta e senza dati
(16 👍, senza risposta).
Verdetto
ponytail è un buon default: non gonfia, riusa bene, e il suo team misura con
un'onestà che quasi nessuno ha. Ma il titolo « -54% » è una cifra Haiku; su un
modello capace il taglio è modesto e, nella metà dei miei test, si è portato
via qualcosa che valeva la pena — la persistenza di un campo, una manciata di
test, un guard di validazione. Lo strumento stesso include un comando
/ponytail-review che ti restituisce una lista di cancellazione:
dà per scontato che tu riveda il diff. È esattamente la via di mezzo di questo
sito — usare gli agenti con criterio, misurare prima di credere, rivedere prima
di fidarsi. Né rifiutare la pigrizia per riflesso, né bere il « -94% » senza
guardare cosa è stato tagliato.