#!/usr/bin/env node import crypto from "node:crypto"; import fs from "node:fs"; import path from "node:path"; import process from "node:process"; const root = process.cwd(); const required = ["README.md", "SECURITY.md", "CONTRIBUTING.md", "LICENSE", "go.mod", "package.json", "deploy/compose.yaml", ".gitea/workflows/public-validation.yml", "PUBLIC_SOURCE_EXPORT.md", "PUBLIC_SOURCE_MANIFEST.json"]; const missing = required.filter((entry) => !fs.existsSync(path.join(root, entry))); if (missing.length) { console.error(`Missing public source files: ${missing.join(", ")}`); process.exit(1); } for (const privatePath of [".agents", ".codex", "artifacts", "design", "planning", "prompts"]) { if (fs.existsSync(path.join(root, privatePath))) { console.error(`Private-only path present in public export: ${privatePath}`); process.exit(1); } } const manifest = JSON.parse(fs.readFileSync(path.join(root, "PUBLIC_SOURCE_MANIFEST.json"), "utf8")); if (!Array.isArray(manifest.files) || manifest.files.length === 0) { console.error("PUBLIC_SOURCE_MANIFEST.json contains no files."); process.exit(1); } for (const entry of manifest.files) { const target = path.join(root, ...entry.path.split("/")); if (!fs.existsSync(target)) { console.error(`Manifest file is missing: ${entry.path}`); process.exit(1); } const bytes = fs.readFileSync(target); const digest = crypto.createHash("sha256").update(bytes).digest("hex"); if (digest !== entry.sha256 || bytes.length !== entry.bytes) { console.error(`Manifest mismatch: ${entry.path}`); process.exit(1); } } console.log(`Public source structure and manifest passed (${manifest.files.length} allowlisted files).`);