feat(okr): leading/lagging-balanse som rubrikk-dimensjon

This commit is contained in:
Kjell Tore Guttormsen 2026-08-02 10:13:25 +02:00
commit 3f7ba74afa
4 changed files with 87 additions and 9 deletions

View file

@ -108,9 +108,9 @@ function divergentEnumeration(line) {
// ==================== parser-sanity (jf. B1) ====================
test('parser-sanity: headingsOf finner 10 rubrikk-dimensjoner', () => {
test('parser-sanity: headingsOf finner 11 rubrikk-dimensjoner', () => {
const dims = headingsOf(readDoc(`${REF}/okr-quality-rubrics.md`), /^### /);
assert.equal(dims.length, 10, `forventet 10 dims, fant ${dims.length}`);
assert.equal(dims.length, 11, `forventet 11 dims, fant ${dims.length}`);
});
test('parser-sanity: mdTables finner confidence-tabellen i okr-framework.md', () => {
@ -386,10 +386,10 @@ function dimTokens(body) {
return tokens;
}
// (g) F-e agent 10 dims. RED til Step 10. Utled dimensjonsnavnene, ikke hardkod.
test('(g) F-e: kvalitetssjekker-agent daekker alle 10 rubrikk-dimensjoner', () => {
// (g) F-e agent 11 dims. RED til Step 10. Utled dimensjonsnavnene, ikke hardkod.
test('(g) F-e: kvalitetssjekker-agent daekker alle 11 rubrikk-dimensjoner', () => {
const dims = headingsOf(readDoc(`${REF}/okr-quality-rubrics.md`), /^### /);
assert.equal(dims.length, 10, `parser-sanity: forventet 10 dims, fant ${dims.length}`);
assert.equal(dims.length, 11, `parser-sanity: forventet 11 dims, fant ${dims.length}`);
const tokens = dimTokens(readDoc('agents/kvalitetssjekker-agent.md'));
const missing = dims.filter((d) => !tokens.has(d));
assert.deepEqual(missing, [], `kvalitetssjekker-agent mangler rubrikk-dims: ${missing.join(', ')}`);
@ -404,6 +404,72 @@ test('(g2) F-e: dim-vakten skiller Outcome fra Outcome-fokus', () => {
assert.ok(!tokens.has('Outcome'), '"Outcome" skal IKKE dekkes av "Outcome-fokus"');
});
// --- (g3) dimensjonstall drift-laas paa tvers av de TRE flatene (D7 steg 15) ---
//
// Rubrikkfila er sannhetskilden; kvalitet.md og kvalitetssjekker-agent.md OMTALER
// antallet i prosa. Da steg 15 la til en 11. dimensjon var det nettopp de omtalene
// som kunne bli staaende paa "10" -- samme defektklasse som (d) F-g loeser for
// antipattern-antallet: ALDRI hardkod tallet i vakten, utled det.
//
// Tre teller-akser, fordi prosaen baerer alle tre: totalen ("alle 11 dimensjonene"),
// Objective-gruppa og Key Result-gruppa. En vakt paa bare totalen ville sluppet
// gjennom "de 5 Key Result-dimensjonene" etter at gruppa ble seks.
function rubricDims() {
const body = readDoc(`${REF}/okr-quality-rubrics.md`);
const groups = { Objective: [], 'Key Result': [] };
let cur = null;
for (const line of body.split('\n')) {
const g = line.match(/^## (Objective|Key Result)-dimensjoner\s*$/);
if (g) { cur = g[1]; continue; }
if (/^## /.test(line)) { cur = null; continue; }
const d = line.match(/^### (.+?)\s*$/);
if (d && cur) groups[cur].push(d[1]);
}
return groups;
}
test('(g3) dimensjonstall i kvalitet + kvalitetssjekker == faktisk antall i rubrikkfila', () => {
const groups = rubricDims();
const obj = groups.Objective.length;
const kr = groups['Key Result'].length;
const total = obj + kr;
// parser-sanity: gruppene skal finnes og summere til overskrifts-tellingen.
assert.ok(obj > 0 && kr > 0, `parser-sanity: fant ${obj} Objective- og ${kr} KR-dims`);
assert.equal(
total,
headingsOf(readDoc(`${REF}/okr-quality-rubrics.md`), /^### /).length,
'gruppe-summen skal daekke alle ###-dimensjoner (ingen dim utenfor de to gruppene)',
);
// Hver dimensjon skal ha NOEYAKTIG fem ankere -- ankerformen er det rubrikkfila lover.
const blocks = readDoc(`${REF}/okr-quality-rubrics.md`).split(/^### /m).slice(1);
const feilAnkertall = blocks
.map((b) => [b.split('\n')[0].trim(), (b.match(/^\d+\. \*\*Anker \d/gm) ?? []).length])
.filter(([, n]) => n !== 5)
.map(([navn, n]) => `${navn}: ${n} ankere`);
assert.deepEqual(feilAnkertall, [], `hver dimensjon skal ha fem ankere:\n${feilAnkertall.join('\n')}`);
// Drift-laasen: hvert tall prosaen oppgir maa matche den utledede tellingen.
const akser = [
[/(\d+)\s+dimensjonene?\b/gi, total, 'totalt antall dimensjoner'],
[/\bObjective\s*\((\d+)\)/g, obj, 'Objective-gruppa'],
[/\bKey Result\s*\((\d+)\)/g, kr, 'Key Result-gruppa'],
[/\bde (\d+) Objective-dimensjonene\b/g, obj, 'Objective-gruppa'],
[/\bde (\d+) Key Result-dimensjonene\b/g, kr, 'Key Result-gruppa'],
];
const drift = [];
for (const f of ['commands/kvalitet.md', 'agents/kvalitetssjekker-agent.md']) {
const body = readDoc(f);
for (const [re, forventet, hva] of akser) {
for (const m of body.matchAll(re)) {
if (Number(m[1]) !== forventet) drift.push(`${f}: "${m[0].trim()}" != ${forventet} (${hva})`);
}
}
}
assert.deepEqual(drift, [], `dimensjonstall-drift (rubrikkfila = ${obj}+${kr}=${total}):\n${drift.join('\n')}`);
});
// (h) F-a fjerning. RED til Step 7. Score->modenhet-avledning borte; sandbagging-trendsignal til stede.
test('(h) F-a: ingen score->modenhet-avledning; sandbagging-trendsignal til stede', () => {
const problems = [];