Pure diff/fold + run-mode selector for course detection (C3.2 av GODKJENT C3-spec). - lib/course-diff.mjs (REN, null imports): normalizeCourse, selectMode, isFullEnumDue, diffCourses, updateRegistry, FULL_ENUM_MAX_AGE_DAYS=30. Full-enum-pin (§4.2): removed beregnes KUN i full-modus; inkrementell og baseline gir removed:[]. Baseline emitterer ingen leads (§8 b). updateRegistry ren (muterer aldri): incremental merger, baseline/full bygger fra API-sett + dropper retirerte UIDs, stamper last_full_enum. - registry-io.mjs +loadCourseRegistry/saveCourseRegistry (additivt, speiler loadRegistry/saveRegistry; fil course-registry.json). - test-course-diff.test.mjs (23) + test-courses-invariant (+3: diff-modul ren). Gate §7 C3.2 møtt. kb-update 237->263, validate PASSED, null regresjon. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
109 lines
3.3 KiB
JavaScript
109 lines
3.3 KiB
JavaScript
// registry-io.mjs — Atomic read/write for url-registry.json and report files.
|
|
// Zero dependencies. Uses rename() for atomic writes.
|
|
|
|
import { readFileSync, writeFileSync, renameSync, existsSync, mkdirSync } from 'node:fs';
|
|
import { join, dirname } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const DEFAULT_DATA_DIR = join(__dirname, '..', 'data');
|
|
|
|
/**
|
|
* Load the URL registry from disk.
|
|
* @param {string} [dataDir] — defaults to ../data/ relative to lib/
|
|
* @returns {object} parsed registry or empty scaffold
|
|
*/
|
|
export function loadRegistry(dataDir = DEFAULT_DATA_DIR) {
|
|
const path = join(dataDir, 'url-registry.json');
|
|
if (!existsSync(path)) {
|
|
return {
|
|
version: 1,
|
|
created_at: null,
|
|
last_poll: null,
|
|
sitemap_state: {},
|
|
urls: {},
|
|
};
|
|
}
|
|
return JSON.parse(readFileSync(path, 'utf8'));
|
|
}
|
|
|
|
/**
|
|
* Save the URL registry atomically (write to .tmp, then rename).
|
|
* @param {object} registry
|
|
* @param {string} [dataDir]
|
|
*/
|
|
export function saveRegistry(registry, dataDir = DEFAULT_DATA_DIR) {
|
|
ensureDir(dataDir);
|
|
const path = join(dataDir, 'url-registry.json');
|
|
const tmp = path + '.tmp';
|
|
writeFileSync(tmp, JSON.stringify(registry, null, 2) + '\n', 'utf8');
|
|
renameSync(tmp, path);
|
|
}
|
|
|
|
/**
|
|
* Load the course registry (C3 detector's private diff-state — mirrors the URL
|
|
* registry but UID-keyed). Absent file → empty scaffold with the full-ISO
|
|
* cursors (last_run + last_full_enum) the diff/mode logic relies on.
|
|
* @param {string} [dataDir] — defaults to ../data/ relative to lib/
|
|
* @returns {object} parsed course registry or empty scaffold
|
|
*/
|
|
export function loadCourseRegistry(dataDir = DEFAULT_DATA_DIR) {
|
|
const path = join(dataDir, 'course-registry.json');
|
|
if (!existsSync(path)) {
|
|
return {
|
|
version: 1,
|
|
created_at: null,
|
|
last_run: null,
|
|
last_full_enum: null,
|
|
courses: {},
|
|
};
|
|
}
|
|
return JSON.parse(readFileSync(path, 'utf8'));
|
|
}
|
|
|
|
/**
|
|
* Save the course registry atomically (write to .tmp, then rename).
|
|
* @param {object} registry
|
|
* @param {string} [dataDir]
|
|
*/
|
|
export function saveCourseRegistry(registry, dataDir = DEFAULT_DATA_DIR) {
|
|
ensureDir(dataDir);
|
|
const path = join(dataDir, 'course-registry.json');
|
|
const tmp = path + '.tmp';
|
|
writeFileSync(tmp, JSON.stringify(registry, null, 2) + '\n', 'utf8');
|
|
renameSync(tmp, path);
|
|
}
|
|
|
|
/**
|
|
* Load a JSON report file (change-report.json or discovery-report.json).
|
|
* @param {string} name — filename without path (e.g. 'change-report.json')
|
|
* @param {string} [dataDir]
|
|
* @returns {object|null} parsed JSON or null if not found
|
|
*/
|
|
export function loadReport(name, dataDir = DEFAULT_DATA_DIR) {
|
|
const path = join(dataDir, name);
|
|
if (!existsSync(path)) return null;
|
|
try {
|
|
return JSON.parse(readFileSync(path, 'utf8'));
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save a JSON report file atomically.
|
|
* @param {string} name
|
|
* @param {object} data
|
|
* @param {string} [dataDir]
|
|
*/
|
|
export function saveReport(name, data, dataDir = DEFAULT_DATA_DIR) {
|
|
ensureDir(dataDir);
|
|
const path = join(dataDir, name);
|
|
const tmp = path + '.tmp';
|
|
writeFileSync(tmp, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
renameSync(tmp, path);
|
|
}
|
|
|
|
function ensureDir(dir) {
|
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
}
|