Renforcer les tests — éliminer les survivants à plus fort impact
L'autre moitié de la boucle locale de mutation-testing. n8n:mutant-score rapporte quelles mutations ont échappé aux tests ; cette skill sélectionne celles qui importent et écrit des changements d'assertion minimaux pour les éliminer.
Quand l'utiliser
- L'utilisateur vient de lancer
/n8n:mutant-score <file>et le verdict estred - L'utilisateur dit : « renforcer les tests », « éliminer les survivants », « corriger le red », « itérer sur les tests pour X »
- En cours de boucle : l'étape de vérification de cette skill appelle
n8n:mutant-scoreà nouveau, la boucle se ferme ici
Ne pas utiliser cette skill :
- Pour un verdict
green— il n'y a rien à renforcer ; si l'utilisateur insiste, repousser et demander quel fichier a réellement besoin de travail - Pour éliminer en masse tous les survivants — explicitement plafonnée à 5 par invocation. Réinvoquez pour plus.
Entrées
Accepte soit un fichier source, soit un résumé existant — celui dont vous disposez :
- Un fichier source (chemin relatif au repo, par ex.
packages/workflow/src/workflow-checksum.ts— package déduit) : auto-amorçage — il n'y a pas encore de résumé, donc l'étape 1 lancen8n:mutant-scorepour en produire un, puis continue. C'est le point d'entrée pour les appelants sans surveillance (par ex. cat-bot agissant sur une lacune du registre). - Un résumé existant :
--summary <path>. Une exécution antérieure den8n:mutant-scorel'a écrit dans lereports/mutation/summary.jsondu package (par ex.packages/workflow/reports/mutation/summary.json). Saute l'amorçage.
Étapes
1. Obtenir un résumé (amorcer si nécessaire)
- Donné un fichier source (ou pas de résumé utilisable sur disque) : lancer d'abord
n8n:mutant-scoresur le fichier pour générer<package-dir>/reports/mutation/summary.json. Puis le lire. - Donné/par défaut un chemin résumé : le lire directement.
Lire le résumé (déjà compact, ~50 KB) et extraire :
files[0].file— le fichier source sous testfiles[0].score— score de mutation actuelfiles[0].survivors[]— chaque mutant survivant (et sans couverture) avec localisation, remplacement, noms des tests couvrant
Si le verdict est déjà green, arrêter — rien à renforcer.
2. Lire la source sous test, parcimonieusement
Lire le fichier source référencé dans summary.json. Lire une fois, le fichier complet (les fichiers sources typiques font 50-500 lignes ; le coût est borné). C'est le seul fichier lu ; ne pas charger les fichiers de test encore.
3. Trier les survivants
Catégoriser chaque survivant dans l'un des trois groupes. Utiliser la grille ci-dessous — ne pas l'appliquer mécaniquement, mais s'en tenir à elle.
Impact ÉLEVÉ — « vrai vecteur de régression » : mutant qui garde un comportement que les vrais utilisateurs frappent réellement via la surface d'API publique.
- Vérifications de type contre les formes que les données utilisateur prennent régulièrement :
null,undefined,Date,Buffer,Uint8Array,Array, objets simples avec clés arbitraires - Branches conditionnelles qui contrôlent une chute critique (ex. « si ceci revient tôt, le mauvais chemin de code s'exécute »)
- Chemins de code qui gèrent le flux contrôlé par l'utilisateur : expressions, comportement des nœuds Code, gestion des éléments binaires, sémantique du deep-clone
- Mutants sur des clauses de garde qui préviennent les crashs (
if (value == null) return ...)
Impact MODÉRÉ — « invariant observable pour l'utilisateur » : réel mais à impact utilisateur moins fréquent.
- Pièges proxy qui changent la sémantique de
Object.keys/in/hasOwnPropertyaprès mutation - Séquences set-puis-delete / re-set-après-delete (patterns d'assignation Code-node)
- Traiter l'assignation
undefinedcomme suppression (obj.foo = undefined→ clé supprimée) - Cas limites qui ne s'activent que sous des patterns d'itération spécifiques
Impact FAIBLE — « assurance refactorisation » ou bruit : ignorer. Documenter le saut dans la sortie mais ne pas écrire de tests pour eux.
- Vérifications de propriété de constructeur (
arr.constructor === Array) sauf si le code de production en dépend clairement - Invariants d'idempotence déclenchés seulement par un autre code bibliothèque (Lodash, spread natif)
- Mutants équivalents — mutations qui produisent du code sémantiquement identique (ex. échanger un conditionnel inutilisé)
- Mutants sur des helpers internes que les utilisateurs ne peuvent atteindre par aucune API publique
Si le résumé liste des survivants noCoverage, les traiter comme leur propre groupe : « la suite de tests n'exécute même pas ces lignes ». Les trier par la même grille, mais les signaler séparément car ils ont besoin d'un nouveau cas de test plutôt qu'une extension d'assertion.
4. Sélectionner l'ensemble de travail : jusqu'à 5 mutants
Ordre : tous les HIGH d'abord, puis MODERATE si le budget reste, jamais LOW. Plafond strictement à 5 au total. Si HIGH seul dépasse 5, sélectionner les 5 plus distincts (ne pas sélectionner 5 mutants sur la même ligne — ils partagent probablement un fix ; sélectionner des représentants dans le fichier).
S'il existe moins de 3 candidats HIGH+MODERATE, faire simplement ce qui est là. Ne pas remplir avec LOW juste pour atteindre un nombre.
Rédiger l'ensemble de travail pour l'utilisateur avant édition :
Sélectionné N survivants à traiter (M ignorés comme assurance refactorisation / faible impact) :
1. [HIGH] localisation — original → remplacement
plan : assert <X> dans <nom du test couvrant>
2. [HIGH] ...
3. [MODERATE] ...
...
C'est la chance pour l'utilisateur de rediriger. Ne pas écrire de code encore.
5. Lire les tests couvrant les survivants sélectionnés
Pour chaque survivant sélectionné, le résumé liste les noms des tests qui ont couvert la ligne. Trouver ces tests dans le fichier de test (généralement packages/<pkg>/test/<source-basename>.test.ts, mais vérifier) et lire juste les blocs test('...') pertinents — pas le fichier entier. Utiliser Grep + Read avec décalages de ligne pour garder le coût en tokens bas.
Objectif : comprendre ce que le test existant affirme afin que la nouvelle assertion soit additive, non contradictoire.
6. Écrire les changements
Contraintes :
- Préférer étendre un test couvrant existant plutôt qu'en ajouter un nouveau. Churn de fichier plus bas, révision plus facile.
- Respecter le style existant — même bibliothèque d'assertion, mêmes idiomes de matcher (
.toBevs.toEqualvs.toStrictEqual). - Ajouts minimaux — une ou deux assertions par mutant, pas un it-block nouveau par mutant.
- Pas de fabrication — seulement affirmer ce que le code source fait réellement. Si vous ne pouvez pas le dire à partir de la source, arrêter et demander à l'utilisateur.
- Pour les survivants
noCoverage, ajouter un nouveau cas de test nommé d'après le comportement étant épinglé. Le placer à côté des tests connexes.
Utiliser Edit avec correspondances de chaîne exacte. Ne jamais réécrire des fichiers de test entiers.
7. Vérifier
Réinvoquer n8n:mutant-score sur le même fichier source. Rapporter :
Avant : red 76,74% (28 survivants)
Après : green 82,34% (22 survivants)
Tués : 6 de 5 ciblés (1 bonus — fix pour #77 a aussi tué #78)
Encore survivants : 22 — réinvoquez /n8n:mutant-fix pour un autre lot.
Si le score a augmenté mais le seuil toujours non atteint : l'itération fonctionne, recommander un autre passage. Si le score a baissé ou est resté le même : au moins un nouveau test n'affirme pas ce que nous pensons. Exposer le diff à l'utilisateur et arrêter — ne pas auto-revenir. Si un test échoue maintenant (pas survit — échoue réellement) : nous avons affirmé quelque chose que le code ne fait pas. Revenir cette assertion spécifique, laisser le reste, rapporter laquelle était fausse.
Forme de sortie
Chaque invocation produit :
- Plan de travail (avant édits) — les survivants sélectionnés et le plan pour chacun
- Diffs (pendant édits) — appels à l'outil Edit, visibles dans la transcription
- Vérification (après) — réexécution + comparaison avant/après
Garder la prose minimale entre sections. Les étapes de plan et vérification sont les sorties structurées ; tout le reste est mécanique.
Contraintes
- 5 mutants max par invocation. Réinvoquez pour plus. Prévient les sessions qui s'emballent sur des fichiers à 30 survivants.
- Ne jamais fabriquer d'assertions. Si la source ne fait clairement pas X, ne pas affirmer qu'elle le fait.
- Pas de fichiers de test nouveaux sauf absolument nécessaire. Étendre le fichier de test couvrant existant.
- Pas de revert des tests d'autres personnes. Éditer seulement les tests dans le package étant muté.
- Pas de réexécution de mutant-score plus d'une fois par invocation. C'est l'étape de vérification. Ne pas boucler dans une seule invocation ; laisser l'utilisateur réinvoquer.
- Pas de commits. Les édits atterrissent dans l'arbre de travail ; l'utilisateur révise et commite.
Suivis courants
- L'utilisateur dit « recommence » → réinvoquez cette skill. Le summary.json reflète maintenant l'état post-édition.
- L'utilisateur dit « pourquoi #N a été classé LOW ? » → expliquer l'application de la grille pour ce mutant spécifique, pas de re-triage des autres.
- L'utilisateur dit « tue #N spécifiquement » → surcharger le triage pour ce mutant, le traiter comme sélectionné.
- L'utilisateur dit « saute l'étape de vérification » → ne pas le faire ; l'étape de vérification est le contrat que les édits ont réellement déplacé le score.
Connexes
n8n:mutant-score— le côté lecture de cette bouclescripts/mutation-health/README.md— l'histoire d'observabilité soutenue par BQ dans laquelle cela s'insère