J'ai déjà publié les 9 skills SEO qui couvrent tout mon workflow. Mais quand on parle SEO technique pur, il y en a 5 qui forment un sous-ensemble cohérent : indexation, données structurées, cannibalisation, maillage et performance.

Voici les 5 skills à installer sur Claude pour pouvoir lancer des agents qui auditent et te proposent un plan d'action correctif. Tu as la possibilité de programmer les actions de chaque agent pour automatiser une bonne partie de ce workflow SEO technique.

Résumé

Un site parfaitement optimisé sur ses mots-clés et son contenu peut devenir invisible pour les robots Google si la technique est catastrophique. Une règle simple : des pages non indexées sont des pages invisibles sur le web. Donc un agent sera dédié à chaque procédure SEO technique, de la vitesse de chargement des pages à la cannibalisation.

  • Chaque skill est documenté pareil : son rôle dans le workflow, le moment où le déclencher, et le SKILL.md complet à copier dans ton dossier .claude/skills/.
  • Ensemble, ils couvrent toute la couche infra du SEO : indexation, données structurées, cannibalisation, maillage interne, Core Web Vitals.
  • Tu pilotes, Claude exécute. Le skill encode ta méthode pour que l'agent tienne ta position même quand tu le pousses.

Pourquoi un skill SEO technique change tout

Aujourd'hui, le SEO technique est devenu une commodité. N'importe quel agent IA peut t'auditer un sitemap. Mais sans cadre, Claude te sort un audit générique, le même que tout le monde pourra produire demain.

Le SKILL.md est l'endroit où tu encodes ta méthode. Sans lui, Claude te donne le corpus moyen d'internet. Avec lui, Claude tient ta position même quand tu le pousses. Le skill, c'est ton manager d'agent. Tu n'es plus l'assistant de Claude, c'est lui qui exécute ta méthode.

Skill n°1 : indexation-check

Définition

Audit complet d'indexation d'un site sans outil tiers payant, en 9 checks par URL : statut HTTP, robots.txt, noindex, sitemap, lastmod, maillage entrant, longueur de contenu, et statut d'indexation Google estimé.

Pourquoi tu en as besoin

C'est le premier skill à lancer parce que tout le reste suppose que Google indexe. Tu peux avoir le meilleur contenu du monde, si ta page porte un noindex accidentel, elle n'existe pas. Et 90% des audits SEO ne testent pas vraiment l'indexation, ils regardent juste le statut HTTP.

La force du skill, c'est la distinction qu'il fait entre « non indexée » (Google a vu et refusé) et « non testable » (Google a rate-limité ton scraping). La plupart des outils confondent les deux et te disent que ta page est absente alors qu'ils n'ont juste pas pu vérifier. C'est cette honnêteté qui rend l'audit utilisable.

Le skill complet

indexation-check/SKILL.md
---
name: indexation-check
description: |
  Audit d'indexation d'un site (B2B / éditorial / pSEO) sans outil tiers payant. Vérifie 9 points sur chaque URL : statut HTTP, blocages techniques, directives noindex, sitemap (présence + lastmod + cohérence), maillage interne entrant, longueur de contenu, statut d'indexation Google estimé. Sortie : rapport markdown avec synthèse, anomalies critiques en tête, anomalies mineures, recommandations priorisées. Distingue strictement "non indexée" et "non testable" (rate-limit Google). Aucune action de forçage, lecture seule sur le web public.

  TOUJOURS utiliser ce skill quand l'utilisateur dit : "audit indexation", "check indexation", "monitoring indexation", "vérifier l'indexation", "mes pages sont-elles indexées", "agent indexation", "routine indexation mensuelle", ou quand il fournit une liste d'URLs / un sitemap / un fichier `urls.txt` / `articles.ts` / `wiki.ts` en demandant un diagnostic d'indexation.

  Ce skill est conçu pour tourner en one-shot (audit ponctuel) ou en récurrent mensuel via `/schedule`. Cible : sites de 30 à 1 000 pages. Au-delà, brancher l'API Google Search Console officielle (voir section "Variante haute fiabilité").
---

# Skill — Audit d'indexation Claude

## Quand déclencher

- Audit ponctuel d'un site (avant call commercial, après une refonte, après un déploiement de cluster)
- Monitoring mensuel récurrent sur un client en accompagnement
- Diagnostic d'une chute de trafic non expliquée
- Pré-flight d'un projet pSEO (vérifier que la base technique est saine avant de scaler)

## Input requis

| Source | Obligatoire |
|--------|-------------|
| Liste des URLs à monitorer (fichier dans le repo : `urls.txt`, `src/data/wiki.ts`, `src/data/articles.ts`) OU URL du sitemap.xml public du site | Oui |
| Domaine cible (ex : `site-client.fr`) | Oui |
| Périmètre du maillage à crawler (pages hubs : `/`, `/glossaire`, `/wiki`, footer, articles) | Recommandé |
| Service account GSC API (upgrade fiabilité) | Optionnel |

Minimum viable : une liste d'URLs et le domaine. Le reste se déduit.

## Architecture (3 couches)

```
1. Charger la liste des URLs (source de vérité humaine)
        ↓
2. COUCHE A — Audit technique (causes potentielles)
        ↓
3. COUCHE B — Statut d'indexation observable
        ↓
4. COUCHE C — Reporting markdown
```

La séparation causes / statut est volontaire. Le check v1 ne regardait que le statut, donc disait "OK 200" même quand la page portait un `noindex` accidentel. Le check v2 sépare strictement les deux.

## Pipeline (9 checks)

### COUCHE A — Audit technique (causes)

**1. HTTP check (404/500, redirects)**
Pour chaque URL :
```bash
curl -sIL -A "Mozilla/5.0" -o /dev/null -w "%{http_code}|%{url_effective}" {URL}
```
Note le code final ET la chaîne de redirects. 200 attendu. Toute redirection 301→200 systématique = dette à signaler (trailing slash, http→https).

**2. robots.txt non bloquant**
```bash
curl -s https://{domaine}/robots.txt
```
Pour chaque path, vérifie qu'aucune directive `Disallow:` ne le bloque pour `User-agent: *` ni `User-agent: Googlebot`.

**3. Pas de balise noindex (HTML + en-tête HTTP)**
```bash
curl -sL -A "Mozilla/5.0" {URL} | grep -i 'name="robots"'
curl -sIL {URL} | grep -i 'x-robots-tag'
```
Signaler toute présence de `noindex` ou `none`.

**4. Sitemap : présence + fraîcheur**
```bash
curl -s https://{domaine}/sitemap.xml
```
Pour chaque slug attendu, vérifie présence d'une `<loc>` et lis le `<lastmod>` associé. Flagger tout `lastmod` > 6 mois.

**5. Cohérence sitemap ↔ source de vérité**
Diff entre les slugs de la SOT (fichier humain) et les `<loc>` du sitemap :
- Slugs en source mais absents du sitemap → à ajouter au build
- `<loc>` en sitemap mais absente de la source → orpheline sitemap

**6. Maillage interne entrant**
Crawl les pages hubs (`/`, `/glossaire`, `/wiki`, footer, sample d'articles) et compte les liens internes entrants par URL cible.
- 0 lien entrant = orpheline (critique)
- 1 seul lien entrant = sous-maillée (à signaler)

**7. Longueur de contenu (proxy thin content)**
Pour chaque URL, extrais le texte du `<main>` ou `<article>` (à défaut du `<body>` moins `<nav>` et `<footer>`) et compte les mots.
- < 300 mots = thin (à creuser)
- 300-800 = court mais acceptable pour fiche atomique
- > 800 = OK

### COUCHE B — Statut d'indexation (sortie)

**8. Indexation Google estimée (scraping `site:`)**
```bash
curl -sL -A "Mozilla/5.0 (Macintosh...)" "https://www.google.com/search?q=site:{URL}&hl=fr"
```
**Garde-fous obligatoires :**
- Espacer les requêtes de 3 à 5 secondes (`sleep 4`)
- Détecter le blocage : `grep -E 'sorry/index|recaptcha|unusual traffic'`
- Si bloqué : marquer "non testable — rate limit", **pas** "non indexée"
- Retry une fois si réponse vide avant de conclure
- Marquer "indexée" si l'URL apparaît dans le HTML de réponse

Fiabilité ~40-60 % à cause du rate-limit. Pour 100 % de fiabilité, voir la variante GSC API.

### COUCHE C — Reporting

**9. Rapport markdown**
Structure obligatoire :
- Synthèse en tête (X/N par dimension, `✅` pour validé, `⚠️` pour anomalie)
- Anomalies CRITIQUES (action immédiate) — `noindex` accidentel, page orpheline, 404 sur page business
- Anomalies mineures — `lastmod` > 6 mois, sous-maillage, thin content
- Recommandations priorisées (2-5 actions, classées par impact estimé)
- Section "Limites du rapport" (ce qui n'a pas pu être testé et pourquoi)

## Sortie obligatoire

```markdown
# Indexation check — {domaine} — {date}

## Synthèse

Vérifications validées :
✅ {check} : X / N
✅ ...

Vérifications avec anomalies :
⚠️ {check} : X / N ({Y détails})
⚠️ ...

## Anomalies CRITIQUES (action immédiate)

⚠️ {URL} — {anomalie détectée}
   {contexte : depuis quand, impact estimé en impressions ou clics}
   → {action concrète à mener}

## Anomalies mineures

- {liste compactée des points secondaires}

## Recommandations priorisées

1. {action 1} — {effort estimé}, {impact estimé}
2. {action 2} — ...
3. {action 3} — ...

## Limites de ce rapport

- {ce qui n'a pas pu être testé et pourquoi : rate-limit Google, JS rendu côté client, etc.}
```

## Garde-fous (NON-NÉGOCIABLES)

- **Lecture seule** sur le web public. Aucune requête authentifiée non explicitement autorisée.
- **Aucune action de forçage d'indexation.** Pas de soumission GSC, pas de force-crawl.
- **Pas de modification du site.** Pas de PR sur les fichiers source. Sortie limitée à `reports/` ou directement dans la conversation.
- **Distinction stricte** "non indexée" vs "non testable" dans le rapport.
- **Priorité au signalement** : un `noindex` accidentel sur une page business doit remonter en TOP du rapport, pas en ligne 47 du tableau.
- Si un check échoue, continuer les autres et signaler dans la section "Limites".

> Discipline : observation côté agent, décision côté humain. Un agent qui peut "réparer" peut aussi tout casser un dimanche à 3 h du matin.

## Variante haute fiabilité — GSC URL Inspection API

Si l'utilisateur dispose d'un service account Google Cloud + propriété GSC :
1. Lire la clé JSON via secret stocké côté projet
2. Signer un JWT, échanger contre access token
3. POST sur `https://searchconsole.googleapis.com/v1/urlInspection/index:inspect`
4. Statuts officiels disponibles : `INDEXED`, `DISCOVERED_NOT_INDEXED`, `CRAWLED_NOT_INDEXED`, `URL_IS_UNKNOWN_TO_GOOGLE`

Passe le check #8 de ~50 % à 100 % de fiabilité. Setup ~30 minutes. Lève le rate-limit Google (limite officielle 2 000 inspections / jour / propriété).

## Variante site JS rendu côté client (SPA)

Si le contenu principal est rendu côté client (Single Page Application), `curl` ne verra qu'une coquille HTML quasi vide. Brancher Playwright headless :
1. Lancer un browser headless
2. Charger l'URL, attendre `networkidle`
3. Comparer le HTML rendu vs le HTML servi par le serveur
4. Signaler tout contenu uniquement présent post-JS (= invisible pour les crawlers qui n'exécutent pas le JS)

À réserver aux sites SPA ou aux clusters critiques (~30 s par URL).

## Récurrence — programmation via `/schedule`

Pour transformer l'audit ponctuel en monitoring continu :
```
/schedule
```
Choix structurants :
- **Récurrence** : `cron_expression: "0 1 1 * *"` (1er de chaque mois, 01h UTC)
- **Alternative one-shot** : `run_once_at` à J+14 après un déploiement de cluster
- **Modèle** : `claude-sonnet-4-6` (Opus inutile pour ce job)
- **Tools** : `Bash, Read, Write, Edit, Glob, Grep`
- **Environment** : Default Anthropic Cloud

À chaque exécution : 5 à 10 min de tourner-en-fond, rapport posté dans la conversation (ou push d'une PR si configuré côté repo).

## Réutilisation client

Pour appliquer ce skill à un client en accompagnement :
1. Créer un fichier `urls-prio.txt` dans son repo (top 30 pages business) OU pointer le sitemap public
2. Adapter le périmètre du maillage (hubs spécifiques au site)
3. Schedule cron mensuel
4. Inclure le rapport mensuel dans la note de suivi

Coût marginal par client : ~15 min de setup, 0 € récurrent. À facturer dans la rétro mensuelle ou comme bonus différenciant en pitch.

## Sources internes

- `wiki/briefs/check-indexation-claude.md` (brief de référence v2, 2026-05-11)
- `wiki/concepts/grounding-score.md`
- `wiki/concepts/passage-ranking.md`
- `raw/articles/brouillons/préparation-newsletter-indexation-2026-05-11.md` (newsletter dérivée)

Travaillons ensemble votre acquisition.

01 / 05

Skill n°2 : seo-donnees-structurees

Définition

Mise en place automatique du balisage JSON-LD sur un site Next.js App Router : un graphe d'entité site-wide unique référencé par @id, et des schémas par page (Article, FAQPage, HowTo, VideoObject) DÉRIVÉS du contenu, jamais saisis à la main.

Pourquoi tu en as besoin

Le balisage structuré, c'est ce qui dit à Google qui tu es et ce que tu fais. Sans ça, ton E-E-A-T reste invisible et tu rates les rich results. Mais 90% des sites mettent du JSON-LD à la main, page par page, et ça désynchronise au premier edit.

La règle non-négociable du skill : aucun champ n'est saisi à la main, tout se calcule depuis la donnée de la page. Si tu dois taper deux fois la même info, c'est cassé. Le skill prend le système qui tourne en prod sur organikk.co et te l'applique sur ton site.

Le skill complet

seo-donnees-structurees/SKILL.md
---
name: seo-donnees-structurees
description: >
  Mise en place de données structurées JSON-LD automatiques sur un site Next.js
  (App Router) : graphe d'entité site-wide unique référencé par @id, et schémas
  par page DÉRIVÉS du contenu (Article, FAQPage, HowTo, VideoObject,
  BreadcrumbList) sans saisie manuelle. Généralisé à partir du système en
  production sur organikk-next (src/lib/schema.ts).

  TOUJOURS utiliser ce skill quand l'utilisateur dit : "données structurées",
  "schema.org", "JSON-LD", "rich results", "rich snippets", "balisage",
  "FAQPage", "HowTo schema", "BreadcrumbList", "VideoObject", "entité Google",
  "knowledge graph du site", "@id graph", ou demande d'ajouter, refondre ou
  auditer le balisage structuré d'un site (en priorité Next.js App Router).
---

# Skill — Données structurées JSON-LD automatiques

## Quand déclencher

Ajouter, refondre ou auditer le balisage structuré d'un site. Objectif : éligibilité aux rich results Google ET consolidation d'une entité unique exploitable par les moteurs de réponse (AEO). Pas du schema décoratif, du schema qui sert le Grounding et la citation.

## Principe non-négociable

Trois règles, dans cet ordre. Si l'une saute, le skill a échoué.

1. **Source unique.** Tout le JSON-LD vient d'un seul module (`lib/schema.ts`). Aucun bloc schema écrit en dur dans une page.
2. **Une entité, référencée par `@id`.** Le graphe site-wide (Organization, WebSite, Person) est émis une seule fois dans le layout racine. Chaque page de contenu pointe vers ces nœuds par `@id` (author / publisher / isPartOf), elle ne redéclare jamais l'auteur ni l'éditeur. Google fusionne tous les blocs d'une page et consolide une seule entité. C'est ça qui construit le knowledge graph du site, pas la répétition.
3. **Le schema est dérivé du contenu, jamais saisi à la main.** Un champ schema qui n'est pas calculé depuis la donnée de la page est une dette : il se désynchronise au premier edit. Si on doit le taper deux fois, c'est cassé.

## Architecture de référence

Graphe site-wide (`siteGraph()`, émis dans le layout racine) :

- `Organization` (+ `ProfessionalService` si activité de service) avec `@id = {SITE_URL}/#organization`
- `WebSite` avec `@id = {SITE_URL}/#website`, `publisher` -> ref Organization
- `Person` (fondateur / auteur) avec `@id = {SITE_URL}/#person-...`, `worksFor` -> ref Organization
- `ImageObject` logo avec `@id = {SITE_URL}/#logo`, partagé par `logo` et `image`
- `sameAs` : profils sociaux réels (LinkedIn, YouTube, X, Substack). Pas d'URL inventée.

Refs légères exportées et réutilisées partout : `AUTHOR_REF = {'@id': PERSON_ID}`, `PUBLISHER_REF = {'@id': ORG_ID}`, `WEBSITE_REF = {'@id': WEBSITE_ID}`.

## Règle de dérivation automatique (le cœur du skill)

Chaque type de schema se déduit d'un signal présent dans la donnée de la page. Tableau de correspondance à appliquer :

| Signal dans le contenu | Schema émis | Dérivation |
|---|---|---|
| Toute page article / post | `Article` ou `BlogPosting` | titre, excerpt, date, section ; `wordCount` calculé par `countWords()` ; `keywords` depuis les highlights ; author/publisher/isPartOf = refs |
| Toute page (toujours) | `BreadcrumbList` | `breadcrumb()` à partir du chemin (Accueil > rubrique > titre) |
| Bloc `faq` présent dans les sections | `FAQPage` | `mainEntity` = map des items q/a en Question/Answer. Zéro saisie |
| Section `video` présente | `VideoObject` | ID YouTube extrait par regex de l'URL ; thumbnail + embedUrl déduits |
| H2 dont le titre matche un prédicat (ex. contient "prompt", "étapes", "tutoriel") | `HowTo` | collecte les H3 numérotés suivants + leur contenu (p / code / listes) en `HowToStep` |
| Page produit / offre avec prix | `Product` / `Offer` | depuis les champs prix/dispo de la donnée. Jamais de prix inventé |

Principe : on ne demande jamais à l'utilisateur "quel schema veux-tu". On lit la structure de la page et on émet ce que la structure prouve. Un schema sans preuve dans le contenu = suppression.

## Procédure

1. **Détecter le terrain.** Framework et version. Si Next.js : App Router ou Pages ? Lire la doc du projet (sur organikk-next : `node_modules/next/dist/docs/`, conventions imposées par AGENTS.md) avant d'écrire une ligne. Ne jamais présumer l'API metadata.
2. **Cartographier le modèle de données.** Où vivent les articles, pages, FAQ, vidéos (ex. `src/data/*.ts`). Identifier la forme des sections (le schema se dérive de là).
3. **Créer / refondre `lib/schema.ts`** : config d'identité en tête (SITE_URL, identité Organization/Person/sociaux), les nœuds, `siteGraph()`, les refs `@id`, les helpers `breadcrumb()` et `countWords()`. Partir de `references/schema-reference.ts`.
4. **Câbler le layout racine** : un seul `<script type="application/ld+json">` avec `siteGraph()`.
5. **Câbler chaque template de contenu** : construire un tableau `jsonLd[]` (Article + breadcrumb toujours, puis FAQ / Video / HowTo conditionnels selon les signaux), sérialisé dans UN script en tête de page.
6. **Dédupliquer.** Supprimer tout ancien bloc Organization/Person redéclaré dans les pages : tout passe par les refs `@id`.
7. **Valider** (voir checklist). Corriger jusqu'à zéro erreur.

## Émission

- Site-wide : un `<script type="application/ld+json">` dans le layout racine, contenu = `siteGraph()`.
- Par page : un seul `<script type="application/ld+json">` en tête, contenu = `JSON.stringify(jsonLd)` où `jsonLd` est le tableau des nœuds de la page.
- App Router : injection via `dangerouslySetInnerHTML` dans un Server Component. Ne pas utiliser de lib tierce, ce sont des objets JS purs.

## Validation (checklist, zéro erreur tolérée)

- [ ] Chaque `@id` site-wide est unique et stable, réutilisé par ref dans les pages
- [ ] Aucune page ne redéclare un nœud Organization ou Person en entier
- [ ] `Article` : headline non vide, `datePublished` valide, `wordCount` calculé (pas codé en dur), author/publisher en ref
- [ ] `FAQPage` émis seulement si un vrai bloc FAQ existe, et chaque réponse est du texte réel
- [ ] `HowTo` émis seulement si des étapes ordonnées existent réellement
- [ ] `VideoObject` : `contentUrl`/`embedUrl` résolus, thumbnail valide
- [ ] `BreadcrumbList` cohérent avec l'URL réelle
- [ ] JSON-LD valide et passe le test Rich Results de Google + le validateur schema.org
- [ ] Aucun champ inventé (prix, note, avis, premier sur X) sans preuve : un faux signal dégrade la note Google de la page

## Anti-patterns (à ne jamais faire)

- Redéclarer l'auteur et l'éditeur sur chaque page au lieu de référencer par `@id`
- Écrire du JSON-LD en dur dans le JSX d'une page
- Émettre un `FAQPage` ou `HowTo` "au cas où" sans contenu correspondant visible sur la page (risque de pénalité, et c'est du mensonge à Google)
- Mettre `aggregateRating` / `Review` / prix non vérifiables
- Multiplier les `<script ld+json>` redondants au lieu d'un tableau unique par page
- Hardcoder `wordCount`, des dates ou des compteurs au lieu de les dériver de la donnée
- Présumer l'API Next.js sans lire la doc de la version du projet

## Référence d'implémentation

`references/schema-reference.ts` : module généralisé prêt à adapter (config d'identité en tête, nœuds, `siteGraph`, refs `@id`, `breadcrumb`, `countWords`, builders `articleSchema` / `faqSchema` / `videoSchema` / `howToFromSections`). C'est la version dé-Organikk-isée du système en production sur `organikk-next/src/lib/schema.ts`. L'adapter au modèle de données du site cible, ne pas le copier tel quel.

## Concepts liés

`aeo` · `grounding-score` · `entites-vectorielles` · `passage-ranking` · `knowledge-graph` · `e-e-a-t`

Skill n°3 : seo-cannibalisation

Définition

Audit depuis les données GSC qui repère les pages en compétition interne sur les mêmes mots-clés ou intentions, classifie le type de conflit, et recommande l'action (301, fusion, différenciation, ou rien).

Pourquoi tu en as besoin

Deux pages qui visent la même requête, c'est zéro page qui ranke. C'est mathématique. Google divise l'autorité entre les deux, elles passent en moyenne, et tu sors du top 3. C'est probablement l'erreur la plus commune sur les sites de plus de 50 pages.

Le skill classifie les cannibalisations en 4 types (mot-clé exact, même intention, proximité sémantique, Triade SERP) et chaque type appelle une action différente. Le piège classique, c'est de foncer en 301 sur la perdante sans analyser les métriques des deux côtés. Si chacune a son trafic et ses liens entrants, tu détruis de la valeur sans le voir.

Le skill complet

seo-cannibalisation/SKILL.md
---
name: seo-cannibalisation
description: |
  Audit de cannibalisation SEO depuis les données GSC. Identifie les pages en compétition interne sur les mêmes mots-clés ou intentions, classifie le type de conflit (mot-clé exact, même intention, proximité sémantique, Triade SERP), analyse les métriques, recommande l'action (301, fusion, différenciation, maillage croisé, ou aucune action si Triade SERP).

  TOUJOURS utiliser ce skill quand l'utilisateur dit : "cannibalisation", "deux pages sur le même mot-clé", "je rankais mieux avant", "pages en compétition interne", "keyword cannibalism", "audit cannibalisation".
---

# Skill — Cannibalisation SEO

## Quand déclencher

Deux pages se concurrencent sur les mêmes mots-clés ou intentions. Chutes de positions inexpliquées, CTR qui stagne malgré le volume.

## Input requis

| Source | Obligatoire |
|--------|-------------|
| Export GSC Requêtes — filtré par URL, 90j | Oui |
| Liste des URLs (scraping ou sitemap) | Recommandé |
| Contexte stratégique (pilier vs satellite) | Recommandé |

## Pipeline (5 étapes)

1. **Identifier les conflits** — requêtes qui déclenchent 2+ URLs dans la GSC
2. **Classifier le type** :
   - **(A) Mot-clé exact** — deux pages sur la même requête précise
   - **(B) Même intention** — deux pages répondent à la même intention
   - **(C) Proximité sémantique** — sujets proches sans conflit direct
   - **(Triade SERP)** — opportunité, pas conflit
3. **Analyser les métriques** — position, impressions, clics, CTR par page
4. **Évaluer l'architecture** — pilier vs satellite, objectif business
5. **Recommander l'action** :

| Situation | Action |
|-----------|--------|
| Type A + perdante faible | Redirection 301 |
| Type A + deux fortes | Fusion + 301 |
| Type B + micro-intentions distinctes | Différenciation + maillage croisé |
| Type C | Renforcement maillage vers pilier |
| Triade SERP | Aucune action — optimiser chaque angle |

## Output obligatoire

```
CANNIBALISATION DÉTECTÉE
Requête : '[requête]' — Type : (A/B/C/Triade)
| URL | Position | Impressions | Clics | CTR | Statut |

→ Diagnostic : [explication]
→ Action : [action précise + 3 étapes d'implémentation]
```

## Règles absolues

- Ne pas recommander une 301 sans analyser les métriques des deux pages
- Ne pas traiter toutes les cannibalisations de la même façon
- Ne pas confondre duplication de contenu et cannibalisation
- Ne pas fusionner des pages avec micro-intentions distinctes
- Identifier les Triades SERP comme opportunités, pas comme problèmes

## Sauvegarde

Output dans `wiki/cannibalisation/YYYY-MM-DD-slug.md` selon hook §7 AGENTS.md.

## Concepts liés

`triade-serp` · `rrf` · `maillage-interne` · `intention-recherche` · `gsc-export`

Skill n°4 : maillage-systeme + maillage-interne-gsc

Définition

Architecture en piliers et plan d'ancres diversifiées pour ton site. Sans données GSC pour le cadrage initial (maillage-systeme), avec données GSC pour l'optimisation comportementale (maillage-interne-gsc). Les deux se complètent.

Pourquoi tu en as besoin

Le maillage interne, c'est la puissance qui circule entre tes pages. Et la majorité des sites le font à l'envers : un bloc « Voir aussi » en bas d'article, des ancres en exact match répétées partout, et zéro lien Know vers Do.

Le skill construit 3 à 5 piliers max, désigne le hub de chaque pilier, et propose 3 ancres par lien à créer : exact match (une seule, sur la première mention), partial match, sémantique étendue. Test à voix haute : si la phrase tombe juste sans le lien, l'ancre est bonne. Sinon, tu refais. Quand tu as 6 mois de GSC, le second skill injecte la donnée comportementale dans le plan.

Le skill complet

maillage-systeme/SKILL.md
---
name: maillage-systeme
description: >
  Maillage interne stratégique d'un site (blog, catalogue, doc) : architecture en piliers,
  classification hub/satellite, sélection d'ancres avec diversification entrante,
  détection des pages orphelines et dead-end, audit complet du graphe interne.

  Complémentaire au skill maillage-interne-gsc qui exploite la donnée Search Console.
  Celui-ci raisonne sur la structure éditoriale et le contenu, sans dépendre de la GSC.
  Utilisable dès la phase de cadrage d'un nouveau site, avant qu'aucune donnée
  comportementale ne soit disponible.

  TOUJOURS utiliser ce skill quand l'utilisateur mentionne : architecture de maillage,
  piliers de blog, hub et satellite, ancres internes, anchor text, choix d'ancre, audit
  maillage interne, orphan pages, pages dead-end, link equity, structurer le maillage
  d'un nouveau site, mailler un blog avant lancement, plan de cocon, ou uploade
  une liste d'articles/URLs et veut un plan de maillage complet.
---

# Skill — Maillage Interne : Architecture, Ancres et Audit

## Rôle

Construire et auditer le graphe de liens internes d'un site à partir du contenu existant ou planifié, sans dépendre de la Search Console. Le skill produit trois livrables : une **architecture en piliers**, un **plan d'ancres diversifiées** par lien, et un **rapport d'audit** des trous structurels.

L'objectif n'est pas de générer des liens à la chaîne. C'est de construire un graphe où chaque lien est justifié par trois signaux : topique, intention et autorité.

---

## Réflexion appliquée (méthode Boussardon)

- Le maillage interne est **un système, pas une passe**. Trois axes simultanés : topique, intention, autorité.
- Une ancre, ce n'est pas un mot-clé. C'est **une promesse de continuité** entre deux pages, lue par Google, par les LLM (en vecteur), et par l'humain (en désir de cliquer).
- **5 ancres possibles vers la même page = 5 ancres différentes.** Un seul exact match. Le reste est partial / sémantique / contextuel long.
- **Test à voix haute** : si la phrase tombe juste sans le lien, l'ancre est bien intégrée. Si elle clopine, l'ancre est plaquée.
- **Le maillage Know→Do passe avant le maillage Know→Know.** Une page qui explique un concept doit toujours pointer vers la page qui permet de l'exécuter (outil, audit, démo).
- **Pas de "Voir aussi" en bas d'article.** Le contexte de lien est dilué. Liens contextuels in-body uniquement.
- **Une page mère n'est pas un titre de catégorie.** C'est l'article le plus stratégique du pilier, celui qui définit le vocabulaire et reçoit le plus de liens internes.
- **Le cross-pillar pollination compte autant que le maillage intra-cluster.** 1 lien sortant sur N doit pointer vers un autre pilier pour éviter la siloïsation.

---

## Données requises

| Source | Description | Obligatoire |
|--------|-------------|-------------|
| Liste des URLs/articles | Titre, slug, catégorie, excerpt, mots-clés cibles | Oui |
| Contenu intégral (markdown ou HTML) | Pour détecter les opportunités contextuelles | Recommandé |
| Mots-clés piliers (3-5) | Le vocabulaire métier business du client | Recommandé |
| Pages "Do" identifiées | URLs des outils, audits, formulaires, simulateurs | Recommandé |

**Minimum viable** : la liste des articles avec titre + excerpt + mots-clés. Sans le contenu intégral, le skill produit l'architecture mais pas les ancres précises.

---

## Raisonnement de l'agent (étapes obligatoires)

L'agent DOIT suivre ces étapes **dans l'ordre** avant de répondre.

### Étape 1 — Classifier chaque page en intention

Pour chaque article, déterminer son intention dominante :

| Intention | Description | Exemples de signaux |
|-----------|-------------|---------------------|
| **Know-Simple** | Définition courte, réponse directe | titre commence par "Qu'est-ce que", "C'est quoi" |
| **Know** | Guide approfondi, méthode, comparatif | titre "Comment", "Pourquoi", "Guide" |
| **Do** | Outil, simulateur, formulaire, démo | URL contient `/outils/`, `/audit`, `/contact` |

Une page peut avoir une intention dominante + une intention secondaire. Noter les deux.

### Étape 2 — Identifier les piliers

Regrouper les articles par cohérence sémantique (pas par catégorie technique). Cibler **3 à 5 piliers max**. Pas plus. Pas moins de 3.

Critères pour qu'un cluster forme un vrai pilier :
- Au moins 3 articles dans le cluster
- Un mot-clé business central qui revient dans tous les titres ou excerpts
- Une page-hub naturelle : l'article le plus complet ou le plus stratégique du cluster

Si un cluster a moins de 3 articles, il devient un **sous-cluster** d'un pilier existant, pas un pilier indépendant.

### Étape 3 — Désigner le hub de chaque pilier

Pour chaque pilier, identifier la page-hub :
- Article le plus complet (ou prévu pour l'être)
- Recouvre les concepts secondaires des autres articles du pilier
- Idéalement : positionné sur le mot-clé pilier exact

Le hub reçoit des liens entrants depuis tous les satellites. Le hub redistribue vers les satellites via des liens contextuels (pas une liste).

### Étape 4 — Cartographier les liens existants

Pour chaque article, lister :
- **Inbound links** : combien d'articles pointent vers lui ?
- **Outbound links** : vers combien d'articles pointe-t-il ?
- **Click depth** : combien de clics depuis la home ?

Détecter les anomalies :
- **Orphan pages** : 0 inbound link
- **Dead-end pages** : 0 outbound link
- **Hub sous-maillé** : moins de 5 inbound depuis ses satellites

### Étape 5 — Sélectionner les ancres pour chaque lien proposé

Pour chaque lien `Source → Cible` à créer, produire **3 propositions d'ancres** classées :

1. **Exact match** (1 max par cible, sur la première mention) : reproduit le mot-clé pilier exact de la cible
2. **Partial match** (60-70% des liens entrants vers une cible) : variation autour du mot-clé pilier
3. **Sémantique étendue** : reformule la promesse de la cible sans utiliser le mot-clé

Pour chaque ancre, vérifier les 5 critères :

| Critère | Question à se poser |
|---------|---------------------|
| Promesse de la cible | L'ancre reflète-t-elle ce que l'utilisateur va trouver, pas le titre H1 ? |
| Phrase porteuse | La phrase reste-t-elle fluide à voix haute sans le lien ? |
| Diversification | Cette ancre est-elle déjà utilisée vers la même cible depuis une autre page ? |
| Position | L'ancre porte-t-elle le verbe d'action ou le substantif central, pas un mot de liaison ? |
| Link context | Les 5 mots avant/après parlent-ils du sujet de la cible ? |

Si une ancre rate un critère, la rejeter.

### Étape 6 — Prioriser les liens à créer

Score d'urgence par lien proposé :
**Score = (impressions cible × poids_intention) + (gain_authority × 0.4)**

Où :
- `poids_intention` : Do = 1.0, Know-décisionnel = 0.8, Know = 0.5, Know-Simple = 0.3
- `gain_authority` : 1 si la source est un hub, 0.5 si la source est un satellite mailé, 0.2 sinon

Prioriser dans cet ordre :
1. **Liens manquants Hub → Satellite** dans un pilier (pour activer le cocon)
2. **Liens Know → Do** (pour orienter le funnel)
3. **Liens cross-pillar** (1 par pilier minimum vers un autre pilier)
4. **Liens vers pages orphelines** identifiées en étape 4

### Étape 7 — Vérifier les règles de conservation

Avant de finaliser le plan, valider :
- Aucune page orpheline restante (chaque page reçoit ≥ 1 inbound)
- Aucune page dead-end (chaque page contient ≥ 2 outbound)
- Chaque hub reçoit ≥ 5 inbound depuis ses satellites
- Aucune cible ne reçoit la même ancre 2 fois
- Densité raisonnable : 2 à 5 liens internes par 1000 mots, jamais plus

**NE PAS répondre avant d'avoir complété chaque étape.**

---

## Format de sortie OBLIGATOIRE

### Bloc 1 — Architecture détectée

```
PILIER 1 — [Nom thématique] (mot-clé pilier : "...")
├── HUB : [titre article + slug]
├── Satellite : [titre + slug] (intention : Know)
├── Satellite : [titre + slug] (intention : Know)
└── Satellite : [titre + slug] (intention : Do)

PILIER 2 — [Nom]
...
```

### Bloc 2 — Audit du graphe existant

Tableau :

| Article | Inbound | Outbound | Click depth | Statut |
|---------|---------|----------|-------------|--------|
| ... | 3 | 4 | 2 | OK |
| ... | 0 | 2 | 3 | **ORPHELINE** |
| ... | 5 | 0 | 1 | **DEAD-END** |

### Bloc 3 — Plan de liens à créer (priorisé)

Pour chaque lien proposé :

```
PRIORITÉ : HAUTE | Score : 8.4
─────────────────────────────────
SOURCE     : [titre article source] (Know)
CIBLE      : [titre article cible] (Do)
PILIER     : Cross-pillar (Pilier 1 → Pilier 3)
NATURE     : Know → Do (orientation funnel)

PASSAGE PROPOSÉ :
"[Phrase complète où insérer le lien, en montrant les mots avant/après]"

ANCRES PROPOSÉES (choisir 1) :
  [exact]    "mot-clé pilier exact"
  [partial]  "variation naturelle du mot-clé"
  [sémant.]  "reformulation de la promesse cible"

JUSTIFICATION : [1 phrase sur pourquoi ce lien crée de la valeur]
```

### Bloc 4 — Règles de gouvernance

Une checklist finale que le client suit à chaque nouvelle publication :

- [ ] Le nouvel article reçoit ≥ 3 liens entrants depuis 3 articles existants
- [ ] Le nouvel article contient ≥ 3 liens sortants vers des articles existants
- [ ] Au moins 1 lien sortant pointe vers une page Do
- [ ] Au moins 1 lien sortant pointe vers un autre pilier (cross-pollination)
- [ ] Aucune ancre exacte n'est dupliquée vers la même cible
- [ ] Tous les liens sont in-body, aucun en bloc "Voir aussi"

---

## Points de vigilance

- **Ne pas tout automatiser.** Le skill propose, l'humain décide. Une ancre forcée détruit le naturel d'un texte.
- **Le contexte vaut plus que l'ancre.** Une ancre parfaite dans une phrase qui parle d'autre chose = lien faible. Réécris la phrase.
- **Le hub n'est pas figé.** Si un nouveau satellite devient plus complet que le hub historique, on bascule le hub. La structure suit le contenu, pas l'inverse.
- **Cross-pillar ≠ liens hors-sujet.** Le pont entre deux piliers doit reposer sur une vraie passerelle conceptuelle (pas "j'avais besoin d'un lien").
- **Ne jamais lier vers la home depuis le contenu.** La home a déjà tout le PageRank, elle n'en a pas besoin. Garde le jus pour les pages business.
- **Les FAQ sont une mine d'ancres.** Chaque réponse de FAQ qui mentionne un sous-sujet déjà traité doit lier. Densité haute, contexte naturel.
- **Densité plafonnée.** Au-delà de 5 liens internes pour 1000 mots, la dilution s'installe. Google pondère chaque ancre par 1/N où N est le nombre total de liens.

---

## Cas particulier — Site sans donnée GSC

Si le site est nouveau (moins de 3 mois ou pas d'accès GSC) :
- Étape 4 (cartographie inbound/outbound) se fait par parsing du contenu (markdown/HTML)
- Étape 6 (scoring) utilise un proxy : `position_business` (1 si Do, 0.7 si Know-décisionnel, 0.5 si Know, 0.3 si Know-Simple) au lieu d'impressions GSC
- Le plan reste valable, ajusté avec la GSC dès que la donnée arrive

---

## Cas particulier — Refonte de site existant

Si le site existe et a ≥ 6 mois de GSC :
- Chaîner ce skill avec **maillage-interne-gsc** : ce skill définit l'architecture, l'autre injecte la donnée comportementale
- Identifier les pages qui rankent déjà sur le pilier et les promouvoir hub si pertinent
- Préserver les liens existants qui marchent (pas de refonte aveugle)

---

## Rappels méthode

> "Le maillage interne, c'est un système, pas une passe."
> "5 liens entrants vers la même page = 5 ancres différentes. Un seul exact match."
> "Test à voix haute : si la phrase tombe juste sans le lien, l'ancre est bonne."
> "Une page Know doit toujours pointer vers une page Do."
> "Pas de Voir aussi. Liens in-body uniquement."
> "Un hub n'est pas une catégorie. C'est l'article le plus stratégique du pilier."
> "Le contexte des 5 mots avant/après l'ancre vaut plus que l'ancre elle-même."
maillage-interne-gsc/SKILL.md
---
name: maillage-interne-gsc
description: |
  Analyse et optimisation du maillage interne depuis les données GSC. Hiérarchie page mère/fille/petite-fille selon la méthode Boussardon. Pipeline en 5 étapes : récupérer GSC → diagnostiquer la structure (mères potentielles, sous-maillées, orphelines) → construire le plan de maillage avec règles Know→Do → prioriser par score urgence → générer recommandations (page source, destination, ancre, contexte).

  TOUJOURS utiliser ce skill quand l'utilisateur dit : "maillage interne", "liens internes", "cocon SEO", "pages orphelines", "GSC + structure de site", quand un fichier GSC est uploadé.
---

# Skill — Maillage Interne GSC

## Quand déclencher

Analyse et optimisation du maillage interne depuis les données GSC. Hiérarchie page mère/fille/petite-fille.

## Philosophie

> "Le maillage interne, c'est la puissance. Et ça part de tes mots-clés."

- Page mère = au moins 5 citations depuis des pages filles/petites-filles
- Le maillage part de la stratégie de mots-clés → le cocon est la conséquence
- Priorité : transactionnel > décisionnel > informationnel
- Maillage par intention (Know → Do) en plus du maillage sémantique

## Input requis

| Source | Obligatoire |
|--------|-------------|
| Export GSC Pages — URL, Clics, Impressions, CTR, Position | Oui |
| Export GSC Requêtes par page | Recommandé |
| Période 3-6 mois | Recommandé |

## Pipeline (5 étapes)

1. **Récupérer les données GSC** — export Pages + Requêtes par URL
2. **Diagnostiquer la structure** :
   - Pages mères potentielles (impressions élevées, pos 4-15, requête transactionnelle)
   - Pages sous-maillées (bonne position mais CTR faible)
   - Pages orphelines (aucune thématique secondaire dans GSC)
3. **Construire le plan de maillage** — hiérarchie mère/fille/petite-fille + règles Know→Do
4. **Prioriser** — score urgence = (Impressions × 0.4) + (Potentiel position × 0.4) + (Business value × 0.2)
5. **Générer les recommandations** — page source + page destination + ancre + contexte + priorité

## Structure hiérarchique

```
Page Mère (mot-clé principal business)
├── Page Fille 1 (requête secondaire transactionnelle)
│   ├── Page Petite-Fille A (longue traîne / micro-intention)
│   └── Page Petite-Fille B
└── Page Fille 2
```

Règle Know → Do : chaque page Know doit pointer vers au moins 1 page Do thématiquement reliée.

## Output obligatoire

Pour chaque action :
- Page source (intention Know/Do/Know+Do)
- Page destination + intention
- Nature du lien (sémantique ou intentionnel Know→Do)
- Ancre recommandée (jamais "cliquez ici")
- Contexte d'insertion
- Priorité (Haute/Moyenne/Faible)

## Règles absolues

- Ne pas automatiser à 100% — le maillage part de la stratégie, pas des outils
- Ne pas mailler sans tenir compte de l'intention (sémantique ≠ intentionnel)
- Ne pas répéter la même ancre sur toutes les pages filles
- 10 citations minimum pour une page mère "active"

## Sauvegarde

Output dans `wiki/maillage/YYYY-MM-DD-slug.md` selon hook §7 AGENTS.md.

## Concepts liés

`cocon-semantique` · `pagerank-interne` · `intention-recherche` · `cannibalisation` · `gsc-export`

Skill n°5 : seo-core-web-vitals

Définition

Audit Core Web Vitals (LCP, CLS, TBT) en mobile via Lighthouse, sur un échantillon d'URLs depuis ton sitemap. Tableau de scores par URL avec verdict Google, problèmes récurrents site-wide, plan de correction priorisé sur les 5 pires pages.

Pourquoi tu en as besoin

Les Core Web Vitals ne te font pas ranker tout seuls. Mais une page lente sort des résultats sur mobile, et Google indexe le mobile d'abord. La moitié des sites B2B que j'audite ont un LCP au-dessus de 4 secondes. Bah ouais.

Ce que ce skill fait que les autres ne font pas : il détecte le pattern qui passe sous le radar dans 50 audits identiques. Si l'opportunity « redirects » apparaît sur plus de 50% des pages, c'est presque toujours un mismatch de trailing slash entre sitemap et serveur. Le skill remonte ça en tête du rapport. C'est souvent le quick win le plus rentable de tout l'audit.

Le skill complet

seo-core-web-vitals/SKILL.md
---
name: seo-core-web-vitals
description: |
  Audit Core Web Vitals (LCP, CLS, TBT) d'un site via Lighthouse local sur un échantillon d'URLs depuis sitemap.xml. Mobile-first. Produit un tableau de scores par URL + problèmes récurrents site-wide + plan de correction priorisé pour les 5 pires pages.

  TOUJOURS utiliser ce skill quand l'utilisateur dit : "audit Core Web Vitals", "audit CWV", "perf SEO", "LCP CLS TBT", "Lighthouse audit", "vitesse de chargement", "audit performance site", "pourquoi mon site est lent en SEO", "PageSpeed score", "audit perf mobile", "score Lighthouse", "audit Web Vitals".

  Skill du périmètre SEO technique. Complémentaire de `indexation-check`, `seo-donnees-structurees`, `seo-cannibalisation`, `maillage-systeme`.
---

# Skill — Audit Core Web Vitals (Lighthouse local)

## Quand déclencher

Diagnostic de performance SEO d'un site complet, ou avant intervention technique (refonte, optimisation perf, migration). Toujours mobile (Google = mobile-first indexing).

## Pré-requis (à vérifier en début de skill)

```bash
which lighthouse || echo "MANQUANT — installer : npm install -g lighthouse"
which jq || echo "MANQUANT — installer : brew install jq"
```

Si Lighthouse manque, proposer `npm install -g lighthouse` à l'utilisateur et stopper. Ne pas installer silencieusement.

Alternative sans install globale : remplacer `lighthouse` par `npx lighthouse` dans toutes les commandes (premier lancement plus lent).

## Input requis

| Source | Obligatoire | Défaut |
|--------|-------------|--------|
| URL du sitemap.xml (ex. `https://site.com/sitemap.xml`) | Oui | — |
| Échantillon | Non | 50 URLs |
| Mode `all` (audit toutes les URLs du sitemap) | Non | off |

## Pipeline (5 étapes)

### Étape 1 — Récupérer le sitemap

```bash
SITEMAP_URL="<url>"
curl -sL "$SITEMAP_URL" | grep -oE '<loc>[^<]+</loc>' | sed -E 's|</?loc>||g' > /tmp/cwv-urls.txt
wc -l /tmp/cwv-urls.txt
```

**IMPORTANT (compat BSD/macOS)** : utiliser `sed -E 's|</?loc>||g'` et **PAS** `sed 's|</\?loc>||g'` — le `\?` ne marche pas en sed BSD (macOS) et laisse les balises intactes silencieusement.

**Si le sitemap est un index** (contient `<sitemapindex>` au lieu de `<urlset>`), récupérer le premier sitemap enfant et l'utiliser :

```bash
FIRST_CHILD=$(curl -sL "$SITEMAP_URL" | grep -oE '<loc>[^<]+</loc>' | sed -E 's|</?loc>||g' | head -1)
curl -sL "$FIRST_CHILD" | grep -oE '<loc>[^<]+</loc>' | sed -E 's|</?loc>||g' > /tmp/cwv-urls.txt
```

### Étape 2 — Échantillonner

```bash
TOTAL=$(wc -l < /tmp/cwv-urls.txt | tr -d ' ')
if [ "$TOTAL" -gt 50 ] && [ "$MODE" != "all" ]; then
  head -50 /tmp/cwv-urls.txt > /tmp/cwv-sample.txt
else
  cp /tmp/cwv-urls.txt /tmp/cwv-sample.txt
fi
wc -l /tmp/cwv-sample.txt
```

Les 50 premières URLs du sitemap sont généralement les plus prioritaires (homepage, hubs, top articles). Si l'utilisateur demande `all`, auditer toutes les URLs.

### Étape 3 — Lancer Lighthouse mobile en parallèle (3 workers)

```bash
rm -rf /tmp/cwv-results /tmp/cwv-errors.log
mkdir -p /tmp/cwv-results
cat /tmp/cwv-sample.txt | xargs -I {} -P 3 sh -c '
  url="$1"
  hash=$(echo -n "$url" | md5)
  lighthouse "$url" \
    --output=json \
    --output-path="/tmp/cwv-results/${hash}.json" \
    --only-categories=performance \
    --form-factor=mobile \
    --screenEmulation.mobile=true \
    --chrome-flags="--headless=new --no-sandbox" \
    --quiet 2>/dev/null || echo "FAIL: $url" >> /tmp/cwv-errors.log
' _ {}
echo "Results: $(ls /tmp/cwv-results/ 2>/dev/null | wc -l | tr -d ' ')"
[ -f /tmp/cwv-errors.log ] && echo "Errors:" && cat /tmp/cwv-errors.log
```

**IMPORTANT (zsh-safe)** : utiliser `rm -rf /tmp/cwv-results && mkdir -p /tmp/cwv-results` et **PAS** `rm -f /tmp/cwv-results/*` — le glob vide fait gueuler zsh (`no matches found`) et l'exit code 1 peut interrompre la suite.

**`--headless=new`** (et pas `--headless` legacy) — plus stable depuis Chrome 109+, requis pour Lighthouse 13.

**Compter** : ~15-30 secondes par URL avec 3 workers parallèles. 50 URLs ≈ 5-10 min.

### Étape 4 — Parser les résultats

Pour chaque JSON dans `/tmp/cwv-results/`, extraire avec jq (chemin Lighthouse 13) :

```bash
for f in /tmp/cwv-results/*.json; do
  jq '{
    url: .finalDisplayedUrl,
    lcp_ms: (.audits."largest-contentful-paint".numericValue // 0),
    cls: (.audits."cumulative-layout-shift".numericValue // 0),
    tbt_ms: (.audits."total-blocking-time".numericValue // 0),
    score: ((.categories.performance.score // 0) * 100),
    lcp_element: [.audits."lcp-breakdown-insight".details.items[]? | select(.type == "node") | {selector, snippet, nodeLabel}] | .[0],
    lcp_breakdown: [.audits."lcp-breakdown-insight".details.items[]? | select(.type == "table") | .items[]? | {subpart, label, duration_ms: .duration}],
    opportunities: [.audits | to_entries[] | select(.value.details.type == "opportunity" and (.value.details.overallSavingsMs // 0) > 100) | {id: .key, title: .value.title, savings_ms: .value.details.overallSavingsMs}] | sort_by(-.savings_ms) | .[0:5]
  }' "$f"
done | jq -s '.' > /tmp/cwv-parsed.json
echo "Parsed: $(jq 'length' /tmp/cwv-parsed.json) URLs"
```

**Chemins clés Lighthouse 13** :
- URL finale : `.finalDisplayedUrl` (et **PAS** `.finalUrl` qui peut être null)
- LCP element : `.audits."lcp-breakdown-insight".details.items[] | select(.type == "node")` (et **PAS** `.audits."largest-contentful-paint-element"` qui est null depuis LH 13)
- LCP breakdown (TTFB / render delay / load delay) : `.audits."lcp-breakdown-insight".details.items[] | select(.type == "table") | .items[]` — utile pour diagnostiquer **où** se passe le temps LCP

**Seuils Google (à appliquer pour le verdict)** :

| Métrique | Good | Needs Improvement | Poor |
|----------|------|-------------------|------|
| LCP | < 2500 ms | 2500-4000 ms | > 4000 ms |
| CLS | < 0.1 | 0.1-0.25 | > 0.25 |
| TBT (proxy INP) | < 200 ms | 200-600 ms | > 600 ms |
| Score perf | ≥ 90 | 50-89 | < 50 |

### Étape 5 — Produire le rapport markdown

Voir section "Output obligatoire" ci-dessous.

### Étape 4bis — Détection automatique du pattern "redirect sitemap"

Si l'opportunity `redirects` apparaît sur **>50% des pages auditées** avec un gain moyen >300ms : c'est quasi-certainement un mismatch de trailing slash entre le sitemap et le serveur.

```bash
REDIRECT_COUNT=$(jq -r '.[] | .opportunities[] | select(.id == "redirects") | .id' /tmp/cwv-parsed.json | wc -l | tr -d ' ')
TOTAL=$(jq 'length' /tmp/cwv-parsed.json)
if [ "$REDIRECT_COUNT" -gt $((TOTAL / 2)) ]; then
  echo "⚠️ ALERTE : $REDIRECT_COUNT/$TOTAL pages ont une redirection. Vérifier le trailing slash du sitemap vs serveur."
fi
```

Ce pattern doit être mis EN TÊTE du rapport — c'est le quick win le plus rentable quand il est présent.

## Output obligatoire

```markdown
# Audit Core Web Vitals — [domaine] — [date]

**Échantillon** : X URLs auditées / Y URLs dans le sitemap. **Form factor** : mobile.
**Note importante** : INP non mesurable en local par Lighthouse (métrique field). TBT utilisé comme proxy.

## Scores par URL

| URL | LCP | CLS | TBT | Score | Verdict |
|-----|-----|-----|-----|-------|---------|
| / | 1.8s ✅ | 0.05 ✅ | 150ms ✅ | 92 | Good |
| /blog/article-x | 4.2s ❌ | 0.18 ⚠️ | 720ms ❌ | 38 | Poor |
| /categorie/y | — | — | — | — | ERROR (timeout) |

Légende : ✅ Good — ⚠️ Needs Improvement — ❌ Poor

## Synthèse

- **X/Y pages** sont en Poor (score < 50)
- **X/Y pages** ont un LCP > 4s (critique pour le ranking)
- **X/Y pages** ont un CLS > 0.25 (UX dégradée)
- Score perf médian : X / Score perf moyen : Y

## Problèmes récurrents site-wide

Opportunities Lighthouse qui apparaissent sur > 30% des pages auditées :

1. **Unused JavaScript** — X pages affectées — gain moyen estimé : Xs
   - Cause probable : [bundle non tree-shaké / scripts tiers (analytics, chat, ads)]
2. **Render-blocking resources** — X pages — gain moyen : Xs
3. **Image sizing / Properly size images** — X pages — gain moyen : Xs
4. **Largest Contentful Paint image** — X pages — gain moyen : Xs
5. **Reduce JavaScript execution time** — X pages — gain moyen : Xs

## Top 5 pages à corriger en priorité

(triées par score perf croissant)

### 1. /blog/article-x (score : 38)
- **LCP** : 4.2s ❌ — élément : `<img class="hero" src="...">` (selector : `main > article > img.hero`)
  - Breakdown : TTFB 800ms / load delay 1200ms / load duration 1500ms / render delay 700ms
  - → Le gros du temps part en **load delay** (image découverte tardivement)
- **CLS** : 0.18 ⚠️ — shifts probables : pub Adsense / images sans dimensions
- **TBT** : 720ms ❌ — JS bloquant : [scripts identifiés]

**Plan de correction** :
1. Préload de l'image hero LCP + `fetchpriority="high"`
2. Définir width/height explicites sur toutes les images du contenu
3. Defer les scripts non critiques (analytics, chat) — passer en `loading="lazy"` les iframes

**Lecture du LCP breakdown** :
- TTFB > 600ms → problème serveur / CDN / cache
- Load delay > 200ms → image LCP pas dans le HTML initial (lazy, dans CSS background, JS-injectée)
- Load duration > 500ms → image trop lourde ou pas servie en format moderne (avif/webp)
- Render delay > 300ms → blocage CSS/JS render-blocking ou hydratation lente

### 2. ...

(répéter pour les 5 plus mauvaises)

## Limites de l'audit

- INP non mesuré (Lighthouse lab → TBT comme proxy)
- Pas de variation par device réel (émulation Moto G Power)
- Pas de mesure sur connexion réelle (throttling 4G simulé)
- Pour les vraies données field : croiser avec PageSpeed Insights API ou CrUX
```

## Règles absolues

- **Mobile uniquement** — Google indexe en mobile-first. Pas de desktop par défaut.
- **INP non mesuré** — toujours préciser dans le rapport que TBT est utilisé comme proxy.
- **Pas de score halluciné** — si une URL échoue (timeout, 404, JS error), la marquer `ERROR` dans le tableau, pas `0` ou un placeholder.
- **Pas de reco sans opportunity Lighthouse correspondante** — ne pas inventer "il faudrait optimiser X" si Lighthouse ne l'a pas remonté pour cette page.
- **Pas d'installation silencieuse** — si Lighthouse manque, demander à l'utilisateur de l'installer.
- **Échantillon honnête** — si l'échantillon est de 50/2000 URLs, le dire clairement, ne pas extrapoler "tout le site" à partir de 2,5% du contenu.

## Edge cases

- **Sitemap index** (sitemapindex au lieu d'urlset) → récupérer le premier child + l'utiliser, signaler à l'utilisateur qu'il peut spécifier un sitemap enfant précis pour cibler.
- **Sitemap protégé** (auth, 403) → demander à l'utilisateur un export local des URLs.
- **Plus de 500 URLs avec mode `all`** → confirmer avec l'utilisateur (sera lent : 500 URLs × ~10s/3 workers ≈ 30 min).
- **Site en SPA ou auth-walled** → certaines pages peuvent timeout, c'est normal, les marquer ERROR.
- **Sitemap multi-langues** → garder tel quel, le rapport montrera les écarts par locale.