feat(okr): retrieval-SKILL m/ OKF-mønster + fixtures (SC1,SC4) [skip-docs]
This commit is contained in:
parent
9b85c0b436
commit
bf0e463b4a
17 changed files with 420 additions and 0 deletions
148
skills/okr-second-brain-search/SKILL.md
Normal file
148
skills/okr-second-brain-search/SKILL.md
Normal file
|
|
@ -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/<id>/`,
|
||||||
|
`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/<id>/` 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/<id>/`,
|
||||||
|
`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.
|
||||||
5
tests/fixtures/okf-minimal/index.md
vendored
Normal file
5
tests/fixtures/okf-minimal/index.md
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# OKF second brain (minimal testfixtur)
|
||||||
|
|
||||||
|
okf_version: kb-layout-2026-06
|
||||||
|
|
||||||
|
* [Tildelingsbrev](tildelingsbrev.md) - Styringssignal med unik testtoken.
|
||||||
12
tests/fixtures/okf-minimal/tildelingsbrev.md
vendored
Normal file
12
tests/fixtures/okf-minimal/tildelingsbrev.md
vendored
Normal 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.
|
||||||
3
tests/fixtures/okf-realistic/dokumenter/index.md
vendored
Normal file
3
tests/fixtures/okf-realistic/dokumenter/index.md
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Dokumenter
|
||||||
|
|
||||||
|
* [Arbeidsnotat](notat.md) - Internt arbeidsnotat med ukjent type.
|
||||||
12
tests/fixtures/okf-realistic/dokumenter/notat.md
vendored
Normal file
12
tests/fixtures/okf-realistic/dokumenter/notat.md
vendored
Normal 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.
|
||||||
4
tests/fixtures/okf-realistic/historikk/index.md
vendored
Normal file
4
tests/fixtures/okf-realistic/historikk/index.md
vendored
Normal 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).
|
||||||
12
tests/fixtures/okf-realistic/historikk/retrospektiv-T3-2025.md
vendored
Normal file
12
tests/fixtures/okf-realistic/historikk/retrospektiv-T3-2025.md
vendored
Normal 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
8
tests/fixtures/okf-realistic/index.md
vendored
Normal 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.
|
||||||
5
tests/fixtures/okf-realistic/strategisk-kontekst/index.md
vendored
Normal file
5
tests/fixtures/okf-realistic/strategisk-kontekst/index.md
vendored
Normal 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.
|
||||||
12
tests/fixtures/okf-realistic/strategisk-kontekst/organisasjonsprofil.md
vendored
Normal file
12
tests/fixtures/okf-realistic/strategisk-kontekst/organisasjonsprofil.md
vendored
Normal 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.
|
||||||
13
tests/fixtures/okf-realistic/strategisk-kontekst/tildelingsbrev-2026.md
vendored
Normal file
13
tests/fixtures/okf-realistic/strategisk-kontekst/tildelingsbrev-2026.md
vendored
Normal 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.
|
||||||
12
tests/fixtures/okf-realistic/strategisk-kontekst/virksomhetsplan.md
vendored
Normal file
12
tests/fixtures/okf-realistic/strategisk-kontekst/virksomhetsplan.md
vendored
Normal 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.
|
||||||
5
tests/fixtures/okf-realistic/syklus/T1-2026/index.md
vendored
Normal file
5
tests/fixtures/okf-realistic/syklus/T1-2026/index.md
vendored
Normal 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.
|
||||||
12
tests/fixtures/okf-realistic/syklus/T1-2026/okr-digitalisering.md
vendored
Normal file
12
tests/fixtures/okf-realistic/syklus/T1-2026/okr-digitalisering.md
vendored
Normal 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.
|
||||||
14
tests/fixtures/okf-realistic/syklus/T1-2026/okr-trafikksikkerhet.md
vendored
Normal file
14
tests/fixtures/okf-realistic/syklus/T1-2026/okr-trafikksikkerhet.md
vendored
Normal 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.
|
||||||
12
tests/fixtures/okf-realistic/syklus/T1-2026/status.md
vendored
Normal file
12
tests/fixtures/okf-realistic/syklus/T1-2026/status.md
vendored
Normal 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.
|
||||||
131
tests/okf-retrieval.test.mjs
Normal file
131
tests/okf-retrieval.test.mjs
Normal 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');
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue