import { createHash } from "node:crypto"; import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs"; import { join, relative, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const root = resolve(fileURLToPath(new URL("..", import.meta.url))); const packageJson = JSON.parse(readFileSync(join(root, "package.json"), "utf8")); function walk(path) { if (!existsSync(path)) throw new Error(`Missing packaged path: ${relative(root, path)}`); if (statSync(path).isFile()) return [path]; return readdirSync(path, { withFileTypes: true }).flatMap((entry) => walk(join(path, entry.name))); } const candidates = packageJson.files .filter((item) => item !== "RELEASE.json") .flatMap((item) => walk(join(root, item))); candidates.push(join(root, "package.json")); const sourceInventory = [...new Set(candidates)] .sort() .map((path) => { const data = readFileSync(path); return { path: relative(root, path).replaceAll("\\", "/"), bytes: data.length, sha256: createHash("sha256").update(data).digest("hex"), }; }); function readGate(file, gate) { const path = join(root, file); if (!existsSync(path)) return { status: "NOT RUN", reason: `Missing evidence file: ${file}` }; try { const data = JSON.parse(readFileSync(path, "utf8")); const passed = data.passed === true || (data.summary && data.summary.passed === true); return { status: passed ? "PASS" : "FAIL", timestamp: data.timestamp || null, scope: data.scope || gate, command: data.command || null, details: data, }; } catch { return { status: "NOT RUN", reason: `Unparsable evidence: ${file}` }; } } const gates = { typescript: readGate("00_admin/validation/typecheck.json", "tsc --noEmit"), behavioralTests: readGate("00_admin/validation/tests.json", "npm test"), runtimeSmoke: readGate("00_admin/validation/smoke.json", "npm run smoke"), structuralVerification: readGate("00_admin/validation/structural.json", "node scripts/verify.mjs"), themeSchemaValidation: readGate("00_admin/validation/theme.json", "theme schema validation"), npmPackDryRun: readGate("00_admin/validation/pack.json", "npm pack --dry-run"), }; const releaseDate = new Date().toISOString().split("T")[0]; const release = { name: "POM — Produce Magnum Opus", package: packageJson.name, version: packageJson.version, schemaVersion: "2.0.0", releaseDate, status: "release-candidate", summary: "OMP-native production kernel with evidence-gated stages, native task/IRC delegation, optional Swarm DAGs, Obsidian/Quartz knowledge projection, cinematic TUI hooks, and verified archive delivery.", compatibility: { bun: packageJson.engines.bun, ompPackages: ">=16.4.6 <17", module: "ESM", }, inventory: { stages: 9, customTools: 7, agents: 14, prompts: 13, rules: 3, themes: 2, behavioralTests: 9, sourceFilesHashed: sourceInventory.length, sourceBytesHashed: sourceInventory.reduce((sum, item) => sum + item.bytes, 0), selfHashPolicy: "RELEASE.json is excluded from its own source inventory; final package and archive hashes are recorded externally.", }, verification: { typescript: gates.typescript, behavioralTests: gates.behavioralTests, runtimeSmoke: gates.runtimeSmoke, structuralVerification: gates.structuralVerification, themeSchemaValidation: gates.themeSchemaValidation, npmPackDryRun: gates.npmPackDryRun, productionDependencyAudit: { status: "NOT RUN", reason: "Requires network access to npm registry" }, fullDevelopmentTreeAudit: { status: "NOT RUN", reason: "Requires network access to npm registry" }, ompPluginDoctor: { status: "NOT RUN", reason: "Requires Bun + OMP installation" }, interactiveTuiSmoke: { status: "NOT RUN", reason: "Requires a live OMP terminal host" }, }, releaseGates: [ "Run npm run verify", "Run npm run doctor in a Bun + OMP installation", "Open /pom and verify the overlay, HUD, status, cards, and /pom theme install", "Create a smoke project, validate stage 0, and verify a checkpoint archive", ], sourceInventory, }; const rendered = `${JSON.stringify(release, null, 2)}\n`; const output = join(root, "RELEASE.json"); if (process.argv.includes("--check")) { if (!existsSync(output) || readFileSync(output, "utf8") !== rendered) { console.error("RELEASE.json is stale; run npm run release:manifest"); process.exit(1); } console.log("POM release manifest PASS"); } else { writeFileSync(output, rendered); console.log(`Wrote RELEASE.json · ${sourceInventory.length} files · ${release.inventory.sourceBytesHashed} bytes`); }