fix(okr): gate-herding B2 scoped strict + B3 alle lenkeformer + M2 realpath-confinement + M3 deriveType basename/ord-grense

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-07-16 20:12:35 +02:00
commit 7f9c790f43
6 changed files with 222 additions and 18 deletions

View file

@ -10,6 +10,8 @@
// jf. Pass-2 ordering-fiks (Revisions #21). Serialiserer via writeFrontmatter
// (additiv array-gren fra Step 3). Zero npm dependencies.
import path from 'node:path';
import { writeFrontmatter } from './frontmatter.mjs';
import { snapType, snapTags, routeLevel, TYPE_VOCAB, TAGS_VOCAB } from './okf-vocab.mjs';
@ -18,11 +20,21 @@ const DESCRIPTION_MAX = 240;
// Vokab sortert lengst-foerst saa "Overordnede OKR" matcher foer "OKR".
const TYPE_BY_LENGTH = [...TYPE_VOCAB].sort((a, b) => b.length - a.length);
// Regel-utledning: foerste vokab-term som forekommer i title+sti -> kanonisk type.
// M3 (A1): vokab-term matcher kun som HELT ord (ikke substring -- kompound som
// «Statusnotat» skal ikke snappe til Status). Ord-grense = ikke-bokstav/-siffer
// paa begge sider (\b haandterer ikke ae/oe/aa -- derfor \p{L}\p{N}-lookaround).
function termMatches(hay, term) {
const esc = term.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
return new RegExp(`(?<![\\p{L}\\p{N}])${esc}(?![\\p{L}\\p{N}])`, 'u').test(hay);
}
// Regel-utledning: foerste vokab-term (lengst foerst) som forekommer som helt
// ord i title + BASENAME av kilden -> kanonisk type. Full sti deltar ALDRI
// (M3: et /okr/-katalogsegment skal ikke forgifte routeLevel-rutingen).
function deriveType(title, sourcePath) {
const hay = `${title} ${sourcePath}`.toLowerCase();
const hay = `${title} ${path.basename(String(sourcePath))}`.toLowerCase();
for (const t of TYPE_BY_LENGTH) {
if (hay.includes(t.toLowerCase())) return snapType(t);
if (termMatches(hay, t)) return snapType(t);
}
return snapType('');
}