fix(okr): drift-laas kommando-antall og haandhev rapport utenfor bundlen
This commit is contained in:
parent
099cabfba6
commit
d63438581d
4 changed files with 92 additions and 8 deletions
|
|
@ -169,7 +169,7 @@ It does **not** own confidence, nor the assessment fields it reserves — the pe
|
||||||
> /okr:freshen-references
|
> /okr:freshen-references
|
||||||
```
|
```
|
||||||
|
|
||||||
`/okr:help` lists all 14 commands, 7 agents, and a recommended workflow keyed to where you are in the tertial cycle. `/okr:freshen-references` is a knowledge-base self-evaluator: it scores 18 of the 19 domain reference files against an anchored quality rubric — the quality rubric itself is excluded, since scoring the scoring instrument is circular — and polls named public sources (UN EGDI, EU eGov Benchmark, OECD DGI, Digdir, WCAG, forvaltningsloven) to flag outdated `Sist oppdatert` markers.
|
`/okr:help` lists all 16 commands, 7 agents, and a recommended workflow keyed to where you are in the tertial cycle. `/okr:freshen-references` is a knowledge-base self-evaluator: it scores 18 of the 19 domain reference files against an anchored quality rubric — the quality rubric itself is excluded, since scoring the scoring instrument is circular — and polls named public sources (UN EGDI, EU eGov Benchmark, OECD DGI, Digdir, WCAG, forvaltningsloven) to flag outdated `Sist oppdatert` markers.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
// Exit: 0 = rapport produsert · 1 = uventet feil (inkl. confinement-brudd)
|
// Exit: 0 = rapport produsert · 1 = uventet feil (inkl. confinement-brudd)
|
||||||
// 2 = bruksfeil (manglende/ukjent sti)
|
// 2 = bruksfeil (manglende/ukjent sti)
|
||||||
|
|
||||||
import { writeFileSync, renameSync, existsSync, statSync } from 'node:fs';
|
import { writeFileSync, renameSync, existsSync, statSync, realpathSync } from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
|
@ -55,7 +55,25 @@ if (isMain) {
|
||||||
try {
|
try {
|
||||||
const ut = byggRapport(bundleRoot);
|
const ut = byggRapport(bundleRoot);
|
||||||
if (rapportFil) {
|
if (rapportFil) {
|
||||||
writeAtomic(path.resolve(rapportFil), ut);
|
// Haandhever loeftet i headeren: rapporten skal ALDRI havne i bundlen den
|
||||||
|
// vurderer. En rapport i treet ville blitt klassifisert av neste kjoering
|
||||||
|
// (selv-forurensning), og et loefte som bare staar i prosa er ikke et
|
||||||
|
// loefte -- samme standard som tool-lista i commands/arkivklar.md.
|
||||||
|
//
|
||||||
|
// BEGGE sider maa realpath-es, ellers er sammenligningen verdiloes: paa
|
||||||
|
// macOS er /var en symlenke til /private/var, saa en realpath-et rot mot
|
||||||
|
// en bare resolvet maal-sti sammenligner to skrivemaater av samme sted og
|
||||||
|
// slipper alt gjennom. Maalet finnes ikke ennaa, saa det er FORELDRE-
|
||||||
|
// katalogen som realpath-es -- samme grep som writeConfined i
|
||||||
|
// lib/innboks-write.mjs.
|
||||||
|
const maalDir = realpathSync(path.dirname(path.resolve(rapportFil)));
|
||||||
|
const maal = path.join(maalDir, path.basename(rapportFil));
|
||||||
|
const rot = realpathSync(path.resolve(bundleRoot));
|
||||||
|
if (maal === rot || maal.startsWith(rot + path.sep)) {
|
||||||
|
process.stderr.write(`arkivklar: rapporten kan ikke skrives inne i bundlen som vurderes: ${rapportFil}\n`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
writeAtomic(maal, ut);
|
||||||
process.stdout.write(`Arkivklar: vurderingsgrunnlag skrevet til ${rapportFil}\n`);
|
process.stdout.write(`Arkivklar: vurderingsgrunnlag skrevet til ${rapportFil}\n`);
|
||||||
} else {
|
} else {
|
||||||
process.stdout.write(ut);
|
process.stdout.write(ut);
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
import { test } from 'node:test';
|
import { test } from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, readdirSync, rmSync, symlinkSync } from 'node:fs';
|
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, readdirSync, existsSync, rmSync, symlinkSync } from 'node:fs';
|
||||||
import { execFileSync } from 'node:child_process';
|
import { execFileSync } from 'node:child_process';
|
||||||
import { tmpdir } from 'node:os';
|
import { tmpdir } from 'node:os';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
|
|
@ -363,3 +363,30 @@ test('(19d) GDPR-posisjonen staar i governance og ender paa «behold, men skjerm
|
||||||
'Datatilsynets vedtakskompetanse skal ikke siteres med paragrafnummer (kun delvis verifisert)',
|
'Datatilsynets vedtakskompetanse skal ikke siteres med paragrafnummer (kun delvis verifisert)',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('(18j) CLI nekter aa skrive rapporten inn i bundlen den vurderer', () => {
|
||||||
|
// Skriptets header lover at rapporten ALDRI havner i bundlen. Prosa som lover
|
||||||
|
// noe koden ikke haandhever er nettopp det (19a) avviser for allowed-tools --
|
||||||
|
// samme standard maa gjelde her. Rapporten i treet ville dessuten blitt
|
||||||
|
// klassifisert av neste kjoering, altsaa selv-forurensning.
|
||||||
|
//
|
||||||
|
// Kjoerer mot en TMP-bundle, ikke den delte fixturen: i RED-fasen (foer vakten
|
||||||
|
// fantes) skrev nettopp denne casen `rapport.md` inn i tests/fixtures/ og
|
||||||
|
// roednet okf-check-suiten. Det er STATE-gotchaen «CLI-VERIFY SKRIVER I
|
||||||
|
// FIXTUREN» -- en test som kan skitne til delt state, gjoer det til slutt.
|
||||||
|
withTmp((tmp) => {
|
||||||
|
const rot = join(tmp, 'bundle');
|
||||||
|
mkdirSync(join(rot, 'dokumenter'), { recursive: true });
|
||||||
|
writeFileSync(join(rot, 'dokumenter', 'notat.md'), '---\ntype: Notat\n---\n\n# Notat\n');
|
||||||
|
|
||||||
|
const r = kjor([rot, join(rot, 'rapport.md')]);
|
||||||
|
assert.equal(r.code, 1, `forventet exit 1, fikk ${r.code}`);
|
||||||
|
assert.match(r.stderr ?? '', /inne i bundlen/i, 'feilmeldingen skal si hvorfor');
|
||||||
|
assert.ok(!existsSync(join(rot, 'rapport.md')), 'ingenting skal vaere skrevet i bundlen');
|
||||||
|
|
||||||
|
// Utenfor bundlen skal det fortsatt gaa fint.
|
||||||
|
const ok = kjor([rot, join(tmp, 'rapport.md')]);
|
||||||
|
assert.equal(ok.code, 0, `rapport utenfor bundlen skal gaa fint, fikk ${ok.code}`);
|
||||||
|
assert.ok(existsSync(join(tmp, 'rapport.md')), 'rapporten skal vaere skrevet utenfor bundlen');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -451,15 +451,27 @@ test('(g3) dimensjonstall i kvalitet + kvalitetssjekker == faktisk antall i rubr
|
||||||
assert.deepEqual(feilAnkertall, [], `hver dimensjon skal ha fem ankere:\n${feilAnkertall.join('\n')}`);
|
assert.deepEqual(feilAnkertall, [], `hver dimensjon skal ha fem ankere:\n${feilAnkertall.join('\n')}`);
|
||||||
|
|
||||||
// Drift-laasen: hvert tall prosaen oppgir maa matche den utledede tellingen.
|
// Drift-laasen: hvert tall prosaen oppgir maa matche den utledede tellingen.
|
||||||
|
//
|
||||||
|
// Aksene daekker BEGGE spraak. SKILL.md er engelsk etter policy (CLAUDE.md
|
||||||
|
// «SKILL.md: English»), saa en rent norsk akse ville vaert blind for
|
||||||
|
// «all 10 dimensions» selv med SKILL.md i fil-lista. Mutasjons-verifisert:
|
||||||
|
// med kun de norske aksene slapp nettopp den formen gjennom.
|
||||||
const akser = [
|
const akser = [
|
||||||
[/(\d+)\s+dimensjonene?\b/gi, total, 'totalt antall dimensjoner'],
|
[/(\d+)\s+(?:dimensjonene?|dimensions?)\b/gi, total, 'totalt antall dimensjoner'],
|
||||||
[/\bObjective\s*\((\d+)\)/g, obj, 'Objective-gruppa'],
|
[/\bObjective\s*\((\d+)\)/g, obj, 'Objective-gruppa'],
|
||||||
[/\bKey Result\s*\((\d+)\)/g, kr, 'Key Result-gruppa'],
|
[/\bKey Result\s*\((\d+)\)/g, kr, 'Key Result-gruppa'],
|
||||||
[/\bde (\d+) Objective-dimensjonene\b/g, obj, 'Objective-gruppa'],
|
[/\b(?:de|the)\s+(\d+)\s+Objective[-\s]dimensjonene?\b/gi, obj, 'Objective-gruppa'],
|
||||||
[/\bde (\d+) Key Result-dimensjonene\b/g, kr, 'Key Result-gruppa'],
|
[/\b(?:de|the)\s+(\d+)\s+Key Result[-\s]dimensjonene?\b/gi, kr, 'Key Result-gruppa'],
|
||||||
|
[/\b(?:de|the)\s+(\d+)\s+Objective\s+dimensions?\b/gi, obj, 'Objective-gruppa (en)'],
|
||||||
|
[/\b(?:de|the)\s+(\d+)\s+Key Result\s+dimensions?\b/gi, kr, 'Key Result-gruppa (en)'],
|
||||||
];
|
];
|
||||||
|
// SKILL.md er med selv om den er ENGELSK og i dag ikke oppgir noe
|
||||||
|
// dimensjonstall. Den staar her av samme grunn som R4 la den inn i
|
||||||
|
// canonScan() (se :36-39): SKILL.md er nettopp flata drift har overlevd paa
|
||||||
|
// foer, og et engelsk «all 11 dimensions» ville vaert usynlig for enhver
|
||||||
|
// norsk grep. Aksene under matcher begge spraak.
|
||||||
const drift = [];
|
const drift = [];
|
||||||
for (const f of ['commands/kvalitet.md', 'agents/kvalitetssjekker-agent.md']) {
|
for (const f of ['commands/kvalitet.md', 'agents/kvalitetssjekker-agent.md', SKILL]) {
|
||||||
const body = readDoc(f);
|
const body = readDoc(f);
|
||||||
for (const [re, forventet, hva] of akser) {
|
for (const [re, forventet, hva] of akser) {
|
||||||
for (const m of body.matchAll(re)) {
|
for (const m of body.matchAll(re)) {
|
||||||
|
|
@ -470,6 +482,33 @@ test('(g3) dimensjonstall i kvalitet + kvalitetssjekker == faktisk antall i rubr
|
||||||
assert.deepEqual(drift, [], `dimensjonstall-drift (rubrikkfila = ${obj}+${kr}=${total}):\n${drift.join('\n')}`);
|
assert.deepEqual(drift, [], `dimensjonstall-drift (rubrikkfila = ${obj}+${kr}=${total}):\n${drift.join('\n')}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- (g5) kommando-antall drift-laas (D7 steg 19 / D6-beslutning 7) ---
|
||||||
|
//
|
||||||
|
// README.md:172 sa «all 14 commands» mens treet hadde 15 -- utdatert FOER denne
|
||||||
|
// oekten, og steg 19 gjorde den ett verre ved aa legge til /okr:arkivklar.
|
||||||
|
// Samme defektklasse som help.md/README.md-glippen i steg 12 (fikset i d2ac153):
|
||||||
|
// en levert kommandoform skal synes paa ALLE flater samtidig.
|
||||||
|
//
|
||||||
|
// Tallet utledes av commands/*.md -- aldri hardkodet, jf. (d) F-g.
|
||||||
|
test('(g5) prosa-omtaler av kommando-antall == faktisk antall commands/*.md', () => {
|
||||||
|
const antall = mdFiles('commands').length;
|
||||||
|
assert.ok(antall >= 10, `parser-sanity: fant ${antall} kommandofiler`);
|
||||||
|
|
||||||
|
// Tabellene som ER kommandolista skal ha en rad per fil.
|
||||||
|
for (const f of ['commands/help.md', 'CLAUDE.md']) {
|
||||||
|
const rader = readDoc(f).split('\n').filter((l) => /^\|\s*`\/okr:/.test(l)).length;
|
||||||
|
assert.equal(rader, antall, `${f}: ${rader} tabellrader != ${antall} kommandofiler`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const drift = [];
|
||||||
|
for (const f of ['README.md', 'commands/help.md', 'CLAUDE.md', SKILL]) {
|
||||||
|
for (const m of readDoc(f).matchAll(/\b(\d+)\s+(?:commands|kommandoer)\b/gi)) {
|
||||||
|
if (Number(m[1]) !== antall) drift.push(`${f}: "${m[0].trim()}" != ${antall}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.deepEqual(drift, [], `kommando-antall drift (telt = ${antall}):\n${drift.join('\n')}`);
|
||||||
|
});
|
||||||
|
|
||||||
// --- (g4) guardrail som KR-designmoenster (D7 steg 16) ---
|
// --- (g4) guardrail som KR-designmoenster (D7 steg 16) ---
|
||||||
//
|
//
|
||||||
// Baseline foer steget: 0 treff paa "guardrail" i commands/, 2 i okr-antipatterns.md
|
// Baseline foer steget: 0 treff paa "guardrail" i commands/, 2 i okr-antipatterns.md
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue