// inject-core-cap.test.mjs // Verifiserer hook-splitten (SC5): inject-okr-context emitterer kun kjerne- // kontekst + EN resolvert index.md-peker, ALDRI tre-enumerering. Beviser: // (a) byte-cap < 512; (b) payload uavhengig av filantall (lite vs stort tre); // (c) ingen konsept-filnavn lekker; (d) home-only -> home-bundle-sti i peker; // (e) ingen '#'. // Spawner hooken som subprosess m/ kontrollert cwd + HOME. Zero npm deps. // Monster: tests/inject-okr-context.test.mjs + tre-bygger-helper. import { test } from 'node:test'; import assert from 'node:assert/strict'; import { execFileSync } from 'node:child_process'; import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; const HOOK = join( dirname(fileURLToPath(import.meta.url)), '..', 'hooks', 'scripts', 'inject-okr-context.mjs', ); const OKR_PROMPT = JSON.stringify({ prompt: 'hjelp meg skrive OKR' }); function makeProjectConfig(work, navn = 'CapTestOrg') { const p = join(work, '.claude'); mkdirSync(p, { recursive: true }); writeFileSync( join(p, 'okr.local.md'), `---\nnavn: "${navn}"\ngjeldende: "T1-2026"\nsektor: "transport"\n---\n`, ); } // Idempotent paa index.md, additiv paa konsept-filer (0..count-1). Distinkte // filnavn (konseptfil-N) for lekkasje-testen. function makeProjectTree(work, count) { const lvl = join(work, '.claude', 'okr', 'strategisk-kontekst'); mkdirSync(lvl, { recursive: true }); writeFileSync( join(work, '.claude', 'okr', 'index.md'), '# OKR-rot\n\nokf_version: 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`); } } 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'); } function runHook(cwd, home, input = OKR_PROMPT) { return execFileSync('node', [HOOK], { cwd, env: { ...process.env, HOME: home }, input, encoding: 'utf8', }); } function systemMessage(out) { return JSON.parse(out).systemMessage; } function withDirs(fn) { const home = mkdtempSync(join(tmpdir(), 'okrhome-')); const work = mkdtempSync(join(tmpdir(), 'okrwork-')); try { fn(home, work); } finally { rmSync(home, { recursive: true, force: true }); rmSync(work, { recursive: true, force: true }); } } // (a) byte-cap < 512 test('cap: systemMessage < 512 byte (utf8) selv med 50 konsept-filer', () => { withDirs((home, work) => { makeProjectConfig(work); makeProjectTree(work, 50); const msg = systemMessage(runHook(work, home)); const bytes = Buffer.byteLength(msg, 'utf8'); assert.ok(bytes < 512, `payload skal vaere < 512 B, var ${bytes}`); }); }); // (b) invariant: lite (index+3) vs stort (index+50) tre -> byte-identisk payload. // SAMME work-dir i begge maalinger -> peker-stien er konstant; eneste variabel // er filantallet, som splitten skal gjore irrelevant. test('invariant: payload byte-identisk for lite og stort tre (ingen enumerering)', () => { withDirs((home, work) => { makeProjectConfig(work); makeProjectTree(work, 3); const small = systemMessage(runHook(work, home)); makeProjectTree(work, 50); const large = systemMessage(runHook(work, home)); assert.equal(small, large, 'filantall skal ikke paavirke payload'); }); }); // (c) ingen konsept-filnavn enumerert test('ingen lekkasje: konsept-filnavn opptrer ikke i payload; peker bruker index.md', () => { withDirs((home, work) => { makeProjectConfig(work); makeProjectTree(work, 5); const msg = systemMessage(runHook(work, home)); assert.doesNotMatch(msg, /konseptfil-/, 'konsept-filnavn skal ikke enumereres'); assert.match(msg, /index\.md/, 'peker skal referere index.md'); }); }); // (d) home-only -> home-bundle index.md i peker test('home-only: peker bruker home-bundle index.md (~/.claude/okr/org/index.md)', () => { withDirs((home, work) => { makeHomeBundle(home); // tomt prosjekt, kun home const msg = systemMessage(runHook(work, home)); assert.match(msg, /HomeCapOrg/, 'org leses fra home-profil'); assert.match(msg, /okr-second-brain-search/, 'peker nevner skillen'); const expected = join(home, '.claude', 'okr', 'org', 'index.md'); assert.ok(msg.includes(expected), `peker skal inneholde home-index-sti: ${expected}`); }); }); // (e) ingen '#' test('ingen "#" i payload (peker + sti ASCII-rent)', () => { withDirs((home, work) => { makeProjectConfig(work); makeProjectTree(work, 3); const msg = systemMessage(runHook(work, home)); assert.doesNotMatch(msg, /#/, 'payload skal ikke inneholde "#"'); }); });