feat(okr): retrieval-SKILL m/ OKF-mønster + fixtures (SC1,SC4) [skip-docs]

This commit is contained in:
Kjell Tore Guttormsen 2026-06-26 14:39:46 +02:00
commit bf0e463b4a
17 changed files with 420 additions and 0 deletions

5
tests/fixtures/okf-minimal/index.md vendored Normal file
View file

@ -0,0 +1,5 @@
# OKF second brain (minimal testfixtur)
okf_version: kb-layout-2026-06
* [Tildelingsbrev](tildelingsbrev.md) - Styringssignal med unik testtoken.

View file

@ -0,0 +1,12 @@
---
type: Tildelingsbrev
resource: https://example.no/min/tildelingsbrev
title: Minimalt tildelingsbrev
description: Et minimalt OKF-konsept med en unik token.
tags:
- styring
timestamp: '2026-01-10T08:00:00+00:00'
---
# Tildelingsbrev
Dette dokumentet inneholder den unike testtokenen kvikkleireskred.

View file

@ -0,0 +1,3 @@
# Dokumenter
* [Arbeidsnotat](notat.md) - Internt arbeidsnotat med ukjent type.

View file

@ -0,0 +1,12 @@
---
type: Notat
resource: local
title: Arbeidsnotat
description: Internt arbeidsnotat (ukjent OKF-type for robusthetstest).
tags:
- internt
timestamp: '2026-03-01T12:00:00+00:00'
---
# Arbeidsnotat
Dette arbeidsnotat har en type som ikke er i det kjente OKR-settet.

View file

@ -0,0 +1,4 @@
# Historikk
* [Retrospektiv T3-2025](retrospektiv-T3-2025.md) - Laeringssloeyfe fra forrige syklus.
* [Retrospektiv T2-2025](retrospektiv-T2-2025.md) - Mangler med vilje (dangling-test).

View file

@ -0,0 +1,12 @@
---
type: Retrospektiv
resource: local
title: Retrospektiv T3-2025
description: Laering fra forrige syklus.
tags:
- retro
timestamp: '2025-12-20T14:00:00+00:00'
---
# Retrospektiv T3-2025
Viktigste laeringssloeyfe: tettere oppfoelging av KR-data.

8
tests/fixtures/okf-realistic/index.md vendored Normal file
View file

@ -0,0 +1,8 @@
# OKF second brain - Vegdirektoratet (realistisk testfixtur)
okf_version: kb-layout-2026-06
* [Strategisk kontekst](strategisk-kontekst/index.md) - Tildelingsbrev, virksomhetsplan og profil.
* [Syklus T1-2026](syklus/T1-2026/index.md) - Aktive OKR og statusrapport.
* [Historikk](historikk/index.md) - Arkiverte sykluser og retrospektiv.
* [Dokumenter](dokumenter/index.md) - Lose arbeidsdokumenter.

View file

@ -0,0 +1,5 @@
# Strategisk kontekst
* [Tildelingsbrev 2026](tildelingsbrev-2026.md) - Klimaomstilling og sikkerhet.
* [Virksomhetsplan 2026](virksomhetsplan.md) - Kompetanseloeft og effektivisering.
* [Organisasjonsprofil](organisasjonsprofil.md) - Vegdirektoratet, offentlig sektor.

View file

@ -0,0 +1,12 @@
---
type: Organisasjonsprofil
resource: local
title: Organisasjonsprofil
description: Identitet og sektor for organisasjonen.
tags:
- profil
timestamp: '2026-01-05T08:00:00+00:00'
---
# Organisasjonsprofil
Organisasjonen er Vegdirektoratet, en etat i offentlig sektor.

View file

@ -0,0 +1,13 @@
---
type: Tildelingsbrev
resource: https://example.no/tildelingsbrev-2026
title: Tildelingsbrev 2026
description: Departementets styringssignaler for budsjettaaret 2026.
tags:
- styring
- klima
timestamp: '2026-01-15T09:00:00+00:00'
---
# Tildelingsbrev 2026
Hovedprioriteten for 2026 er klimaomstilling i transportsektoren.

View file

@ -0,0 +1,12 @@
---
type: Virksomhetsplan
resource: https://example.no/virksomhetsplan-2026
title: Virksomhetsplan 2026
description: Intern operasjonalisering av tildelingsbrevet.
tags:
- plan
timestamp: '2026-01-20T10:00:00+00:00'
---
# Virksomhetsplan 2026
Et sentralt tiltak er et kompetanseloeft for digital forvaltning.

View file

@ -0,0 +1,5 @@
# Syklus T1-2026
* [Trafikksikkerhet og tunnelsikkerhet](okr-trafikksikkerhet.md) - Maal om nullvisjon i tunneler.
* [Digitalisering av tjenester](okr-digitalisering.md) - Oekt selvbetjeningsgrad.
* [Statusrapport T1](status.md) - Fremdriftsindikator for tertialet.

View file

@ -0,0 +1,12 @@
---
type: OKR
resource: local
title: Digitalisering av tjenester
description: Tertialmaal for digital selvbetjening.
tags:
- digital
timestamp: '2026-02-01T09:05:00+00:00'
---
# Digitalisering av tjenester
Objective: Oeke selvbetjeningsgrad i publikumstjenester.

View file

@ -0,0 +1,14 @@
---
type: OKR
resource: local
title: Trafikksikkerhet og tunnelsikkerhet
description: Tertialmaal for sikrere veier og tunneler.
tags:
- sikkerhet
- tunnel
timestamp: '2026-02-01T09:00:00+00:00'
---
# Trafikksikkerhet og tunnelsikkerhet
Objective: Styrke tunnelsikkerhet i tertialet.
KR1: Naa nullvisjon for alvorlige tunnelhendelser.

View file

@ -0,0 +1,12 @@
---
type: Status
resource: local
title: Statusrapport T1-2026
description: Fremdrift for innevaerende tertial.
tags:
- status
timestamp: '2026-04-30T16:00:00+00:00'
---
# Statusrapport T1-2026
Fremdriftsindikator viser god progresjon. Arbeidet med tunnelsikkerhet ligger noe bak skjema.

View file

@ -0,0 +1,131 @@
// okf-retrieval.test.mjs
// Beviser retrieval-MEKANISMEN for okr-second-brain-search SKILLen: native
// Glob/Read/Grep-seamen mot et OKF-kompatibelt bundle (fixture = parameterisert
// bundle-rot). Tester IKKE modell-trigger (Assumption 1 -> royktest i SKILL-body).
// Referanse-implementasjonen speiler SKILLens dokumenterte retrieval-algoritme;
// semantisk dekomponering (synonym-bridging) er modell-jobb og utelatt her.
// Zero npm deps. Plassert i tests/ (samme monster som org-profile-write.test.mjs).
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync, readdirSync, existsSync } from 'node:fs';
import { join, dirname, basename } from 'node:path';
import { fileURLToPath } from 'node:url';
import { parseFrontmatter } from '../lib/frontmatter.mjs';
const HERE = dirname(fileURLToPath(import.meta.url));
const MINIMAL = join(HERE, 'fixtures', 'okf-minimal');
const REALISTIC = join(HERE, 'fixtures', 'okf-realistic');
// --- Reference retrieval (native-tool seam: Glob + Read + Grep + rank) ---
// Glob: alle konsept-filer (.md unntatt index.md) under en bundle-rot, rekursivt.
function globConcepts(root) {
const out = [];
const walk = (dir) => {
for (const e of readdirSync(dir, { withFileTypes: true })) {
const p = join(dir, e.name);
if (e.isDirectory()) walk(p);
else if (e.isFile() && e.name.endsWith('.md') && e.name !== 'index.md') out.push(p);
}
};
walk(root);
return out;
}
// Naermeste index.md-linje som lenker til <file>, ellers null. Kaster aldri.
function indexEntry(file) {
const idx = join(dirname(file), 'index.md');
if (!existsSync(idx)) return { line: null, order: Number.MAX_SAFE_INTEGER };
const base = basename(file);
const lines = readFileSync(idx, 'utf8').split('\n').filter((l) => l.trimStart().startsWith('*'));
for (let i = 0; i < lines.length; i++) {
if (lines[i].includes(`](${base})`)) return { line: lines[i], order: i };
}
return { line: null, order: Number.MAX_SAFE_INTEGER };
}
// Grep + rank: konsept-filer som inneholder token, rangert etter dokumentert
// tie-break: +2 token i frontmatter, +1 token i nivaaets index.md-entry; ekte
// tie -> index-rekkefolge; siste tie -> sti-sortering (deterministisk).
function retrieve(root, token) {
const lc = token.toLowerCase();
const hits = [];
for (const f of globConcepts(root)) {
const content = readFileSync(f, 'utf8');
if (!content.toLowerCase().includes(lc)) continue;
const { raw } = parseFrontmatter(content);
const inFm = raw !== null && raw.toLowerCase().includes(lc);
const { line, order } = indexEntry(f);
const inIdx = line !== null && line.toLowerCase().includes(lc);
hits.push({ path: f, score: (inFm ? 2 : 0) + (inIdx ? 1 : 0), order });
}
hits.sort((a, b) => b.score - a.score || a.order - b.order || a.path.localeCompare(b.path));
return hits;
}
// Les index.md-lenker, returner kun de som finnes paa disk (dangling droppes, kaster ikke).
function resolveIndexLinks(indexPath) {
const dir = dirname(indexPath);
const links = [];
for (const l of readFileSync(indexPath, 'utf8').split('\n')) {
const m = l.match(/\]\(([^)]+)\)/);
if (m) links.push(m[1]);
}
return links.filter((relPath) => existsSync(join(dir, relPath)));
}
const rel = (root, p) => p.slice(root.length + 1).split('\\').join('/');
// (a) MEKANISME: unik token (excl. index.md) -> forventet sti.
test('mekanisme: unik token resolver til forventet konsept-fil (okf-minimal)', () => {
const hits = retrieve(MINIMAL, 'kvikkleireskred');
assert.equal(hits.length, 1, 'unik token skal treffe noyaktig en konsept-fil');
assert.equal(rel(MINIMAL, hits[0].path), 'tildelingsbrev.md');
});
// (b) BRUKERVERDI-PROXY: query -> token -> konsept-tabell mot okf-realistic.
test('brukerverdi: token-tabell resolver hver til forventet konsept (okf-realistic)', () => {
const table = [
{ token: 'klimaomstilling', expect: 'strategisk-kontekst/tildelingsbrev-2026.md' },
{ token: 'kompetanseloeft', expect: 'strategisk-kontekst/virksomhetsplan.md' },
{ token: 'Vegdirektoratet', expect: 'strategisk-kontekst/organisasjonsprofil.md' },
{ token: 'selvbetjeningsgrad', expect: 'syklus/T1-2026/okr-digitalisering.md' },
{ token: 'nullvisjon', expect: 'syklus/T1-2026/okr-trafikksikkerhet.md' },
{ token: 'fremdriftsindikator', expect: 'syklus/T1-2026/status.md' },
{ token: 'laeringssloeyfe', expect: 'historikk/retrospektiv-T3-2025.md' },
{ token: 'arbeidsnotat', expect: 'dokumenter/notat.md' },
];
for (const { token, expect } of table) {
const hits = retrieve(REALISTIC, token);
assert.ok(hits.length >= 1, `token «${token}» skal gi minst ett treff`);
assert.equal(rel(REALISTIC, hits[0].path), expect, `token «${token}» -> ${expect}`);
}
});
// (c) DISAMBIGUERING (Pass 2): delt token mellom 2 filer -> index.md-routing/ranking
// velger riktig topp-treff (frontmatter+index-entry tie-break).
test('disambiguering: delt token rangerer frontmatter/index-treff over body-treff', () => {
const hits = retrieve(REALISTIC, 'tunnelsikkerhet');
assert.equal(hits.length, 2, 'delt token skal treffe begge filer');
assert.equal(rel(REALISTIC, hits[0].path), 'syklus/T1-2026/okr-trafikksikkerhet.md',
'frontmatter+index-entry-treff skal rangeres over body-only-treff');
assert.equal(rel(REALISTIC, hits[1].path), 'syklus/T1-2026/status.md');
assert.ok(hits[0].score > hits[1].score, 'topp-treff skal ha hoyere score (deterministisk tie-break)');
});
// (d) ROBUSTHET: dangling-link + ukjent-type kaster ikke.
test('robusthet: dangling index-link droppes uten kast', () => {
const histIdx = join(REALISTIC, 'historikk', 'index.md');
const resolved = resolveIndexLinks(histIdx);
assert.ok(resolved.includes('retrospektiv-T3-2025.md'), 'eksisterende lenke beholdes');
assert.ok(!resolved.some((r) => r.includes('T2-2025')), 'dangling lenke (T2-2025) droppes');
});
test('robusthet: ukjent type parses uten kast, retrieval treffer fortsatt', () => {
const notat = join(REALISTIC, 'dokumenter', 'notat.md');
const { get } = parseFrontmatter(readFileSync(notat, 'utf8'));
assert.equal(get('type'), 'Notat', 'ukjent type leses raatt uten kast');
const hits = retrieve(REALISTIC, 'arbeidsnotat');
assert.equal(rel(REALISTIC, hits[0].path), 'dokumenter/notat.md');
});