#!/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) /bundle/ is a conformant OKF bundle (§3): every concept file carries `type:` // and the root index.md carries `okf_version`. // (2) /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); }