[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 résultat écrit de l'audit par mutation (§ 14.13) : rediger compose le
|
[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
|
|
|
|
// Markdown et le JSON que audit.js écrit sous audit/, versionnés ; relire
|
|
|
|
|
|
// recompose le Markdown du seul JSON écrit, par la même composition ;
|
|
|
|
|
|
// equivalentsDe lit audit/mutants-equivalents.json. Fonctions pures et
|
|
|
|
|
|
// déterministes : elles reçoivent des textes, ne lisent aucun fichier ni
|
|
|
|
|
|
// horloge, et la sortie de rediger ne dépend pas de l'ordre des résultats
|
|
|
|
|
|
// reçus.
|
[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
|
|
|
|
//
|
|
|
|
|
|
// Un rapport ne nomme pas sa machine (§ 19.10) : son en-tête est une liste
|
|
|
|
|
|
// fermée de champs, chacun d'une forme stricte — la version, le périmètre en
|
|
|
|
|
|
// chemins relatifs, l'empreinte des sources, la version de Node —, et rien
|
|
|
|
|
|
// n'y porte un nom d'hôte, un chemin de compte ni une date. Les durées ne
|
|
|
|
|
|
// s'y écrivent pas : elles dépendent de la machine. Un en-tête ou un
|
|
|
|
|
|
// résultat d'une autre forme lève, rien n'est écrit.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Les survivants sur une ligne couverte viennent en tête : une épreuve qui
|
|
|
|
|
|
// exécute la ligne sans que la mutation la fasse échouer exécute sans
|
[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
|
|
|
|
// affirmer, ce que l'audit est là pour désigner. Un survivant de la série
|
|
|
|
|
|
// node se rejoue contre les épreuves longues qui atteignent son module avant
|
|
|
|
|
|
// d'être compté : tué par elles, il n'est pas un survivant, et se compte et
|
|
|
|
|
|
// se liste à part, dans sa colonne. Un survivant que
|
[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
|
|
|
|
// audit/mutants-equivalents.json déclare équivalent se compte et se liste à
|
|
|
|
|
|
// part, avec sa raison.
|
|
|
|
|
|
import { deriver } from '../version.js';
|
|
|
|
|
|
import { OPERATEURS } from './mutants.js';
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @typedef {'tue'|'survivant'|'delai'|'erreur'} Issue
|
|
|
|
|
|
* @typedef {Object} Entete
|
|
|
|
|
|
* @property {string} version forme affichée de version.json, que deriver de scripts/version.js admet
|
|
|
|
|
|
* @property {string[]} perimetre dossiers mutés, relatifs à la racine
|
|
|
|
|
|
* @property {string} empreinte SHA-256 hexadécimal des sources du périmètre et de leurs épreuves
|
|
|
|
|
|
* @property {string} node process.version, préversion comprise
|
|
|
|
|
|
* @typedef {Object} Resultat
|
|
|
|
|
|
* @property {import('./mutants.js').Mutant} mutant
|
|
|
|
|
|
* @property {Issue} issue contre la série du périmètre, sous node --test
|
|
|
|
|
|
* @property {boolean} couverte la ligne du mutant est exécutée par cette série
|
|
|
|
|
|
* @property {Issue|null} longue issue du rejeu d'un survivant contre les
|
|
|
|
|
|
* .long.test.js qui atteignent son module ;
|
|
|
|
|
|
* null quand il n'est pas rejoué
|
|
|
|
|
|
* @property {string|null} equivalent la raison, pour un survivant déclaré équivalent
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
const ISSUES = ['tue', 'survivant', 'delai', 'erreur'];
|
|
|
|
|
|
const CHAMPS_ENTETE = ['version', 'perimetre', 'empreinte', 'node'];
|
|
|
|
|
|
|
|
|
|
|
|
// Les champs d'en-tête qui désignent l'hôte, son utilisateur ou un instant :
|
|
|
|
|
|
// leur présence dit pourquoi l'en-tête est refusé. Un nom se lit en
|
|
|
|
|
|
// minuscules, sans tiret ni tiret bas.
|
|
|
|
|
|
const CHAMP_IDENTIFIANT = /host|hote|machine|computer|poste|user|utilisateur|compte|login|date|instant|horodatage|timestamp/;
|
|
|
|
|
|
|
|
|
|
|
|
// Un chemin de compte : le dossier personnel sous Linux (/home/x), sous macOS
|
|
|
|
|
|
// (/Users/x), celui de root, le profil sous Windows (C:\Users\x).
|
|
|
|
|
|
const CHEMIN_DE_COMPTE = /\/(?:home|Users)\/[^/\s]+|\/root(?:\/|$)|[A-Za-z]:[\\/](?:Users|Documents and Settings)[\\/]/i;
|
|
|
|
|
|
|
|
|
|
|
|
const MACHINE = 'un rapport ne nomme pas sa machine';
|
|
|
|
|
|
|
|
|
|
|
|
// La version de Node admet un suffixe de préversion (-rc.1, -pre) fait de
|
|
|
|
|
|
// lettres, de chiffres et de points : ni blanc ni barre oblique, donc ni hôte
|
|
|
|
|
|
// ni chemin.
|
|
|
|
|
|
const FORMES_ENTETE = {
|
|
|
|
|
|
empreinte: [/^[0-9a-f]{64}$/, '64 chiffres hexadécimaux'],
|
|
|
|
|
|
node: [/^v\d+\.\d+\.\d+(?:-[0-9A-Za-z.]+)?$/, 'vX.Y.Z, suivi ou non de -préversion'],
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Un chemin relatif, en barres obliques : ni racine, ni lecteur, ni
|
|
|
|
|
|
// segment « .. », ni blanc.
|
|
|
|
|
|
const estRelatif = (chemin) =>
|
|
|
|
|
|
typeof chemin === 'string' &&
|
|
|
|
|
|
/^[^/\\\s:][^\\\s:]*$/.test(chemin) &&
|
|
|
|
|
|
!chemin.split('/').some((segment) => segment === '..' || segment === '' || segment === '.');
|
|
|
|
|
|
|
|
|
|
|
|
// Lève quand le texte porte un chemin de compte.
|
|
|
|
|
|
function sansCompte(texte, champ) {
|
|
|
|
|
|
if (typeof texte === 'string' && CHEMIN_DE_COMPTE.test(texte)) {
|
|
|
|
|
|
throw new TypeError(`${MACHINE} : chemin de compte dans ${champ}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* L'en-tête validé, recopié dans l'ordre de CHAMPS_ENTETE. Lève une
|
|
|
|
|
|
* TypeError sur un en-tête qui nomme la machine ou n'a pas la forme de
|
|
|
|
|
|
* Entete ; la version se juge par deriver de scripts/version.js, seule
|
|
|
|
|
|
* autorité de sa forme (§ 13.2). audit.js l'appelle avant de mesurer, rediger
|
|
|
|
|
|
* à nouveau avant d'écrire.
|
|
|
|
|
|
* @param {Entete} entete
|
|
|
|
|
|
* @returns {Entete}
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function verifierEntete(entete) {
|
|
|
|
|
|
if (typeof entete !== 'object' || entete === null || Array.isArray(entete)) {
|
|
|
|
|
|
throw new TypeError('en-tête attendu : { version, perimetre, empreinte, node }');
|
|
|
|
|
|
}
|
|
|
|
|
|
const cles = Object.keys(entete).sort();
|
|
|
|
|
|
for (const cle of cles) {
|
|
|
|
|
|
if (CHAMP_IDENTIFIANT.test(cle.toLowerCase().replace(/[-_]/g, ''))) {
|
|
|
|
|
|
throw new TypeError(`${MACHINE} : champ ${cle} refusé`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
for (const cle of cles) {
|
|
|
|
|
|
if (!CHAMPS_ENTETE.includes(cle)) {
|
|
|
|
|
|
throw new TypeError(`${MACHINE} : champ ${cle} hors de l'en-tête (${CHAMPS_ENTETE.join(', ')})`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
const { perimetre } = entete;
|
|
|
|
|
|
if (!Array.isArray(perimetre) || perimetre.length === 0) {
|
|
|
|
|
|
throw new TypeError('en-tête : perimetre, liste non vide de chemins relatifs attendue');
|
|
|
|
|
|
}
|
|
|
|
|
|
for (const dossier of perimetre) {
|
|
|
|
|
|
sansCompte(dossier, 'perimetre');
|
|
|
|
|
|
if (!estRelatif(dossier)) throw new TypeError(`en-tête : perimetre, chemin relatif attendu, reçu ${String(dossier)}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
sansCompte(entete.version, 'version');
|
|
|
|
|
|
try {
|
|
|
|
|
|
deriver(entete.version);
|
|
|
|
|
|
} catch (erreur) {
|
|
|
|
|
|
throw new TypeError(`en-tête : version, ${erreur.message} ; ${MACHINE}, aucun autre texte n'y entre`);
|
|
|
|
|
|
}
|
|
|
|
|
|
for (const [champ, [forme, attendu]] of Object.entries(FORMES_ENTETE)) {
|
|
|
|
|
|
const valeur = entete[champ];
|
|
|
|
|
|
sansCompte(valeur, champ);
|
|
|
|
|
|
if (typeof valeur !== 'string' || !forme.test(valeur)) {
|
|
|
|
|
|
throw new TypeError(`en-tête : ${champ}, ${attendu} attendu ; ${MACHINE}, aucun autre texte n'y entre`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return { version: entete.version, perimetre: [...perimetre], empreinte: entete.empreinte, node: entete.node };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const estEntierPositif = (n) => Number.isInteger(n) && n > 0;
|
|
|
|
|
|
|
|
|
|
|
|
// Le résultat validé, réduit à ce que le rapport écrit.
|
|
|
|
|
|
function verifierResultat(resultat, rang) {
|
|
|
|
|
|
const ici = `résultat ${rang}`;
|
|
|
|
|
|
if (typeof resultat !== 'object' || resultat === null) throw new TypeError(`${ici} : objet attendu`);
|
|
|
|
|
|
const { mutant, issue, couverte, longue, equivalent } = resultat;
|
|
|
|
|
|
if (typeof mutant !== 'object' || mutant === null) throw new TypeError(`${ici} : mutant attendu`);
|
|
|
|
|
|
const { cle, fichier, ligne, colonne, operateur, original, remplacement } = mutant;
|
|
|
|
|
|
if (typeof cle !== 'string' || cle === '') throw new TypeError(`${ici} : clé non vide attendue`);
|
|
|
|
|
|
sansCompte(fichier, `${ici}, fichier`);
|
|
|
|
|
|
sansCompte(cle, `${ici}, clé`);
|
|
|
|
|
|
if (!estRelatif(fichier)) throw new TypeError(`${ici} : chemin relatif attendu, reçu ${String(fichier)}`);
|
|
|
|
|
|
if (!estEntierPositif(ligne)) throw new TypeError(`${ici} : ligne, entier ≥ 1 attendu`);
|
|
|
|
|
|
if (!estEntierPositif(colonne)) throw new TypeError(`${ici} : colonne, entier ≥ 1 attendu`);
|
|
|
|
|
|
if (!OPERATEURS.includes(operateur)) throw new TypeError(`${ici} : opérateur inconnu ${String(operateur)}`);
|
|
|
|
|
|
if (typeof original !== 'string' || typeof remplacement !== 'string') {
|
|
|
|
|
|
throw new TypeError(`${ici} : original et remplacement, deux chaînes attendues`);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!ISSUES.includes(issue)) throw new TypeError(`${ici} : issue inconnue ${String(issue)}`);
|
|
|
|
|
|
if (typeof couverte !== 'boolean') throw new TypeError(`${ici} : couverte, booléen attendu`);
|
|
|
|
|
|
if (longue !== null && !(issue === 'survivant' && ISSUES.includes(longue))) {
|
|
|
|
|
|
throw new TypeError(`${ici} : série longue, une issue pour un survivant seul, null sinon`);
|
|
|
|
|
|
}
|
[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
|
|
|
|
if (equivalent !== null && !(issue === 'survivant' && longue !== 'tue' && typeof equivalent === 'string' && equivalent !== '')) {
|
|
|
|
|
|
throw new TypeError(`${ici} : équivalent, une raison non vide pour un survivant seul, que la série longue ne tue pas ; null sinon`);
|
[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
|
|
|
|
}
|
|
|
|
|
|
sansCompte(equivalent, `${ici}, équivalent`);
|
|
|
|
|
|
return { cle, fichier, ligne, colonne, operateur, original, remplacement, issue, couverte, longue, equivalent };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Comparaison par unités de code : l'ordre ne dépend d'aucune locale.
|
|
|
|
|
|
const comparer = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
|
|
|
|
|
|
|
|
|
|
// Survivants couverts d'abord, puis fichier, ligne, colonne, rang de
|
|
|
|
|
|
// l'opérateur, clé : un ordre total, la clé étant unique.
|
|
|
|
|
|
function comparerLieux(a, b) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
Number(b.couverte) - Number(a.couverte) ||
|
|
|
|
|
|
comparer(a.fichier, b.fichier) ||
|
|
|
|
|
|
a.ligne - b.ligne ||
|
|
|
|
|
|
a.colonne - b.colonne ||
|
|
|
|
|
|
OPERATEURS.indexOf(a.operateur) - OPERATEURS.indexOf(b.operateur) ||
|
|
|
|
|
|
comparer(a.cle, b.cle)
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
[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
|
|
|
|
// La colonne de comptes où tombe un résultat. Un survivant de la série node
|
|
|
|
|
|
// que la série longue tue n'est pas un survivant (§ 14.13) ; un survivant
|
|
|
|
|
|
// qu'elle laisse vivre, ou qu'elle ne rejoue pas, en reste un, ou un
|
|
|
|
|
|
// équivalent déclaré.
|
[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 colonneDe = (r) =>
|
|
|
|
|
|
r.issue === 'tue'
|
|
|
|
|
|
? 'tues'
|
|
|
|
|
|
: r.issue === 'delai'
|
|
|
|
|
|
? 'delais'
|
|
|
|
|
|
: r.issue === 'erreur'
|
|
|
|
|
|
? 'erreurs'
|
[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
|
|
|
|
: r.longue === 'tue'
|
|
|
|
|
|
? 'tuesSerieLongue'
|
|
|
|
|
|
: r.equivalent === null
|
|
|
|
|
|
? 'survivants'
|
|
|
|
|
|
: 'equivalents';
|
[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
|
|
|
|
|
[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
|
|
|
|
// Les colonnes de comptes, dans l'ordre du JSON et des tableaux.
|
|
|
|
|
|
const COLONNES = ['total', 'tues', 'tuesSerieLongue', 'survivants', 'equivalents', 'delais', 'erreurs'];
|
|
|
|
|
|
const compteVide = () => Object.fromEntries(COLONNES.map((colonne) => [colonne, 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
|
|
|
|
|
|
|
|
|
|
function compter(liste) {
|
|
|
|
|
|
const compte = compteVide();
|
|
|
|
|
|
for (const r of liste) {
|
|
|
|
|
|
compte.total += 1;
|
|
|
|
|
|
compte[colonneDe(r)] += 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
return compte;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
[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 ENTETES_COLONNES = '| total | tués | tués par la série longue | survivants | équivalents | délais | erreurs |';
|
|
|
|
|
|
const ALIGNEMENT_COMPTES = COLONNES.map(() => '---:').join('|');
|
[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 ligneComptes = (tete, compte) => `| ${tete === null ? '' : `${tete} | `}${COLONNES.map((c) => compte[c]).join(' | ')} |`;
|
|
|
|
|
|
|
|
|
|
|
|
// Le texte d'un nœud dans une cellule : blancs ramenés à une espace, au plus
|
|
|
|
|
|
// LONGUEUR_CELLULE points de code, « | » échappé pour le tableau, entre des
|
|
|
|
|
|
// accents graves plus nombreux que la plus longue suite qu'il contient.
|
|
|
|
|
|
const LONGUEUR_CELLULE = 60;
|
|
|
|
|
|
function cellule(code) {
|
|
|
|
|
|
const plat = code.replace(/\s+/g, ' ').trim();
|
|
|
|
|
|
if (plat === '') return '(vide)';
|
|
|
|
|
|
const points = [...plat];
|
|
|
|
|
|
const court = points.length > LONGUEUR_CELLULE ? `${points.slice(0, LONGUEUR_CELLULE - 1).join('')}…` : plat;
|
|
|
|
|
|
const echappe = court.replaceAll('|', '\\|');
|
|
|
|
|
|
const suite = Math.max(0, ...[...echappe.matchAll(/`+/g)].map(([s]) => s.length));
|
|
|
|
|
|
const garde = '`'.repeat(suite + 1);
|
|
|
|
|
|
return suite === 0 ? `${garde}${echappe}${garde}` : `${garde} ${echappe} ${garde}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const lieu = (r) => `\`${r.fichier}:${r.ligne}:${r.colonne}\``;
|
|
|
|
|
|
const ISSUE_LUE = { tue: 'tué', survivant: 'survit', delai: 'délai', erreur: 'erreur' };
|
|
|
|
|
|
|
|
|
|
|
|
// Chaque élément d'une liste du JSON, dans l'ordre de ses champs.
|
|
|
|
|
|
const mutantEcrit = (r) => ({
|
|
|
|
|
|
cle: r.cle,
|
|
|
|
|
|
fichier: r.fichier,
|
|
|
|
|
|
ligne: r.ligne,
|
|
|
|
|
|
colonne: r.colonne,
|
|
|
|
|
|
operateur: r.operateur,
|
|
|
|
|
|
original: r.original,
|
|
|
|
|
|
remplacement: r.remplacement,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
[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
|
|
|
|
// Les champs d'une entrée de la liste des survivants, et de celle des tués
|
|
|
|
|
|
// par la série longue.
|
|
|
|
|
|
const vivantEcrit = (r) => ({ ...mutantEcrit(r), couverte: r.couverte, longue: r.longue });
|
|
|
|
|
|
|
|
|
|
|
|
// Une ligne de liste du Markdown : lieu, opérateur, avant, après, puis les
|
|
|
|
|
|
// cellules propres à la liste.
|
|
|
|
|
|
const ligneDeListe = (r, ...suite) =>
|
|
|
|
|
|
`| ${lieu(r)} | ${r.operateur} | ${cellule(r.original)} | ${cellule(r.remplacement)} | ${suite.join(' | ')} |`;
|
|
|
|
|
|
|
|
|
|
|
|
// Une section du Markdown : son titre, puis « Aucun. » ou le tableau.
|
|
|
|
|
|
const section = (titre, introduction, liste, entete, ligne) => [
|
|
|
|
|
|
`## ${titre}`,
|
|
|
|
|
|
'',
|
|
|
|
|
|
...introduction,
|
|
|
|
|
|
...(liste.length === 0
|
|
|
|
|
|
? ['Aucun.']
|
|
|
|
|
|
: [`| lieu | opérateur | avant | après | ${entete.join(' | ')} |`, `|---|---|---|---|${entete.map(() => '---').join('|')}|`, ...liste.map(ligne)]),
|
|
|
|
|
|
'',
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// Le Markdown d'un audit, composé de ses données, celles que le JSON porte :
|
|
|
|
|
|
// rediger et relire en tirent le même texte.
|
|
|
|
|
|
function composer(donnees) {
|
|
|
|
|
|
const { entete: tete, bilan, parFichier, parOperateur, survivants, tuesSerieLongue, equivalents, delaisEtErreurs } = donnees;
|
|
|
|
|
|
const longueLue = (r) => (r.longue === null ? '—' : ISSUE_LUE[r.longue]);
|
|
|
|
|
|
return [
|
|
|
|
|
|
'# Audit par mutation du moteur',
|
|
|
|
|
|
'',
|
|
|
|
|
|
'Écrit par `make mutation` (`scripts/mutation/audit.js`, § 14.13) ; ne se modifie pas à la main.',
|
|
|
|
|
|
'',
|
|
|
|
|
|
'| | |',
|
|
|
|
|
|
'|---|---|',
|
|
|
|
|
|
`| version | ${tete.version} |`,
|
|
|
|
|
|
`| périmètre | ${tete.perimetre.map((d) => `\`${d}\``).join(', ')} |`,
|
|
|
|
|
|
`| sources et épreuves du périmètre (SHA-256) | \`${tete.empreinte}\` |`,
|
|
|
|
|
|
`| Node | ${tete.node} |`,
|
|
|
|
|
|
'',
|
|
|
|
|
|
'Chaque mutant se joue contre les épreuves de la série node qui atteignent son module, sous `node --test`.',
|
|
|
|
|
|
'Un survivant se rejoue contre les épreuves longues qui atteignent son module avant d’être compté : tué par elles, il se compte à part.',
|
|
|
|
|
|
'Un survivant sur une ligne couverte désigne une épreuve qui exécute sans affirmer.',
|
|
|
|
|
|
'',
|
|
|
|
|
|
'## Bilan',
|
|
|
|
|
|
'',
|
|
|
|
|
|
ENTETES_COLONNES.replace('| total', '| mutants'),
|
|
|
|
|
|
`|${ALIGNEMENT_COMPTES}|`,
|
|
|
|
|
|
ligneComptes(null, bilan),
|
|
|
|
|
|
'',
|
|
|
|
|
|
'## Par fichier',
|
|
|
|
|
|
'',
|
|
|
|
|
|
`| fichier ${ENTETES_COLONNES}`,
|
|
|
|
|
|
`|---|${ALIGNEMENT_COMPTES}|`,
|
|
|
|
|
|
...parFichier.map((c) => ligneComptes(`\`${c.fichier}\``, c)),
|
|
|
|
|
|
'',
|
|
|
|
|
|
'## Par opérateur',
|
|
|
|
|
|
'',
|
|
|
|
|
|
`| opérateur ${ENTETES_COLONNES}`,
|
|
|
|
|
|
`|---|${ALIGNEMENT_COMPTES}|`,
|
|
|
|
|
|
...parOperateur.map((c) => ligneComptes(c.operateur, c)),
|
|
|
|
|
|
'',
|
|
|
|
|
|
...section(`Survivants (${survivants.length}), lignes couvertes d’abord`, [], survivants, ['ligne couverte', 'série longue'], (r) =>
|
|
|
|
|
|
ligneDeListe(r, r.couverte ? 'oui' : 'non', longueLue(r)),
|
|
|
|
|
|
),
|
|
|
|
|
|
...section(
|
|
|
|
|
|
`Tués par la série longue (${tuesSerieLongue.length})`,
|
|
|
|
|
|
['Survivants de la série node que les épreuves longues tuent : la série node exécute leur ligne sans l’affirmer.', ''],
|
|
|
|
|
|
tuesSerieLongue,
|
|
|
|
|
|
['ligne couverte'],
|
|
|
|
|
|
(r) => ligneDeListe(r, r.couverte ? 'oui' : 'non'),
|
|
|
|
|
|
),
|
|
|
|
|
|
...section(
|
|
|
|
|
|
`Mutants équivalents (${equivalents.length})`,
|
|
|
|
|
|
['Déclarés dans `audit/mutants-equivalents.json`, avec leur raison.', ''],
|
|
|
|
|
|
equivalents,
|
|
|
|
|
|
['raison'],
|
|
|
|
|
|
(r) => ligneDeListe(r, r.raison.replace(/\s+/g, ' ').replaceAll('|', '\\|')),
|
|
|
|
|
|
),
|
|
|
|
|
|
...section(`Délais et erreurs (${delaisEtErreurs.length})`, [], delaisEtErreurs, ['issue'], (r) => ligneDeListe(r, ISSUE_LUE[r.issue])),
|
|
|
|
|
|
].join('\n');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const enJson = (donnees) => `${JSON.stringify(donnees, null, 2)}\n`;
|
|
|
|
|
|
|
[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
|
|
|
|
/**
|
|
|
|
|
|
* Le rapport d'un audit, en Markdown et en JSON, déterministe.
|
|
|
|
|
|
* Lève sur des résultats vides — un audit qui n'a rien mesuré n'a rien à
|
|
|
|
|
|
* écrire (§ 14.2) —, sur un en-tête qui nomme la machine ou n'a pas la forme
|
|
|
|
|
|
* de Entete, et sur un résultat d'une autre forme que Resultat ou dont la
|
|
|
|
|
|
* clé revient.
|
|
|
|
|
|
* @param {{entete: Entete, resultats: Resultat[]}} demande
|
|
|
|
|
|
* @returns {{markdown: string, json: string}}
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function rediger({ entete, resultats } = {}) {
|
|
|
|
|
|
if (!Array.isArray(resultats) || resultats.length === 0) {
|
|
|
|
|
|
throw new RangeError('aucun résultat : un audit qui ne mesure rien ne s’écrit pas');
|
|
|
|
|
|
}
|
|
|
|
|
|
const tete = verifierEntete(entete);
|
|
|
|
|
|
const lus = resultats.map(verifierResultat).sort(comparerLieux);
|
|
|
|
|
|
const cles = new Set();
|
|
|
|
|
|
for (const r of lus) {
|
|
|
|
|
|
if (cles.has(r.cle)) throw new TypeError(`clé en double : ${r.cle}`);
|
|
|
|
|
|
cles.add(r.cle);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const fichiers = [...new Set(lus.map((r) => r.fichier))].sort(comparer);
|
|
|
|
|
|
const parFichier = fichiers.map((fichier) => ({ fichier, ...compter(lus.filter((r) => r.fichier === fichier)) }));
|
|
|
|
|
|
const parOperateur = OPERATEURS.filter((op) => lus.some((r) => r.operateur === op)).map((operateur) => ({
|
|
|
|
|
|
operateur,
|
|
|
|
|
|
...compter(lus.filter((r) => r.operateur === operateur)),
|
|
|
|
|
|
}));
|
[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 dans = (colonne) => lus.filter((r) => colonneDe(r) === colonne);
|
[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 donnees = {
|
|
|
|
|
|
format: 1,
|
|
|
|
|
|
entete: tete,
|
[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
|
|
|
|
bilan: compter(lus),
|
[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
|
|
|
|
parFichier,
|
|
|
|
|
|
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
|
|
|
|
survivants: dans('survivants').map(vivantEcrit),
|
|
|
|
|
|
tuesSerieLongue: dans('tuesSerieLongue').map(vivantEcrit),
|
|
|
|
|
|
equivalents: dans('equivalents').map((r) => ({ ...mutantEcrit(r), raison: r.equivalent })),
|
|
|
|
|
|
delaisEtErreurs: lus.filter((r) => r.issue === 'delai' || r.issue === 'erreur').map((r) => ({ ...mutantEcrit(r), issue: r.issue })),
|
[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
|
|
|
|
};
|
[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
|
|
|
|
return { markdown: composer(donnees), json: enJson(donnees) };
|
|
|
|
|
|
}
|
[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
|
|
|
|
|
[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
|
|
|
|
// Les clés du JSON écrit, dans son ordre.
|
|
|
|
|
|
const CLES_DONNEES = ['format', 'entete', 'bilan', 'parFichier', 'parOperateur', 'survivants', 'tuesSerieLongue', 'equivalents', 'delaisEtErreurs'];
|
|
|
|
|
|
|
|
|
|
|
|
// Lève quand les comptes d'une ligne ne sont pas ceux de COLONNES, entiers
|
|
|
|
|
|
// positifs ou nuls, dont le total est la somme des autres.
|
|
|
|
|
|
function verifierComptes(compte, ici) {
|
|
|
|
|
|
const ecarts =
|
|
|
|
|
|
compte === null ||
|
|
|
|
|
|
typeof compte !== 'object' ||
|
|
|
|
|
|
COLONNES.some((c) => !Number.isInteger(compte[c]) || compte[c] < 0) ||
|
|
|
|
|
|
COLONNES.slice(1).reduce((somme, c) => somme + compte[c], 0) !== compte.total;
|
|
|
|
|
|
if (ecarts) throw new TypeError(`${ici} : comptes ${COLONNES.join(', ')} attendus, le total égal à la somme des autres`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Lève quand les lignes ne somment pas, colonne par colonne, au bilan.
|
|
|
|
|
|
function verifierSommes(lignes, bilan, ici) {
|
|
|
|
|
|
if (!Array.isArray(lignes)) throw new TypeError(`${ici} : liste attendue`);
|
|
|
|
|
|
lignes.forEach((ligne, rang) => verifierComptes(ligne, `${ici}, ligne ${rang + 1}`));
|
|
|
|
|
|
for (const colonne of COLONNES) {
|
|
|
|
|
|
if (lignes.reduce((somme, ligne) => somme + ligne[colonne], 0) !== bilan[colonne]) {
|
|
|
|
|
|
throw new TypeError(`${ici} : la colonne ${colonne} ne somme pas au bilan`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Relit le JSON qu'a écrit rediger et en recompose le Markdown, par la même
|
|
|
|
|
|
* composition. Lève une TypeError sur un texte que rediger n'écrit pas : une
|
|
|
|
|
|
* autre mise en forme ou d'autres clés, un en-tête que verifierEntete
|
|
|
|
|
|
* refuse, des comptes qui contredisent les listes, ou un survivant que la
|
|
|
|
|
|
* série longue tue, laissé parmi les survivants.
|
|
|
|
|
|
* @param {string} json le texte de audit/mutation-moteur.json
|
|
|
|
|
|
* @returns {{donnees: object, markdown: string}}
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function relire(json) {
|
|
|
|
|
|
let donnees;
|
|
|
|
|
|
try {
|
|
|
|
|
|
donnees = JSON.parse(json);
|
|
|
|
|
|
} catch (erreur) {
|
|
|
|
|
|
throw new TypeError(`JSON illisible : ${erreur.message}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (typeof donnees !== 'object' || donnees === null || enJson(donnees) !== json || Object.keys(donnees).join() !== CLES_DONNEES.join()) {
|
|
|
|
|
|
throw new TypeError(`autre forme que celle que rediger écrit : ${CLES_DONNEES.join(', ')}, indentées de deux espaces`);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (donnees.format !== 1) throw new TypeError(`format ${String(donnees.format)} : 1 attendu`);
|
|
|
|
|
|
const tete = verifierEntete(donnees.entete);
|
|
|
|
|
|
if (enJson(tete) !== enJson(donnees.entete)) throw new TypeError('en-tête : autres champs ou autre ordre que ceux de verifierEntete');
|
|
|
|
|
|
const { bilan, survivants, tuesSerieLongue, equivalents, delaisEtErreurs } = donnees;
|
|
|
|
|
|
verifierComptes(bilan, 'bilan');
|
|
|
|
|
|
verifierSommes(donnees.parFichier, bilan, 'par fichier');
|
|
|
|
|
|
verifierSommes(donnees.parOperateur, bilan, 'par opérateur');
|
|
|
|
|
|
const listes = [
|
|
|
|
|
|
['survivants', survivants, bilan.survivants],
|
|
|
|
|
|
['tuesSerieLongue', tuesSerieLongue, bilan.tuesSerieLongue],
|
|
|
|
|
|
['equivalents', equivalents, bilan.equivalents],
|
|
|
|
|
|
['delaisEtErreurs', delaisEtErreurs, bilan.delais + bilan.erreurs],
|
[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
|
|
|
|
];
|
[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
|
|
|
|
for (const [nom, liste, compte] of listes) {
|
|
|
|
|
|
if (!Array.isArray(liste) || liste.length !== compte) throw new TypeError(`bilan : ${compte} attendus dans ${nom}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
const lieuLu = (r) => `${r.fichier}:${r.ligne}:${r.colonne}`;
|
|
|
|
|
|
const tueParLaLongue = survivants.find((r) => r.longue === 'tue');
|
|
|
|
|
|
if (tueParLaLongue !== undefined) {
|
|
|
|
|
|
throw new TypeError(`un survivant que la série longue tue, laissé parmi les survivants : ${lieuLu(tueParLaLongue)}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
const autre = tuesSerieLongue.find((r) => r.longue !== 'tue');
|
|
|
|
|
|
if (autre !== undefined) throw new TypeError(`tuesSerieLongue : un mutant que la série longue ne tue pas, ${lieuLu(autre)}`);
|
|
|
|
|
|
return { donnees, markdown: composer(donnees) };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const FORME_EQUIVALENTS = 'liste de { cle, raison }';
|
[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
|
|
|
|
|
[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
|
|
|
|
/**
|
|
|
|
|
|
* Les équivalences déclarées dans audit/mutants-equivalents.json : chaque
|
|
|
|
|
|
* clé vers sa raison, dans l'ordre du fichier. Lève une TypeError sur un
|
|
|
|
|
|
* texte illisible, une autre forme qu'une liste de { cle, raison } — deux
|
|
|
|
|
|
* chaînes non vides —, une clé en double, et une raison qui porte un chemin
|
|
|
|
|
|
* de compte, que rediger refuserait après l'audit entier.
|
|
|
|
|
|
* @param {string} texte
|
|
|
|
|
|
* @returns {Map<string, string>}
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function equivalentsDe(texte) {
|
|
|
|
|
|
let liste;
|
|
|
|
|
|
try {
|
|
|
|
|
|
liste = JSON.parse(texte);
|
|
|
|
|
|
} catch (erreur) {
|
|
|
|
|
|
throw new TypeError(`illisible : ${erreur.message}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Array.isArray(liste)) throw new TypeError(`${FORME_EQUIVALENTS} attendue`);
|
|
|
|
|
|
const equivalents = new Map();
|
|
|
|
|
|
liste.forEach((entree, rang) => {
|
|
|
|
|
|
if (typeof entree !== 'object' || entree === null || Array.isArray(entree)) throw new TypeError(`${FORME_EQUIVALENTS} attendue, rang ${rang + 1}`);
|
|
|
|
|
|
const { cle, raison } = entree;
|
|
|
|
|
|
if (typeof cle !== 'string' || cle === '') throw new TypeError(`${FORME_EQUIVALENTS}, clé vide ou absente au rang ${rang + 1}`);
|
|
|
|
|
|
if (typeof raison !== 'string' || raison === '') throw new TypeError(`${FORME_EQUIVALENTS}, raison vide pour ${cle}`);
|
|
|
|
|
|
if (equivalents.has(cle)) throw new TypeError(`${FORME_EQUIVALENTS}, clé en double : ${cle}`);
|
|
|
|
|
|
sansCompte(raison, `la raison de ${cle}`);
|
|
|
|
|
|
equivalents.set(cle, raison);
|
|
|
|
|
|
});
|
|
|
|
|
|
return equivalents;
|
[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
|
|
|
|
}
|