From ecee0e1d7ba471b1199e2b19a89c18a788896db2 Mon Sep 17 00:00:00 2001 From: sirxavor Date: Sat, 22 Aug 2026 18:28:27 +0200 Subject: [PATCH] =?UTF-8?q?docs(metodo):=20re-estampar=20el=20linter=20?= =?UTF-8?q?=E2=80=94=20D100=20(vocabulario=20de=20ids=20+=20l=C3=ADmite=20?= =?UTF-8?q?por=20la=20derecha)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _IDPFX gana B y M (medusa numera Mn, hermes-sitad Bn: el linter era ciego a los dos corpus enteros) y la extracción de ids gana límite por la derecha: P9b no es P9, E8.a no es E8, R5-bis no es R5, E2E no es un id. El reconocedor pasa a awk, porque el límite no se puede expresar en un grep -oE sin lookahead. Y el censo dice ahora lo que TIRA (sub-ids) y lo que NO ADMITE, en vez de callarlo. Copia vendorizada: aquí NO se edita. Co-Authored-By: Claude Opus 5 (1M context) --- .metodo/CHECKSUMS | 2 +- .metodo/bin/metodo | 80 +++++++++++++++++++++++++++++++++++----------- 2 files changed, 62 insertions(+), 20 deletions(-) diff --git a/.metodo/CHECKSUMS b/.metodo/CHECKSUMS index 6bbfde5..d915639 100644 --- a/.metodo/CHECKSUMS +++ b/.metodo/CHECKSUMS @@ -1,2 +1,2 @@ 140cf44bc4d8e9eff08cd4f6466df22c897efa2c6c6f83662a747e3348a4f2bd metodo.md -d469ed0f661c56e3137953f880f5a7dfc51e0b74e49ae7616ac5d88bf6d91b99 bin/metodo +252f0e98ca4f8e015fceff57493a132f665514e4fd590ef2172dc6e4dfa021ab bin/metodo diff --git a/.metodo/bin/metodo b/.metodo/bin/metodo index cdb418d..4fc8170 100644 --- a/.metodo/bin/metodo +++ b/.metodo/bin/metodo @@ -93,22 +93,53 @@ check_drift() { # fichero —«vistos: 0» fue indistinguible de «no hay» durante dos semanas— y si un fichero # tiene líneas con FORMA de id y 0 parseados, saca nota de posible ceguera. Es el # gate-sin-sujeto aplicado al propio linter. -_IDPFX='(D|A|G|H|C|R|F|E|P)' -# id DEFINIDO = al principio de una fila de tabla (|) o item de lista (-*), con el adorno que -# sea. Así una MENCIÓN inline ("ver D5") no cuenta como definición (evita falsos positivos). -_RE_ID_DEF="^[[:space:]]*[|*_-]+[[:space:]]*[*~\`]*${_IDPFX}[0-9]+" -# LAXO: la MISMA posición de definición, pero con CUALQUIER adorno (hasta 12 caracteres no -# alfanuméricos: casillas `- [ ]`, emoji, comillas…) y —esto es lo que importa— con CUALQUIER -# PREFIJO de letra, no los de `_IDPFX`. Un detector de ceguera construido sobre el vocabulario -# que audita sólo sabe cazar la ceguera de ADORNO, jamás la de PREFIJO: sería el gate-sin-sujeto -# un piso más arriba. Medido el 2026-08-22: con `_IDPFX` no veía los nueve `B` de hermes-sitad. -# Es a propósito más ancho que el estricto y mucho más estrecho que «el id en cualquier sitio de -# la línea» — una mención en mitad de una frase no tiene forma de definición. Sólo se usa cuando -# el estricto ha visto 0: es el detector de «el sujeto está y no lo veo», no un criterio de id. -# La casilla de markdown (`- [ ]` / `- [x]` / `- [~]`) se salta APARTE: lleva un alfanumérico -# dentro y un item CERRADO define tanto como uno abierto. -_RE_ID_LOOSE="^[[:space:]]*[|*_-][[:space:]]*(\[.\])?[^[:alnum:]]{0,12}[A-Z]{1,2}[0-9]+" -_ids_def() { grep -oE "$_RE_ID_DEF" "$1" | grep -oE "${_IDPFX}[0-9]+"; } +# Prefijos de id que reconoce el linter. ⚠️ **`B` y `M` entraron el 2026-08-22 (`D100`)**: medusa +# numera su backlog `M0`…`Mn` y hermes-sitad sus avisos `B1`…`B9`, y el linter era CIEGO a los dos +# corpus ENTEROS — 49 y 9 líneas, 0 vistas, aprobando sobre la nada desde el día uno. Lo cazó la +# nota anti-ceguera de aquí abajo en su primera ejecución sobre corpus reales. +_IDPFX='D|A|B|G|H|C|M|R|F|E|P' + +# ── el reconocedor de ids ──────────────────────────────────────────────────── +# ⚠️ Está en **awk y no en un `grep -oE` encadenado**, y el motivo es el LÍMITE POR LA DERECHA +# (`D100`): no se puede expresar sin lookahead. Se intentó con un segundo `grep -vE` y el filtro +# casaba `D1` + `7` dentro de `D17` ⇒ **declaraba sub-id a todo id de dos cifras**. Aquí el id y su +# cola se miran POR SEPARADO, que es la única forma de que la pregunta sea la que parece. +# +# modo `def` → **id DEFINIDO**: posición de definición (fila de tabla `|` o item de lista `-*`, +# con su adorno: negrita, tachado y comilla invertida) + prefijo de `_IDPFX`. Así +# una MENCIÓN inline («ver D5») no define nada y no da falsos positivos. +# modo `forma` → **¿tiene esta línea FORMA de definición?**, con CUALQUIER prefijo de letra y +# CUALQUIER adorno (casilla `- [ ]`/`- [x]` incluida — un item cerrado define tanto +# como uno abierto). Es a propósito agnóstico de `_IDPFX`: un detector de ceguera +# construido sobre el vocabulario que audita sólo caza la ceguera de ADORNO, jamás +# la de PREFIJO, que es el gate-sin-sujeto un piso más arriba. Sólo se usa cuando +# el estricto ha visto 0. +# +# El LÍMITE POR LA DERECHA vale para los dos: `P9b`/`P9c` NO son `P9`, `E8.a` NO es `E8` y +# `R5-bis` NO es `R5` —son **sub-ids**, no colisiones, y contarlos como el padre fabricaba +# duplicados que no existen—; y `E2E` («end-to-end») no es un id en absoluto. Ninguno de los dos +# casos se puede distinguir del otro por su forma, así que ninguno cuenta como definición. +_id_scan() { # $1 = fichero · $2 = def|forma + awk -v pfx="$_IDPFX" -v modo="$2" ' + { if (modo != "forma") { if (match($0, "^[[:space:]]*[|*_-]+[[:space:]]*[*~`]*") == 0) next } + else { if (match($0, "^[[:space:]]*[|*_-][[:space:]]*([[].[]])?[^[:alnum:]]*") == 0) next } + rest = substr($0, RLENGTH + 1) + re = (modo == "forma") ? "^[A-Z][A-Z]?[0-9]+" : "^(" pfx ")[0-9]+" + if (match(rest, re) == 0) next + id = substr(rest, 1, RLENGTH); cola = substr(rest, RLENGTH + 1) + # ⚠️ El LÍMITE POR LA DERECHA (`D100`) NO se aplica al modo `forma`: ese es el detector de + # «el sujeto está y no lo veo», y un detector que descarta lo mismo que el criterio deja de + # detectar nada. Se midió: con el límite puesto, medusa decía 12 líneas con forma de id + # donde hay 49. Es la MISMA lección que ya obligó a hacerlo agnóstico de prefijo. + if (modo != "forma") { + if (cola ~ /^[[:alnum:]]/ ) { if (modo == "subid") print "1"; next } # P9b / E2E + if (cola ~ /^[.-][[:alnum:]]/ ) { if (modo == "subid") print "1"; next } # E8.a / R5-bis + } + if (modo == "def") print id; else if (modo == "forma") print "1" }' "$1" +} +_ids_def() { _id_scan "$1" def; } +_lineas_forma_id() { _id_scan "$1" forma | grep -c "^1$"; } +_subids() { _id_scan "$1" subid | grep -c "^1$"; } # lo que el LÍMITE tira: se dice, no se calla check_ids() { hdr "colisión de ids en los ficheros de estado (C17)" @@ -116,14 +147,14 @@ check_ids() { # número sea < 100 (un legacy MIGRADO ya tiene dueño que puede cerrarlo). local dec="$ROOT/doc/decisiones.md" nuevos_dec="" [ -f "$dec" ] && nuevos_dec=$(awk '/^### /{ s=$0; gsub(/[*`]/,"",s); split(s,a,"[ \t]+"); if (a[2]!="") print a[2] }' "$dec") - local any=0 f base ids vistos sueltos dups id num rojos legacy + local any=0 f base ids vistos sueltos sub fuera detalle dups id num rojos legacy for f in "$ROOT/doc/AVISOS.md" "$ROOT/doc/backlog.md"; do [ -f "$f" ] || continue any=1; base=$(basename "$f") ids=$(_ids_def "$f") vistos=$(printf '%s\n' "$ids" | grep -c '[^[:space:]]') if [ "$vistos" -eq 0 ]; then - sueltos=$(grep -cE "$_RE_ID_LOOSE" "$f") + sueltos=$(_lineas_forma_id "$f") if [ "${sueltos:-0}" -gt 0 ]; then warn "$base: 0 ids DEFINIDOS parseados pero ${sueltos} línea(s) con forma de id — posible CEGUERA del regex (A6): el verde de este fichero no prueba nada, compruébalo a mano" else @@ -131,7 +162,18 @@ check_ids() { fi continue fi - info "$base: ${vistos} id(s) DEFINIDOS vistos" + # ── el CENSO, y es censo COMPLETO a propósito ──────────────────────────────────────────── + # Dice lo que VE, lo que TIRA (`D100` descarta sub-ids: `P9b` no es `P9`) y lo que NO ADMITE + # (casillas `- [ ]`, prefijos fuera de `_IDPFX`). Un descarte callado es un cap silencioso: + # medusa tiene 7 ids y **49** líneas con forma de id, y sin esta línea el censo diría «7» a + # secas. La nota de ceguera de arriba sólo salta con `vistos == 0` ⇒ la ceguera PARCIAL se + # escaparía entera. + sub=$(_subids "$f"); sueltos=$(_lineas_forma_id "$f") + fuera=$(( sueltos - vistos - sub )); [ "$fuera" -lt 0 ] && fuera=0 + detalle="" + [ "${sub:-0}" -gt 0 ] && detalle="$detalle · ${sub} sub-id(s) descartados (D100)" + [ "$fuera" -gt 0 ] && detalle="$detalle · ${fuera} línea(s) con forma de id que el censo estricto NO admite" + info "$base: ${vistos} id(s) DEFINIDOS vistos${detalle}" rojos=0; legacy=0 dups=$(printf '%s\n' "$ids" | sort | uniq -d) if [ -n "$dups" ]; then