ktg-plugin-marketplace/scripts/okf-frontmatter.mjs
Kjell Tore Guttormsen c06e4d7b55 feat(catalog): shared OKF conformance checker + wire as cross-plugin acceptance gate
Lift okr's reference okf-check.mjs into the catalog as the single cross-plugin
acceptance gate for the OKF-compatible second-brain form (spec §3):

- scripts/okf-check.mjs (+ vendored okf-frontmatter.mjs): verdict logic
  byte-identical to okr's reference impl, English output, zero deps, self-contained.
- scripts/okf-check.test.mjs: 5 self-contained tests (temp-dir bundles).
- spec §7 + §14: the shared checker now lives here; only TS/mjs reconciliation
  remains Stage-3, not required for the gate. log.md protocol §6: conformance is
  verified by the gate, not asserted — a plugin moves to 🟢 only after passing it.

Verified: 33/33 catalog tests green (canonical glob form); verdict parity with
okr's checker on okr fixtures (positive + negative missing-type); a scaffolded
linkedin-studio brain/ validates clean (exit 0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GqEHp4uDiivfrAUjw4BdE
2026-06-29 10:27:05 +02:00

32 lines
1.2 KiB
JavaScript

// okf-frontmatter.mjs
// Minimal flat-frontmatter reader for the shared OKF conformance checker.
// Vendored from okr's lib/frontmatter.mjs (the reference implementation) 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. Parsing logic is kept byte-identical to okr's so verdicts stay in parity.
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;
const m = raw.match(new RegExp(`^\\s*${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 };
}