feat(okr): deterministisk frontmatter-projeksjon + vokab-snap + mtime-timestamp (SC idempotens) [skip-docs]
This commit is contained in:
parent
136093da38
commit
d011fb1922
2 changed files with 199 additions and 0 deletions
75
lib/innboks-frontmatter.mjs
Normal file
75
lib/innboks-frontmatter.mjs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
// innboks-frontmatter.mjs
|
||||
// Step 5: deterministisk frontmatter-projeksjon for et konsept (fra splitConcepts).
|
||||
// Regel-basert (INGEN LLM): type/tags utledes fra title+sti og snappes mot det
|
||||
// lukkede vokabularet (okf-vocab); resource = original relativ sti (kanonisk navn);
|
||||
// description = foerste ikke-tomme avsnitt (whitespace kollapset, trunkert);
|
||||
// timestamp = ISO-8601 av original-fil-mtime (IKKE veggklokke -> idempotens by
|
||||
// construction per maskin); kilde:innboks provenans-markoer (extension key for
|
||||
// retrieval-rangering). Beregner ogsaa konseptets mal-sti
|
||||
// (concept.destRel = routeLevel(type)/slug.md) FOER relasjons-steget (Step 6),
|
||||
// jf. Pass-2 ordering-fiks (Revisions #21). Serialiserer via writeFrontmatter
|
||||
// (additiv array-gren fra Step 3). Zero npm dependencies.
|
||||
|
||||
import { writeFrontmatter } from './frontmatter.mjs';
|
||||
import { snapType, snapTags, routeLevel, TYPE_VOCAB, TAGS_VOCAB } from './okf-vocab.mjs';
|
||||
|
||||
const DESCRIPTION_MAX = 240;
|
||||
|
||||
// Vokab sortert lengst-foerst saa "Overordnede OKR" matcher foer "OKR".
|
||||
const TYPE_BY_LENGTH = [...TYPE_VOCAB].sort((a, b) => b.length - a.length);
|
||||
|
||||
// Regel-utledning: foerste vokab-term som forekommer i title+sti -> kanonisk type.
|
||||
function deriveType(title, sourcePath) {
|
||||
const hay = `${title} ${sourcePath}`.toLowerCase();
|
||||
for (const t of TYPE_BY_LENGTH) {
|
||||
if (hay.includes(t.toLowerCase())) return snapType(t);
|
||||
}
|
||||
return snapType('');
|
||||
}
|
||||
|
||||
// Tags: vokab-termer som forekommer i title (vokab-rekkefolge bevart), saa snappet.
|
||||
function deriveTags(title) {
|
||||
const hay = String(title).toLowerCase();
|
||||
return snapTags(TAGS_VOCAB.filter((t) => hay.includes(t.toLowerCase())));
|
||||
}
|
||||
|
||||
// Foerste ikke-tomme avsnitt, whitespace kollapset, trunkert deterministisk.
|
||||
function deriveDescription(body) {
|
||||
const para = String(body)
|
||||
.split(/\n\s*\n/)
|
||||
.map((p) => p.replace(/\s+/g, ' ').trim())
|
||||
.find((p) => p !== '');
|
||||
if (!para) return '';
|
||||
return para.length > DESCRIPTION_MAX ? `${para.slice(0, DESCRIPTION_MAX).trimEnd()}...` : para;
|
||||
}
|
||||
|
||||
// concept (fra splitConcepts) -> beriket konsept med OKF-frontmatter + destRel.
|
||||
export function projectFrontmatter(concept, { sourcePath, sourceMtime } = {}) {
|
||||
const resource = String(sourcePath ?? '');
|
||||
const type = deriveType(concept.title, resource);
|
||||
const description = deriveDescription(concept.body);
|
||||
const tags = deriveTags(concept.title);
|
||||
const timestamp = new Date(sourceMtime).toISOString();
|
||||
const destRel = `${routeLevel(type)}/${concept.slug}.md`;
|
||||
|
||||
// Kanonisk noekkel-rekkefolge; description/tags utelates naar tomme.
|
||||
const fields = { type, resource, title: concept.title };
|
||||
if (description) fields.description = description;
|
||||
if (tags.length > 0) fields.tags = tags;
|
||||
fields.timestamp = timestamp;
|
||||
fields.kilde = 'innboks';
|
||||
|
||||
const frontmatter = writeFrontmatter(fields);
|
||||
|
||||
return {
|
||||
...concept,
|
||||
type,
|
||||
resource,
|
||||
description,
|
||||
tags,
|
||||
timestamp,
|
||||
kilde: 'innboks',
|
||||
destRel,
|
||||
frontmatter,
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue