mutant-fix

Par n8n-io · n8n

Prendre un fichier summary.json de Stryker (issu de n8n:mutant-score), trier les mutants survivants par risque sur les comportements accessibles à l'utilisateur, écrire des modifications d'assertions minimales pour éliminer les 3 à 5 survivants à plus fort levier, puis vérifier en relançant n8n:mutant-score. À utiliser quand l'utilisateur vient d'exécuter des tests de mutation et souhaite renforcer la suite de tests, ou dit « tuer les survivants / renforcer les tests / corriger le rouge. » S'associe à n8n:mutant-score comme face écriture interne d'une seule itération.

npx skills add https://github.com/n8n-io/n8n --skill mutant-fix

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 est red
  • 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 lance n8n:mutant-score pour 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 de n8n:mutant-score l'a écrit dans le reports/mutation/summary.json du 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-score sur 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 test
  • files[0].score — score de mutation actuel
  • files[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 / hasOwnProperty après mutation
  • Séquences set-puis-delete / re-set-après-delete (patterns d'assignation Code-node)
  • Traiter l'assignation undefined comme 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 (.toBe vs .toEqual vs .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 :

  1. Plan de travail (avant édits) — les survivants sélectionnés et le plan pour chacun
  2. Diffs (pendant édits) — appels à l'outil Edit, visibles dans la transcription
  3. 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 boucle
  • scripts/mutation-health/README.md — l'histoire d'observabilité soutenue par BQ dans laquelle cela s'insère

Skills similaires