From dbe64cbfbbbd1aa74eb505c44ba203815cbfe0da Mon Sep 17 00:00:00 2001 From: Zhongwei Li Date: Sat, 29 Nov 2025 17:59:01 +0800 Subject: [PATCH] Initial commit --- .claude-plugin/plugin.json | 15 ++ README.md | 3 + commands/elegant-objects.md | 6 + commands/phpstan.md | 5 + plugin.lock.json | 61 ++++++++ skills/elegant-objects/SKILL.md | 159 +++++++++++++++++++ skills/phpstan-resolver/README.md | 69 +++++++++ skills/phpstan-resolver/SKILL.md | 250 ++++++++++++++++++++++++++++++ 8 files changed, 568 insertions(+) create mode 100644 .claude-plugin/plugin.json create mode 100644 README.md create mode 100644 commands/elegant-objects.md create mode 100644 commands/phpstan.md create mode 100644 plugin.lock.json create mode 100644 skills/elegant-objects/SKILL.md create mode 100644 skills/phpstan-resolver/README.md create mode 100644 skills/phpstan-resolver/SKILL.md diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..12e3241 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,15 @@ +{ + "name": "qa", + "description": "Quality assurance : PHPStan automatisé, tests, linters avec skills spécialisés", + "version": "1.2.0", + "author": { + "name": "Aurélien Tournayre", + "email": "aurelien.tournayre@gmail.com" + }, + "skills": [ + "./skills" + ], + "commands": [ + "./commands" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b7dd691 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# qa + +Quality assurance : PHPStan automatisé, tests, linters avec skills spécialisés diff --git a/commands/elegant-objects.md b/commands/elegant-objects.md new file mode 100644 index 0000000..90d829c --- /dev/null +++ b/commands/elegant-objects.md @@ -0,0 +1,6 @@ +--- +description: Vérifie la conformité aux principes Elegant Objects (fichier ou fichiers modifiés) +argument-hint: "[fichier.php] (optionnel, sinon analyse les fichiers modifiés dans la branche)" +--- + +You must use the Skill tool to invoke the "qa:elegant-objects" skill. diff --git a/commands/phpstan.md b/commands/phpstan.md new file mode 100644 index 0000000..3ca1ce7 --- /dev/null +++ b/commands/phpstan.md @@ -0,0 +1,5 @@ +--- +description: Résoudre les erreurs PHPStan en utilisant l'agent phpstan-error-resolver +--- + +You must use the Skill tool to invoke the "phpstan-resolver" skill. diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..7cbdaaf --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,61 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:atournayre/claude-marketplace:qa", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "138dced66315e433e19da6e3f1d39d500d9e4b2f", + "treeHash": "976a76ebdb12942b969e859bf7d5c97518952f23d461c86f03bd6b2dd453d939", + "generatedAt": "2025-11-28T10:14:00.215464Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "qa", + "description": "Quality assurance : PHPStan automatisé, tests, linters avec skills spécialisés", + "version": "1.2.0" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "83011278f384a96a5571adc935faa5e7066a0c8693d382c267c30cd5c14581e7" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "c25df66cddb8a514cbd5335426b8b03cb2d8f0f6b7734eb5d1fd21e702f87f54" + }, + { + "path": "commands/phpstan.md", + "sha256": "b5a61aaf73e2d808ceea123db72ff892003586b26048d8f46628ffd75814d585" + }, + { + "path": "commands/elegant-objects.md", + "sha256": "5accfef0e1e1a312b990ac6b4a56e753fd30117ca24e8c380b5bff5aa6b07ad1" + }, + { + "path": "skills/elegant-objects/SKILL.md", + "sha256": "cb010340643eba833fa436ada0fd061d27366e9de568bcd02727f65edfce6261" + }, + { + "path": "skills/phpstan-resolver/README.md", + "sha256": "30b9f28224ceb138c4d4bc65dda65f3aebeb6ad2f3a1f2677739311af07fbf1c" + }, + { + "path": "skills/phpstan-resolver/SKILL.md", + "sha256": "974d3aa83846d73f754cfce45c472901a29ad6b83fa6dc830a4485e628a5ee5b" + } + ], + "dirSha256": "976a76ebdb12942b969e859bf7d5c97518952f23d461c86f03bd6b2dd453d939" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file diff --git a/skills/elegant-objects/SKILL.md b/skills/elegant-objects/SKILL.md new file mode 100644 index 0000000..240d154 --- /dev/null +++ b/skills/elegant-objects/SKILL.md @@ -0,0 +1,159 @@ +--- +name: elegant-objects +description: > + Vérifie la conformité du code PHP aux principes Elegant Objects de Yegor Bugayenko. + Analyse un fichier spécifique ou tous les fichiers modifiés dans la branche. +allowed-tools: [Bash, Read, Grep, Glob] +model: sonnet +--- + +# Elegant Objects Reviewer Skill + +## Variables + +```bash +TARGET="$ARGUMENTS" # Fichier spécifique ou vide pour fichiers modifiés +``` + +## Workflow + +### Étape 1: Déterminer les fichiers à analyser + +```bash +if [ -n "$TARGET" ] && [ -f "$TARGET" ]; then + # Fichier spécifique fourni + FILES_TO_ANALYZE="$TARGET" + echo "📁 Analyse du fichier: $TARGET" +else + # Fichiers PHP modifiés dans la branche + BASE_BRANCH=$(git rev-parse --abbrev-ref HEAD@{upstream} 2>/dev/null || echo "main") + FILES_TO_ANALYZE=$(git diff --name-only "$BASE_BRANCH"...HEAD -- '*.php' 2>/dev/null || git diff --name-only HEAD -- '*.php') + + if [ -z "$FILES_TO_ANALYZE" ]; then + echo "⚠️ Aucun fichier PHP modifié détecté" + exit 0 + fi + + echo "📁 Fichiers PHP modifiés dans la branche:" + echo "$FILES_TO_ANALYZE" | while read f; do echo " - $f"; done +fi +``` + +### Étape 2: Analyser chaque fichier + +Pour chaque fichier PHP, vérifier les règles Elegant Objects. + +#### Règles de conception des classes + +1. **Classes final** - Toutes les classes doivent être `final` (sauf abstraites) +2. **Attributs max 4** - Chaque classe encapsule 1 à 4 attributs maximum +3. **Pas de getters** - Éviter les méthodes `getX()` (modèle anémique) +4. **Pas de setters** - Éviter les méthodes `setX()` (mutabilité) +5. **Pas de méthodes statiques** - Strictement interdites dans les classes +6. **Pas de classes utilitaires** - Classes avec uniquement des méthodes statiques interdites +7. **Noms sans -er** - Noms de classes ne finissant pas par -er (Manager, Handler, Helper...) +8. **Constructeur unique** - Un seul constructeur principal par classe +9. **Constructeurs simples** - Ne contiennent que des affectations + +#### Règles de méthodes + +1. **Pas de retour null** - Les méthodes ne doivent jamais retourner `null` +2. **Pas d'argument null** - `null` ne doit pas être passé en argument +3. **Corps sans lignes vides** - Les corps de méthodes sans lignes vides +4. **Corps sans commentaires** - Les corps de méthodes sans commentaires inline +5. **Noms verbes simples** - Noms de méthodes sont des verbes simples (pas composés) +6. **CQRS** - Séparation commandes (void) et queries (retour valeur) + +#### Règles de style + +1. **Messages sans point final** - Messages d'erreur/log sans point final +2. **Messages une phrase** - Messages d'erreur/log en une seule phrase +3. **Fail fast** - Exceptions lancées au plus tôt + +#### Règles de tests + +1. **Une assertion par test** - Chaque test ne contient qu'une assertion +2. **Assertion dernière** - L'assertion est la dernière instruction +3. **Pas de setUp/tearDown** - Ne pas utiliser ces méthodes +4. **Noms français** - Noms de tests en français décrivant le comportement +5. **Messages négatifs** - Assertions avec messages d'échec négatifs +6. **Pas de constantes partagées** - Pas de littéraux statiques partagés + +### Étape 3: Générer le rapport + +Format du rapport: + +``` +## Score de conformité Elegant Objects + +Score global: X/100 + +## Violations critiques (bloquantes) + +### [Règle violée] +- **Fichier:** /chemin/fichier.php:ligne +- **Problème:** Description précise +- **Suggestion:** Code corrigé ou approche + +## Violations majeures (à corriger) + +[Même format] + +## Recommandations (améliorations) + +[Même format] + +## Statistiques + +- Fichiers analysés: X +- Classes analysées: Y +- Méthodes analysées: Z +- Tests analysés: W +- Total violations: N + +## Prochaines étapes + +Liste priorisée des corrections à effectuer +``` + +## Calcul du score + +- Violation critique: -10 points +- Violation majeure: -5 points +- Recommandation: -2 points +- Score de base: 100 + +## Patterns à détecter (regex) + +```php +# Classes non-final +/^class\s+\w+/ # sans 'final' avant + +# Getters +/public\s+function\s+get[A-Z]\w*\s*\(/ + +# Setters +/public\s+function\s+set[A-Z]\w*\s*\(/ + +# Méthodes statiques +/public\s+static\s+function/ + +# Noms en -er +/class\s+\w+(er|or|Manager|Handler|Helper|Builder|Factory|Provider|Controller|Processor)\b/ + +# Retour null +/return\s+null\s*;/ + +# Lignes vides dans méthodes +/\{\s*\n\s*\n/ # ouverture suivie de ligne vide + +# Commentaires dans corps +/^\s+\/\// # à l'intérieur des méthodes +``` + +## Notes + +- Ignorer fichiers vendor/, var/, cache/ +- Contexte framework Symfony considéré (Controllers tolérés) +- Prioriser violations par criticité +- Proposer code corrigé quand possible diff --git a/skills/phpstan-resolver/README.md b/skills/phpstan-resolver/README.md new file mode 100644 index 0000000..8646950 --- /dev/null +++ b/skills/phpstan-resolver/README.md @@ -0,0 +1,69 @@ +# PHPStan Error Resolver Skill + +Résout automatiquement les erreurs PHPStan en boucle jusqu'à zéro erreur ou stagnation. + +## Fonctionnalités + +- Détection automatique des erreurs PHPStan +- Groupement des erreurs par fichier +- Résolution par batch (5 erreurs max par fichier par itération) +- Boucle de résolution avec vérification après chaque correction +- Détection de stagnation (erreurs qui ne diminuent plus) +- Rapport détaillé avec taux de succès + +## Usage + +Via la commande délégante : +```bash +/qa:phpstan +``` + +Ou directement via le skill : +```bash +# Utiliser l'outil Task avec le skill phpstan-resolver +``` + +## Workflow + +1. Analyse PHPStan initiale +2. Groupement erreurs par fichier +3. Pour chaque fichier avec erreurs : + - Batch de 5 erreurs max + - Délégation à agent `@phpstan-error-resolver` + - Correction via Edit tool +4. Re-exécution PHPStan pour vérification +5. Répétition jusqu'à : + - Zéro erreur (succès total) + - Stagnation (erreurs ne diminuent plus) + - Max itérations (10) + +## Configuration + +- `ERROR_BATCH_SIZE`: 5 erreurs par fichier par itération +- `MAX_ITERATIONS`: 10 itérations maximum +- `PHPSTAN_CONFIG`: phpstan.neon ou phpstan.neon.dist + +## Dépendances + +- PHPStan installé (`./vendor/bin/phpstan`) +- Configuration PHPStan valide +- Agent `@phpstan-error-resolver` disponible +- `jq` pour parsing JSON + +## Rapport généré + +```yaml +details: + total_errors_initial: [nombre] + total_errors_final: [nombre] + errors_fixed: [nombre] + success_rate: "[%]" + iterations: [nombre] +``` + +## Notes + +- Utilise format JSON de PHPStan pour parsing précis +- Évite boucles infinies avec max itérations +- Détecte stagnation automatiquement +- Rapport détaillé même en cas d'échec partiel diff --git a/skills/phpstan-resolver/SKILL.md b/skills/phpstan-resolver/SKILL.md new file mode 100644 index 0000000..83f616c --- /dev/null +++ b/skills/phpstan-resolver/SKILL.md @@ -0,0 +1,250 @@ +--- +name: phpstan-resolver +description: > + Résout automatiquement les erreurs PHPStan détectées dans le projet en analysant + et corrigeant les problèmes de types stricts, annotations generics, array shapes + et collections Doctrine. Boucle jusqu'à zéro erreur ou stagnation. +allowed-tools: [Task, Bash, Read, Edit, Grep, Glob, TodoWrite] +model: claude-opus-4-1-20250805 +--- + +# PHPStan Error Resolver Skill + +## Variables + +```bash +PHPSTAN_CONFIG="phpstan.neon" # ou phpstan.neon.dist +PHPSTAN_BIN="./vendor/bin/phpstan" +ERROR_BATCH_SIZE=5 +MAX_ITERATIONS=10 +``` + +## Workflow + +### Étape 0: Timing +```bash +START_TIME=$(date +%s) +date +``` + +### Étape 1: Vérification Environnement + +```bash +# Vérifier PHPStan installé +if [ ! -f "$PHPSTAN_BIN" ]; then + echo "❌ PHPStan non trouvé: $PHPSTAN_BIN" + exit 1 +fi + +# Vérifier config PHPStan +if [ ! -f "$PHPSTAN_CONFIG" ] && [ ! -f "phpstan.neon.dist" ]; then + echo "❌ Configuration PHPStan introuvable" + exit 1 +fi + +# Utiliser phpstan.neon.dist si phpstan.neon absent +if [ ! -f "$PHPSTAN_CONFIG" ]; then + PHPSTAN_CONFIG="phpstan.neon.dist" +fi + +echo "✅ Environnement PHPStan valide" +echo " Config: $PHPSTAN_CONFIG" +``` + +### Étape 2: Exécution Initiale PHPStan + +```bash +echo "🔍 Analyse PHPStan initiale..." + +# Exécuter PHPStan +$PHPSTAN_BIN analyze --no-progress --error-format=json > /tmp/phpstan_initial.json + +# Parser résultat +TOTAL_ERRORS_INITIAL=$(jq '.totals.file_errors' /tmp/phpstan_initial.json) + +if [ "$TOTAL_ERRORS_INITIAL" -eq 0 ]; then + echo "✅ Aucune erreur PHPStan détectée" + exit 0 +fi + +echo "📊 Erreurs détectées: $TOTAL_ERRORS_INITIAL" +``` + +### Étape 3: TodoWrite Initialisation + +```yaml +todos: + - content: "Analyser erreurs PHPStan" + status: "completed" + activeForm: "Analyse des erreurs PHPStan" + - content: "Grouper erreurs par fichier" + status: "pending" + activeForm: "Groupement des erreurs par fichier" + - content: "Résoudre erreurs (itération 1)" + status: "pending" + activeForm: "Résolution des erreurs (itération 1)" + - content: "Vérifier résolution" + status: "pending" + activeForm: "Vérification de la résolution" +``` + +### Étape 4: Groupement Erreurs par Fichier + +Marquer todo #2 `in_progress`. + +```bash +# Parser JSON et grouper par fichier +jq -r '.files | to_entries[] | "\(.key)|\(.value.messages | length)"' /tmp/phpstan_initial.json > /tmp/phpstan_files.txt + +# Afficher groupement +echo "📁 Erreurs par fichier:" +cat /tmp/phpstan_files.txt | while IFS='|' read file count; do + echo " - $file: $count erreurs" +done +``` + +Marquer todo #2 `completed`. + +### Étape 5: Boucle de Résolution + +Marquer todo #3 `in_progress`. + +```bash +ITERATION=1 +ERRORS_CURRENT=$TOTAL_ERRORS_INITIAL +ERRORS_PREVIOUS=0 + +while [ $ITERATION -le $MAX_ITERATIONS ] && [ $ERRORS_CURRENT -gt 0 ] && [ $ERRORS_CURRENT -ne $ERRORS_PREVIOUS ]; do + echo "" + echo "🔄 Itération $ITERATION/$MAX_ITERATIONS" + echo " Erreurs: $ERRORS_CURRENT" + + # Traiter chaque fichier avec erreurs + cat /tmp/phpstan_files.txt | while IFS='|' read file error_count; do + if [ "$error_count" -gt 0 ]; then + echo " 📝 Traitement: $file ($error_count erreurs)" + + # Extraire erreurs pour ce fichier + jq -r --arg file "$file" '.files[$file].messages[] | "\(.line)|\(.message)"' /tmp/phpstan_initial.json > /tmp/phpstan_file_errors.txt + + # Limiter batch size + head -n $ERROR_BATCH_SIZE /tmp/phpstan_file_errors.txt > /tmp/phpstan_batch.txt + + # Déléguer à agent phpstan-error-resolver + echo "Utiliser agent @phpstan-error-resolver avec:" + echo "Fichier: $file" + echo "Erreurs:" + cat /tmp/phpstan_batch.txt + + # L'agent lit le fichier, analyse erreurs, applique corrections via Edit + fi + done + + # Re-exécuter PHPStan + echo " 🔍 Vérification post-correction..." + $PHPSTAN_BIN analyze --no-progress --error-format=json > /tmp/phpstan_iteration_${ITERATION}.json + + ERRORS_PREVIOUS=$ERRORS_CURRENT + ERRORS_CURRENT=$(jq '.totals.file_errors' /tmp/phpstan_iteration_${ITERATION}.json) + + echo " 📊 Résultat: $ERRORS_CURRENT erreurs restantes" + + # Mettre à jour fichiers avec erreurs + jq -r '.files | to_entries[] | "\(.key)|\(.value.messages | length)"' /tmp/phpstan_iteration_${ITERATION}.json > /tmp/phpstan_files.txt + + ITERATION=$((ITERATION + 1)) +done +``` + +Marquer todo #3 `completed`. + +### Étape 6: Analyse Résultat Final + +Marquer todo #4 `in_progress`. + +```bash +ERRORS_FIXED=$((TOTAL_ERRORS_INITIAL - ERRORS_CURRENT)) +SUCCESS_RATE=$(awk "BEGIN {printf \"%.1f\", ($ERRORS_FIXED / $TOTAL_ERRORS_INITIAL) * 100}") + +echo "" +echo "📊 Résumé Final:" +echo " - Erreurs initiales: $TOTAL_ERRORS_INITIAL" +echo " - Erreurs restantes: $ERRORS_CURRENT" +echo " - Erreurs corrigées: $ERRORS_FIXED" +echo " - Taux de succès: ${SUCCESS_RATE}%" +echo " - Itérations: $((ITERATION - 1))/$MAX_ITERATIONS" + +# Identifier fichiers non résolus +if [ $ERRORS_CURRENT -gt 0 ]; then + echo "" + echo "⚠️ Fichiers avec erreurs restantes:" + cat /tmp/phpstan_files.txt | while IFS='|' read file count; do + if [ "$count" -gt 0 ]; then + echo " - $file: $count erreurs" + fi + done +fi +``` + +Marquer todo #4 `completed`. + +### Étape 7: Rapport Final + +```bash +END_TIME=$(date +%s) +DURATION=$((END_TIME - START_TIME)) + +if [ $DURATION -lt 60 ]; then + DURATION_STR="${DURATION}s" +elif [ $DURATION -lt 3600 ]; then + MINUTES=$((DURATION / 60)) + SECONDS=$((DURATION % 60)) + DURATION_STR="${MINUTES}m ${SECONDS}s" +else + HOURS=$((DURATION / 3600)) + MINUTES=$(((DURATION % 3600) / 60)) + SECONDS=$((DURATION % 60)) + DURATION_STR="${HOURS}h ${MINUTES}m ${SECONDS}s" +fi + +echo "⏱️ Durée: $DURATION_STR" +``` + +```yaml +task: "Résolution des erreurs PHPStan" +status: "terminé" +details: + total_errors_initial: $TOTAL_ERRORS_INITIAL + total_errors_final: $ERRORS_CURRENT + errors_fixed: $ERRORS_FIXED + success_rate: "${SUCCESS_RATE}%" + iterations: $((ITERATION - 1)) +files: + fixed: + - [Liste des fichiers corrigés] + failed: + - [Liste des fichiers non résolus] +statistics: + success_rate: "${SUCCESS_RATE}%" + execution_time: "$DURATION_STR" + phpstan_level: "[Niveau détecté depuis config]" +notes: + - "Toutes les erreurs PHPStan ont été analysées" + - "Les corrections ont été appliquées automatiquement" + - "Relancer si erreurs restantes avec contexte différent" +``` + +## Error Handling + +- PHPStan non trouvé → ARRÊT (exit 1) +- Config PHPStan absente → ARRÊT (exit 1) +- Stagnation (erreurs ne diminuent plus) → ARRÊT avec rapport +- Max itérations atteint → ARRÊT avec rapport + +## Notes + +- Utilise l'agent `@phpstan-error-resolver` pour corrections +- Batch size de 5 erreurs par fichier par itération +- Maximum 10 itérations pour éviter boucles infinies +- Parser JSON via `jq` +- Marquer CHAQUE todo completed immédiatement après succès