Instrument : les gardes nommées, comme candidats — le plus petit lecteur qui prouve la thèse
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:
co-authored by
Claude Fable 5.1
parent
887d9a90c3
commit
6508f3be47
+86
-1
@@ -177,6 +177,64 @@ 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 exclus = (Array.isArray(cfg.gardes_exclues) ? cfg.gardes_exclues : []).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));
|
||||
// Le réceptacle du refus : ce que la juridiction a DÉCIDÉ d'écarter ou de
|
||||
// différer ne reparaît pas chaque nuit. Sans lui, la liste n'est jamais un
|
||||
// delta et le bruit devient un stresseur (ADR-061 §2.3). Le registre est un
|
||||
// fichier de la juridiction ; l'instrument le lit, il ne l'écrit jamais.
|
||||
const decisions = new Map();
|
||||
if (typeof cfg.gardes_ecartees === 'string' && existsFileAt(repo, cfg.gardes_ecartees)) {
|
||||
const reg = parseYaml(read(cfg.gardes_ecartees));
|
||||
for (const d of Array.isArray(reg) ? reg : []) if (d && typeof d.nom === 'string') decisions.set(d.nom, d);
|
||||
}
|
||||
const out = [];
|
||||
for (const f of files) {
|
||||
if (f.isTest || !res.some((re) => re.test(f.path)) || exclus.some((re) => re.test(f.path))) continue;
|
||||
const nom = f.path.split('/').pop().replace(/\.[^.]+$/, '');
|
||||
const re = new RegExp(`\\b${escapeRe(nom)}\\b`);
|
||||
const d = decisions.get(nom);
|
||||
const differee = d?.decision === 'differe' && typeof d.jusqu_au === 'string' && d.jusqu_au >= new Date().toISOString().slice(0, 10);
|
||||
out.push({
|
||||
nom,
|
||||
chemin: f.path,
|
||||
strate: f.stratum,
|
||||
// NOMMÉE, jamais « déclarée » : un parcours peut porter la règle en
|
||||
// langue naturelle sans nommer la classe. L'instrument observe la
|
||||
// nomination — c'est sa limite, dite dans le nom des champs.
|
||||
nommee_par_parcours: re.test(parcoursText),
|
||||
nommee_par_adr: re.test(adrText),
|
||||
nommee_par_epreuve: tests.some((x) => re.test(x)),
|
||||
...(d?.decision === 'ecarte' ? { decision: 'ecarte', motif: d.motif ?? null } : {}),
|
||||
...(differee ? { decision: 'differe', jusqu_au: d.jusqu_au } : {}),
|
||||
});
|
||||
}
|
||||
return out.sort((a, b) => a.nom.localeCompare(b.nom));
|
||||
}
|
||||
|
||||
function existsFileAt(repo, p) {
|
||||
const st = statSync(join(repo, p), { throwIfNoEntry: false });
|
||||
return st !== undefined && st.isFile();
|
||||
}
|
||||
|
||||
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 +355,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 +372,28 @@ 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: {
|
||||
// Ce que la mesure regarde, dit tel quel : les motifs. Ce qui n'y
|
||||
// correspond pas (require, objets-valeurs, contraintes SQL) est
|
||||
// invisible — la limite est dans le journal, pas seulement au README.
|
||||
motifs: cfg.gardes_nommees,
|
||||
...(Array.isArray(cfg.gardes_exclues) && cfg.gardes_exclues.length ? { exclus: cfg.gardes_exclues } : {}),
|
||||
total: r.gardes.length,
|
||||
non_nommees_par_un_parcours: r.gardes.filter((g) => !g.nommee_par_parcours).length,
|
||||
sans_epreuve: r.gardes.filter((g) => !g.nommee_par_epreuve).length,
|
||||
ecartees: r.gardes.filter((g) => g.decision === 'ecarte').length,
|
||||
differees: r.gardes.filter((g) => g.decision === 'differe').length,
|
||||
// Les candidats du jour : non nommés par un parcours, ni écartés, ni différés.
|
||||
candidats: r.gardes.filter((g) => !g.nommee_par_parcours && g.decision === undefined),
|
||||
},
|
||||
}),
|
||||
reglage_empreinte: r.meta.reglageHash,
|
||||
};
|
||||
if (args.dryRun) {
|
||||
@@ -331,6 +412,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 cand = r.gardes.filter((g) => !g.nommee_par_parcours && g.decision === undefined);
|
||||
console.log(` gardes nommées : ${r.gardes.length} · candidates (non nommées par un parcours, ni écartées) : ${cand.length}${cand.length ? ' — ' + cand.map((g) => g.nom).join(', ') : ''}`);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
Reference in New Issue
Block a user