Files
brain/tools/graph-radar.mjs
T

45 lines
2.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* graph-radar (§5.7 ДР-3) — файл-уровень проверки полноты: тронул X → граф связывает
* с Y → адресован ли Y? Чистое ядро (граф инъектируется, формат project-graph B).
* Сосед, который сам в touchedFiles, не флагуется (уже адресован).
*/
/** F-F2: нормализация пути для сравнения — backslash → '/', strip './'-префикс. */
function normPath(p) {
if (typeof p !== 'string') return '';
let s = p.replace(/\\/g, '/');
while (s.startsWith('./')) s = s.slice(2);
return s;
}
export function buildGraphRadar({ touchedFiles, graph } = {}) {
const touched = (Array.isArray(touchedFiles) ? touchedFiles : []).map(normPath).filter(Boolean);
const nodes = graph && Array.isArray(graph.nodes) ? graph.nodes : [];
const links = graph && Array.isArray(graph.links) ? graph.links : [];
const touchedSet = new Set(touched);
// id → узел (для source_file соседа)
const byId = new Map(nodes.map((n) => [n && n.id, n]).filter(([id]) => id));
// id тронутых узлов (сравнение по нормализованному пути — F-F2)
const touchedIds = new Set(nodes.filter((n) => n && touchedSet.has(normPath(n.source_file))).map((n) => n.id));
const seen = new Set();
const neighbors = [];
for (const l of links) {
if (!l || typeof l !== 'object') continue;
for (const [a, b] of [[l.source, l.target], [l.target, l.source]]) {
if (!touchedIds.has(a)) continue; // a — тронутый конец
const nb = byId.get(b);
if (!nb) continue;
if (touchedSet.has(normPath(nb.source_file))) continue; // сосед сам тронут → адресован
const key = `${b}<-${a}`;
if (seen.has(key)) continue;
seen.add(key);
// F-F4 (L2-пол): элемент заморожен — машинная мутация addressed бросает TypeError;
// отметка «адресовано» живёт ВНЕ структуры (наставник/владелец).
neighbors.push(Object.freeze({ node: b, label: nb.label ?? null, source_file: nb.source_file ?? null, viaTouched: a, addressed: false }));
}
}
// F-F1: touchedFiles (подано) рядом с touchedNodes (найдено) — слепота радара видна:
// touchedFiles>0 ∧ touchedNodes=0 ⇒ «радар слеп» (сигнал-всегда, зеркало F-C6/F-C2-3).
return { neighbors, summary: { touchedFiles: touched.length, touchedNodes: touchedIds.size, unaddressed: neighbors.length } };
}