uxskill
Star on GitHub
Lanzamiento · v3.0.0 · 2026-05-28

ux-skill v3.0 — lanzamos The Brain. Las brand specs son datos de entrenamiento, no plantillas.

v2 elegía del catálogo. v3 destila desde él. Las mismas 160 brand specs, un papel completamente distinto. El recomendador puntuaba 1.182 entradas y devolvía la más cercana al brief. El sintetizador ahora las lee como vocabulario y compila un lenguaje de diseño nuevo a partir de cada brief. Cada llamada devuelve un sistema que no existía antes de la llamada.

Las brand specs son datos de entrenamiento,
no plantillas.

El cambio en un párrafo

v2 era un recomendador. Le dabas un brief y devolvía la coincidencia más cercana de un catálogo curado: 84 estilos, 176 paletas, 160 brand specs, 70 parejas tipográficas. La salida era siempre una fila de la base de datos. v3 conserva el catálogo entero pero invierte su propósito: esas 1.182 entradas son ahora el vocabulario del que destila el motor. Un brief ya no extrae una fila: se compila en siete valores de eje, y esos valores sintetizan tokens frescos. El catálogo le enseña al motor cómo se ve algo «aireado y corporativo»; el motor genera un sistema «aireado y corporativo» nuevo que nunca se ha enviado. Los mismos datos. Un papel completamente diferente.

Lo que se rompió en v2 y v3 arregla

Tres bugs reales y un agujero de diseño. Los bugs no eran ruidosos: eran del tipo silencioso donde el motor seguía devolviendo salidas plausibles y nosotros las seguíamos enviando. Solo los pillamos cuando nos sentamos a escribir la especificación de la actualización y una revisión externa nos obligó a mirarlo con honestidad.

1. Pseudo-determinismo por desempates basados en el orden del sistema de archivos

Varias ordenaciones dentro del pipeline del recomendador puntuaban candidatos por similitud y rompían empates con el orden que devolvía os.listdir de Python. En macOS era alfabético. En Linux dependía del orden de inserción en el diario del sistema de archivos. Entre un clon recién creado y un clon viejo, el desempate podía invertirse. Nos contábamos a nosotros mismos que el motor era determinista. Era determinista hasta el empate. Después del empate era lo que el sistema operativo casualmente recordara.

v3 endurece cada ordenación con un desempate explícito por orden alfabético de brand_id. Mismo brief, mismos ejes, misma salida entre máquinas, sistemas de archivos y versiones de Python. Hay un test que ejecuta el sintetizador 200 veces en tres directorios temporales y afirma que la salida es idéntica byte a byte.

2. Colisiones de ejes resueltas por casualidad

En v2, cuando un brief tenía dos etiquetas que tiraban en direcciones opuestas — por ejemplo dense y corporate — el scorer del recomendador ponderaba una más fuerte mediante algún coeficiente oculto, y la salida se inclinaba hacia la etiqueta que el coeficiente favorecía. No había ninguna regla documentada. Podíamos explicar el resultado a toro pasado. No podíamos predecirlo antes.

v3 sube esto a una matriz de interacción de ejes. Cada conflicto documentado tiene una resolución explícita y una justificación. Densidad y formalidad pelean por el espaciado: gana la densidad en briefs densos (escuela Bloomberg), gana la formalidad en briefs aireados (lujo). La matriz está testeada. Es legible. Está commiteada.

3. El evaluador estaba corrigiendo los deberes del propio sintetizador

Este fue el más vergonzoso, y lo pillamos porque ChatGPT lo señaló en una revisión externa de la especificación v2.1 — lo que llamó el problema del self-referential drift. El flujo parecía limpio: el sintetizador compilaba un sistema desde el brief, luego score_tone_match evaluaba cómo de bien el sistema casaba con el tono del brief. El problema era que score_tone_match derivaba los valores de eje desde el brief usando la misma lógica que el sintetizador, y después comparaba los ejes del sintetizador con sus propios ejes derivados. Ambos lados de la comparación pasaban por la misma función. Claro que puntuaba bien. El sintetizador se estaba calificando a sí mismo con su propia rúbrica.

v3 los desacopla. score_tone_match ahora compara la salida del sintetizador con las tone tags crudas del brief — las cadenas warm, bold, minimal, etc. — a través de un mapeo independiente que nunca toca la lógica de ejes del sintetizador. Los dos caminos ya no pueden copiarse. Esta observación se la debemos a la revisión externa, y lo decimos para que conste.

4. El registro de decisiones era de solo escritura: nunca se consumía

v2 escribía una entrada en .ux/decisions.jsonl tras cada recomendación, pero nada la volvía a leer. Era un archivo de log para nuestra propia depuración, no una señal de retroalimentación. El recomendador se comportaba igual en la llamada 1 y en la llamada 1.000. No había aprendizaje. El repositorio tenía memoria y el motor la ignoraba.

v3 cierra ese ciclo. El registro tiene un esquema bloqueado (_v: 1), el recomendador re-rankea candidatos según éxitos pasados en el mismo bucket (industry, ui_type), y solo cuentan para el re-rank las decisiones que sacaron buena puntuación y que el usuario realmente envió. El cerebro tiene por fin ojos sobre su propio historial. La sección 6 es el paseo completo.

El sintetizador de 7 ejes

En el centro de v3 hay una función determinista que mapea un brief a siete ejes numéricos, y esos siete ejes a un sistema de diseño completo. Los ejes no son magia: cada uno es un escalar normalizado con un rango documentado, y cada uno apunta a un bundle de tokens concreto. La idea de funcionar sobre ejes (en lugar de elegir una fila del catálogo) es que cualquier combinación es alcanzable, incluyendo combinaciones que ninguna marca real del catálogo usa. El catálogo define la forma del espacio; el sintetizador puede aterrizar en cualquier punto dentro de él.

Eje Qué mide Mapea a
warmth Temperatura cromática de la paleta — del carbón frío a la sienna cálida familia de tono de la paleta, curva de saturación del acento
contrast Volumen visual — quiet/balanced/loud ratio de escala tipográfica (1.200 / 1.250 / 1.333), profundidad de sombra, intensidad del acento
density Densidad de información por viewport — aireado a denso base de la escala de espaciado (4/8/12/16px), multiplicadores de line-height
geometry Personalidad del borde — sharp/balanced/soft escala de radius (2/8/18px), topología de esquina, grosor de borde
formality Registro de tono — playful/balanced/corporate curvas de peso tipográfico, tracking, recorrido de caption a hero
motion Presupuesto de animación — restrained/balanced/cinematic rampas de duración (120/240/420ms), familia de easing, comportamiento de scroll
type_personality Voz tipográfica — humanist/neutral/geometric/expressive familia de la display face, escalera de pesos, estrategia de cursiva

El punto de entrada en Python es lo bastante pequeño para caber en un solo ejemplo. Pasas un brief y recibes un sistema sintetizado con los valores de eje, la paleta elegida, la configuración tipográfica, la escala de espaciado, la escala de radius y los presets de motion adjuntos.

# de un brief a un sistema, en una sola llamada
from engine.synthesizer import synthesize

brief = Brief(
  industry="fintech-payments",
  tone=["bold", "serious"],
  audience=["merchants", "developers"],
)

sys = synthesize(brief)
# sys.mode == "pure_synthesis"
# sys.axes == {warmth: 0.32, contrast: 0.72, density: 0.58, ...}
# sys.palette, sys.type, sys.spacing, sys.radius, sys.motion

Mismo brief de entrada, mismos ejes, mismos tokens de salida. Siempre. El sintetizador no tiene semilla aleatoria porque no hay aleatoriedad que sembrar.

Tres modos, una sola lógica

El sintetizador despacha automáticamente entre tres modos según lo que contenga el brief. Para la mayoría de usos no hay un flag para elegir modo a mano: la forma del brief lo decide. La lógica es deliberadamente simple: la presencia de una marca de referencia y un flag estricto determinan el camino.

strict_brand · el más rápido

100% la marca nombrada

reference_brands=[stripe] con strict=True devuelve la spec de Stripe tal cual. Sin interpretación, sin mezcla, sin síntesis. Útil cuando la marca existe, la spec es autoritativa y el brief solo quiere los tokens. Es el camino cuando tienes un Figma y un sistema documentado y necesitas código que no improvise.

brand_anchor · 70 / 30

Anclado a la marca, ajustado por ejes al brief

reference_brands=[stripe] sin strict=True devuelve un 70% de tokens de Stripe fusionado con un 30% de deltas derivados de los ejes de cuatro marcas hermanas elegidas por proximidad axial. La salida sigue siendo inconfundiblemente Stripe, pero se inclina hacia el tono del brief: un Stripe más juguetón para una función de consumo, un Stripe más formal para un panel de empresa. La marca ancla sobrevive a todos los conflictos.

pure_synthesis · espacio infinito

Sin marca nombrada — lenguaje nuevo en cada llamada

Sin reference_brands en el brief. El sintetizador puntúa los siete ejes desde el brief, encuentra los ocho ejemplares más cercanos del catálogo por distancia axial, destila la estructura compartida entre ellos y emite un bundle fresco de paleta, tipografía, espaciado, radius y motion. Briefs distintos aterrizan en regiones distintas del mismo espacio; cada salida es internamente coherente e identificable como propia.

Desde la CLI los tres caminos se ven así:

# strict — 100% la marca
$ uxskill synthesize --brand stripe --strict

# anchor — 70/30
$ uxskill synthesize --brand stripe

# pure synthesis — sin marca
$ uxskill synthesize --industry fintech-payments --tone bold

Un sintetizador, una lógica, tres personalidades de salida. El usuario no cambia de implementación: el brief se enruta solo.

La matriz de interacción de ejes

Los ejes tiran de los mismos tokens. La densidad quiere espaciado apretado; la formalidad quiere espaciado generoso para un registro de lujo. La geometría quiere esquinas afiladas para un tono editorial; la formalidad quiere esquinas suaves para un panel financiero. En v2 esos conflictos se resolvían por el coeficiente que resultara mayor en el scorer. En v3 cada conflicto documentado tiene un resultado declarado y una escuela de diseño a la que apunta, con nombre, para que puedas leerlo y discrepar si quieres.

Cuatro casos representativos, todos commiteados como fixtures de test:

dense + corporate
Base de la escala de espaciado: 4px

Gana la densidad. La respuesta de la escuela Bloomberg. La densidad de información es la virtud máxima cuando el brief pide paneles densos con registro corporativo — la formalidad cede ante la legibilidad a alta densidad.

airy + corporate
Base de la escala de espaciado: 12px

Gana la formalidad. La respuesta de las finanzas premium. Aireado + corporativo es el brief para banca privada, gestión de patrimonios, paneles ejecutivos — el aire alrededor de cada elemento es el mensaje de seriedad.

sharp + corporate
Escala de radius: 2px

Gana la geometría. La respuesta de la escuela NYT. Afilado + corporativo es el brief editorial — los ángulos rectos y los micro-radios de 2px se leen como institucionales, meditados, de gran formato.

soft + playful
Escala de radius: 18px

Ambos ejes alinean. La respuesta de la escuela Glossier. Geometría suave más un registro juguetón colapsan en redondeos generosos — 18px es el umbral donde los rectángulos empiezan a leerse como guijarros.

Por qué importa: en v2 el mismo brief podía aterrizar en 4px o 12px según qué coeficiente le ganara ese mes al scorer. En v3, dense + corporate siempre es 4px. airy + corporate siempre es 12px. Las reglas son legibles, testeables y discutibles. Si no estás de acuerdo con una resolución, puedes abrir un issue contra la matriz y la conversación va sobre gusto, no sobre una constante oculta.

El cerebro aprende

Esta es la parte de v3 que es genuinamente nueva para el proyecto: el motor ahora aprende de tus decisiones locales. El mecanismo es pequeño, determinista y totalmente offline. No hay telemetría, no hay cuenta, no hay sync en la nube. Tu instalación aprende de tu instalación. Repositorios distintos construyen cerebros distintos.

El registro

Cada llamada a /ux-recommend y a /ux-synthesize escribe una línea en .ux/decisions.jsonl. El esquema está bloqueado en _v: 1:

{
  "_v": 1,
  "ts": "2026-05-28T14:22:09Z",
  "frame": { "industry": "fintech-payments", "ui_type": "dashboard", ... },
  "system": { "style_id": "editorial-calm-dark", "palette_id": "charcoal-amber", ... },
  "axes": { "warmth": 0.31, "contrast": 0.72, ... },
  "lint_score": 88,
  "user_accepted": true
}

El re-rank

En la siguiente llamada, el recomendador agrupa las entradas pasadas por (industry, ui_type). Cada candidato que considera recibe un +5 de bonificación si coincide con un precedente del mismo bucket. Solo cuentan los precedentes con lint_score >= 80 Y user_accepted = true — no aprendemos de salidas rechazadas. El bucket necesita al menos tres precedentes válidos para que el re-rank se active. Por debajo de eso, el motor arranca en frío y se comporta igual que una instalación recién hecha.

Las garantías

Las garantías que damos sobre esto son estrechas y merecen decirse en voz alta. El determinismo se preserva. Mismo brief más mismo registro siempre producen la misma salida. El +5 se aplica antes de cualquier desempate, y la regla del orden alfabético por brand_id sigue ganando los empates por debajo. El arranque en frío es seguro. Un repositorio nuevo se comporta como cualquier otro repositorio nuevo. El aprendizaje es local. Tus decisiones no salen de tu máquina.

Puedes inspeccionar lo que ha aprendido tu instalación cuando quieras:

$ uxskill stats --html
[OK] Wrote .ux/stats.html
[OK] Open in browser to see your install's learned priors

El dashboard HTML muestra el número de decisiones por bucket, la puntuación media del linter, las paletas más recurrentes, las distribuciones de ejes. Es la prueba visible del auto-aprendizaje: ves cómo el perfil de gusto de tu instalación se va espesando con el tiempo a medida que envías. Nada de esto está en un servidor. Está en tu repo, en JSONL plano más una vista HTML generada.

/ux-evolve, el bucle automático

El nuevo comando de v3 es /ux-evolve. Hasta ahora el ciclo de pulido era manual: ejecutar el linter, leer los findings, arreglarlos, volver a lintear. Evolve cierra ese ciclo. Le pasas un artefacto y una puntuación objetivo; ejecuta el linter, aplica seis pasadas de pulido idempotentes, vuelve a lintear y para al alcanzar el objetivo, al detectar una meseta, o al llegar al tope de cinco rondas.

La forma de una ronda:

  1. Lint — 145 reglas regex deterministas sobre A11y, contenido, calidad y tipografía. Devuelve una puntuación de 0 a 100 y una lista de findings.
  2. Pulido — seis pasadas que atacan primero los findings de mayor severidad. Idempotentes: volver a aplicar una pasada sobre una salida ya pulida es un no-op.
  3. Re-lint — puntúa el artefacto pulido.
  4. Decidir — si la nueva puntuación es ≥ 90, para. Si el delta respecto a la ronda anterior es menor que 5 (meseta), para. Si estamos en la ronda 5, para. Si no, sigue.

La puerta de calidad está fija en 65. Si la puntuación final está por debajo de 65, el motor se niega a enviar el artefacto salvo que pases --force. La razón: una salida de 65 o menos es slop de IA reconocible, y no dejamos que nuestro propio motor emita slop en silencio. El usuario puede saltarse la puerta, pero el override es explícito y queda en el registro de decisiones.

Una ejecución concreta: un esqueleto de panel fintech sacó 72 en el primer lint — sobre todo estados de foco ausentes, un par de etiquetas con contraste bajo, un finding por Inter en tamaño display. La ronda 1 pulió los estados de foco hasta 81. La ronda 2 arregló contraste hasta 86. La ronda 3 cambió Inter por la display face propia del brief y rebalanceó las captions — 91, objetivo alcanzado, bucle terminado. Tres rondas, sin rechazos, enviado.

Lo que no construimos (y por qué)

La especificación maximalista de v2.1 proponía varias cosas que no entraron en v3. Decir cuáles, y por qué, es la única manera honesta de hablar de alcance.

Nada de LLM en el bucle

La propuesta maximalista incluía un eje estético subjetivo juzgado por LLM — «pregúntale a un modelo si esto se siente editorial». Lo descartamos por principio. El punto entero del motor es el determinismo: mismo brief, misma salida, sin sorpresas mañana por la mañana. Un LLM dentro del sintetizador haría que cada llamada fuera irreproducible. v3 llama a cero LLMs. El sintetizador es Python puro sobre JSON; el linter es regex; el evaluador es basado en reglas.

Nada de mutación genética multi-candidato

La propuesta también tenía un paso de algoritmo genético: emitir N candidatos por llamada, mutarlos, puntuarlos y devolver el más apto. Lo intentamos en una rama. La salida empeoró, no mejoró, porque la mutación introducía ruido que el ciclo de pulido luego tenía que deshacer. v3 envía síntesis de artefacto único con ciclo de pulido. Un candidato. Seis pasadas idempotentes. Mejores resultados, menos superficie de código.

Nada de renombrar comandos

La propuesta renombraba /ux-recommend a /generate:ui y /ux-polish a /mutate:ui. Mantuvimos los 22 comandos existentes con sus nombres existentes y añadimos /ux-evolve como el 23º. No queríamos que una actualización de v2 a v3 rompiera la memoria muscular de nadie ni sus alias de shell.

Nada de «quemar el catálogo por espacio infinito»

La versión más maximalista de la propuesta decía que el catálogo era un lastre — que el sintetizador podía alcanzar el espacio de diseño entero por su cuenta y que las 1.182 entradas deberían irse. Las conservamos todas. El catálogo es lo que le enseña al sintetizador la forma del espacio — sin ejemplares los ejes no tienen anclas, y la salida se desboca. v3 lee el catálogo como vocabulario; v3 necesita el vocabulario.

Son juicios de gusto. Quienes tengan otras prioridades discreparán, y está bien. Le debemos al proyecto decir qué no hicimos y por qué, para que conste.

Los números de v3

23
Comandos
18
Tools MCP
1.182
Entradas
145
Anti-patterns
160
Brand specs
17
IDEs
17
Idiomas
223
Tests OK

Los 22 slash commands existentes mantienen sus nombres, más /ux-evolve como el 23º. Las 15 herramientas MCP se conservan, más tres nuevas — ux_synthesize, ux_decisions_query, ux_decisions_stats — para agentes que quieran pilotar el sintetizador o consultar lo que el cerebro ha aprendido. El catálogo y el linter no se han tocado. El instalador de 17 IDEs no se ha tocado. Las 17 homepages localizadas y los READMEs no han cambiado. v3 es aditivo en la superficie y reescrito en el núcleo.

v2 era un recomendador que elegía de un catálogo. v3 es un compilador que destila desde uno. Los mismos datos están haciendo el trabajo opuesto.

Instalación en 60 segundos

v3 se distribuye por los mismos tres caminos que v2 — pip, npm y los marketplaces de plugins de IDE. El paso init auto-detecta tu IDE y escribe el archivo de configuración correcto en el sitio correcto. El paso synthesize toma un brief o un par industry + tone y devuelve un sistema de diseño completo en .ux/last-system.json dentro del repo actual.

$ pip install uxskill
$ uxskill init                     # auto-detecta tu IDE
$ uxskill synthesize --industry fintech-payments --tone bold

[OK] Mode: pure_synthesis
[OK] Axes: warmth=0.31, contrast=0.74, density=0.58, geometry=0.42, ...
[OK] Wrote .ux/last-system.json (palette, type, spacing, radius, motion)
[OK] Wrote .ux/decisions.jsonl (1 entry, schema _v: 1)

El mismo camino de instalación en todos los IDEs soportados: Claude Code, Cursor, Windsurf, GitHub Copilot, Gemini CLI, Codex, Kiro, Cline, Continue, Aider, Zed, JetBrains AI, Pieces, Tabby, Tabnine, CodeWhisperer, Roo Cline. El servidor MCP por stdio es la única fuente de verdad para todos ellos.

Alcance honesto

v3 es una base, no un destino terminado.

El sintetizador de 7 ejes es el trabajo para el que hemos publicado evidencia. Los ejes 8 y 9 (saturation_strategy, surface_depth) están esbozados pero no enviados — profundizarían el split dark / light y la estrategia de gradiente. El registro de decisiones solo lo consume hoy el re-rank del recomendador; esperamos que el evaluador y el scorer del linter empiecen a leerlo en v3.1. La propuesta maximalista v2.1 que rechazamos no está muerta — algunas de esas ideas aterrizarán más tarde, cuando podamos probar que ayudan.

Si encuentras un brief que produce salidas insatisfactorias, o una resolución de la matriz que choca con tu gusto, abre un issue. La matriz está commiteada, los tests están commiteados, el cerebro está en tu repo. Las discusiones se dan ahora sobre un sustrato que puedes leer.

Qué cambia para ti

Si venías de v2: no se rompe nada. Los 22 comandos que ya usas se comportan igual. El linter corre a la misma velocidad. El servidor MCP expone 15 de las 18 tools con sus nombres antiguos más tres nuevas. Si actualizas y nunca llamas a /ux-synthesize ni a /ux-evolve, tu flujo diario no se mueve nada.

Si estabas esperando a que el motor generara en lugar de recomendar: eso es v3. El sintetizador es el nuevo centro; /ux-recommend sigue funcionando y ahora re-rankea desde tu registro; /ux-evolve cierra el ciclo de pulido. Las brand specs son datos de entrenamiento ahora, no plantillas. Las mismas 1.182 entradas están haciendo un trabajo completamente distinto.

Llamamos a v3 The Brain porque eso es lo que se construyó — un compilador de diseño generativo y restringido con un ciclo de retroalimentación cerrado. Completamente offline. Llama a cero LLMs. Mismo brief, mismos ejes, misma salida. Briefs distintos, regiones distintas, salidas infinitas. Tu instalación aprende de tu repo. Repositorios distintos construyen cerebros distintos. Todo bajo licencia MIT, sub-300ms en una máquina normal, instalable con pip.

Pruébalo en un brief que te importe. Mira qué devuelve. Abre un issue si la salida discrepa con tu gusto — el conflicto entra en la matriz, la regla recibe un nombre y la siguiente instalación se comporta mejor. Así se compone el cerebro.

Ejecútalo en tu proyecto

Un motor. Tres caminos de instalación.

El sintetizador que mueve v3 se distribuye en el mismo paquete que el linter y el recomendador. 145 anti-patterns. 160 brand specs. 1.182 entradas. 17 IDEs. MIT, sin telemetría, sin cuenta.

$ /plugin marketplace add Laith0003/ux-skill
$ /plugin install ux@ux-skill
— o —
$ pip install uxskill
— o —
$ npx uxskill@latest init

Código en GitHub: Laith0003/ux-skill · README en español: README.es.md · npm: uxskill · PyPI: uxskill · Versión en inglés: English version · Lecturas relacionadas: Brand specs son training data, Anti-patterns, Brands