docs(metodo): puntero de enrutado en CLAUDE.md + gate que lo vigila

El Método ya viajaba completo a este repo desde el cutover del 2026-08-19, pero
se midió que en 9 de 18 repos NADA lo enrutaba: la copia llegaba y no la abría
nadie. Es el raíl §8 aplicado al propio Método — un fallo benigno solo protege
si alguien LEE la línea; una constitución solo gobierna si alguien la ABRE.

El puntero va a CLAUDE.md porque es el único fichero que el harness carga solo
en toda sesión sobre el repo; cualquier otro destino reproduce la enfermedad.
Bloque GESTIONADO entre marcadores (<!-- metodo:puntero -->): `metodo update`
sustituye sólo su interior y no toca un byte fuera — probado con diff contra
git en los dos CLAUDE.md ricos, e idempotente (3 updates, mismo md5).

Y su gate: `metodo check` gana `check_pointer` — el bloque existe y su destino
resuelve. Probado EN ROJO en los tres estados (sin CLAUDE.md, sin bloque,
destino inexistente) y con el código de salida moviéndose, que es lo que
separa un gate de un aviso que nadie lee.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
sirxavor 2026-08-19 20:19:14 +02:00
parent e7225bc758
commit db9d69d119
3 changed files with 101 additions and 1 deletions

View File

@ -1,2 +1,2 @@
37cd8e9792f5cad5e57827068fe6cb00ca5602eca7692ef2ca943a48806bc8a8 metodo.md 37cd8e9792f5cad5e57827068fe6cb00ca5602eca7692ef2ca943a48806bc8a8 metodo.md
653e618441c519689753e709df9c8e886db78d84faab4d55167883c5026872a9 bin/metodo 22f810404cd02141177066918e66e98ba208c8529fe5ad92cdd66ccb98873ee5 bin/metodo

View File

@ -152,6 +152,31 @@ check_commits() {
[ "$bad_c" -eq 0 ] && ok "los ${n} commit(s) desde la adopción siguen Conventional" [ "$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 '<!-- metodo:puntero -->' "$cm" || ! grep -qxF '<!-- /metodo:puntero -->' "$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 '/^<!-- metodo:puntero -->$/{f=1;next} /^<!-- \/metodo:puntero -->$/{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) ─────── # ── plantillas de doc/ (sólo se crean si faltan — brownfield: no pisar) ───────
_scaffold_docs() { # $1 = repo destino _scaffold_docs() { # $1 = repo destino
# La casa de docs es UNA y se llama doc/ (el linter ancla ahí el corpus de estado). # La casa de docs es UNA y se llama doc/ (el linter ancla ahí el corpus de estado).
@ -192,6 +217,67 @@ EOF
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='<!-- metodo:puntero -->'
_PTR_CLOSE='<!-- /metodo:puntero -->'
_ptr_block() { # escribe el bloque COMPLETO (marcadores incluidos) por stdout
cat <<'PTREOF'
<!-- metodo:puntero -->
⛔ **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`.*
<!-- /metodo:puntero -->
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 <repo> — vendoriza el Método a un repo por primera vez ──────── # ── metodo init <repo> — vendoriza el Método a un repo por primera vez ────────
cmd_init() { cmd_init() {
local tgt="${1:-.}" local tgt="${1:-.}"
@ -219,6 +305,7 @@ Etiquetas: [NUEVA-REGLA] · [REFINA Cn] · [Cn-NO-ENCAJA] · [FALSA]. Se cosecha
--- ---
EOF EOF
_scaffold_docs "$tgt" _scaffold_docs "$tgt"
_stamp_pointer "$tgt"
echo "metodo init: $tgt inicializado — .metodo/ (máster $mver) + doc/ de tres capas." 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 .'" 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" [ -n "$adopt" ] && echo "adopted_commit: $adopt"; } > "$md/VERSION"
_write_checksums "$md" || echo " (aviso: sin sha256 → CHECKSUMS no escrito)" _write_checksums "$md" || echo " (aviso: sin sha256 → CHECKSUMS no escrito)"
printf '* text eol=lf\n' > "$md/.gitattributes" 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." 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_headings
check_links check_links
check_commits check_commits
check_pointer
hdr "resultado" hdr "resultado"
if [ "$FAILS" -eq 0 ]; then if [ "$FAILS" -eq 0 ]; then
printf ' \033[32mPASA el Método\033[0m (0 avisos)\n'; exit 0 printf ' \033[32mPASA el Método\033[0m (0 avisos)\n'; exit 0

11
CLAUDE.md Normal file
View File

@ -0,0 +1,11 @@
# smtp-relay
<!-- metodo:puntero -->
**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`.*
<!-- /metodo:puntero -->