chore(cleanup): untrack debris pre-BFG — audio, PEM, screenshots, reports

Phase 0 (J2 cleanup) of chore/v1.0.7-cleanup branch. Pure index removals
before BFG history rewrite. No working-tree changes, no code touched.

Removed from git index (still on disk):
  - 44× veza-backend-api/uploads/*.mp3        (audio fixtures, ~200MB)
  - 23× root PNG screenshots                  (design-system, forgot-password,
                                                register, reset-password, settings,
                                                storybook — various prefixes)
  - 1× docker/haproxy/certs/veza.pem          (self-signed dev cert, regen via
                                                scripts/generate-ssl-cert.sh)
  - 1× generate_page_fix_prompts.sh           (one-off generated tooling)
  - 4× apps/web/*.json                        (AUDIT_ISSUES, audit_remediation,
                                                lint_comprehensive, storybook-roadmap)

.gitignore enriched (post-audit J2 block) to prevent recommits:
  - veza-backend-api/uploads/                 (audio fixtures → git-lfs or external)
  - config/ssl/*.{pem,key,crt}
  - .playwright-mcp/                          (MCP session debris)
  - CLAUDE_CONTEXT.txt, UI_CONTEXT_SUMMARY.md, *.context.txt  (AI session artefacts)
  - Root PNG prefixes beyond existing rules
  - apps/web/{AUDIT_ISSUES,audit_remediation,lint_comprehensive,storybook-*}.json
  - /generate_page_fix_prompts.sh, /build-archive.log

Next: BFG for history rewrite to compact .git (currently 2.3 GB).

Refs: AUDIT_REPORT.md §9.1, FUNCTIONAL_AUDIT.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
senke 2026-04-20 09:56:47 +02:00
parent 6d51f52aae
commit d12b901de5
2 changed files with 40 additions and 980 deletions

40
.gitignore vendored
View file

@ -196,3 +196,43 @@ veza-backend-api/backend*.log
# AI tooling session state (not code) # AI tooling session state (not code)
.cursor/ .cursor/
# ============================================================
# Post-audit J2 (2026-04-20) — branch chore/v1.0.7-cleanup
# ============================================================
# Tracked audio fixtures — use git-lfs or fixtures repo, never commit raw audio
veza-backend-api/uploads/
# TLS/SSL certificates committed pre-2026-04 (regen with scripts/generate-ssl-cert.sh)
config/ssl/*.pem
config/ssl/*.key
config/ssl/*.crt
# Playwright MCP session debris
.playwright-mcp/
# AI session artefacts / context dumps
CLAUDE_CONTEXT.txt
UI_CONTEXT_SUMMARY.md
*.context.txt
*.ai-session.txt
# One-off generated tooling scripts (should live in scripts/ if kept)
/generate_page_fix_prompts.sh
/build-archive.log
# Apps/web stale audit reports (generated, never tracked)
apps/web/AUDIT_ISSUES.json
apps/web/audit_remediation.json
apps/web/lint_comprehensive.json
apps/web/storybook-roadmap.json
apps/web/storybook-*.json
# Root PNG screenshots — move to docs/screenshots/ if historical value
/design-system-*.png
/forgot-password-*.png
/register-*.png
/reset-password-*.png
/settings-*.png
/storybook-*.png

View file

@ -1,980 +0,0 @@
#!/usr/bin/env python3
"""
VEZA — Générateur de prompts Claude Code pour audit exhaustif de toutes les pages.
Génère un prompt par route, prêt à être copié-collé dans Claude Code avec Playwright MCP.
Usage:
python veza-prompt-generator.py # Génère tous les prompts dans ./prompts/
python veza-prompt-generator.py --route /settings # Génère un seul prompt
python veza-prompt-generator.py --list # Liste toutes les routes
python veza-prompt-generator.py --batch 1 # Génère le batch 1 (routes groupées)
python veza-prompt-generator.py --combined # Un seul fichier avec tous les prompts
"""
import argparse
import os
import sys
from dataclasses import dataclass, field
from datetime import datetime
from typing import Optional
# ─────────────────────────────────────────────
# Configuration
# ─────────────────────────────────────────────
OUTPUT_DIR = "./prompts"
DOMAIN = "veza.fr"
TEST_ACCOUNTS = [
{"role": "admin", "email": "admin@veza.music", "password": "Admin123!"},
{"role": "creator", "email": "artist@veza.music", "password": "Artist123!"},
{"role": "user", "email": "user@veza.music", "password": "User123!"},
{"role": "moderator", "email": "mod@veza.music", "password": "Mod123!"},
{"role": "user_new", "email": "new@veza.music", "password": "New123!"},
]
E2E_TEST_DIR = "veza-e2e/"
# ─────────────────────────────────────────────
# Route definitions
# ─────────────────────────────────────────────
@dataclass
class Route:
number: int
path: str
name: str
category: str
auth_required: bool = True
role: str = "user"
params: str = ""
test_with_accounts: list = field(default_factory=list)
specific_checks: list = field(default_factory=list)
ROUTES = [
# ── Pages publiques ──
Route(1, "/login", "Connexion", "public", auth_required=False,
specific_checks=[
"Formulaire email + password fonctionnel",
"Validation des champs (email invalide, champs vides)",
"Messages d'erreur (mauvais credentials)",
"Lien 'Mot de passe oublié' fonctionnel",
"Lien 'Inscription' fonctionnel",
"Redirection après login réussi",
"Protection contre brute-force (rate limiting visible)",
"Autocomplete attributes sur les inputs",
"Accessibilité: labels, focus order, aria",
]),
Route(2, "/register", "Inscription", "public", auth_required=False,
specific_checks=[
"Tous les champs du formulaire (username, email, password, confirm)",
"Validation temps réel (email format, password strength, username dispo)",
"Messages d'erreur clairs pour chaque règle de validation",
"Soumission et retour serveur (201, 409 duplicate, 422 validation)",
"Lien vers /login fonctionnel",
"CGU / checkbox conditions d'utilisation si présent",
"Accessibilité: labels, focus, aria, autocomplete",
]),
Route(3, "/forgot-password", "Récupération mot de passe", "public", auth_required=False,
specific_checks=[
"Formulaire email fonctionnel",
"Message de confirmation après soumission",
"Gestion email inexistant (pas de leak d'info)",
"Lien retour vers /login",
"Rate limiting sur les soumissions",
]),
Route(4, "/verify-email", "Vérification email", "public", auth_required=False,
specific_checks=[
"Comportement avec token valide vs invalide vs expiré",
"Message de succès / erreur approprié",
"Redirection après vérification",
"Gestion du cas sans token dans l'URL",
]),
Route(5, "/reset-password", "Réinitialisation mot de passe", "public", auth_required=False,
specific_checks=[
"Formulaire nouveau password + confirmation",
"Validation password strength côté client",
"Comportement avec token valide vs invalide vs expiré",
"Message de succès + redirection vers /login",
"Autocomplete attributes",
]),
Route(6, "/launch", "Landing page pré-lancement", "public", auth_required=False,
specific_checks=[
"Rendu visuel complet (hero, sections, CTA)",
"Tous les liens/CTA fonctionnels",
"Responsive (mobile/tablet/desktop)",
"Performance de chargement",
"SEO: title, meta description, OG tags",
]),
Route(7, "/design-system", "Démo du design system", "public", auth_required=False,
specific_checks=[
"Tous les composants se rendent sans erreur",
"Pas d'erreurs console",
"Interactivité des composants démo (boutons, toggles, modals)",
]),
Route(8, "/u/:username", "Profil public utilisateur", "public", auth_required=False,
params="username",
specific_checks=[
"Affichage avec username existant (ex: tester avec chaque compte)",
"Page 404 avec username inexistant",
"Infos publiques affichées (avatar, bio, tracks publiques)",
"Pas de fuite d'infos privées (email, settings)",
"Boutons follow/unfollow si connecté",
"Liste des tracks/playlists publiques",
"Liens fonctionnels vers les tracks",
]),
Route(9, "/playlists/shared/:token", "Playlist partagée", "public", auth_required=False,
params="token",
specific_checks=[
"Affichage avec token valide",
"Gestion token invalide / expiré",
"Lecture des tracks depuis le partage",
"Infos playlist (titre, description, nombre de tracks)",
]),
# ── Navigation principale ──
Route(10, "/dashboard", "Tableau de bord", "main_nav",
test_with_accounts=["user", "creator", "admin"],
specific_checks=[
"Widgets / cartes de stats se chargent",
"Données cohérentes avec le rôle (creator voit ses stats artiste)",
"Liens rapides fonctionnels",
"Activité récente / feed",
"Pas d'erreurs réseau dans la console",
"Responsive layout",
]),
Route(11, "/discover", "Découverte musicale", "main_nav",
specific_checks=[
"Sections de découverte (trending, new releases, genres)",
"Cartes de tracks/albums cliquables",
"Lecture depuis la page discover",
"Filtres / catégories fonctionnels",
"Infinite scroll ou pagination",
"État vide si aucun contenu",
]),
Route(12, "/feed", "Fil d'actualité", "main_nav",
specific_checks=[
"Posts / activités des artistes suivis",
"Infinite scroll ou pagination",
"Interactions (like, comment, share)",
"État vide (aucun artiste suivi)",
"Chargement sans erreurs réseau",
]),
Route(13, "/library", "Bibliothèque musicale", "main_nav",
test_with_accounts=["user", "creator"],
specific_checks=[
"Liste des tracks / albums / playlists de l'utilisateur",
"Tri et filtres fonctionnels",
"Recherche dans la bibliothèque",
"Actions sur les items (play, add to playlist, delete)",
"État vide pour un nouveau compte",
"Pagination / infinite scroll",
]),
Route(14, "/queue", "File de lecture", "main_nav",
specific_checks=[
"Affichage de la queue actuelle",
"Drag & drop pour réordonner",
"Suppression d'items de la queue",
"Bouton clear queue",
"État vide",
"Persistance après navigation",
]),
Route(15, "/search", "Recherche globale", "main_nav",
specific_checks=[
"Barre de recherche fonctionnelle",
"Résultats par catégorie (tracks, artists, playlists, albums)",
"Recherche en temps réel / debounce",
"Résultats cliquables et navigation correcte",
"État 'aucun résultat'",
"Historique de recherche si présent",
]),
# ── Playlists & Tracks ──
Route(16, "/playlists", "Liste des playlists", "playlists",
specific_checks=[
"Liste des playlists de l'utilisateur",
"Bouton créer une playlist",
"Cartes playlist cliquables",
"Infos affichées (titre, nombre de tracks, durée, cover)",
"Actions (edit, delete, share)",
"État vide",
]),
Route(17, "/playlists/favoris", "Playlist favoris", "playlists",
specific_checks=[
"Liste des tracks favorites",
"Bouton unfavorite fonctionnel",
"Lecture depuis les favoris",
"Tri (date ajoutée, titre, artiste)",
"État vide",
]),
Route(18, "/playlists/:id", "Détail playlist", "playlists",
params="id",
specific_checks=[
"Header playlist (titre, description, cover, auteur)",
"Liste des tracks avec numérotation",
"Boutons play / shuffle",
"Actions par track (play, add to queue, remove)",
"Boutons share / edit / delete (si owner)",
"Playlist inexistante → 404",
"Playlist privée d'un autre user → 403 ou 404",
]),
Route(19, "/playlists/:id/edit", "Édition playlist", "playlists",
params="id",
specific_checks=[
"Formulaire pré-rempli (titre, description)",
"Upload / changement de cover",
"Réordonnement des tracks (drag & drop)",
"Ajout / suppression de tracks",
"Sauvegarde fonctionnelle",
"Validation (titre requis)",
"Accès interdit si pas owner → redirect ou 403",
]),
Route(20, "/tracks/:id", "Détail track", "playlists",
params="id",
specific_checks=[
"Infos track (titre, artiste, album, durée, cover)",
"Bouton play fonctionnel",
"Boutons like / add to playlist / share",
"Waveform / visualisation audio si présent",
"Commentaires si présent",
"Tracks similaires / recommandations",
"Track inexistante → 404",
"Métadonnées (genre, BPM, key si affichés)",
]),
# ── Social & Communication ──
Route(21, "/social", "Communauté", "social",
specific_checks=[
"Liste des utilisateurs / artistes",
"Recherche / filtres",
"Boutons follow / unfollow",
"Profils cliquables",
"Compteurs followers/following",
"Sections (trending artists, new members, etc.)",
]),
Route(22, "/chat", "Messagerie", "social",
test_with_accounts=["user", "creator"],
specific_checks=[
"Liste des conversations",
"Envoi de message",
"Réception en temps réel (WebSocket)",
"Création de nouvelle conversation",
"Recherche dans les conversations",
"État vide (aucune conversation)",
"Indicateurs read/unread",
"Envoi de médias si supporté",
]),
Route(23, "/chat/join/:token", "Rejoindre un chat", "social",
params="token",
specific_checks=[
"Token valide → rejoint le chat + redirect",
"Token invalide → message d'erreur",
"Token expiré → message d'erreur",
"Déjà membre → redirect vers le chat existant",
]),
Route(24, "/live", "Streams en direct", "social",
specific_checks=[
"Liste des streams en cours",
"Cartes stream (titre, artiste, viewers, thumbnail)",
"Clic → page de visionnage",
"État vide (aucun stream)",
"Compteurs viewers",
]),
Route(25, "/live/go-live", "Lancer un stream", "social",
role="creator",
test_with_accounts=["creator", "admin"],
specific_checks=[
"Formulaire de configuration stream (titre, description, catégorie)",
"Prévisualisation caméra/micro",
"Bouton Go Live",
"Vérification permissions (role creator/admin requis)",
"Utilisateur standard → redirect ou message d'erreur",
"Configuration RTMP/HLS si affichée",
]),
Route(26, "/listen-together/:sessionId", "Écoute collaborative", "social",
params="sessionId",
specific_checks=[
"Session valide → rejoint l'écoute",
"Session invalide → erreur",
"Synchronisation audio entre participants",
"Liste des participants",
"Chat intégré si présent",
"Contrôles de lecture (host only vs all)",
]),
# ── Marketplace & Commerce ──
Route(27, "/marketplace", "Place de marché", "marketplace",
specific_checks=[
"Grille / liste de produits",
"Filtres (catégorie, prix, type)",
"Recherche produits",
"Cartes produits (image, titre, prix, vendeur)",
"Pagination / infinite scroll",
"Tri (prix, date, popularité)",
]),
Route(28, "/marketplace/products/:id", "Détail produit", "marketplace",
params="id",
specific_checks=[
"Infos produit (images, titre, description, prix)",
"Bouton acheter / ajouter au panier",
"Avis / ratings si présent",
"Infos vendeur",
"Produit inexistant → 404",
"Produits similaires",
]),
Route(29, "/wishlist", "Liste de souhaits", "marketplace",
specific_checks=[
"Liste des produits wishlistés",
"Bouton retirer de la wishlist",
"Bouton acheter directement",
"État vide",
"Infos produit à jour (prix, disponibilité)",
]),
Route(30, "/purchases", "Historique d'achats", "marketplace",
specific_checks=[
"Liste des achats passés",
"Détails par achat (date, montant, produit, statut)",
"Téléchargement si digital",
"État vide",
"Pagination si beaucoup d'achats",
]),
Route(31, "/checkout/complete", "Confirmation de commande", "marketplace",
specific_checks=[
"Message de confirmation",
"Récapitulatif de la commande",
"Liens vers les achats / téléchargements",
"Comportement si accès direct sans commande → redirect",
]),
Route(32, "/sell", "Dashboard vendeur", "marketplace",
role="creator",
test_with_accounts=["creator", "admin"],
specific_checks=[
"Stats de vente (revenus, nombre de ventes)",
"Liste des produits en vente",
"Bouton ajouter un produit",
"Gestion des produits (edit, delete, toggle visibility)",
"Accès interdit pour les non-creators",
]),
# ── Créateur & Analytics ──
Route(33, "/analytics", "Tableau de bord analytics", "creator",
role="creator",
test_with_accounts=["creator", "admin"],
specific_checks=[
"Graphiques de stats (plays, listeners, revenue)",
"Période sélectionnable (7j, 30j, 90j, 1an)",
"Top tracks / Top playlists",
"Données démographiques si présent",
"Export de données si présent",
"État vide pour un nouveau creator",
"Accès interdit pour user standard",
]),
Route(34, "/cloud", "Stockage cloud", "creator",
role="creator",
test_with_accounts=["creator"],
specific_checks=[
"Explorateur de fichiers",
"Upload de fichiers audio",
"Progress bar upload",
"Organisation en dossiers",
"Actions (rename, delete, move, download)",
"Quota de stockage affiché",
"Types de fichiers acceptés / rejetés",
]),
Route(35, "/gear", "Inventaire équipement", "creator",
role="creator",
specific_checks=[
"Liste de l'équipement",
"Ajout / édition / suppression d'items",
"Catégories (instruments, software, hardware)",
"Photos d'équipement si supporté",
"État vide",
]),
Route(36, "/distribution", "Distribution externe", "creator",
role="creator",
specific_checks=[
"Liste des plateformes de distribution",
"Statut de distribution par track/album",
"Bouton distribuer",
"Configuration des métadonnées requises",
"Historique des distributions",
]),
# ── Compte & Paramètres ──
Route(37, "/profile", "Profil (redirect)", "account",
specific_checks=[
"Redirection vers /u/:username du user connecté",
"Redirection correcte pour chaque rôle",
]),
Route(38, "/settings", "Paramètres du compte", "account",
test_with_accounts=["user", "creator", "admin"],
specific_checks=[
"Onglet Account: changement de password fonctionnel",
"Onglet Account: setup/disable 2FA (Authenticator + SMS)",
"Onglet Account: statut 2FA correct",
"Onglet Account: Delete Account (validation 'DELETE', password requis)",
"Onglet Préférences: thème (radio buttons mutuellement exclusifs)",
"Onglet Préférences: langue (changement effectif)",
"Onglet Préférences: timezone",
"Onglet Notifications: tous les toggles fonctionnels",
"Onglet Confidentialité: tous les toggles fonctionnels",
"Onglet Playback: audio quality, crossfade, etc.",
"Bouton Save Config fonctionnel (pas d'erreur validation)",
"Accessibilité: labels sur tous les checkboxes/toggles",
"Pas de mélange i18n (tout FR ou tout EN)",
"Pas de fuite d'infos (email dans search bar, VAPID key)",
]),
Route(39, "/settings/sessions", "Sessions actives", "account",
specific_checks=[
"Liste des sessions actives (device, IP, date)",
"Session courante identifiée",
"Bouton révoquer une session",
"Bouton révoquer toutes les autres sessions",
"Confirmation avant révocation",
]),
Route(40, "/notifications", "Centre de notifications", "account",
specific_checks=[
"Liste des notifications",
"Marquage lu/non-lu",
"Bouton marquer tout comme lu",
"Filtres par type de notification",
"Clic sur notification → navigation correcte",
"État vide",
"Pagination / infinite scroll",
]),
Route(41, "/subscription", "Abonnements & plans", "account",
specific_checks=[
"Affichage des plans disponibles",
"Plan actuel mis en évidence",
"Boutons upgrade / downgrade",
"Comparaison des features par plan",
"Historique de facturation si présent",
"Gestion du moyen de paiement",
]),
# ── Apprentissage & Support ──
Route(42, "/education", "Formation & ressources", "learning",
specific_checks=[
"Liste des cours / ressources",
"Catégories / filtres",
"Progression si présent",
"Contenu vidéo / texte se charge",
"Liens fonctionnels",
]),
Route(43, "/support", "Support & aide", "learning",
specific_checks=[
"FAQ / base de connaissances",
"Formulaire de contact / ticket",
"Chat support si présent",
"Recherche dans l'aide",
"Liens vers documentation",
]),
# ── Administration ──
Route(44, "/admin", "Dashboard admin", "admin", role="admin",
test_with_accounts=["admin"],
specific_checks=[
"Stats globales plateforme (users, tracks, revenue)",
"Graphiques / KPIs",
"Accès interdit pour non-admin → redirect ou 403",
"Liens rapides vers sous-sections admin",
]),
Route(45, "/admin/moderation", "Modération contenu", "admin", role="admin",
test_with_accounts=["admin", "moderator"],
specific_checks=[
"Queue de modération (signalements)",
"Actions (approve, reject, ban)",
"Filtres par type de contenu / statut",
"Détail du signalement",
"Historique des actions de modération",
]),
Route(46, "/admin/platform", "Administration plateforme", "admin", role="admin",
test_with_accounts=["admin"],
specific_checks=[
"Configuration plateforme",
"Gestion des features flags",
"Stats système (storage, bandwidth)",
"Logs si présent",
]),
Route(47, "/admin/transfers", "Gestion transferts/paiements", "admin", role="admin",
test_with_accounts=["admin"],
specific_checks=[
"Liste des transferts / paiements",
"Statuts (pending, completed, failed)",
"Détail par transfert",
"Actions (approve, reject, retry)",
"Filtres et recherche",
]),
Route(48, "/admin/roles", "Gestion des rôles", "admin", role="admin",
test_with_accounts=["admin"],
specific_checks=[
"Liste des rôles existants",
"Permissions par rôle",
"Assignation de rôles aux utilisateurs",
"Création / modification de rôles",
"Protection contre la suppression du rôle admin",
]),
# ── Développeur ──
Route(49, "/developer", "Dashboard développeur", "developer",
specific_checks=[
"Clés API (création, révocation, copie)",
"Documentation API intégrée ou liens",
"Stats d'utilisation API",
"Webhooks configurés",
"Rate limits affichés",
]),
Route(50, "/webhooks", "Gestion webhooks", "developer",
specific_checks=[
"Liste des webhooks configurés",
"Ajout d'un webhook (URL, events, secret)",
"Test de webhook",
"Logs de delivery (succès/échec)",
"Actions (edit, delete, toggle active)",
]),
# ── Redirections ──
Route(51, "/", "Redirect → /launch", "redirect", auth_required=False,
specific_checks=[
"Redirection automatique vers /launch",
"Status code 301/302 approprié",
"Pas de flash de contenu avant redirect",
]),
Route(52, "/tracks", "Redirect → /library", "redirect",
specific_checks=[
"Redirection automatique vers /library",
"Fonctionne quand connecté",
]),
Route(53, "/community", "Redirect → /social", "redirect",
specific_checks=[
"Redirection automatique vers /social",
]),
Route(54, "/favorites", "Redirect → /playlists/favoris", "redirect",
specific_checks=[
"Redirection automatique vers /playlists/favoris",
]),
Route(55, "/home", "Redirect → /dashboard", "redirect",
specific_checks=[
"Redirection automatique vers /dashboard",
]),
# ── Pages d'erreur ──
Route(56, "/404", "Page non trouvée", "error", auth_required=False,
specific_checks=[
"Affichage du message 404",
"Lien retour vers l'accueil",
"Design cohérent avec le reste du site",
"Pas d'erreurs console",
]),
Route(57, "/500", "Erreur serveur", "error", auth_required=False,
specific_checks=[
"Affichage du message d'erreur serveur",
"Lien retour / bouton réessayer",
"Pas de stack trace exposée",
]),
Route(58, "/random-nonexistent-path", "Catch-all → /404", "error", auth_required=False,
specific_checks=[
"Redirection vers la page 404",
"URL arbitraire correctement interceptée",
]),
]
# ─────────────────────────────────────────────
# Prompt template
# ─────────────────────────────────────────────
def build_test_accounts_block() -> str:
lines = []
lines.append("Comptes de test :")
lines.append("")
lines.append("┌───────────┬───────────────────┬──────────────┐")
lines.append("│ Rôle │ Email │ Mot de passe │")
lines.append("├───────────┼───────────────────┼──────────────┤")
for i, acc in enumerate(TEST_ACCOUNTS):
role = acc["role"].ljust(9)
email = acc["email"].ljust(17)
pwd = acc["password"].ljust(12)
lines.append(f"│ {role} │ {email} │ {pwd} │")
if i < len(TEST_ACCOUNTS) - 1:
lines.append("├───────────┼───────────────────┼──────────────┤")
lines.append("└───────────┴───────────────────┴──────────────┘")
lines.append(f"Domaine : {DOMAIN}")
return "\n".join(lines)
def build_prompt(route: Route) -> str:
"""Génère le prompt Claude Code pour une route donnée."""
# Déterminer les comptes à utiliser
if route.test_with_accounts:
accounts_to_use = route.test_with_accounts
elif route.role == "admin":
accounts_to_use = ["admin"]
elif route.role == "creator":
accounts_to_use = ["creator"]
elif not route.auth_required:
accounts_to_use = ["(pas de connexion)", "user"]
else:
accounts_to_use = ["user", "creator"]
accounts_str = ", ".join(accounts_to_use)
# Checks spécifiques
checks_block = ""
if route.specific_checks:
checks_lines = [f" - {c}" for c in route.specific_checks]
checks_block = "\n".join(checks_lines)
# Paramètres dynamiques
params_note = ""
if route.params:
params_note = f"""
Note sur les paramètres dynamiques :
Cette route utilise le paramètre `{route.params}`.
- Teste avec une valeur VALIDE (existante en base)
- Teste avec une valeur INVALIDE (inexistante) → comportement d'erreur attendu
- Teste avec une valeur malformée (injection, caractères spéciaux)
"""
prompt = f"""ok maintenant il faut dresser une liste exhaustive de toutes les erreurs qui existent dans la page "{route.name}" ({route.path}).
Il faut tester chaque fonctionnalité une par une avec le MCP Playwright pour simuler un utilisateur réel.
{build_test_accounts_block()}
Comptes à utiliser pour cette page : {accounts_str}
{params_note}
─── INSTRUCTIONS ───
1. AUDIT EXHAUSTIF avec Playwright MCP :
Pour la page {route.path} ("{route.name}"), teste systématiquement :
a) CHARGEMENT & RENDU
- La page se charge sans erreur (pas de crash, pas de blank screen)
- Pas d'erreurs dans la console navigateur (JS errors, failed network requests)
- Tous les éléments visuels attendus sont présents et visibles
- Le layout est correct (pas d'overflow, pas d'éléments qui se chevauchent)
b) FONCTIONNALITÉS SPÉCIFIQUES À CETTE PAGE
{checks_block}
c) RÉSEAU & API
- Tous les appels API réussissent (pas de 4xx/5xx inattendus)
- Les données affichées correspondent aux réponses API
- Gestion correcte du loading state
- Gestion correcte des erreurs réseau
d) SÉCURITÉ
- Pas de fuite d'informations sensibles (tokens, emails dans l'URL, clés API)
- Les données d'un autre utilisateur ne sont pas accessibles
- Les actions protégées nécessitent bien l'authentification
- CSRF / XSS : vérifier les inputs utilisateur
{" - Vérifier que les rôles non-autorisés reçoivent bien un 403 ou redirect" if route.role != "user" else ""}
e) ACCESSIBILITÉ (a11y)
- Tous les éléments interactifs ont un accessible name descriptif (pas juste "Checkbox" ou "Button")
- Les labels sont associés aux inputs
- Le focus order est logique (Tab navigation)
- Les aria-labels/aria-describedby sont présents et pertinents
- Contraste suffisant sur les textes
f) INTERNATIONALISATION (i18n)
- Pas de mélange de langues (tout FR ou tout EN, pas les deux)
- Les traductions sont complètes (pas de clés i18n brutes affichées)
g) RESPONSIVE
- Tester en viewport mobile (375px), tablet (768px), desktop (1280px)
- Pas d'éléments qui débordent
- Navigation mobile fonctionnelle
2. FORMAT DU RAPPORT :
Produis un rapport structuré exactement comme suit :
```
Rapport exhaustif des erreurs — Page {route.name} ({route.path})
Testé avec le(s) compte(s) : {accounts_str}
Date : [date du test]
───
ERREURS CRITIQUES (bloquantes)
BUG #1 — [Titre court]
- Sévérité: CRITIQUE
- Section: [section de la page]
- Repro: [étapes de reproduction]
- Erreur: [description technique]
- Source: [fichier:ligne si identifiable]
- Impact: [impact utilisateur]
───
ERREURS HAUTES
...
───
ERREURS MOYENNES
...
───
ERREURS FAIBLES
...
───
RÉSUMÉ
┌────────────────────────────┬─────────┬──────────────┐
│ Catégorie │ Nombre │ Sévérité max │
├────────────────────────────┼─────────┼──────────────┤
│ ... │ ... │ ... │
└────────────────────────────┴─────────┴──────────────┘
```
3. CORRECTION DES BUGS :
Après avoir dressé la liste, corrige TOUS les problèmes trouvés pour que la page soit
entièrement fonctionnelle. Pour chaque fix :
- Identifie le fichier source exact
- Applique le correctif
- Vérifie avec Playwright que le bug est résolu
4. TRANSFORMATION EN TESTS E2E :
Transforme toute la suite de vérification que tu viens de faire pour la page {route.path} en suite de
tests Playwright que tu peux ajouter directement à ceux existants dans {E2E_TEST_DIR}.
Spécifications des tests :
- Fichier : {E2E_TEST_DIR}tests/{_route_to_filename(route.path)}.spec.ts
- Framework : Playwright Test (@playwright/test)
- Pattern : Page Object Model si la page est complexe
- Chaque bug trouvé ET corrigé = au moins 1 test de non-régression
- Chaque fonctionnalité testée manuellement = 1 test automatisé
- Tests organisés par describe() : "Chargement", "Fonctionnalités", "Sécurité", "a11y", "i18n"
- Les tests doivent être indépendants (setup/teardown propre)
- Utiliser les test accounts définis ci-dessus pour l'auth dans les fixtures
Structure attendue :
```typescript
import {{ test, expect }} from '@playwright/test';
test.describe('{route.name} ({route.path})', () => {{
test.describe('Chargement & Rendu', () => {{
test('la page se charge sans erreur', async ({{ page }}) => {{
// ...
}});
}});
test.describe('Fonctionnalités', () => {{
// Un test par fonctionnalité
}});
test.describe('Sécurité', () => {{
// Tests de sécurité
}});
test.describe('Accessibilité', () => {{
// Tests a11y
}});
test.describe('Régression', () => {{
// Un test par bug corrigé
}});
}});
```
Assure-toi que les nouveaux tests s'intègrent avec la config Playwright existante
et ne dupliquent pas des tests déjà présents.
"""
return prompt.strip()
def _route_to_filename(path: str) -> str:
"""Convertit un path comme /admin/moderation en admin-moderation."""
clean = path.strip("/").replace("/", "-").replace(":", "")
if not clean:
clean = "root-redirect"
return clean
# ─────────────────────────────────────────────
# Output functions
# ─────────────────────────────────────────────
def write_single_prompt(route: Route, output_dir: str) -> str:
"""Écrit un prompt dans un fichier et retourne le chemin."""
os.makedirs(output_dir, exist_ok=True)
filename = f"{route.number:02d}-{_route_to_filename(route.path)}.md"
filepath = os.path.join(output_dir, filename)
prompt = build_prompt(route)
with open(filepath, "w", encoding="utf-8") as f:
f.write(prompt)
return filepath
def write_combined(routes: list[Route], output_dir: str) -> str:
"""Écrit tous les prompts dans un seul fichier."""
os.makedirs(output_dir, exist_ok=True)
filepath = os.path.join(output_dir, "ALL-PROMPTS.md")
with open(filepath, "w", encoding="utf-8") as f:
f.write(f"# VEZA — Prompts d'audit exhaustif pour les {len(routes)} routes\n")
f.write(f"# Généré le {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(f"# Usage : copier-coller chaque section dans Claude Code une par une.\n\n")
for route in routes:
f.write(f"\n{'='*80}\n")
f.write(f"# ROUTE {route.number:02d}/{len(routes)} — {route.path} ({route.name})\n")
f.write(f"{'='*80}\n\n")
f.write(build_prompt(route))
f.write("\n\n")
return filepath
def list_routes(routes: list[Route]):
"""Affiche toutes les routes."""
categories = {}
for r in routes:
categories.setdefault(r.category, []).append(r)
cat_names = {
"public": "Pages publiques",
"main_nav": "Navigation principale",
"playlists": "Playlists & Tracks",
"social": "Social & Communication",
"marketplace": "Marketplace & Commerce",
"creator": "Créateur & Analytics",
"account": "Compte & Paramètres",
"learning": "Apprentissage & Support",
"admin": "Administration",
"developer": "Développeur",
"redirect": "Redirections",
"error": "Pages d'erreur",
}
for cat, cat_routes in categories.items():
print(f"\n ── {cat_names.get(cat, cat)} ──")
for r in cat_routes:
auth = "🔓" if not r.auth_required else f"🔒 {r.role}"
print(f" {r.number:2d}. {r.path:<30s} {r.name:<35s} {auth}")
print(f"\n Total : {len(routes)} routes")
def get_batch(routes: list[Route], batch_num: int, batch_size: int = 5) -> list[Route]:
"""Retourne un sous-ensemble de routes par batch."""
start = (batch_num - 1) * batch_size
end = start + batch_size
return routes[start:end]
# ─────────────────────────────────────────────
# CLI
# ─────────────────────────────────────────────
def main():
parser = argparse.ArgumentParser(
description="VEZA — Générateur de prompts Claude Code pour audit exhaustif",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Exemples :
python veza-prompt-generator.py # Tous les prompts → ./prompts/
python veza-prompt-generator.py --route /settings # Un seul prompt
python veza-prompt-generator.py --route /admin # Un seul prompt
python veza-prompt-generator.py --list # Liste les routes
python veza-prompt-generator.py --batch 3 # Routes 11-15
python veza-prompt-generator.py --batch 3 --size 10 # Routes 21-30
python veza-prompt-generator.py --combined # Tout dans un fichier
python veza-prompt-generator.py --category admin # Toutes les routes admin
python veza-prompt-generator.py --out ./my-prompts # Dossier custom
python veza-prompt-generator.py --print /settings # Affiche dans le terminal
""",
)
parser.add_argument("--route", type=str, help="Génère le prompt pour une route spécifique (ex: /settings)")
parser.add_argument("--list", action="store_true", help="Liste toutes les routes")
parser.add_argument("--batch", type=int, help="Numéro de batch (1-indexed)")
parser.add_argument("--size", type=int, default=5, help="Taille d'un batch (défaut: 5)")
parser.add_argument("--combined", action="store_true", help="Génère un seul fichier avec tous les prompts")
parser.add_argument("--category", type=str, help="Filtre par catégorie (public, admin, social, ...)")
parser.add_argument("--out", type=str, default=OUTPUT_DIR, help=f"Dossier de sortie (défaut: {OUTPUT_DIR})")
parser.add_argument("--print", dest="print_route", type=str, help="Affiche le prompt dans le terminal au lieu de l'écrire")
parser.add_argument("--critical-only", action="store_true", help="Uniquement les routes avec auth/rôle spécifique")
args = parser.parse_args()
# ── List ──
if args.list:
list_routes(ROUTES)
return
# ── Print single ──
if args.print_route:
route = next((r for r in ROUTES if r.path == args.print_route), None)
if not route:
print(f"❌ Route '{args.print_route}' non trouvée.", file=sys.stderr)
print(" Utilise --list pour voir toutes les routes.", file=sys.stderr)
sys.exit(1)
print(build_prompt(route))
return
# ── Single route ──
if args.route:
route = next((r for r in ROUTES if r.path == args.route), None)
if not route:
print(f"❌ Route '{args.route}' non trouvée.", file=sys.stderr)
print(" Utilise --list pour voir toutes les routes.", file=sys.stderr)
sys.exit(1)
filepath = write_single_prompt(route, args.out)
print(f"✅ Prompt généré : {filepath}")
return
# ── Filter by category ──
routes = ROUTES
if args.category:
routes = [r for r in ROUTES if r.category == args.category]
if not routes:
print(f"❌ Catégorie '{args.category}' non trouvée.", file=sys.stderr)
cats = sorted(set(r.category for r in ROUTES))
print(f" Catégories disponibles : {', '.join(cats)}", file=sys.stderr)
sys.exit(1)
if args.critical_only:
routes = [r for r in routes if r.role != "user" or not r.auth_required]
# ── Batch ──
if args.batch:
routes = get_batch(routes, args.batch, args.size)
if not routes:
total_batches = (len(ROUTES) + args.size - 1) // args.size
print(f"❌ Batch {args.batch} vide. Batches disponibles : 1-{total_batches}", file=sys.stderr)
sys.exit(1)
print(f"📦 Batch {args.batch} ({len(routes)} routes)")
# ── Combined ──
if args.combined:
filepath = write_combined(routes, args.out)
print(f"✅ Fichier combiné généré : {filepath} ({len(routes)} prompts)")
return
# ── All individual files ──
os.makedirs(args.out, exist_ok=True)
for route in routes:
filepath = write_single_prompt(route, args.out)
print(f" ✅ {route.number:2d}. {route.path:<30s} → {filepath}")
print(f"\n🎉 {len(routes)} prompts générés dans {args.out}/")
print(f" Copie chaque prompt dans Claude Code un par un.")
total_batches = (len(routes) + args.size - 1) // args.size
print(f" Ou utilise --batch N (1-{total_batches}) pour y aller par groupes de {args.size}.")
if __name__ == "__main__":
main()