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
This commit is contained in:
parent
99d164e31e
commit
c06e4d7b55
5 changed files with 262 additions and 6 deletions
32
scripts/okf-frontmatter.mjs
Normal file
32
scripts/okf-frontmatter.mjs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// 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 };
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue