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
129 lines
5.7 KiB
JavaScript
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);
|
|
}
|