397777089e
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
45 lines
2.7 KiB
JavaScript
45 lines
2.7 KiB
JavaScript
#!/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 } };
|
||
}
|