feat(ms-ai-architect): C3.2 — course-diff (ren) + course-registry load/save (TDD) [skip-docs]

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>
This commit is contained in:
Kjell Tore Guttormsen 2026-06-23 13:12:49 +02:00
commit 94c7f42128
4 changed files with 504 additions and 1 deletions

View file

@ -40,6 +40,40 @@ export function saveRegistry(registry, dataDir = DEFAULT_DATA_DIR) {
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')