// innboks-relations.mjs // Step 6: deterministisk relasjons-resolusjon innen KJOERINGENS konsept-sett // (lukket lenke-vokabular). For hvert konsept oppdages referanser til ANDRE // emitterte konsepter (exact-title substring, case-insensitivt) og emitteres som // leading-'/' bundle-root-lenker bygd paa malet-konseptets destRel (satt i Step 5, // kjent FOER skriv -- Pass-2 ordering-fiks). Asserterer zero-dangling mot de // emitterte destRel-stiene og at hver generert lenke er trygg (delt okf-links- // allow-list). v1-narrowing: kun innen-kjoering-settet -- lenking til pre- // eksisterende tre-konsepter er v1.1. Zero npm dependencies. import { isSafeBundleLink } from './okf-links.mjs'; const REL_HEADING = '## Relaterte dokumenter'; // concepts (beriket av projectFrontmatter, m/ destRel) -> samme konsepter m/ // relations[] + body utvidet med en deterministisk relasjons-seksjon. export function resolveRelations(concepts) { const emitted = new Set(concepts.map((c) => c.destRel)); return concepts.map((concept) => { const haystack = `${concept.title}\n${concept.body}`.toLowerCase(); const found = new Map(); // target -> { title, target, destRel } for (const other of concepts) { if (other === concept) continue; if (!other.title || !other.destRel) continue; if (!haystack.includes(other.title.toLowerCase())) continue; const target = `/${other.destRel}`; if (!isSafeBundleLink(target)) { throw new Error(`innboks-relations: utrygg generert lenke ${target}`); } if (!emitted.has(other.destRel)) { throw new Error(`innboks-relations: dangling relasjon ${other.destRel}`); } found.set(target, { title: other.title, target, destRel: other.destRel }); } const relations = [...found.values()].sort((a, b) => a.target.localeCompare(b.target)); let body = concept.body; if (relations.length > 0) { const links = relations.map((r) => `- [${r.title}](${r.target})`).join('\n'); body = `${concept.body}\n\n${REL_HEADING}\n\n${links}\n`; } return { ...concept, relations, body }; }); }