diff --git a/.metodo/CHECKSUMS b/.metodo/CHECKSUMS index f7ce312..b17a909 100644 --- a/.metodo/CHECKSUMS +++ b/.metodo/CHECKSUMS @@ -1,2 +1,2 @@ 37cd8e9792f5cad5e57827068fe6cb00ca5602eca7692ef2ca943a48806bc8a8 metodo.md -653e618441c519689753e709df9c8e886db78d84faab4d55167883c5026872a9 bin/metodo +22f810404cd02141177066918e66e98ba208c8529fe5ad92cdd66ccb98873ee5 bin/metodo diff --git a/.metodo/bin/metodo b/.metodo/bin/metodo index 8857129..17dcaa2 100644 --- a/.metodo/bin/metodo +++ b/.metodo/bin/metodo @@ -152,6 +152,31 @@ check_commits() { [ "$bad_c" -eq 0 ] && ok "los ${n} commit(s) desde la adopción siguen Conventional" } +# ── Check 6 · el puntero de enrutado existe y RESUELVE (raíl 8) ────────────── +# Un puntero sin gate se pudre: es la línea que nadie lee de mañana. Y no basta con que el bloque +# esté — su DESTINO tiene que resolver, porque un puntero a la nada enruta a la nada (y encima con +# la conciencia tranquila de haberlo puesto). Sabe ponerse rojo de tres formas distintas: +# sin CLAUDE.md · con CLAUDE.md pero sin bloque · con bloque cuyo destino no existe. +check_pointer() { + hdr "puntero de enrutado en CLAUDE.md (raíl 8)" + [ -d "$ROOT/.metodo" ] || { skip "no hay .metodo/ — repo sin el Método vendorizado"; return; } + local cm="$ROOT/CLAUDE.md" + [ -f "$cm" ] || { bad "no hay CLAUDE.md — el Método VIAJA pero nada lo enruta (raíl 8); 'metodo update' desde la matriz"; return; } + if ! grep -qxF '' "$cm" || ! grep -qxF '' "$cm"; then + bad "CLAUDE.md sin el bloque 'metodo:puntero' — el Método viaja y nada lo abre; 'metodo update'"; return + fi + ok "CLAUDE.md lleva el bloque del puntero" + local tgt found=0 broken=0 l + while IFS= read -r l; do + tgt="${l%%#*}"; [ -z "$tgt" ] && continue + found=$((found+1)) + [ -e "$ROOT/$tgt" ] || { bad "el puntero apunta a algo que NO EXISTE: $tgt"; broken=$((broken+1)); } + done < <(awk '/^$/{f=1;next} /^$/{f=0} f' "$cm" \ + | grep -oE '\]\([^)]+\)' | sed -E 's/^\]\(//; s/\)$//') + [ "$found" -eq 0 ] && { bad "el bloque del puntero no lleva NINGÚN enlace — no enruta a nada"; return; } + [ "$broken" -eq 0 ] && ok "el destino del puntero resuelve ($found enlace(s) comprobado(s))" +} + # ── plantillas de doc/ (sólo se crean si faltan — brownfield: no pisar) ─────── _scaffold_docs() { # $1 = repo destino # La casa de docs es UNA y se llama doc/ (el linter ancla ahí el corpus de estado). @@ -192,6 +217,67 @@ EOF EOF } +# ── el PUNTERO de enrutado (bloque gestionado en CLAUDE.md) ────────────────── +# Por qué en CLAUDE.md y sólo ahí: es el ÚNICO fichero que el harness carga solo en toda sesión +# sobre el repo. Un puntero en cualquier otro sitio reproduce la enfermedad que viene a curar +# ("protege si alguien lo abre") — medido el 2026-08-19: la copia viajaba a 9 de 18 repos sin que +# nada la abriera. El bloque va entre marcadores y `update` sustituye SÓLO su interior: los +# CLAUDE.md ricos son del repo, no nuestros, y no se toca un byte fuera. +_PTR_OPEN='' +_PTR_CLOSE='' + +_ptr_block() { # escribe el bloque COMPLETO (marcadores incluidos) por stdout + cat <<'PTREOF' + +⛔ **Antes de tocar nada, lee el Método: [`.metodo/metodo.md`](.metodo/metodo.md)** — los raíles +transversales, completos y con sus cicatrices. **Viaja con este repo y aquí NO se edita**: su fuente +es el repo `metodo` (la matriz), que lo re-estampa con `metodo update` — este bloque incluido. +⇒ Del corpus de este repo, **`doc/AVISOS.md` se lee ANTES que el backlog**. El gate de todo esto es +**`metodo check .`** — y si sale rojo, no lo apagues: arréglalo o dilo. +⇒ *Existe porque el 2026-08-19 se midió que el Método viajaba a 9 de 18 repos **sin que nada lo +abriera**. Una constitución solo gobierna si alguien la lee: es su propio raíl `§8`.* + +PTREOF +} + +_stamp_pointer() { # $1 = repo destino + local tgt="$1" cm="$1/CLAUDE.md" blk tmp + blk=$(mktemp); _ptr_block > "$blk" + tmp=$(mktemp) + if [ ! -f "$cm" ]; then + # No hay CLAUDE.md: se crea MÍNIMO (título + bloque y nada más). Lo que la matriz no puede + # mantener no se siembra: se pudriría (raíl 7). + { printf '# %s\n\n' "$(basename "$(cd "$tgt" && pwd)")"; cat "$blk"; } > "$cm" + rm -f "$blk" "$tmp"; echo " CLAUDE.md creado (mínimo) con el puntero"; return + fi + if grep -qxF "$_PTR_OPEN" "$cm" && grep -qxF "$_PTR_CLOSE" "$cm"; then + # Ya está: se sustituye SÓLO el interior de los marcadores (idempotente). + awk -v blk="$blk" -v o="$_PTR_OPEN" -v c="$_PTR_CLOSE" ' + $0 == o { inb=1; while ((getline l < blk) > 0) print l; close(blk); next } + $0 == c { inb=0; next } + inb { next } + { print }' "$cm" > "$tmp" && mv "$tmp" "$cm" + rm -f "$blk"; echo " CLAUDE.md: puntero re-estampado (sólo el interior de los marcadores)"; return + fi + # Existe pero sin bloque: se inserta ANTES del primer `## `; si no hay, tras el H1; si no, arriba. + awk -v blk="$blk" ' + function put( l){ while ((getline l < blk) > 0) print l; close(blk); print ""; done=1 } + !done && /^## / { put() } + { print } + NR==1 && $0 ~ /^# / { h1=1 } + END { if (!done) exit 9 }' "$cm" > "$tmp" + if [ $? -eq 9 ]; then + # sin ningún `## `: tras el H1 (o al principio si no hay H1) + awk -v blk="$blk" ' + function put( l){ print ""; while ((getline l < blk) > 0) print l; done=1 } + NR==1 { print; if ($0 ~ /^# /) put(); else { print ""; } ; next } + NR==2 && !done { put(); print; next } + { print }' "$cm" > "$tmp" + fi + mv "$tmp" "$cm"; rm -f "$blk" + echo " CLAUDE.md: puntero insertado antes del primer encabezado" +} + # ── metodo init — vendoriza el Método a un repo por primera vez ──────── cmd_init() { local tgt="${1:-.}" @@ -219,6 +305,7 @@ Etiquetas: [NUEVA-REGLA] · [REFINA Cn] · [Cn-NO-ENCAJA] · [FALSA]. Se cosecha --- EOF _scaffold_docs "$tgt" + _stamp_pointer "$tgt" echo "metodo init: $tgt inicializado — .metodo/ (máster $mver) + doc/ de tres capas." echo " siguiente: siembra doc/backlog.md con el trabajo abierto de hoy y corre 'metodo check .'" } @@ -239,6 +326,7 @@ cmd_update() { [ -n "$adopt" ] && echo "adopted_commit: $adopt"; } > "$md/VERSION" _write_checksums "$md" || echo " (aviso: sin sha256 → CHECKSUMS no escrito)" printf '* text eol=lf\n' > "$md/.gitattributes" + _stamp_pointer "$tgt" echo "metodo update: $tgt actualizado al máster $mver (metodo.md + linter, byte a byte); realimentacion.md y adopted_commit preservados." } @@ -249,6 +337,7 @@ cmd_check() { check_headings check_links check_commits + check_pointer hdr "resultado" if [ "$FAILS" -eq 0 ]; then printf ' \033[32mPASA el Método\033[0m (0 avisos)\n'; exit 0 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b5e4b70 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,11 @@ +# smtp-relay + + +⛔ **Antes de tocar nada, lee el Método: [`.metodo/metodo.md`](.metodo/metodo.md)** — los raíles +transversales, completos y con sus cicatrices. **Viaja con este repo y aquí NO se edita**: su fuente +es el repo `metodo` (la matriz), que lo re-estampa con `metodo update` — este bloque incluido. +⇒ Del corpus de este repo, **`doc/AVISOS.md` se lee ANTES que el backlog**. El gate de todo esto es +**`metodo check .`** — y si sale rojo, no lo apagues: arréglalo o dilo. +⇒ *Existe porque el 2026-08-19 se midió que el Método viajaba a 9 de 18 repos **sin que nada lo +abriera**. Una constitución solo gobierna si alguien la lee: es su propio raíl `§8`.* +