#!/usr/bin/env node // report-verified-staleness.mjs — Spor 3 Port 3, INCREMENTAL cadence pass. // Compares each tracked source's sitemap_lastmod to the reference file's // **Verified:** header (the Port 1 born-verified stamp) and reports the files // whose source changed AFTER they were last verified — the cheap, lastmod- // visible drift the periodic full-pass (P3d) complements. // // Run on the KB-refresh cadence, NOT on SessionStart (the SessionStart hook // only READS the cached report this writes — the reporting floor, P3b). All // logic lives in lib/verified-staleness.mjs (pure, unit-tested); this file is // I/O glue mirroring report-changes.mjs. // // Usage: node report-verified-staleness.mjs [--json] import { readFileSync, existsSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; import { loadRegistry, saveReport } from './lib/registry-io.mjs'; import { parseTypeHeader, parseVerifiedHeader } from './lib/kb-headers.mjs'; import { buildStalenessReport } from './lib/verified-staleness.mjs'; const __dirname = dirname(fileURLToPath(import.meta.url)); const PLUGIN_ROOT = join(__dirname, '..', '..'); const DATA_DIR = join(__dirname, 'data'); const jsonOnly = process.argv.includes('--json'); // Read a reference file's Port 1 headers, or null if the file is gone on disk // (registry hygiene, not a staleness signal — buildStalenessReport skips it). // Only the top 500 bytes are read; both header parsers scan that region. function headerReader(refFile) { const full = join(PLUGIN_ROOT, refFile); if (!existsSync(full)) return null; const head = readFileSync(full, 'utf8').slice(0, 500); return { type: parseTypeHeader(head), verified: parseVerifiedHeader(head) }; } const registry = loadRegistry(DATA_DIR); if (!registry.last_poll) { console.error('Registry has not been polled yet. Run poll-sitemaps.mjs first.'); process.exit(1); } const today = new Date().toISOString().split('T')[0]; const report = buildStalenessReport(registry, headerReader, { today }); saveReport('verified-staleness-report.json', report, DATA_DIR); if (jsonOnly) { process.stdout.write(JSON.stringify(report, null, 2) + '\n'); } else { const c = report.counts; console.log(`\n=== Verified-staleness Report (${report.generated_at}) — incremental ===`); console.log(`Sources last polled: ${report.last_poll}`); console.log( `Reference files w/ tracked sources: ${c.total} ` + `(fresh: ${c.fresh}, stale-since-verified: ${c.stale}, unverified: ${c.unverified}, unmigrated: ${c.unmigrated}, out-of-scope: ${c.outOfScope})`, ); if (report.flagged.length > 0) { console.log(`\nFlagged for re-judge (${report.flagged.length}):`); for (const f of report.flagged.slice(0, 30)) { console.log(` [${f.reason}] ${f.path}`); console.log(` verified: ${f.verified ?? '—'} source changed: ${f.newestSourceLastmod ?? '—'}`); } if (report.flagged.length > 30) console.log(` ... and ${report.flagged.length - 30} more`); console.log('\nNext: human-confirm each flag against its source, fix, then bump **Verified:** (never auto-fix).'); } else { console.log('\nNo lastmod-visible drift since last verification.'); } }