Versionado: semver, rupturas, tags y ramas estables
Qué significan de verdad los tres números aplicados a un plugin de editor, el inventario completo de lo que cuenta como ruptura, cómo se publica con tags anotados y ramas estables, y por qué la versión cero es una promesa distinta que conviene declarar en voz alta.
Un número de versión es un mensaje comprimido dirigido a alguien que no va a leer tu código: le dice si puede actualizar sin mirar, si debe leer las notas antes, o si tiene trabajo por delante. Esa compresión solo funciona si el emisor y el receptor comparten el significado exacto de cada dígito, y ahí es donde el ecosistema de Neovim tiene un problema particular: la mayoría de los usuarios no fija ninguna versión, sigue la rama principal y recibe cada commit tuyo como si fuera una entrega estable. Versionar bien en este contexto no es rellenar un campo de metadatos, es decidir conscientemente qué le estás prometiendo a gente que no ha declarado ninguna expectativa.
- Traducir los tres números de
semvera hechos concretos de un plugin de editor. - Enumerar qué cambios cuentan como ruptura, incluidos los que no parecen código.
- Publicar con tags anotados, notas de cambios y, si procede, una rama estable.
- Comunicar el régimen de estabilidad de la versión cero antes de que alguien lo asuma.
Semver, traducido
El versionado semántico define tres números con un significado preciso, y toda su utilidad depende de aplicarlo literalmente en vez de por intuición. El número mayor sube cuando algo que funcionaba deja de funcionar. El menor sube cuando hay capacidades nuevas y todo lo anterior sigue igual. El de parche sube cuando se corrige un fallo sin cambiar nada de lo prometido.
Mayor
Rompe. Quien actualiza debe leer las notas y probablemente tocar su configuración. Nunca se hace por sorpresa.
Menor
Añade. Funciones nuevas, opciones nuevas con valor por defecto compatible, mejoras de rendimiento. Actualizar es seguro.
Parche
Arregla. Corrige un comportamiento que ya estaba documentado como correcto. Actualizar es seguro y además recomendable.
Los tres números se leen además de derecha a izquierda como una escala de riesgo, y esa lectura es la que usa un gestor de plugins cuando le pides compatibilidad con una versión mayor: puede subirte el parche y el menor sin consultarte, porque ambos prometen no romper nada, pero jamás cruzará una frontera mayor sin que se lo digas explícitamente. Todo el mecanismo automático de actualización descansa sobre esa promesa, y por eso violarla una sola vez tiene un coste desproporcionado: no solo rompe a quien actualizó, enseña a todo el mundo a dejar de confiar en tus números y a clavarse a un tag concreto para siempre.
La regla de oro que resuelve el noventa por ciento de las dudas es que el número lo dicta el impacto sobre el usuario, no el esfuerzo del autor. Una reescritura completa del núcleo que no cambia ni un nombre público es una versión de parche si arregla algo y menor si añade algo; renombrar una opción, que son diez segundos de trabajo, es una versión mayor. Confundir esfuerzo con impacto es el error más frecuente y el que destruye la confianza en el esquema.
El corolario incómodo de esa regla es que la versión mayor deja de ser un logro y pasa a ser una factura. Nadie celebra llegar a la cinco: significa que has roto a tus usuarios cuatro veces. Los proyectos maduros del ecosistema llevan años en la uno o la dos precisamente porque acertaron con la frontera desde el principio, y esa es la lectura correcta del número: no mide madurez, mide cuántas veces tuviste que pedir perdón.
Qué cuenta como ruptura
El inventario de rupturas de un plugin de editor es más amplio que el de una biblioteca convencional, porque tu superficie no son solo las funciones de Lua: incluye todo lo que el usuario puede haber escrito en su configuración referido a ti.
| Cambio | Ruptura | Alternativa compatible |
|---|---|---|
| Renombrar o borrar una función pública | Sí | Mantener el alias antiguo con aviso |
| Renombrar una clave de configuración | Sí | Aceptar ambas y avisar de la vieja |
| Cambiar el valor por defecto de una opción | Sí | Añadir la opción y cambiarlo en la mayor |
| Renombrar un grupo de resaltado | Sí | Enlazar el nombre viejo al nuevo |
| Renombrar un comando de usuario | Sí | Registrar los dos durante un ciclo |
Renombrar un evento User que emites |
Sí | Emitir los dos durante un ciclo |
| Subir la versión mínima de Neovim | Sí | Anunciarlo y publicar rama estable |
| Cambiar la forma de una tabla devuelta | Sí | Devolver campos nuevos sin quitar los viejos |
| Añadir una opción con defecto inocuo | No | Es una versión menor |
| Reescribir el núcleo sin tocar la fachada | No | Es una versión de parche o menor |
El caso más resbaladizo del inventario es el fallo que la gente ya usaba como característica. Si tu función devolvía las líneas en orden inverso por descuido y alguien construyó su flujo encima, arreglarlo rompe su configuración aunque el comportamiento nuevo sea el documentado. No hay respuesta universal: si el uso accidental es marginal, se arregla en un parche y se menciona en las notas; si está extendido, se arregla en una versión mayor o se ofrece una opción para conservar el comportamiento antiguo durante un ciclo.
Las tres filas que sorprenden son las de los grupos de resaltado, los comandos y los eventos, y sorprenden porque no son Lua. Pero un usuario que escribió una regla de color para tu grupo, un mapeo hacia tu comando o un autocomando escuchando tu evento tiene una dependencia tan real como quien llama a tu función, y se rompe igual de rápido. La cuarta que se pasa por alto es subir la versión mínima del editor: no cambia tu API en absoluto, y sin embargo deja fuera a todo el que no puede actualizar, que es la definición operativa de ruptura.
Existe una prueba mental que resuelve los casos dudosos en un par de segundos y que conviene automatizar en la cabeza: coge la configuración de ejemplo de tu propio README de hace un año, aplícala mentalmente a la versión que estás a punto de publicar y pregúntate si sigue funcionando exactamente igual. Si algo cambia de nombre, de forma, de valor por defecto o de resultado visible, es una ruptura, por pequeña que te parezca la modificación en el código.
Tags, ramas y el acto de publicar
Publicar en este ecosistema significa crear un tag de Git. No hay registro central, no hay paquete que subir: los gestores de plugins clonan tu repositorio y resuelven la versión a partir de los tags que encuentran. Los tags deben ser anotados, porque llevan autor, fecha y mensaje, y ordenables como versiones.
# Tag anotado: lleva metadatos y mensaje propio
git tag -a v1.4.0 -m "Anade soporte para inyecciones de lenguaje"
git push origin v1.4.0
# Que version resolvera un gestor que pida compatibilidad con la 1
git tag --list "v1.*" --sort=-version:refname | head -1
La tentación aparece siempre: te das cuenta de que la versión que acabas de etiquetar tiene un fallo tonto y piensas en rehacer el tag sobre el commit corregido. No lo hagas. Hay gente cuyo gestor ya descargó ese tag y lo tiene en caché, y moverlo produce dos instalaciones distintas con el mismo nombre, que es la clase de incidencia más difícil de diagnosticar que existe. La respuesta correcta siempre es la misma y cuesta diez segundos: publicar la siguiente versión de parche.
Del lado del usuario, el gestor traduce eso a una restricción declarativa: pedir la última versión publicada, pedir compatibilidad con una mayor concreta, o clavarse a un tag exacto. Ese es el mecanismo que hace útil todo lo anterior, y solo funciona si tus tags son consistentes.
{ "autor/mi-plugin" }, -- rama principal: lo ultimo
{ "autor/mi-plugin", version = "*" }, -- ultimo tag de version
{ "autor/mi-plugin", version = "^1.0.0" }, -- compatible con la mayor 1
{ "autor/mi-plugin", tag = "v1.4.0" }, -- exactamente esa
{ "autor/mi-plugin", branch = "stable" }, -- rama de mantenimiento
Decidir a mano qué número toca funciona hasta que se te olvida una vez. La alternativa madura es derivar la versión del propio historial adoptando un formato de mensaje de commit con prefijo semántico, y dejar que una herramienta calcule el salto, redacte las notas agrupadas por tipo y cree el tag.
fix: no fallar cuando el buffer se cierra durante el analisis -> parche
feat: anadir opcion de borde para la ventana flotante -> menor
feat!: renombrar setup opts.color a opts.highlight -> mayor
BREAKING CHANGE: la clave color pasa a llamarse highlight
El signo de admiración y el pie de ruptura son lo que dispara el salto mayor, y tienen una virtud secundaria que compensa por sí sola la disciplina: obligan a escribir la migración en el momento en que cometes la ruptura, cuando todavía recuerdas exactamente qué hay que cambiar, en lugar de reconstruirla meses después leyendo tu propio historial.
La rama estable es una herramienta distinta y complementaria: una rama que apunta siempre a la última publicación considerada segura y que recibe correcciones importadas desde la principal, sin funciones nuevas. Solo merece la pena cuando tienes suficientes usuarios como para que alguien necesite parches sin novedades, o cuando has subido la versión mínima del editor y quieres dejar viva una línea para quien no puede actualizar. Antes de eso, es ceremonia sin beneficio.
flowchart TB c[Commits en la rama principal] --> notas[Notas de cambios agrupadas por tipo] notas --> dec[Hubo alguna ruptura del inventario] dec --> may[Sube el numero mayor] dec --> men[Sube el menor o el parche] may --> tag[Tag anotado y empujado] men --> tag tag --> res[El gestor resuelve la version pedida] tag --> est[Rama estable con correcciones importadas] style tag fill:#cba6f7,color:#11111b style res fill:#a6e3a1,color:#11111b
Un detalle operativo cierra el mecanismo: el tag debe apuntar a un commit en el que la documentación, las notas de cambios y la versión mínima declarada ya estén actualizadas. Etiquetar primero y documentar después produce una versión publicada que se describe a sí misma de forma incorrecta, y como los tags no se mueven, esa incorrección queda fijada para siempre en la única versión que algunos usuarios tendrán instalada durante meses.
La versión cero y el pin que nadie pone
Mientras el número mayor es cero, el contrato es explícitamente otro: cualquier cosa puede cambiar en cualquier momento. Esa licencia es legítima y útil mientras el diseño se asienta, pero tiene fecha de caducidad social. Un proyecto que lleva tres años en 0.x con miles de usuarios ya no está experimentando: está evitando comprometerse mientras cosecha los beneficios de la adopción, y el primer cambio incómodo generará la misma indignación que si hubiera prometido estabilidad. Llegar a la versión uno no es un rito de madurez, es la declaración de que estás dispuesto a sostener lo que ya tienes.
Hay un matiz técnico de la versión cero que casi nadie aplica y que conviene conocer: en el esquema semántico, mientras el mayor es cero, el papel de guardián de las rupturas lo hereda el número menor. Un salto de 0.3.7 a 0.4.0 es donde caben las rupturas, y los parches dentro de 0.3 siguen siendo seguros. Adoptar ese convenio en lugar de tratar toda la versión cero como tierra de nadie te da la mayor parte del beneficio de semver sin comprometerte todavía con una API definitiva, y hace posible que quien quiera fijarse pueda hacerlo.
Queda el hecho más importante y el menos cómodo: la mayoría de tus usuarios no fija ninguna versión. Siguen la rama principal, actualizan cuando les apetece y reciben cada uno de tus commits como si fuera una publicación. En esa realidad, las notas de cambios y los tags no protegen a nadie por sí solos; lo que protege es la disciplina de que la rama principal esté siempre en estado publicable, que los cambios grandes maduren en una rama aparte, y que un aviso claro en la sesión —una sola vez, no en cada arranque— acompañe a cualquier ruptura durante al menos un ciclo. El número de versión es la etiqueta; el comportamiento cotidiano es el producto.
La forma habitual de pensar el versionado es interna y por eso falla: se imagina el número como una propiedad del código, una especie de marca de progreso que dice cuánto has avanzado desde la última vez. Bajo esa lectura las discusiones se vuelven irresolubles —si esto es una mejora grande o pequeña, si la reescritura merece un salto— porque se está midiendo la magnitud equivocada. Semver propone algo mucho más radical y mucho más útil: el número no habla de ti, habla del receptor, y responde a una única pregunta, que es cuánto trabajo tendrá que hacer alguien que actualice sin mirar. Con esa reformulación las dudas se disuelven, porque la pregunta pasa a ser empírica en lugar de estética: si existe al menos una configuración razonable que deja de funcionar, es una mayor; si no, no lo es, por muy espectacular que sea el cambio. Lo que hace especialmente exigente aplicar esto a un plugin de editor es que la superficie del contrato es enorme y difusa. Una biblioteca convencional se rompe por sus funciones; un plugin se rompe además por sus nombres de resaltado, sus comandos, sus eventos, sus mapeos por defecto, la forma exacta de su tabla de configuración y hasta por el nombre de las etiquetas de su ayuda, porque cada una de esas cosas es una cuerda que alguien pudo atar desde su propio archivo. Reconocer esa amplitud tiene una consecuencia de diseño que cierra el círculo con la lección primera: la mejor estrategia de versionado es tener poco que versionar. Cuanto más pequeña sea la superficie que publicaste, menos cuerdas existen, menos cosas cuentan como ruptura y más barato resulta evolucionar sin traicionar a nadie. Los proyectos que llevan una década publicando versiones menores sin una sola mayor no es que hayan dejado de cambiar: es que colocaron la frontera en el sitio correcto desde el principio, y por eso pueden reescribirse enteros por dentro sin que ningún usuario se entere jamás. Esa es la recompensa acumulada de la disciplina, y solo se cobra pasados los años.
El número lo dicta el impacto en el usuario, nunca tu esfuerzo. El inventario de rupturas incluye funciones, claves de configuración, valores por defecto, grupos de resaltado, comandos, eventos y la versión mínima del editor. Publicar es crear un tag anotado; la rama estable solo compensa cuando alguien la necesita. Declara si estás en versión cero, y recuerda que la mayoría sigue la rama principal: mantenla siempre publicable.
- Revisa el historial reciente de un plugin tuyo y clasifica cada commit como mayor, menor o parche según el impacto en el usuario, no según el esfuerzo.
- Recorre el inventario de rupturas y comprueba cuántas de ellas cometiste sin darte cuenta, especialmente en grupos de resaltado y nombres de comandos.
- Crea un tag anotado, empújalo y verifica desde otra máquina que tu gestor resuelve la versión pedida con una restricción de compatibilidad.
- Escribe unas notas de cambios agrupadas por tipo, con una sección de rupturas que explique la migración exacta línea a línea.
- Decide y escribe en el README si estás en versión cero o en versión uno, y qué implica eso para quien te instale mañana.