Vida del proyecto: issues, contribuciones, CI y el coste de mantener
La reproducción mínima como condición de entrada, plantillas que filtran sin ofender, qué contribuciones se pueden aceptar y cuáles no, una integración continua que responde por ti, y la aritmética honesta de sostener algo que otras personas usan a diario.
Publicar un plugin es rápido. Lo que empieza ese día es otra cosa: una relación de duración indefinida con desconocidos que dependen de tu tiempo libre. Llegarán issues escritos en dos líneas desde configuraciones que combinan cuarenta plugins, contribuciones bienintencionadas que doblan la superficie de la API, peticiones razonables que no puedes atender y agradecimientos que llegan justo cuando ibas a abandonar. Nada de eso es un problema técnico, y sin embargo es lo que decide si el proyecto sigue vivo dentro de dos años. Esta última lección trata de construir las estructuras que hacen sostenible esa relación, y de mirar de frente su coste real antes de contraerlo.
- Exigir una reproducción mínima y proporcionar los medios para producirla sin fricción.
- Filtrar y encauzar contribuciones con reglas escritas en lugar de con criterio caso a caso.
- Automatizar en integración continua todo lo que no quieras volver a decir a mano.
- Calcular el coste de mantenimiento y decidir conscientemente cuánto estás dispuesto a pagar.
El issue como unidad de trabajo
Un issue útil contiene tres cosas: qué esperabas, qué pasó y cómo reproducirlo desde cero. La tercera es la que falta siempre, y sin ella la conversación se convierte en una investigación por correspondencia que consume más tiempo que arreglar el fallo. La solución no es pedirla con más énfasis, es hacerla trivial: se distribuye un archivo de configuración mínima que instala solo tu plugin y sus dependencias, y se pide que el fallo se reproduzca con él.
-- repro.lua : ejecutar con nvim --clean -u repro.lua
local raiz = vim.fn.stdpath("run") .. "/mi-plugin-repro"
for _, sub in ipairs({ "data", "state", "cache", "config" }) do
vim.env[("XDG_%s_HOME"):format(sub:upper())] = raiz .. "/" .. sub
end
local lazy = raiz .. "/data/nvim/lazy/lazy.nvim"
if not vim.uv.fs_stat(lazy) then
vim.fn.system({ "git", "clone", "--filter=blob:none",
"https://github.com/folke/lazy.nvim.git", lazy })
end
vim.opt.runtimepath:prepend(lazy)
require("lazy").setup({
{ "autor/mi-plugin", opts = {} },
}, { root = raiz .. "/data/nvim/lazy" })
Ese archivo hace dos cosas a la vez, y la segunda importa más que la primera. Resuelve técnicamente el aislamiento —nada del entorno del usuario contamina la prueba—, y traslada la carga de la demostración a quien afirma, que es donde debe estar. Buena parte de los issues se cierran solos en cuanto alguien intenta reproducirlos en limpio y descubre que el conflicto estaba en otro plugin. La plantilla de issue, por su parte, no es burocracia: es un formulario con la versión de Neovim, la salida del comando de comprobación de salud y el archivo de reproducción, y su función real es evitar la primera ronda de preguntas, que es la que agota.
La otra mitad del triaje la resuelve una comprobación de salud, que es el único mecanismo que te permite recibir en el issue un diagnóstico del entorno ajeno sin tener que pedirlo pieza a pieza.
-- lua/mi-plugin/health.lua : responde a :checkhealth mi-plugin
local M = {}
function M.check()
vim.health.start("mi-plugin")
if vim.fn.has("nvim-0.10") == 1 then
vim.health.ok("version de Neovim suficiente")
else
vim.health.error("se requiere Neovim 0.10 o superior")
end
if vim.fn.executable("rg") == 1 then
vim.health.ok("ripgrep encontrado")
else
vim.health.warn("ripgrep ausente", { "instalalo para la busqueda rapida" })
end
end
return M
Media hora de trabajo, y a cambio cada issue llega con las dependencias, las versiones y la configuración efectiva ya comprobadas. Es probablemente la mejor relación entre esfuerzo y tiempo ahorrado de todo este nivel.
Contribuciones que puedes aceptar
Una contribución no es un regalo neutro: es una propuesta de que asumas el mantenimiento perpetuo de código que no escribiste. Con esa lente, aceptar deja de ser lo educado por defecto y pasa a ser una decisión con criterio, y el criterio conviene tenerlo escrito antes de que llegue la primera.
| Tipo | Respuesta razonable |
|---|---|
| Corrección con reproducción y prueba | Aceptar rápido, es el caso ideal |
| Corrección sin prueba | Aceptar pidiendo la prueba en la misma revisión |
| Función nueva sin discusión previa | Pedir issue antes: el diseño se acuerda, no se envía |
| Función que amplía la API pública | Evaluar contra la superficie mínima, casi siempre no |
| Refactorización estética sin fallo detrás | Declinar con gratitud, el riesgo no compensa |
| Soporte para un caso muy específico | Preguntar si cabe como punto de extensión |
Escribir un archivo de contribución con estas reglas ahorra conversaciones incómodas, porque convierte un rechazo personal en la aplicación de una norma pública que ya existía. Debe incluir además lo aburrido y necesario: cómo ejecutar las pruebas en local, qué formateador y qué analizador estático se usan, el formato de los mensajes de commit si automatizas las notas de cambios, y la licencia bajo la que se aporta. Y hay una frase que conviene decir en voz alta y sin rodeos: no, dicho pronto y con amabilidad, es un servicio a quien contribuye. Una petición que queda abierta seis meses cuesta más disgusto que un rechazo razonado el primer día.
Integración continua que responde por ti
Todo lo que puedas verificar automáticamente es una conversación que no tendrás que mantener. Una integración continua modesta —formato, análisis estático, pruebas sobre las versiones que declaraste soportar y regeneración de la documentación— cubre la práctica totalidad de los comentarios repetitivos de una revisión.
jobs:
pruebas:
strategy:
matrix:
version: [v0.10.4, stable, nightly]
steps:
- uses: actions/checkout@v4
- uses: rhysd/action-setup-vim@v1
with:
neovim: true
version: ${{ matrix.version }}
- run: make test
- run: make lint
describe("anotaciones", function()
it("marca la funcion actual al ejecutar el comando", function()
local buf = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_lines(buf, 0, -1, false, { "local function f() end" })
vim.api.nvim_set_current_buf(buf)
require("mi-plugin").setup({})
vim.cmd("MiPluginAnotar")
local ns = vim.api.nvim_create_namespace("mi-plugin")
local marcas = vim.api.nvim_buf_get_extmarks(buf, ns, 0, -1, {})
assert.equals(1, #marcas)
end)
end)
Las pruebas de un plugin de Neovim se ejecutan dentro de una instancia real del editor, porque casi todo lo interesante depende de buffers, ventanas y eventos que no existen fuera. Las bibliotecas habituales arrancan una instancia hija, ejecutan las aserciones contra ella y devuelven el resultado, lo que permite probar de verdad que un comando modifica el buffer que debe. El resto de la automatización es igual de valiosa y mucho más barata: comprobar que el formateador no cambiaría nada, que el analizador estático no encuentra variables globales accidentales, y que el archivo de ayuda regenerado coincide con el versionado.
flowchart TB iss[Issue nuevo] --> pl[Plantilla con version y salud] pl --> rep[Reproduccion con archivo minimo] rep --> tri[Triaje y etiqueta] tri --> cerr[Se cierra solo: era otro plugin] tri --> arr[Correccion con prueba de regresion] arr --> ci[Integracion continua sobre la matriz] ci --> tag[Publicacion y notas de cambios] style rep fill:#cba6f7,color:#11111b style ci fill:#a6e3a1,color:#11111b
El coste real de mantener
Conviene hacer la aritmética en vez de descubrirla. Un plugin con unos pocos miles de usuarios genera un flujo constante y modesto: algunos issues al mes, alguna contribución, un par de roturas al año provocadas por cambios del propio editor, y el trabajo periódico de publicar. Nada de eso es dramático por separado; lo que agota es que no se detiene, que llega en el momento que no eliges y que la parte que consume más tiempo no es programar sino leer, entender, responder y decidir.
Hay cuatro decisiones que reducen ese coste más que cualquier otra cosa, y todas se toman antes de necesitarlas. Declarar por escrito qué no hace el plugin, para que las peticiones fuera de alcance se resuelvan con un enlace. Mantener la superficie pequeña, porque cada símbolo público es una fuente permanente de preguntas. Automatizar sin piedad todo lo verificable. Y responder pronto aunque sea para decir que no vas a hacerlo: la deuda emocional de un issue sin contestar pesa mucho más que la de uno cerrado.
La regla que más rentabiliza el tiempo a largo plazo es no cerrar nunca un fallo sin añadir la prueba que lo habría detectado. No es una cuestión de rigor: los fallos de un plugin de editor reaparecen con una frecuencia asombrosa, porque los provocan combinaciones raras de estado que vuelves a romper sin darte cuenta al refactorizar. Una prueba de regresión convierte un issue resuelto en un issue que no puede volver, y ese es el único tipo de trabajo cuyo beneficio se acumula en vez de agotarse.
Queda la parte que casi nadie escribe y que es la más importante. Un proyecto puede terminar, y terminarlo bien es un acto de responsabilidad, no de fracaso. Archivar el repositorio con una nota honesta en el README, señalar las alternativas que existen y ofrecer el traspaso a quien quiera continuarlo deja a los usuarios en mejor posición que un proyecto formalmente vivo que lleva año y medio sin responder. Un mantenedor que se retira con claridad hace más por su comunidad que uno que desaparece en silencio.
Todo el nivel converge en un desplazamiento que casi nadie anticipa antes de vivirlo: al publicar, el objeto de tu trabajo deja de ser el código y pasa a ser la relación entre tu código y las personas que lo usan. Mientras era tuyo, la calidad se medía por propiedades internas —elegancia, rendimiento, cobertura, ausencia de fallos—, y todas ellas dependían solo de ti. Desde la publicación, las propiedades que gobiernan si el proyecto vive son de otra naturaleza y ninguna es técnica: cuánto tarda alguien en entender qué hace, con qué facilidad averigua si el problema es tuyo o suyo, cuánta confianza tiene en que actualizar no le arruine la mañana, y cuánto cuesta contribuir sin adivinar tus criterios. Ese desplazamiento explica retrospectivamente por qué las cuatro lecciones anteriores tratan de lo que tratan. La superficie mínima no es minimalismo estético: es reducir el número de promesas que tendrás que sostener con tu tiempo. La documentación en formato de ayuda no es cortesía: es sustituir conversaciones futuras por texto que responde solo. El versionado semántico no es metadato: es un canal de comunicación comprimido que le ahorra a cada usuario la lectura de tu historial. La detección de capacidades y la deprecación gradual no son rigor técnico: son la forma de cambiar de opinión sin castigar a quien confió en ti. Las cuatro son, en el fondo, mecanismos para que el coste de tu proyecto no crezca linealmente con su número de usuarios, porque si crece de ese modo el final está determinado desde el principio y solo falta saber la fecha. Y de ahí se deriva la única conclusión que de verdad importa cuando alguien decide publicar algo: el software libre no se sostiene con generosidad, se sostiene con diseño. La generosidad se agota; la buena estructura acumula. Los proyectos que llevan una década funcionando no tienen mantenedores más entregados que los que murieron en el segundo año: tienen fronteras más claras, promesas más pequeñas, automatización más completa y menos deudas contraídas por descuido. Entender eso es lo que separa a quien escribió un plugin de quien mantiene uno, y es la última cosa que este nivel tenía que enseñarte.
Exige reproducción mínima y regálala hecha, con archivo de arranque limpio y comprobación de salud. Escribe las reglas de contribución antes de necesitarlas y di que no pronto y con amabilidad. Automatiza formato, análisis estático, pruebas sobre la matriz de versiones y regeneración de la ayuda. Declara qué no hace el plugin. Y si algún día terminas, termina con claridad: archivar bien es mejor que desaparecer.
- Escribe el archivo de reproducción mínima de tu plugin y compruébalo con el editor arrancado en limpio.
- Crea plantillas de issue que pidan la versión de Neovim, la salida de la comprobación de salud y el archivo de reproducción.
- Redacta el archivo de contribución con tus reglas, incluida una sección explícita de qué contribuciones no vas a aceptar y por qué.
- Monta una integración continua con formato, análisis estático y pruebas sobre las tres versiones que declaraste soportar.
- Escribe en el README un párrafo con lo que el plugin no hace, y otro con qué ocurriría si algún día dejaras de mantenerlo.