138 lines
4.9 KiB
JavaScript
138 lines
4.9 KiB
JavaScript
// 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 "#"');
|
|
});
|
|
});
|