ktg-plugin-marketplace/scripts/check-nav-golden.mjs
Kjell Tore Guttormsen 325727523d test(okf): consume nav-golden fixtures + thin STEG 0 corpus gate
Consume commons' nav-golden fixture class byte-exact from
portfolio-optimiser-commons @ b641741 (nav-golden-hierarchy positive +
nav-golden-escape boundary) into test/nav-golden-corpus/ and wire a thin
gate scripts/check-nav-golden.mjs (+ .test.mjs, 10 tests).

Scope (operator decision, thin): the catalog is the convention owner and
holds no consumer (null konsument), and the shared retrieval skill is
deferred (spec.md §10 Stage 3). So the gate does NOT run a read-context
navigator and does NOT assert the goldens byte-exact. It asserts only what
the catalog owns: each fixture bundle/ is a conformant OKF bundle under the
existing §3 checkBundle (every concept typed + root okf_version), and the
committed expected-read-context.md golden is present and non-empty. Byte-exact
navigator conformance stays a consumer concern (okr STEG 4 / Stage 3 skill).

Fixtures pinned byte-exact (.gitattributes -text); manifest carries source
repo+SHA per the vendoring-provenance rule. Naming: the stale "§5 pts 1-5"
read-context label maps to the settled method-spec §3 Step 1 (commons 9801d35);
no open §5 dependency. commons confirmed b641741 complete + goldens derived
from §3 Step 1 (via coord).

Verified: check-nav-golden 10/10; gate CLI exit 0 (2 fixtures PASS); full
suite 73/73 across the six test files.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NeK9hkxrU9wFPBYGYnSV1V
2026-07-24 20:35:44 +02:00

129 lines
5.7 KiB
JavaScript

#!/usr/bin/env node
// check-nav-golden.mjs — STEG 0 nav-golden corpus gate (thin: conformance + golden presence).
//
// WHY THIS EXISTS (docs/okf-second-brain/log.md, STEG 0): commons authored a "nav-golden"
// fixture class (examples/nav-golden-* @ b641741) — each fixture is an OKF bundle paired with
// a committed expected-read-context.md golden, the byte-exact fasit a conformant read-context
// navigator (commons method-spec.md §3 Step 1 + §11 Navigation boundary) must produce. The
// catalog consumes those fixtures (test/nav-golden-corpus/) so they stay alive under CI.
//
// SCOPE (honest, thin — by design): the catalog is the CONVENTION OWNER and holds no consumer
// (null konsument), and the shared retrieval skill is explicitly deferred (spec.md §10 Stage 3,
// "do not build before Stage 2 says so"). So this gate does NOT run a navigator and does NOT
// assert the goldens byte-exact. It asserts only what the catalog owns: (1) each fixture's
// bundle/ is a conformant OKF bundle under the existing §3 check (checkBundle: every concept
// typed + root okf_version present), and (2) the committed golden is present and non-empty.
// Byte-exact navigator conformance is a consumer concern (okr STEG 4 / the deferred Stage 3
// skill), NOT catalog's — see docs/okf-second-brain/spec.md.
//
// The runner CHECKS; it does not author the fasit (commons owns the goldens; the catalog owns
// the corpus axes in manifest.json). Zero npm dependencies (node: builtins).
import { readdirSync, existsSync, readFileSync, statSync } from 'node:fs';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { checkBundle } from './okf-check.mjs';
export const GOLDEN_FILE = 'expected-read-context.md';
export const BUNDLE_DIR = 'bundle';
// Check one consumed nav-golden fixture directory. Pure over fixtureDir; no process.exit.
// (1) <fixtureDir>/bundle/ is a conformant OKF bundle (§3): every concept file carries `type:`
// and the root index.md carries `okf_version`.
// (2) <fixtureDir>/expected-read-context.md exists and is non-empty (trimmed).
// Returns { fixture, status, problems[], okf, goldenPresent, goldenNonEmpty }.
export function checkFixture(fixtureDir, name) {
const problems = [];
let okf = null;
const bundleRoot = join(fixtureDir, BUNDLE_DIR);
if (!existsSync(bundleRoot) || !statSync(bundleRoot).isDirectory()) {
problems.push(`missing ${BUNDLE_DIR}/ directory`);
} else {
okf = checkBundle(bundleRoot);
if (okf.missingType.length > 0) {
problems.push(`${BUNDLE_DIR}/ has ${okf.missingType.length} concept file(s) without type: ${okf.missingType.join(', ')}`);
}
if (!okf.okfVersion) {
problems.push(`${BUNDLE_DIR}/ root index.md has no okf_version`);
}
}
const goldenPath = join(fixtureDir, GOLDEN_FILE);
const goldenPresent = existsSync(goldenPath) && statSync(goldenPath).isFile();
let goldenNonEmpty = false;
if (!goldenPresent) {
problems.push(`missing golden ${GOLDEN_FILE}`);
} else {
goldenNonEmpty = readFileSync(goldenPath, 'utf8').trim().length > 0;
if (!goldenNonEmpty) problems.push(`golden ${GOLDEN_FILE} is empty`);
}
return {
fixture: name ?? fixtureDir,
status: problems.length === 0 ? 'PASS' : 'FAIL',
problems,
okf,
goldenPresent,
goldenNonEmpty,
};
}
// Run the whole nav-golden corpus against its manifest. Pure over corpusRoot; no process.exit.
export function runCorpus(corpusRoot) {
const manifestPath = join(corpusRoot, 'manifest.json');
if (!existsSync(manifestPath)) {
throw new Error(`corpus manifest not found: ${manifestPath}`);
}
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
const fixtures = manifest.fixtures ?? {};
const results = [];
for (const [name, spec] of Object.entries(fixtures)) {
const dir = join(corpusRoot, name);
if (!existsSync(dir) || !statSync(dir).isDirectory()) {
results.push({ fixture: name, axis: spec.axis, status: 'MISSING', problems: ['fixture directory absent on disk'] });
continue;
}
const r = checkFixture(dir, name);
results.push({ ...r, axis: spec.axis });
}
// Orphan directories present on disk but absent from the manifest (no silent coverage gaps).
const onDisk = readdirSync(corpusRoot, { withFileTypes: true })
.filter((e) => e.isDirectory())
.map((e) => e.name);
const orphans = onDisk.filter((d) => !(d in fixtures));
const failed = results.filter((r) => r.status === 'FAIL' || r.status === 'MISSING');
return { results, orphans, failed, exitCode: failed.length === 0 ? 0 : 1 };
}
// --- CLI ---
const isMain = process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1];
if (isMain) {
const arg = process.argv[2];
const corpusRoot = arg
? arg
: fileURLToPath(new URL('../test/nav-golden-corpus/', import.meta.url));
const { results, orphans, failed, exitCode } = runCorpus(corpusRoot);
const out = [];
out.push(`Nav-golden corpus gate over: ${corpusRoot}`);
out.push('');
for (const r of results) {
out.push(`${r.fixture.padEnd(22)} [${(r.axis || '').padEnd(24)}] ${r.status}`);
for (const p of r.problems ?? []) out.push(` - ${p}`);
if (r.okf) out.push(` okf: ${r.okf.scanned} concept(s), okf_version=${r.okf.okfVersion}, golden=${r.goldenPresent ? 'present' : 'ABSENT'}`);
}
out.push('');
const pass = results.filter((r) => r.status === 'PASS').length;
out.push(`Summary: ${results.length} fixtures — ${pass} pass, ${failed.length} fail.`);
if (orphans.length) out.push(` ! Unmanifested corpus directories (add to manifest): ${orphans.join(', ')}`);
out.push(exitCode === 0
? 'OK: nav-golden fixtures conform and goldens present.'
: `FAIL: ${failed.length} fixture(s) off expectation.`);
process.stdout.write(`${out.join('\n')}\n`);
process.exit(exitCode);
}