// okf-frontmatter.mjs // Minimal TOP-LEVEL frontmatter reader for the shared OKF conformance checker. // Provenance: vendored from okr's lib/frontmatter.mjs at c06e4d7 so the catalog-hosted // checker is self-contained — zero npm dependencies, no cross-repo import. The checker // only reads (never writes), so only parseFrontmatter().get is vendored. okr has since // added BOM/CRLF normalization here (lib/frontmatter.mjs:23); this copy has not, so the // two have diverged and are no longer byte-identical (parity is the parity-gate's job, // not this file's). // // SECOND, DELIBERATE DIVERGENCE (2026-08-01): the key is anchored at column 0 here, while // okr keeps `^\s*key:`. That is not okr lagging — their reader is SHARED, and the nested // match is load-bearing for their injector (lib/frontmatter.mjs:7-9 -> inject:69). A // conformance checker needs the opposite: §4.1's keys are top-level, and `sources[]` // entries carry their own `resource`/`title`/`type`. Pinned as parity fixture // `red-nested-key`. const FM_RE = /^---\n([\s\S]*?)\n---/; export function parseFrontmatter(content) { const match = String(content).match(FM_RE); const raw = match ? match[1] : null; const get = (key) => { if (raw === null) return null; // Anchored at column 0: OKF's §4.1 keys are TOP-LEVEL keys. `sources[]` entries carry their // own `resource`/`title`/`type` (§5:302), and §? :467-469 names them as distinct fields from // the same-named top-level ones. A leading \s* matched those nested entries and reported the // top-level key as present — a false negative, on `type` too (§4.1's only required field). const m = raw.match(new RegExp(`^${key}:\\s*(.*)$`, 'm')); if (!m) return null; let v = m[1].trim(); if (v === '') return null; const q = v[0]; if (q === '"' || q === "'") { const end = v.indexOf(q, 1); if (end !== -1) return v.slice(1, end); // internal '#' preserved v = v.slice(1); // unterminated quote: fall back to the rest } else { v = v.replace(/\s+#.*$/, '').trim(); // unquoted: strip trailing comment } return v === '' ? null : v; }; return { raw, get }; }