Hasta ahora el hreflang era un estado —puesto una vez a mano— y no una
regla. Un slug que cambiara habría dejado el enlace apuntando al artículo
viejo sin que nada avisara.
El auditor gana dos reglas: hreflang.missing (pareja declarada sin bloque)
y hreflang.stale (bloque que ya no coincide con la pareja). Viven en
seo_hreflang y no en seo_rules porque necesitan hreflang-parejas.md, que es
una decisión editorial curada; el motor sigue sin saber que hay parejas.
Probadas en los cinco estados, no solo en el feliz: dispara sin bloque,
dispara con el bloque apuntando a un slug viejo, y se calla cuando está
correcto, cuando el post no es de ninguna pareja y cuando aún es borrador.
Y el remate gana el paso 3c: buscar el espejo en el otro idioma y dejarlo
en el informe. Con la advertencia explícita de decidir por TÍTULO y no por
vector, y las dos reglas duras — solo espejos publicados, y uno a uno.
El blog ES nunca se había auditado entero: seo_audit era EN-only aunque
seo_exceptions ya estuviera indexado por sitio desde el principio. Ahora
acepta --site es. Resultado del estreno: 40 de 40 limpios.
Para que eso signifique algo hubo que arreglar SITE_HOST, que era una
constante con el dominio del EN y decide qué href cuenta como enlace
interno. Auditando el ES con el host del EN salían 21 posts marcados por
enlaces internos; con el host correcto, cero. Un informe entero de
hallazgos falsos que nadie habría podido arreglar.
Y `ghst post list --status published` NO filtra: devuelve published Y
scheduled. El encabezado llevaba desde siempre diciendo "42 published
posts" cuando eran 33 publicados y 9 programados. Se pide sin filtro y se
separa en el código, que además deja explícito que los programados también
se auditan — que está bien, pero hay que decirlo.
Versioning ~/seo-tools as critical SEO infrastructure (was home-dir only).
Canonical home of seo_rules.py — ResearchOwl will vendor a copy with a CI diff
guard against this repo. No secrets; Ghost access is via the ghst-en wrapper.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>