#!/usr/bin/env node // build-gold-set.mjs — Fase 0, steg 2/3 glue: assemble the gold correctness set. // // Consolidates the per-batch verification returns (scripts/kb-eval/data/ // fase0-returns/batch-*.json — produced by Opus 4.8 subagents that checked each // volatile claim against live MS Learn) into a single, reusable gold set, and // joins the DETERMINISTIC lastmod_changed signal in code (never an LLM call): // for each file, did any cited source change on the MS Learn sitemap after the // file's own "last updated" date? That is exactly what the existing KB staleness // loop sees, so it tells us which genuine errors a correctness judge would catch // that the staleness loop would miss. // // The non-trivial logic (date parse, lastmod comparison) lives in tested // lib/base-rate.mjs; this CLI is thin wiring over it. // // Usage: node scripts/kb-eval/build-gold-set.mjs [--write] // (default: print summary; --write: persist gold-correctness-set.json) import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { parseLastUpdated, fileLastmodChanged } from './lib/base-rate.mjs'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const ROOT = path.resolve(__dirname, '..', '..'); const DATA = path.join(__dirname, 'data'); const RETURNS = path.join(DATA, 'fase0-returns'); const frame = JSON.parse(fs.readFileSync(path.join(DATA, 'fase0-sample-frame.json'), 'utf8')); const registry = JSON.parse( fs.readFileSync(path.join(ROOT, 'scripts/kb-update/data/url-registry.json'), 'utf8'), ); // file -> citedUrls (from the deterministic sample frame) const citedByFile = {}; for (const e of [...frame.volatile, ...frame.control]) citedByFile[e.file] = e.citedUrls; // file -> { date, lastmod_changed } (read each file once) const fileMeta = {}; function metaFor(file) { if (fileMeta[file]) return fileMeta[file]; let date = null; try { date = parseLastUpdated(fs.readFileSync(path.join(ROOT, file), 'utf8')); } catch { date = null; } const changed = fileLastmodChanged(citedByFile[file] || [], registry, date); return (fileMeta[file] = { date, lastmod_changed: changed }); } // relpath after skills//references/ for a compact, stable claim id function relOf(file, skill) { const prefix = `skills/${skill}/references/`; return file.startsWith(prefix) ? file.slice(prefix.length) : file; } const claims = []; const batchFiles = fs.readdirSync(RETURNS).filter((f) => /^batch-\d+\.json$/.test(f)).sort(); for (const bf of batchFiles) { const batch = JSON.parse(fs.readFileSync(path.join(RETURNS, bf), 'utf8')); for (const c of batch.claims) { const meta = metaFor(c.file); claims.push({ id: `${c.skill}/${relOf(c.file, c.skill)}#${c.n}`, file: c.file, skill: c.skill, stratum: c.stratum, claim: c.claim, claim_type: c.claim_type, verdict: c.verdict, evidence_url: c.evidence_url ?? null, lastmod_changed: meta.lastmod_changed, file_last_updated: meta.date, notes: c.notes ?? '', }); } } const goldSet = { _meta: { created: '2026-06-26', method: 'Per-file volatile-claim verification against live MS Learn by Opus 4.8 subagents (strict v2 evidence rule: correct/outdated/wrong require a fetched learn.microsoft.com quote; otherwise unsourced). lastmod_changed joined deterministically in code from url-registry sitemap_lastmod vs the file last-updated date.', sample_frame: 'fase0-sample-frame.json', raw_returns: 'fase0-returns/batch-*.json', note_evidence_quote: 'Per-claim evidence_quote omitted here to bound size; full verbatim quotes live in the session transcript. notes + evidence_url capture each verdict basis.', claim_count: claims.length, }, claims, }; if (process.argv.includes('--write')) { const out = path.join(DATA, 'gold-correctness-set.json'); fs.writeFileSync(out, JSON.stringify(goldSet, null, 2) + '\n'); console.log(`wrote ${out} (${claims.length} claims)`); } else { const filesWithChange = Object.entries(fileMeta).filter(([, m]) => m.lastmod_changed === true).length; const filesNoChange = Object.entries(fileMeta).filter(([, m]) => m.lastmod_changed === false).length; const filesUnknown = Object.entries(fileMeta).filter(([, m]) => m.lastmod_changed === null).length; console.log(`claims: ${claims.length} | files: ${Object.keys(fileMeta).length}`); console.log(`file lastmod_changed: true=${filesWithChange} false=${filesNoChange} unknown(null date)=${filesUnknown}`); console.log('(dry run — pass --write to persist gold-correctness-set.json)'); }