Instrument : les gardes nommées, comme candidats — le plus petit lecteur qui prouve la thèse
gardes du contrat / conformite (pull_request) Successful in 17s

Une exception de domaine est une règle qui a un nom, un throw, presque
toujours une épreuve, et souvent aucune déclaration. Si le réglage le
demande (gardes_nommees, adr), le journal rend les candidats qu'aucun
parcours ne cite, avec leur strate et ce qui les cite. Rien ne devient un
constat, aucun état ne change de sens, la section n'existe pas sans les
clés : additif, ignorable.

Ce qu'un dessin de trois lecteurs (Java annoté, SQL, réconciliation)
voulait démontrer, abattu par l'objecteur mandaté, ces quarante lignes
le démontrent : huit exceptions de domaine chez openathle, zéro déclarée.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
temoin-agent
2026-09-21 15:25:16 +02:00
co-authored by Claude Fable 5.1
parent 887d9a90c3
commit ecf13fe1da
7 changed files with 118 additions and 1 deletions
+55 -1
View File
@@ -177,6 +177,42 @@ function scanCitations(repo, files, ids) {
return hits;
}
/**
* Les GARDES NOMMÉES — le plus petit instrument qui prouve que le code tient
* des règles que nul n'a déclarées. Une exception de domaine a un nom, un
* `throw`, presque toujours une épreuve — et souvent aucune déclaration.
* L'instrument la rend comme CANDIDAT, jamais comme constat : il mesure une
* existence, il ne déclare rien, il ne renomme aucun état. Déclarer ou non
* reste à la juridiction (P4 : l'humain écrit ce que la règle signifie).
* Absente du réglage (`gardes_nommees`), la section n'existe pas — rien ne
* change pour qui ne l'a pas demandée.
*/
function namedGuards(repo, cfg, files) {
if (!Array.isArray(cfg.gardes_nommees) || cfg.gardes_nommees.length === 0) return null;
const res = cfg.gardes_nommees.map(globToRegExp);
const read = (p) => readFileSync(join(repo, p), 'utf8');
const mdUnder = (dir) =>
typeof dir === 'string' && existsSync(join(repo, dir)) ? walk(repo, join(repo, dir), []).filter((p) => p.endsWith('.md')) : [];
const parcoursText = mdUnder(cfg.parcours).map(read).join('\n');
const adrText = mdUnder(cfg.adr).map(read).join('\n');
const tests = files.filter((f) => f.isTest).map((f) => read(f.path));
const out = [];
for (const f of files) {
if (f.isTest || !res.some((re) => re.test(f.path))) continue;
const nom = f.path.split('/').pop().replace(/\.[^.]+$/, '');
const re = new RegExp(`\\b${escapeRe(nom)}\\b`);
out.push({
nom,
chemin: f.path,
strate: f.stratum,
dans_parcours: re.test(parcoursText),
dans_adr: re.test(adrText),
eprouve: tests.some((x) => re.test(x)),
});
}
return out.sort((a, b) => a.nom.localeCompare(b.nom));
}
function judge(hits, cfg) {
const order = cfg.strates.map((s) => s.nom);
const strata = [...new Set(hits.filter((h) => h.isTest).map((h) => h.stratum))]
@@ -297,7 +333,8 @@ function main() {
if (last.get(inv.id) === key) continue;
fresh.push(constatItem(cfg, inv, verdict, hits.get(inv.id), (counts.get(inv.id) ?? 0) + 1, meta));
}
emit(args, cfg, { journeys, invariants: invariants.size, tally, fresh, meta, sortiePath });
const gardes = namedGuards(args.repo, cfg, files);
emit(args, cfg, { journeys, invariants: invariants.size, tally, fresh, meta, sortiePath, gardes });
}
function emit(args, cfg, r) {
@@ -313,6 +350,19 @@ function emit(args, cfg, r) {
: {}),
etats: r.tally,
constats_nouveaux: r.fresh.length,
// Les candidats vont au journal, réécrit à chaque course : ce n'est pas un
// constat (rien n'est mesuré sur un invariant), c'est un inventaire du jour.
// Seuls ceux qu'aucun parcours ne cite sont listés — lisibilité (P9) ; le
// total dit combien de gardes nommées le code porte.
...(r.gardes === null
? {}
: {
gardes_nommees: {
total: r.gardes.length,
sans_declaration: r.gardes.filter((g) => !g.dans_parcours).length,
candidats: r.gardes.filter((g) => !g.dans_parcours),
},
}),
reglage_empreinte: r.meta.reglageHash,
};
if (args.dryRun) {
@@ -331,6 +381,10 @@ function emit(args, cfg, r) {
console.log(` ${r.invariants} invariant(s) déclaré(s) par ${r.journeys} parcours`);
console.log(` à son étage : ${r.tally.domicilie} · renforcé : ${r.tally.renforce} · hors de son étage : ${r.tally.mal_domicilie} · jamais éprouvé : ${r.tally.aucun_locus}`);
console.log(` constat(s) nouveau(x) : ${r.fresh.length}${args.dryRun ? '' : ` → ${cfg.sortie}`}`);
if (r.gardes !== null) {
const sans = r.gardes.filter((g) => !g.dans_parcours);
console.log(` gardes nommées : ${r.gardes.length}, dont ${sans.length} qu'aucun parcours ne déclare${sans.length ? ' — ' + sans.map((g) => g.nom).join(', ') : ''}`);
}
}
main();