Senior Review
skill_version : 1.16.0 (historique : CHANGELOG.md). Revue de code de niveau senior, conçue à partir de l'état de l'art académique (LLM-as-judge, vérification, mutation) et des meilleurs outils de revue IA (CodeRabbit, Greptile, Cursor BugBot, GitHub Copilot agentic, Qodo, Snyk).
Fichiers du skill (progressive disclosure) : scripts/mutate.py (harnais de mutation, cf. Étape 4), lessons.md et misses.md (instantanés publiés, promus à la main ; les mémoires de travail sont hors dépôt, cf. Étape 1.8), reference/references.md (sources détaillées, à la demande), CHANGELOG.md (historique).
Mémoires de travail, hors dépôt, dans ~/.claude/skill-memory/ :
senior-review-lessons.md : leçons confirmées (vues sur au moins 2 runs). Chargée à l'Étape 1.8. Plafond 40.
senior-review-lessons-candidates.md : leçons vues une seule fois. Jamais chargée. Une leçon n'entre dans la mémoire chargée qu'une fois recroisée, exactement comme un finding n'est remonté qu'une fois prouvé.
senior-review-misses.md : ce qui a raté. Chargée à l'Étape 1.8. Plafond 40.
senior-review-runs.jsonl : une ligne par revue, jamais chargée. Elle sert à analyser les runs entre eux, pas à en informer un.
Posture (ce qui distingue une revue excellente d'une revue bruyante)
La recherche converge sur un seul vrai critère de qualité : le rapport signal/bruit, pas le recall. Un reviewer qui crie au loup est désactivé — c'est l'un des premiers motifs d'abandon des outils de revue IA. Donc : peu de faux positifs, findings prouvés, silence assumé quand rien de matériel, et chaque finding actionnable (file:line + raison + fix). Le second levier le plus fort est le contexte : le diff seul plafonne le catch-rate (44 %), le contexte cross-fichiers + intention le double (82 %, bench Greptile). D'où une revue context-first et liée au ticket (une revue doit vérifier que le code fait ce qui était demandé, pas seulement qu'il est correct — la dimension la plus souvent oubliée et la plus chère en prod).
Principes non négociables
- Reviewer ≠ auteur (blind review). Le self-preference bias est prouvé et causal : un modèle qui juge sa propre sortie se surnote (Panickssery 2024 ; Wataoka 2024). Les agents reviewers tournent en contexte vierge, ne voient PAS le prompt d'implémentation, et — si l'auteur du code est connu comme étant un modèle donné — sont d'un modèle/famille différent. Le nom du développeur n'entre jamais dans le prompt.
- Pas de finding sans preuve vérifiée. Tout finding cite
file:line ET passe une vérification (grep/ast-grep, exécution en sandbox, test qui échoue, traçage du flux) avant d'être remonté. Un finding non vérifiable est droppé silencieusement. Les LLMs sur-flaggent (overcorrection systématique, arXiv:2603.00539) ; l'execution-grounding rejette ~60 % des faux positifs (arXiv:2604.10800).
- Signal > recall. Mieux vaut 3 vrais bugs que 3 vrais + 11 faux. Cap des nits (≤ 5 inline, le reste compté en résumé), silence explicite quand rien de bloquant, et une section « ce qu'on NE flague PAS » aussi importante que « ce qu'on cherche » (Cloudflare : « telling an LLM what not to do is where the value is »).
- CoT avant verdict, confiance par finding. Raisonner (constat → pourquoi → est-ce causé par les lignes modifiées ?) AVANT de poser sévérité + note (G-Eval : CoT avant la note). Confiance basse → marquée « à vérifier manuellement », jamais bloquante.
- Signal externe, pas auto-critique en boucle. L'auto-correction LLM sans oracle dégrade (Huang ICLR 2024). La revue s'ancre sur des signaux EXTERNES (lint, typecheck, tests, SAST, exécution), jamais sur la seule relecture du modèle. Pas de « revue de la revue » en boucle fermée.
- Scope = le diff. On ne note jamais du code legacy non modifié (même mauvais), ni ce qu'un linter/formatter/CI attrape déjà, ni les fichiers générés/vendored/lock.
- Spec-alignment est une dimension de premier rang. Première question : le diff implémente-t-il ce que le ticket demande (couverture complète + pas de scope creep) — pas seulement « est-ce correct ». Sans la description du problème, la revue LLM perd sensiblement en précision (arXiv:2505.20206).
- La sécurité est une lentille DISTINCTE (mindset attaquant, modèle de menace), pas fondue dans la revue de correction : objectifs opposés (la sécu optimise le rappel/paranoïa, la correction la précision). Toujours son propre agent.
- Effort proportionné. Pas de panel multi-agents sur un diff de 10 lignes. Paliers
--quick / standard / --deep.
- Discipline tokens/latence. À qualité égale, le run le moins cher gagne : (a) le context pack (Étape 1) est écrit UNE fois, stable, réutilisé verbatim par tous les reviewers (cache-friendly Anthropic) — jamais reconstruit ni re-collé par dimension ; (b) les receipts de l'Étape 4 sont vérifiés par l'orchestrateur lui-même (grep/exec/Read direct), jamais délégués à un agent dédié — un aller-retour agent coûterait un tour complet pour reproduire ce qu'une commande fait en un appel ; (c) le refute-panel (Étape 5) ne tourne QUE sur le critique/incertain, jamais en aveugle sur l'ensemble des findings ; (d) les sorties d'outils (Étape 2 : lint/tests/SAST) entrent résumées dans le context pack (échecs + comptes, pas le log brut complet) ; (e) une re-revue immédiate du même diff dans la même session (ex. l'utilisateur redemande une revue après avoir appliqué les fixes suggérés) continue les reviewers déjà lancés via
SendMessage plutôt que d'en relancer 5 aveugles frais sur l'intégralité — l'aveuglement protège le PREMIER jugement, pas la vérification d'un correctif ; repasser en aveugle frais dès que le diff contient du code substantiellement nouveau hors du delta déjà couvert (pattern evaluator-optimizer, transposé de feature-loop 8.11.0) ; au-delà de la session — après un /clear, ou une revue reprise des jours plus tard sur la même branche — c'est le ledger d'arbitrages (Étapes 1.9 et 7) qui porte la continuité, jamais un re-collage des findings précédents.
- Review-only par défaut. On propose des fixes ; on n'édite/poste rien sans
--fix/--comment explicite, et on confirme avant toute action sortante (commentaire PR, push).
- Mode spécialiste selon l'architecture détectée. Un généraliste rate les invariants propres à une stack. La revue reconnaît l'archi (Étape 1) — ex. « Shopify + CQRS/ES en Go », « Next/tRPC », « Spring/DDD » — et bascule en expert senior de cette stack : on le propose/confirme à l'utilisateur quand l'angle n'est pas déjà donné, et chaque reviewer reçoit la persona experte + les invariants/pièges connus de l'archi à vérifier en priorité. Le mode spécialiste n'élargit pas le bruit : il affine ce qu'on cherche, pas le nombre de findings.
- Recherche externe autorisée en cas de doute (avec discipline). Quand un doute porte sur un comportement version/API/framework-spécifique (sémantique d'un flag, API tierce, CVE, idiome récent, plafond/pagination d'une API), un reviewer PEUT consulter le web (
WebSearch/WebFetch) plutôt que deviner ou sur-flaguer. Discipline : source primaire/officielle d'abord, citer la source + sa date, et re-vérifier contre le code et la version réelle du repo — une réponse web informe mais n'est jamais le receipt (le receipt reste grep/exec/test). En cas d'indispo réseau, le dire et baisser la confiance.
Pipeline
0. PARSE + DÉTECTION CIBLE (working tree défaut / staged / branche vs base / PR) + tier (quick|standard|deep)
↓
1. CONTEXT ASSEMBLY (le différenciateur n°1)
diff (incl. untracked!) + TICKET/spec + conventions projet (CLAUDE.md/rules/lint)
+ cross-file (call sites/callers/impls) + git blame/log + stack/versions + learnings repo
↓ → "context pack" stable (cache-friendly), réutilisé par tous les reviewers
2. GATE OBJECTIF (signal externe AVANT jugement LLM)
lint + typecheck + tests + SAST (gosec/semgrep/…) → nourrit les reviewers (ne pas re-flaguer)
+ routage dimensions pertinentes (pas de chasse SQLi sans DB — iCodeReviewer)
↓
3. REVUE DÉCOMPOSÉE EN AVEUGLE (agents spécialisés ∥, reviewer ≠ auteur)
spec-alignment · correctness/bugs · security(threat-model) · design/maintainability · tests · [perf/ux cond.]
chacun : CoT → findings {file:line, sévérité, confiance, raison, fix, PLAN DE VÉRIFICATION}
↓
4. GATE DE VÉRIFICATION ("receipts" — tue les faux positifs)
chaque finding (surtout critical/major) PROUVÉ : grep/ast-grep, exécution sandbox, test qui rougit,
traçage flux source→sink. Non prouvé → drop. "tests faibles" → red-check par mutation.
↓
5. SYNTHÈSE CALIBRÉE (dédup cross-dimensions + panel adversarial sur le douteux/critique seulement)
refute-panel (skeptique indépendant tente de réfuter ; majorité pour garder) — PoLL
+ calibration confiance + tiers sévérité (🔴 bloquant / 🟡 important / 🔵 nit·suggestion / 👍 praise)
↓
6. VERDICT + RAPPORT (signal/bruit discipliné) → spec-coverage verdict + go/no-go ; silence si rien
[option --comment → poste PR ; --fix → applique les fixes high-confidence, après confirmation]
↓
7. LEARNINGS (mémoire par repo : FP confirmés, conventions découvertes) + lessons et misses cross-projet
Logs (préfixes, style factuel, pas d'emojis hors rapport final)
[scope] [context] [gate] [route] [review:<dim>] [verify] [panel] [calib] [verdict] [report] [comment] [fix] [learn]. 1 ligne par sous-étape clé, pas de silence > 3 min.
Parsing des arguments
Cible (auto-détectée, override possible) :
- (défaut) working tree : modifs non commitées =
git status --porcelain → tracked modifiés + untracked (⚠️ git diff seul rate les fichiers neufs ; lire les untracked en entier).
--base <ref> : revoir git diff <ref>...HEAD (revue de branche ; base = origin/main/develop si déduisible).
--staged : git diff --cached.
--pr <N> : récupérer la PR via gh pr (GitHub) ou glab mr/glab issue (GitLab) ; diff + description + commentaires.
<path> : restreindre à un fichier/module.
Tier d'effort :
--quick : 1 reviewer aveugle (Sonnet) sur un context-pack léger + gate outils, pas de panel. Passe PR rapide.
- confirmation (auto-détecté, jamais demandé) : la branche a déjà un ledger ET son diff n'a pas bougé depuis le dernier tour (même sommet, ou seul un merge de la base sans conflit de contenu) → gate outils rejouée sur le sommet courant + ledger relu + notes de MR, zéro reviewer. C'est la réponse à « je peux merger ? » ; un aveugle de plus n'y trouve rien et coûte autant qu'un delta neuf. Dès que le diff porte du code nouveau, retour au tier demandé.
- (défaut) standard : revue décomposée par dimension (∥), gate de vérification, dédup, panel seulement sur critical/incertain.
--deep : tout — toutes dimensions + sécu threat-model + refute-panel sur tous les majors + red-check mutation sur les tests + execution-grounding live. Pré-release / scope sensible.
Mode boucle — --loop, ou toute formulation de l'utilisateur qui demande une boucle (« en boucle », « jusqu'au vert », « recommence jusqu'à ce qu'il n'y ait plus rien ») : ce n'est pas une intensité, c'est un contrat de terminaison. Voir Étape 6.5. En une phrase : on ne s'arrête pas sur un tour propre, on s'arrête sur un tour qui n'a rien changé.
Plafond de fan-out : 5 agents par tour, mesuré et non estimé. Sur 66 runs du journal, le rendement s'effondre au-delà :
| agents/tour |
runs |
confirmés/run |
tokens/run |
tokens par finding |
| 1 |
22 |
1,5 |
85 k |
55 k |
| 2-3 |
17 |
4,1 |
278 k |
68 k |
| 4-5 |
15 |
9,3 |
553 k |
59 k |
| 6-8 |
5 |
12,6 |
919 k |
73 k |
| 9+ |
7 |
12,7 |
1 384 k |
109 k |
Passer de 6-8 à 9+ coûte +50 % de tokens pour +0,1 finding par tour. La zone 4-5 est le meilleur rapport. Donc : au plus 5 agents par tour, refuteurs compris ; s'il en faut davantage, c'est un tour de plus, pas un tour plus large, et un tour de delta trouve à 55 k le finding contre 109 k pour un dixième agent. Dimensionner sur le volume du diff et non sur l'enjeu ressenti : environ un reviewer par 150 LOC non générées, plancher 2, plafond 5.
Autres : --ticket <id> (force le rattachement spec), --security (force la passe sécu profonde même en quick/standard), --fix (applique les fixes high-confidence après confirmation), --comment (poste le rapport/inline sur la PR après confirmation), --no-tools (si lint/tests indisponibles).
Dimensionnement modèles : orchestrateur = le modèle de la session (le plus capable disponible — Opus, Fable… ; synthèse, arbitrage). Reviewers dimension = tier standard (Sonnet ; correctness/sécu escaladent au tier max si --deep ou scope sensible). Refuteurs panel = tier standard (modèle/famille ≠ du reviewer initial via MCP si disponible — l'outillage natif est mono-famille Claude, la diversité réelle vient surtout du prompt reformulé + ordre inversé qui cassent le biais de position). Tâches mécaniques (récup ticket, extraction conventions) = tier rapide (Haiku). Les noms = mapping courant des tiers rapide/standard/max — sur une génération plus récente, lire par tier, pas par nom (paramètre model du tool Agent).
Étape 0 — Scope + tier
Déterminer la cible et le tier. Cible vide (working tree propre sans --base/--pr/path, ou diff vide) → le dire en une ligne et stop — pas de revue à vide. Branche déjà revue et inchangée (ledger présent, sommet identique au dernier tour à un merge de base près) → tier confirmation : gate + ledger, pas de reviewer, le dire dans le rapport. Compter la taille du diff : > 400 LOC modifiées → avertir (au-delà, le taux de détection chute fortement — SmartBear/Cisco ; 87 % détection ≤100 LOC vs 28 % >1000 LOC, Propel) et découper en passes ≤ 300-400 LOC (par fichier/feature), agréger+dédupliquer ensuite. Logger [scope] <cible>, <N> fichiers, <M> LOC, tier=<...>.
Étape 1 — Context assembly (NE PAS sauter — c'est le différenciateur)
Construire un context pack (placé en tête de chaque prompt reviewer = zone stable, cache-friendly) :
- Le changeset : hunks modifiés ; pour un fichier neuf (untracked), son contenu entier. Jamais le repo entier.
- Ticket / spec : auto-détecter l'id (nom de branche
feature/123-…, --ticket, PR liée) → gh issue view / glab issue view → titre + corps + critères d'acceptation. C'est l'oracle de la dimension spec-alignment. Si introuvable, le dire et reviewer sans (en le signalant).
- Conventions projet (court, < 50 lignes) :
CLAUDE.md racine + des dossiers touchés, .cursor/rules, CONTRIBUTING.md, configs lint. Extraire un digest des règles **réellement applicables** (déléguer la lecture à un agent Haiku — ne pas charger > 200 lignes dans le contexte mère).
- Contexte cross-fichiers (ce qui fait passer 44 %→82 %) : pour chaque symbole exporté modifié, trouver call sites / callers / implémentations (grep, ou LSP/gopls si dispo :
findReferences, goToImplementation). Détecter les breaking changes hors diff (un appelant que le changement casse). Chaque fait cross-file du pack porte son receipt — la ligne d'import ou de déclaration, pas un grep de méthode : x.Client.Foo( ne dit pas quel type est derrière. Un pack qui affirme « B réutilise le client de A » sans l'avoir vu ancre tous les reviewers sur une fausse couverture, et il faut qu'un reviewer contredise son brief pour trouver le jumeau non corrigé. Sémantique nouvelle sur un champ générique : dès que le diff donne un sens nouveau à la valeur d'un champ existant (« si correlation_id est un uuid, c'est un lien vers X »), le pack liste tous les producteurs de ce champ (grep "Field:"), avec leur type de valeur — c'est ce que les tests du diff, écrits avec les données du diff, ne peuvent pas voir.
- Historique git :
git blame/git log -p ciblé sur les lignes/fichiers touchés → pourquoi ce code existe, changements récents liés, anti-régression.
- Stack / architecture / versions : langage, framework, archi dominante (CQRS/ES, hexagonal, event-driven, microservices, monolithe modulaire…), deps majeures + versions (les patterns sécu/perf et les idiomes sont version- ET archi-dépendants). En cas d'archi/dep peu familière, une recherche web ciblée est permise (cf. principe « recherche externe »).
- Learnings repo (mémoire de feedback) : lire
~/.claude/projects/<encoded-cwd>/memory/senior_review_learnings.md s'il existe → FP déjà confirmés à ne pas répéter + conventions d'équipe découvertes. Logger [context] N learnings repo chargés.
- Lessons cross-projet (mémoire du skill) :
~/.claude/skill-memory/senior-review-lessons.md, hors dépôt, écrit à l'Étape 7 des runs passés ; absent au premier run, continuer sans. Ce sont les leçons sur comment reviewer, et elle ne contient que du confirmé : une leçon n'y entre qu'après avoir été recroisée sur un second run (cf. Étape 7). Ne jamais charger senior-review-lessons-candidates.md, qui porte les leçons vues une seule fois : une observation unique n'est pas une règle, et la charger revient à payer du bruit à chaque revue. Chaque leçon porte une étiquette de dimension en tête de ligne : [spec] [correctness] [security] [design] [tests] [perf] [harness]. Les injecter dans le context pack en routant chaque leçon vers la dimension concernée ; [harness] (hygiène d'orchestration, mutation, worktree) va à l'orchestrateur et à l'Étape 4. En tier --quick, ne charger que [harness] et [correctness] (grep -E '^- \[(harness|correctness)\]' lessons.md), les autres dimensions n'y sont pas reviewées. Logger [context] M lessons confirmées chargées.
Misses cross-projet : ~/.claude/skill-memory/senior-review-misses.md — hors dépôt, même régime d'absence. Ce sont les échecs de la revue : [bruit] remonté à tort, [manqué] trouvé après coup, [coût] tours de trop. Les injecter uniquement dans « ce qu'on NE flague PAS » (Étape 3) et jamais dans le brief de recherche d'un reviewer — un miss est un filtre, pas une piste. Logger [context] K misses chargés.
Reconnaissance d'architecture → mode spécialiste. À partir de la stack + archi détectées, nommer la combinaison (ex. « Shopify + CQRS/ES en Go »). Quand cette combinaison porte des invariants et pièges propres qui changent matériellement la revue :
- Proposer/confirmer le mode : si l'utilisateur n'a pas déjà donné l'angle, lui demander via
AskUserQuestion (« je revois en expert senior ? ») — en non-interactif, assumer le mode détecté en le signalant.
- Persona experte par reviewer : chaque agent de dimension devient un senior 10+ ans de cette stack précise (pas un généraliste) et reçoit la liste d'invariants/pièges connus de l'archi à vérifier en priorité. Exemples : CQRS/ES → idempotence des commandes, immutabilité/rejouabilité des events, cohérence projection↔agrégat, sagas/effets de bord rétroactifs ; Shopify → pagination/éviction, normalisation E.164, scopes/permissions, throttling, champs version-dépendants de l'Admin API ; front → hydratation, a11y, états de chargement.
- Ledger d'arbitrages (revues précédentes de CETTE branche) : lire
~/.claude/projects/<encoded-cwd>/memory/arbitrages-<branche|PR>.md s'il existe. Il ne porte que trois catégories — tranché (arbitrage acté par un humain + le pourquoi), prouvé (receipt déjà payé + la commande qui l'a payé), hors périmètre (réel mais routé ailleurs + le ticket). Il ne porte jamais les findings, sévérités ou verdicts des tours précédents : les relire ancrerait le jugement et tuerait l'aveuglement — c'est le seul garde-fou qui compte ici. L'orchestrateur lit les trois catégories ; les reviewers ne reçoivent que tranché aplati en « ne flague pas ça » et prouvé en « ne le refais pas », jamais le récit. Une entrée tranché reste réouvrable au prix d'un receipt exécuté : gratuit de passer, coûteux mais possible de rouvrir — c'est cette asymétrie qui empêche le ledger de figer une erreur. Logger [context] ledger: N tranchés, M prouvés (tour K).
Logger [context] ticket=<#id|absent>, N conventions, M call-sites tracés, stack=<...>, mode spécialiste=<stack | généraliste> (confirmé|détecté).
Étape 2 — Gate objectif (signal externe avant tout jugement LLM)
Lancer les outils dispos sur le périmètre (paralléliser) — ils sont l'oracle externe et désamorcent les FP :
- lint (golangci-lint/eslint/ruff…), typecheck, tests du périmètre, SAST (gosec/semgrep/bandit).
Passer leurs sorties résumées (échecs, comptes, lignes clés — pas le log brut complet) au context pack : les reviewers ne re-flaguent pas ce qu'un outil attrape (anti-bruit) et s'appuient dessus comme findings ancrés. Si un outil manque →
--no-tools/skip gracieux, le noter (la robustesse de la revue baisse, le dire).
Cible non-code (document, ticket, spec) : le gate ne disparaît pas, il change de nature. Sans lint ni tests, la tentation est de passer droit au jugement du modèle — c'est exactement là qu'on perd l'ancrage externe qui fait la valeur de cette étape. L'oracle mécanique équivalent : chaque référence citée résout (ticket, MR, fichier, ancre), chaque renvoi croisé pointe où il prétend, la numérotation d'une liste est contiguë, chaque compte annoncé se recalcule depuis sa source, et tout tableau concorde avec les objets qu'il indexe. Une passe de commandes, avant tout reviewer : elle attrape les liens morts, les renvois qui sur-promettent et les tables désynchronisées pour un coût dérisoire, et elle achète aux reviewers le droit de ne parler que du fond. Son résultat entre dans le context pack comme n'importe quel gate.
Attribution baseline (échec préexistant ≠ introduit par le diff) : un outil qui échoue n'incrimine le diff que si l'échec n'existe pas déjà sur la base. En cas d'échec (tests/lint/typecheck), vérifier l'attribution : pour --base/--pr, rejouer l'outil dans un worktree temporaire jetable sur le ref de base (git worktree add puis cleanup) ; pour le working tree, comparer vs HEAD quand c'est faisable à coût raisonnable, sinon marquer « attribution non vérifiée ». Un échec préexistant est exclu du verdict (scope = le diff) mais signalé en une ligne ; un échec introduit est un finding ancré. L'attribution entre dans le context pack — flaguer le diff sur un échec préexistant est exactement le faux positif qu'on combat.
Routage des dimensions (mixture-of-prompts, iCodeReviewer) : n'activer que les lentilles pertinentes au diff (pas de passe « injection SQL » sans accès DB, pas de passe a11y sans front). Logger [gate] lint/typecheck/tests/SAST: <résumés> + [route] dimensions actives: <liste>.
Étape 3 — Revue décomposée en aveugle (agents ∥, reviewer ≠ auteur)
Un agent distinct par dimension, contexte vierge, recevant le context pack (stable) + sa rubrique (volatile). Décomposer plutôt qu'un « God reviewer » (chaque agent a un focus net, les FP d'une dimension ne contaminent pas les autres — Qodo, Intercom, Cursor). Dimensions standard (activer selon routage) :
- spec-alignment : le diff couvre-t-il TOUT le ticket (chemins, critères d'acceptation) ? gaps non implémentés ? scope creep (modifs hors ticket) ?
« Conforme à l'existant » n'est pas un verdict quand le ticket se plaint de l'existant. Un ticket qui dit « aujourd'hui c'est trois étapes », « le vendeur repart de zéro », « on perd la donnée » énonce une mesure à améliorer. Le flux neuf se juge alors contre CETTE plainte (nombre d'étapes, de clics, culs-de-sac, ressaisies), jamais contre le flux voisin. Absoudre un parcours parce que son voisin fait pareil est le mode d'échec le plus coûteux de cette dimension : il passe toutes les autres coupes, et c'est l'utilisateur final qui le trouve en trente secondes. Recompter explicitement les étapes du parcours livré et les comparer au chiffre du ticket.
- correctness / bugs : logique, cas limites, concurrence/races, nil/maps, off-by-one, fuites de ressources,
defer en boucle, error shadowing, aliasing de slices, gestion d'erreurs. Cohérence inter-couches (lentille obligatoire dès que le diff touche une requête agrégée OU un mapping post-requête) : quand une couche COMPTE/agrège (SQL COUNT, total de pagination, GROUP BY) et qu'une autre FILTRE/projette ensuite (mapping applicatif, continue, dédup), vérifier que les deux opèrent sur le MÊME ensemble — un total calculé en amont d'un filtre aval est gonflé (pages courtes, « suivant » actif à tort). Vérifier la granularité de jointure (un WHERE/NOT EXISTS par-LIGNE jointe ≠ intention par-ENTITÉ : cas multi-items même clé) et la symétrie de scope entre filtre SQL et filtre applicatif (même clé tenant/slug ?). Tracer le count ET les rows jusqu'à l'UI. (Language-aware.) Un identifiant qui parse n'est pas un lien : quand une valeur existante reçoit un sens nouveau (« ça ressemble à un uuid, donc c'est un X »), vérifier contre la liste des producteurs du champ (Étape 1.4) que rien d'autre n'y écrit une valeur de même forme — sinon la nouvelle règle casse tout ce qui l'écrivait déjà.
- security (mindset attaquant) : entrées contrôlées par l'attaquant → sinks (injection, XSS, SSRF, path traversal), authz/authn, secrets/PII en logs, crypto, trust boundaries, fail-open. Modèle de menace, pas check-list mécanique (gosec couvre le mécanique).
- design / maintainability : abstraction au bon niveau, sur-ingénierie/YAGNI (test de suppression : si retirer le module fait disparaître de la complexité sans rien casser, c'était un pass-through inutile ; couture réelle = 2 implémentations, une seule = couture hypothétique → ne pas abstraire), boussole ETC (« ce choix rend-il le système plus facile ou plus dur à changer ? »), lisibilité (« compréhensible en 5 s »), nommage (test des 3 questions : le nom dit-il pourquoi/quoi/comment ? sinon renommer plutôt que commenter), commentaires (pourquoi non-évident, pas paraphrase ; un commentaire long et pénible à écrire signale une abstraction ratée), complexité poussée aux appelants (un check que chaque appelant doit répéter devrait être absorbé par le module — borner, défaut, null object), dérive de nom après consolidation (un diff qui fait gagner un nouvel appelant à une fonction existante via délégation/fusion — ex.
xResend appelé maintenant aussi par l'envoi initial, pas seulement le renvoi — invalide-t-il la promesse de son nom ? un nom fidèle à un seul appelant devient trompeur une fois partagé), adhérence conventions projet.
- tests : couverture du comportement modifié, cas d'erreur (pas que le happy path), assertions utiles, et — candidat red-check — les tests échouent-ils vraiment si le code casse ?
- texte d'interface (cond., dès que le diff ajoute ou change un libellé vu par un utilisateur) : on ne juge pas le goût, on juge trois choses vérifiables. Le temps : une confirmation annonce ce qui va se produire, un compte rendu ce qui s'est produit ; « X est créé » sur un bouton pas encore cliqué est faux. Le référent : un terme métier employé sans ce qui l'ancre (« les brouillons », « le pack », « la synchro ») est une ambiguïté réelle, pas une préférence. La promesse : ce que le texte annonce doit correspondre à ce que le code fait, champ par champ.
- perf (cond.) : N+1, allocations, requêtes, complexité ; pas de micro-opt prématurée. ux/a11y/i18n (cond. front).
Chaque agent raisonne avant de noter et rend, par finding : { severity: blocking|important|nit|suggestion, file, line, confidence: high|med|low, reasoning, finding, fix_concret, verification_plan }. On lui donne explicitement la section « ce qu'on NE flague PAS » (voir plus bas). Prompt « senior 10+ ans — en mode spécialiste, expert de la stack détectée (Étape 1) : adopte cette persona et vérifie en priorité les invariants/pièges de l'archi —, terse, evidence-based ; ne récompense pas la longueur ; en cas d'hésitation, baisse la confiance ; en cas de doute version/API, une recherche web ciblée est permise (cite la source, re-vérifie contre le repo) ».
Logger [review:<dim>] N findings (x bloquants, y importants).
Exécution : un appel Agent par dimension, lancés en parallèle dans un même message. Si le tool Workflow est disponible, le fan-out PEUT passer par un script Workflow (un agent() par dimension avec schema JSON imposé sur le format finding) — sorties validées structurellement, retries de parsing éliminés. L'interactif (AskUserQuestion) reste en conversation principale, jamais dans un workflow.
Étape 4 — Gate de vérification ("receipts")
Aucun finding critical/major n'est remonté sans preuve. Pour chacun, l'orchestrateur exécute lui-même le verification_plan (grep/exec/Read direct) — pas d'agent de vérification dédié, sauf besoin d'isolation (ex. mutation destructive sur un fichier suivi) :
- grep/ast-grep : confirmer que le pattern existe vraiment et sur une ligne modifiée.
- exécution sandbox : reproduire (snippet, requête, test qui échoue sur le code actuel — fail-to-pass, TDD-Bench). Pour un bug allégué → écrire le test rouge ; s'il ne rougit pas, le finding est suspect → drop ou rétrograder.
- sécurité : confirmer que la source est réellement attaquant-contrôlée ET atteint le sink (traçage flux). Sinon → drop.
- « ça va tout casser » (un finding annonce qu'un changement de contrat fait échouer N flux) → grille appelant par appelant avant tout panel : chaque appelant est classé (a) déjà en erreur, (b) succès silencieusement faux, (c) succès correct — seul (c) est une régression ; sans (c), le finding reste un fait mais descend en 🟡 « rend explicite un défaut préexistant », et c'est le refuteur qui reçoit la grille remplie, pas un récit.
- « tests faibles » → red-check mutation : muter la ligne ciblée (inverser une condition / constante, type-préservant), relancer la suite. Si rien ne rougit → le test est vacant → finding réel. Si le test ciblé rougit (et lui seul) → les tests protègent → finding FAUX, drop.
Passer par
scripts/mutate.py, pas par des commandes improvisées. Il prend un spec JSON (name, file, old, new, cmd, expect, must_match optionnel) et couvre trois pièges qui coûtent chacun un tour de boucle : il copie tous les fichiers cibles hors du dépôt AVANT la première édition et restaure dans un finally vérifié par empreinte, donc un plantage ne laisse jamais un fichier muté ; il refuse une mutation dont la commande de test n'exécute aucun test (un filtre -run/-k qui ne matche rien sort en succès et se lit comme un faux SURVIVED) ; il imprime chaque résultat au fil de l'eau, donc une interruption ne perd pas les mesures déjà payées. Il n'utilise jamais git : un checkout/restore effacerait le travail non commité, et écrire dans l'index ferait embarquer la version mutée par un commit ultérieur.
Un écart entre prédiction et résultat se re-mesure avant d'être cru : ANCHOR, NO-TESTS et NO-MATCH disent que le harnais a raté, pas que le test est faible.
Hygiène du re-run live (V1.0.1) : si vérifier un finding exige de muter temporairement un fichier suivi (ex: pointer un config.js/.env vers un serveur de test), restaurer via git checkout -- <file> et ne laisser AUCUN backup parasite (.bak/.orig/config.js.orig_backup…). git suit déjà le fichier — pas besoin de copie manuelle ; un backup oublié pollue le working tree de l'user et le diff. Vérifier git status propre en fin de revue. Piège du workflow hybride (V1.1.1) : si le fichier muté porte AUSSI des éditions non commitées (cas fix-puis-review sur le même fichier), git checkout -- <file> les efface TOUTES, pas seulement la mutation — restaurer chirurgicalement (Edit inverse sur les seules lignes mutées, ou git stash avant de muter), jamais par checkout global.
Recherche web en appui (pas en substitut) : pour un doute version/API/framework, consulter la doc officielle peut confirmer ou réfuter l'hypothèse (ex. « cette API plafonne-t-elle sans first: ? »). Mais le receipt reste local (grep/exec/test contre la version réelle du repo) ; la source web est citée (lien + date) et ne suffit jamais seule à remonter un finding bloquant.
Findings non prouvés → drop silencieux (ou rétrogradés en low confidence — à vérifier). Exception au drop — désaccord inter-couches dont le receipt exige une donnée multi-entités : un finding de cohérence count↔display ou de granularité de jointure dont la preuve demande de FABRIQUER un jeu de données (multi-items même clé, count ≠ rows visibles) ne se drope PAS faute de receipt rapide. Si la lecture du flux SQL→mapping rend le désaccord plausible, le remonter en 🟡 important — à vérifier manuellement avec le scénario de données exact à construire. Un receipt cher n'est pas l'absence de bug. Logger [verify] k/n findings confirmés, j droppés (faux positifs).
Étape 5 — Synthèse calibrée (dédup + panel adversarial ciblé)
- Dédup cross-dimensions (même
file:line / même cause).
- Refute-panel — seulement sur le douteux/critique (proportionné : pas sur les nits) : pour chaque finding bloquant ou à confiance non-haute, un skeptique indépendant (modèle/famille ≠ si possible ; sinon prompt reformulé + ordre inversé pour casser le biais de position) est mandaté pour RÉFUTER. On garde le finding si la majorité ne le réfute pas (panel de juges variés > juge unique — PoLL, Verga 2024 ; le vote majoritaire tue les flukes d'une seule passe — Cursor BugBot). Escalade graduée : 1 refuteur → si désaccord, panel de 3 (médiane/majorité) → si toujours partagé sur un bloquant, présenter à l'user (ne jamais trancher seul un bloquant incertain).
- Calibration : confiance finale par finding ; sous le seuil →
low confidence. Tiers de sévérité : 🔴 bloquant (vrai merge-blocker) / 🟡 important / 🔵 nit·suggestion / 👍 praise (1-2 max). Mapping déterministe quand possible (MUST→bloquant, SHOULD→important, MAY→suggestion). Cap nits ≤ 5 inline, le reste en compte résumé.
Logger [panel] x findings réfutés, y confirmés · [calib] 🔴N 🟡N 🔵N.
Étape 6 — Verdict + rapport (discipline signal/bruit)
Rapport structuré, concis, commente le code jamais l'auteur, chaque finding avec file:line + raison + fix concret + confiance (+ preuve de vérif si critique) :
# Revue — <cible> (<N fichiers, M LOC>)
## Spec-coverage
<Le diff fait-il ce que le ticket #<id> demande ? — Oui / Partiel (gaps listés) / Hors-sujet>
<Scope creep éventuel : modifs hors ticket>
## 🔴 Bloquants (N) <vrais merge-blockers — sinon "aucun">
1. <constat> — `file.go:42` (confiance: haute) — preuve: <vérif> — fix: <concret>
## 🟡 Importants (N)
…
## 🔵 Mineurs / suggestions (N inline, +K en plus non listés)
…
## 👍 Bien vu (≤2)
…
## Verdict
<approve | request-changes | needs-discussion> — <1 phrase>
Métriques: lint <…> · typecheck <…> · tests <…/…> · SAST <…> · coût: <N agents, ~M tokens>
Si rien de matériel : le dire (« Aucun bloquant. N suggestions mineures. Le changement améliore la base. ») — le silence est une feature (GitHub Copilot : 29 % des revues silencieuses, assumé). Le seuil de greenlight (Google) : le changement améliore la base, pas « est parfait ».
Coût mesuré, pas espéré : sommer les subagent_tokens retournés par chaque appel Agent du run (reviewers + refuteurs) pour la ligne coût: — mesure objective, base de comparaison avant/après toute optimisation du skill (même logique que subagent_tokens_total dans feature-loop).
Verdict approve ET un skill branch-wrap-up disponible ET la cible est du travail local non clôturé (working tree/branche, pas une PR déjà ouverte) → suggérer en une ligne branch-wrap-up --no-review pour la clôture (commit/push/MR-PR), la review étant faite.
Execution-grounding live (option, surtout --deep ou findings runtime) : si l'app est runnable et qu'un bug bloquant est suspecté, exercer réellement le chemin pour confirmer qu'il reproduit (relancer un serveur stale, aligner le schéma DB runtime — cf. discipline smoke-live). Sinon, review-only.
--comment : après confirmation, poster via gh pr comment/gh pr review (GitHub) ou glab mr note (GitLab), inline avec liens file#Lx-Ly (SHA complet pour GitHub). --fix : appliquer SEULEMENT les fixes high-confidence, dans le working tree, après confirmation — jamais de commit/push auto.
Étape 6.5 — Boucle (mode --loop uniquement)
Le mode boucle a un seul critère d'arrêt, et il porte sur le delta, pas sur le rapport :
On boucle tant qu'un tour a modifié du code. On s'arrête au premier tour qui ne remonte aucun bloquant ET ne produit aucune modification.
Ce qui est interdit de conclure : « ce tour n'a plus de bloquant, donc c'est fini ». Un tour qui corrige crée du code que personne n'a jugé, et son auteur est l'orchestrateur — le juge le plus mal placé pour l'évaluer (principe reviewer ≠ auteur). Tant qu'une ligne a bougé, il reste un delta non relu : c'est ce delta qui décide s'il faut un tour de plus, pas la sévérité du tour écoulé.
Le tour N+1 en pratique :
- Cible = le delta, pas la branche entière : le diff des correctifs du tour N (contre un instantané pris avant eux — pas contre
HEAD si l'arbre porte du travail non commité).
- Reviewers frais et aveugles, jamais les mêmes agents relancés : une continuation coûte autant qu'une passe neuve et arrive avec l'ancrage du tour précédent. Deux dimensions suffisent en général (correctness+sécurité du delta ; tests du delta) — proportionner au volume du delta, pas à celui de la branche.
- Le brief inverse la question : non pas « ce correctif corrige-t-il ? » (déjà prouvé par mutation) mais « que casse-t-il, qu'oublie-t-il, que surcorrige-t-il ? ». Lister explicitement les correctifs et ce dont chacun est soupçonné.
- Chaque correctif se prouve par mutation inverse : annuler le correctif doit faire rougir un test. Un correctif sans test qui meurt à son annulation n'est pas fini — c'est le seul signal externe qui distingue « corrigé » de « corrigé en apparence ».
- Les décisions tranchées par l'utilisateur et les receipts déjà payés passent au ledger (Étape 7) et ne se re-litigent pas.
Terminaison et honnêteté du décompte : le rapport final d'une boucle dit le nombre de tours, ce que chaque tour a trouvé, et ce que le dernier tour n'a pas changé. Une boucle annoncée close avec un delta non relu est un rapport faux, quelle que soit la couleur du gate. À partir du tour 3, ajouter une ligne de coût cumulé (agents, tokens) au rapport — la boucle reste due, l'utilisateur reste informé de ce qu'elle coûte.
Garde-fous : un bloquant qui survit à deux tours sans converger, ou deux tours qui se renvoient le même arbitrage, remonte à l'utilisateur au lieu d'un troisième correctif (déjà la règle pour un bloquant incertain). Une modification qui n'est que du commentaire ou du renommage sans effet sémantique ne relance pas un tour complet : elle se vérifie par gate outils + relecture directe, et le rapport le dit.
Étape 7 — Learnings (ledger de branche + mémoire par repo + lessons cross-projet)
- Faux positif confirmé par l'user (« ça c'est voulu ») ou par la vérif → l'écrire dans
~/.claude/projects/<encoded-cwd>/memory/senior_review_learnings.md (codebase_fact vs team_preference) pour ne pas le répéter. Ne pas polluer avec des learnings trop génériques/vieux.
- **Leçon sur *comment reviewe
…(truncated)
1---2name: senior-review3description: Use when the user wants a thorough senior-level fresh-eyes review of changes (working tree, staged, branch, or PR) before merge: bug hunting, security threat-model, design, tests, spec-alignment vs the linked ticket. Use it whenever the user says things like "review my changes", "relis mon code", "revue de code", "can I merge this", "is this ready to ship", "check this MR/PR", even if they never say "senior-review". Research-grounded pipeline: context-first assembly (cross-file call sites, conventions, git history, ticket), objective tool gate, parallel blind reviewers (reviewer is never the author), verification gate (every critical finding needs a receipt: grep, execution, or a red test), adversarial refute-panel on critical/uncertain findings only. Optimizes signal/noise over recall: caps nits, explicit what-NOT-to-flag, silence when nothing material. Tiers: --quick / standard / --deep. Flags: --pr N, --base ref, --staged, --ticket N, --security, --fix, --comment, --no-tools. NOT for building features (use4---56# Senior Review78**skill_version : 1.16.0** (historique : `CHANGELOG.md`). Revue de code de niveau senior, conçue à partir de l'état de l'art académique (LLM-as-judge, vérification, mutation) et des meilleurs outils de revue IA (CodeRabbit, Greptile, Cursor BugBot, GitHub Copilot agentic, Qodo, Snyk).910**Fichiers du skill (progressive disclosure)** : `scripts/mutate.py` (harnais de mutation, cf. Étape 4), `lessons.md` et `misses.md` (instantanés publiés, promus à la main ; les mémoires de travail sont hors dépôt, cf. Étape 1.8), `reference/references.md` (sources détaillées, à la demande), `CHANGELOG.md` (historique).1112**Mémoires de travail, hors dépôt**, dans `~/.claude/skill-memory/` :13- `senior-review-lessons.md` : leçons **confirmées** (vues sur au moins 2 runs). Chargée à l'Étape 1.8. Plafond 40.14- `senior-review-lessons-candidates.md` : leçons vues **une seule fois**. **Jamais chargée.** Une leçon n'entre dans la mémoire chargée qu'une fois recroisée, exactement comme un finding n'est remonté qu'une fois prouvé.15- `senior-review-misses.md` : ce qui a raté. Chargée à l'Étape 1.8. Plafond 40.16- `senior-review-runs.jsonl` : une ligne par revue, **jamais chargée**. Elle sert à analyser les runs entre eux, pas à en informer un.1718## Posture (ce qui distingue une revue excellente d'une revue bruyante)1920La recherche converge sur **un seul vrai critère de qualité : le rapport signal/bruit, pas le recall.** Un reviewer qui crie au loup est désactivé — c'est l'un des premiers motifs d'abandon des outils de revue IA. Donc : peu de faux positifs, findings prouvés, silence assumé quand rien de matériel, et chaque finding actionnable (file:line + raison + fix). Le second levier le plus fort est le **contexte** : le diff seul plafonne le catch-rate (~44 %), le contexte cross-fichiers + intention le double (~82 %, bench Greptile). D'où une revue **context-first** et **liée au ticket** (une revue doit vérifier que le code fait *ce qui était demandé*, pas seulement qu'il est correct — la dimension la plus souvent oubliée et la plus chère en prod).2122## Principes non négociables2324<!-- LOCKED: modif humaine directe uniquement (jamais via une boucle d'auto-amélioration). -->2526- **Reviewer ≠ auteur (blind review).** Le *self-preference bias* est prouvé et causal : un modèle qui juge sa propre sortie se surnote (Panickssery 2024 ; Wataoka 2024). Les agents reviewers tournent en **contexte vierge**, ne voient PAS le prompt d'implémentation, et — si l'auteur du code est connu comme étant un modèle donné — sont d'un **modèle/famille différent**. Le nom du développeur n'entre jamais dans le prompt.27- **Pas de finding sans preuve vérifiée.** Tout finding cite `file:line` ET passe une **vérification** (grep/ast-grep, exécution en sandbox, test qui échoue, traçage du flux) avant d'être remonté. Un finding non vérifiable est **droppé silencieusement**. Les LLMs sur-flaggent (overcorrection systématique, arXiv:2603.00539) ; l'execution-grounding rejette ~60 % des faux positifs (arXiv:2604.10800).28- **Signal > recall.** Mieux vaut 3 vrais bugs que 3 vrais + 11 faux. Cap des nits (≤ 5 inline, le reste compté en résumé), **silence explicite** quand rien de bloquant, et une section **« ce qu'on NE flague PAS »** aussi importante que « ce qu'on cherche » (Cloudflare : « telling an LLM what not to do is where the value is »).29- **CoT avant verdict, confiance par finding.** Raisonner (constat → pourquoi → est-ce causé par les lignes modifiées ?) AVANT de poser sévérité + note (G-Eval : CoT avant la note). Confiance basse → marquée « à vérifier manuellement », jamais bloquante.30- **Signal externe, pas auto-critique en boucle.** L'auto-correction LLM sans oracle dégrade (Huang ICLR 2024). La revue s'ancre sur des signaux EXTERNES (lint, typecheck, tests, SAST, exécution), jamais sur la seule relecture du modèle. Pas de « revue de la revue » en boucle fermée.31- **Scope = le diff.** On ne note jamais du code legacy non modifié (même mauvais), ni ce qu'un linter/formatter/CI attrape déjà, ni les fichiers générés/vendored/lock.32- **Spec-alignment est une dimension de premier rang.** Première question : *le diff implémente-t-il ce que le ticket demande* (couverture complète + pas de scope creep) — pas seulement « est-ce correct ». Sans la description du problème, la revue LLM perd sensiblement en précision (arXiv:2505.20206).33- **La sécurité est une lentille DISTINCTE** (mindset attaquant, modèle de menace), pas fondue dans la revue de correction : objectifs opposés (la sécu optimise le rappel/paranoïa, la correction la précision). Toujours son propre agent.34- **Effort proportionné.** Pas de panel multi-agents sur un diff de 10 lignes. Paliers `--quick` / standard / `--deep`.35- **Discipline tokens/latence.** À qualité égale, le run le moins cher gagne : (a) le **context pack** (Étape 1) est écrit UNE fois, stable, réutilisé verbatim par tous les reviewers (cache-friendly Anthropic) — jamais reconstruit ni re-collé par dimension ; (b) les **receipts** de l'Étape 4 sont vérifiés par **l'orchestrateur lui-même** (grep/exec/Read direct), jamais délégués à un agent dédié — un aller-retour agent coûterait un tour complet pour reproduire ce qu'une commande fait en un appel ; (c) le **refute-panel** (Étape 5) ne tourne QUE sur le critique/incertain, jamais en aveugle sur l'ensemble des findings ; (d) les sorties d'outils (Étape 2 : lint/tests/SAST) entrent **résumées** dans le context pack (échecs + comptes, pas le log brut complet) ; (e) une **re-revue immédiate du même diff dans la même session** (ex. l'utilisateur redemande une revue après avoir appliqué les fixes suggérés) continue les reviewers déjà lancés via `SendMessage` plutôt que d'en relancer 5 aveugles frais sur l'intégralité — l'aveuglement protège le PREMIER jugement, pas la vérification d'un correctif ; repasser en aveugle frais dès que le diff contient du code substantiellement nouveau hors du delta déjà couvert (pattern evaluator-optimizer, transposé de feature-loop 8.11.0) ; au-delà de la session — après un `/clear`, ou une revue reprise des jours plus tard sur la même branche — c'est le **ledger d'arbitrages** (Étapes 1.9 et 7) qui porte la continuité, jamais un re-collage des findings précédents.36- **Review-only par défaut.** On propose des fixes ; on n'édite/poste rien sans `--fix`/`--comment` explicite, et on confirme avant toute action sortante (commentaire PR, push).37- **Mode spécialiste selon l'architecture détectée.** Un généraliste rate les invariants propres à une stack. La revue **reconnaît l'archi** (Étape 1) — ex. « Shopify + CQRS/ES en Go », « Next/tRPC », « Spring/DDD » — et bascule en **expert senior de cette stack** : on le **propose/confirme à l'utilisateur** quand l'angle n'est pas déjà donné, et chaque reviewer reçoit la persona experte + les invariants/pièges connus de l'archi à vérifier en priorité. Le mode spécialiste n'élargit pas le bruit : il **affine** ce qu'on cherche, pas le nombre de findings.38- **Recherche externe autorisée en cas de doute (avec discipline).** Quand un doute porte sur un comportement *version/API/framework-spécifique* (sémantique d'un flag, API tierce, CVE, idiome récent, plafond/pagination d'une API), un reviewer PEUT consulter le web (`WebSearch`/`WebFetch`) plutôt que deviner ou sur-flaguer. Discipline : source **primaire/officielle** d'abord, **citer la source + sa date**, et **re-vérifier contre le code et la version réelle du repo** — une réponse web *informe* mais n'est jamais le receipt (le receipt reste grep/exec/test). En cas d'indispo réseau, le dire et baisser la confiance.3940## Pipeline4142```430. PARSE + DÉTECTION CIBLE (working tree défaut / staged / branche vs base / PR) + tier (quick|standard|deep)44 ↓451. CONTEXT ASSEMBLY (le différenciateur n°1)46 diff (incl. untracked!) + TICKET/spec + conventions projet (CLAUDE.md/rules/lint)47 + cross-file (call sites/callers/impls) + git blame/log + stack/versions + learnings repo48 ↓ → "context pack" stable (cache-friendly), réutilisé par tous les reviewers492. GATE OBJECTIF (signal externe AVANT jugement LLM)50 lint + typecheck + tests + SAST (gosec/semgrep/…) → nourrit les reviewers (ne pas re-flaguer)51 + routage dimensions pertinentes (pas de chasse SQLi sans DB — iCodeReviewer)52 ↓533. REVUE DÉCOMPOSÉE EN AVEUGLE (agents spécialisés ∥, reviewer ≠ auteur)54 spec-alignment · correctness/bugs · security(threat-model) · design/maintainability · tests · [perf/ux cond.]55 chacun : CoT → findings {file:line, sévérité, confiance, raison, fix, PLAN DE VÉRIFICATION}56 ↓574. GATE DE VÉRIFICATION ("receipts" — tue les faux positifs)58 chaque finding (surtout critical/major) PROUVÉ : grep/ast-grep, exécution sandbox, test qui rougit,59 traçage flux source→sink. Non prouvé → drop. "tests faibles" → red-check par mutation.60 ↓615. SYNTHÈSE CALIBRÉE (dédup cross-dimensions + panel adversarial sur le douteux/critique seulement)62 refute-panel (skeptique indépendant tente de réfuter ; majorité pour garder) — PoLL63 + calibration confiance + tiers sévérité (🔴 bloquant / 🟡 important / 🔵 nit·suggestion / 👍 praise)64 ↓656. VERDICT + RAPPORT (signal/bruit discipliné) → spec-coverage verdict + go/no-go ; silence si rien66 [option --comment → poste PR ; --fix → applique les fixes high-confidence, après confirmation]67 ↓687. LEARNINGS (mémoire par repo : FP confirmés, conventions découvertes) + lessons et misses cross-projet69```7071## Logs (préfixes, style factuel, pas d'emojis hors rapport final)72`[scope]` `[context]` `[gate]` `[route]` `[review:<dim>]` `[verify]` `[panel]` `[calib]` `[verdict]` `[report]` `[comment]` `[fix]` `[learn]`. 1 ligne par sous-étape clé, pas de silence > 3 min.7374## Parsing des arguments7576**Cible** (auto-détectée, override possible) :77- *(défaut)* **working tree** : modifs non commitées = `git status --porcelain` → tracked modifiés **+ untracked** (⚠️ `git diff` seul rate les fichiers neufs ; lire les untracked en entier).78- `--base <ref>` : revoir `git diff <ref>...HEAD` (revue de branche ; base = `origin/main`/`develop` si déduisible).79- `--staged` : `git diff --cached`.80- `--pr <N>` : récupérer la PR via `gh pr` (GitHub) ou `glab mr`/`glab issue` (GitLab) ; diff + description + commentaires.81- `<path>` : restreindre à un fichier/module.8283**Tier d'effort** :84- `--quick` : 1 reviewer aveugle (Sonnet) sur un context-pack léger + gate outils, pas de panel. Passe PR rapide.85- **confirmation** (auto-détecté, jamais demandé) : la branche a déjà un ledger ET son diff n'a pas bougé depuis le dernier tour (même sommet, ou seul un merge de la base sans conflit de contenu) → gate outils rejouée sur le sommet courant + ledger relu + notes de MR, **zéro reviewer**. C'est la réponse à « je peux merger ? » ; un aveugle de plus n'y trouve rien et coûte autant qu'un delta neuf. Dès que le diff porte du code nouveau, retour au tier demandé.86- *(défaut)* **standard** : revue décomposée par dimension (∥), gate de vérification, dédup, panel **seulement** sur critical/incertain.87- `--deep` : tout — toutes dimensions + sécu threat-model + refute-panel sur tous les majors + red-check mutation sur les tests + execution-grounding live. Pré-release / scope sensible.8889**Mode boucle** — `--loop`, ou toute formulation de l'utilisateur qui demande une boucle (« en boucle », « jusqu'au vert », « recommence jusqu'à ce qu'il n'y ait plus rien ») : ce n'est pas une intensité, c'est un **contrat de terminaison**. Voir Étape 6.5. En une phrase : on ne s'arrête pas sur un tour propre, on s'arrête sur un tour qui **n'a rien changé**.9091**Plafond de fan-out : 5 agents par tour, mesuré et non estimé.** Sur 66 runs du journal, le rendement s'effondre au-delà :9293| agents/tour | runs | confirmés/run | tokens/run | tokens par finding |94|---|---|---|---|---|95| 1 | 22 | 1,5 | 85 k | 55 k |96| 2-3 | 17 | 4,1 | 278 k | 68 k |97| **4-5** | 15 | **9,3** | 553 k | **59 k** |98| 6-8 | 5 | 12,6 | 919 k | 73 k |99| 9+ | 7 | 12,7 | 1 384 k | 109 k |100101Passer de 6-8 à 9+ coûte **+50 % de tokens pour +0,1 finding par tour**. La zone 4-5 est le meilleur rapport. Donc : **au plus 5 agents par tour**, refuteurs compris ; s'il en faut davantage, c'est un tour de plus, pas un tour plus large, et un tour de delta trouve à 55 k le finding contre 109 k pour un dixième agent. Dimensionner sur le **volume du diff** et non sur l'enjeu ressenti : environ un reviewer par 150 LOC non générées, plancher 2, plafond 5.102103**Autres** : `--ticket <id>` (force le rattachement spec), `--security` (force la passe sécu profonde même en quick/standard), `--fix` (applique les fixes high-confidence après confirmation), `--comment` (poste le rapport/inline sur la PR après confirmation), `--no-tools` (si lint/tests indisponibles).104105**Dimensionnement modèles** : orchestrateur = le modèle de la session (le plus capable disponible — Opus, Fable… ; synthèse, arbitrage). Reviewers dimension = tier standard (Sonnet ; correctness/sécu escaladent au tier max si `--deep` ou scope sensible). Refuteurs panel = tier standard (modèle/famille ≠ du reviewer initial via MCP si disponible — l'outillage natif est mono-famille Claude, la diversité réelle vient surtout du prompt reformulé + ordre inversé qui cassent le biais de position). Tâches mécaniques (récup ticket, extraction conventions) = tier rapide (Haiku). **Les noms = mapping courant des tiers rapide/standard/max** — sur une génération plus récente, lire par tier, pas par nom (paramètre `model` du tool Agent).106107## Étape 0 — Scope + tier108109Déterminer la cible et le tier. **Cible vide** (working tree propre sans `--base`/`--pr`/path, ou diff vide) → le dire en une ligne et stop — pas de revue à vide. **Branche déjà revue et inchangée** (ledger présent, sommet identique au dernier tour à un merge de base près) → tier **confirmation** : gate + ledger, pas de reviewer, le dire dans le rapport. Compter la taille du diff : **> 400 LOC modifiées → avertir** (au-delà, le taux de détection chute fortement — SmartBear/Cisco ; 87 % détection ≤100 LOC vs 28 % >1000 LOC, Propel) et **découper** en passes ≤ 300-400 LOC (par fichier/feature), agréger+dédupliquer ensuite. Logger `[scope] <cible>, <N> fichiers, <M> LOC, tier=<...>`.110111## Étape 1 — Context assembly (NE PAS sauter — c'est le différenciateur)112113Construire un **context pack** (placé en tête de chaque prompt reviewer = zone stable, cache-friendly) :1141151. **Le changeset** : hunks modifiés ; pour un fichier neuf (untracked), son contenu entier. Jamais le repo entier.1162. **Ticket / spec** : auto-détecter l'id (nom de branche `feature/123-…`, `--ticket`, PR liée) → `gh issue view` / `glab issue view` → titre + corps + **critères d'acceptation**. C'est l'oracle de la dimension spec-alignment. Si introuvable, le dire et reviewer sans (en le signalant).1173. **Conventions projet** (court, < 50 lignes) : `CLAUDE.md` racine + des dossiers touchés, `.cursor/rules`, `CONTRIBUTING.md`, configs lint. Extraire un digest des règles **réellement applicables** (déléguer la lecture à un agent Haiku — ne pas charger > 200 lignes dans le contexte mère).1184. **Contexte cross-fichiers** (ce qui fait passer 44 %→82 %) : pour chaque symbole exporté modifié, trouver **call sites / callers / implémentations** (grep, ou LSP/gopls si dispo : `findReferences`, `goToImplementation`). Détecter les *breaking changes hors diff* (un appelant que le changement casse). **Chaque fait cross-file du pack porte son receipt** — la ligne d'import ou de déclaration, pas un grep de méthode : `x.Client.Foo(` ne dit pas quel type est derrière. Un pack qui affirme « B réutilise le client de A » sans l'avoir vu ancre tous les reviewers sur une fausse couverture, et il faut qu'un reviewer contredise son brief pour trouver le jumeau non corrigé. **Sémantique nouvelle sur un champ générique** : dès que le diff donne un sens nouveau à la valeur d'un champ existant (« si `correlation_id` est un uuid, c'est un lien vers X »), le pack liste **tous les producteurs** de ce champ (`grep "Field:"`), avec leur type de valeur — c'est ce que les tests du diff, écrits avec les données du diff, ne peuvent pas voir.1195. **Historique git** : `git blame`/`git log -p` ciblé sur les lignes/fichiers touchés → *pourquoi* ce code existe, changements récents liés, anti-régression.1206. **Stack / architecture / versions** : langage, framework, **archi dominante** (CQRS/ES, hexagonal, event-driven, microservices, monolithe modulaire…), deps majeures + versions (les patterns sécu/perf **et les idiomes** sont version- ET archi-dépendants). En cas d'archi/dep peu familière, une recherche web ciblée est permise (cf. principe « recherche externe »).1217. **Learnings repo** (mémoire de feedback) : lire `~/.claude/projects/<encoded-cwd>/memory/senior_review_learnings.md` s'il existe → FP déjà confirmés à ne pas répéter + conventions d'équipe découvertes. Logger `[context] N learnings repo chargés`.1228. **Lessons cross-projet (mémoire du skill)** : `~/.claude/skill-memory/senior-review-lessons.md`, **hors dépôt**, écrit à l'Étape 7 des runs passés ; absent au premier run, continuer sans. Ce sont les leçons sur *comment reviewer*, et **elle ne contient que du confirmé** : une leçon n'y entre qu'après avoir été recroisée sur un second run (cf. Étape 7). Ne **jamais** charger `senior-review-lessons-candidates.md`, qui porte les leçons vues une seule fois : une observation unique n'est pas une règle, et la charger revient à payer du bruit à chaque revue. Chaque leçon porte une étiquette de dimension en tête de ligne : `[spec] [correctness] [security] [design] [tests] [perf] [harness]`. Les injecter dans le context pack en **routant chaque leçon vers la dimension concernée** ; `[harness]` (hygiène d'orchestration, mutation, worktree) va à l'orchestrateur et à l'Étape 4. **En tier `--quick`, ne charger que `[harness]` et `[correctness]`** (`grep -E '^- \[(harness|correctness)\]' lessons.md`), les autres dimensions n'y sont pas reviewées. Logger `[context] M lessons confirmées chargées`.123 **Misses cross-projet** : `~/.claude/skill-memory/senior-review-misses.md` — **hors dépôt**, même régime d'absence. Ce sont les **échecs** de la revue : `[bruit]` remonté à tort, `[manqué]` trouvé après coup, `[coût]` tours de trop. Les injecter **uniquement dans « ce qu'on NE flague PAS »** (Étape 3) et jamais dans le brief de recherche d'un reviewer — un miss est un filtre, pas une piste. Logger `[context] K misses chargés`.124125**Reconnaissance d'architecture → mode spécialiste.** À partir de la stack + archi détectées, **nommer la combinaison** (ex. « Shopify + CQRS/ES en Go »). Quand cette combinaison porte des invariants et pièges propres qui changent matériellement la revue :126- **Proposer/confirmer le mode** : si l'utilisateur n'a pas déjà donné l'angle, lui demander via `AskUserQuestion` (« je revois en expert senior <stack> ? ») — en non-interactif, assumer le mode détecté **en le signalant**.127- **Persona experte par reviewer** : chaque agent de dimension devient un **senior 10+ ans de cette stack précise** (pas un généraliste) et reçoit la **liste d'invariants/pièges connus** de l'archi à vérifier en priorité. Exemples : *CQRS/ES* → idempotence des commandes, immutabilité/rejouabilité des events, cohérence projection↔agrégat, sagas/effets de bord rétroactifs ; *Shopify* → pagination/éviction, normalisation E.164, scopes/permissions, throttling, champs version-dépendants de l'Admin API ; *front* → hydratation, a11y, états de chargement.1281299. **Ledger d'arbitrages (revues précédentes de CETTE branche)** : lire `~/.claude/projects/<encoded-cwd>/memory/arbitrages-<branche|PR>.md` s'il existe. Il ne porte que trois catégories — **`tranché`** (arbitrage acté par un humain + le pourquoi), **`prouvé`** (receipt déjà payé + la commande qui l'a payé), **`hors périmètre`** (réel mais routé ailleurs + le ticket). Il ne porte **jamais** les findings, sévérités ou verdicts des tours précédents : les relire ancrerait le jugement et tuerait l'aveuglement — c'est le seul garde-fou qui compte ici. **L'orchestrateur** lit les trois catégories ; **les reviewers** ne reçoivent que `tranché` aplati en « ne flague pas ça » et `prouvé` en « ne le refais pas », jamais le récit. Une entrée `tranché` reste **réouvrable au prix d'un receipt exécuté** : gratuit de passer, coûteux mais possible de rouvrir — c'est cette asymétrie qui empêche le ledger de figer une erreur. Logger `[context] ledger: N tranchés, M prouvés (tour K)`.130131Logger `[context] ticket=<#id|absent>, N conventions, M call-sites tracés, stack=<...>, mode spécialiste=<stack | généraliste> (confirmé|détecté)`.132133## Étape 2 — Gate objectif (signal externe avant tout jugement LLM)134135Lancer les outils dispos sur le périmètre (paralléliser) — ils sont l'oracle externe et **désamorcent les FP** :136- **lint** (golangci-lint/eslint/ruff…), **typecheck**, **tests** du périmètre, **SAST** (gosec/semgrep/bandit).137Passer leurs sorties **résumées** (échecs, comptes, lignes clés — pas le log brut complet) au context pack : les reviewers **ne re-flaguent pas** ce qu'un outil attrape (anti-bruit) et **s'appuient** dessus comme findings ancrés. Si un outil manque → `--no-tools`/skip gracieux, le noter (la robustesse de la revue baisse, le dire).138139**Cible non-code (document, ticket, spec) : le gate ne disparaît pas, il change de nature.** Sans lint ni tests, la tentation est de passer droit au jugement du modèle — c'est exactement là qu'on perd l'ancrage externe qui fait la valeur de cette étape. L'oracle mécanique équivalent : chaque référence citée **résout** (ticket, MR, fichier, ancre), chaque renvoi croisé pointe **où il prétend**, la numérotation d'une liste est **contiguë**, chaque compte annoncé se **recalcule depuis sa source**, et tout tableau concorde avec les objets qu'il indexe. Une passe de commandes, avant tout reviewer : elle attrape les liens morts, les renvois qui sur-promettent et les tables désynchronisées pour un coût dérisoire, et elle achète aux reviewers le droit de ne parler que du fond. Son résultat entre dans le context pack comme n'importe quel gate.140141**Attribution baseline (échec préexistant ≠ introduit par le diff)** : un outil qui échoue n'incrimine le diff que si l'échec n'existe pas déjà sur la base. En cas d'échec (tests/lint/typecheck), vérifier l'attribution : pour `--base`/`--pr`, rejouer l'outil dans un worktree temporaire jetable sur le ref de base (`git worktree add` puis cleanup) ; pour le working tree, comparer vs HEAD quand c'est faisable à coût raisonnable, sinon marquer « attribution non vérifiée ». Un échec **préexistant** est exclu du verdict (scope = le diff) mais signalé en une ligne ; un échec **introduit** est un finding ancré. L'attribution entre dans le context pack — flaguer le diff sur un échec préexistant est exactement le faux positif qu'on combat.142143**Routage des dimensions** (mixture-of-prompts, iCodeReviewer) : n'activer que les lentilles pertinentes au diff (pas de passe « injection SQL » sans accès DB, pas de passe a11y sans front). Logger `[gate] lint/typecheck/tests/SAST: <résumés>` + `[route] dimensions actives: <liste>`.144145## Étape 3 — Revue décomposée en aveugle (agents ∥, reviewer ≠ auteur)146147Un **agent distinct par dimension**, contexte vierge, recevant le context pack (stable) + sa rubrique (volatile). Décomposer plutôt qu'un « God reviewer » (chaque agent a un focus net, les FP d'une dimension ne contaminent pas les autres — Qodo, Intercom, Cursor). Dimensions standard (activer selon routage) :148149- **spec-alignment** : le diff couvre-t-il TOUT le ticket (chemins, critères d'acceptation) ? gaps non implémentés ? scope creep (modifs hors ticket) ?150 **« Conforme à l'existant » n'est pas un verdict quand le ticket se plaint de l'existant.** Un ticket qui dit « aujourd'hui c'est trois étapes », « le vendeur repart de zéro », « on perd la donnée » énonce une mesure à améliorer. Le flux neuf se juge alors contre CETTE plainte (nombre d'étapes, de clics, culs-de-sac, ressaisies), jamais contre le flux voisin. Absoudre un parcours parce que son voisin fait pareil est le mode d'échec le plus coûteux de cette dimension : il passe toutes les autres coupes, et c'est l'utilisateur final qui le trouve en trente secondes. Recompter explicitement les étapes du parcours livré et les comparer au chiffre du ticket.151- **correctness / bugs** : logique, cas limites, concurrence/races, nil/maps, off-by-one, fuites de ressources, `defer` en boucle, error shadowing, aliasing de slices, gestion d'erreurs. **Cohérence inter-couches (lentille obligatoire dès que le diff touche une requête agrégée OU un mapping post-requête)** : quand une couche COMPTE/agrège (SQL `COUNT`, total de pagination, `GROUP BY`) et qu'une autre FILTRE/projette ensuite (mapping applicatif, `continue`, dédup), vérifier que les deux opèrent sur le MÊME ensemble — un total calculé en amont d'un filtre aval est gonflé (pages courtes, « suivant » actif à tort). Vérifier la **granularité de jointure** (un `WHERE`/`NOT EXISTS` par-LIGNE jointe ≠ intention par-ENTITÉ : cas multi-items même clé) et la **symétrie de scope** entre filtre SQL et filtre applicatif (même clé tenant/slug ?). Tracer le count ET les rows jusqu'à l'UI. (Language-aware.) **Un identifiant qui parse n'est pas un lien** : quand une valeur existante reçoit un sens nouveau (« ça ressemble à un uuid, donc c'est un X »), vérifier contre la liste des producteurs du champ (Étape 1.4) que rien d'autre n'y écrit une valeur de même forme — sinon la nouvelle règle casse tout ce qui l'écrivait déjà.152- **security** (mindset attaquant) : entrées contrôlées par l'attaquant → sinks (injection, XSS, SSRF, path traversal), authz/authn, secrets/PII en logs, crypto, trust boundaries, fail-open. Modèle de menace, pas check-list mécanique (gosec couvre le mécanique).153- **design / maintainability** : abstraction au bon niveau, sur-ingénierie/YAGNI (**test de suppression** : si retirer le module fait disparaître de la complexité sans rien casser, c'était un pass-through inutile ; **couture réelle = 2 implémentations**, une seule = couture hypothétique → ne pas abstraire), boussole ETC (« ce choix rend-il le système plus facile ou plus dur à changer ? »), lisibilité (« compréhensible en 5 s »), **nommage (test des 3 questions : le nom dit-il pourquoi/quoi/comment ? sinon renommer plutôt que commenter)**, commentaires (pourquoi non-évident, pas paraphrase ; un commentaire long et pénible à écrire signale une abstraction ratée), complexité poussée aux appelants (un check que chaque appelant doit répéter devrait être absorbé par le module — borner, défaut, null object), **dérive de nom après consolidation** (un diff qui fait gagner un nouvel appelant à une fonction existante via délégation/fusion — ex. `xResend` appelé maintenant aussi par l'envoi initial, pas seulement le renvoi — invalide-t-il la promesse de son nom ? un nom fidèle à un seul appelant devient trompeur une fois partagé), adhérence conventions projet.154- **tests** : couverture du *comportement modifié*, cas d'erreur (pas que le happy path), assertions utiles, et — candidat red-check — *les tests échouent-ils vraiment si le code casse ?*155- **texte d'interface** (cond., dès que le diff ajoute ou change un libellé vu par un utilisateur) : on ne juge pas le goût, on juge trois choses vérifiables. **Le temps** : une confirmation annonce ce qui *va* se produire, un compte rendu ce qui s'est produit ; « X est créé » sur un bouton pas encore cliqué est faux. **Le référent** : un terme métier employé sans ce qui l'ancre (« les brouillons », « le pack », « la synchro ») est une ambiguïté réelle, pas une préférence. **La promesse** : ce que le texte annonce doit correspondre à ce que le code fait, champ par champ.156- **perf** (cond.) : N+1, allocations, requêtes, complexité ; pas de micro-opt prématurée. **ux/a11y/i18n** (cond. front).157158Chaque agent **raisonne avant de noter** et rend, par finding : `{ severity: blocking|important|nit|suggestion, file, line, confidence: high|med|low, reasoning, finding, fix_concret, verification_plan }`. On lui donne explicitement la section **« ce qu'on NE flague PAS »** (voir plus bas). Prompt « senior 10+ ans **— en mode spécialiste, expert de la stack détectée (Étape 1) : adopte cette persona et vérifie en priorité les invariants/pièges de l'archi** —, terse, evidence-based ; ne récompense pas la longueur ; en cas d'hésitation, baisse la confiance ; en cas de doute version/API, une recherche web ciblée est permise (cite la source, re-vérifie contre le repo) ».159160Logger `[review:<dim>] N findings (x bloquants, y importants)`.161162*Exécution* : un appel `Agent` par dimension, lancés en parallèle dans un même message. Si le tool `Workflow` est disponible, le fan-out PEUT passer par un script Workflow (un `agent()` par dimension avec `schema` JSON imposé sur le format finding) — sorties validées structurellement, retries de parsing éliminés. L'interactif (AskUserQuestion) reste en conversation principale, jamais dans un workflow.163164## Étape 4 — Gate de vérification ("receipts")165166**Aucun finding critical/major n'est remonté sans preuve.** Pour chacun, l'**orchestrateur exécute lui-même** le `verification_plan` (grep/exec/Read direct) — pas d'agent de vérification dédié, sauf besoin d'isolation (ex. mutation destructive sur un fichier suivi) :167- **grep/ast-grep** : confirmer que le pattern existe vraiment et sur une ligne modifiée.168- **exécution sandbox** : reproduire (snippet, requête, test qui échoue sur le code actuel — fail-to-pass, TDD-Bench). Pour un bug allégué → écrire le test rouge ; s'il ne rougit pas, le finding est suspect → drop ou rétrograder.169- **sécurité** : confirmer que la source est réellement attaquant-contrôlée ET atteint le sink (traçage flux). Sinon → drop.170- **« ça va tout casser »** (un finding annonce qu'un changement de contrat fait échouer N flux) → **grille appelant par appelant** avant tout panel : chaque appelant est classé (a) déjà en erreur, (b) succès silencieusement faux, (c) succès correct — **seul (c) est une régression** ; sans (c), le finding reste un fait mais descend en 🟡 « rend explicite un défaut préexistant », et c'est le refuteur qui reçoit la grille remplie, pas un récit.171- **« tests faibles »** → **red-check mutation** : muter la ligne ciblée (inverser une condition / constante, type-préservant), relancer la suite. Si rien ne rougit → le test est vacant → finding réel. Si le test ciblé rougit (et lui seul) → les tests protègent → finding FAUX, drop.172 **Passer par `scripts/mutate.py`, pas par des commandes improvisées.** Il prend un spec JSON (`name`, `file`, `old`, `new`, `cmd`, `expect`, `must_match` optionnel) et couvre trois pièges qui coûtent chacun un tour de boucle : il **copie tous les fichiers cibles hors du dépôt AVANT la première édition** et restaure dans un `finally` vérifié par empreinte, donc un plantage ne laisse jamais un fichier muté ; il **refuse une mutation dont la commande de test n'exécute aucun test** (un filtre `-run`/`-k` qui ne matche rien sort en succès et se lit comme un faux `SURVIVED`) ; il **imprime chaque résultat au fil de l'eau**, donc une interruption ne perd pas les mesures déjà payées. Il n'utilise jamais `git` : un `checkout`/`restore` effacerait le travail non commité, et écrire dans l'index ferait embarquer la version mutée par un commit ultérieur.173 Un écart entre prédiction et résultat **se re-mesure avant d'être cru** : `ANCHOR`, `NO-TESTS` et `NO-MATCH` disent que le harnais a raté, pas que le test est faible.174175**Hygiène du re-run live (V1.0.1)** : si vérifier un finding exige de muter temporairement un fichier suivi (ex: pointer un `config.js`/`.env` vers un serveur de test), restaurer via `git checkout -- <file>` et **ne laisser AUCUN backup parasite** (`.bak`/`.orig`/`config.js.orig_backup`…). git suit déjà le fichier — pas besoin de copie manuelle ; un backup oublié pollue le working tree de l'user et le diff. Vérifier `git status` propre en fin de revue. **Piège du workflow hybride (V1.1.1)** : si le fichier muté porte AUSSI des éditions non commitées (cas fix-puis-review sur le même fichier), `git checkout -- <file>` les efface TOUTES, pas seulement la mutation — restaurer chirurgicalement (Edit inverse sur les seules lignes mutées, ou `git stash` avant de muter), jamais par checkout global.176177**Recherche web en appui (pas en substitut)** : pour un doute version/API/framework, consulter la doc officielle peut *confirmer ou réfuter* l'hypothèse (ex. « cette API plafonne-t-elle sans `first:` ? »). Mais le **receipt reste local** (grep/exec/test contre la version réelle du repo) ; la source web est citée (lien + date) et ne suffit jamais seule à remonter un finding bloquant.178179Findings non prouvés → **drop silencieux** (ou rétrogradés en `low confidence — à vérifier`). **Exception au drop — désaccord inter-couches dont le receipt exige une donnée multi-entités** : un finding de cohérence count↔display ou de granularité de jointure dont la preuve demande de FABRIQUER un jeu de données (multi-items même clé, count ≠ rows visibles) ne se drope PAS faute de receipt rapide. Si la lecture du flux SQL→mapping rend le désaccord plausible, le remonter en `🟡 important — à vérifier manuellement` avec le scénario de données exact à construire. Un receipt cher n'est pas l'absence de bug. Logger `[verify] k/n findings confirmés, j droppés (faux positifs)`.180181## Étape 5 — Synthèse calibrée (dédup + panel adversarial ciblé)1821831. **Dédup** cross-dimensions (même `file:line` / même cause).1842. **Refute-panel — seulement sur le douteux/critique** (proportionné : pas sur les nits) : pour chaque finding **bloquant** ou à **confiance non-haute**, un **skeptique indépendant** (modèle/famille ≠ si possible ; sinon prompt reformulé + ordre inversé pour casser le biais de position) est mandaté pour **RÉFUTER**. On garde le finding si la majorité ne le réfute pas (panel de juges variés > juge unique — PoLL, Verga 2024 ; le vote majoritaire tue les flukes d'une seule passe — Cursor BugBot). Escalade graduée : 1 refuteur → si désaccord, panel de 3 (médiane/majorité) → si toujours partagé sur un bloquant, **présenter à l'user** (ne jamais trancher seul un bloquant incertain).1853. **Calibration** : confiance finale par finding ; sous le seuil → `low confidence`. **Tiers de sévérité** : 🔴 bloquant (vrai merge-blocker) / 🟡 important / 🔵 nit·suggestion / 👍 praise (1-2 max). Mapping déterministe quand possible (MUST→bloquant, SHOULD→important, MAY→suggestion). **Cap nits ≤ 5 inline**, le reste en compte résumé.186187Logger `[panel] x findings réfutés, y confirmés` · `[calib] 🔴N 🟡N 🔵N`.188189## Étape 6 — Verdict + rapport (discipline signal/bruit)190191Rapport structuré, concis, **commente le code jamais l'auteur**, chaque finding avec `file:line` + raison + fix concret + confiance (+ preuve de vérif si critique) :192193```markdown194# Revue — <cible> (<N fichiers, M LOC>)195196## Spec-coverage197<Le diff fait-il ce que le ticket #<id> demande ? — Oui / Partiel (gaps listés) / Hors-sujet>198<Scope creep éventuel : modifs hors ticket>199200## 🔴 Bloquants (N) <vrais merge-blockers — sinon "aucun">2011. <constat> — `file.go:42` (confiance: haute) — preuve: <vérif> — fix: <concret>202203## 🟡 Importants (N)204…205206## 🔵 Mineurs / suggestions (N inline, +K en plus non listés)207…208209## 👍 Bien vu (≤2)210…211212## Verdict213<approve | request-changes | needs-discussion> — <1 phrase>214Métriques: lint <…> · typecheck <…> · tests <…/…> · SAST <…> · coût: <N agents, ~M tokens>215```216217Si rien de matériel : **le dire** (« Aucun bloquant. N suggestions mineures. Le changement améliore la base. ») — le silence est une feature (GitHub Copilot : 29 % des revues silencieuses, assumé). Le seuil de greenlight (Google) : le changement **améliore** la base, pas « est parfait ».218219**Coût mesuré, pas espéré** : sommer les `subagent_tokens` retournés par chaque appel `Agent` du run (reviewers + refuteurs) pour la ligne `coût:` — mesure objective, base de comparaison avant/après toute optimisation du skill (même logique que `subagent_tokens_total` dans feature-loop).220221Verdict `approve` ET un skill `branch-wrap-up` disponible ET la cible est du travail local non clôturé (working tree/branche, pas une PR déjà ouverte) → suggérer en une ligne `branch-wrap-up --no-review` pour la clôture (commit/push/MR-PR), la review étant faite.222223**Execution-grounding live (option, surtout `--deep` ou findings runtime)** : si l'app est runnable et qu'un bug bloquant est suspecté, exercer réellement le chemin pour confirmer qu'il reproduit (relancer un serveur stale, aligner le schéma DB runtime — cf. discipline smoke-live). Sinon, review-only.224225**`--comment`** : après confirmation, poster via `gh pr comment`/`gh pr review` (GitHub) ou `glab mr note` (GitLab), inline avec liens `file#Lx-Ly` (SHA complet pour GitHub). **`--fix`** : appliquer SEULEMENT les fixes high-confidence, dans le working tree, après confirmation — jamais de commit/push auto.226227## Étape 6.5 — Boucle (mode `--loop` uniquement)228229Le mode boucle a **un seul critère d'arrêt, et il porte sur le delta, pas sur le rapport** :230231> On boucle tant qu'un tour a **modifié du code**. On s'arrête au premier tour qui ne remonte **aucun bloquant** ET **ne produit aucune modification**.232233Ce qui est interdit de conclure : « ce tour n'a plus de bloquant, donc c'est fini ». Un tour qui corrige **crée du code que personne n'a jugé**, et son auteur est l'orchestrateur — le juge le plus mal placé pour l'évaluer (principe reviewer ≠ auteur). Tant qu'une ligne a bougé, il reste un delta non relu : c'est ce delta qui décide s'il faut un tour de plus, pas la sévérité du tour écoulé.234235Le tour N+1 en pratique :2361. **Cible = le delta**, pas la branche entière : le diff des correctifs du tour N (contre un instantané pris avant eux — pas contre `HEAD` si l'arbre porte du travail non commité).2372. **Reviewers frais et aveugles**, jamais les mêmes agents relancés : une continuation coûte autant qu'une passe neuve et arrive avec l'ancrage du tour précédent. Deux dimensions suffisent en général (correctness+sécurité du delta ; tests du delta) — proportionner au volume du delta, pas à celui de la branche.2383. **Le brief inverse la question** : non pas « ce correctif corrige-t-il ? » (déjà prouvé par mutation) mais « **que casse-t-il, qu'oublie-t-il, que surcorrige-t-il ?** ». Lister explicitement les correctifs et ce dont chacun est soupçonné.2394. **Chaque correctif se prouve par mutation inverse** : annuler le correctif doit faire rougir un test. Un correctif sans test qui meurt à son annulation n'est pas fini — c'est le seul signal externe qui distingue « corrigé » de « corrigé en apparence ».2405. Les décisions **tranchées** par l'utilisateur et les **receipts déjà payés** passent au ledger (Étape 7) et ne se re-litigent pas.241242Terminaison et honnêteté du décompte : le rapport final d'une boucle dit **le nombre de tours**, ce que chaque tour a trouvé, et **ce que le dernier tour n'a pas changé**. Une boucle annoncée close avec un delta non relu est un rapport faux, quelle que soit la couleur du gate. À partir du **tour 3**, ajouter une ligne de coût cumulé (agents, tokens) au rapport — la boucle reste due, l'utilisateur reste informé de ce qu'elle coûte.243244Garde-fous : un bloquant qui survit à deux tours sans converger, ou deux tours qui se renvoient le même arbitrage, **remonte à l'utilisateur** au lieu d'un troisième correctif (déjà la règle pour un bloquant incertain). Une modification qui n'est **que** du commentaire ou du renommage sans effet sémantique ne relance pas un tour complet : elle se vérifie par gate outils + relecture directe, et le rapport le dit.245246## Étape 7 — Learnings (ledger de branche + mémoire par repo + lessons cross-projet)247248- **Faux positif confirmé** par l'user (« ça c'est voulu ») ou par la vérif → l'écrire dans `~/.claude/projects/<encoded-cwd>/memory/senior_review_learnings.md` (`codebase_fact` vs `team_preference`) pour ne pas le répéter. Ne pas polluer avec des learnings trop génériques/vieux.249- **Leçon sur *comment reviewe250251…(truncated)