docs(metodo): re-estampar el linter — A6 (check_ids ve, y parte el veredicto) + A7 (palabra == rc)

check_ids admite la comilla invertida (veía 0 ids donde hay 130/118), parte el
veredicto en rojo-de-esquema-nuevo vs nota-con-conteo-legacy, y DICE cuántos ids
vio por fichero (anti-ceguera, con detector agnóstico de prefijo). El veredicto
final deriva palabra y rc del mismo número y IMPRIME el rc al lado de la palabra,
para que una tubería no se lo coma. Copia vendorizada: aquí NO se edita.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
sirxavor 2026-08-22 16:08:52 +02:00
parent a901586bab
commit d2df969153
2 changed files with 90 additions and 19 deletions

View File

@ -1,2 +1,2 @@
140cf44bc4d8e9eff08cd4f6466df22c897efa2c6c6f83662a747e3348a4f2bd metodo.md
bf2144db0bb987bd32ce7eb8d99021ca0d29cb7b6a48d30a8ffd0fe723ca2a3f bin/metodo
d469ed0f661c56e3137953f880f5a7dfc51e0b74e49ae7616ac5d88bf6d91b99 bin/metodo

View File

@ -35,12 +35,13 @@ _write_checksums() { # $1 = dir .metodo del destino
}
_is_matriz() { [ -f "$SELF_ROOT/constitucion-de-facto.md" ]; } # marca inequívoca de la matriz
# --- veredictos (los cinco colores del check) ---
# --- veredictos (los cinco colores) + 'info', que es CENSO y no veredicto ---
hdr() { printf '\n\033[1m== %s ==\033[0m\n' "$*"; }
ok() { printf ' \033[32mok \033[0m %s\n' "$*"; }
bad() { printf ' \033[31mAVISO\033[0m %s\n' "$*"; FAILS=$((FAILS+1)); }
skip() { printf ' \033[33mSKIP\033[0m %s\n' "$*"; }
warn() { printf ' \033[33mnota\033[0m %s\n' "$*"; WARNS=$((WARNS+1)); }
info() { printf ' \033[36minfo\033[0m %s\n' "$*"; } # censo, NO veredicto: no mueve ningún contador
# ── Check 1 · integridad de la copia vendorizada (C11) ───────────────────────
# El máster vive en OTRO repo (la matriz), así que el testigo no es el máster sino el
@ -72,26 +73,85 @@ check_drift() {
}
# ── Check 2 · colisión de ids en los ficheros de estado (C17) ────────────────
# ⚠️ Este check estuvo CIEGO del 2026-08-08 al 2026-08-22 (`A6`): su regex de «id DEFINIDO» no
# admitía la COMILLA INVERTIDA, y los dos corpus más grandes del árbol escriben así sus
# decisiones — ``- **`D1` (técnica)** — …``. Veía **0** ids donde hay **130** y **118**, y con
# ellos **46 duplicados que nunca reportó**, incluidas las 18 `D1` que son la cicatriz de `D6`.
# El arreglo tiene TRES piezas, y la tercera es la que importa:
# (1) La vista se ENSANCHA: se admite cualquier adorno de markdown antes del id (`*` negrita,
# `~` tachado y la comilla invertida que faltaba).
# (2) El veredicto se PARTE, porque no todo duplicado es cerrable y un gate incerrable acaba
# apagado:
# · esquema NUEVO (nº ≥ 100, o id definido en doc/decisiones.md) → ROJO que mueve el rc.
# Es real y CERRABLE: el contador del repo es único y monótono, no debe pasar jamás.
# · legacy en backlog.md → UNA nota con el CONTEO. Renumerar está PROHIBIDO (C17) y la
# migración conserva el id como `fase·Dn` ⇒ una lista por-id sería una alarma que su
# dueño no puede cerrar. Es un MEDIDOR que drena a 0 con cada migración (D6).
# · AVISOS.md no entra en el reparto: sus ids son un contador único del repo, ahí un
# duplicado SÍ se cierra fusionando, y sigue en ROJO como hasta hoy.
# (3) ANTI-CEGUERA, que es la causa raíz y no el regex: el check DICE cuántos ids VIO por
# 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]+"; }
check_ids() {
hdr "colisión de ids en los ficheros de estado (C17)"
local statefiles=( "$ROOT/doc/AVISOS.md" "$ROOT/doc/backlog.md" )
local any=0 f dups id
for f in "${statefiles[@]}"; do
# Ids del esquema NUEVO que ya viven en la cuarta capa: un duplicado suyo es rojo aunque su
# 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
for f in "$ROOT/doc/AVISOS.md" "$ROOT/doc/backlog.md"; do
[ -f "$f" ] || continue
any=1
# id DEFINIDO = al principio de una fila de tabla (|) o item de lista (-*) en negrita.
# Así una MENCIÓN inline ("ver D5") no cuenta como definición (evita falsos positivos).
dups=$(grep -oE '^[[:space:]]*[|*_-]+[[:space:]]*\**~*\**(D|A|G|H|C|R|F|E|P)[0-9]+' "$f" \
| grep -oE '(D|A|G|H|C|R|F|E|P)[0-9]+' | sort | uniq -d)
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")
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
info "$base: 0 ids definidos y 0 líneas con forma de id — no hay sujeto que medir"
fi
continue
fi
info "$base: ${vistos} id(s) DEFINIDOS vistos"
rojos=0; legacy=0
dups=$(printf '%s\n' "$ids" | sort | uniq -d)
if [ -n "$dups" ]; then
while IFS= read -r id; do
[ -n "$id" ] && bad "id duplicado en $(basename "$f"): $id — renombra o fusiona (C17)"
done <<< "$dups"
[ -n "$id" ] || continue
num=${id#[A-Z]}
if { [ "${num:-0}" -ge 100 ] 2>/dev/null; } || printf '%s\n' "$nuevos_dec" | grep -qxF "$id"; then
bad "id duplicado en $base: $id — id de ESQUEMA NUEVO: citar uno gobierna con el otro (C17); renumera el nuevo desde el contador del repo"; rojos=$((rojos+1))
elif [ "$base" = "backlog.md" ]; then
legacy=$((legacy+1))
else
ok "$(basename "$f"): sin ids duplicados"
bad "id duplicado en $base: $id — renombra o fusiona (C17)"; rojos=$((rojos+1))
fi
done <<< "$dups"
fi
[ "$legacy" -gt 0 ] && warn "$base: ${legacy} id(s) legacy duplicados (de ${vistos} vistos) — se resuelven MIGRANDO a doc/decisiones.md y citando \`fase·Dn\`, NUNCA renumerando (C17 · D6); es un medidor que drena, no una alarma"
[ "$rojos" -eq 0 ] && [ "$legacy" -eq 0 ] && ok "$base: sin ids duplicados"
done
[ "$any" -eq 0 ] && skip "sin doc/AVISOS.md ni doc/backlog.md (repo sin corpus de estado)"
return 0
}
# ── Check 3 · encabezados sin marca de estado (C16) ──────────────────────────
@ -459,6 +519,13 @@ cmd_update() {
echo "metodo update: $tgt actualizado al máster $mver (metodo.md + linter, byte a byte); realimentacion.md y adopted_commit preservados."
}
# El veredicto final (`A7`, 2026-08-22). Regla: **la palabra y el `rc` se derivan del MISMO
# número**, así que no hay camino por el que puedan discrepar. Y el `rc` se IMPRIME al lado de
# la palabra, porque la única forma medida de ver «NO pasa» con rc=0 es leer esta salida por
# una TUBERÍA (`… | tail`), que se come el exit code del script y deja el texto como único
# testigo. Un testigo que no sobrevive a cómo se le lee no es un testigo.
# Vocabulario, que es la mitad del arreglo: `AVISO` GATEA (mueve el rc) · `nota` es
# informativa y NO gatea · `info` es censo · `SKIP` es fallo benigno · `ok` es verde.
cmd_check() {
printf 'metodo check — %s\n' "$ROOT"
check_drift
@ -470,15 +537,19 @@ cmd_check() {
check_decisiones_en_backlog
check_pointer
hdr "resultado"
local nota=""
[ "$WARNS" -gt 0 ] && nota=$(printf ' · %d nota(s) — no mueven el código de salida, pero se cuentan' "$WARNS")
if [ "$FAILS" -eq 0 ]; then
printf ' \033[32mPASA el Método\033[0m (0 avisos%s)\n' "$nota"; exit 0
local rc=0
[ "$FAILS" -gt 0 ] && rc=1
if [ "$rc" -ne 0 ]; then
printf ' \033[31mNO pasa el Método — %d aviso(s) que GATEAN\033[0m (y %d nota(s) que no) · rc=%d\n' \
"$FAILS" "$WARNS" "$rc"
elif [ "$WARNS" -gt 0 ]; then
printf ' \033[32mPASA el Método\033[0m con %d nota(s) informativa(s) — 0 avisos que gateen; las notas se cuentan y NO mueven el rc · rc=%d\n' \
"$WARNS" "$rc"
else
printf ' \033[31m%d aviso(s) — el Método NO pasa\033[0m%s\n' "$FAILS" "$nota"; exit 1
printf ' \033[32mPASA el Método\033[0m (0 avisos, 0 notas) · rc=%d\n' "$rc"
fi
exit "$rc"
}
case "${1:-}" in
check) ROOT="${2:-.}"; cmd_check ;;
init) cmd_init "${2:-.}" ;;