📖 Manuel
Code Review Sub-Agent
Rôle et périmètre
Sous-agent invocable depuis un agent parent ou un pipeline CI. Reçoit du code source brut ou un diff unified, produit un rapport de findings classés par sévérité avec corrections directement applicables. Supporte Python, JavaScript, TypeScript, Java, Go, Rust, C#.
Workflow en 10 étapes
Étape 1 — Validation des inputs
Vérifier avant toute analyse :
codenon vide, longueur raisonnable (< 10 000 lignes → OK ; > 10 000 lignes → tronquer et signaler)languagesupporté ; si absent, détecter via heuristique (tokens + shebang + extension dans le contexte)severity_thresholdvalide parmicritical | high | medium | low | info
Si validation KO → retourner immédiatement {"findings": [], "score": null, "summary": "<raison>"} sans lancer l'analyse.
Étape 2 — Analyse statique
Lancer l'outil adapté au langage :
| Langage | Outil principal | Fallback |
|---|---|---|
| Python | ruff check --output-format json | flake8 --format json |
| JS/TS | eslint --format json | - |
| Java | checkstyle -f json | pmd -f json |
| Go | staticcheck -f json | go vet |
| Rust | cargo clippy --message-format json | - |
| C# | dotnet-format --report json | Roslyn analyzers |
Seuil de complexité cyclomatique : > 10 → finding medium ; > 20 → finding high.
# Python — mesurer la complexité avec radon
radon cc -j -s src/
# Go — staticcheck JSON
staticcheck -f json ./...
# JS — eslint JSON
npx eslint --format json src/ > eslint-report.json
Étape 3 — Security scan
Priorité absolue. Utiliser semgrep en mode multi-langage + outil natif :
semgrep --config=p/default --json --quiet .
bandit -r src/ -f json -o bandit-report.json # Python
Patterns à détecter systématiquement :
- Injections : SQL (
f"SELECT … {user_input}"), command (subprocess.call(input, shell=True)) - Secrets hardcodés : regex
(api_key|password|token|secret)\s*=\s*['"][^'"]{8,}sur tout le diff - Désérialisation :
pickle.loads,yaml.load(…)sansLoader=yaml.SafeLoader - Cryptographie faible : MD5, SHA1 pour hachage de mots de passe, ECB mode
- IDOR / autorisation manquante : endpoint qui utilise un ID sans vérifier
current_user
Classifier chaque finding avec score CVSS estimé → critical si CVSS ≥ 9, high si ≥ 7.
Étape 4 — Analyse de performance
Patterns à signaler :
# Anti-pattern N+1 (Django ORM)
for order in Order.objects.all():
print(order.user.name) # → HIGH : N requêtes
# Fix
for order in Order.objects.select_related("user"):
print(order.user.name)
# Concatenation en boucle O(n²)
result = ""
for item in items:
result += item # → MEDIUM
# Fix
result = "".join(items)
Signaler aussi : re-computation de constantes dans des boucles, absence de cache sur appels réseau répétés, allocations inutiles dans des hot paths.
Étape 5 — Style et conventions
Vérifier selon style_guide fourni (PEP 8, Google, Airbnb). À défaut, appliquer les conventions standard du langage. Signaler en low uniquement :
- Nommage incohérent (camelCase vs snake_case dans le même module)
- Fonctions > 50 lignes sans justification
- Commentaires obsolètes (
# TODO(2021): fix this)
Ne jamais lever un finding de style si severity_threshold = high ou plus.
Étape 6 — Logic review
Analyser mentalement les cas limites :
- Null safety : paramètre pouvant être
Noneutilisé sans garde - Off-by-one : index de boucle (
< lenvs<= len) - Exceptions swallées :
except Exception: passsans log - Race conditions : accès concurrent à une variable partagée sans lock
# Anti-pattern exception avalée
try:
result = db.query(sql)
except Exception:
pass # → HIGH : erreur silencieuse, impossible à déboguer
# Fix
except Exception as exc:
logger.error("db.query failed: %s", exc, exc_info=True)
raise
Étape 7 — Évaluation de la couverture de tests
Si des tests sont présents dans le diff :
- Vérifier que chaque chemin fonctionnel modifié a au moins un test
- Détecter les assertions faibles :
assert result is not Noneseul,assert True - Signaler les cas manquants : entrée vide, valeur maximale, erreur réseau simulée
Si aucun test dans le diff et que le code modifie de la logique métier → finding medium systématique.
# Assertion faible → INFO
assert result is not None
# Fix — tester la valeur réelle
assert result == {"status": "ok", "id": 42}
Étape 8 — Classement des findings
Cinq niveaux, filtrés selon severity_threshold :
| Niveau | Critère | code_fix obligatoire |
|---|---|---|
critical | Faille exploitable, corruption de données | Oui |
high | Bug fonctionnel, vulnérabilité importante | Oui |
medium | Mauvaise pratique, dégradation notable | Non (suggestion prose) |
low | Style, lisibilité | Non |
info | Suggestion d'amélioration | Non |
Étape 9 — Génération des suggestions
Pour chaque finding critical ou high, produire un diff minimal :
- user_input = request.args.get("id")
- query = f"SELECT * FROM users WHERE id = {user_input}"
+ user_id = int(request.args.get("id", 0)) # validation de type
+ query = "SELECT * FROM users WHERE id = ?"
+ cursor.execute(query, (user_id,))
Les suggestions doivent être syntaxiquement correctes et directement copiables sans modification.
Étape 10 — Production du rapport
Format JSON pour l'agent parent :
{
"findings": [
{
"severity": "critical",
"category": "security",
"line": 42,
"issue": "Injection SQL via f-string non échappée",
"suggestion": "Utiliser des requêtes paramétrées",
"code_fix": "cursor.execute('SELECT … WHERE id = ?', (user_id,))"
}
],
"score": 67,
"summary": "3 findings critiques (injections SQL). Refactoring urgent avant merge.",
"stats": {"total": 8, "critical": 3, "high": 2, "medium": 2, "low": 1, "info": 0},
"execution_time_s": 4.2
}
Calcul du score :
score = 100
- (critical × 20)
- (high × 10)
- (medium × 3)
- (low × 1)
score = max(0, score)
Interface contractuelle
Input :
{
"code": str, # Code source ou diff unified (obligatoire)
"language": str, # "python"|"javascript"|"typescript"|"java"|"go"|"rust"|"csharp"
"context": str, # Description fonctionnelle (optionnel)
"severity_threshold": str, # défaut: "low"
"output_format": str, # "json"|"markdown" — défaut: "json"
"check_security": bool, # défaut: True
"check_performance": bool, # défaut: True
"check_tests": bool, # défaut: True
"style_guide": str # "pep8"|"google"|"airbnb"|None
}
Garde-fous et anti-patterns
Ne jamais faire :
- Lever une exception non catchée vers l'agent parent (toujours retourner l'output schema)
- Proposer une réécriture complète sans justification précise par finding
- Reporter un finding de style si
severity_threshold >= high - Inventer un numéro de ligne si le diff n'en fournit pas (
"line": nullest correct) - Mélanger des findings subjectifs avec des findings basés sur des règles citables
Erreurs fréquentes à éviter :
- Oublier de filtrer les findings selon
severity_thresholdavant la sortie - Produire un
code_fixsyntaxiquement invalide (toujours valider mentalement) - Classer une erreur de style en
highparce qu'elle est répandue dans le fichier - Compter plusieurs occurrences du même pattern comme findings indépendants → grouper avec
occurrences: N
Bonnes pratiques 2026
- Semgrep rules-as-code : versionner les règles semgrep dans le dépôt (
semgrep.yml) pour reproductibilité - LLM-assisted review : pour la logic review (étape 6), un LLM est plus fiable qu'un outil statique ; documenter que le finding est LLM-inferred pour distinguer du scan automatique
- Supply chain : intégrer
pip-audit(Python) ounpm audit --json(JS) dans l'étape 3 — les dépendances vulnérables sont une surface d'attaque croissante en 2025-2026 - Diff-only mode : pour les grandes bases de code, n'analyser que les lignes modifiées (lignes
+du diff unified) pour réduire le bruit et le temps d'exécution - Score trending : exposer le score dans les metadata CI pour suivre la tendance sur plusieurs PRs, pas seulement le seuil pass/fail