ktg-plugin-marketplace/scripts/okf-frontmatter.mjs
Kjell Tore Guttormsen c4b776e4c6 fix(okf-check): required and recommended fields are read at top level only
The frontmatter reader matched `^\s*<key>:` with the m flag — indentation-
agnostic, so a block-form nested entry satisfied a top-level lookup. Measured
against okf/SPEC.md at frozen 3fcbb9f, this is a field confusion, not a near
miss: :467-469 names `resource`, `sources[].resource`, `executor.resource` and
`attester.resource` as DISTINCT fields. Top-level `resource` is the URI of the
asset a concept describes (:196); `sources[].resource` is the material it
derives from (:302). `sources` entries carry their own `title` and `type` too.

The consequence was not confined to warnings. Measured before the fix, a
concept with NO top-level `type:` and a `sources[].type` reported "0 files
without type: / OK: valid OKF bundle" — a false negative on §4.1's only
always-required field. `untyped` IS in the parity signature
(check-okf-parity.mjs:73-76), but okr vendors the same regex, so both impls
were blind identically and the gate stayed green while both were wrong.

Anchoring the key at column 0 fixes it. Flow-form never had the bug: in
`sources: [{ id: s1, resource: fixture }]` the nested key is mid-line, so `^`
cannot match it — measured against llm-ingestion-okf's v0.2 golden bundle
(6e0a7c0, read-only), which warns about `resource` and `description` both
before and after.

The divergence from okr is deliberate and is NOT okr lagging. Their reader is
SHARED, and the nested match is documented as load-bearing for their injector
(lib/frontmatter.mjs:7-9 -> inject:69) — while the same module backs their
scripts/okf-check.mjs:101, which needs the opposite. Pinned as parity fixture
`red-nested-key` (catalog FAILs on the nested type, okr passes it), so the
split is a running red/green signal instead of a note. It flips to `agree`
only if okr scopes the checker's reader without touching inject.

Correcting two premises carried in from the previous session, both measured:
- The reader was NOT flat/top-level-only. It read nested keys, so the suspected
  false POSITIVE on `resource` was actually a false NEGATIVE, opposite sign.
- "No v0.2 bundle exists" held for our own corpora and emitters only.
  llm-ingestion-okf ships a v0.2 golden bundle, where the previous commit's
  version-conditional list has real effect — and behaves correctly there.

docs/okf-second-brain/spec.md is untouched deliberately: it makes no claim
about key scope, so gate and convention do not disagree here.

Tests 103 -> 106 (okf-check 22 -> 25), parity 9/9 -> 10/10. All six suites
green; check-versions 11 OK / 0 WARN / 0 ERROR.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RGZGiDPYcHUMSDCVJavRhp
2026-08-01 19:57:29 +02:00

45 lines
2.1 KiB
JavaScript

// 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 };
}