okr/lib/frontmatter.mjs
Kjell Tore Guttormsen 482effbad1 fix(okr): B2 ingestion-kode-hygiene + bump 1.7.1 (patch-lane)
8 hygiene-fikser fra review 2026-07-16 par.4 kode-lista, TDD roed-groenn
(suite 138 -> 149):
- innboks-split: slugify translittererer ae/oe (datatap-fiks)
- innboks-frontmatter: beskrivende feil ved manglende sourceMtime
- okf-index: --okf-version bumper eksisterende rot, flagg-tolerant CLI
  (exit 2 ved manglende verdi), sanitizeEntry strip C1/zero-width/bidi/
  Unicode-tag
- write-org-profile: circuit-breaker MERGER i stedet for aa overskrive
  full config (M4); test beviser at eksisterende config overlever
- compose-org-profile: intern ----linje trunkerer ikke blokken
- coaching-hook: at-risk teller status-markerte tabellrader (M1/m1)
- inject-okr-context: topic-guard treffer boeyningsformer (maalene)
- frontmatter: BOM/CRLF-toleranse (falsk mangler-type-fiks)

Versjonsflater bumpet til 1.7.1 (package/plugin/lock/CLAUDE/README/
SKILL x2/package-shape-test) + CHANGELOG 1.7.1-seksjon (B1+B2).
Release-tag + katalog-ref venter paa [G-B] operatoer-go.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 04:02:10 +02:00

70 lines
2.9 KiB
JavaScript

// frontmatter.mjs
// Delt frontmatter-parse/skrive for okr-pluginen. Zero npm dependencies.
//
// Konsoliderer den dupliserte flate `get()`-parseren fra inject-okr-context.mjs
// (:59-66) og coaching-hook.mjs (:20-27) til EN modul, med to korreksjoner mot
// den gamle atferden:
// 1. Linjeanker (`^\s*key:`, multiline) — key matcher kun ved linjestart
// (modulo innrykk), aldri som substring midt i en annen key/verdi. Bevarer
// first-match og nestet (innrykket) oppslag (load-bearing: inject:69).
// 2. Trailing " #kommentar" strippes KUN fra USITERTE verdier. Siterte verdier
// beholder en intern '#' ("A #B" -> A #B). Retter comment-leak-bugen der
// `okr_frikoblet_fra_loenn: true # ...` lakk kommentaren inn i verdien.
//
// Tolererer fler-linje OKF-list-verdier (f.eks. `tags:`) uten krasj: get() paa
// en list-key returnerer null (rest-av-linja er tom); list-elementer paa
// foelgende linjer konsumeres aldri (ingen konsument leser tre-filenes `tags`).
const FM_RE = /^---\n([\s\S]*?)\n---/;
export function parseFrontmatter(content) {
// B2: toler UTF-8 BOM foran forste fence + CRLF-linjeskift (Windows-produserte
// filer) -- ellers bommer FM_RE og fila rapporteres falskt som "mangler type".
const normalized = String(content).replace(/^\uFEFF/, '').replace(/\r\n/g, '\n');
const match = normalized.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); // intern '#' bevart
v = v.slice(1); // uavsluttet quote: fall tilbake til resten
} else {
v = v.replace(/\s+#.*$/, '').trim(); // usitert: strip trailing kommentar
}
return v === '' ? null : v;
};
return { raw, get };
}
// Siter naar verdien inneholder '#'/':' eller har kant-whitespace, slik at
// round-trip via parseFrontmatter bevarer den eksakt (jf. siter-#-regelen).
const quoteIfNeeded = (s) =>
/[#:]/.test(s) || /^\s|\s$/.test(s) || /^["']/.test(s) ? JSON.stringify(s) : s;
export function writeFrontmatter(fields) {
const lines = ['---'];
for (const [key, value] of Object.entries(fields)) {
// Additiv array-gren (Step 3): emit OKF multi-linje list-verdi (f.eks.
// `tags`) som `key:\n - item`. Lese-siden er uendret -- parseFrontmatter.
// get() returnerer fortsatt null for list-keys. Skalar-grenen under er
// bevart bit-for-bit (kun loftet ut til quoteIfNeeded, samme regel).
if (Array.isArray(value)) {
lines.push(`${key}:`);
for (const item of value) {
lines.push(` - ${quoteIfNeeded(String(item))}`);
}
continue;
}
lines.push(`${key}: ${quoteIfNeeded(String(value))}`);
}
lines.push('---');
return lines.join('\n') + '\n';
}