From bf0e463b4a19de75b5c25c6da7481a8ed4f51707 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Fri, 26 Jun 2026 14:39:46 +0200 Subject: [PATCH] =?UTF-8?q?feat(okr):=20retrieval-SKILL=20m/=20OKF-m=C3=B8?= =?UTF-8?q?nster=20+=20fixtures=20(SC1,SC4)=20[skip-docs]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- skills/okr-second-brain-search/SKILL.md | 148 ++++++++++++++++++ tests/fixtures/okf-minimal/index.md | 5 + tests/fixtures/okf-minimal/tildelingsbrev.md | 12 ++ .../okf-realistic/dokumenter/index.md | 3 + .../okf-realistic/dokumenter/notat.md | 12 ++ .../fixtures/okf-realistic/historikk/index.md | 4 + .../historikk/retrospektiv-T3-2025.md | 12 ++ tests/fixtures/okf-realistic/index.md | 8 + .../strategisk-kontekst/index.md | 5 + .../organisasjonsprofil.md | 12 ++ .../tildelingsbrev-2026.md | 13 ++ .../strategisk-kontekst/virksomhetsplan.md | 12 ++ .../okf-realistic/syklus/T1-2026/index.md | 5 + .../syklus/T1-2026/okr-digitalisering.md | 12 ++ .../syklus/T1-2026/okr-trafikksikkerhet.md | 14 ++ .../okf-realistic/syklus/T1-2026/status.md | 12 ++ tests/okf-retrieval.test.mjs | 131 ++++++++++++++++ 17 files changed, 420 insertions(+) create mode 100644 skills/okr-second-brain-search/SKILL.md create mode 100644 tests/fixtures/okf-minimal/index.md create mode 100644 tests/fixtures/okf-minimal/tildelingsbrev.md create mode 100644 tests/fixtures/okf-realistic/dokumenter/index.md create mode 100644 tests/fixtures/okf-realistic/dokumenter/notat.md create mode 100644 tests/fixtures/okf-realistic/historikk/index.md create mode 100644 tests/fixtures/okf-realistic/historikk/retrospektiv-T3-2025.md create mode 100644 tests/fixtures/okf-realistic/index.md create mode 100644 tests/fixtures/okf-realistic/strategisk-kontekst/index.md create mode 100644 tests/fixtures/okf-realistic/strategisk-kontekst/organisasjonsprofil.md create mode 100644 tests/fixtures/okf-realistic/strategisk-kontekst/tildelingsbrev-2026.md create mode 100644 tests/fixtures/okf-realistic/strategisk-kontekst/virksomhetsplan.md create mode 100644 tests/fixtures/okf-realistic/syklus/T1-2026/index.md create mode 100644 tests/fixtures/okf-realistic/syklus/T1-2026/okr-digitalisering.md create mode 100644 tests/fixtures/okf-realistic/syklus/T1-2026/okr-trafikksikkerhet.md create mode 100644 tests/fixtures/okf-realistic/syklus/T1-2026/status.md create mode 100644 tests/okf-retrieval.test.mjs diff --git a/skills/okr-second-brain-search/SKILL.md b/skills/okr-second-brain-search/SKILL.md new file mode 100644 index 0000000..f8a3fa1 --- /dev/null +++ b/skills/okr-second-brain-search/SKILL.md @@ -0,0 +1,148 @@ +--- +name: okr-second-brain-search +description: >- + Search the user's personal OKR/organization "second brain" — an OKF-compatible + markdown wiki under .claude/okr/ (project) and ~/.claude/okr/org/ (home) — to + retrieve the right strategic, governance, or cycle context on demand, in free + chat and during /okr:* commands, without pre-injecting everything. Use whenever + the user refers to their own goals, tildelingsbrev, strategy, steering signals, + or a previous cycle and the answer likely lives in their wiki rather than the + prompt. Triggers on: "våre mål", "overordnede mål", "mål dette tertialet", + "tertialmål", "tildelingsbrev", "hva sier OKR-ene våre", "forrige syklus", + "strategi", "styringssignaler". +version: "1.6.0" +--- + +# OKR Second-Brain Search + +Retrieve the *right* personal/organizational OKR context at the *right* moment by +searching a user-owned, OKF-compatible markdown wiki with native tools only — +**Glob, Read, Grep**. No MCP, no search engine, no pre-injection. This is the +on-demand counterpart to the `inject-okr-context` hook: the hook emits only a +tiny pointer; this skill does the actual retrieval when the conversation needs it. + +## When to use + +Activate when the user references their own goals, governance documents, strategy, +or prior cycles — in free chat **or** under an `/okr:*` command — and the answer +plausibly lives in their wiki rather than in the prompt. Typical Norwegian cues +are listed in `Triggers on:` above ("våre mål", "tildelingsbrev", "forrige +syklus", "styringssignaler", …). Do **not** activate for generic OKR methodology +questions — those belong to the `okr-offentlig-sektor` skill. + +## The two bundle roots + +The second brain lives in **two roots with different lifecycles**; always search +both, project first: + +1. **Project bundle** — `.claude/okr/` in the current working directory + (cycle/work data, cwd-bound): `strategisk-kontekst/`, `syklus//`, + `historikk/`, `dokumenter/`. +2. **Home bundle** — `~/.claude/okr/org/` (organization identity, survives + reinstall). + +Each root carries its own `index.md` per level and an `okf_version` marker on its +root `index.md`. Project content overrides home content on conflict +(most-specific-wins, mirroring the hook's resolution). + +## OKF layout (what you are searching) + +The wiki follows the Knowledge Catalog **"Documents / `kb` Layout"** ("Metadata as +Code"). Each **concept file** carries YAML frontmatter in this field order: + +```yaml +--- +type: Tildelingsbrev # Title-Case human string; the only de-facto required field +resource: https://… # URL/path to the source +title: … +description: … +tags: # YAML list (multi-line) — optional +- styring +timestamp: '2026-01-15T09:00:00+00:00' # ISO-8601, quoted +--- +# Heading +…body… +``` + +`type` values you will encounter: `Tildelingsbrev`, `OKR`, `Retrospektiv`, +`Organisasjonsprofil`, `Virksomhetsplan`, `Status` (and occasionally others — +treat unknown types as valid, never error on them). + +Each level has an **`index.md`** with **no frontmatter**, formatted as a heading +plus one bullet per concept file: + +``` +# Heading +* [title](relative.md) - description +``` + +The `index.md` is the curated table of contents for its level — use it to +navigate and to break ranking ties (below). + +## Retrieval procedure + +Adopt the OKF `kb-search` triad (`list_contents` / `read_file` / `search_content` +≡ Glob / Read / Grep) together with the `discovery` quality pattern (semantic +decomposition). Concretely: + +1. **Locate & navigate.** Glob both roots — `.claude/okr/**/*.md` and + `~/.claude/okr/org/**/*.md`. Read each level's `index.md` first to understand + what exists before reading any concept file. +2. **Semantic decomposition** — *do not* grep the user's words verbatim. Generate + **up to 3 query variations** and search all of them: + - **(i) Direct + synonyms** — the literal term plus close synonyms + (e.g. "mål" → "mål", "objective", "OKR"). + - **(ii) Domain translation** — map everyday phrasing to the domain/governance + vocabulary actually written in the files (e.g. "hva må vi levere" → + "tildelingsbrev", "styringssignal"; "forrige runde" → "retrospektiv", + "historikk"). + - **(iii) Broader category** — fall back to the `type:` or the containing level + (e.g. search `type: OKR` under `syklus/`, or `type: Tildelingsbrev` under + `strategisk-kontekst/`). +3. **Grep** each variation across the concept files (token in body/frontmatter, + or `type:` for category queries). Exclude `index.md` from concept matches. +4. **Merge, dedup, and rank.** Combine the hits from all variations, dedup by + path, then rank by this **deterministic tie-break**: + - **+2** if the token appears in the file's **frontmatter** (`title`/`type`/`tags`). + - **+1** if the token appears in the file's entry in the nearest **`index.md`**. + - On equal score, the file listed **earlier in its `index.md`** wins; final ties + break by path order. + This is exactly the ranking the regression test (`tests/okf-retrieval.test.mjs`) + pins: a frontmatter/index match outranks a body-only match, so a shared token + resolves to the concept that *names* it rather than one that merely mentions it. +5. **Read only the top concept file(s)** — usually one. Do not bulk-read the tree; + the point is selective retrieval. Cite the file path you used. + +### Robustness + +- **Dangling links** in an `index.md` (a bullet pointing at a missing file): skip + silently, never fail. +- **Unknown `type:`**: accept it; rank and read as normal. +- **Empty/greenfield wiki**: if neither root exists or has concept files, say so + briefly and proceed from the prompt — never fabricate wiki content. + +## Trigger smoke-test (Assumption 1 — manual) + +Skill activation is model-judged, not unit-testable. Verify it manually: + +- **`/doctor`** — confirm `okr-second-brain-search` is listed and its + `description` is not trimmed by the skill-listing budget. If trimmed, raise + `skillListingBudgetFraction` (the `Triggers on:` phrases are placed early in the + description precisely so the highest-signal cues survive trimming). +- **Free-chat probe** — with a populated `.claude/okr/`, send a prompt with **no** + `/okr:*` command, e.g. *"Hva sier tildelingsbrevet vårt om mål dette tertialet?"* + The skill should activate and retrieve from `strategisk-kontekst/` and + `syklus//` without being told where to look. +- **Fallback** — if activation does not fire, the `inject-okr-context` hook still + emits a deterministic pointer to the resolved `index.md`, and any `/okr:*` + command can invoke this skill explicitly. Retrieval degrades, it does not break. + +## Resources + +- **Wiki data (project):** `.claude/okr/` — `strategisk-kontekst/`, `syklus//`, + `historikk/`, `dokumenter/`, each with an `index.md`. +- **Wiki data (home):** `~/.claude/okr/org/` — organization profile bundle. +- **Index/check tooling:** `scripts/okf-index.mjs` (regenerate `index.md` per root) + and `scripts/okf-check.mjs` (validate every concept file has `type:`). +- **Companion skill:** `okr-offentlig-sektor` for OKR methodology (writing, + scoring, cascading) — this skill only *retrieves* the user's own context. diff --git a/tests/fixtures/okf-minimal/index.md b/tests/fixtures/okf-minimal/index.md new file mode 100644 index 0000000..1294af6 --- /dev/null +++ b/tests/fixtures/okf-minimal/index.md @@ -0,0 +1,5 @@ +# OKF second brain (minimal testfixtur) + +okf_version: kb-layout-2026-06 + +* [Tildelingsbrev](tildelingsbrev.md) - Styringssignal med unik testtoken. diff --git a/tests/fixtures/okf-minimal/tildelingsbrev.md b/tests/fixtures/okf-minimal/tildelingsbrev.md new file mode 100644 index 0000000..552f230 --- /dev/null +++ b/tests/fixtures/okf-minimal/tildelingsbrev.md @@ -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. diff --git a/tests/fixtures/okf-realistic/dokumenter/index.md b/tests/fixtures/okf-realistic/dokumenter/index.md new file mode 100644 index 0000000..0acb5b3 --- /dev/null +++ b/tests/fixtures/okf-realistic/dokumenter/index.md @@ -0,0 +1,3 @@ +# Dokumenter + +* [Arbeidsnotat](notat.md) - Internt arbeidsnotat med ukjent type. diff --git a/tests/fixtures/okf-realistic/dokumenter/notat.md b/tests/fixtures/okf-realistic/dokumenter/notat.md new file mode 100644 index 0000000..444b712 --- /dev/null +++ b/tests/fixtures/okf-realistic/dokumenter/notat.md @@ -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. diff --git a/tests/fixtures/okf-realistic/historikk/index.md b/tests/fixtures/okf-realistic/historikk/index.md new file mode 100644 index 0000000..a65fc07 --- /dev/null +++ b/tests/fixtures/okf-realistic/historikk/index.md @@ -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). diff --git a/tests/fixtures/okf-realistic/historikk/retrospektiv-T3-2025.md b/tests/fixtures/okf-realistic/historikk/retrospektiv-T3-2025.md new file mode 100644 index 0000000..6d9fedd --- /dev/null +++ b/tests/fixtures/okf-realistic/historikk/retrospektiv-T3-2025.md @@ -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. diff --git a/tests/fixtures/okf-realistic/index.md b/tests/fixtures/okf-realistic/index.md new file mode 100644 index 0000000..5198037 --- /dev/null +++ b/tests/fixtures/okf-realistic/index.md @@ -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. diff --git a/tests/fixtures/okf-realistic/strategisk-kontekst/index.md b/tests/fixtures/okf-realistic/strategisk-kontekst/index.md new file mode 100644 index 0000000..da01fb4 --- /dev/null +++ b/tests/fixtures/okf-realistic/strategisk-kontekst/index.md @@ -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. diff --git a/tests/fixtures/okf-realistic/strategisk-kontekst/organisasjonsprofil.md b/tests/fixtures/okf-realistic/strategisk-kontekst/organisasjonsprofil.md new file mode 100644 index 0000000..7ca506b --- /dev/null +++ b/tests/fixtures/okf-realistic/strategisk-kontekst/organisasjonsprofil.md @@ -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. diff --git a/tests/fixtures/okf-realistic/strategisk-kontekst/tildelingsbrev-2026.md b/tests/fixtures/okf-realistic/strategisk-kontekst/tildelingsbrev-2026.md new file mode 100644 index 0000000..9eeb1dc --- /dev/null +++ b/tests/fixtures/okf-realistic/strategisk-kontekst/tildelingsbrev-2026.md @@ -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. diff --git a/tests/fixtures/okf-realistic/strategisk-kontekst/virksomhetsplan.md b/tests/fixtures/okf-realistic/strategisk-kontekst/virksomhetsplan.md new file mode 100644 index 0000000..2e754c1 --- /dev/null +++ b/tests/fixtures/okf-realistic/strategisk-kontekst/virksomhetsplan.md @@ -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. diff --git a/tests/fixtures/okf-realistic/syklus/T1-2026/index.md b/tests/fixtures/okf-realistic/syklus/T1-2026/index.md new file mode 100644 index 0000000..9a38f21 --- /dev/null +++ b/tests/fixtures/okf-realistic/syklus/T1-2026/index.md @@ -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. diff --git a/tests/fixtures/okf-realistic/syklus/T1-2026/okr-digitalisering.md b/tests/fixtures/okf-realistic/syklus/T1-2026/okr-digitalisering.md new file mode 100644 index 0000000..9673590 --- /dev/null +++ b/tests/fixtures/okf-realistic/syklus/T1-2026/okr-digitalisering.md @@ -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. diff --git a/tests/fixtures/okf-realistic/syklus/T1-2026/okr-trafikksikkerhet.md b/tests/fixtures/okf-realistic/syklus/T1-2026/okr-trafikksikkerhet.md new file mode 100644 index 0000000..b5ca7f9 --- /dev/null +++ b/tests/fixtures/okf-realistic/syklus/T1-2026/okr-trafikksikkerhet.md @@ -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. diff --git a/tests/fixtures/okf-realistic/syklus/T1-2026/status.md b/tests/fixtures/okf-realistic/syklus/T1-2026/status.md new file mode 100644 index 0000000..7bab58d --- /dev/null +++ b/tests/fixtures/okf-realistic/syklus/T1-2026/status.md @@ -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. diff --git a/tests/okf-retrieval.test.mjs b/tests/okf-retrieval.test.mjs new file mode 100644 index 0000000..ae90de3 --- /dev/null +++ b/tests/okf-retrieval.test.mjs @@ -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 , 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'); +});