Merge pull request 'chore(glossaire): auditer la convergence effective du graphe' (#348) from chore/audit-convergence-effective-glossaire into main
Reviewed-on: #348
This commit was merged in pull request #348.
This commit is contained in:
@@ -5,29 +5,43 @@ import yaml from "js-yaml";
|
|||||||
const ROOT = "src/content/glossaire";
|
const ROOT = "src/content/glossaire";
|
||||||
const DEFAULTS_FILE = "src/lib/glossary-navigation-defaults.ts";
|
const DEFAULTS_FILE = "src/lib/glossary-navigation-defaults.ts";
|
||||||
const HUB_LIMIT = 5;
|
const HUB_LIMIT = 5;
|
||||||
|
const EFFECTIVE_TOP_LIMIT = 10;
|
||||||
|
const PATH_KEYS = ["understand", "deepen", "compare", "apply"];
|
||||||
|
|
||||||
const defaultsRaw = fs.readFileSync(DEFAULTS_FILE, "utf-8");
|
const defaultsRaw = fs.readFileSync(DEFAULTS_FILE, "utf-8");
|
||||||
|
|
||||||
const defaultFamilies = new Set(
|
const defaultFamilies = new Set(
|
||||||
[...defaultsRaw.matchAll(/^\s{4}"?([a-z0-9-]+)"?\s*:/gm)].map((m) => m[1]),
|
[...defaultsRaw.matchAll(/^\s{4}"?([a-z0-9-]+)"?\s*:/gm)].map((m) => m[1]),
|
||||||
);
|
);
|
||||||
|
|
||||||
const defaultPathKeysByFamily = new Map();
|
const defaultPathKeysByFamily = new Map();
|
||||||
|
const defaultTargetsByFamily = new Map();
|
||||||
for (const match of defaultsRaw.matchAll(/^\s{4}"?([a-z0-9-]+)"?\s*:\s*\{([\s\S]*?)^\s{4}\},/gm)) {
|
|
||||||
const family = match[1];
|
for (const match of defaultsRaw.matchAll(
|
||||||
const body = match[2];
|
/^\s{4}"?([a-z0-9-]+)"?\s*:\s*\{([\s\S]*?)^\s{4}\},/gm,
|
||||||
const keys = new Set();
|
)) {
|
||||||
|
const family = match[1];
|
||||||
for (const key of ["understand", "deepen", "compare", "apply"]) {
|
const body = match[2];
|
||||||
if (new RegExp(`\\b${key}\\s*:\\s*\\[[^\\]]+\\]`).test(body)) {
|
const keys = new Set();
|
||||||
keys.add(key);
|
const targetsByKey = new Map();
|
||||||
}
|
|
||||||
}
|
for (const key of PATH_KEYS) {
|
||||||
|
const pathMatch = body.match(new RegExp(`\\b${key}\\s*:\\s*\\[([^\\]]*)\\]`));
|
||||||
defaultPathKeysByFamily.set(family, keys);
|
const targets = pathMatch
|
||||||
|
? pathMatch[1]
|
||||||
|
.split(",")
|
||||||
|
.map((x) => x.trim().replace(/^["']|["']$/g, ""))
|
||||||
|
.filter(Boolean)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
if (targets.length > 0) keys.add(key);
|
||||||
|
targetsByKey.set(key, targets);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defaultPathKeysByFamily.set(family, keys);
|
||||||
|
defaultTargetsByFamily.set(family, targetsByKey);
|
||||||
|
}
|
||||||
|
|
||||||
const files = fs.readdirSync(ROOT).filter((f) => f.endsWith(".md"));
|
const files = fs.readdirSync(ROOT).filter((f) => f.endsWith(".md"));
|
||||||
const slugs = new Set(files.map((f) => f.replace(".md", "")));
|
const slugs = new Set(files.map((f) => f.replace(".md", "")));
|
||||||
|
|
||||||
@@ -58,6 +72,19 @@ const edges = {};
|
|||||||
const incoming = {};
|
const incoming = {};
|
||||||
const families = new Set();
|
const families = new Set();
|
||||||
|
|
||||||
|
const effectiveOutgoing = new Map();
|
||||||
|
const effectiveIncoming = new Map();
|
||||||
|
|
||||||
|
function addEffectiveEdge(from, to) {
|
||||||
|
if (!from || !to || from === to || !slugs.has(to)) return;
|
||||||
|
|
||||||
|
if (!effectiveOutgoing.has(from)) effectiveOutgoing.set(from, new Set());
|
||||||
|
if (!effectiveIncoming.has(to)) effectiveIncoming.set(to, new Set());
|
||||||
|
|
||||||
|
effectiveOutgoing.get(from).add(to);
|
||||||
|
effectiveIncoming.get(to).add(from);
|
||||||
|
}
|
||||||
|
|
||||||
for (const { slug, data, noFrontmatter } of entries) {
|
for (const { slug, data, noFrontmatter } of entries) {
|
||||||
if (noFrontmatter) continue;
|
if (noFrontmatter) continue;
|
||||||
|
|
||||||
@@ -75,6 +102,7 @@ for (const { slug, data, noFrontmatter } of entries) {
|
|||||||
if (next) {
|
if (next) {
|
||||||
edges[slug] = next;
|
edges[slug] = next;
|
||||||
incoming[next] = (incoming[next] || 0) + 1;
|
incoming[next] = (incoming[next] || 0) + 1;
|
||||||
|
addEffectiveEdge(slug, next);
|
||||||
|
|
||||||
if (next === slug) selfLoops.push(slug);
|
if (next === slug) selfLoops.push(slug);
|
||||||
if (!slugs.has(next)) deadPrimaryNext.push(`${slug} → ${next}`);
|
if (!slugs.has(next)) deadPrimaryNext.push(`${slug} → ${next}`);
|
||||||
@@ -84,14 +112,24 @@ for (const { slug, data, noFrontmatter } of entries) {
|
|||||||
|
|
||||||
const explicitPaths = nav.paths || {};
|
const explicitPaths = nav.paths || {};
|
||||||
const familyDefaults = defaultPathKeysByFamily.get(data.family) || new Set();
|
const familyDefaults = defaultPathKeysByFamily.get(data.family) || new Set();
|
||||||
|
const familyDefaultTargets = defaultTargetsByFamily.get(data.family) || new Map();
|
||||||
|
|
||||||
const pathCount = ["understand", "deepen", "compare", "apply"].filter((key) => {
|
const pathCount = PATH_KEYS.filter((key) => {
|
||||||
const explicit = Array.isArray(explicitPaths[key]) && explicitPaths[key].length > 0;
|
const explicit = Array.isArray(explicitPaths[key]) && explicitPaths[key].length > 0;
|
||||||
const fromDefault = familyDefaults.has(key);
|
const fromDefault = familyDefaults.has(key);
|
||||||
return explicit || fromDefault;
|
return explicit || fromDefault;
|
||||||
}).length;
|
}).length;
|
||||||
|
|
||||||
if (pathCount < 2) weakPaths.push(slug);
|
if (pathCount < 2) weakPaths.push(slug);
|
||||||
|
|
||||||
|
for (const key of PATH_KEYS) {
|
||||||
|
const explicitTargets = Array.isArray(explicitPaths[key]) ? explicitPaths[key] : [];
|
||||||
|
const defaultTargets = familyDefaultTargets.get(key) || [];
|
||||||
|
|
||||||
|
for (const target of [...explicitTargets, ...defaultTargets]) {
|
||||||
|
addEffectiveEdge(slug, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const seenPairs = new Set();
|
const seenPairs = new Set();
|
||||||
@@ -161,6 +199,24 @@ if (selfLoops.length) {
|
|||||||
selfLoops.forEach((s) => console.log(" -", s));
|
selfLoops.forEach((s) => console.log(" -", s));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`\n📊 Effective convergence top ${EFFECTIVE_TOP_LIMIT}:`);
|
||||||
|
[...effectiveIncoming.entries()]
|
||||||
|
.map(([slug, sources]) => [slug, sources.size])
|
||||||
|
.sort((a, b) => b[1] - a[1])
|
||||||
|
.slice(0, EFFECTIVE_TOP_LIMIT)
|
||||||
|
.forEach(([slug, n]) => {
|
||||||
|
console.log(` ${n} ${slug}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`\n📊 Effective branching top ${EFFECTIVE_TOP_LIMIT}:`);
|
||||||
|
[...effectiveOutgoing.entries()]
|
||||||
|
.map(([slug, targets]) => [slug, targets.size])
|
||||||
|
.sort((a, b) => b[1] - a[1])
|
||||||
|
.slice(0, EFFECTIVE_TOP_LIMIT)
|
||||||
|
.forEach(([slug, n]) => {
|
||||||
|
console.log(` ${n} ${slug}`);
|
||||||
|
});
|
||||||
|
|
||||||
const hardFailures =
|
const hardFailures =
|
||||||
missingNavigation.length +
|
missingNavigation.length +
|
||||||
directCycles.length +
|
directCycles.length +
|
||||||
|
|||||||
Reference in New Issue
Block a user