refactor(okr): begge hooks bruker delt frontmatter-modul (SC2) [skip-docs]

This commit is contained in:
Kjell Tore Guttormsen 2026-06-26 14:21:58 +02:00
commit 9b85c0b436
3 changed files with 116 additions and 17 deletions

View file

@ -7,6 +7,7 @@
import { readFileSync, existsSync, readdirSync } from 'node:fs';
import { join } from 'node:path';
import { parseFrontmatter } from '../../lib/frontmatter.mjs';
const cwd = process.cwd();
const configPath = join(cwd, '.claude', 'okr.local.md');
@ -17,14 +18,8 @@ if (!existsSync(configPath)) {
try {
const content = readFileSync(configPath, 'utf8');
const match = content.match(/^---\n([\s\S]*?)\n---/);
if (!match) process.exit(0);
const fm = match[1];
const get = (key) => {
const m = fm.match(new RegExp(`${key}:\\s*["']?([^"'\\n]+)["']?`));
return m ? m[1].trim() : null;
};
const { raw, get } = parseFrontmatter(content);
if (raw === null) process.exit(0);
const cycleId = get('id');
const fase = get('fase');

View file

@ -8,6 +8,7 @@
import { readFileSync, existsSync, readdirSync } from 'node:fs';
import { join } from 'node:path';
import { homedir } from 'node:os';
import { parseFrontmatter } from '../../lib/frontmatter.mjs';
const cwd = process.cwd();
const projectConfigPath = join(cwd, '.claude', 'okr.local.md');
@ -56,20 +57,14 @@ if (!configPath) {
try {
const content = readFileSync(configPath, 'utf8');
const match = content.match(/^---\n([\s\S]*?)\n---/);
if (!match) process.exit(0);
const fm = match[1];
const get = (key) => {
const m = fm.match(new RegExp(`${key}:\\s*["']?([^"'\\n]+)["']?`));
return m ? m[1].trim() : null;
};
const { raw, get } = parseFrontmatter(content);
if (raw === null) process.exit(0);
// Core fields (backwards-compatible with old 4-field format)
const org = get('navn') || get('name');
const syklus = get('gjeldende') || get('id') || get('current_cycle');
const sektor = get('sektor') || get('sector') || get('domene');
const linear = fm.includes('aktivert: true') || fm.includes('enabled: true');
const linear = raw.includes('aktivert: true') || raw.includes('enabled: true');
// New v1.1 fields (silently skipped if absent)
const modenhet = get('modenhetsnivaa');

View file

@ -0,0 +1,109 @@
// frontmatter-compat.test.mjs
// Karakteriserings-/kompatibilitetstest (SC2): beviser at ruting av
// inject-okr-context.mjs gjennom den delte lib/frontmatter.mjs er
// ATFERDS-BEVARENDE for de 9 leste noeklene, med EN tilsiktet endring:
// comment-leak paa usitert `okr_frikoblet_fra_loenn`-linje forsvinner.
// Spawner hooken som subprosess (full 9-felts frontmatter, rikere enn
// inject-okr-context.test.mjs sin makeProjectConfig som kun skriver navn).
// Zero npm deps. Moenster: tests/inject-okr-context.test.mjs.
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',
);
// Bygger en full 9-felts okr.local.md. okr_frikoblet_fra_loenn skrives USITERT
// MED en trailing "# ..."-kommentar — det er denne lekkasjen den delte
// parseren skal fjerne.
function makeFullConfig(workDir, fields = {}) {
const f = {
navn: 'TestOrg',
kortform: 'TO',
gjeldende: 'T2-2026',
sektor: 'transport',
modenhetsnivaa: 'nivaa-2',
fase: 'utforsker',
okr_frikoblet_fra_loenn: 'true',
psykologisk_trygghet: 'hoy',
linear: true,
...fields,
};
const body =
[
'---',
`navn: "${f.navn}"`,
`kortform: "${f.kortform}"`,
`gjeldende: "${f.gjeldende}"`,
`sektor: "${f.sektor}"`,
`modenhetsnivaa: "${f.modenhetsnivaa}"`,
`fase: "${f.fase}"`,
`okr_frikoblet_fra_loenn: ${f.okr_frikoblet_fra_loenn} # frikoblet fra lonn`,
`psykologisk_trygghet: "${f.psykologisk_trygghet}"`,
'linear:',
` aktivert: ${f.linear}`,
'---',
'',
].join('\n');
const p = join(workDir, '.claude');
mkdirSync(p, { recursive: true });
writeFileSync(join(p, 'okr.local.md'), body);
}
const OKR_PROMPT = JSON.stringify({ prompt: 'hjelp meg skrive OKR' });
function runHook(cwd, home, input = OKR_PROMPT) {
return execFileSync('node', [HOOK], {
cwd,
env: { ...process.env, HOME: home },
input,
encoding: 'utf8',
});
}
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 });
}
}
test('full config: alle 9 noekler resolver likt som foer', () => {
withDirs((home, work) => {
makeFullConfig(work);
const out = runHook(work, home);
assert.match(out, /Organisasjon: TestOrg \(TO\)/, 'navn + kortform');
assert.match(out, /Syklus: T2-2026 \[utforsker\]/, 'gjeldende + fase');
assert.match(out, /Sektor: transport/, 'sektor');
assert.match(out, /Modenhet: nivaa-2/, 'modenhetsnivaa');
assert.match(out, /OKR frikoblet fra lonn: true/, 'frikoblet-verdi');
assert.match(out, /Psykologisk trygghet: hoy/, 'psykologisk_trygghet');
assert.match(out, /Linear: aktivert/, 'linear aktivert: true');
});
});
test('comment-leak fjernet: ingen "#" i injisert melding', () => {
withDirs((home, work) => {
makeFullConfig(work);
const out = runHook(work, home);
assert.doesNotMatch(
out,
/#/,
'usitert frikoblet-linjes "# ..."-kommentar skal ikke lekke inn i meldingen',
);
});
});