chore(seo-data): remove transient GSC+CrUX design spec + plan (shipped, documented, capitalized)
This commit is contained in:
@@ -1,383 +0,0 @@
|
||||
# Spec — Couche data GSC + CrUX pour `/seo` (+`/geo`) FULL
|
||||
|
||||
- **Date** : 2026-07-09
|
||||
- **Statut** : Design validé — prêt pour `/writing-plans`
|
||||
- **Auteur** : Bastien Chanot (design assisté)
|
||||
- **Repo** : claude-config (`~/Documents/claude`, symlinké dans `~/.claude` via `link.sh`)
|
||||
- **Cycle de vie** : document de travail **transitoire** — à supprimer une fois la feature livrée,
|
||||
documentée (`/document-release` ou `/doc`) et capitalisée (`decisions.md`). Ne pas conserver à long terme.
|
||||
|
||||
---
|
||||
|
||||
## 1. Contexte & objectif
|
||||
|
||||
L'audit comparatif entre les skills perso `/seo` + `/geo` et l'outil marketplace
|
||||
`agricidaniel/claude-seo` a isolé **un seul gap structurel** : les skills perso ne
|
||||
peuvent pas lire la **donnée Google réelle** d'un site (requêtes/positions/impressions
|
||||
de la Search Console, statut d'indexation, Core Web Vitals **terrain**). Ils se limitent
|
||||
à l'API PageSpeed anonyme (données *labo*) et à `WebSearch`.
|
||||
|
||||
**Objectif** : combler ce gap **sans** installer l'outil tiers (860 KB de Python, mainteneur
|
||||
unique, surface supply-chain + credentials OAuth à confier). On ajoute une couche data
|
||||
minimale, isolée, sous contrôle, branchée sur les analyzers existants.
|
||||
|
||||
**Ce que ça débloque concrètement** :
|
||||
- CWV **terrain** (CrUX, 75e percentile, mobile + desktop, historique) au lieu du seul labo.
|
||||
- Requêtes GSC : le pattern « **positions 4-10 à fort volume d'impressions** » = quick wins
|
||||
que les skills ne pouvaient pas voir.
|
||||
- Indexation réelle par URL (GSC URL Inspection) au lieu d'une déduction.
|
||||
|
||||
---
|
||||
|
||||
## 2. Principes directeurs (non négociables)
|
||||
|
||||
1. **Sécurité avant tout.** Secrets hors git, permissions `0600`, scope OAuth **lecture seule**,
|
||||
redaction systématique, aucun secret dans un rapport/log. Toute surface secret nouvelle sous
|
||||
`~/.claude` est **explicitement allowlistée** dans `.gitleaks.toml`.
|
||||
2. **Dégradation gracieuse (fail-open audit).** Creds absents / token révoqué / quota 429 →
|
||||
l'audit FULL **continue** en retombant sur PageSpeed anonyme + une action utilisateur.
|
||||
Jamais de crash.
|
||||
3. **Consentement OAuth one-shot, runs silencieux ensuite.** Le consentement navigateur ne se
|
||||
fait qu'au setup (ou à l'ajout d'un compte). Les audits suivants sont non-interactifs.
|
||||
4. **Multi-compte, zéro conflit.** Plusieurs comptes Google connectables ; deux audits de deux
|
||||
sites en simultané sont **isolés par construction** (compte + propriété = paramètres explicites
|
||||
par appel, jamais un état global mutable).
|
||||
5. **Isolation.** Le moteur ne connaît rien du SEO (rend du JSON) ; les analyzers ne connaissent
|
||||
rien d'OAuth (consomment du JSON). Deps Python isolées dans un venv dédié.
|
||||
|
||||
---
|
||||
|
||||
## 3. Décisions actées
|
||||
|
||||
| # | Décision | Choix |
|
||||
|---|---|---|
|
||||
| Auth GSC | OAuth2 installed-app, consentement one-time, refresh token stocké | **OAuth2** |
|
||||
| Scope data v1 | GSC Search Analytics + URL Inspection + CrUX field | **oui** (GA4/Ads/Indexing hors v1) |
|
||||
| Surface | Fold dans `/seo` (+`/geo`) FULL ; pas de nouveau skill d'audit | **oui** (setup = `make seo-connect`, pas un skill d'audit) |
|
||||
| Langage | Helper Python + `google-auth`, venv isolé | **oui** |
|
||||
| Multi-compte | Store keyé par compte ; sélection à **chaque** audit FULL | **oui** |
|
||||
| Persistance token | Auto-écriture idempotente dans le store (write-temp→rename) | **oui** |
|
||||
| Déclencheur consentement | `install.sh` (proposé) **et** `make seo-connect` (toujours dispo) | **oui, les deux** |
|
||||
| Gitleaks | Allowlister le token store (comme `~/.claude/.env`) | **oui** |
|
||||
|
||||
---
|
||||
|
||||
## 4. Architecture
|
||||
|
||||
### 4.1 Vue d'ensemble
|
||||
|
||||
```
|
||||
lib/seo-data/ ← LE MOTEUR (nouveau, isolé, sans logique SEO)
|
||||
├── fetch.sh entrypoint bash : source ~/.claude/.env → active venv → dispatch
|
||||
│ sous-commandes → JSON sur stdout → dégrade proprement si creds absents
|
||||
├── google_seo.py appels GSC (Search Analytics, URL Inspection, sites.list) + CrUX,
|
||||
│ refresh OAuth via google-auth, normalisation JSON
|
||||
├── connect.py consentement OAuth one-time (InstalledAppFlow) + écriture store
|
||||
├── tokenstore.py lecture/écriture atomique du store keyé (partagé par connect+fetch)
|
||||
├── requirements.txt deps épinglées : google-auth, google-auth-oauthlib, requests
|
||||
└── README.md contrat d'usage + sous-commandes
|
||||
|
||||
~/.claude/.venv-seo-data/ ← venv isolé (deps hors système, reproductible)
|
||||
~/.claude/seo-data/tokens.json ← store keyé par compte (0600, hors git)
|
||||
|
||||
CONSOMMATEURS (existants, patchés) :
|
||||
agents/seo-analyzer.md STEP 4 (CWV) → data terrain CrUX quand dispo ; nouvelle
|
||||
sous-section « Performance GSC » ; STEP 9 axe Technical nourri au réel.
|
||||
skills/seo/SKILL.md STEP 0 → sélection compte + propriété (main loop, interactif).
|
||||
```
|
||||
|
||||
### 4.2 Composants & frontières
|
||||
|
||||
| Unité | Rôle unique | Utilisée comment | Dépend de |
|
||||
|---|---|---|---|
|
||||
| `fetch.sh` | orchestre env→venv→python, dégrade, redige | `bash fetch.sh <cmd> --account … --property …` → JSON | `~/.claude/.env`, venv, tokenstore |
|
||||
| `google_seo.py` | appelle GSC + CrUX, normalise en JSON | appelé par `fetch.sh` | google-auth, requests |
|
||||
| `connect.py` | consentement OAuth one-time + découverte propriétés | `make seo-connect` / STEP 0 | google-auth-oauthlib, tokenstore |
|
||||
| `tokenstore.py` | I/O atomique du store keyé | importé par connect + google_seo | stdlib (json, os, fcntl) |
|
||||
| seo-analyzer (patch) | consomme le JSON, score, rapporte | inchangé pour l'utilisateur | `fetch.sh` (optionnel) |
|
||||
| seo/SKILL.md (patch) | sélectionne compte+propriété en STEP 0 | interactif, main loop | `fetch.sh accounts` |
|
||||
|
||||
---
|
||||
|
||||
## 5. Authentification & secrets (multi-compte)
|
||||
|
||||
### 5.1 Modèle OAuth
|
||||
|
||||
- **Type** : OAuth2 « installed app » (client Desktop créé dans la console GCP par l'utilisateur).
|
||||
- **Scope unique** : `https://www.googleapis.com/auth/webmasters.readonly` (GSC lecture seule).
|
||||
Least privilege strict : le token ne peut **rien modifier** sur GSC, révocable côté Google.
|
||||
- **Flow** : `connect.py` construit la config client **en mémoire** (`InstalledAppFlow.from_client_config`)
|
||||
à partir des vars d'env — **aucun `client_secret.json` sur disque**. Le consentement ouvre un
|
||||
serveur local + navigateur ; au retour, on obtient un `refresh_token` (durable) écrit dans le store.
|
||||
- **Runs d'audit** : `google_seo.py` échange le `refresh_token` contre un `access_token` **éphémère
|
||||
en mémoire** (jamais persisté). Donc **aucune écriture disque pendant un audit**.
|
||||
|
||||
### 5.2 Vault `~/.claude/.env` (app partagée + CrUX)
|
||||
|
||||
Ne contient que ce qui est **commun à tous les comptes** :
|
||||
|
||||
```
|
||||
# ── Google SEO data layer (lib/seo-data) ──
|
||||
# App OAuth Desktop partagée (console GCP → APIs & Services → Identifiants).
|
||||
# Scope demandé : webmasters.readonly. Setup : make seo-connect
|
||||
GOOGLE_OAUTH_CLIENT_ID=<votre-client-id.apps.googleusercontent.com>
|
||||
GOOGLE_OAUTH_CLIENT_SECRET=<votre-client-secret>
|
||||
# Clé API CrUX + PageSpeed (console GCP → clé API restreinte à ces deux APIs).
|
||||
# Get it: https://developer.chrome.com/docs/crux/api (bouton "Get a key")
|
||||
CRUX_API_KEY=<votre-cle-crux>
|
||||
```
|
||||
|
||||
Les **refresh tokens ne sont PAS ici** (multi-compte → store dédié).
|
||||
|
||||
### 5.3 Token store keyé
|
||||
|
||||
`~/.claude/seo-data/tokens.json`, permissions `0600`, dossier `0700` :
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"accounts": {
|
||||
"client-a": {
|
||||
"refresh_token": "<opaque>",
|
||||
"scopes": ["https://www.googleapis.com/auth/webmasters.readonly"],
|
||||
"granted_at": "2026-07-09T…",
|
||||
"properties": ["sc-domain:site-a.com", "https://www.site-a.com/"]
|
||||
},
|
||||
"client-b": { "…": "…" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Clé = **label choisi par l'utilisateur** au moment du `connect` (ex. `client-a`), **pas** l'email.
|
||||
Raison sécurité : keyer par email obligerait à élargir le scope OAuth (`userinfo.email`) juste pour
|
||||
l'identification. On reste à `webmasters.readonly` strict ; le label suffit à distinguer les comptes.
|
||||
Collision de label → `connect` demande confirmation (écraser / renommer).
|
||||
- `properties` = propriétés GSC accessibles (via `sites.list`, **déjà** dans le scope
|
||||
`webmasters.readonly`), pré-remplies au `connect` pour la sélection en STEP 0.
|
||||
- Écriture **uniquement** au `connect` (jamais pendant un audit), **atomique** : write vers
|
||||
`tokens.json.tmp` → `fsync` → `rename` ; verrou `fcntl` exclusif le temps de l'échange
|
||||
read-modify-write pour couvrir deux `connect` simultanés.
|
||||
|
||||
### 5.4 Gitleaks allowlist + gitignore
|
||||
|
||||
- `~/.claude/seo-data/tokens.json` vit **hors du repo** (le repo ne symlink que
|
||||
`hooks agents skills lib templates rules`). Il n'entre donc jamais en git directement.
|
||||
- Mais `make scan-secrets` (gitleaks) balaie `~/.claude` à la recherche de copies de secrets.
|
||||
On **ajoute une entrée d'allowlist** dans `.gitleaks.toml` `[allowlist].paths`, exactement
|
||||
comme `~/.claude/.env` l'est déjà :
|
||||
```toml
|
||||
# Token store OAuth de la couche seo-data — secret local légitime (BDR-026 pattern),
|
||||
# hors git, 0600. On l'allowliste pour ne pas noyer scan-secrets de faux positifs.
|
||||
'''(^|/)\.claude/seo-data/tokens\.json$''',
|
||||
```
|
||||
- `.gitignore` : ajouter `.venv-seo-data/` et `seo-data/tokens.json` par prudence (au cas où un
|
||||
chemin relatif les ferait apparaître sous le repo), en complément de l'exclusion `.env*` existante.
|
||||
|
||||
---
|
||||
|
||||
## 6. Sélection de compte & propriété (STEP 0, main loop)
|
||||
|
||||
Interactif → se déroule **dans le dispatcher `/seo` (main loop)**, jamais dans le subagent
|
||||
(qui ne peut pas interagir). Uniquement en **FULL** (LOCAL n'a pas de donnée live).
|
||||
|
||||
1. Lister les comptes connectés : `bash lib/seo-data/fetch.sh accounts` → JSON `{accounts:[…]}`.
|
||||
2. Présenter à l'utilisateur (label + propriétés découvertes) :
|
||||
```
|
||||
COMPTE GOOGLE pour cet audit FULL :
|
||||
1) client-a (sc-domain:site-a.com, https://www.site-a.com/)
|
||||
2) client-b (sc-domain:site-b.com)
|
||||
N) Connecter un nouveau compte (choisir un label)
|
||||
S) Ignorer (audit sans donnée GSC — CWV terrain via CrUX seulement)
|
||||
```
|
||||
3. « Connecter un nouveau compte » → demande un **label**, lance `connect.py` dans le main loop
|
||||
(consentement navigateur), puis auto-découverte des propriétés (`sites.list`) → re-liste.
|
||||
4. Compte choisi → si plusieurs propriétés, demander **laquelle** correspond au site audité.
|
||||
5. Le couple `(account, property)` retenu est passé **explicitement** dans le contexte de
|
||||
l'analyzer (bloc BUSINESS CONTEXT du dispatch, STEP 1), qui appellera
|
||||
`fetch.sh queries|inspect --account <a> --property <p>`.
|
||||
|
||||
CrUX ne demande pas de compte (clé API publique) → toujours tenté si `CRUX_API_KEY` présent,
|
||||
indépendamment du choix de compte.
|
||||
|
||||
---
|
||||
|
||||
## 7. Sûreté concurrentielle (2 sites en parallèle)
|
||||
|
||||
Garantie **par construction**, pas par verrou global :
|
||||
|
||||
- **Pas d'état « compte courant ».** Le compte + la propriété sont des **arguments explicites**
|
||||
de chaque `fetch.sh`. Deux audits (2 sessions Claude, ou 2 sites) ne partagent aucune variable
|
||||
mutable de sélection.
|
||||
- **Audits = lecture seule** du store. Les access tokens sont éphémères en mémoire, jamais écrits.
|
||||
Donc deux audits concurrents ne s'écrivent jamais dessus.
|
||||
- **Écriture = seulement au `connect`**, atomique (`tmp`→`fsync`→`rename`) sous verrou `fcntl`,
|
||||
pour couvrir le cas rare de deux consentements simultanés.
|
||||
- Le venv est en lecture seule à l'exécution (créé/maj uniquement par `make seo-connect`).
|
||||
|
||||
---
|
||||
|
||||
## 8. Périmètre data & mapping dans le rapport
|
||||
|
||||
| Donnée | Source | Sous-commande | Atterrit dans `SEO.md` |
|
||||
|---|---|---|---|
|
||||
| CWV terrain (LCP/INP/CLS 75e pct, mobile+desktop, historique) | CrUX API | `fetch.sh crux` | §2 Audit technique — **note primaire** ; PageSpeed labo gardé en secondaire diagnostic |
|
||||
| Requêtes (impressions, clics, CTR, position) | GSC Search Analytics | `fetch.sh queries` | §2/§8 — sous-section « Performance GSC » + **quick wins position 4-10** |
|
||||
| Pages (perf par URL) | GSC Search Analytics | `fetch.sh queries --dim page` | idem — top pages |
|
||||
| Indexation par URL | GSC URL Inspection | `fetch.sh inspect` | §2 indexabilité — **fait** vs déduction |
|
||||
|
||||
- **Scoring** : l'axe *Technical* (STEP 9 de `seo-analyzer`) se calcule sur le **terrain** quand
|
||||
dispo ; sinon labo (dégradation).
|
||||
- **Nouveau contenu de rapport** : une sous-section « Performance GSC (90 j) » dans §2, listant top
|
||||
requêtes + les quick wins position 4-10. Reste en **français**, cohérent avec l'existant.
|
||||
|
||||
---
|
||||
|
||||
## 9. Interface du moteur (`fetch.sh`)
|
||||
|
||||
Contrat stable que les analyzers consomment (JSON sur stdout, exit 0 même en dégradé) :
|
||||
|
||||
```bash
|
||||
fetch.sh accounts
|
||||
→ {"status":"ok","accounts":[{"label":"…","properties":[…],"granted_at":"…"}]} # [] si aucun compte
|
||||
|
||||
fetch.sh crux --url https://ex.com [--strategy mobile|desktop]
|
||||
→ {"status":"ok","source":"crux","lcp_p75_ms":…,"inp_p75_ms":…,"cls_p75":…} # métrique absente = clé omise
|
||||
→ {"status":"degraded","reason":"no_crux_key"|"no_field_data"|"rate_limited"}
|
||||
# 404 page-level → retry automatique origin-level avant de dégrader
|
||||
|
||||
fetch.sh queries --account client-a --property sc-domain:ex.com [--days 90] [--dim query|page]
|
||||
→ {"status":"ok","source":"gsc","rows":[{"key":"…","clicks":…,"impressions":…,"ctr":…,"position":…}]}
|
||||
→ {"status":"degraded","reason":"no_credentials"|"token_revoked"|"network_error"|"rate_limited"}
|
||||
|
||||
fetch.sh inspect --account client-a --property … --url https://ex.com/page
|
||||
→ {"status":"ok","source":"gsc","indexed":true,"coverage":"…","last_crawl":"…"}
|
||||
→ {"status":"degraded","reason":"…"}
|
||||
```
|
||||
|
||||
Règles : **jamais** de secret dans la sortie ; messages d'erreur génériques ; exit 0 en dégradé
|
||||
(l'analyzer décide de la suite), exit ≠ 0 uniquement sur mauvais usage (args invalides, exit 2).
|
||||
**Toute erreur imprévue** (HTTP 403/5xx, timeout, DNS) → `{"status":"degraded","reason":"unexpected_error"}`
|
||||
+ exit 0 — jamais de traceback, jamais de stdout vide. Tests : `SEO_DATA_ENV_FILE` permet de
|
||||
substituer le vault (les tests pointent `/dev/null` — jamais le vrai `~/.claude/.env`) ;
|
||||
`SEO_DATA_DEBUG=1` réactive stderr pour diagnostiquer.
|
||||
|
||||
---
|
||||
|
||||
## 10. Dégradation gracieuse & posture sécurité
|
||||
|
||||
- **Fail-open audit / fail-closed data** : creds manquants, refresh échoué, 429 → `{"status":"degraded"}`,
|
||||
exit 0. L'analyzer bascule sur PageSpeed anonyme et émet en §11 « Connecter GSC : `make seo-connect` »
|
||||
(réutilise la formulation `automation-catalog.md`).
|
||||
- **Redaction** : `fetch.sh` ne logge jamais les variables d'env ni le token ; stdout = JSON de
|
||||
données uniquement ; stderr = messages génériques.
|
||||
- **Least privilege** : scope `webmasters.readonly` seul ; clé CrUX restreinte à CrUX + PageSpeed.
|
||||
- **Supply-chain maîtrisée** : 3 libs Google officielles, **épinglées** dans `requirements.txt`,
|
||||
isolées dans un venv — surface auditablement listée, sans commune mesure avec l'outil tiers.
|
||||
- **Reprise sur token révoqué** : `doctor.sh` signale, message pointe vers `make seo-connect` pour re-consentir.
|
||||
|
||||
---
|
||||
|
||||
## 11. Install & déploiement (touch-list)
|
||||
|
||||
Séquence respectant l'ordre critique **`link.sh` → vault joignable → consentement** (évite le
|
||||
blocker connu : le symlink `~/.claude/.env` est créé par `link.sh`, absent sur machine fraîche ;
|
||||
tout lecteur de creds vise le **canonical `~/.claude/.env`**, pas `$REPO/.env`).
|
||||
|
||||
| Fichier | Modification |
|
||||
|---|---|
|
||||
| `.env.example` | Ajouter les 3 vars (client id/secret, CrUX key) au format existant (`# Used by:` / `# Get it:` + placeholder). **Seul fichier versionné touché côté secrets.** |
|
||||
| `install.sh` | Après `link.sh` (§5) et `claude login` (§3) : étape **optionnelle idempotente** (moule « Press Enter to connect… ») → si aucun compte dans le store, propose `make seo-connect` ; skip sinon. |
|
||||
| `Makefile` | Cible user-facing `seo-connect` (venv + `pip install -r lib/seo-data/requirements.txt` + `connect.py`, rejouable) **et** extension de la cible `test` pour découvrir `lib/seo-data/*.test.sh` (le test vit hors `lib/tests/`, gaté par `config-protection.sh`). |
|
||||
| `doctor.sh` | Nouveau check (lit `~/.claude/.env` canonical) : venv + deps présents ? au moins un compte dans le store ? `CRUX_API_KEY` présent ? → **PASS / WARN, jamais fatal**. |
|
||||
| `.gitleaks.toml` | Allowlist du token store (cf. §5.4). |
|
||||
| `.gitignore` | Ajouter `.venv-seo-data/` et `seo-data/tokens.json` (ceinture + bretelles). |
|
||||
|
||||
---
|
||||
|
||||
## 12. Tests
|
||||
|
||||
`lib/seo-data/seo-data.test.sh`, convention du repo (`tf`/`tr_`/`tn` + compteurs PASS/FAIL),
|
||||
**sans appel réseau**. Placé sous `lib/seo-data/` (co-localisé, **hors `lib/tests/`** que
|
||||
`hooks/config-protection.sh` protège comme dossier-garde) ; `make test` le découvre via le glob
|
||||
ajouté en Task 6. En TDD : `bash lib/seo-data/seo-data.test.sh`.
|
||||
|
||||
- Parsing des sous-commandes/args de `fetch.sh` (bons/mauvais usages, exit codes).
|
||||
- Parsing de forme JSON sur **fixtures commitées** (réponses GSC/CrUX mockées) → shape attendue.
|
||||
- **Redaction** : erreur simulée (token bidon) → aucune valeur secrète dans stdout/stderr.
|
||||
- **Dégradation** : creds absents → `{"status":"degraded"}` + **exit 0** (pas 1).
|
||||
- Isolement : deux invocations `--account` différentes → sélections indépendantes (pas d'état partagé).
|
||||
|
||||
Fixtures sous `lib/seo-data/fixtures/` (réponses synthétiques, aucun vrai secret/PII).
|
||||
|
||||
---
|
||||
|
||||
## 13. Hors périmètre (YAGNI v1)
|
||||
|
||||
- GA4 (trafic organique), Google Ads / Keyword Planner, Indexing API.
|
||||
- Modification de `geo-analyzer` (le GSC pourrait plus tard éclairer quelles requêtes déclenchent
|
||||
des AI Overviews — v2).
|
||||
- Audit multi-propriétés en un seul run (une propriété par audit en v1).
|
||||
- Monitoring/drift dans le temps (SQLite) — l'outil tiers le fait ; hors scope v1.
|
||||
- CrUX History API (tendance 25 semaines) — v1 = snapshot p75 seulement.
|
||||
- Routage de l'appel PageSpeed via `fetch.sh` avec `CRUX_API_KEY` (dé-quota) — **rejeté en v1
|
||||
pour raison sécurité** : passer la clé à l'analyzer l'exposerait dans le contexte du subagent
|
||||
(ligne de commande curl → risque de fuite dans rapport/log). v2 : sous-commande
|
||||
`fetch.sh pagespeed` où la clé reste confinée au moteur.
|
||||
- Nouveau skill d'audit dédié `/gsc` (le setup passe par `make seo-connect`, l'usage par `/seo` FULL).
|
||||
|
||||
---
|
||||
|
||||
## 14. Liste des fichiers touchés
|
||||
|
||||
**Créés**
|
||||
- `lib/seo-data/fetch.sh`
|
||||
- `lib/seo-data/google_seo.py`
|
||||
- `lib/seo-data/connect.py`
|
||||
- `lib/seo-data/tokenstore.py`
|
||||
- `lib/seo-data/requirements.txt`
|
||||
- `lib/seo-data/README.md`
|
||||
- `lib/seo-data/seo-data.test.sh`
|
||||
- `lib/seo-data/fixtures/*.json`
|
||||
|
||||
**Modifiés**
|
||||
- `.env.example` (3 vars)
|
||||
- `install.sh` (étape consentement optionnelle post-link)
|
||||
- `Makefile` (cible `seo-connect`)
|
||||
- `doctor.sh` (check creds non-fatal)
|
||||
- `.gitleaks.toml` (allowlist token store)
|
||||
- `.gitignore` (venv + token store)
|
||||
- `agents/seo-analyzer.md` (STEP 4 CWV terrain + sous-section Performance GSC ; STEP 9 axe Technical)
|
||||
- `skills/seo/SKILL.md` (STEP 0 sélection compte + propriété en FULL)
|
||||
- `agents/resources/automation-catalog.md` (section « Google Search Console — connexion OAuth » réutilisable en §11)
|
||||
|
||||
**Hors repo (générés au setup, jamais commités)**
|
||||
- `~/.claude/.venv-seo-data/`
|
||||
- `~/.claude/seo-data/tokens.json`
|
||||
|
||||
---
|
||||
|
||||
## 15. Risques & mitigations
|
||||
|
||||
| Risque | Mitigation |
|
||||
|---|---|
|
||||
| Symlink `~/.claude/.env` absent sur machine fraîche | Lecture du **canonical** `~/.claude/.env` ; consentement **après** `link.sh` |
|
||||
| Deux `connect` simultanés corrompent le store | Écriture atomique `tmp`→`rename` + verrou `fcntl` |
|
||||
| Deux audits sur 2 sites se marchent dessus | Compte+propriété **explicites par appel** ; audits en lecture seule |
|
||||
| Secret loggé par erreur | Redaction imposée + test dédié |
|
||||
| Token store flaggé par scan-secrets | Allowlist gitleaks explicite (§5.4) |
|
||||
| `python3`/venv absent | `doctor.sh` warn ; `make seo-connect` crée le venv ; dégradation si absent |
|
||||
| Quota GSC/CrUX (429) | Traité comme `degraded` → audit continue |
|
||||
|
||||
---
|
||||
|
||||
## 16. Questions ouvertes résolues
|
||||
|
||||
- **Scopes** → `webmasters.readonly` seul.
|
||||
- **CrUX vs PageSpeed** → les deux : CrUX terrain primaire, PageSpeed labo secondaire/fallback.
|
||||
- **Forme data en §2** → terrain 75e pct primaire, labo en secondaire.
|
||||
- **GSC obligatoire ?** → non, optionnel, dégradation gracieuse ; proposé à chaque FULL.
|
||||
- **Token expiré** → `degraded` + `doctor.sh` pointe `make seo-connect`.
|
||||
- **Locale** → rapports en français, cohérent avec l'existant.
|
||||
- **Multi-compte** → store keyé par **label utilisateur** (scope inchangé) ; sélection par audit ;
|
||||
isolation par arguments explicites.
|
||||
```
|
||||
Reference in New Issue
Block a user