[DTP-Worker] Sprint 8 · buffer · SEO/projets_master : IDENTITÉ du CATALOGUE PROJETS (P01..P09) ANCRÉE sur CLAUDE.md §Projets
La matière première du worker SEO est `05_deliverables_mvp/seo/fixtures/projets_master.json` — les 9 projets (`code`+`nom`) que le bundle expose à Google (un listing `Residence`/projet dans le JSON-LD indexé sous `vente.otov7.com/projets/…`). Chaque entrée DÉCLARE `source.fichiers: ["CLAUDE.md#Projets"]` et le README:80 l'affirme (« 9 projets P01..P09 · noms sourcés de `CLAUDE.md §Projets` }) MAIS AUCUN gate ne le VÉRIFIAIT. check_artifacts prouve QUE les out/ SE RECONSTRUISENT depuis la fixture (byte-for-byte), PAS que ses code/nom == CLAUDE.md §Projets ; les blocs SEO amont gatent les COMPTES de mots-clés + la COMPOSITION du graphe schema.org (bijection un-listing/projet) + l'IDENTITÉ de la marque racine `Helios RD` (§Entités), tous AVEUGLES à l'identité de la LISTE de projets. Piège #6 : RENOMMER un projet dans CLAUDE.md §Projets (« Aqua Terra Las Terrenas »→« … Bay »), le RENOMMER/AJOUTER/RETIRER dans la fixture, puis régénérer, laisse le JSON-LD PUBLIC lister un projet FANTÔME ou en OMETTRE un canonique pendant que la byte-gate reste VERTE (l'artefact se reconstruit fidèlement… depuis une fixture DÉRIVÉE) et que le compte reste 9 — « vert trompeur » de la MÊME classe que la marque org (§Entités), les entités CONFOTUR (§Entités) et les libellés projets du run_sheet démo (§Projets). Aucune suite tests/ (FONCTIONS de génération, jamais l'ancrage à CLAUDE.md) ne l'attrape. Gate ajouté (bloc « SEO · l'IDENTITÉ du CATALOGUE PROJETS ») : RE-DÉRIVE le catalogue de CLAUDE.md §Projets (zéro duplication · source unique) puis exige — (a) identité d'ensemble des codes fixture ⇔ CLAUDE.md (aucun fantôme/manquant · pas de doublon) ; (b) `nom` de chaque entrée == CLAUDE.md §Projets (accent-INSENSIBLE · la fixture normalise « Xamaná »→« Xamana ») ; (c) chaque entrée DÉCLARE l'ancrage `CLAUDE.md#Projets` dans source.fichiers ; (d) les `name` des listings du graphe schema.org PUBLIC == l'ensemble des noms CLAUDE.md §Projets + bijection (1 listing/projet) + MANIFEST counts projects/listings == |§Projets| ; (e) le README cite l'ancrage §Projets. Un claim absent échoue AUSSI (disparition = régression). 13 confirmations vertes ; 7 morsures vérifiées puis restaurées (CLAUDE.md rename P07 = bites b+d simultanés l'ancre est vive · fixture nom drift P03 · fixture drop P09 · fixture add fantôme P10 · fixture strip source.fichiers P01 · artefact schema.org rename Structure→Estructura = le vrai silent-green public · README retire la citation §Projets), restauré vert, 7 gates CI re-verts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2666,6 +2666,121 @@ if brand is not None and sog is not None:
|
||||
else:
|
||||
good(f"SEO org · seo_spec.json déclare l'ancrage CLAUDE.md §Entités pour {brand!r}")
|
||||
|
||||
# ============================================================================
|
||||
# SEO · l'IDENTITÉ du CATALOGUE PROJETS (P01..P09) — les 9 projets (`code` +
|
||||
# `nom`) que le bundle SEO expose à Google — ANCRÉE sur CLAUDE.md §Projets.
|
||||
# ----------------------------------------------------------------------------
|
||||
# La matière première du worker SEO est `seo/fixtures/projets_master.json`
|
||||
# (README:22 « sortie canonique … le worker ne fabrique aucun fait de projet »).
|
||||
# Chaque entrée DÉCLARE `source.fichiers: ["CLAUDE.md#Projets"]` et le README:80
|
||||
# l'affirme (« 9 projets P01..P09 · noms sourcés de `CLAUDE.md §Projets` ») — MAIS
|
||||
# AUCUN gate ne le VÉRIFIAIT. check_artifacts prouve QUE les out/ SE RECONSTRUISENT
|
||||
# depuis la fixture (byte-for-byte), PAS que ses `code`/`nom` == CLAUDE.md §Projets ;
|
||||
# les blocs SEO amont gatent les COMPTES de mots-clés + la COMPOSITION du graphe
|
||||
# schema.org (bijection un-listing-par-projet) + l'IDENTITÉ de la marque racine
|
||||
# (§Entités), tous AVEUGLES à l'identité de la LISTE de projets. Piège #6 :
|
||||
# RENOMMER un projet dans CLAUDE.md §Projets (« Aqua Terra Las Terrenas » → « … Bay »),
|
||||
# le RENOMMER/AJOUTER/RETIRER dans la fixture, puis régénérer, laisse le JSON-LD PUBLIC
|
||||
# — celui que Google indexe sous `vente.otov7.com/projets/…` — lister un projet
|
||||
# FANTÔME ou en OMETTRE un canonique pendant que la byte-gate reste VERTE (l'artefact
|
||||
# se reconstruit fidèlement… depuis une fixture DÉRIVÉE) et que le compte reste 9 —
|
||||
# « vert trompeur » de la MÊME classe que la marque org (§Entités), les entités
|
||||
# CONFOTUR (§Entités) et les libellés projets du run_sheet démo (§Projets). Aucune
|
||||
# suite tests/ (FONCTIONS de génération, jamais l'ancrage à CLAUDE.md) ne l'attrape.
|
||||
# On RE-DÉRIVE le catalogue de CLAUDE.md §Projets (zéro duplication · source unique)
|
||||
# puis on exige : (a) identité d'ensemble des codes fixture ⇔ CLAUDE.md (aucun
|
||||
# fantôme/manquant · pas de doublon) ; (b) `nom` de chaque entrée == CLAUDE.md §Projets
|
||||
# (accent-insensible · la fixture normalise « Xamaná »→« Xamana ») ; (c) chaque entrée
|
||||
# DÉCLARE l'ancrage `CLAUDE.md#Projets` dans `source.fichiers` ; (d) les `name` des
|
||||
# listings du graphe schema.org PUBLIC == l'ensemble des noms CLAUDE.md §Projets +
|
||||
# bijection (1 listing/projet) + MANIFEST counts projects/listings == |§Projets| ;
|
||||
# (e) le README cite l'ancrage CLAUDE.md §Projets. Un claim absent échoue AUSSI
|
||||
# (disparition = régression). État courant : aucune valeur périmée.
|
||||
# ============================================================================
|
||||
_pnfc = lambda s: unicodedata.normalize("NFC", str(s))
|
||||
_pai = lambda s: "".join(c for c in unicodedata.normalize("NFKD", str(s))
|
||||
if unicodedata.category(c) != "Mn").lower().replace(" ", "")
|
||||
try:
|
||||
cm_pj = open("CLAUDE.md", encoding="utf-8").read()
|
||||
_mpj = re.search(r"^## Projets\s*\n(.+)$", cm_pj, re.M)
|
||||
if _mpj is None:
|
||||
raise ValueError("section « ## Projets » INTROUVABLE")
|
||||
seo_pmap = {}
|
||||
for _t in _mpj.group(1).split("·"):
|
||||
_mc = re.match(r"(P\d{2})\s+(.+)$", _pnfc(_t.strip()))
|
||||
if _mc:
|
||||
seo_pmap[_mc.group(1)] = _mc.group(2).strip()
|
||||
if not seo_pmap:
|
||||
raise ValueError("aucun projet PNN parsé")
|
||||
except (OSError, ValueError) as e:
|
||||
bad(f"SEO projets · CLAUDE.md §Projets illisible/inattendu : {e}"); seo_pmap = None
|
||||
try:
|
||||
with open(f"{D}/seo/fixtures/projets_master.json", encoding="utf-8") as _f:
|
||||
seo_pm = json.load(_f)["projets"]
|
||||
sg_pj = load("seo/out/seo_schema_org.json")["@graph"]
|
||||
seo_cpj = load("seo/out/MANIFEST.json")["counts"]
|
||||
lst_pj = [n for n in sg_pj if not str(n.get("@id", "")).endswith("#organization")]
|
||||
except (OSError, KeyError, ValueError, TypeError) as e:
|
||||
bad(f"SEO projets · fixture/artefact illisible/inattendu : {e}"); seo_pm = None
|
||||
if seo_pmap is not None and seo_pm is not None:
|
||||
# (a) ANCRAGE : identité d'ensemble des codes fixture ⇔ CLAUDE.md §Projets
|
||||
fix_codes = [p.get("code") for p in seo_pm]
|
||||
if len(set(fix_codes)) != len(fix_codes) or None in fix_codes or "" in fix_codes:
|
||||
bad(f"SEO projets · projets_master : codes en doublon/vides {fix_codes}")
|
||||
fx = {c for c in fix_codes if c}
|
||||
miss = sorted(set(seo_pmap) - fx); extra = sorted(fx - set(seo_pmap))
|
||||
if miss or extra:
|
||||
bad(f"SEO projets · codes projets_master ≠ CLAUDE.md §Projets"
|
||||
f"{' · manquants=' + str(miss) if miss else ''}"
|
||||
f"{' · fantômes=' + str(extra) if extra else ''}")
|
||||
else:
|
||||
good(f"SEO projets · {len(fx)} codes P01..P09 projets_master == CLAUDE.md §Projets")
|
||||
# (b)+(c) nom ancré + déclaration de l'ancrage, par projet
|
||||
for p in seo_pm:
|
||||
code = p.get("code"); nom = _pnfc(p.get("nom", ""))
|
||||
anchor = seo_pmap.get(code)
|
||||
if anchor is None:
|
||||
continue # code fantôme : déjà mordu par le set-diff (a)
|
||||
if _pai(nom) != _pai(anchor):
|
||||
bad(f"SEO projets · {code} nom « {nom} » (projets_master) DÉRIVE de "
|
||||
f"CLAUDE.md §Projets « {anchor} » → aligner (ou CLAUDE.md)")
|
||||
else:
|
||||
good(f"SEO projets · {code} — nom « {nom} » == CLAUDE.md §Projets")
|
||||
srcf = (p.get("source") or {}).get("fichiers") or []
|
||||
if not any("CLAUDE.md" in str(x) and "Projets" in str(x) for x in srcf):
|
||||
bad(f"SEO projets · {code} — projets_master ne DÉCLARE pas l'ancrage "
|
||||
f"`CLAUDE.md#Projets` dans source.fichiers : {srcf}")
|
||||
# (d) artefact PUBLIC : `name` des listings schema.org == noms CLAUDE.md §Projets
|
||||
if not lst_pj:
|
||||
bad("SEO projets · aucun nœud listing schema.org → graphe suspect")
|
||||
else:
|
||||
got_n = {_pai(n.get("name", "")) for n in lst_pj}
|
||||
want_n = {_pai(v) for v in seo_pmap.values()}
|
||||
gm = sorted(want_n - got_n); ge = sorted(got_n - want_n)
|
||||
if gm or ge:
|
||||
bad(f"SEO projets · `name` des listings schema.org (JSON-LD public) ≠ "
|
||||
f"CLAUDE.md §Projets{' · manquants=' + str(gm) if gm else ''}"
|
||||
f"{' · en trop=' + str(ge) if ge else ''}")
|
||||
elif len(lst_pj) != len(seo_pmap):
|
||||
bad(f"SEO projets · {len(lst_pj)} listing(s) schema.org MAIS "
|
||||
f"{len(seo_pmap)} projet(s) CLAUDE.md §Projets (bijection cassée)")
|
||||
else:
|
||||
good(f"SEO projets · {len(lst_pj)} listings schema.org (name) == CLAUDE.md "
|
||||
f"§Projets (JSON-LD indexé fidèle au mandat)")
|
||||
if seo_cpj.get("projects") != len(seo_pmap) or seo_cpj.get("listings") != len(seo_pmap):
|
||||
bad(f"SEO projets · MANIFEST counts projects={seo_cpj.get('projects')}/"
|
||||
f"listings={seo_cpj.get('listings')} ≠ {len(seo_pmap)} (CLAUDE.md §Projets)")
|
||||
else:
|
||||
good(f"SEO projets · MANIFEST counts projects/listings == {len(seo_pmap)} (§Projets)")
|
||||
# (e) le README cite l'ancrage CLAUDE.md §Projets
|
||||
seo_p_rd = read_norm(f"{D}/seo/README.md")
|
||||
if seo_p_rd is not None:
|
||||
if re.search(r"CLAUDE\.md\s*§?\s*Projets", seo_p_rd):
|
||||
good("SEO projets · README cite l'ancrage CLAUDE.md §Projets")
|
||||
else:
|
||||
bad("SEO projets · README ne cite pas l'ancrage CLAUDE.md §Projets "
|
||||
"(attendu « noms sourcés de CLAUDE.md §Projets »)")
|
||||
|
||||
# ============================================================================
|
||||
# SEO · la CARTE hreflang (`alternate` FR/EN/ES + `x-default` par page) — la
|
||||
# 3ᵉ surface du même README SEO — décrite EN PROSE sans AUCUN gate d'IDENTITÉ.
|
||||
|
||||
Reference in New Issue
Block a user