mutant-score

Par n8n-io · n8n

Exécute les tests de mutation Stryker sur un seul fichier source et retourne un rapport structuré, économe en tokens, utilisable dans un pipeline pour une boucle de suivi « renforcement des tests ». À utiliser quand l'utilisateur saisit `/mutant-score`, « mutation test this file », ou vient de modifier des tests et veut vérifier qu'ils valident bien un comportement. Périmètre limité au fichier unique — les runs de mutation sur l'ensemble du package sont hors scope.

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

Tests de mutation — fichier unique

Encapsule pnpm mutate <file> et analyse summary.json dans une forme compacte et structurée, adaptée aux itérations suivantes de « renforcement des mutants survivants ». Fonctionne avec n'importe quel package vitest — pnpm mutate déduit le package du chemin.

Quand l'utiliser

  • L'utilisateur invoque explicitement : /mutant-score <path>, « test de mutation sur ce fichier », « vérifie l'efficacité de mes tests sur X »
  • L'utilisateur vient de modifier un fichier de test et veut savoir si ses assertions sont pertinentes
  • Boucle de suivi après un verdict red — alimenter la sortie structurée vers une itération de « correction »

N'utilisez pas cette skill pour :

  • Les exécutions de mutation de tout le package ou du repo entier — fichier unique uniquement
  • Les questions de couverture % (utilisez le workflow de couverture existant)
  • Les packages jest (nodes-base, cli, db) — le vitest-runner de Stryker couvre uniquement les packages vitest
  • @n8n/expression-runtime — c'est le moteur isolated-vm (bloqué par DEVP-257)

Entrées

Un argument obligatoire : le fichier source à muter. Préférez un chemin relatif au repo — le package en est déduit :

  • packages/workflow/src/cron.ts (package déduit)
  • packages/@n8n/crdt/src/utils.ts (package déduit)

Un chemin relatif au package seul (src/cron.ts) est ambigu — passez le chemin relatif au repo, ou ajoutez --package-dir <pkg>. Ne devinez pas le package.

Étapes

  1. Résoudre la cible. N'importe quel package vitest fonctionne ; pnpm mutate déduit le package d'un chemin relatif au repo. Si le fichier se trouve dans un package jest ou @n8n/expression-runtime, signalez-le et arrêtez — ne fabriquez pas de sortie.

  2. Exécuter Stryker avec sortie réduite :

    pnpm mutate <repo-relative-file> 2>&1 | tail -40

    tail -40 élimine le spam de la barre de progression de Stryker ; les nombres pertinents + la liste des survivants atterrissent toujours dans les ~30 dernières lignes. Codes de sortie : 0 = réussi, 1 = sous le seuil (toujours valide, summary.json existe), 2 = erreur d'utilisation, 3 = échec de Stryker (pas de summary.json).

  3. Si code de sortie 3, affichez la queue réduite à l'utilisateur, suggérez de vérifier que les dépendances de l'espace de travail sont construites (pnpm build), et arrêtez. Ne fabriquez pas de rapport.

  4. Lire le reports/mutation/summary.json du package (par exemple packages/workflow/reports/mutation/summary.json) — jamais raw.json. raw.json fait plus de 600 Ko et n'est pas nécessaire pour la boucle de renforcement. summary.json contient déjà chaque mutant survivant avec sa localisation, son remplacement, le nom du mutateur et les noms des tests qui ont couvert la ligne.

  5. Limiter covering_tests à 3 par survivant. Si un mutant a été couvert par plus de 3 tests, conservez les 3 premiers et ajoutez +N more comme comptage. Les noms au-delà de 3 consomment des tokens sans ajouter de signal exploitable — la boucle de renforcement a besoin de savoir quel test étendre, pas tous.

  6. Calculer minimum_kills_needed pour atteindre le seuil :

    killed_now = summary.overall.counts.killed + summary.overall.counts.timeout
    valid_total = killed_now + summary.overall.counts.survived + summary.overall.counts.noCoverage
    needed = ceil((threshold/100) * valid_total) - killed_now

    Cela indique à la boucle suivante le nombre minimum de survivants qu'elle doit tuer pour basculer de red à green. Limiter au nombre de survivants.

  7. Produire la forme structurée décrite ci-dessous. Garder la prose à une seule ligne de titre ; le reste est le bloc JSON.

Forme de sortie

Une ligne de titre, puis un bloc JSON délimité. Rien d'autre — pas de préambule, pas de commentaire par survivant, pas de triage des risques (c'est le travail de la boucle suivante).

[red|green] <score>% (seuil <T>%) — <N> survivants ; besoin de tuer ≥<K> pour passer au vert.

```json
{
  "verdict": "red",
  "target": "packages/workflow/src/augment-object.ts",
  "package": "n8n-workflow",
  "score": 76.74,
  "threshold": 80,
  "delta_to_threshold": 3.26,
  "minimum_kills_needed": 5,
  "counts": {
    "killed": 99,
    "survived": 28,
    "no_coverage": 2,
    "timeout": 0
  },
  "survivors": [
    {
      "id": "77",
      "mutator": "ConditionalExpression",
      "location": "src/augment-object.ts:95:6",
      "original": "value === null",
      "replacement": "false",
      "covering_tests": [
        "augmentObject should handle null values",
        "augmentObject should handle nested nulls"
      ],
      "covering_tests_overflow": 0
    }
  ]
}
```

Trier le tableau survivors par location (numéro de ligne croissant, puis colonne) pour que la boucle de renforcement les traite de haut en bas du fichier.

Contraintes

  • Pas de raw.json — jamais le lire ou l'afficher. summary.json est la seule entrée.
  • Pas de rapport HTML — ne pas open raw.html ni coller de liens. Si l'utilisateur veut explorer visuellement, il demandera.
  • Pas de triage automatique — ne pas catégoriser les survivants par « bug réel » vs « assurance refactorisation ». C'est une étape d'analyse séparée qui doit se faire sur demande, pas par défaut. Garde le coût en tokens prévisible.
  • Pas de « je vais régénérer les tests pour vous maintenant » — cette skill rapporte l'écart. Utilisez n8n:mutant-fix si vous voulez des modifications d'assertion.

Suites courantes (ne les faites que si demandé)

  • L'utilisateur dit « correction de ces » → démarrer une boucle de renforcement en utilisant la sortie JSON comme entrée. Lire la source des covering_tests, proposer des modifications par mutant, relancer la skill pour vérifier.
  • L'utilisateur dit « explique le survivant #N » → récupérer ce mutant de summary.json, afficher ses ~5 lignes environnantes du fichier source, pas d'analyse au-delà de ce que summary.json contient.
  • L'utilisateur dit « quel est le seuil ? » → 80 % provisoire ; voir scripts/mutation-health/README.md pour la justification.
  • L'utilisateur dit « exécute-la sur les fichiers modifiés » → utiliser n8n:mutant-diff (mute le diff vs origin/master).

Connexes

  • scripts/mutation-health/README.md — l'histoire d'observabilité plus large soutenue par BQ
  • scripts/mutation-health/stryker.default.mjs — la configuration Stryker par défaut ; un package peut l'écraser avec son propre stryker.config.mjs (par exemple, packages/workflow exclut le moteur isolated-vm)
  • n8n:mutant-fix — l'équivalent du renforcement des survivants

Skills similaires