[ADD] mutation: engine audit command and its versioned written report
Spec 14.13 asks for an audit that shows which engine mutants the tests
kill, written where a reviewer can read it. npm run mutation and make
mutation run the baseline, a coverage pass that stops early when nothing is
measured, then every mutant; survivors are replayed against the long
series. The report holds version, scope, source fingerprint and Node
version only, no duration, host or date, and refuses any other field.
Declared equivalent mutants live in a separate list of key and reason.
Checked: red first; 3612 node tests green from the index alone.
--- FR ---
[ADD] mutation : commande d'audit du moteur, rapport écrit et versionné
Le § 14.13 demande un audit qui montre quels mutants du moteur les épreuves
tuent, écrit là où un relecteur le lit. npm run mutation et make mutation
jouent la ligne de base, une passe de couverture qui s'arrête tôt si rien
n'est mesuré, puis chaque mutant ; les survivants se rejouent contre la
série longue. Le rapport ne porte que version, périmètre, empreinte du
source et version de Node, ni durée, ni hôte, ni date, et refuse tout autre
champ. Les mutants déclarés équivalents vivent dans une liste à part.
Vérifié : rouge d'abord ; 3612 node verts depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:29:08 -04:00
// © 2026 TechnoLibre (http://www.technolibre.ca)
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
// Le rapport de l'audit par mutation (§ 14.13) : rediger rend le Markdown et
// le JSON d'un audit, sur des résultats inventés. Les modules nommés ici
[IMP] mutation: count long-series kills apart, test the audit end to end
A mutant the node series missed but the long series killed was still
counted as a survivor, so the committed audit overstated what escapes
the tests. It now has its own column: 15 of the 137 survivors move
there, leaving 122; the committed files are regenerated from their own
results, without a rerun. The audit itself now runs in the long series
on a small fixture tree: long-series replay, exit code 0 with
survivors, exit 2 and nothing written when the baseline fails. The
committed audit files are read back and must match what rediger writes.
Checked: 3868 node and 105 long tests from the index alone.
--- FR ---
[IMP] mutation : tués par la série longue à part, audit éprouvé entier
Un mutant que la série node manquait et que la série longue tuait
comptait encore comme survivant : l'audit commis surestimait ce qui
échappe aux épreuves. Il a sa colonne : 15 des 137 survivants y passent,
il en reste 122 ; les fichiers commis sont réengendrés de leurs propres
résultats, sans rejeu. L'audit tourne entier dans la série longue sur un
petit arbre : rejeu long, sortie 0 avec survivants, sortie 2 et rien
d'écrit quand la ligne de base tombe. Les fichiers commis sont relus et
doivent égaler ce que rediger écrit.
Vérifié : 3868 node et 105 longues depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-10 18:51:43 -04:00
// n'existent pas : rediger ne lit aucun fichier. La dernière partie lit, elle,
// les trois fichiers committés d'audit/, comme du texte, sans importer
// audit.js.
[ADD] mutation: engine audit command and its versioned written report
Spec 14.13 asks for an audit that shows which engine mutants the tests
kill, written where a reviewer can read it. npm run mutation and make
mutation run the baseline, a coverage pass that stops early when nothing is
measured, then every mutant; survivors are replayed against the long
series. The report holds version, scope, source fingerprint and Node
version only, no duration, host or date, and refuses any other field.
Declared equivalent mutants live in a separate list of key and reason.
Checked: red first; 3612 node tests green from the index alone.
--- FR ---
[ADD] mutation : commande d'audit du moteur, rapport écrit et versionné
Le § 14.13 demande un audit qui montre quels mutants du moteur les épreuves
tuent, écrit là où un relecteur le lit. npm run mutation et make mutation
jouent la ligne de base, une passe de couverture qui s'arrête tôt si rien
n'est mesuré, puis chaque mutant ; les survivants se rejouent contre la
série longue. Le rapport ne porte que version, périmètre, empreinte du
source et version de Node, ni durée, ni hôte, ni date, et refuse tout autre
champ. Les mutants déclarés équivalents vivent dans une liste à part.
Vérifié : rouge d'abord ; 3612 node verts depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:29:08 -04:00
import assert from 'node:assert/strict' ;
[IMP] mutation: count long-series kills apart, test the audit end to end
A mutant the node series missed but the long series killed was still
counted as a survivor, so the committed audit overstated what escapes
the tests. It now has its own column: 15 of the 137 survivors move
there, leaving 122; the committed files are regenerated from their own
results, without a rerun. The audit itself now runs in the long series
on a small fixture tree: long-series replay, exit code 0 with
survivors, exit 2 and nothing written when the baseline fails. The
committed audit files are read back and must match what rediger writes.
Checked: 3868 node and 105 long tests from the index alone.
--- FR ---
[IMP] mutation : tués par la série longue à part, audit éprouvé entier
Un mutant que la série node manquait et que la série longue tuait
comptait encore comme survivant : l'audit commis surestimait ce qui
échappe aux épreuves. Il a sa colonne : 15 des 137 survivants y passent,
il en reste 122 ; les fichiers commis sont réengendrés de leurs propres
résultats, sans rejeu. L'audit tourne entier dans la série longue sur un
petit arbre : rejeu long, sortie 0 avec survivants, sortie 2 et rien
d'écrit quand la ligne de base tombe. Les fichiers commis sont relus et
doivent égaler ce que rediger écrit.
Vérifié : 3868 node et 105 longues depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-10 18:51:43 -04:00
import { readFileSync } from 'node:fs' ;
import { join } from 'node:path' ;
import { fileURLToPath } from 'node:url' ;
[ADD] mutation: engine audit command and its versioned written report
Spec 14.13 asks for an audit that shows which engine mutants the tests
kill, written where a reviewer can read it. npm run mutation and make
mutation run the baseline, a coverage pass that stops early when nothing is
measured, then every mutant; survivors are replayed against the long
series. The report holds version, scope, source fingerprint and Node
version only, no duration, host or date, and refuses any other field.
Declared equivalent mutants live in a separate list of key and reason.
Checked: red first; 3612 node tests green from the index alone.
--- FR ---
[ADD] mutation : commande d'audit du moteur, rapport écrit et versionné
Le § 14.13 demande un audit qui montre quels mutants du moteur les épreuves
tuent, écrit là où un relecteur le lit. npm run mutation et make mutation
jouent la ligne de base, une passe de couverture qui s'arrête tôt si rien
n'est mesuré, puis chaque mutant ; les survivants se rejouent contre la
série longue. Le rapport ne porte que version, périmètre, empreinte du
source et version de Node, ni durée, ni hôte, ni date, et refuse tout autre
champ. Les mutants déclarés équivalents vivent dans une liste à part.
Vérifié : rouge d'abord ; 3612 node verts depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:29:08 -04:00
import { VERSION } from '../../src/version.genere.js' ;
import { describe , test } from '../../test/lanceur.js' ;
[IMP] mutation: count long-series kills apart, test the audit end to end
A mutant the node series missed but the long series killed was still
counted as a survivor, so the committed audit overstated what escapes
the tests. It now has its own column: 15 of the 137 survivors move
there, leaving 122; the committed files are regenerated from their own
results, without a rerun. The audit itself now runs in the long series
on a small fixture tree: long-series replay, exit code 0 with
survivors, exit 2 and nothing written when the baseline fails. The
committed audit files are read back and must match what rediger writes.
Checked: 3868 node and 105 long tests from the index alone.
--- FR ---
[IMP] mutation : tués par la série longue à part, audit éprouvé entier
Un mutant que la série node manquait et que la série longue tuait
comptait encore comme survivant : l'audit commis surestimait ce qui
échappe aux épreuves. Il a sa colonne : 15 des 137 survivants y passent,
il en reste 122 ; les fichiers commis sont réengendrés de leurs propres
résultats, sans rejeu. L'audit tourne entier dans la série longue sur un
petit arbre : rejeu long, sortie 0 avec survivants, sortie 2 et rien
d'écrit quand la ligne de base tombe. Les fichiers commis sont relus et
doivent égaler ce que rediger écrit.
Vérifié : 3868 node et 105 longues depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-10 18:51:43 -04:00
import { equivalentsDe , rediger , relire } from './rapport.js' ;
const RACINE = fileURLToPath ( new URL ( '../../' , import . meta . url ) ) ;
[ADD] mutation: engine audit command and its versioned written report
Spec 14.13 asks for an audit that shows which engine mutants the tests
kill, written where a reviewer can read it. npm run mutation and make
mutation run the baseline, a coverage pass that stops early when nothing is
measured, then every mutant; survivors are replayed against the long
series. The report holds version, scope, source fingerprint and Node
version only, no duration, host or date, and refuses any other field.
Declared equivalent mutants live in a separate list of key and reason.
Checked: red first; 3612 node tests green from the index alone.
--- FR ---
[ADD] mutation : commande d'audit du moteur, rapport écrit et versionné
Le § 14.13 demande un audit qui montre quels mutants du moteur les épreuves
tuent, écrit là où un relecteur le lit. npm run mutation et make mutation
jouent la ligne de base, une passe de couverture qui s'arrête tôt si rien
n'est mesuré, puis chaque mutant ; les survivants se rejouent contre la
série longue. Le rapport ne porte que version, périmètre, empreinte du
source et version de Node, ni durée, ni hôte, ni date, et refuse tout autre
champ. Les mutants déclarés équivalents vivent dans une liste à part.
Vérifié : rouge d'abord ; 3612 node verts depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:29:08 -04:00
const ENTETE = Object . freeze ( {
version : VERSION . affichee ,
perimetre : [ 'src/verger' ] ,
empreinte : 'a' . repeat ( 64 ) ,
node : 'v26.1.0' ,
} ) ;
// Un mutant inventé, aux champs du contrat de mutants.js.
function mutant ( fichier , ligne , operateur , original , remplacement , colonne = 5 ) {
return {
cle : ` ${ fichier } : ${ operateur } : ${ ligne } ${ colonne } :0 ` ,
fichier ,
ligne ,
colonne ,
operateur ,
original ,
remplacement ,
debut : ligne * 100 + colonne ,
fin : ligne * 100 + colonne + original . length ,
} ;
}
const resultat = ( m , issue , { couverte = true , longue = null , equivalent = null } = { } ) => ( {
mutant : m ,
issue ,
couverte ,
longue ,
equivalent ,
} ) ;
// Neuf résultats sur deux modules, dans le désordre :
// pommes.js arithmetique tué ; frontiere survivant couvert (l. 30) ;
// negation survivant non couvert (l. 4) ; nombre-zero délai ;
// si-vrai survivant équivalent
// poires.js arithmetique survivant couvert (l. 12), tué par la série
[IMP] mutation: count long-series kills apart, test the audit end to end
A mutant the node series missed but the long series killed was still
counted as a survivor, so the committed audit overstated what escapes
the tests. It now has its own column: 15 of the 137 survivors move
there, leaving 122; the committed files are regenerated from their own
results, without a rerun. The audit itself now runs in the long series
on a small fixture tree: long-series replay, exit code 0 with
survivors, exit 2 and nothing written when the baseline fails. The
committed audit files are read back and must match what rediger writes.
Checked: 3868 node and 105 long tests from the index alone.
--- FR ---
[IMP] mutation : tués par la série longue à part, audit éprouvé entier
Un mutant que la série node manquait et que la série longue tuait
comptait encore comme survivant : l'audit commis surestimait ce qui
échappe aux épreuves. Il a sa colonne : 15 des 137 survivants y passent,
il en reste 122 ; les fichiers commis sont réengendrés de leurs propres
résultats, sans rejeu. L'audit tourne entier dans la série longue sur un
petit arbre : rejeu long, sortie 0 avec survivants, sortie 2 et rien
d'écrit quand la ligne de base tombe. Les fichiers commis sont relus et
doivent égaler ce que rediger écrit.
Vérifié : 3868 node et 105 longues depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-10 18:51:43 -04:00
// longue, donc compté à part des survivants ; frontiere tué ;
// logique erreur ; frontiere survivant couvert (l. 12,
// colonne 2)
[ADD] mutation: engine audit command and its versioned written report
Spec 14.13 asks for an audit that shows which engine mutants the tests
kill, written where a reviewer can read it. npm run mutation and make
mutation run the baseline, a coverage pass that stops early when nothing is
measured, then every mutant; survivors are replayed against the long
series. The report holds version, scope, source fingerprint and Node
version only, no duration, host or date, and refuses any other field.
Declared equivalent mutants live in a separate list of key and reason.
Checked: red first; 3612 node tests green from the index alone.
--- FR ---
[ADD] mutation : commande d'audit du moteur, rapport écrit et versionné
Le § 14.13 demande un audit qui montre quels mutants du moteur les épreuves
tuent, écrit là où un relecteur le lit. npm run mutation et make mutation
jouent la ligne de base, une passe de couverture qui s'arrête tôt si rien
n'est mesuré, puis chaque mutant ; les survivants se rejouent contre la
série longue. Le rapport ne porte que version, périmètre, empreinte du
source et version de Node, ni durée, ni hôte, ni date, et refuse tout autre
champ. Les mutants déclarés équivalents vivent dans une liste à part.
Vérifié : rouge d'abord ; 3612 node verts depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:29:08 -04:00
const RESULTATS = Object . freeze ( [
resultat ( mutant ( 'src/verger/pommes.js' , 3 , 'arithmetique' , '+' , '-' ) , 'tue' ) ,
resultat ( mutant ( 'src/verger/poires.js' , 12 , 'arithmetique' , '*' , '/' ) , 'survivant' , { longue : 'tue' } ) ,
resultat ( mutant ( 'src/verger/pommes.js' , 30 , 'frontiere' , '<' , '<=' ) , 'survivant' , { longue : 'survivant' } ) ,
resultat ( mutant ( 'src/verger/pommes.js' , 4 , 'negation' , '===' , '!==' ) , 'survivant' , { couverte : false } ) ,
resultat ( mutant ( 'src/verger/poires.js' , 7 , 'frontiere' , '>' , '>=' ) , 'tue' ) ,
resultat ( mutant ( 'src/verger/pommes.js' , 9 , 'nombre-zero' , '1' , '0' ) , 'delai' ) ,
resultat ( mutant ( 'src/verger/poires.js' , 20 , 'logique' , 'a && b' , '((a )||( b))' ) , 'erreur' ) ,
resultat ( mutant ( 'src/verger/pommes.js' , 15 , 'si-vrai' , 'n > 0' , 'true' ) , 'survivant' , {
equivalent : 'le test est toujours vrai à cet endroit' ,
} ) ,
resultat ( mutant ( 'src/verger/poires.js' , 12 , 'frontiere' , '<=' , '<' , 2 ) , 'survivant' ) ,
] ) ;
const lieux = ( liste ) => liste . map ( ( { fichier , ligne , colonne } ) => ` ${ fichier } : ${ ligne } : ${ colonne } ` ) ;
describe ( 'rapport de mutation : les comptes' , ( ) => {
const { json } = rediger ( { entete : ENTETE , resultats : RESULTATS } ) ;
const lu = JSON . parse ( json ) ;
[IMP] mutation: count long-series kills apart, test the audit end to end
A mutant the node series missed but the long series killed was still
counted as a survivor, so the committed audit overstated what escapes
the tests. It now has its own column: 15 of the 137 survivors move
there, leaving 122; the committed files are regenerated from their own
results, without a rerun. The audit itself now runs in the long series
on a small fixture tree: long-series replay, exit code 0 with
survivors, exit 2 and nothing written when the baseline fails. The
committed audit files are read back and must match what rediger writes.
Checked: 3868 node and 105 long tests from the index alone.
--- FR ---
[IMP] mutation : tués par la série longue à part, audit éprouvé entier
Un mutant que la série node manquait et que la série longue tuait
comptait encore comme survivant : l'audit commis surestimait ce qui
échappe aux épreuves. Il a sa colonne : 15 des 137 survivants y passent,
il en reste 122 ; les fichiers commis sont réengendrés de leurs propres
résultats, sans rejeu. L'audit tourne entier dans la série longue sur un
petit arbre : rejeu long, sortie 0 avec survivants, sortie 2 et rien
d'écrit quand la ligne de base tombe. Les fichiers commis sont relus et
doivent égaler ce que rediger écrit.
Vérifié : 3868 node et 105 longues depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-10 18:51:43 -04:00
test ( 'le bilan compte chaque issue ; un survivant que la série longue tue, et un équivalent, à part' , ( ) => {
assert . deepEqual ( lu . bilan , { total : 9 , tues : 2 , tuesSerieLongue : 1 , survivants : 3 , equivalents : 1 , delais : 1 , erreurs : 1 } ) ;
[ADD] mutation: engine audit command and its versioned written report
Spec 14.13 asks for an audit that shows which engine mutants the tests
kill, written where a reviewer can read it. npm run mutation and make
mutation run the baseline, a coverage pass that stops early when nothing is
measured, then every mutant; survivors are replayed against the long
series. The report holds version, scope, source fingerprint and Node
version only, no duration, host or date, and refuses any other field.
Declared equivalent mutants live in a separate list of key and reason.
Checked: red first; 3612 node tests green from the index alone.
--- FR ---
[ADD] mutation : commande d'audit du moteur, rapport écrit et versionné
Le § 14.13 demande un audit qui montre quels mutants du moteur les épreuves
tuent, écrit là où un relecteur le lit. npm run mutation et make mutation
jouent la ligne de base, une passe de couverture qui s'arrête tôt si rien
n'est mesuré, puis chaque mutant ; les survivants se rejouent contre la
série longue. Le rapport ne porte que version, périmètre, empreinte du
source et version de Node, ni durée, ni hôte, ni date, et refuse tout autre
champ. Les mutants déclarés équivalents vivent dans une liste à part.
Vérifié : rouge d'abord ; 3612 node verts depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:29:08 -04:00
} ) ;
test ( 'par fichier, dans l’ ordre des chemins' , ( ) => {
assert . deepEqual ( lu . parFichier , [
[IMP] mutation: count long-series kills apart, test the audit end to end
A mutant the node series missed but the long series killed was still
counted as a survivor, so the committed audit overstated what escapes
the tests. It now has its own column: 15 of the 137 survivors move
there, leaving 122; the committed files are regenerated from their own
results, without a rerun. The audit itself now runs in the long series
on a small fixture tree: long-series replay, exit code 0 with
survivors, exit 2 and nothing written when the baseline fails. The
committed audit files are read back and must match what rediger writes.
Checked: 3868 node and 105 long tests from the index alone.
--- FR ---
[IMP] mutation : tués par la série longue à part, audit éprouvé entier
Un mutant que la série node manquait et que la série longue tuait
comptait encore comme survivant : l'audit commis surestimait ce qui
échappe aux épreuves. Il a sa colonne : 15 des 137 survivants y passent,
il en reste 122 ; les fichiers commis sont réengendrés de leurs propres
résultats, sans rejeu. L'audit tourne entier dans la série longue sur un
petit arbre : rejeu long, sortie 0 avec survivants, sortie 2 et rien
d'écrit quand la ligne de base tombe. Les fichiers commis sont relus et
doivent égaler ce que rediger écrit.
Vérifié : 3868 node et 105 longues depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-10 18:51:43 -04:00
{ fichier : 'src/verger/poires.js' , total : 4 , tues : 1 , tuesSerieLongue : 1 , survivants : 1 , equivalents : 0 , delais : 0 , erreurs : 1 } ,
{ fichier : 'src/verger/pommes.js' , total : 5 , tues : 1 , tuesSerieLongue : 0 , survivants : 2 , equivalents : 1 , delais : 1 , erreurs : 0 } ,
[ADD] mutation: engine audit command and its versioned written report
Spec 14.13 asks for an audit that shows which engine mutants the tests
kill, written where a reviewer can read it. npm run mutation and make
mutation run the baseline, a coverage pass that stops early when nothing is
measured, then every mutant; survivors are replayed against the long
series. The report holds version, scope, source fingerprint and Node
version only, no duration, host or date, and refuses any other field.
Declared equivalent mutants live in a separate list of key and reason.
Checked: red first; 3612 node tests green from the index alone.
--- FR ---
[ADD] mutation : commande d'audit du moteur, rapport écrit et versionné
Le § 14.13 demande un audit qui montre quels mutants du moteur les épreuves
tuent, écrit là où un relecteur le lit. npm run mutation et make mutation
jouent la ligne de base, une passe de couverture qui s'arrête tôt si rien
n'est mesuré, puis chaque mutant ; les survivants se rejouent contre la
série longue. Le rapport ne porte que version, périmètre, empreinte du
source et version de Node, ni durée, ni hôte, ni date, et refuse tout autre
champ. Les mutants déclarés équivalents vivent dans une liste à part.
Vérifié : rouge d'abord ; 3612 node verts depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:29:08 -04:00
] ) ;
} ) ;
test ( 'par opérateur, dans l’ ordre d’ OPERATEURS' , ( ) => {
[IMP] mutation: count long-series kills apart, test the audit end to end
A mutant the node series missed but the long series killed was still
counted as a survivor, so the committed audit overstated what escapes
the tests. It now has its own column: 15 of the 137 survivors move
there, leaving 122; the committed files are regenerated from their own
results, without a rerun. The audit itself now runs in the long series
on a small fixture tree: long-series replay, exit code 0 with
survivors, exit 2 and nothing written when the baseline fails. The
committed audit files are read back and must match what rediger writes.
Checked: 3868 node and 105 long tests from the index alone.
--- FR ---
[IMP] mutation : tués par la série longue à part, audit éprouvé entier
Un mutant que la série node manquait et que la série longue tuait
comptait encore comme survivant : l'audit commis surestimait ce qui
échappe aux épreuves. Il a sa colonne : 15 des 137 survivants y passent,
il en reste 122 ; les fichiers commis sont réengendrés de leurs propres
résultats, sans rejeu. L'audit tourne entier dans la série longue sur un
petit arbre : rejeu long, sortie 0 avec survivants, sortie 2 et rien
d'écrit quand la ligne de base tombe. Les fichiers commis sont relus et
doivent égaler ce que rediger écrit.
Vérifié : 3868 node et 105 longues depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-10 18:51:43 -04:00
const vide = { tues : 0 , tuesSerieLongue : 0 , survivants : 0 , equivalents : 0 , delais : 0 , erreurs : 0 } ;
[ADD] mutation: engine audit command and its versioned written report
Spec 14.13 asks for an audit that shows which engine mutants the tests
kill, written where a reviewer can read it. npm run mutation and make
mutation run the baseline, a coverage pass that stops early when nothing is
measured, then every mutant; survivors are replayed against the long
series. The report holds version, scope, source fingerprint and Node
version only, no duration, host or date, and refuses any other field.
Declared equivalent mutants live in a separate list of key and reason.
Checked: red first; 3612 node tests green from the index alone.
--- FR ---
[ADD] mutation : commande d'audit du moteur, rapport écrit et versionné
Le § 14.13 demande un audit qui montre quels mutants du moteur les épreuves
tuent, écrit là où un relecteur le lit. npm run mutation et make mutation
jouent la ligne de base, une passe de couverture qui s'arrête tôt si rien
n'est mesuré, puis chaque mutant ; les survivants se rejouent contre la
série longue. Le rapport ne porte que version, périmètre, empreinte du
source et version de Node, ni durée, ni hôte, ni date, et refuse tout autre
champ. Les mutants déclarés équivalents vivent dans une liste à part.
Vérifié : rouge d'abord ; 3612 node verts depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:29:08 -04:00
assert . deepEqual ( lu . parOperateur , [
[IMP] mutation: count long-series kills apart, test the audit end to end
A mutant the node series missed but the long series killed was still
counted as a survivor, so the committed audit overstated what escapes
the tests. It now has its own column: 15 of the 137 survivors move
there, leaving 122; the committed files are regenerated from their own
results, without a rerun. The audit itself now runs in the long series
on a small fixture tree: long-series replay, exit code 0 with
survivors, exit 2 and nothing written when the baseline fails. The
committed audit files are read back and must match what rediger writes.
Checked: 3868 node and 105 long tests from the index alone.
--- FR ---
[IMP] mutation : tués par la série longue à part, audit éprouvé entier
Un mutant que la série node manquait et que la série longue tuait
comptait encore comme survivant : l'audit commis surestimait ce qui
échappe aux épreuves. Il a sa colonne : 15 des 137 survivants y passent,
il en reste 122 ; les fichiers commis sont réengendrés de leurs propres
résultats, sans rejeu. L'audit tourne entier dans la série longue sur un
petit arbre : rejeu long, sortie 0 avec survivants, sortie 2 et rien
d'écrit quand la ligne de base tombe. Les fichiers commis sont relus et
doivent égaler ce que rediger écrit.
Vérifié : 3868 node et 105 longues depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-10 18:51:43 -04:00
{ operateur : 'arithmetique' , total : 2 , ... vide , tues : 1 , tuesSerieLongue : 1 } ,
{ operateur : 'frontiere' , total : 3 , ... vide , tues : 1 , survivants : 2 } ,
{ operateur : 'negation' , total : 1 , ... vide , survivants : 1 } ,
{ operateur : 'logique' , total : 1 , ... vide , erreurs : 1 } ,
{ operateur : 'nombre-zero' , total : 1 , ... vide , delais : 1 } ,
{ operateur : 'si-vrai' , total : 1 , ... vide , equivalents : 1 } ,
[ADD] mutation: engine audit command and its versioned written report
Spec 14.13 asks for an audit that shows which engine mutants the tests
kill, written where a reviewer can read it. npm run mutation and make
mutation run the baseline, a coverage pass that stops early when nothing is
measured, then every mutant; survivors are replayed against the long
series. The report holds version, scope, source fingerprint and Node
version only, no duration, host or date, and refuses any other field.
Declared equivalent mutants live in a separate list of key and reason.
Checked: red first; 3612 node tests green from the index alone.
--- FR ---
[ADD] mutation : commande d'audit du moteur, rapport écrit et versionné
Le § 14.13 demande un audit qui montre quels mutants du moteur les épreuves
tuent, écrit là où un relecteur le lit. npm run mutation et make mutation
jouent la ligne de base, une passe de couverture qui s'arrête tôt si rien
n'est mesuré, puis chaque mutant ; les survivants se rejouent contre la
série longue. Le rapport ne porte que version, périmètre, empreinte du
source et version de Node, ni durée, ni hôte, ni date, et refuse tout autre
champ. Les mutants déclarés équivalents vivent dans une liste à part.
Vérifié : rouge d'abord ; 3612 node verts depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:29:08 -04:00
] ) ;
} ) ;
test ( 'le Markdown porte les mêmes comptes, en lignes de tableau' , ( ) => {
const { markdown } = rediger ( { entete : ENTETE , resultats : RESULTATS } ) ;
const lignes = markdown . split ( '\n' ) ;
[IMP] mutation: count long-series kills apart, test the audit end to end
A mutant the node series missed but the long series killed was still
counted as a survivor, so the committed audit overstated what escapes
the tests. It now has its own column: 15 of the 137 survivors move
there, leaving 122; the committed files are regenerated from their own
results, without a rerun. The audit itself now runs in the long series
on a small fixture tree: long-series replay, exit code 0 with
survivors, exit 2 and nothing written when the baseline fails. The
committed audit files are read back and must match what rediger writes.
Checked: 3868 node and 105 long tests from the index alone.
--- FR ---
[IMP] mutation : tués par la série longue à part, audit éprouvé entier
Un mutant que la série node manquait et que la série longue tuait
comptait encore comme survivant : l'audit commis surestimait ce qui
échappe aux épreuves. Il a sa colonne : 15 des 137 survivants y passent,
il en reste 122 ; les fichiers commis sont réengendrés de leurs propres
résultats, sans rejeu. L'audit tourne entier dans la série longue sur un
petit arbre : rejeu long, sortie 0 avec survivants, sortie 2 et rien
d'écrit quand la ligne de base tombe. Les fichiers commis sont relus et
doivent égaler ce que rediger écrit.
Vérifié : 3868 node et 105 longues depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-10 18:51:43 -04:00
assert . ok ( lignes . includes ( '| mutants | tués | tués par la série longue | survivants | équivalents | délais | erreurs |' ) , 'en-tête du bilan' ) ;
assert . ok ( lignes . includes ( '| 9 | 2 | 1 | 3 | 1 | 1 | 1 |' ) , 'ligne du bilan' ) ;
assert . ok ( lignes . includes ( '| `src/verger/poires.js` | 4 | 1 | 1 | 1 | 0 | 0 | 1 |' ) , 'ligne de poires.js' ) ;
assert . ok ( lignes . includes ( '| `src/verger/pommes.js` | 5 | 1 | 0 | 2 | 1 | 1 | 0 |' ) , 'ligne de pommes.js' ) ;
assert . ok ( lignes . includes ( '| frontiere | 3 | 1 | 0 | 2 | 0 | 0 | 0 |' ) , 'ligne de frontiere' ) ;
[ADD] mutation: engine audit command and its versioned written report
Spec 14.13 asks for an audit that shows which engine mutants the tests
kill, written where a reviewer can read it. npm run mutation and make
mutation run the baseline, a coverage pass that stops early when nothing is
measured, then every mutant; survivors are replayed against the long
series. The report holds version, scope, source fingerprint and Node
version only, no duration, host or date, and refuses any other field.
Declared equivalent mutants live in a separate list of key and reason.
Checked: red first; 3612 node tests green from the index alone.
--- FR ---
[ADD] mutation : commande d'audit du moteur, rapport écrit et versionné
Le § 14.13 demande un audit qui montre quels mutants du moteur les épreuves
tuent, écrit là où un relecteur le lit. npm run mutation et make mutation
jouent la ligne de base, une passe de couverture qui s'arrête tôt si rien
n'est mesuré, puis chaque mutant ; les survivants se rejouent contre la
série longue. Le rapport ne porte que version, périmètre, empreinte du
source et version de Node, ni durée, ni hôte, ni date, et refuse tout autre
champ. Les mutants déclarés équivalents vivent dans une liste à part.
Vérifié : rouge d'abord ; 3612 node verts depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:29:08 -04:00
} ) ;
test ( 'l’ en-tête se recopie tel quel quand il ne nomme pas la machine' , ( ) => {
assert . deepEqual ( lu . entete , { ... ENTETE } ) ;
const { markdown } = rediger ( { entete : ENTETE , resultats : RESULTATS } ) ;
for ( const valeur of [ ENTETE . version , ENTETE . empreinte , ENTETE . node , 'src/verger' ] ) {
assert . ok ( markdown . includes ( valeur ) , valeur ) ;
}
} ) ;
} ) ;
describe ( 'rapport de mutation : l’ ordre' , ( ) => {
const { json , markdown } = rediger ( { entete : ENTETE , resultats : RESULTATS } ) ;
const lu = JSON . parse ( json ) ;
test ( 'les survivants couverts d’ abord, puis par fichier, ligne et colonne' , ( ) => {
[IMP] mutation: count long-series kills apart, test the audit end to end
A mutant the node series missed but the long series killed was still
counted as a survivor, so the committed audit overstated what escapes
the tests. It now has its own column: 15 of the 137 survivors move
there, leaving 122; the committed files are regenerated from their own
results, without a rerun. The audit itself now runs in the long series
on a small fixture tree: long-series replay, exit code 0 with
survivors, exit 2 and nothing written when the baseline fails. The
committed audit files are read back and must match what rediger writes.
Checked: 3868 node and 105 long tests from the index alone.
--- FR ---
[IMP] mutation : tués par la série longue à part, audit éprouvé entier
Un mutant que la série node manquait et que la série longue tuait
comptait encore comme survivant : l'audit commis surestimait ce qui
échappe aux épreuves. Il a sa colonne : 15 des 137 survivants y passent,
il en reste 122 ; les fichiers commis sont réengendrés de leurs propres
résultats, sans rejeu. L'audit tourne entier dans la série longue sur un
petit arbre : rejeu long, sortie 0 avec survivants, sortie 2 et rien
d'écrit quand la ligne de base tombe. Les fichiers commis sont relus et
doivent égaler ce que rediger écrit.
Vérifié : 3868 node et 105 longues depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-10 18:51:43 -04:00
assert . deepEqual ( lieux ( lu . survivants ) , [ 'src/verger/poires.js:12:2' , 'src/verger/pommes.js:30:5' , 'src/verger/pommes.js:4:5' ] ) ;
[ADD] mutation: engine audit command and its versioned written report
Spec 14.13 asks for an audit that shows which engine mutants the tests
kill, written where a reviewer can read it. npm run mutation and make
mutation run the baseline, a coverage pass that stops early when nothing is
measured, then every mutant; survivors are replayed against the long
series. The report holds version, scope, source fingerprint and Node
version only, no duration, host or date, and refuses any other field.
Declared equivalent mutants live in a separate list of key and reason.
Checked: red first; 3612 node tests green from the index alone.
--- FR ---
[ADD] mutation : commande d'audit du moteur, rapport écrit et versionné
Le § 14.13 demande un audit qui montre quels mutants du moteur les épreuves
tuent, écrit là où un relecteur le lit. npm run mutation et make mutation
jouent la ligne de base, une passe de couverture qui s'arrête tôt si rien
n'est mesuré, puis chaque mutant ; les survivants se rejouent contre la
série longue. Le rapport ne porte que version, périmètre, empreinte du
source et version de Node, ni durée, ni hôte, ni date, et refuse tout autre
champ. Les mutants déclarés équivalents vivent dans une liste à part.
Vérifié : rouge d'abord ; 3612 node verts depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:29:08 -04:00
assert . deepEqual (
lu . survivants . map ( ( { couverte , longue } ) => [ couverte , longue ] ) ,
[
[ true , null ] ,
[ true , 'survivant' ] ,
[ false , null ] ,
] ,
) ;
} ) ;
[IMP] mutation: count long-series kills apart, test the audit end to end
A mutant the node series missed but the long series killed was still
counted as a survivor, so the committed audit overstated what escapes
the tests. It now has its own column: 15 of the 137 survivors move
there, leaving 122; the committed files are regenerated from their own
results, without a rerun. The audit itself now runs in the long series
on a small fixture tree: long-series replay, exit code 0 with
survivors, exit 2 and nothing written when the baseline fails. The
committed audit files are read back and must match what rediger writes.
Checked: 3868 node and 105 long tests from the index alone.
--- FR ---
[IMP] mutation : tués par la série longue à part, audit éprouvé entier
Un mutant que la série node manquait et que la série longue tuait
comptait encore comme survivant : l'audit commis surestimait ce qui
échappe aux épreuves. Il a sa colonne : 15 des 137 survivants y passent,
il en reste 122 ; les fichiers commis sont réengendrés de leurs propres
résultats, sans rejeu. L'audit tourne entier dans la série longue sur un
petit arbre : rejeu long, sortie 0 avec survivants, sortie 2 et rien
d'écrit quand la ligne de base tombe. Les fichiers commis sont relus et
doivent égaler ce que rediger écrit.
Vérifié : 3868 node et 105 longues depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-10 18:51:43 -04:00
test ( 'un survivant que la série longue tue se liste à part, entre les survivants et les équivalents' , ( ) => {
assert . deepEqual (
lu . tuesSerieLongue . map ( ( r ) => [ ` ${ r . fichier } : ${ r . ligne } : ${ r . colonne } ` , r . couverte , r . longue ] ) ,
[ [ 'src/verger/poires.js:12:5' , true , 'tue' ] ] ,
) ;
const section = ( titre ) => markdown . indexOf ( ` \n ## ${ titre } ` ) ;
const ici = markdown . indexOf ( '`src/verger/poires.js:12:5`' ) ;
assert . ok ( section ( 'Survivants (3)' ) > 0 , 'trois survivants annoncés' ) ;
assert . ok ( section ( 'Tués par la série longue (1)' ) > section ( 'Survivants (3)' ) , 'la section suit les survivants' ) ;
assert . ok ( ici > section ( 'Tués par la série longue (1)' ) && ici < section ( 'Mutants équivalents (1)' ) , 'le mutant est dans sa section' ) ;
assert . equal ( markdown . indexOf ( '`src/verger/poires.js:12:5`' , ici + 1 ) , - 1 , 'et nulle part ailleurs' ) ;
} ) ;
[ADD] mutation: engine audit command and its versioned written report
Spec 14.13 asks for an audit that shows which engine mutants the tests
kill, written where a reviewer can read it. npm run mutation and make
mutation run the baseline, a coverage pass that stops early when nothing is
measured, then every mutant; survivors are replayed against the long
series. The report holds version, scope, source fingerprint and Node
version only, no duration, host or date, and refuses any other field.
Declared equivalent mutants live in a separate list of key and reason.
Checked: red first; 3612 node tests green from the index alone.
--- FR ---
[ADD] mutation : commande d'audit du moteur, rapport écrit et versionné
Le § 14.13 demande un audit qui montre quels mutants du moteur les épreuves
tuent, écrit là où un relecteur le lit. npm run mutation et make mutation
jouent la ligne de base, une passe de couverture qui s'arrête tôt si rien
n'est mesuré, puis chaque mutant ; les survivants se rejouent contre la
série longue. Le rapport ne porte que version, périmètre, empreinte du
source et version de Node, ni durée, ni hôte, ni date, et refuse tout autre
champ. Les mutants déclarés équivalents vivent dans une liste à part.
Vérifié : rouge d'abord ; 3612 node verts depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:29:08 -04:00
test ( 'dans un même fichier, les lignes en ordre numérique : 9 avant 12' , ( ) => {
// L'opérateur de la ligne 12 précède celui de la ligne 9 dans OPERATEURS,
// et « 12 » précède « 9 » en ordre lexical : seul l'ordre numérique des
// lignes met la ligne 9 en tête.
const paire = [
resultat ( mutant ( 'src/verger/pommes.js' , 12 , 'arithmetique' , '+' , '-' ) , 'survivant' ) ,
resultat ( mutant ( 'src/verger/pommes.js' , 9 , 'frontiere' , '<' , '<=' ) , 'survivant' ) ,
] ;
for ( const resultats of [ paire , [ ... paire ] . reverse ( ) ] ) {
const { survivants } = JSON . parse ( rediger ( { entete : ENTETE , resultats } ) . json ) ;
assert . deepEqual ( lieux ( survivants ) , [ 'src/verger/pommes.js:9:5' , 'src/verger/pommes.js:12:5' ] ) ;
}
} ) ;
test ( 'le Markdown range les survivants dans le même ordre, l’ équivalent dans sa section' , ( ) => {
const rangs = lieux ( lu . survivants ) . map ( ( lieu ) => markdown . indexOf ( ` \` ${ lieu } \` ` ) ) ;
assert . ok ( rangs . every ( ( rang ) => rang > 0 ) , 'chaque survivant est nommé' ) ;
assert . deepEqual ( [ ... rangs ] . sort ( ( a , b ) => a - b ) , rangs ) ;
const section = markdown . indexOf ( '## Mutants équivalents' ) ;
assert . ok ( section > rangs . at ( - 1 ) , 'la section des équivalents suit les survivants' ) ;
assert . ok ( markdown . indexOf ( '`src/verger/pommes.js:15:5`' ) > section ) ;
assert . ok ( markdown . includes ( 'le test est toujours vrai à cet endroit' ) ) ;
} ) ;
test ( 'équivalents, délais et erreurs sont listés à part, hors des survivants' , ( ) => {
assert . deepEqual ( lieux ( lu . equivalents ) , [ 'src/verger/pommes.js:15:5' ] ) ;
assert . equal ( lu . equivalents [ 0 ] . raison , 'le test est toujours vrai à cet endroit' ) ;
assert . deepEqual (
lu . delaisEtErreurs . map ( ( r ) => ` ${ r . fichier } : ${ r . ligne } ${ r . issue } ` ) ,
[ 'src/verger/poires.js:20 erreur' , 'src/verger/pommes.js:9 delai' ] ,
) ;
} ) ;
test ( 'l’ ordre des résultats reçus ne change pas un octet de la sortie' , ( ) => {
const renverse = rediger ( { entete : ENTETE , resultats : [ ... RESULTATS ] . reverse ( ) } ) ;
assert . equal ( renverse . markdown , markdown ) ;
assert . equal ( renverse . json , json ) ;
} ) ;
} ) ;
describe ( 'rapport de mutation : ce qu’ il refuse' , ( ) => {
test ( 'des résultats vides ou absents' , ( ) => {
assert . throws ( ( ) => rediger ( { entete : ENTETE , resultats : [ ] } ) , /aucun résultat/ ) ;
assert . throws ( ( ) => rediger ( { entete : ENTETE } ) , /aucun résultat/ ) ;
} ) ;
test ( 'aucune date ni heure dans le texte' , ( ) => {
const { markdown , json } = rediger ( { entete : ENTETE , resultats : RESULTATS } ) ;
for ( const texte of [ markdown , json ] ) {
assert . doesNotMatch ( texte , /\d{4}-\d{2}-\d{2}/ ) ;
assert . doesNotMatch ( texte , /\d{1,2}:\d{2}:\d{2}/ ) ;
assert . doesNotMatch ( texte , /\b(?:date|instant|horodatage)\b/i ) ;
}
} ) ;
test ( 'un en-tête qui nomme la machine : un nom d’ hôte, un chemin de compte' , ( ) => {
const refuse = ( entete ) =>
assert . throws ( ( ) => rediger ( { entete , resultats : RESULTATS } ) , /ne nomme pas sa machine/ , JSON . stringify ( entete ) ) ;
refuse ( { ... ENTETE , hote : 'atelier-ombrelle' } ) ;
refuse ( { ... ENTETE , poste : 'atelier-ombrelle.local' } ) ;
refuse ( { ... ENTETE , node : 'atelier-ombrelle' } ) ;
refuse ( { ... ENTETE , node : '/home/exemple/.local/node/bin/node' } ) ;
refuse ( { ... ENTETE , perimetre : [ '/home/exemple/verger/src/verger' ] } ) ;
refuse ( { ... ENTETE , perimetre : [ 'C:\\Users\\exemple\\verger' ] } ) ;
refuse ( { ... ENTETE , date : 'mardi' } ) ;
refuse ( { ... ENTETE , node : 'v26.0.0-/home/exemple' } ) ;
} ) ;
test ( 'une version de Node avec un suffixe de préversion, sans blanc ni barre oblique' , ( ) => {
for ( const node of [ 'v26.0.0-rc.1' , 'v27.0.0-pre' ] ) {
assert . equal ( JSON . parse ( rediger ( { entete : { ... ENTETE , node } , resultats : RESULTATS } ) . json ) . entete . node , node ) ;
}
for ( const node of [ 'v26.0.0-rc 1' , 'v26.0.0-a/b' , 'v26.0.0-' , 'v26.0.0+rc' ] ) {
assert . throws ( ( ) => rediger ( { entete : { ... ENTETE , node } , resultats : RESULTATS } ) , /en-tête : node/ , node ) ;
}
} ) ;
test ( 'une version que la dérivation de scripts/version.js refuse' , ( ) => {
const [ annee , , , rang ] = VERSION . affichee . split ( '.' ) ;
for ( const version of [ [ annee , '13' , '45' , rang ] . join ( '.' ) , ` ${ VERSION . affichee } 2 ` ] ) {
assert . throws ( ( ) => rediger ( { entete : { ... ENTETE , version } , resultats : RESULTATS } ) , /en-tête : version/ , version ) ;
}
} ) ;
test ( 'un en-tête incomplet ou d’ une autre forme' , ( ) => {
const refuse = ( entete ) => assert . throws ( ( ) => rediger ( { entete , resultats : RESULTATS } ) , TypeError , JSON . stringify ( entete ) ) ;
const { empreinte , ... sansEmpreinte } = ENTETE ;
assert . equal ( empreinte . length , 64 ) ;
refuse ( sansEmpreinte ) ;
refuse ( { ... ENTETE , empreinte : 'b' . repeat ( 63 ) } ) ;
refuse ( { ... ENTETE , version : 'v1' } ) ;
refuse ( { ... ENTETE , perimetre : [ ] } ) ;
refuse ( { ... ENTETE , perimetre : [ 'src/../verger' ] } ) ;
refuse ( null ) ;
} ) ;
test ( 'un résultat d’ une autre forme : issue, opérateur, chemin, équivalence, rejeu, clé' , ( ) => {
const [ premier , second ] = RESULTATS ;
const refuse = ( remplace , motif ) =>
assert . throws ( ( ) => rediger ( { entete : ENTETE , resultats : [ remplace , second ] } ) , motif , JSON . stringify ( remplace ) ) ;
refuse ( { ... premier , issue : 'blesse' } , /issue/ ) ;
refuse ( { ... premier , mutant : { ... premier . mutant , operateur : 'inversion' } } , /opérateur/ ) ;
refuse ( { ... premier , mutant : { ... premier . mutant , fichier : '/home/exemple/verger/pommes.js' } } , /ne nomme pas sa machine/ ) ;
refuse ( { ... premier , mutant : { ... premier . mutant , fichier : '../verger/pommes.js' } } , /chemin/ ) ;
refuse ( { ... premier , mutant : { ... premier . mutant , ligne : 0 } } , /ligne/ ) ;
refuse ( { ... premier , equivalent : 'tué pourtant' } , /équivalent/ ) ;
for ( const equivalent of [ 'vu dans /home/exemple/notes.txt' , 'vu dans C:\\Users\\exemple\\notes.txt' ] ) {
assert . throws (
( ) => rediger ( { entete : ENTETE , resultats : [ { ... RESULTATS [ 2 ] , equivalent } , second ] } ) ,
/ne nomme pas sa machine : chemin de compte dans résultat 0, équivalent/ ,
equivalent ,
) ;
}
refuse ( { ... premier , longue : 'tue' } , /série longue/ ) ;
[IMP] mutation: count long-series kills apart, test the audit end to end
A mutant the node series missed but the long series killed was still
counted as a survivor, so the committed audit overstated what escapes
the tests. It now has its own column: 15 of the 137 survivors move
there, leaving 122; the committed files are regenerated from their own
results, without a rerun. The audit itself now runs in the long series
on a small fixture tree: long-series replay, exit code 0 with
survivors, exit 2 and nothing written when the baseline fails. The
committed audit files are read back and must match what rediger writes.
Checked: 3868 node and 105 long tests from the index alone.
--- FR ---
[IMP] mutation : tués par la série longue à part, audit éprouvé entier
Un mutant que la série node manquait et que la série longue tuait
comptait encore comme survivant : l'audit commis surestimait ce qui
échappe aux épreuves. Il a sa colonne : 15 des 137 survivants y passent,
il en reste 122 ; les fichiers commis sont réengendrés de leurs propres
résultats, sans rejeu. L'audit tourne entier dans la série longue sur un
petit arbre : rejeu long, sortie 0 avec survivants, sortie 2 et rien
d'écrit quand la ligne de base tombe. Les fichiers commis sont relus et
doivent égaler ce que rediger écrit.
Vérifié : 3868 node et 105 longues depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-10 18:51:43 -04:00
assert . throws (
( ) => rediger ( { entete : ENTETE , resultats : [ { ... RESULTATS [ 1 ] , equivalent : 'déclaré à tort' } ] } ) ,
/équivalent/ ,
'un survivant que la série longue tue n’ est pas équivalent' ,
) ;
[ADD] mutation: engine audit command and its versioned written report
Spec 14.13 asks for an audit that shows which engine mutants the tests
kill, written where a reviewer can read it. npm run mutation and make
mutation run the baseline, a coverage pass that stops early when nothing is
measured, then every mutant; survivors are replayed against the long
series. The report holds version, scope, source fingerprint and Node
version only, no duration, host or date, and refuses any other field.
Declared equivalent mutants live in a separate list of key and reason.
Checked: red first; 3612 node tests green from the index alone.
--- FR ---
[ADD] mutation : commande d'audit du moteur, rapport écrit et versionné
Le § 14.13 demande un audit qui montre quels mutants du moteur les épreuves
tuent, écrit là où un relecteur le lit. npm run mutation et make mutation
jouent la ligne de base, une passe de couverture qui s'arrête tôt si rien
n'est mesuré, puis chaque mutant ; les survivants se rejouent contre la
série longue. Le rapport ne porte que version, périmètre, empreinte du
source et version de Node, ni durée, ni hôte, ni date, et refuse tout autre
champ. Les mutants déclarés équivalents vivent dans une liste à part.
Vérifié : rouge d'abord ; 3612 node verts depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:29:08 -04:00
refuse ( { ... premier , couverte : 'oui' } , /couverte/ ) ;
refuse ( { ... premier , mutant : { ... premier . mutant , cle : second . mutant . cle } } , /clé/ ) ;
} ) ;
} ) ;
describe ( 'rapport de mutation : les cellules de code' , ( ) => {
test ( 'une barre verticale s’ échappe, un nœud sur plusieurs lignes tient sur une' , ( ) => {
const resultats = [
resultat ( mutant ( 'src/verger/pommes.js' , 2 , 'logique' , 'a ||\n b' , '((a )&&(\n b))' ) , 'survivant' ) ,
resultat ( mutant ( 'src/verger/pommes.js' , 3 , 'corps-si' , ` { ${ 'x += 1; ' . repeat ( 20 ) } } ` , '{}' ) , 'survivant' ) ,
] ;
const { markdown , json } = rediger ( { entete : ENTETE , resultats } ) ;
const ligne = markdown . split ( '\n' ) . find ( ( l ) => l . includes ( '`src/verger/pommes.js:2:5`' ) ) ;
assert . ok ( ligne . includes ( '`a \\|\\| b`' ) , ligne ) ;
assert . ok ( ligne . includes ( '`((a )&&( b))`' ) , ligne ) ;
const longue = markdown . split ( '\n' ) . find ( ( l ) => l . includes ( '`src/verger/pommes.js:3:5`' ) ) ;
assert . ok ( longue . includes ( '…`' ) , longue ) ;
assert . ok ( longue . length < 200 , longue ) ;
assert . equal ( JSON . parse ( json ) . survivants [ 0 ] . original , 'a ||\n b' ) ;
} ) ;
test ( 'un accent grave dans le code garde la cellule entière' , ( ) => {
const resultats = [ resultat ( mutant ( 'src/verger/pommes.js' , 2 , 'logique' , 'a && `x`' , '((a )||( `x`))' ) , 'survivant' ) ] ;
const { markdown } = rediger ( { entete : ENTETE , resultats } ) ;
assert . ok ( markdown . includes ( '`` a && `x` ``' ) , markdown ) ;
} ) ;
} ) ;
[IMP] mutation: count long-series kills apart, test the audit end to end
A mutant the node series missed but the long series killed was still
counted as a survivor, so the committed audit overstated what escapes
the tests. It now has its own column: 15 of the 137 survivors move
there, leaving 122; the committed files are regenerated from their own
results, without a rerun. The audit itself now runs in the long series
on a small fixture tree: long-series replay, exit code 0 with
survivors, exit 2 and nothing written when the baseline fails. The
committed audit files are read back and must match what rediger writes.
Checked: 3868 node and 105 long tests from the index alone.
--- FR ---
[IMP] mutation : tués par la série longue à part, audit éprouvé entier
Un mutant que la série node manquait et que la série longue tuait
comptait encore comme survivant : l'audit commis surestimait ce qui
échappe aux épreuves. Il a sa colonne : 15 des 137 survivants y passent,
il en reste 122 ; les fichiers commis sont réengendrés de leurs propres
résultats, sans rejeu. L'audit tourne entier dans la série longue sur un
petit arbre : rejeu long, sortie 0 avec survivants, sortie 2 et rien
d'écrit quand la ligne de base tombe. Les fichiers commis sont relus et
doivent égaler ce que rediger écrit.
Vérifié : 3868 node et 105 longues depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-10 18:51:43 -04:00
describe ( 'rapport de mutation : la relecture du JSON écrit' , ( ) => {
const ecrit = rediger ( { entete : ENTETE , resultats : RESULTATS } ) ;
// Le JSON écrit, modifié par changer sur sa forme lue, réécrit comme
// rediger l'écrit.
const modifie = ( changer ) => {
const lu = JSON . parse ( ecrit . json ) ;
changer ( lu ) ;
return ` ${ JSON . stringify ( lu , null , 2 ) } \n ` ;
} ;
test ( 'relire rend le Markdown que rediger a écrit, octet pour octet' , ( ) => {
const relu = relire ( ecrit . json ) ;
assert . equal ( relu . markdown , ecrit . markdown ) ;
assert . deepEqual ( relu . donnees , JSON . parse ( ecrit . json ) ) ;
} ) ;
test ( 'relire refuse un JSON réindenté, un en-tête qui nomme la machine, des comptes contraires aux listes' , ( ) => {
assert . throws ( ( ) => relire ( JSON . stringify ( JSON . parse ( ecrit . json ) ) ) , /forme/ ) ;
assert . throws ( ( ) => relire ( '{' ) , /illisible/ ) ;
assert . throws ( ( ) => relire ( modifie ( ( lu ) => Object . assign ( lu . entete , { hote : 'atelier-ombrelle' } ) ) ) , /ne nomme pas sa machine/ ) ;
assert . throws ( ( ) => relire ( modifie ( ( lu ) => Object . assign ( lu , { format : 2 } ) ) ) , /format/ ) ;
assert . throws ( ( ) => relire ( modifie ( ( lu ) => ( lu . bilan . survivants += 1 ) ) ) , /bilan/ ) ;
assert . throws ( ( ) => relire ( modifie ( ( lu ) => ( lu . parFichier [ 0 ] . tues += 1 ) ) ) , /par fichier/ ) ;
assert . throws ( ( ) => relire ( modifie ( ( lu ) => ( lu . parOperateur [ 0 ] . tues += 1 ) ) ) , /par opérateur/ ) ;
} ) ;
test ( 'relire refuse un survivant que la série longue tue, laissé parmi les survivants' , ( ) => {
// L'ancien compte : le mutant tué par la série longue rangé avec les
// survivants, bilan, fichier et opérateur ajustés pour que seuls les
// listes et la règle du compte le trahissent.
const ancien = modifie ( ( lu ) => {
lu . survivants . splice ( 1 , 0 , ... lu . tuesSerieLongue . splice ( 0 ) ) ;
for ( const compte of [ lu . bilan , lu . parFichier [ 0 ] , lu . parOperateur [ 0 ] ] ) {
compte . survivants += 1 ;
compte . tuesSerieLongue -= 1 ;
}
} ) ;
assert . throws ( ( ) => relire ( ancien ) , /série longue/ ) ;
} ) ;
} ) ;
describe ( 'rapport de mutation : les équivalents déclarés' , ( ) => {
const texte = ( liste ) => ` ${ JSON . stringify ( liste , null , 2 ) } \n ` ;
const CLE = 'src/verger/pommes.js:si-vrai:00000000000000aa:0' ;
test ( 'equivalentsDe rend chaque clé avec sa raison, dans l’ ordre du fichier' , ( ) => {
const lus = equivalentsDe ( texte ( [ { cle : CLE , raison : 'toujours vrai ici' } , { cle : ` ${ CLE } 1 ` , raison : 'idem' } ] ) ) ;
assert . deepEqual ( [ ... lus ] , [
[ CLE , 'toujours vrai ici' ] ,
[ ` ${ CLE } 1 ` , 'idem' ] ,
] ) ;
assert . deepEqual ( [ ... equivalentsDe ( '[]\n' ) ] , [ ] ) ;
} ) ;
test ( 'equivalentsDe refuse un texte illisible, une autre forme, une raison vide, une clé en double, un chemin de compte' , ( ) => {
assert . throws ( ( ) => equivalentsDe ( '[' ) , /illisible/ ) ;
assert . throws ( ( ) => equivalentsDe ( '{}' ) , /liste de \{ cle, raison \}/ ) ;
assert . throws ( ( ) => equivalentsDe ( texte ( [ { cle : CLE } ] ) ) , new RegExp ( ` raison.* ${ CLE } ` ) ) ;
assert . throws ( ( ) => equivalentsDe ( texte ( [ { cle : CLE , raison : '' } ] ) ) , new RegExp ( ` raison.* ${ CLE } ` ) ) ;
assert . throws ( ( ) => equivalentsDe ( texte ( [ { cle : '' , raison : 'x' } ] ) ) , /clé/ ) ;
assert . throws ( ( ) => equivalentsDe ( texte ( [ null ] ) ) , /liste de \{ cle, raison \}/ ) ;
assert . throws (
( ) => equivalentsDe ( texte ( [ { cle : CLE , raison : 'a' } , { cle : CLE , raison : 'b' } ] ) ) ,
new RegExp ( ` en double.* ${ CLE } ` ) ,
) ;
assert . throws ( ( ) => equivalentsDe ( texte ( [ { cle : CLE , raison : 'lu dans /home/exemple/notes' } ] ) ) , /ne nomme pas sa machine/ ) ;
} ) ;
} ) ;
// Les fautes des trois fichiers d'audit/, lus comme du texte : un JSON que
// relire accepte, au périmètre du moteur seul ; le Markdown que sa
// composition rejoue ; les équivalents déclarés, exactement ceux que le JSON
// range à part, chacun avec la même raison. Une liste vide quand tout
// s'accorde.
function fautesAudit ( { markdown , json , equivalents } ) {
let relu ;
try {
relu = relire ( json ) ;
} catch ( erreur ) {
return [ ` mutation-moteur.json : ${ erreur . message } ` ] ;
}
const fautes = [ ] ;
const { perimetre } = relu . donnees . entete ;
if ( perimetre . length !== 1 || perimetre [ 0 ] !== 'src/moteur' ) fautes . push ( ` périmètre ${ perimetre . join ( ', ' ) } : src/moteur seul attendu ` ) ;
if ( markdown !== relu . markdown ) fautes . push ( 'mutation-moteur.md : autre que le Markdown que rediger rejoue' ) ;
let declares ;
try {
declares = equivalentsDe ( equivalents ) ;
} catch ( erreur ) {
return [ ... fautes , ` mutants-equivalents.json : ${ erreur . message } ` ] ;
}
const ranges = new Map ( relu . donnees . equivalents . map ( ( { cle , raison } ) => [ cle , raison ] ) ) ;
for ( const [ cle , raison ] of declares ) {
if ( ! ranges . has ( cle ) ) fautes . push ( ` mutants-equivalents.json : clé inconnue du JSON, ${ cle } ` ) ;
else if ( ranges . get ( cle ) !== raison ) fautes . push ( ` mutants-equivalents.json : raison autre que celle du JSON, ${ cle } ` ) ;
}
for ( const cle of ranges . keys ( ) ) {
if ( ! declares . has ( cle ) ) fautes . push ( ` mutation-moteur.json : équivalent non déclaré, ${ cle } ` ) ;
}
return fautes ;
}
describe ( 'rapport de mutation : audit/ committé (§ 14.13)' , ( ) => {
const lire = ( fichier ) => readFileSync ( join ( RACINE , 'audit' , fichier ) , 'utf8' ) ;
const COMMITTE = Object . freeze ( {
markdown : lire ( 'mutation-moteur.md' ) ,
json : lire ( 'mutation-moteur.json' ) ,
equivalents : lire ( 'mutants-equivalents.json' ) ,
} ) ;
test ( 'audit/ committé : l’ en-tête passe verifierEntete, le Markdown est celui que rediger rejoue, chaque équivalent a une clé unique et une raison non vide, connue du JSON' , ( ) => {
assert . deepEqual ( fautesAudit ( COMMITTE ) , [ ] ) ;
assert . deepEqual ( relire ( COMMITTE . json ) . donnees . entete . perimetre , [ 'src/moteur' ] ) ;
assert . ok ( equivalentsDe ( COMMITTE . equivalents ) . size > 0 , 'des équivalents déclarés' ) ;
} ) ;
test ( 'la garde relève, en mémoire, une raison vidée, une clé inconnue, un Markdown retouché, un périmètre élargi' , ( ) => {
const liste = JSON . parse ( COMMITTE . equivalents ) ;
const premiere = liste [ 0 ] . cle ;
const videe = ` ${ JSON . stringify ( [ { ... liste [ 0 ] , raison : '' } , ...liste.slice(1)], null, 2)} \n ` ;
assert . deepEqual ( fautesAudit ( { ... COMMITTE , equivalents : videe } ) , [
` mutants-equivalents.json : liste de { cle, raison }, raison vide pour ${ premiere } ` ,
] ) ;
const inconnue = 'src/moteur/inventee.js:frontiere:0123456789abcdef:0' ;
const ajoutee = ` ${ JSON . stringify ( [ ... liste , { cle : inconnue , raison : 'une clé que nul audit n’ a rendue' } ], null, 2)} \n ` ;
assert . deepEqual ( fautesAudit ( { ... COMMITTE , equivalents : ajoutee } ) , [ ` mutants-equivalents.json : clé inconnue du JSON, ${ inconnue } ` ] ) ;
assert . deepEqual ( fautesAudit ( { ... COMMITTE , markdown : COMMITTE . markdown . replace ( '## Bilan' , '## Bilan retouché' ) } ) , [
'mutation-moteur.md : autre que le Markdown que rediger rejoue' ,
] ) ;
const elargi = COMMITTE . json . replace ( '"src/moteur"\n' , '"src/moteur",\n "src/application"\n' ) ;
assert . notEqual ( elargi , COMMITTE . json ) ;
assert . deepEqual ( fautesAudit ( { ... COMMITTE , json : elargi } ) , [
'périmètre src/moteur, src/application : src/moteur seul attendu' ,
'mutation-moteur.md : autre que le Markdown que rediger rejoue' ,
] ) ;
} ) ;
} ) ;