#!/usr/bin/env bash
#
# verificar-convenciones-sync.sh
#
# Avisa cuando una convención se pierde entre `CONVENCIONES-CLAUDE.md` (este archivo del
# repo, el que lee el equipo) y lo que de verdad cargan las ventanas de Claude en la Mac
# de Manuel: `~/.claude/CLAUDE.md` más sus skills en `~/.claude/skills/`.
#
# ⚠️ Hasta el 13-ago-2026 esto comparaba los dos archivos con `diff`, porque la convención
# pedía que fueran idénticos. Ese día se midió el costo —el archivo más la memoria eran
# ~38 KB reenviados en CADA petición de CADA ventana— y tres bloques operativos se
# movieron a skills, que se cargan sólo cuando la tarea las toca. Desde entonces los dos
# archivos difieren A PROPÓSITO y un `diff` fallaría siempre.
#
# Lo que NO cambió es el motivo de la regla: que no se pierda una convención sin que nadie
# se entere. Por eso esto no es "confiar", es una comprobación de cobertura: cada sección
# del archivo del repo tiene que seguir existiendo en el CLAUDE.md local o en una skill.
#
# El archivo del repo es la fuente de verdad y NO se recorta nunca — las demás máquinas
# no tienen las skills de Manuel.
#
# Corre en el pre-commit de mbinv, y también a mano.
#
# Salida:
#   0  = todo cubierto, o NO APLICA — y en ese segundo caso lo DICE por stderr,
#        porque un silencio que puede significar las dos cosas no sirve de nada
#   1  = falta al menos una sección (la nombra)
#
# Callar sólo significa una cosa: se comprobó y está todo.
#
set -u

LOCAL="$HOME/.claude/CLAUDE.md"
SKILLS="$HOME/.claude/skills"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
REPO="$SCRIPT_DIR/CONVENCIONES-CLAUDE.md"

# ⚠️ Salir en silencio con 0 cuando NO SE COMPROBÓ NADA es el mismo defecto que este
# guion existe para evitar: quien lo corre no puede distinguir «todo cubierto» de «no
# miré». Es el patrón del canario de estructura que pasó porque no había nada que
# aplicar, y el del `Syntax OK` sobre un vhost mutilado.
#
# Sigue saliendo con 0 —no aplicar no es un error, y el pre-commit no debe frenarse—
# pero LO DICE. Y lo dice por stderr, para no ensuciar la salida de quien lo llame
# esperando silencio en el caso bueno.
if [ ! -f "$LOCAL" ]; then
    echo "ℹ️  No se comprobó nada: no existe $LOCAL." >&2
    echo "   Esta comprobación es de la Mac de Manuel. En otra máquina no aplica." >&2
    exit 0
fi
if [ ! -f "$REPO" ]; then
    echo "ℹ️  No se comprobó nada: no existe $REPO," >&2
    echo "   que es el texto de referencia. ¿El checkout está incompleto?" >&2
    exit 0
fi

SUPERFICIE=$(mktemp) || {
    echo "⚠️  No se comprobó nada: mktemp falló, no hay dónde armar la superficie." >&2
    exit 0
}
trap 'rm -f "$SUPERFICIE"' EXIT
cat "$LOCAL" > "$SUPERFICIE"
# ⚠️ `-L` NO ES UN ADORNO: sin él, `find` NO ENTRA A LOS ENLACES SIMBÓLICOS.
#
# Desde el 28-ago-2026 —PR mb-herramientas#80 y #82— `~/.claude/skills/*` son enlaces
# a `mb-herramientas/claude/skills/*`, que es justo lo que se quería: las skills
# versionadas y la Mac usándolas de una vez. El efecto colateral fue que este guion
# dejó de leer UNA SOLA de ellas, y como las secciones operativas viven ahí desde el
# 13-ago, empezó a gritar «SE PERDIÓ UNA CONVENCIÓN» con diez secciones intactas,
# en cada commit y en todas las ventanas.
#
# Medido el 28-ago-2026, con las skills ya enlazadas:
#
#     find    "$HOME/.claude/skills" -name SKILL.md  ->  0
#     find -L "$HOME/.claude/skills" -name SKILL.md  ->  4
#
# ⚠️ Y lo que hace daño no es la falta: es el falso positivo. Una alarma que suena
# siempre enseña a ignorarla, y el día que se pierda una convención de verdad este
# aviso ya no lo va a leer nadie. El aviso apareció en once transcripciones de
# sesiones distintas y ninguna lo atendió — se leía como ruido de siempre.
SKILLS_LEIDAS=0
if [ -d "$SKILLS" ]; then
    SKILLS_LEIDAS=$(find -L "$SKILLS" -name "SKILL.md" 2>/dev/null | wc -l | tr -d " ")
    find -L "$SKILLS" -name "SKILL.md" -exec cat {} \; >> "$SUPERFICIE" 2>/dev/null
fi

# Y DECIR HASTA DÓNDE SE MIRÓ, que es la otra mitad de la lección: una auditoría
# automática con el alcance mal declarado se ve igual que una completa. Si la carpeta
# de skills existe y no se leyó ninguna, la comparación de abajo va a acusar como
# perdido todo lo que viva en una skill — y eso hay que decirlo ANTES de la lista, no
# dejar que el que la lee lo adivine.
if [ -d "$SKILLS" ] && [ "$SKILLS_LEIDAS" -eq 0 ]; then
    echo "⚠️  OJO: existe $SKILLS y no se leyó NINGUNA skill." >&2
    echo "   Todo lo que viva en una skill va a salir abajo como perdido, y" >&2
    echo "   probablemente no lo esté. Revisar esa carpeta antes de creerle a la lista." >&2
fi

REPO="$REPO" SUPERFICIE="$SUPERFICIE" python3 - <<'PY'
import os, re, sys

repo = open(os.environ["REPO"], encoding="utf-8").read().split("\n")
superficie = open(os.environ["SUPERFICIE"], encoding="utf-8").read()


def normal(s):
    """Quita el formato de markdown y aplasta espacios, para comparar el contenido
    y no la presentación: una regla que se movió a una skill puede quedar con otro
    nivel de encabezado o sin las negritas, y sigue siendo la misma regla."""
    s = re.sub(r"[*`>_#\[\]()]", "", s)
    return re.sub(r"\s+", " ", s).strip().lower()


sup = normal(superficie)

secciones, actual = [], None
for linea in repo:
    if linea.startswith("## "):
        actual = (linea[3:].strip(), [])
        secciones.append(actual)
    elif actual is not None:
        actual[1].append(linea)

# Se comprueba CADA frase larga de cada sección, no una muestra.
#
# ⚠️ La primera versión de esto miraba sólo las 3 frases más largas por sección. La
# prueba negativa la tumbó: se le borraron tres renglones a una regla en medio de una
# sección —el de nombrar los respaldos con fecha— y el chequeo pasó igual, porque las
# 3 frases más largas seguían ahí. Un chequeo que confirma que la sección "existe" no
# es lo mismo que uno que confirma que está completa, y el primero da una falsa
# tranquilidad que es peor que no tener nada.
faltantes = []
for titulo, cuerpo in secciones:
    frases = [normal(l) for l in cuerpo if len(normal(l)) > 45]
    if not frases:
        continue  # sección sin frases largas: no hay nada que comprobar
    perdidas = [f for f in frases if f not in sup]
    if perdidas:
        faltantes.append((titulo, perdidas[0], len(perdidas), len(frases)))

if faltantes:
    print("", file=sys.stderr)
    print("⚠️  SE PERDIÓ UNA CONVENCIÓN", file=sys.stderr)
    print("   Estas secciones de CONVENCIONES-CLAUDE.md no están ni en", file=sys.stderr)
    print("   ~/.claude/CLAUDE.md ni en ninguna skill de ~/.claude/skills/:", file=sys.stderr)
    print("", file=sys.stderr)
    for titulo, frase, cuantas, total in faltantes:
        print(f"     • {titulo}  ({cuantas} de {total} frases)", file=sys.stderr)
        print(f'       falta: "{frase[:80]}..."', file=sys.stderr)
    print("", file=sys.stderr)
    print("   Arreglar: copiar esa sección al CLAUDE.md local, o a la skill que", file=sys.stderr)
    print("   corresponda (trabajo-en-repos / pruebas-y-evidencia / claves-y-credenciales).", file=sys.stderr)
    print("", file=sys.stderr)
    sys.exit(1)

sys.exit(0)
PY
