ADR 0106 — Las cuentas y las rutas del lint salen de los mensajes
This content is not available in your language yet.
Estado: aceptado · Fecha: 2026-11-06
Contexto
Sección titulada «Contexto»El artefacto del lint (lint.repmod.json y lint-rest.repmod.json) guarda dos
cosas que pueden no decir lo mismo: los mensajes y las cuentas
(errors, warnings y el summary). Las hojas, el riesgo y el MCP ya cuentan
mensajes (lintTotals, lintFileCounts). El lector, en cambio, devolvía las
cifras declaradas tal cual. Un artefacto de fuera con errors: 9 y un solo
aviso enseñaba 9 en un sitio y 1 en otro.
Las rutas tenían el mismo problema al revés. Al escribir, canonicalLintPath
deja la ruta relativa, con / y sin ... Al leer, la clave se conservaba
como viniera: ./src/a.ts, src\\a.ts y src/Cafe\u0301.ts no eran
src/a.ts ni src/Café.ts. El mapa compara por cadena, así que el hallazgo no
caía en el fichero.
summary.measured es otra cifra: cuántos ficheros miró la pasada, limpios
incluidos. Esos limpios no están en files, así que no sale de los mensajes.
No es una cuenta por carpeta.
Subir el esquema a 1.3 para quitar las cuentas heredadas rompería los artefactos 1.0, 1.1 y 1.2 que ya están en repos. Los nombres de los ficheros tampoco se tocan (ADR 0100).
Decisión
Sección titulada «Decisión»-
El esquema sigue en 1.2. No hay 1.3. Los ficheros siguen llamándose
lint.repmod.jsonylint-rest.repmod.json. Las cuentas heredadas siguen en el fichero: al leer se regeneran, no se tiran. -
Los mensajes son la única fuente de las cuentas.
readLintArtifactreescribe, conlintFileCountsylintTotals:errorsywarningsde cada fichero;summary.files,summary.errors,summary.warningsysummary.messages.
summary.fileses el número de ficheros con hallazgos: cualquiera con al menos un mensaje. Uninfocuenta como hallazgo y como fichero, y no como error ni como aviso.summary.messageses el total de mensajes (lo quelintTotalsllamafindings). Un fichero sin mensajes sale del mapa: no es un hallazgo, aunque declarara errores.createLintArtifactsigue generando las cuentas igual que antes, desde los mensajes, al escribir. -
summary.measuredno se regenera. Es la cobertura global de la pasada, limpios incluidos. No es de una carpeta y no se puede reconstruir desdefiles. Si el artefacto la trae (1.2), se conserva; si no (1.0 y 1.1), sigue faltando. El checksum declarado tampoco se recalcula: es el de quien escribió. -
La ruta canónica es una sola función.
canonicalLintPath, la misma al escribir y al leer, y la que usa el resto (normalizePath):- separadores POSIX (
\pasa a/); - sin
./, sin barras repetidas y sin segmento vacío; - NFC por segmento, como
normalizeNameen la identidad delcc.json; - se rechaza una ruta absoluta y cualquier
..(no se resuelve: salirse del repo es un error); - no se pasa a minúsculas.
Src/Foo.tsysrc/foo.tsson ficheros distintos. La comparación es la de la cadena ya canónica, y distingue mayúsculas.
Al leer, se canoniza la clave de cada fichero. Esa clave es la ruta del mensaje: el mensaje no lleva otra. Una ruta que no se puede canonizar invalida el artefacto. El lector sigue sin lanzar: devuelve
null, como con cualquier forma rota. - separadores POSIX (
-
Una colisión de rutas se avisa y no se funde en silencio. Si dos claves crudas distintas caen en la misma ruta, el resultado lleva
diagnostics:{ kind: "path-collision", path: "src/Café.ts", keys: ["src/Cafe\u0301.ts", "src/Café.ts"] }Los mensajes de todas las claves se conservan, en ese orden, bajo la ruta canónica, sin deduplicar. La cuenta sale de esa lista. No es un fallo: el artefacto se lee.
diagnosticsno es del esquema 1.0–1.2.createLintArtifactno lo escribe: al escribir, la misma ruta es un fichero a propósito (dos herramientas) y los mensajes repetidos se cuentan una vez. Un aviso mal formado se ignora. Uno bien formado se conserva, para queread(serialize(read(x)))sea igual aread(x). -
Quien anuncia el lint usa la agregación compartida. El aviso del live sale de
lintTotals, no delsummarydeclarado.messagesdel aviso es el total de hallazgos, info incluido.measuredno viaja en ese aviso.
Consecuencias
Sección titulada «Consecuencias»- Un artefacto con cuentas que no cuadran enseña, al leer, lo que dicen sus mensajes. La app, el CLI, el MCP y el mapa no dependen del número declarado.
src/Cafe\u0301.tsysrc/Café.tsson el mismo fichero después de leer../src/a.ts,src\\a.tsysrc//a.tstambién.Src/Foo.tsno essrc/foo.ts.- El render y el MCP ven la ruta canónica porque leen el artefacto: el hallazgo cae en el fichero del mapa.
- Una colisión queda a la vista. Los mensajes no se tiran ni se deduplican, así que la cuenta puede ser mayor que si se hubieran fundido al escribir. El aviso dice por qué.
- El resto descarta una ruta que el artefacto rechazaría (absoluta, vacía o
con
..) en vez de tumbar la pasada. El destino de un enlace se canoniza igual, para que un enlace en NFD encuentre el fichero en NFC. - Los artefactos 1.0 y 1.1 se siguen leyendo. El nombre de los ficheros no cambia.
Alternativas descartadas
Sección titulada «Alternativas descartadas»- Subir a 1.3 y quitar las cuentas heredadas. Rompe lo ya escrito. Las cuentas siguen siendo útiles en el fichero; lo que no manda es el número viejo.
- Lanzar si hay colisión. El lector no lanza: un artefacto raro no puede tumbar el mapa. Un aviso en el resultado es aditivo y el artefacto se sigue leyendo.
- Fundir en silencio al leer, como al escribir. Al escribir es el contrato (dos herramientas, un fichero, mensajes deduplicados). Al leer, dos claves distintas son un dato del fichero que hay que ver.
- Pasar las rutas a minúsculas. En un sistema de ficheros que distingue
mayúsculas,
Foo.tsyfoo.tsno son el mismo nodo. - Resolver
.., como hacepathSegmentsdelcc.json. En el lint,..es una ruta que se sale del repo, no un segmento que se deshace. - Regenerar
summary.measured. Los ficheros limpios no están en el artefacto. Inventar la cobertura sería mentir.
Hoja de ruta
Sección titulada «Hoja de ruta»Sin fase nueva: el esquema sigue en 1.2 y se aplica al leer.
- ✅ Las cuentas heredadas se regeneran desde los mensajes.
summary.filesson los ficheros con hallazgos. - ✅
summary.measuredse conserva y queda documentado como cifra global. - ✅ Las rutas se canonizan al leer, con NFC por segmento, y una colisión se
avisa en
diagnostics. - ✅ El live agrega con
lintTotals. El render y el MCP ven la ruta canónica.
El registro de lo hecho vive en el feed de la guía.