name: content-gen
disable-model-invocation: true
description: "[DEPRECATED → /seo-content-loop · RAG ≠ source de contenu (ADR-031/046)] LEGACY : lit RAG + KP → écrit en DB. NE PLUS UTILISER comme source de contenu — le contenu vient de WIKI (RAW→WIKI→projection ADR-059). Réécriture no-RAG requise. Usage historique : /content-gen <pg_alias|vehicle_slug> [--r1|--r3|--r4|--r5|--r6|--r8|--meta|--all]"
argument-hint: " [--r1|--r3|--r4|--r5|--r6|--r8|--meta|--all]"
allowed-tools: Read, mcp__claude_ai_Supabase__execute_sql, Glob
Content Generator — Skill /content-gen v1.0
Usage
/content-gen filtre-a-huile— génère contenu pour TOUS les rôles R* de cette gamme/content-gen filtre-a-huile --r1— R1 seul (sg_content + meta)/content-gen filtre-a-huile --r3— R3 seul (sections conseil)/content-gen filtre-a-huile --r4— R4 seul (référence encyclopédique)/content-gen filtre-a-huile --r6— R6 seul (guide d'achat)/content-gen filtre-a-huile --meta— meta descriptions seulement (R1+R3+R6)/content-gen renault-clio-3 --r8— R8 véhicule/content-gen filtre-a-huile --all— forcer tous les rôles
Projet Supabase
cxpojprgwgubzjyqzmoq
Principe
Ce skill utilise Claude Code lui-même comme LLM pour générer le contenu. Pas de clé API payante nécessaire. Le contenu est généré dans la conversation puis écrit directement en DB via MCP Supabase.
Procédure
Étape 0 — Détection gamme / véhicule
Même logique que /kp et /rag-check :
- Lire
/opt/automecanik/rag/knowledge/gammes/{input}.md→ MODE GAMME - Sinon
vehicles/{input}.md→ MODE VÉHICULE - Sinon DB
Étape 1 — Résoudre l'entité
Gamme :
SELECT pg_id, pg_alias, pg_name FROM pieces_gamme
WHERE pg_alias = '{input}' OR pg_id::text = '{input}';
Véhicule : extraire du frontmatter (modele_id, marque_id)
Étape 2 — Lire les sources
2a — Fichier RAG gamme/véhicule :
Read /opt/automecanik/rag/knowledge/gammes/{pg_alias}.md
Parser le frontmatter YAML complet.
2b — Keywords bruts depuis __seo_keywords (source Google Ads KP) :
Les KW arrivent SANS role pre-assigne (skills-first architecture). C'est à toi (Claude) de classifier contextuellement chaque KW selon le role en cours de génération.
SELECT keyword, volume, competition, competition_idx
FROM __seo_keywords
WHERE pg_id = {pg_id} AND source = 'google-ads-kp'
ORDER BY volume DESC;
2b-bis — Classification contextuelle par Claude (au moment de générer) :
Pour le role {role} en cours, applique ces règles de sélection :
| Role | Critères de selection |
|---|---|
| R1 (transactionnel) | KW qui décrivent la piece à acheter : {gamme}, {gamme} + modèle véhicule, {gamme} + prix, {gamme} + pas cher, achat/acheter {gamme}, cartouche/element + {gamme}. Exclure les KW outils (cloche, clé), industriels (hydraulique, tracteur, micron), how-to (changer, vidange), informationnels (c'est quoi, role). |
| R3 (how-to) | KW qui décrivent une procédure : comment changer/remplacer {gamme}, tuto/tutoriel {gamme}, {gamme} étape, dépose/repose {gamme}, {gamme} + vidange (SI sans "prix"). Exclure prix {gamme} (R1), meilleur {gamme} (R6). |
| R4 (référence) | KW informationnels/définition : c'est quoi {gamme}, role/fonctionnement {gamme}, type de {gamme}, différence {gamme}, à quoi sert. Très peu de KW Google Ads tombent dans R4 — majoritairement via PAA. |
| R6 (guide d'achat) | KW d'investigation commerciale : meilleur {gamme}, comparatif, quel {gamme} choisir, marque + {gamme} (purflux, mann, bosch...), {gamme} OEM vs adaptable, budget/prix moyen. |
Règle de priorité absolue : si un KW a plusieurs intents :
prixgagne toujours → R1 (intent transactionnel primaire)marque + gamme→ R6 (investigation)comment/tuto(sans prix) → R3- Sinon → R1 par défaut
Assignation vol (percentile par role) : après avoir sélectionné les KW du role, trie-les par volume desc et assigne :
- HIGH = top 10%
- MED = 10-40%
- LOW = 60% restants
Chaque role a ses propres HIGH proportionnels à son volume total.
Écriture dans __seo_keyword_results :
INSERT INTO __seo_keyword_results (pg_id, pg_alias, role, kw, intent, vol, source)
VALUES ({pg_id}, '{pg_alias}', '{role}', '{kw}', '{intent}', '{vol}', 'google-ads-kp')
ON CONFLICT (pg_id, kw, role) DO UPDATE SET
vol = EXCLUDED.vol,
intent = EXCLUDED.intent,
source = EXCLUDED.source;
Utilisation dans le contenu généré :
- KW vol=HIGH → OBLIGATOIRE dans H1 ou H2 + body text
- KW vol=MED → intégrer dans body text ou FAQ
- KW vol=LOW → optionnel, variantes naturelles
2b-fallback — Si __seo_keywords est vide pour ce pg_id, fallback sur __seo_keyword_results direct (données legacy de claude_chrome ou kp-batch) :
SELECT kw, intent, vol FROM __seo_keyword_results
WHERE pg_id = {pg_id} AND role = '{role}'
ORDER BY CASE vol WHEN 'HIGH' THEN 1 WHEN 'MED' THEN 2 ELSE 3 END;
2c — Contenu existant (pour ne pas régresser) :
SELECT length(sg_content) as current_r1_chars, sg_h1, sg_title, sg_descrip
FROM __seo_gamme WHERE sg_pg_id = '{pg_id}';
2d — Prompt canonique du rôle :
Read .claude/prompts/R{X}_{ROLE}/generator.md
Étape 3 — Déterminer les rôles à générer
| Argument | Rôles |
|---|---|
| (aucun) | R1 + R3 + R4 + R6 (si evidence suffisante) |
--r1 |
R1 seul |
--r3 |
R3 seul |
--r4 |
R4 seul |
--r5 |
R5 seul |
--r6 |
R6 seul |
--r8 |
R8 seul (véhicule) |
--meta |
Meta descriptions R1+R3+R6 |
--all |
Tous les rôles forcés |
Étape 4 — Lancer via le moteur agentique
Le skill /content-gen passe par le moteur agentique pour orchestrer la génération. Cela donne automatiquement : plan multi-branches → solve parallèle → critique → approve humain.
4a — Créer le run agentique
curl -s -X POST http://localhost:3000/api/admin/agentic/runs \
-H "Content-Type: application/json" \
-d '{
"goal": "Content generation pour gamme {pg_alias} (pg_id={pg_id})",
"goal_type": "content_generation",
"triggered_by": "skill:content-gen"
}'
Récupérer le run_id de la réponse.
4b — Lancer le planner
Agent tool, subagent_type: "agentic-planner"
Prompt: "run_id = {run_id}"
Le planner crée les branches (ex: r1-content-batch, r4-content-batch, r6-content-batch, conseil-batch).
4c — Lancer les solvers (en parallèle)
Pour chaque branch_id, lancer un solver en parallèle :
Agent tool, subagent_type: "agentic-solver"
Prompt: "run_id = {run_id}, branch_id = {branch_id}"
run_in_background: true
4d — Lancer le critic
Agent tool, subagent_type: "agentic-critic"
Prompt: "run_id = {run_id}"
4e — Gate humaine
Le run s'arrête en phase applying. Afficher :
"Run terminé. Approve avec POST /api/admin/agentic/runs/{run_id}/approve"
Fallback : si le backend n'est pas démarré, générer directement le contenu par rôle ci-dessous.
RÈGLES DE GÉNÉRATION (mode direct ou solver) :
- Utiliser UNIQUEMENT les données du RAG + KW importés — ne pas inventer
- Respecter le vocabulaire interdit du rôle (voir generator.md)
- Respecter les seuils de longueur :
- R1 : 250-400 mots (contenu riche, 4-5 H2, intégration KW)
- R3 : 50-400 mots par section, 8 sections
- R4 : 200-2000 chars pour la définition, 100-600 pour chaque champ
- R6 : >1000 chars pour how_to_choose
- Format HTML pour R1 sg_content et R3 sgc_content
- Texte brut pour R4 (definition, role_mecanique) et R6 (how_to_choose)
- INTÉGRATION OBLIGATOIRE DES KW IMPORTÉS :
- Lire
__seo_keyword_results WHERE pg_id={pg_id} AND role='{role}' - KW vol=HIGH → DOIT apparaître dans H1/H2 et dans le body (au moins 1 occurrence naturelle)
- KW vol=MED → intégrer dans le body, les listes à puces, ou les FAQ
- KW vol=LOW → utiliser comme variantes naturelles dans le texte (longue traîne)
- PAA → transformer en questions FAQ si section FAQ présente
- Vérifier APRÈS génération que les KW HIGH sont bien présents dans le contenu
- Si un KW HIGH est absent → réviser le contenu pour l'inclure naturellement
- Lire
Étape 5 — Génération R1 (page gamme router)
R1 = 2 prompts séparés, 1 étape de chargement commune :
5.0 — Chargement données (commun aux 2 prompts)
Charger UNE SEULE FOIS et réutiliser dans 5a + 5b :
# 1. KW importés
kw = SQL("SELECT kw, intent, vol FROM __seo_keyword_results WHERE pg_id={pg_id} AND role='R1' ORDER BY CASE vol WHEN 'HIGH' THEN 1 WHEN 'MED' THEN 2 ELSE 3 END")
kw_high = [k for k in kw if k.vol == 'HIGH']
kw_med = [k for k in kw if k.vol == 'MED']
kw_paa = [k for k in kw if k.intent == 'paa']
# 2. RAG gamme
rag = parse_yaml("/opt/automecanik/rag/knowledge/gammes/{pg_alias}.md")
# 3. DB aggregates
agg = SQL("SELECT products_total, top_brands FROM gamme_aggregates WHERE ga_pg_id={pg_id}")
# 4. Contenu existant (pour garde anti-régression)
existing = SQL("SELECT length(sg_content) as chars, sg_h1 FROM __seo_gamme WHERE sg_pg_id='{pg_id}'")
⛔ GATE : si len(kw) == 0 :
- Afficher : "⚠️ Aucun KW importé pour R1. Le contenu sera générique. Importez d'abord via /admin/keyword-planner."
- Continuer avec les données RAG uniquement (pas de blocage dur)
- Marquer
kw_driven = false(pour le rapport)
5a — Contenu éditorial (sg_content) — PROMPT: .claude/prompts/R1_ROUTER/editorial.md
Lire le prompt editorial.md et suivre ses instructions. En résumé :
- Utiliser les données chargées en 5.0
- Générer 1500-2000 mots HTML avec 6-8 H2 enrichis KW
- Intégrer TOUS les KW vol=HIGH dans H2 + body
- Transformer les KW intent=paa en FAQ
<details><summary> - Maillage interne R3 + R4 + R6 + gammes liées
Cibles de longueur :
- Minimum : 10 000 chars / 1500 mots / 6 H2
- Optimal : 15 000 chars / 2000 mots / 8 H2
- Maximum : 20 000 chars (au-delà, risque de dilution)
5b — Meta tags
sg_h1: KW vol=HIGH le plus fort (max 70c)sg_title_draft: KW vol=HIGH + "| AutoMecanik" (max 60c)sg_descrip_draft: 120-155 chars, KW HIGH principal + "livraison" ou "en stock"
5c — H2 Overrides
Extraire les H2 du sg_content généré et écrire dans sgpg_h2_overrides :
UPDATE __seo_gamme_purchase_guide SET sgpg_h2_overrides = $h2${
"content": "[Premier H2 du sg_content]",
"motorizations": "[Gamme] compatible avec votre véhicule",
"equipementiers": "Marques [gamme] : [top 3]",
"checklist": "Vérifications avant achat de [gamme]",
"faq": "[H2 FAQ du sg_content]"
}$h2$ WHERE sgpg_pg_id = '{pg_id}';
Règles H2 : nom gamme obligatoire, KW HIGH dans "content", max 60 chars. Si 0 KW → ne PAS générer de h2Overrides (fallbacks hardcodés s'appliquent).
5d — Écriture avec garde anti-régression
⛔ GUARD ANTI-RÉGRESSION (BLOQUANT) :
new_length = len(generated_content)
existing_length = existing.chars # chargé en 5.0
if existing_length > 0 and new_length < existing_length:
print(f"⛔ GUARD: Contenu existant ({existing_length}c) > généré ({new_length}c). Écriture BLOQUÉE.")
# NE PAS écrire. Demander confirmation humaine.
return
Si garde OK → écrire directement dans sg_content (champ live) :
UPDATE __seo_gamme SET
sg_content = $content$[HTML 1500-2000 mots]$content$,
sg_h1 = '[H1 enrichi KW]',
sg_title_draft = '[title draft]',
sg_descrip_draft = '[description draft]',
sg_draft_source = 'content-gen-skill',
sg_draft_updated_at = now()
WHERE sg_pg_id = '{pg_id}';
Note :
sg_contentest écrit directement (live) car le editorial.md produit du contenu validé et la garde anti-régression protège contre les régressions.sg_title_draftetsg_descrip_draftrestent en draft (promotion séparée via SeoTitleEngine).
5e — Validation KW post-écriture
Après écriture, vérifier AUTOMATIQUEMENT l'intégration des KW :
content_lower = generated_content.lower()
missing_high = [k.kw for k in kw_high if not fuzzy_match(k.kw, content_lower)]
missing_med = [k.kw for k in kw_med if not fuzzy_match(k.kw, content_lower)]
if len(missing_high) > 0:
print(f"⚠️ {len(missing_high)} KW HIGH manquants : {missing_high}")
print("→ Réviser le contenu pour les inclure naturellement.")
# NE PAS bloquer, mais AVERTIR
integration_score = round(
(len(kw_high) - len(missing_high)) / max(len(kw_high), 1) * 50 +
(len(kw_med) - len(missing_med)) / max(len(kw_med), 1) * 35 +
15 # LOW = bonus fixe
)
print(f"Score intégration KW : {integration_score}/100")
5f — Invalidation cache
OBLIGATOIRE après toute écriture dans __seo_gamme ou __seo_gamme_purchase_guide :
POST http://localhost:3000/api/admin/cache/invalidate?pg_id={pg_id}
Fallback si endpoint non disponible : redis-cli DEL gamme:rpc:v2:{pg_id}
Sans cette étape, l'ancien contenu est servi pendant 1h (TTL cache).
5g — Maillage bidirectionnel
Source de vérité : table __seo_gamme_links (1199 liens, 236 gammes).
Après écriture du sg_content pour gamme A :
1. Liens sortants (A → cibles) :
SELECT l.target_pg_id, l.anchor_text, l.context, l.relation,
pg.pg_alias, pg.pg_name
FROM __seo_gamme_links l
JOIN pieces_gamme pg ON pg.pg_id = l.target_pg_id
WHERE l.source_pg_id = {pg_id}
ORDER BY l.relation, pg.pg_name;
Pour chaque lien cible :
- Vérifier si
sg_contentcontient déjà un<a href="/pieces/{target_alias}-{target_id}.html"> - Si absent → l'ajouter naturellement dans le body (pas en append brut)
- Format :
<a href="/pieces/{alias}-{id}.html">{anchor_text}</a>dans une phrase contextuelle
2. Liens entrants (sources → A, bidirectionnel) :
SELECT l.source_pg_id, l.anchor_text, l.context,
pg.pg_alias, pg.pg_name,
sg.sg_content
FROM __seo_gamme_links l
JOIN pieces_gamme pg ON pg.pg_id = l.source_pg_id
JOIN __seo_gamme sg ON sg.sg_pg_id = l.source_pg_id::text
WHERE l.target_pg_id = {pg_id} AND l.bidirectional = true;
Pour chaque gamme source :
- Vérifier si
source.sg_contentcontient déjà un lien vers gamme A (/pieces/{pg_alias}-{pg_id}.html) - Si absent ET source.sg_content n'est pas vide :
UPDATE __seo_gamme SET sg_content = sg_content || $link$ <p class="mt-3 text-sm text-slate-500">{context} — <a href="/pieces/{pg_alias}-{pg_id}.html" class="text-blue-600 hover:underline">{anchor_text}</a></p> $link$ WHERE sg_pg_id = '{source_pg_id}'; - Invalider le cache Redis de la gamme source :
redis-cli DEL gamme:rpc:v2:{source_pg_id}
Règles maillage :
- Append only — ne jamais modifier le contenu existant, ajouter à la fin
- Max 3 liens entrants ajoutés par exécution — pas de sur-optimisation
- Deduplicate — si le lien href existe déjà dans sg_content, NE PAS l'ajouter
- Ancre naturelle — utiliser le
anchor_textde la table, pas "cliquez ici" - Contexte — utiliser le
contextde la table pour la phrase d'intro du lien - Garde anti-régression — append = length augmente toujours (OK)
5h — Image prompts R1
Après maillage, générer les prompts image R1 si pas déjà existants :
curl -s -X POST http://localhost:3000/api/admin/r1-image-prompts/generate/{pg_alias} \
-b cookies.txt
5 slots : HERO_EDITORIAL, TYPES_SCHEMA, PRICE_CHART, MOUNTING_DIAGRAM, OG_IMAGE.
G7-R1 gate : max 3 in-article sélectionnés (top richness score).
Si prompts déjà existants et --force non set → skip.
Les images sont rendues automatiquement dans le sg_content par le response builder
quand rip_status = 'approved' et rip_image_url est rempli.
Workflow image complet :
- content-gen génère les prompts (step 5h)
- Admin approuve les prompts (PATCH /approve)
- Images générées avec Midjourney/DALL-E/ComfyUI
- Upload vers Supabase storage (
uploads/articles/gammes-produits/r1-editorial/{pg_alias}/) - Set URL (PATCH /set-image-url)
- Response builder injecte les
<figure>dans sg_content - Invalider cache Redis
Mode batch
Pour traiter plusieurs gammes :
/content-gen --batch top20 --r1
Logique :
- Charger les 20 gammes avec le plus de KW importés (
SELECT pg_id, COUNT(*) FROM __seo_keyword_results WHERE role='R1' GROUP BY pg_id ORDER BY count DESC LIMIT 20) - Pour chaque gamme, exécuter les étapes 5.0 → 5f séquentiellement
- Afficher un rapport batch en fin :
## Batch R1 — 20 gammes
| Gamme | Avant | Après | Score KW | Statut |
|-------|-------|-------|----------|--------|
| filtre-a-huile | 6195c | 15200c | 89/100 | ✅ |
| disque-de-frein | 1200c | 14800c | 92/100 | ✅ |
| ... | ... | ... | ... | ... |
| TOTAL | 45k | 295k | 87 avg | 18/20 OK |
Étape 6 — Génération R3 (sections conseil)
KW-first : Lire __seo_keyword_results WHERE pg_id={pg_id} AND role='R3' en premier.
Répartir les KW par section :
- KW how_to → S3 (Dépose/repose), S4
- KW informational/entretien → S2 (Quand intervenir), S5
- KW cout → S7 (Pièces associées) ou body text
- KW PAA → S8 (FAQ) — transformer chaque KW PAA en question/réponse
À partir du RAG frontmatter + KW importés, générer 8 sections HTML :
| Section | Source RAG | Budget |
|---|---|---|
| S1 Avant de commencer | domain.role, safety | 50-150 mots |
| S2 Quand intervenir | maintenance.interval, wear_signs | 120-250 mots |
| S3 Compatibilité | selection.criteria | 100-200 mots |
| S4 Dépose/repose | diagnostic.depose_steps | 200-400 mots (si evidence) |
| S5 Erreurs fréquentes | selection.anti_mistakes | 100-220 mots |
| S6 Vérification finale | maintenance.good_practices | 80-180 mots |
| S7 Pièces associées | domain.related_parts | 60-150 mots |
| S8 FAQ | rendering.faq | 150-350 mots |
Format : HTML avec <p>, <ul>, <li>, <strong>. Pas de H2 (le heading est séparé).
Écriture par section (upsert sur la contrainte unique (sgc_pg_id, sgc_section_type)) :
INSERT INTO __seo_gamme_conseil (sgc_pg_id, sgc_section_type, sgc_content, sgc_quality_score, sgc_enriched_by)
VALUES ('{pg_id}', 'S1', $content$...$content$, 85, 'content-gen-skill')
ON CONFLICT (sgc_pg_id, sgc_section_type) DO UPDATE SET
sgc_content = CASE
WHEN length(EXCLUDED.sgc_content) >= length(__seo_gamme_conseil.sgc_content)
THEN EXCLUDED.sgc_content
ELSE __seo_gamme_conseil.sgc_content
END,
sgc_quality_score = EXCLUDED.sgc_quality_score,
sgc_enriched_by = 'content-gen-skill';
IMPORTANT : Utiliser
ON CONFLICT (sgc_pg_id, sgc_section_type)— jamais(sgc_pg_id, sgc_id). Le CASE protège contre la régression : le contenu plus court ne remplace JAMAIS le contenu plus long.
6h — Image prompts R3
Après génération des sections R3, générer les prompts image R3 si pas déjà existants :
curl -s -X POST http://localhost:3000/api/admin/r3-image-prompts/generate/{pg_alias} \
-b cookies.txt
8 slots (1 par section S1-S8) : diagrammes, visuels étape par étape, callouts sécurité.
Si prompts déjà existants et --force non set → skip.
Workflow identique à R1 : generate → approve → create image → upload → set-url → cache invalidate.
Étape 7 — Génération R4 (référence encyclopédique)
Prompt canonique : lire .claude/prompts/R4_REFERENCE/generator.md et suivre ses instructions.
Le generator produit un JSON structuré avec 9 sections (B1-B9). Utiliser le RAG COMPLET (pas de truncation).
| Section | Champ DB | Contraintes |
|---|---|---|
| B1 definition | definition + takeaways | 50-110 mots + 3-5 bullets |
| B2 role_mecanique | role_mecanique | 70-140 mots |
| B3 composition | composition | 4-7 éléments |
| B4 variants | variants | 3-5 cartes (optionnel) |
| B5 key_specs | key_specs | 4-8 specs avec "selon véhicule" |
| B6 FAQ | confusions_courantes + common_questions | 3-5 confusions + 4-7 Q/A (25-60 mots) |
| B7 role_negatif | role_negatif | 5-8 phrases "ne fait pas" |
| B8 regles_metier | regles_metier | 5-9 règles (Toujours/Ne jamais/Doit) |
| B9 scope | scope_limites | 80-140 mots + renvoi R3 |
Lint gates : 8 gates (LG1-LG8), seuil >= 70. Voir generator.md pour détails.
Écriture :
UPDATE __seo_reference SET
definition = $def$...$def$,
takeaways = ARRAY['...', '...', '...'],
role_mecanique = $rm$...$rm$,
role_negatif = $rn$...$rn$,
confusions_courantes = ARRAY['...', '...', '...'],
common_questions = $faq$[{"question":"...","answer":"..."}]$faq$::jsonb,
regles_metier = ARRAY['...', '...', '...'],
composition = ARRAY['...', '...', '...'],
key_specs = $specs$[{"label":"...","value":"...","note":"selon véhicule"}]$specs$::jsonb,
scope_limites = '...'
WHERE pg_id = {pg_id};
7h — Image prompts R4
Après écriture R4, générer 2 prompts image :
- HERO_TECHNIQUE : schéma technique éclaté de la pièce (vue en coupe)
- COMPOSITION_SCHEMA : diagramme des composants avec labels
Écrire les prompts inline (pas d'endpoint NestJS dédié pour R4) :
INSERT INTO __seo_r4_image_prompts (rip_pg_id, rip_pg_alias, rip_slot, rip_prompt, rip_status)
VALUES
({pg_id}, '{pg_alias}', 'HERO_TECHNIQUE', $p$...$p$, 'draft'),
({pg_id}, '{pg_alias}', 'COMPOSITION_SCHEMA', $p$...$p$, 'draft')
ON CONFLICT (rip_pg_id, rip_slot) DO NOTHING;
Si la table __seo_r4_image_prompts n'existe pas encore → skip avec warning.
Étape 8 — Génération R6 (guide d'achat)
À partir du RAG + KP R6, générer :
| Champ | Source | Longueur |
|---|---|---|
| sgpg_intro_role | domain.role | 100-200 chars |
| sgpg_risk_explanation | diagnostic.causes | 100-300 chars |
| sgpg_how_to_choose | selection.criteria + anti_mistakes | >1000 chars (guide complet) |
Écriture :
UPDATE __seo_gamme_purchase_guide SET
sgpg_intro_role = '...',
sgpg_risk_explanation = '...',
sgpg_how_to_choose = $htc$...$htc$,
sgpg_updated_at = now()
WHERE sgpg_pg_id = '{pg_id}';
8h — Image prompts R6
Après écriture R6, générer les prompts image via l'agent r6-image-prompt :
Agent tool, subagent_type: "r6-image-prompt"
Prompt: "pg_id = {pg_id}, pg_alias = {pg_alias}"
Ou si l'agent n'est pas disponible, générer 3 prompts inline :
- HERO_GUIDE : visuel comparatif des critères de choix
- QUALITY_TIERS : infographie niveaux de qualité (budget/standard/premium)
- CHECKLIST_VISUAL : checklist illustrée avant achat
Si prompts déjà existants et --force non set → skip.
Rapport contenu + images unifié
Après génération de tous les rôles demandés, afficher le rapport unifié :
## Content + Images — {pg_name} (pg_id={pg_id})
| Rôle | Contenu | Images | KW Score | Statut |
|------|---------|--------|----------|--------|
| R1 | {chars}c | {n}/5 prompts | {score}/100 | ✅/⚠️ |
| R3 | {n} sections | {n}/8 prompts | {score}/100 | ✅/⚠️ |
| R4 | {n} sections | {n}/2 prompts | {score}/100 | ✅/⚠️ |
| R6 | {n} champs | {n}/3 prompts | {score}/100 | ✅/⚠️ |
Images : {total} prompts générés (status=draft, en attente d'approbation admin)
Étape 9 — Génération meta descriptions (--meta)
Pour chaque rôle avec du contenu :
R1 meta :
UPDATE __seo_gamme SET sg_descrip_draft = '...', sg_draft_source = 'content-gen-skill', sg_draft_updated_at = now()
WHERE sg_pg_id = '{pg_id}';
INTERDIT : ne JAMAIS écrire
sg_descrip(champ live). Utilisersg_descrip_draft. Format : 120-155 chars, nom pièce + "véhicule" + "compatible" + "livraison"
R3 meta (via __blog_advice si existe) :
UPDATE __blog_advice SET ba_meta_description = '...' WHERE ba_pg_id = '{pg_id}';
Format : 120-155 chars, "comment" + nom pièce + "guide" + "étape par étape"
Étape 10 — Rapport de génération
Afficher :
## Content Generated — {pg_name} (pg_id={pg_id})
| Rôle | Avant | Après | Delta | Écrit en DB |
|------|-------|-------|-------|-------------|
| R1 | 1061c | 1187c | +126c | ✅ __seo_gamme |
| R3 | 10 sections | 10 sections (7 updated) | 0 new, 7 maj | ✅ __seo_gamme_conseil |
| R4 | def 1550c | def 1600c | +50c | ✅ __seo_reference |
| R6 | choose 14014c | choose 14200c | +186c | ✅ __seo_gamme_purchase_guide |
Vocabulaire interdit : 0 fuites détectées
Source : RAG + KW importés (Claude Code generation)
### Intégration KW
| Vol | Total KW | Intégrés | Manquants |
|-----|----------|----------|-----------|
| HIGH | 5 | 5/5 | — |
| MED | 12 | 10/12 | "filtre à huile en ligne", "filtre à huile auto" |
| LOW | 28 | 15/28 | (optionnel) |
| PAA | 7 | 4/7 → FAQ | "où acheter" non utilisé |
Étape 11 — Vérification post-génération
Après écriture, lancer automatiquement :
-- Vérifier que le draft est bien écrit
SELECT length(sg_content_draft) as r1_draft_chars, sg_draft_updated_at::text
FROM __seo_gamme WHERE sg_pg_id = '{pg_id}';
Si le draft n'a pas changé → signaler "⚠️ Écriture échouée — vérifier les RLS"
Invalidation cache Redis (OBLIGATOIRE après écriture) :
redis-cli DEL gamme:rpc:v2:{pg_id}
Sans cette étape, l'ancien contenu est servi pendant 1h (TTL cache). Toujours invalider après écriture de sg_content, sg_h1, sg_title, sg_descrip ou sgpg_h2_overrides.
Règles
- Ne JAMAIS inventer — uniquement RAG + KP comme source
- Vérifier le vocabulaire interdit avant écriture
- ⛔ GUARD ANTI-RÉGRESSION (BLOQUANT) :
- AVANT chaque UPDATE, comparer
new_lengthvsexisting_lengthen DB - Si
new_length < existing_length→ BLOQUER l'écriture - Afficher : "⛔ GUARD: Contenu R{X} existant ({existing}c) > contenu généré ({new}c). Écriture bloquée."
- Ne JAMAIS écrire un contenu vide si l'existant n'est pas vide
- Exception :
--forcebypass le guard (avec warning) - S'applique à : sg_content (R1), sgc_content (R3), definition (R4), sgpg_how_to_choose (R6), role_mecanique (R4)
- AVANT chaque UPDATE, comparer
- Écrire avec des dollar-quoted strings (
$content$...$content$) pour éviter les problèmes d'échappement SQL - Signaler les sections non générées (evidence insuffisante)
- Format HTML pour R1/R3, texte brut pour R4/R6
- Intégrer les maillages inter-rôles dans R1 sg_content
- Respecter les limites de longueur par rôle (R1 = court, R3 = moyen, R4/R6 = long)
- Toujours lire le contenu existant AVANT de générer — pour calibrer la longueur cible (≥ existant)