fix(okr): okf-index saner title/desc + escape klammer + skip innboks/dot (SC index-integritet)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012HzGPGJ81k1BkUC6UvgX4Y
This commit is contained in:
parent
3b45be70be
commit
dec8a13952
2 changed files with 88 additions and 4 deletions
|
|
@ -26,6 +26,13 @@ import { parseFrontmatter } from '../lib/frontmatter.mjs';
|
|||
|
||||
export const OKF_VERSION = 'kb-layout-2026-06';
|
||||
|
||||
// Drop-zone (raa innboks-filer) + skjulte kataloger (.cache osv.) er ikke nivaaer:
|
||||
// de skal verken faa egen index.md eller listes som underkatalog-peker. Maa
|
||||
// filtreres i BEGGE enumererings-steder (subdir-listing + rekursjon).
|
||||
function isWalkableDir(name) {
|
||||
return !name.startsWith('.') && name !== 'innboks';
|
||||
}
|
||||
|
||||
// "strategisk-kontekst" -> "Strategisk kontekst"
|
||||
function titleFromName(name) {
|
||||
const spaced = name.replace(/[-_]+/g, ' ').trim();
|
||||
|
|
@ -49,16 +56,37 @@ function parseExistingIndex(path) {
|
|||
return result;
|
||||
}
|
||||
|
||||
// Bygg en enkelt entry-linje paa OKF-form. Tom beskrivelse -> dropp ` - d`.
|
||||
// Saner en frontmatter-avledet tittel/beskrivelse for trygg, idempotent emit:
|
||||
// noytraliser markdown-lenker (RAG-injeksjon), strip kontrolltegn + strooe ]/)
|
||||
// som ville korrumpert round-trip-parsen (parseExistingIndex), kollaps whitespace,
|
||||
// og cap lengden. Idempotent: sanitizeEntry(sanitizeEntry(x)) === sanitizeEntry(x).
|
||||
function sanitizeEntry(s) {
|
||||
if (!s) return '';
|
||||
return String(s)
|
||||
.replace(/[\x00-\x1f\x7f]/g, ' ') // kontrolltegn -> mellomrom
|
||||
.replace(/\[([^\]]*)\]\([^)]*\)/g, '$1') // noytraliser markdown-lenker (behold tekst)
|
||||
.replace(/[\])]/g, '') // strip strooe ] ) som brekker round-trip
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim()
|
||||
.slice(0, 200);
|
||||
}
|
||||
|
||||
// Bygg en enkelt entry-linje paa OKF-form. Tittel/beskrivelse saneres (link
|
||||
// forblir uroert -- kontrollert filnavn). Tom beskrivelse -> dropp ` - d`.
|
||||
function entryLine(title, link, desc) {
|
||||
return desc ? `* [${title}](${link}) - ${desc}` : `* [${title}](${link})`;
|
||||
const t = sanitizeEntry(title);
|
||||
const d = sanitizeEntry(desc);
|
||||
return d ? `* [${t}](${link}) - ${d}` : `* [${t}](${link})`;
|
||||
}
|
||||
|
||||
// Generer og skriv index.md for EN katalog (ikke rekursivt). isRoot styrer okf_version.
|
||||
function writeIndexFor(dir, isRoot, okfVersion) {
|
||||
const existing = parseExistingIndex(join(dir, 'index.md'));
|
||||
const dirents = readdirSync(dir, { withFileTypes: true });
|
||||
const subdirs = dirents.filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
||||
const subdirs = dirents
|
||||
.filter((e) => e.isDirectory() && isWalkableDir(e.name))
|
||||
.map((e) => e.name)
|
||||
.sort();
|
||||
const concepts = dirents
|
||||
.filter((e) => e.isFile() && e.name.endsWith('.md') && e.name !== 'index.md')
|
||||
.map((e) => e.name)
|
||||
|
|
@ -95,7 +123,7 @@ export function generateIndexes(root, opts = {}) {
|
|||
const walk = (dir, isRoot) => {
|
||||
writeIndexFor(dir, isRoot, okfVersion);
|
||||
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
||||
if (e.isDirectory()) walk(join(dir, e.name), false);
|
||||
if (e.isDirectory() && isWalkableDir(e.name)) walk(join(dir, e.name), false);
|
||||
}
|
||||
};
|
||||
if (!existsSync(root)) throw new Error(`Bundle-rot finnes ikke: ${root}`);
|
||||
|
|
|
|||
|
|
@ -65,6 +65,16 @@ function allDirs(root) {
|
|||
return out;
|
||||
}
|
||||
|
||||
// Byte-snapshot av alle index.md under root (for idempotens-sammenligning).
|
||||
function snapshotIndexes(root) {
|
||||
return allDirs(root)
|
||||
.map((d) => join(d, 'index.md'))
|
||||
.filter((p) => existsSync(p))
|
||||
.sort()
|
||||
.map((p) => `${p}\n${readFileSync(p, 'utf8')}`)
|
||||
.join('\n=====\n');
|
||||
}
|
||||
|
||||
// --- okf-index ---
|
||||
|
||||
test('okf-index: genererer index.md per nivaa (hver katalog) i temp-kopi', () => {
|
||||
|
|
@ -254,3 +264,49 @@ test('okf-check: raa filer i innboks/ og .cache/ ignoreres av walk (ingen falsk
|
|||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
// --- okf-index herding (Step 9): saner title/desc + skip innboks/dot ---
|
||||
|
||||
test('okf-index: tittel/beskrivelse med ]/) + lenke saneres -> 2x byte-identisk + round-trip-trygg', () => {
|
||||
const dir = tmpRoot();
|
||||
try {
|
||||
mkdirSync(join(dir, 'dokumenter'), { recursive: true });
|
||||
writeFileSync(join(dir, 'index.md'), '# Bundle\n\nokf_version: kb-layout-2026-06\n');
|
||||
writeFileSync(
|
||||
join(dir, 'dokumenter', 'kr.md'),
|
||||
'---\ntype: OKR\ntitle: "KR1 [resultat] (maal) [lenke](http://x)"\n'
|
||||
+ 'description: "Status (delvis) [ref](http://y)"\n---\n# KR\n',
|
||||
);
|
||||
generateIndexes(dir);
|
||||
const run1 = snapshotIndexes(dir);
|
||||
generateIndexes(dir);
|
||||
const run2 = snapshotIndexes(dir);
|
||||
assert.equal(run1, run2, 'generateIndexes skal vaere byte-idempotent');
|
||||
const idx = readFileSync(join(dir, 'dokumenter', 'index.md'), 'utf8');
|
||||
const entry = idx.split('\n').find((l) => l.startsWith('* '));
|
||||
assert.ok(entry, 'kr.md skal ha en entry');
|
||||
assert.match(entry, /^\* \[[^\]]*\]\([^)]+\)/, 'entry skal vaere round-trip-trygg OKF-form');
|
||||
assert.ok(!entry.includes('http'), 'injisert lenke skal vaere noytralisert');
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('okf-index: innboks/ + dot-katalog -> ingen egen index.md OG ikke listet i rot-index', () => {
|
||||
const dir = tmpRoot();
|
||||
try {
|
||||
cpSync(REALISTIC, dir, { recursive: true });
|
||||
mkdirSync(join(dir, 'innboks'), { recursive: true });
|
||||
mkdirSync(join(dir, '.cache'), { recursive: true });
|
||||
writeFileSync(join(dir, 'innboks', 'raa.md'), '# Raa\n');
|
||||
writeFileSync(join(dir, '.cache', 'c.md'), '# Cache\n');
|
||||
generateIndexes(dir);
|
||||
assert.ok(!existsSync(join(dir, 'innboks', 'index.md')), 'innboks/ skal ikke faa index.md');
|
||||
assert.ok(!existsSync(join(dir, '.cache', 'index.md')), 'dot-katalog skal ikke faa index.md');
|
||||
const root = readFileSync(join(dir, 'index.md'), 'utf8');
|
||||
assert.ok(!root.includes('innboks/index.md'), 'rot-index skal ikke liste innboks/ som peker');
|
||||
assert.ok(!root.includes('.cache/index.md'), 'rot-index skal ikke liste dot-katalog som peker');
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue