diff --git a/.github/skills/add-provider/SKILL.md b/.github/skills/add-provider/SKILL.md new file mode 100644 index 0000000..5a772f5 --- /dev/null +++ b/.github/skills/add-provider/SKILL.md @@ -0,0 +1,284 @@ +--- +name: add-provider +description: > + Créer la fiche d'un nouveau fournisseur cloud dans le répertoire cloudlandscape. + Utiliser ce skill quand l'utilisateur fournit l'URL d'un site commercial cloud et + demande de générer la fiche du provider, d'ajouter un fournisseur à l'annuaire, + de remplir le template provider, ou mentionne un opérateur cloud européen à + référencer. Déclencher aussi si l'utilisateur donne une URL et dit « ajoute ce + provider », « crée la fiche », « génère index.md et index.en.md » ou équivalent. + Ce skill scrape le site, mappe les services et certifications aux taxonomies du + projet, et génère les deux fichiers TOML+Markdown bilingues prêts à être écrits + dans content/providers/{slug}/. +--- + +# Add Provider + +Skill pour créer les fiches bilingues d'un fournisseur cloud dans cloudlandscape. + +**Entrée :** une URI de site commercial (ex. `https://www.scaleway.com`) +**Sortie :** deux fichiers prêts à écrire dans `content/providers/{slug}/` : +- `index.md` — fiche en **français** +- `index.en.md` — fiche en **anglais** + +--- + +## Étape 1 — Recherche web + +### 1.1 Scraper le site du fournisseur + +Fetcher les pages dans l'ordre suivant. S'arrêter dès que tous les champs requis (§ Étape 2) sont couverts. + +| Priorité | Chemins à essayer | +|----------|------------------| +| Haute | `/`, `/about`, `/company`, `/en`, `/fr` | +| Moyenne | `/products`, `/services`, `/cloud`, `/solutions`, `/platform` | +| Moyenne | `/certifications`, `/compliance`, `/trust`, `/security`, `/legal` | +| Basse | `/pricing`, `/datacenters`, `/infrastructure`, `/careers` | + +Si `fetch` est bloqué (403, redirect, JS-only), essayer `browser/open` + `browser/snapshot -i`. + +### 1.2 Vérifier les certifications dans les registres officiels + +**Ne jamais inclure une certification sans la vérifier ici.** Chercher le nom du provider dans chaque registre pertinent. + +| Certification | Registre officiel | +|---------------|------------------| +| `secnumcloud` | `https://cyber.gouv.fr` — rechercher le nom du provider + « SecNumCloud » | +| `hds` | `https://esante.gouv.fr` — liste des organismes certifiés HDS | +| `eucs` | `https://www.enisa.europa.eu` — liste EUCS candidates / certifiées | + +> **Note :** `ssi.gouv.fr` redirige désormais vers `cyber.gouv.fr`. Toujours utiliser le nouveau domaine dans `certification_links`. + +--- + +## Étape 2 — Extraire les données du provider + +Construire un enregistrement depuis le contenu scrappé. Les champs **requis** doivent avoir une valeur réelle avant de générer les fichiers ; les champs optionnels ne sont inclus que s'ils sont trouvés. + +| Champ | Type | Requis ? | Contrainte | Notes | +|-------|------|----------|-----------|-------| +| `title` | string | oui | 1–100 car. | Nom de marque officiel, casse exacte (ex. `OVHcloud`, `3DS Outscale`) | +| `slug` | string | oui | `^[a-z0-9]+(-[a-z0-9]+)*$`, max 50 | kebab-case dérivé du nom | +| `description` (FR) | string | oui | 50–200 car. | Proposition de valeur en français | +| `description` (EN) | string | oui | 50–200 car. | Proposition de valeur en anglais — **traduction distincte, pas une copie** | +| `country` | string | oui | casse normale | Pays d'immatriculation (ex. `France`, `Germany`) | +| `headquarters` | string | oui | `Ville, Pays` | ex. `Paris, France` | +| `website` | string | oui | commence par `https://` | URL canonique de la page d'accueil | +| `datacenter_locations` | tableau | oui | min 1 élément, pas de doublons | Noms de villes ou régions | +| `founded` | entier | non | 1950–2030, sans guillemets | Année de fondation ; omettre si inconnu | +| `services` | tableau | oui | min 1, slugs valides uniquement (§ Étape 3) | Seulement les services réellement proposés | +| `certifications` | tableau | non | slugs valides uniquement (§ Étape 4) | Seulement les certifications réellement détenues | +| `certification_links` | objet | non | valeurs URI | URLs directes vers les attestations officielles | + +--- + +## Étape 3 — Mapper les services vers la taxonomie + +**N'inclure que les slugs des services que le provider propose réellement.** Ne pas déduire depuis du texte marketing générique. + +| Slug | Inclure si le provider propose… | +|------|--------------------------------| +| `compute` | Machines virtuelles (VMs), bare metal, serveurs dédiés | +| `kubernetes` | Service Kubernetes managé (K8s-as-a-Service) | +| `object-storage` | Stockage objet compatible S3, blob storage | +| `database` | Bases de données managées (SQL, NoSQL, Redis, etc.) | +| `paas` | Platform-as-a-Service — hébergement applicatif, fonctions serverless, environnements d'exécution | +| `caas` | Containers-as-a-Service — registre de conteneurs, runtime conteneur hébergé (distinct du K8s managé) | +| `iam` | Identity & Access Management — SSO, LDAP, OAuth2, annuaire d'entreprise | +| `api-gateway` | Gestion d'API, gateway, gestion du trafic, rate limiting | + +> Ces huit slugs sont déclarés dans `zola.toml` et appliqués par `docs/provider-schema.json`. **Ne jamais inventer de nouveaux slugs.** + +--- + +## Étape 4 — Mapper les certifications vers la taxonomie + +**Vérifier chaque certification dans le registre officiel avant de l'inclure.** + +| Slug | Nom complet | Émetteur | Vérification | +|------|-------------|----------|-------------| +| `secnumcloud` | Qualification SecNumCloud | ANSSI (France) | Chercher le nom du provider sur `cyber.gouv.fr` | +| `hds` | Hébergeur de Données de Santé | ANS / Ministère de la Santé | Chercher le nom du provider sur `esante.gouv.fr` | +| `eucs` | EU Cybersecurity Certification Scheme | ENISA (UE) | Chercher sur le site ENISA ou les communiqués officiels | + +Règles supplémentaires : +- Si non trouvé dans le registre officiel → **ne pas inclure** le slug ; le signaler dans la sortie comme non vérifié. +- Les clés de `[extra.certification_links]` doivent être un **sous-ensemble strict** des slugs dans `taxonomies.certifications`. Ne jamais ajouter un lien pour une certification absente du tableau taxonomie. + +--- + +## Étape 5 — Générer les deux fichiers + +### Règles du frontmatter TOML (critiques) + +- Les délimiteurs de frontmatter sont `+++` (TOML). **Ne jamais utiliser `---` (YAML).** +- `description` : 50–200 caractères (contrainte du schéma JSON — `mise run validate` échouera sinon). +- `slug` : doit correspondre à `^[a-z0-9]+(-[a-z0-9]+)*$`. +- `countries` : tableau de chaînes en **minuscules**, ex. `["france"]`, jamais `["France"]`. +- `datacenter_locations` : minimum 1 élément, pas de doublons. +- `founded` : entier brut, **sans guillemets**. +- Les deux fichiers partagent des valeurs de frontmatter **identiques**, sauf `description` (spécifique à la langue). +- `[extra.certification_links]` est une sous-table TOML ; ne l'inclure que si au moins un lien existe. + +### Corps Markdown — `index.md` (français) + +Rédiger **2 à 4 paragraphes en français**, dans cet ordre : + +1. **Présentation générale** — activité de l'entreprise, année de fondation, appartenance à un groupe. +2. **Catalogue de services** — principaux services cloud, spécificités notables (régions, stack open-source, modèle tarifaire). +3. **Certifications & conformité** — certifications détenues et ce qu'elles garantissent. Omettre ce paragraphe si aucune certification n'est confirmée. +4. **Formation / certifications utilisateurs** *(optionnel)* — seulement si le provider propose des programmes de formation ou des certifications professionnelles à ses clients. + +Ton : factuel, neutre, encyclopédique. Pas de superlatifs marketing. + +### Corps Markdown — `index.en.md` (anglais) + +Même structure, rédigée en **anglais**. Le champ `description` doit aussi être en anglais. + +--- + +## Étape 6 — Format de sortie + +Présenter la sortie avec exactement cette structure : + +``` +### Chemins cibles +- content/providers/{slug}/index.md +- content/providers/{slug}/index.en.md + +### index.md ++++ +(frontmatter TOML complet) ++++ + +(corps Markdown en français) + +--- + +### index.en.md ++++ +(frontmatter TOML complet avec description en anglais) ++++ + +(corps Markdown en anglais) + +--- + +### Champs non vérifiés ou manquants +- Lister tout champ non confirmé avec la raison. + Exemple : `certifications.hds` — aucune entrée trouvée sur esante.gouv.fr pour ce provider ; + exclu jusqu'à vérification manuelle. + +### Étape suivante +Exécuter : mise run validate +``` + +--- + +## Exemple de sortie correcte + +### `index.md` (français) + +``` ++++ +title = "Scaleway" +slug = "scaleway" +description = "Fournisseur cloud français proposant des solutions d'infrastructure cloud souveraine" + +[taxonomies] +services = ["compute", "kubernetes", "object-storage", "database", "paas", "caas"] +certifications = ["secnumcloud", "hds"] +countries = ["france"] + +[extra] +country = "France" +headquarters = "Paris, France" +website = "https://www.scaleway.com" +datacenter_locations = ["Paris", "Amsterdam", "Warsaw"] +founded = 1999 + +[extra.certification_links] +secnumcloud = "https://cyber.gouv.fr/enjeux-technologiques/cloud/" +hds = "https://esante.gouv.fr/produits-services/hds" ++++ + +Scaleway est un acteur majeur du cloud français, filiale du groupe Iliad. Fondée en 1999, la +société propose une gamme complète de services cloud incluant des instances compute, du stockage +objet S3-compatible, des clusters Kubernetes managés et des bases de données managées. + +Avec ses datacenters situés en France, aux Pays-Bas et en Pologne, Scaleway offre une alternative +souveraine aux hyperscalers américains. La plateforme inclut également des services PaaS et CaaS, +un registre de conteneurs, des fonctions serverless et une API cohérente. + +Qualifiée SecNumCloud par l'ANSSI et certifiée HDS, Scaleway répond aux exigences de sécurité +les plus strictes pour les administrations françaises et les acteurs du secteur de la santé. +``` + +### `index.en.md` (anglais) + +``` ++++ +title = "Scaleway" +slug = "scaleway" +description = "French cloud provider offering sovereign cloud infrastructure solutions across Europe" + +[taxonomies] +services = ["compute", "kubernetes", "object-storage", "database", "paas", "caas"] +certifications = ["secnumcloud", "hds"] +countries = ["france"] + +[extra] +country = "France" +headquarters = "Paris, France" +website = "https://www.scaleway.com" +datacenter_locations = ["Paris", "Amsterdam", "Warsaw"] +founded = 1999 + +[extra.certification_links] +secnumcloud = "https://cyber.gouv.fr/enjeux-technologiques/cloud/" +hds = "https://esante.gouv.fr/produits-services/hds" ++++ + +Scaleway is a major French cloud provider and a subsidiary of the Iliad group. Founded in 1999, +the company offers a complete range of cloud services including compute instances, S3-compatible +object storage, managed Kubernetes clusters, and managed databases. + +With datacenters located in France, the Netherlands, and Poland, Scaleway provides a sovereign +alternative to American hyperscalers. The platform also includes PaaS and CaaS services, a +container registry, serverless functions, and a consistent API. + +Holding the SecNumCloud qualification from ANSSI and the HDS certification, Scaleway meets the +strictest security requirements for French government organisations and healthcare providers. +``` + +--- + +## Checklist de validation + +Vérifier chaque point avant de présenter la sortie : + +- [ ] `slug` correspond à `^[a-z0-9]+(-[a-z0-9]+)*$` (lettres minuscules, chiffres, tirets ; pas de tiret en début ou fin) +- [ ] `description` entre **50 et 200 caractères** dans les deux fichiers +- [ ] Les deux `description` sont **différentes** (vraies traductions FR et EN) +- [ ] Les délimiteurs de frontmatter sont `+++`, pas `---` +- [ ] Seulement des slugs `services` valides : `compute`, `kubernetes`, `object-storage`, `database`, `paas`, `caas`, `iam`, `api-gateway` +- [ ] Seulement des slugs `certifications` valides : `secnumcloud`, `hds`, `eucs` +- [ ] Chaque certification a été **vérifiée dans son registre officiel** avant inclusion +- [ ] Les clés de `[extra.certification_links]` sont un sous-ensemble strict de `taxonomies.certifications` +- [ ] Les valeurs de `countries` sont en **minuscules** (`"france"`, pas `"France"`) +- [ ] `datacenter_locations` contient **au moins un** élément +- [ ] `website` commence par `https://` +- [ ] `founded` est un **entier brut** (sans guillemets) entre 1950 et 2030, ou le champ est omis +- [ ] Le corps de `index.md` est entièrement rédigé en **français** +- [ ] Le corps de `index.en.md` est entièrement rédigé en **anglais** +- [ ] Corps de 2 à 4 paragraphes ; ton factuel et neutre ; aucun superlatif marketing +- [ ] `mise run validate` termine avec le code 0 + +--- + +## Références + +- `references/provider-template.md` — Template annoté complet avec toutes les valeurs valides +- `docs/provider-schema.json` — Schéma JSON appliqué par `mise run validate` +- `zola.toml` — Déclaration des taxonomies (`services`, `certifications`, `countries`) diff --git a/.github/skills/add-provider/references/provider-template.md b/.github/skills/add-provider/references/provider-template.md new file mode 100644 index 0000000..2f5b287 --- /dev/null +++ b/.github/skills/add-provider/references/provider-template.md @@ -0,0 +1,152 @@ +# Provider Template — Référence complète + +Template annoté pour la création d'une fiche provider dans cloudlandscape. +Ce fichier est une référence ; le template TOML source est dans `docs/provider-template.yaml`. + +--- + +## Frontmatter TOML — `index.md` (français) + +```toml ++++ +# Nom officiel du provider (casse exacte de la marque) +title = "Provider Name" + +# Identifiant URL en kebab-case minuscule +slug = "provider-name" + +# Description courte en FRANÇAIS — 50 à 200 caractères +description = "Description courte du fournisseur cloud et de sa proposition de valeur principale" + +[taxonomies] +# Services réellement proposés — choisir parmi les 8 slugs valides uniquement +services = [ + "compute", # Machines virtuelles, bare metal + "kubernetes", # Kubernetes managé + "object-storage", # Stockage objet compatible S3 + "database", # Bases de données managées + "paas", # Platform as a Service + "caas", # Containers as a Service + "iam", # Identity & Access Management + "api-gateway" # Gestion d'API et gateway +] + +# Certifications détenues — choisir parmi les 3 slugs valides uniquement +# NE PAS inclure si le provider ne détient pas la certification +certifications = [ + "secnumcloud", # Qualification ANSSI (France) + "hds", # Hébergeur de Données de Santé (France) + "eucs" # EU Cybersecurity Certification Scheme +] + +# Pays de siège social — tableau de chaînes en minuscules +countries = ["france"] + +[extra] +# Pays d'origine (casse normale) +country = "France" + +# Siège social (Ville, Pays) +headquarters = "Paris, France" + +# Site web officiel (HTTPS obligatoire) +website = "https://www.provider-website.com" + +# Emplacements des datacenters (au moins 1) +datacenter_locations = ["Paris", "Amsterdam", "Frankfurt"] + +# Année de fondation (entier brut, sans guillemets) — optionnel +founded = 2010 + +# Liens vers les attestations officielles — optionnel +# Les clés doivent être un sous-ensemble strict de taxonomies.certifications +[extra.certification_links] +secnumcloud = "https://cyber.gouv.fr/..." +hds = "https://esante.gouv.fr/..." ++++ +``` + +--- + +## Frontmatter TOML — `index.en.md` (anglais) + +Identique à `index.md` sauf le champ `description` qui doit être en anglais : + +```toml +description = "Short description of the cloud provider and their main value proposition" +``` + +--- + +## Corps Markdown + +### `index.md` — 2 à 4 paragraphes en français + +**Paragraphe 1 — Présentation générale** +Activité de l'entreprise, année de fondation, appartenance à un groupe éventuel, positionnement marché. + +**Paragraphe 2 — Catalogue de services** +Principaux services cloud proposés, spécificités notables (régions, stack open-source, modèle tarifaire, segments de clientèle). + +**Paragraphe 3 — Certifications & conformité** *(si applicable)* +Certifications détenues et ce qu'elles garantissent pour les clients français et européens. + +**Paragraphe 4 — Formation / certifications utilisateurs** *(optionnel)* +Programmes de formation ou certifications professionnelles proposées aux clients. + +### `index.en.md` — Même structure en anglais + +--- + +## Valeurs de taxonomie valides + +### Services (8 slugs) + +| Slug | Inclure si le provider propose… | +|------|--------------------------------| +| `compute` | VMs, bare metal, serveurs dédiés | +| `kubernetes` | K8s managé (pas seulement des VMs pour faire tourner K8s) | +| `object-storage` | Stockage objet compatible S3 / blob | +| `database` | DBaaS — SQL, NoSQL, Redis, etc. | +| `paas` | Hébergement applicatif, serverless, fonctions | +| `caas` | Registre de conteneurs, runtime conteneur hébergé | +| `iam` | SSO, LDAP, OAuth2, annuaire cloud | +| `api-gateway` | Gestion d'API, gateway, traffic management | + +### Certifications (3 slugs) + +| Slug | Registre de vérification | +|------|-------------------------| +| `secnumcloud` | `https://cyber.gouv.fr` | +| `hds` | `https://esante.gouv.fr` | +| `eucs` | `https://www.enisa.europa.eu` | + +--- + +## Contraintes du schéma JSON (`docs/provider-schema.json`) + +| Champ | Contrainte | +|-------|----------| +| `title` | 1–100 caractères | +| `slug` | `^[a-z0-9]+(-[a-z0-9]+)*$`, max 50 caractères | +| `description` | 50–200 caractères | +| `services` | min 1 élément, valeurs dans l'enum des 8 slugs | +| `certifications` | valeurs dans l'enum des 3 slugs | +| `countries` | chaînes minuscules | +| `datacenter_locations` | min 1 élément | +| `website` | URI commençant par `https?://` | +| `founded` | entier, 1950–2030 | + +--- + +## Erreurs fréquentes à éviter + +| Erreur | Correction | +|--------|----------| +| Utiliser `---` comme délimiteur de frontmatter | Utiliser `+++` (TOML, pas YAML) | +| `countries = ["France"]` | `countries = ["france"]` (minuscules) | +| `founded = "2010"` | `founded = 2010` (entier sans guillemets) | +| Copier la `description` FR dans le fichier EN | Écrire une vraie traduction en anglais | +| Inclure `certification_links.hds` sans `certifications = ["hds"]` | Les clés de `certification_links` doivent être dans `certifications` | +| Inventer un slug service inexistant | Utiliser uniquement les 8 slugs valides | +| `description` de 201 caractères | Raccourcir à 200 caractères maximum | diff --git a/.github/skills/lighthouse-ci-reader/SKILL.md b/.github/skills/lighthouse-ci-reader/SKILL.md new file mode 100644 index 0000000..253af0d --- /dev/null +++ b/.github/skills/lighthouse-ci-reader/SKILL.md @@ -0,0 +1,280 @@ +--- +name: lighthouse-ci-reader +description: > + Lire, analyser et traiter les rapports d'analyse Lighthouse CI (fichiers .json générés par + https://github.com/GoogleChrome/lighthouse-ci). Utiliser ce skill dès que l'utilisateur + mentionne un rapport Lighthouse, un fichier lhr.json, un résultat LHCI, des scores de + performance/accessibilité/SEO web, des Core Web Vitals, ou demande d'améliorer les résultats + d'un audit web automatisé. Déclencher aussi si l'utilisateur colle du JSON Lighthouse dans + le chat ou évoque des métriques comme LCP, CLS, INP, TBT, FCP, TTI, ou des audits échoués. +--- + +# Lighthouse CI Reader + +Skill pour lire et traiter les rapports Lighthouse CI (LHR — Lighthouse Result objects). + +## Formats de rapports supportés + +| Format | Description | +|--------|-------------| +| `lhr-*.json` | Rapport brut généré par `lhci collect` | +| `manifest.json` | Index LHCI listant plusieurs runs | +| JSON collé directement dans le chat | LHR complet ou partiel | +| HTML Lighthouse | Extraire le JSON embarqué (voir Étape 1, Cas 4) | + +--- + +## Étape 1 — Récupérer le rapport + +### Cas 1 : Fichier uploadé ou path fourni +```bash +cat /lhr-*.json | python3 -c " +import json, sys +lhr = json.load(sys.stdin) +print(json.dumps(lhr, indent=2)) +" +``` + +### Cas 2 : Répertoire `.lighthouseci/` +Lighthouse CI stocke ses rapports dans `.lighthouseci/` par défaut. +```bash +ls .lighthouseci/ +# Chercher les fichiers lhr-*.json +``` + +### Cas 3 : JSON collé dans le chat +Traiter directement le JSON fourni. + +### Cas 4 : Rapport HTML Lighthouse +Le JSON est embarqué dans le HTML sous forme de variable JS. +```bash +python3 - <<'EOF' +import re, json + +with open("report.html", "r", encoding="utf-8") as f: + html = f.read() + +# Lighthouse embarque le LHR dans window.__LIGHTHOUSE_JSON__ ou window.__LIGHTHOUSE_REPORT__ +match = re.search(r'window\.__LIGHTHOUSE_JSON__\s*=\s*(\{.*?\});\s*', html, re.DOTALL) +if not match: + match = re.search(r'window\.__LIGHTHOUSE_REPORT__\s*=\s*(\{.*?\});\s*', html, re.DOTALL) + +if match: + lhr = json.loads(match.group(1)) + with open("extracted-lhr.json", "w") as out: + json.dump(lhr, out, indent=2) + print("LHR extrait dans extracted-lhr.json") +else: + print("JSON non trouvé dans le HTML — vérifier le format du rapport") +EOF +``` + +--- + +## Étape 2 — Parser le LHR + +Structure clé du LHR : + +``` +lhr +├── finalUrl # URL auditée +├── fetchTime # Timestamp ISO-8601 +├── lighthouseVersion # Ex: "11.x" +├── configSettings +│ ├── formFactor # "mobile" | "desktop" +│ └── throttlingMethod # "simulate" | "devtools" | "provided" +├── categories # Scores par catégorie (0–1) +│ ├── performance { score, auditRefs } +│ ├── accessibility { score, auditRefs } +│ ├── best-practices { score, auditRefs } +│ ├── seo { score, auditRefs } +│ └── pwa { score, auditRefs } (optionnel) +└── audits # Résultats détaillés de chaque audit + └── + ├── score # null | 0–1 + ├── scoreDisplayMode # binary | numeric | informative | notApplicable | error + ├── displayValue # Valeur lisible + ├── numericValue # Valeur brute (ms, bytes, etc.) + ├── explanation # Raison de l'échec + ├── warnings # Avertissements + └── details # Tableau/liste d'éléments à corriger +``` + +**Score → Couleur :** +- `0.9–1.0` = ✅ Bon +- `0.5–0.89` = ⚠️ À améliorer +- `0–0.49` = ❌ Mauvais +- `null` = ℹ️ Informatif / non applicable + +--- + +## Étape 3 — Résumer les scores de catégories + +Toujours afficher le contexte d'audit. Les scores mobile et desktop ne sont pas comparables. +Extraire `configSettings.formFactor` et `configSettings.throttlingMethod` depuis le LHR. + +``` +URL : https://example.com +Date : 2024-01-15T10:30:00Z +Appareil : Mobile (simulation throttling) + +CATÉGORIE SCORE STATUT +Performance 72 ⚠️ +Accessibility 94 ✅ +Best Practices 83 ⚠️ +SEO 91 ✅ +PWA — N/A +``` + +--- + +## Étape 4 — Identifier les problèmes prioritaires + +### Script d'extraction des problèmes avec catégorisation + +```python +import json + +def extract_issues(lhr_path): + with open(lhr_path) as f: + lhr = json.load(f) + + issues = [] + for audit_id, audit in lhr['audits'].items(): + score = audit.get('score') + mode = audit.get('scoreDisplayMode') + + # Ignorer les audits non notés + if mode in ('informative', 'notApplicable', 'manual') or score is None: + continue + + if score < 0.9: + # Catégoriser par niveau de criticité + if score == 0 and mode == 'binary': + priority = 'critical' + elif score < 0.5: + priority = 'major' + else: + priority = 'minor' + + issues.append({ + 'id': audit_id, + 'title': audit.get('title', ''), + 'score': score, + 'priority': priority, + 'displayValue': audit.get('displayValue', ''), + 'explanation': audit.get('explanation', ''), + 'details': audit.get('details', {}), + }) + + # Trier : critical → major → minor, puis par score croissant + priority_order = {'critical': 0, 'major': 1, 'minor': 2} + issues.sort(key=lambda x: (priority_order[x['priority']], x['score'] or 0)) + return issues +``` + +### Niveaux de priorité + +| Niveau | Critère | +|--------|---------| +| 🔴 Critique | `score == 0` + `scoreDisplayMode == "binary"` | +| 🟠 Majeur | `score < 0.5` | +| 🟡 Mineur | `0.5 ≤ score < 0.9` | + +--- + +## Étape 5 — Présenter les résultats + +Pour chaque problème identifié : + +``` +❌ [titre de l'audit] + Score : 0.0 | Valeur : + Problème : + → Recommandation : + + Éléments concernés (si details.items exist) : + - url ou element + - impact estimé +``` + +Regrouper par catégorie : +1. **Performance** (LCP, INP, CLS, TBT, FCP, TTI, Speed Index) +2. **Accessibilité** (contraste, aria, sémantique) +3. **Best Practices** (HTTPS, console errors, dépréciations) +4. **SEO** (meta, liens, crawlabilité) + +--- + +## Étape 6 — Générer un plan d'action + +```markdown +## Plan d'action — [URL] — [date] + +### 🔴 Actions critiques (score == 0, binary) +1. ... + +### 🟠 Problèmes majeurs (score < 0.5) +1. ... + +### 🟡 Améliorations recommandées (score 0.5–0.89) +1. ... +``` + +Chaque action doit inclure : le problème, la correction, l'impact estimé sur le score. + +--- + +## Comparer plusieurs runs + +> ⚠️ Ne jamais comparer un rapport mobile et un rapport desktop. Vérifier `configSettings.formFactor` avant toute comparaison. + +```python +import json + +def compare_runs(path_before, path_after): + with open(path_before) as f: + before = json.load(f) + with open(path_after) as f: + after = json.load(f) + + # Vérifier la cohérence des appareils + form_before = before.get('configSettings', {}).get('formFactor', 'unknown') + form_after = after.get('configSettings', {}).get('formFactor', 'unknown') + if form_before != form_after: + print(f"⚠️ Comparaison {form_before} vs {form_after} — résultats non comparables") + + results = [] + for cat_id, cat_before in before.get('categories', {}).items(): + cat_after = after.get('categories', {}).get(cat_id) + if not cat_after: + continue + score_b = round((cat_before.get('score') or 0) * 100) + score_a = round((cat_after.get('score') or 0) * 100) + delta = score_a - score_b + status = '✅' if delta > 0 else ('⚠️' if delta < 0 else '=') + results.append({ + 'category': cat_before.get('title', cat_id), + 'before': score_b, + 'after': score_a, + 'delta': delta, + 'status': status, + }) + + return results + +# Affichage attendu : +# CATÉGORIE AVANT APRÈS DELTA +# Performance 65 78 +13 ✅ +# Accessibility 88 94 +6 ✅ +# Best Practices 75 75 = +# SEO 90 85 -5 ⚠️ +``` + +--- + +## Références + +- `references/audits-guide.md` — Guide détaillé par audit : description, impact, correction +- Source : https://github.com/GoogleChrome/lighthouse-ci +- Documentation LHR : https://github.com/GoogleChrome/lighthouse/blob/main/docs/understanding-results.md diff --git a/.github/skills/lighthouse-ci-reader/references/audits-guide.md b/.github/skills/lighthouse-ci-reader/references/audits-guide.md new file mode 100644 index 0000000..af8dc42 --- /dev/null +++ b/.github/skills/lighthouse-ci-reader/references/audits-guide.md @@ -0,0 +1,249 @@ +# Guide des audits Lighthouse — Corrections et recommandations + +## Table des matières +1. [Performance](#performance) +2. [Accessibilité](#accessibilité) +3. [Best Practices](#best-practices) +4. [SEO](#seo) + +--- + +## Performance + +### Core Web Vitals + +#### `largest-contentful-paint` (LCP) +**Seuils :** ✅ < 2.5s | ⚠️ 2.5–4s | ❌ > 4s +**Problème :** Le plus grand élément visible met trop de temps à s'afficher. +**Corrections :** +- Précharger l'image hero avec `` +- Utiliser un CDN pour les assets statiques +- Optimiser le TTFB serveur (cache, HTTP/2) +- Éviter le CSS/JS bloquant avant le LCP element +- Utiliser des formats modernes : WebP, AVIF + +#### `total-blocking-time` (TBT) +**Seuils :** ✅ < 200ms | ⚠️ 200–600ms | ❌ > 600ms +**Problème :** Le thread principal est bloqué par du JavaScript long. +**Corrections :** +- Diviser les long tasks (> 50ms) en chunks +- Utiliser `requestIdleCallback` ou `setTimeout(0)` pour le travail non urgent +- Lazy-loader les scripts tiers +- Réduire la taille des bundles JS (tree-shaking, code splitting) + +#### `interaction-to-next-paint` (INP) +**Seuils :** ✅ < 200ms | ⚠️ 200–500ms | ❌ > 500ms +**Problème :** Délai entre une interaction utilisateur (clic, frappe) et la mise à jour visuelle suivante. +**Note :** INP remplace FID comme Core Web Vital depuis mars 2024. +**Corrections :** +- Éviter les long tasks JS pendant les interactions (> 50ms) +- Utiliser `scheduler.yield()` pour céder le thread principal entre les tâches +- Différer le travail non essentiel avec `requestIdleCallback` +- Réduire la taille des composants React/Vue re-rendus inutilement +- Identifier les handlers d'événements lourds avec Chrome DevTools > Performance + +#### `cumulative-layout-shift` (CLS) +**Seuils :** ✅ < 0.1 | ⚠️ 0.1–0.25 | ❌ > 0.25 +**Problème :** Les éléments bougent de façon inattendue pendant le chargement. +**Corrections :** +- Définir `width` et `height` sur toutes les images/vidéos +- Réserver l'espace pour les publicités et iframes avec `min-height` +- Éviter d'insérer du contenu au-dessus du contenu existant +- Utiliser `font-display: optional` ou précharger les fonts + +#### `first-contentful-paint` (FCP) +**Seuils :** ✅ < 1.8s | ⚠️ 1.8–3s | ❌ > 3s +**Corrections :** +- Éliminer les ressources bloquant le rendu +- Minifier CSS critique et l'inliner dans `` +- Réduire les redirections + +#### `speed-index` +**Seuils :** ✅ < 3.4s | ⚠️ 3.4–5.8s | ❌ > 5.8s +**Corrections :** +- Optimiser le Critical Rendering Path +- Différer le CSS/JS non critique + +#### `interactive` (TTI) +**Seuils :** ✅ < 3.8s | ⚠️ 3.8–7.3s | ❌ > 7.3s +**Corrections :** +- Réduire le JS exécuté au démarrage +- Différer les scripts tiers non essentiels + +--- + +### Opportunités fréquentes + +#### `render-blocking-resources` +**Problème :** CSS ou JS dans `` bloquent l'affichage. +**Correction :** Ajouter `defer` ou `async` aux scripts. Inliner le CSS critique, différer le reste. + +#### `unused-javascript` +**Problème :** Des bytes de JS sont chargés mais jamais exécutés. +**Correction :** Code splitting, tree-shaking, lazy loading des routes. + +#### `unused-css-rules` +**Problème :** Du CSS inutilisé est chargé. +**Correction :** Utiliser PurgeCSS, supprimer les frameworks CSS inutilisés. + +#### `uses-optimized-images` +**Problème :** Images non optimisées (trop lourdes). +**Correction :** Compresser avec squoosh/imagemin, utiliser WebP/AVIF. + +#### `uses-responsive-images` +**Problème :** Images trop grandes par rapport à leur affichage. +**Correction :** Utiliser `srcset` et `sizes`, générer plusieurs résolutions. + +#### `efficient-animated-content` +**Problème :** GIFs animés au lieu de vidéos. +**Correction :** Convertir en MP4/WebM avec autoplay, loop, muted. + +#### `uses-text-compression` +**Problème :** Les ressources texte ne sont pas compressées (gzip/brotli). +**Correction :** Activer gzip ou brotli sur le serveur web. + +#### `uses-long-cache-ttl` +**Problème :** Les assets statiques ont un TTL de cache court. +**Correction :** Configurer `Cache-Control: max-age=31536000` + fingerprinting des fichiers. + +#### `eliminate-render-blocking-resources` +Voir `render-blocking-resources` ci-dessus. + +#### `server-response-time` (TTFB) +**Seuils :** ✅ < 600ms +**Correction :** Cache serveur, CDN, optimiser les requêtes DB, upgrade hébergement. + +#### `third-party-summary` +**Problème :** Scripts tiers (analytics, pub, chat) ralentissent la page. +**Correction :** Charger en `defer`, utiliser `rel="preconnect"` pour les domaines tiers. + +--- + +## Accessibilité + +#### `color-contrast` +**Problème :** Texte avec contraste insuffisant (ratio < 4.5:1 pour texte normal, < 3:1 pour grand texte). +**Correction :** Augmenter le contraste. Vérifier avec https://webaim.org/resources/contrastchecker/ + +#### `image-alt` +**Problème :** Images sans attribut `alt`. +**Correction :** Ajouter `alt="description"` à toutes les images. `alt=""` pour les images décoratives. + +#### `button-name` +**Problème :** Boutons sans libellé accessible. +**Correction :** Ajouter `aria-label` ou du texte visible dans le bouton. + +#### `link-name` +**Problème :** Liens sans texte descriptif. +**Correction :** Éviter les "cliquez ici", préférer des textes descriptifs. Utiliser `aria-label` si nécessaire. + +#### `label` +**Problème :** Champs de formulaire sans `