#!/usr/bin/env node import { readFile, writeFile } from 'node:fs/promises'; import { classifyPreviousPage, destinationForPageId } from './lib/previous-wiki.mjs'; const live = JSON.parse(await readFile('to-be-studied/previous-wiki/2026-08-03/manifest.json', 'utf8')); const migration = JSON.parse(await readFile('docs/migration-manifest.json', 'utf8')); const supplemental = JSON.parse(await readFile('docs/supplemental-content.json', 'utf8')); const publishedLiveCourses = new Map(supplemental .filter(({ category }) => category === 'live-course-recovery') .map((entry) => [entry.source, entry.destination])); const represented = new Map(migration.entries.map((entry) => [ entry.source.replace(/^pages\//, '').replace(/\.txt$/, '').replaceAll('/', ':'), entry.destination, ])); const records = []; for (const page of live.pages) { const source = await readFile(`to-be-studied/previous-wiki/2026-08-03/${page.localPath}`, 'utf8'); let status = classifyPreviousPage(page.id, new Set(represented.keys()), source); const plannedDestination = status === 'migrate' ? destinationForPageId(page.id) : ''; if (status === 'migrate' && publishedLiveCourses.get(page.url) === plannedDestination) status = 'migrated'; const destination = status === 'represented' ? represented.get(page.id) : status === 'migrate' || status === 'migrated' ? plannedDestination : ''; const reason = { represented: 'Already represented by the selected export migration.', migrate: 'Substantive public course page missing from the selected export.', migrated: 'Substantive public course page migrated from the live DokuWiki.', empty: 'Explicit placeholder page with no course details.', excluded: 'Outside the Bachelor, Master AI, and Master DSDM course-detail scope.', }[status]; records.push({ id: page.id, url: page.url, status, destination, reason, localPath: page.localPath }); } for (const failure of live.failures) { records.push({ id: failure.id, url: failure.url, status: 'failed', destination: '', reason: failure.error, localPath: '' }); } records.sort((a, b) => a.id.localeCompare(b.id)); const header = ['page_id', 'source_url', 'status', 'destination', 'captured_source', 'reason']; const tsv = [header, ...records.map((record) => [ record.id, record.url, record.status, record.destination, record.localPath ? `to-be-studied/previous-wiki/2026-08-03/${record.localPath}` : '', record.reason, ])].map((row) => row.join('\t')).join('\n'); await writeFile('docs/live-course-recovery.tsv', `${tsv}\n`); const counts = new Map(); for (const record of records) counts.set(record.status, (counts.get(record.status) ?? 0) + 1); const report = `# Previous live-wiki course recovery Audit date: 2026-08-03 The previous DokuWiki was crawled recursively through its public sitemap at the pinned Vimmex origin. The crawl was read-only and completed without authentication. | Outcome | Pages | | --- | ---: | | Already represented in the selected export migration | ${counts.get('represented') ?? 0} | | Course pages migrated | ${counts.get('migrated') ?? 0} | | Course pages still pending | ${counts.get('migrate') ?? 0} | | Empty course placeholders | ${counts.get('empty') ?? 0} | | Outside the course-detail scope | ${counts.get('excluded') ?? 0} | | Capture failures | ${counts.get('failed') ?? 0} | | **Total public pages audited** | **${records.length}** | The selected filesystem export was internally complete for its declared 29-source scope, but it was not a complete export of the public DokuWiki. The recursive live sitemap exposed ${records.length} pages. After excluding ${counts.get('empty') ?? 0} explicit placeholders and non-course material, ${counts.get('migrated') ?? 0} substantive course pages were migrated and ${counts.get('migrate') ?? 0} remain pending. The machine-readable classification and source-to-destination mappings are in \`docs/live-course-recovery.tsv\`. Captured raw sources and the crawl manifest are under \`to-be-studied/previous-wiki/2026-08-03/\`. `; await writeFile('docs/live-course-recovery.md', report); console.log(`Classified ${records.length} pages: ${counts.get('migrated') ?? 0} migrated, ${counts.get('migrate') ?? 0} pending, ${counts.get('represented') ?? 0} represented, ${counts.get('empty') ?? 0} empty, ${counts.get('excluded') ?? 0} excluded, ${counts.get('failed') ?? 0} failed.`);