#!/usr/bin/env bun import fs from "node:fs" import path from "node:path" const ROOT = path.resolve(import.meta.dir, "..") const MANIFEST_PATH = path.join(ROOT, "core", "CORE-MANIFEST.json") const CHECK = process.argv.includes("--check") const REF_RE = /\B#(\d{3,5})\b/g const PLACEHOLDER = "org-internal" interface Mapping { core: string dogfood: string | null sync: string } function fail(message: string): never { console.error(`delink-core: ${message}`) process.exit(1) } if (!fs.existsSync(MANIFEST_PATH)) fail(`manifest not found: ${MANIFEST_PATH}`) let mappings: Mapping[] try { mappings = (JSON.parse(fs.readFileSync(MANIFEST_PATH, "utf8")) as { mappings: Mapping[] }).mappings } catch (e) { fail(`manifest is not valid JSON: ${(e as Error).message}`) } function listTextFiles(absDir: string, base = ""): string[] { const out: string[] = [] for (const entry of fs.readdirSync(absDir, { withFileTypes: true })) { const rel = base ? `${base}/${entry.name}` : entry.name if (entry.isDirectory()) out.push(...listTextFiles(path.join(absDir, entry.name), rel)) else if (/\.(md|yaml|json)$/.test(entry.name)) out.push(rel) } return out } const byteLocked = new Set() for (const m of mappings) { if (m.sync !== "verbatim" && m.sync !== "verbatimDir") continue if (m.sync === "verbatimDir") { const dir = m.core.endsWith("/") ? m.core : `${m.core}/` for (const f of listTextFiles(path.join(ROOT, dir))) byteLocked.add(`${dir}${f}`) } else if (/\.(md|yaml|json)$/.test(m.core)) { byteLocked.add(m.core) } } function transformContent(content: string): { next: string; count: number } { let count = 0 const next = content.replace(REF_RE, (match, _digits: string, offset: number, whole: string) => { const before = whole.slice(Math.max(0, offset - 14), offset) if (before.endsWith(`[${PLACEHOLDER} `)) return match count++ return `[${PLACEHOLDER} #${_digits}]` }) return { next, count } } export function transformContentForTest(content: string): { next: string; count: number } { return transformContent(content) } const files = listTextFiles(path.join(ROOT, "core")) const perFile: { file: string; count: number }[] = [] const deferredLocked: { file: string; count: number }[] = [] let total = 0 let replacedTotal = 0 for (const rel of files) { const fullRel = `core/${rel}` const content = fs.readFileSync(path.join(ROOT, "core", rel), "utf8") const { next, count } = transformContent(content) if (count === 0) continue if (byteLocked.has(fullRel)) { deferredLocked.push({ file: fullRel, count }) total += count continue } perFile.push({ file: fullRel, count }) total += count if (!CHECK) { fs.writeFileSync(path.join(ROOT, "core", rel), next) replacedTotal += count } } if (CHECK) { if (perFile.length > 0) { console.error(`delink-core: ${perFile.length} file(s) still carry replaceable refs:`) for (const p of perFile) console.error(` ${p.file}: ${p.count}`) process.exit(1) } const lockedRefs = deferredLocked.reduce((a, b) => a + b.count, 0) console.log( `delink-core: check ok — 0 replaceable refs; ${deferredLocked.length} byte-locked verbatim mirror(s) carrying ${lockedRefs} refs deferred to dogfood-side delink (Inc 6b)`, ) process.exit(0) } console.log(`delink-core: replaced ${replacedTotal} refs across ${perFile.length} file(s)`) const top = [...perFile, ...deferredLocked].sort((a, b) => b.count - a.count).slice(0, 10) for (const p of top) console.log(` ${p.file}: ${p.count}`) process.exit(0)