8 hygiene-fikser fra review 2026-07-16 par.4 kode-lista, TDD roed-groenn (suite 138 -> 149): - innboks-split: slugify translittererer ae/oe (datatap-fiks) - innboks-frontmatter: beskrivende feil ved manglende sourceMtime - okf-index: --okf-version bumper eksisterende rot, flagg-tolerant CLI (exit 2 ved manglende verdi), sanitizeEntry strip C1/zero-width/bidi/ Unicode-tag - write-org-profile: circuit-breaker MERGER i stedet for aa overskrive full config (M4); test beviser at eksisterende config overlever - compose-org-profile: intern ----linje trunkerer ikke blokken - coaching-hook: at-risk teller status-markerte tabellrader (M1/m1) - inject-okr-context: topic-guard treffer boeyningsformer (maalene) - frontmatter: BOM/CRLF-toleranse (falsk mangler-type-fiks) Versjonsflater bumpet til 1.7.1 (package/plugin/lock/CLAUDE/README/ SKILL x2/package-shape-test) + CHANGELOG 1.7.1-seksjon (B1+B2). Release-tag + katalog-ref venter paa [G-B] operatoer-go. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
124 lines
4.2 KiB
JavaScript
124 lines
4.2 KiB
JavaScript
// coaching-hook.test.mjs
|
|
// Tester coaching-hook (SC4): graceful-exit-grener + gyldig-syklus-emisjon +
|
|
// deterministisk fase via OKR_NOW klokke-seam. Spawner hooken som subprosess
|
|
// med kontrollert cwd + env. Zero npm deps. Coaching leser kun cwd, saa én
|
|
// temp-dir per test holder. 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',
|
|
'coaching-hook.mjs',
|
|
);
|
|
|
|
function writeConfig(workDir, body) {
|
|
const p = join(workDir, '.claude');
|
|
mkdirSync(p, { recursive: true });
|
|
writeFileSync(join(p, 'okr.local.md'), body);
|
|
}
|
|
|
|
function runHook(cwd, okrNow) {
|
|
// execFileSync returnerer stdout; hooken avslutter alltid med exit 0.
|
|
const env = { ...process.env };
|
|
if (okrNow) env.OKR_NOW = okrNow;
|
|
else delete env.OKR_NOW;
|
|
return execFileSync('node', [HOOK], { cwd, env, encoding: 'utf8' });
|
|
}
|
|
|
|
function withWork(fn) {
|
|
const work = mkdtempSync(join(tmpdir(), 'okrcoach-'));
|
|
try {
|
|
fn(work);
|
|
} finally {
|
|
rmSync(work, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
test('ingen config: exit 0, tom stdout (graceful)', () => {
|
|
withWork((work) => {
|
|
const out = runHook(work);
|
|
assert.equal(out.trim(), '', 'ingen config -> tom stdout, ingen blokkering');
|
|
});
|
|
});
|
|
|
|
test('config uten id: exit 0, tom stdout', () => {
|
|
withWork((work) => {
|
|
writeConfig(work, '---\nnavn: "Org"\n---\n');
|
|
const out = runHook(work);
|
|
assert.equal(out.trim(), '', 'manglende id -> tom stdout');
|
|
});
|
|
});
|
|
|
|
test('ugyldig id: exit 0, tom stdout', () => {
|
|
withWork((work) => {
|
|
writeConfig(work, '---\nid: "ugyldig"\n---\n');
|
|
const out = runHook(work);
|
|
assert.equal(out.trim(), '', 'id som ikke matcher T/Q-moenster -> tom stdout');
|
|
});
|
|
});
|
|
|
|
test('gyldig syklus: emitterer systemMessage', () => {
|
|
withWork((work) => {
|
|
writeConfig(work, '---\nid: "T2-2026"\n---\n');
|
|
const out = runHook(work);
|
|
assert.match(out, /OKR coaching/, 'gyldig syklus skal emittere coaching-melding');
|
|
});
|
|
});
|
|
|
|
test('OKR_NOW tidlig fase: early-coaching', () => {
|
|
withWork((work) => {
|
|
writeConfig(work, '---\nid: "T2-2026"\n---\n');
|
|
const out = runHook(work, '2026-05-05');
|
|
assert.match(out, /Tidlig i syklusen/, 'uke 1 av T2 -> tidlig fase');
|
|
});
|
|
});
|
|
|
|
test('OKR_NOW midtveis fase: mid-coaching', () => {
|
|
withWork((work) => {
|
|
writeConfig(work, '---\nid: "T2-2026"\n---\n');
|
|
const out = runHook(work, '2026-06-20');
|
|
assert.match(out, /Midtveis i syklusen/, 'uke ~8 av T2 -> midtveis fase');
|
|
});
|
|
});
|
|
|
|
test('at-risk telles per status-RAD (tabell), ikke raaforekomster i prosa (B2/M1)', () => {
|
|
withWork((work) => {
|
|
writeConfig(work, '---\nid: "T2-2026"\n---\n');
|
|
const statusDir = join(work, '.claude', 'okr', 'syklus', 'T2-2026');
|
|
mkdirSync(statusDir, { recursive: true });
|
|
// 2 markerte rader + markoer-ord i forklaring OG prosa: kun radene skal telle.
|
|
writeFileSync(join(statusDir, 'status.md'), [
|
|
'# Status T2-2026',
|
|
'',
|
|
'Merk: "I fare" betyr flat trend; "Blokkert" betyr ingen fremgang.',
|
|
'',
|
|
'| KR | Baseline | Maal | Naa | Score | Status |',
|
|
'|----|----------|------|-----|-------|--------|',
|
|
'| KR1: Redusere ulykker | 40 | 30 | 35 | 0.50 | I fare |',
|
|
'| KR2: Oppetid | 10 | 25 | 22 | 0.80 | Paa sporet |',
|
|
'| KR3: Tilfredshet | 60% | 90% | 65% | 0.17 | Blokkert |',
|
|
'',
|
|
'KR1 er i fare fordi trenden er flat. KR3 er blokkert av leverandoer.',
|
|
'',
|
|
].join('\n'));
|
|
const out = runHook(work, '2026-06-20'); // midtveis -> at-risk rapporteres
|
|
assert.match(out, /OBS: 2 KR er merket/, '2 markerte rader -> teller noeyaktig 2 (ikke 6)');
|
|
});
|
|
});
|
|
|
|
test('OKR_NOW sen fase: late-coaching', () => {
|
|
withWork((work) => {
|
|
writeConfig(work, '---\nid: "T2-2026"\n---\n');
|
|
const out = runHook(work, '2026-08-25');
|
|
assert.match(out, /sluttspurt/, 'uke ~16 av T2 -> sen fase');
|
|
});
|
|
});
|