feat(okr): 1.8.1 okf_version/okf_layout-splitt (OKF-spec 12)

Rot-index.md bar en markoer som dekket to urelaterte konsepter: den upstream
OKF-versjonen bundelen sikter mot OG pluginens egen layout-revisjon. Specen
(catalog/docs/okf-second-brain/spec.md 12, log.md 2026-07-23) skiller dem i to
markoerer. Denne releasen migrerer emitteren og sjekkeren over.

- okf-index.mjs: emitterer okf_version: 0.1 (upstream, paakrevd per 3) +
  okf_layout: kb-layout-2026-06 (vaar revisjon, valgfri per 12). Ny konstant
  OKF_LAYOUT; OKF_VERSION baerer naa upstream-verdien. Begge rot-eksklusive.
- Migrasjonssti: en ikke-upstream verdi i okf_version FLYTTES verbatim til
  okf_layout ved neste kjoering. Spec-konform okf_version roeres aldri;
  eksisterende okf_layout bevares. Byte-idempotent.
- okf-check.mjs: ekkoer begge markoerene (fravaerende -> MANGLER). Rent ekko,
  ingen ny haandheving -- spec 3 er ikke haandhevende paa form ennaa.
- CLI: --okf-layout er kanon. --okf-version beholdt som deprecated alias
  (verdien var alltid en layout-revisjon) m/ varsel til stderr, ALDRI stdout.
  Samme aliasing for generateIndexes({ okfLayout }) mot { okfVersion }.
- Doc-flater (CLAUDE.md, second-brain SKILL.md, commands/oppsett.md) beskriver
  naa to markoerer der de beskrev en.

Suite 167 -> 179, alle groenne. Versjonssync 1.8.1 over alle shippede flater.

Laaser opp catalog + llm-ingestion-okf, som begge ventet paa denne migrasjonen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016pUfkQ1YyH75z9y6RBaBHG
This commit is contained in:
Kjell Tore Guttormsen 2026-07-25 15:33:30 +02:00
commit 0059da7bef
16 changed files with 333 additions and 59 deletions

View file

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

View file

@ -1,6 +1,7 @@
# OKF second brain - Vegdirektoratet (realistisk testfixtur)
okf_version: kb-layout-2026-06
okf_version: 0.1
okf_layout: 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.

View file

@ -41,7 +41,7 @@ function makeProjectTree(work, count) {
mkdirSync(lvl, { recursive: true });
writeFileSync(
join(work, '.claude', 'okr', 'index.md'),
'# OKR-rot\n\nokf_version: kb-layout-2026-06\n',
'# OKR-rot\n\nokf_version: 0.1\nokf_layout: kb-layout-2026-06\n',
);
for (let i = 0; i < count; i++) {
writeFileSync(join(lvl, `konseptfil-${i}.md`), `---\ntype: OKR\n---\n# K${i}\n`);
@ -52,7 +52,7 @@ function makeHomeBundle(home, navn = 'HomeCapOrg') {
const org = join(home, '.claude', 'okr', 'org');
mkdirSync(org, { recursive: true });
writeFileSync(join(org, 'profil.md'), `---\nnavn: "${navn}"\n---\n`);
writeFileSync(join(org, 'index.md'), '# Org-rot\n\nokf_version: kb-layout-2026-06\n');
writeFileSync(join(org, 'index.md'), '# Org-rot\n\nokf_version: 0.1\nokf_layout: kb-layout-2026-06\n');
}
function runHook(cwd, home, input = OKR_PROMPT) {

View file

@ -8,7 +8,7 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import { execFileSync, spawnSync } from 'node:child_process';
import {
mkdtempSync, cpSync, writeFileSync, readFileSync, existsSync, readdirSync, rmSync, mkdirSync,
} from 'node:fs';
@ -122,6 +122,23 @@ test('okf-index: rot-index baerer okf_version, undernivaa gjoer ikke', () => {
}
});
test('okf-index: undernivaa-index har verken markoer eller tom markoer-linje', () => {
const dir = tmpRoot();
try {
cpSync(REALISTIC, dir, { recursive: true });
generateIndexes(dir);
const sub = readFileSync(join(dir, 'strategisk-kontekst', 'index.md'), 'utf8');
assert.ok(!/okf_layout/.test(sub), 'undernivaa skal IKKE ha okf_layout');
// Markoer-blokken er rot-eksklusiv: undernivaaet gaar rett fra H1 til entries.
const lines = sub.split('\n');
assert.match(lines[0], /^# /, 'linje 1 = overskrift');
assert.equal(lines[1], '', 'linje 2 = blank');
assert.match(lines[2], /^\* \[/, 'linje 3 = foerste entry (ingen markoer-blokk)');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('okf-index: konsept-tittel/beskrivelse hentes fra frontmatter', () => {
const dir = tmpRoot();
try {
@ -165,7 +182,7 @@ test('okf-check: konsept-fil uten type: -> exit != 0 + teller > 0 + navngir file
test('okf-check: rapporterer okf_version fra rot-index', () => {
const { stdout } = runCheck(REALISTIC);
assert.match(stdout, /okf_version:\s*kb-layout-2026-06/, 'skal ekko okf_version for menneskelig sammenligning');
assert.match(stdout, /okf_version:\s*0\.1/, 'skal ekko okf_version for menneskelig sammenligning');
});
// --- okf-check --strict-ingest (Step 8): lukket vokab + lenke-allow-liste ---
@ -316,17 +333,20 @@ test('okf-index: innboks/ + dot-katalog -> ingen egen index.md OG ikke listet i
const INDEX_CLI = join(ROOT, 'scripts', 'okf-index.mjs');
test('okf-index CLI: eksplisitt --okf-version bumper eksisterende rot-index (flagg foer rot)', () => {
// 1.8.1: verdien flagget baerer er en LAYOUT-revisjon (den var det ogsaa foer
// splitten) -- flagget godtas fortsatt her (deprecated alias), men landet den
// skriver er `okf_layout`. Se alias-testene i 1.8.1-seksjonen nederst.
test('okf-index CLI: eksplisitt layout-flagg bumper eksisterende rot-index (flagg foer rot)', () => {
const dir = tmpRoot();
try {
buildCleanIngest(dir); // rot-index har allerede okf_version: kb-layout-2026-06
execFileSync('node', [INDEX_CLI, '--okf-version', 'kb-layout-2027-01', dir], { encoding: 'utf8' });
execFileSync('node', [INDEX_CLI, '--okf-layout', 'kb-layout-2027-01', dir], { encoding: 'utf8' });
const root = readFileSync(join(dir, 'index.md'), 'utf8');
assert.match(root, /^okf_version: kb-layout-2027-01$/m, 'eksplisitt versjon skal vinne over eksisterende');
// Uten flagg bevares den bumpede versjonen (idempotent vedlikehold, som foer).
assert.match(root, /^okf_layout: kb-layout-2027-01$/m, 'eksplisitt layout skal vinne over eksisterende');
// Uten flagg bevares den bumpede verdien (idempotent vedlikehold, som foer).
execFileSync('node', [INDEX_CLI, dir], { encoding: 'utf8' });
const root2 = readFileSync(join(dir, 'index.md'), 'utf8');
assert.match(root2, /^okf_version: kb-layout-2027-01$/m, 'implisitt kjoering bevarer eksisterende versjon');
assert.match(root2, /^okf_layout: kb-layout-2027-01$/m, 'implisitt kjoering bevarer eksisterende layout');
} finally {
rmSync(dir, { recursive: true, force: true });
}
@ -456,3 +476,175 @@ test('checkBundle { files }: strict validerer KUN oppgitte filer, aldri hele rot
rmSync(dir, { recursive: true, force: true });
}
});
// --- 1.8.1: okf_version / okf_layout-splitt (OKF-spec §12) ---
// `okf_version` = upstream Google OKF-versjon ALENE (verdisett eid av Google,
// enkeltverdi). `okf_layout` = vaar EGEN layout-revisjon (valgfri, verdisett eid
// av emitteren). Rot-index baerer BEGGE. Migrasjonssti: en eksisterende rot-index
// som baerer en layout-verdi i `okf_version` (vaar form foer 1.8.1) splittes.
// Kilde: catalog/docs/okf-second-brain/spec.md §3 + §12, log.md 2026-07-23.
// Skriv en rot-index med et vilkaarlig sett markoer-linjer (legacy/splittet/tom).
function writeRootIndex(dir, markerLines) {
mkdirSync(dir, { recursive: true });
writeFileSync(join(dir, 'index.md'), `# Bundle\n\n${markerLines.join('\n')}\n`);
}
test('okf-index: rot-index baerer BEGGE markoerene, undernivaa ingen av dem', () => {
const dir = tmpRoot();
try {
cpSync(REALISTIC, dir, { recursive: true });
generateIndexes(dir);
const root = readFileSync(join(dir, 'index.md'), 'utf8');
assert.match(root, /^okf_version: 0\.1$/m, 'rot skal baere upstream-versjonen alene');
assert.match(root, /^okf_layout: kb-layout-2026-06$/m, 'rot skal baere vaar layout-revisjon');
const sub = readFileSync(join(dir, 'strategisk-kontekst', 'index.md'), 'utf8');
assert.ok(!/^okf_version:/m.test(sub), 'undernivaa skal IKKE ha okf_version');
assert.ok(!/^okf_layout:/m.test(sub), 'undernivaa skal IKKE ha okf_layout');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('okf-index: legacy rot-index (layout-verdi i okf_version) migreres til splittet form', () => {
const dir = tmpRoot();
try {
writeRootIndex(dir, ['okf_version: kb-layout-2026-06']);
generateIndexes(dir);
const root = readFileSync(join(dir, 'index.md'), 'utf8');
assert.match(root, /^okf_version: 0\.1$/m, 'okf_version skal settes til upstream-verdien');
assert.match(root, /^okf_layout: kb-layout-2026-06$/m, 'layout-verdien skal flyttes hit, ikke tapes');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('okf-index: en FREMMED layout-verdi i okf_version bevares gjennom migrasjonen', () => {
const dir = tmpRoot();
try {
// Migrasjonen skal flytte verdien, ikke erstatte den med vaar egen konstant.
writeRootIndex(dir, ['okf_version: kb-layout-2025-01']);
generateIndexes(dir);
const root = readFileSync(join(dir, 'index.md'), 'utf8');
assert.match(root, /^okf_version: 0\.1$/m);
assert.match(root, /^okf_layout: kb-layout-2025-01$/m, 'den FUNNE verdien skal flyttes verbatim');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('okf-index: spec-konform okf_version (upstream-form) migreres IKKE til okf_layout', () => {
const dir = tmpRoot();
try {
writeRootIndex(dir, ['okf_version: 0.2']);
generateIndexes(dir);
const root = readFileSync(join(dir, 'index.md'), 'utf8');
assert.match(root, /^okf_version: 0\.2$/m, 'upstream-verdi skal bevares, ikke overskrives');
assert.match(root, /^okf_layout: kb-layout-2026-06$/m, 'layout fylles fra konstant');
assert.ok(!/^okf_layout: 0\.2$/m.test(root), 'upstream-verdien skal ALDRI havne i okf_layout');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('okf-index: eksisterende splittet form bevares verbatim (idempotent vedlikehold)', () => {
const dir = tmpRoot();
try {
writeRootIndex(dir, ['okf_version: 0.3', 'okf_layout: kb-layout-2099-12']);
generateIndexes(dir);
const root = readFileSync(join(dir, 'index.md'), 'utf8');
assert.match(root, /^okf_version: 0\.3$/m, 'eksisterende okf_version bevares');
assert.match(root, /^okf_layout: kb-layout-2099-12$/m, 'eksisterende okf_layout bevares');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('okf-index CLI: --okf-layout bumper layout og lar okf_version staa', () => {
const dir = tmpRoot();
try {
buildCleanIngest(dir); // legacy rot-index: okf_version: kb-layout-2026-06
execFileSync('node', [INDEX_CLI, '--okf-layout', 'kb-layout-2027-01', dir], { encoding: 'utf8' });
const root = readFileSync(join(dir, 'index.md'), 'utf8');
assert.match(root, /^okf_layout: kb-layout-2027-01$/m, 'eksplisitt layout skal vinne');
assert.match(root, /^okf_version: 0\.1$/m, 'okf_version skal vaere upstream-verdien');
// Uten flagg bevares den bumpede layouten (idempotent vedlikehold).
execFileSync('node', [INDEX_CLI, dir], { encoding: 'utf8' });
const root2 = readFileSync(join(dir, 'index.md'), 'utf8');
assert.match(root2, /^okf_layout: kb-layout-2027-01$/m, 'implisitt kjoering bevarer bumpet layout');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('okf-index CLI: --okf-version er deprecated alias for --okf-layout (m/ stderr-varsel)', () => {
const dir = tmpRoot();
try {
buildCleanIngest(dir);
// Aliaset maa fortsatt sette LAYOUT -- det var alltid det verdien betydde.
execFileSync('node', [INDEX_CLI, '--okf-version', 'kb-layout-2027-05', dir], {
encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
});
const root = readFileSync(join(dir, 'index.md'), 'utf8');
assert.match(root, /^okf_layout: kb-layout-2027-05$/m, 'aliaset skal sette layout');
assert.match(root, /^okf_version: 0\.1$/m, 'aliaset skal IKKE sette okf_version');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('okf-index CLI: --okf-version skriver deprecation-varsel til stderr, ikke stdout', () => {
const dir = tmpRoot();
try {
buildCleanIngest(dir);
const res = spawnSync(
'node', [INDEX_CLI, '--okf-version', 'kb-layout-2027-06', dir],
{ encoding: 'utf8' },
);
assert.equal(res.status, 0, 'aliaset skal fortsatt lykkes');
assert.match(res.stderr, /--okf-version/, 'varselet skal navngi det utgaaende flagget');
assert.match(res.stderr, /--okf-layout/, 'varselet skal peke paa erstatteren');
assert.ok(!/--okf-layout/.test(res.stdout), 'varselet skal IKKE forurense stdout');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('okf-index CLI: --okf-layout uten verdi -> bruksfeil exit 2, ingen skriving', () => {
const dir = tmpRoot();
try {
buildCleanIngest(dir);
const before = readFileSync(join(dir, 'index.md'), 'utf8');
let status = 0;
try {
execFileSync('node', [INDEX_CLI, dir, '--okf-layout'], { encoding: 'utf8', stdio: 'pipe' });
} catch (e) {
status = e.status;
}
assert.equal(status, 2, 'manglende flagg-verdi skal gi bruksfeil exit 2');
assert.equal(readFileSync(join(dir, 'index.md'), 'utf8'), before, 'rot-index skal vaere uendret');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('okf-check: ekkoer BEGGE markoerene fra rot-index', () => {
const { stdout } = runCheck(REALISTIC);
assert.match(stdout, /okf_version:\s*0\.1/, 'skal ekko upstream-versjonen');
assert.match(stdout, /okf_layout:\s*kb-layout-2026-06/, 'skal ekko vaar layout-revisjon');
});
test('okf-check: rot-index uten okf_layout ekkoer MANGLER for den, ikke for okf_version', () => {
const dir = tmpRoot();
try {
buildCleanIngest(dir);
writeRootIndex(dir, ['okf_version: 0.1']); // splittet form, layout utelatt (lovlig: valgfri)
const r = checkBundle(dir);
assert.equal(r.okfVersion, '0.1', 'okf_version skal parses');
assert.equal(r.okfLayout, null, 'fravaerende okf_layout skal vaere null, ikke undefined');
const { stdout } = runCheck(dir);
assert.match(stdout, /okf_layout: MANGLER/, 'ekkoet skal si fra at layout-markoeren mangler');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});

View file

@ -2,7 +2,7 @@
// Step 10 (A1): package.json-kontrakten for det bevisste zero-dep-bruddet.
// Verifiserer at dep-laget er EXACT-pinnet (ingen ^/~/*), at engines-gulvet er
// satt (unpdf krever node >= 22), at pakken er ESM (type: module), at versjonen
// er 1.8.0 (minor-lane C: En kanon) paa ALLE shippede flater, og at .npmrc slaar av
// er 1.8.1 (patch-lane: okf_layout-migrasjon) paa ALLE shippede flater, og at .npmrc slaar av
// install-scripts (Shai-Hulud / supply-chain). Zero npm deps i selve testen.
// Moenster: tests/frontmatter.test.mjs (les fil, assert struktur).
@ -24,10 +24,10 @@ function readPkg() {
return JSON.parse(readFileSync(PKG, 'utf8'));
}
test('package.json: type module + version 1.8.0 (minor-lane)', () => {
test('package.json: type module + version 1.8.1 (patch-lane)', () => {
const pkg = readPkg();
assert.equal(pkg.type, 'module');
assert.equal(pkg.version, '1.8.0');
assert.equal(pkg.version, '1.8.1');
});
test('package.json: engines.node-gulv satt (unpdf krever >= 22)', () => {