En el artículo anterior sobre WebPerf Snippets y Agent SKILLs expliqué cómo los scripts de diagnóstico se organizan como herramientas determinísticas que el agente lee y ejecuta en el navegador. La siguiente pregunta lógica es: ¿y qué hace el agente con el resultado?
La respuesta, hasta hace poco, era: parsear texto de consola.
El problema de escribir para humanos cuando el lector es un agente
Los WebPerf Snippets escriben en consola mensajes como este:
🟢 LCP: 1.24s (good)
Element: img.hero
URL: hero.avif
Para quien abre DevTools manualmente, eso es perfecto. Colores, emojis, jerarquía visual. Pero cuando el consumidor es un agente de IA que ejecuta el script vía evaluate_script() y recibe el output como texto plano, ese formato deja de ser una ventaja.
El agente tiene que localizar el valor numérico dentro del string, interpretar el rating a partir del emoji, extraer atributos adicionales de líneas con indentación variable. Parsing frágil sobre un formato diseñado para la lectura humana.
Esta situación está bien documentada en el artículo Rewrite your CLI for AI agents: las interfaces para humanos y las interfaces para agentes tienen requisitos distintos. Lo que hace legible un CLI para las personas (color, emojis, alineación de columnas) genera ruido innecesario cuando el consumidor es un LLM.
Retornos estructurados desde evaluate_script()
El cambio central es que cada script ahora retorna un objeto JSON desde la propia función IIFE:
// Antes: el agente tenía que parsear "🟢 LCP: 1.24s (good)" de consola
// Después:
{
script: "LCP",
status: "ok",
metric: "LCP",
value: 1240,
unit: "ms",
rating: "good",
thresholds: { good: 2500, needsImprovement: 4000 },
details: {
element: "img.hero",
url: "hero.avif",
sizePixels: 756000
}
}
El agente lee el valor de retorno directamente desde evaluate_script(). Sin parsing. Sin depender del formato de consola.
El schema está documentado en el repositorio y distingue tres tipos de script:
- Scripts de métrica →
{ value, unit, rating, thresholds, details } - Scripts de auditoría →
{ count, items[], issues[] } - Scripts de tracking →
{ status: "tracking", getDataFn }con una funciónwindow.getFn()que el agente puede llamar después de la interacción del usuario - Retornos tempranos (sin datos, API no soportada) →
{ status: "error"|"unsupported", error }
El output de consola no cambia. El retorno JSON es adicional: los scripts siguen siendo útiles en DevTools manual, y ahora también son consumibles directamente por agentes.
Build agent-first con terser
Si los agentes no necesitan el output de consola, tampoco tiene sentido que ese código ocupe tokens en los scripts que cargan. La solución: eliminarlo en el build.
El generador de skills, scripts/generate-skills.js, ahora minifica cada script con terser en lugar de copiarlo tal cual:
// Antes: copyFileSync(src, dest)
// Después:
const result = await minify(source, {
compress: {
drop_console: true,
pure_getters: true,
passes: 2,
},
mangle: true,
format: { comments: false },
});
drop_console: true elimina todas las llamadas a console.*. Un snippet de 80 líneas que dedica la mitad al formatting de consola se convierte en una sola línea de ~300 caracteres.
Si la minificación falla, el build hace fallback a la copia literal. El proceso nunca se rompe.
Eliminación de código innecesario: pure_getters + passes: 2
drop_console: true elimina las llamadas a consola, pero deja las constantes que solo se usaban para esas llamadas. En varios scripts existe un objeto RATING con iconos emoji y colores para console.group():
const RATING = {
good: { icon: "🟢", color: "#0CCE6A" },
"needs-improvement": { icon: "🟡", color: "#FFA400" },
poor: { icon: "🔴", color: "#FF4E42" },
};
const { icon, color } = RATING[rating];
console.group(`${icon} LCP: ${value}ms (${rating})`); // ← eliminado por drop_console
Después de eliminar el console.group(), icon y color quedan sin usar. Pero terser, en su primera pasada, no puede determinar que acceder a RATING[rating] no tiene efectos secundarios, así que mantiene la destructuring.
Dos opciones adicionales resuelven esto:
pure_getters: true: indica a terser que los accesos a propiedades (obj.prop,obj[key]) no tienen efectos secundarios, por lo que los resultados destructurados pueden eliminarse si no se usan.passes: 2: ejecuta una segunda pasada de compresión para eliminar el código innecesario, variables sin consumidores cuya única dependencia ya fue eliminada en la primera pasada.
El resultado: el objeto RATING completo, su destructuring y sus referencias desaparecen del script generado.
// ❌ Sin pure_getters + passes: 2 — RATING sobrevive
const t = { good: { icon: "🟢", color: "#0CCE6A" }, ... }
const { icon: c, color: m } = t[l] // c y m no se usan, pero terser los mantiene
// ✅ Con pure_getters + passes: 2 — completamente eliminado
(e(n), o.element) // solo las partes con efectos secundarios reales
Impacto en tokens
Un snippet con output de consola elaborado (80 líneas) pasa a ser una sola línea de ~300 caracteres. Con ~50 scripts distribuidos entre los 6 SKILLs, la reducción acumulada es significativa cuando el agente carga varios scripts en una sesión de análisis.
La eliminación de los objetos RATING reduce adicionalmente cada script, al quitar constantes que existían exclusivamente para el formatting visual.
Trazabilidad: el script generado siempre apunta a su fuente
Cada script generado incluye una cabecera de una línea:
// snippets/CoreWebVitals/LCP.js | sha256:a3f1b2c9d4e5f678 | https://github.com/nucliweb/webperf-snippets/blob/main/snippets/CoreWebVitals/LCP.js
(()=>{...código minificado...})();
El SHA-256 se calcula sobre el fichero fuente original, no sobre el minificado. Esto permite verificar en cualquier momento que el script instalado en ~/.claude/skills/ corresponde exactamente a una versión concreta del snippet:
shasum -a 256 snippets/CoreWebVitals/LCP.js | cut -c1-16
# debe coincidir con el hash en .claude/skills/webperf-core-web-vitals/scripts/LCP.js
Conclusión
El cambio en los retornos y el build es pequeño en código pero relevante en diseño. Los scripts de diagnóstico tienen ahora dos interfaces: la visual (en consola) para quien audita manualmente; y la estructurada (en el valor de retorno) para los agentes que los consumen de forma programática. El build elimina la primera cuando genera los skills, dejando solo lo que el agente necesita. Menos tokens, datos directamente accionables, trazabilidad del origen.