diff --git a/.metodo/CHECKSUMS b/.metodo/CHECKSUMS index 58d5b7a..6bbfde5 100644 --- a/.metodo/CHECKSUMS +++ b/.metodo/CHECKSUMS @@ -1,2 +1,2 @@ 140cf44bc4d8e9eff08cd4f6466df22c897efa2c6c6f83662a747e3348a4f2bd metodo.md -bf2144db0bb987bd32ce7eb8d99021ca0d29cb7b6a48d30a8ffd0fe723ca2a3f bin/metodo +d469ed0f661c56e3137953f880f5a7dfc51e0b74e49ae7616ac5d88bf6d91b99 bin/metodo diff --git a/.metodo/bin/metodo b/.metodo/bin/metodo index 96f9813..cdb418d 100644 --- a/.metodo/bin/metodo +++ b/.metodo/bin/metodo @@ -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)" + [ -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 + bad "id duplicado en $base: $id — renombra o fusiona (C17)"; rojos=$((rojos+1)) + fi done <<< "$dups" - else - ok "$(basename "$f"): sin ids duplicados" 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:-.}" ;;