compose-org-profile.mjs prepender top-level OKF-noekler (type: Organisasjonsprofil/resource/timestamp) over den nestede org-profilen i ETT frontmatter-blokk, pipes til write-org-profile.mjs (uendret). Kun de tre trygge noeklene top-level; FORBID nye top-level navn/id/fase/domene/ sektor som ville skygge nestede verdier hooken leser. oppsett.md (begge profil-skriv-sites) + template oppdatert. Test: compose->pip->readback + inject-roundtrip (4), full suite 51->55. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SFW5scLL7oEwWWTv1fPQG6
65 lines
2.7 KiB
JavaScript
65 lines
2.7 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
// compose-org-profile.mjs
|
|
// Purpose: wrap the NESTED org-profile YAML body (organisasjon:/program: ...,
|
|
// read from stdin) in an OKF-compatible frontmatter document, prepending the
|
|
// three top-level OKF keys the second-brain "Documents/kb" layout expects on a
|
|
// concept file: type / resource / timestamp. Emits the full document to stdout,
|
|
// meant to be piped into write-org-profile.mjs (which writes it atomically to
|
|
// the reinstall-surviving home path ~/.claude/okr/org/profil.md).
|
|
//
|
|
// Why a dedicated composer (not lib/frontmatter.mjs writeFrontmatter): the org
|
|
// profile is NESTED (organisasjon: { navn, type }, program: { ... }). The flat
|
|
// writeFrontmatter would flatten/destroy that structure. We instead keep the
|
|
// caller's nested YAML verbatim and only PREPEND OKF keys above it.
|
|
//
|
|
// Why ONLY type/resource/timestamp top-level: the home profile is read by the
|
|
// flat parser (lib/frontmatter.mjs) in inject-okr-context.mjs via
|
|
// get('navn')/get('id')/get('domene')/... A NEW top-level key colliding with
|
|
// any of those (navn/name/id/fase/domene/sektor) would be matched FIRST and
|
|
// shadow the nested value, breaking org/cycle resolution (the Critical risk).
|
|
// type/resource/timestamp are read by NO consumer, so they are safe to prepend.
|
|
// The nested organisasjon.type ("offentlig") is untouched and still unread.
|
|
//
|
|
// Single-block invariant: the OKF keys and the nested body MUST live in ONE
|
|
// frontmatter block. A stray intermediate '---' would truncate the block the
|
|
// flat parser reads (dropping organisasjon: -> get('navn') === null -> no
|
|
// injection), so any fences the caller already put around the body are stripped
|
|
// before re-wrapping.
|
|
//
|
|
// Testable clock seam: OKR_NOW (ISO-8601) overrides the wall clock so the
|
|
// emitted timestamp is deterministic under test.
|
|
//
|
|
// Zero npm dependencies (node: builtins only). ASCII-clean identifiers.
|
|
|
|
import { readFileSync } from 'node:fs';
|
|
|
|
// OKF resource pointer: where the canonical org profile lives. A bare string
|
|
// pointer (OKF: "URL/path to the source"); no consumer reads it.
|
|
const RESOURCE = '~/.claude/okr/org/profil.md';
|
|
|
|
let body = '';
|
|
try {
|
|
body = readFileSync(0, 'utf8');
|
|
} catch {
|
|
body = '';
|
|
}
|
|
|
|
// Strip a leading/trailing frontmatter fence if the caller already wrapped the
|
|
// body, so the result is exactly one frontmatter block (see single-block note).
|
|
body = body.replace(/^\s*---\s*\r?\n/, '');
|
|
body = body.replace(/\r?\n---\s*\r?\n?\s*$/, '\n');
|
|
body = body.replace(/\s+$/, '');
|
|
|
|
const timestamp = process.env.OKR_NOW || new Date().toISOString();
|
|
|
|
const lines = [
|
|
'---',
|
|
'type: Organisasjonsprofil',
|
|
`resource: ${RESOURCE}`,
|
|
`timestamp: '${timestamp}'`,
|
|
];
|
|
if (body) lines.push(body);
|
|
lines.push('---', '');
|
|
|
|
process.stdout.write(lines.join('\n'));
|