import { spawnSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; const proofDir = process.env.PROJECTBRAIN_PROOF_ARCHIVE_DIR ?? "checkpoints/release-proof"; const signoffDir = process.env.PROJECTBRAIN_SIGNOFF_ARCHIVE_DIR ?? "checkpoints/release-signoff"; const deploymentProofDir = process.env.PROJECTBRAIN_DEPLOY_PROOF_ARCHIVE_DIR ?? "checkpoints/deployment-proof"; const dossierOut = process.env.PROJECTBRAIN_RELEASE_DOSSIER_OUT; const run = (command, args) => spawnSync(command, args, { encoding: "utf8", shell: process.platform === "win32", }); const getGitCommit = () => { const result = run("git", ["rev-parse", "--short", "HEAD"]); return result.status === 0 ? result.stdout.trim() : "unknown"; }; const parseField = (text, label) => { const match = text.match(new RegExp(`^${label}:\\s*(.+)$`, "m")); return match?.[1]?.trim() ?? "unknown"; }; const displayPath = (filePath) => filePath.split(path.sep).join("/"); const listMarkdownFiles = (archiveDir) => { if (!fs.existsSync(archiveDir)) return []; return fs.readdirSync(archiveDir) .filter((fileName) => fileName.endsWith(".md")) .map((fileName) => path.join(archiveDir, fileName)) .filter((filePath) => fs.statSync(filePath).isFile()); }; const sortNewestFirst = (items) => items.sort((a, b) => { const generatedDelta = Date.parse(b.generated) - Date.parse(a.generated); if (Number.isFinite(generatedDelta) && generatedDelta !== 0) return generatedDelta; return b.fileName.localeCompare(a.fileName); }); const parseArtifact = (filePath, type) => { const text = fs.readFileSync(filePath, "utf8"); const generated = parseField(text, "Generated"); const commit = type === "deployment" ? parseField(text, "Local commit") : parseField(text, "Commit"); return { type, text, filePath, displayPath: displayPath(filePath), fileName: path.basename(filePath), generated, commit, verdict: parseField(text, "Verdict"), freshness: type === "deployment" ? parseField(text, "Commit freshness") : parseField(text, "Freshness"), }; }; const findLatestCurrentArtifact = (archiveDir, type, currentCommit) => { const artifacts = sortNewestFirst(listMarkdownFiles(archiveDir).map((filePath) => parseArtifact(filePath, type))); return artifacts.find((artifact) => { if (artifact.commit !== currentCommit) return false; if (type === "proof") return artifact.verdict === "PASS"; if (type === "signoff") return artifact.freshness === "current"; if (type === "deployment") return artifact.verdict === "PASS" && artifact.freshness === "current"; return false; }); }; const indentArtifact = (artifact) => artifact.text .split("\n") .map((line) => line ? `> ${line}` : ">") .join("\n"); const createDefaultDossierPath = (commit) => { const stamp = new Date().toISOString().replace(/[:.]/g, "-"); return `checkpoints/release-dossier/projectbrain-release-dossier-${commit}-${stamp}.md`; }; const currentCommit = getGitCommit(); const proof = findLatestCurrentArtifact(proofDir, "proof", currentCommit); const signoff = findLatestCurrentArtifact(signoffDir, "signoff", currentCommit); const deployment = findLatestCurrentArtifact(deploymentProofDir, "deployment", currentCommit); const missing = [ proof ? null : "current passing release proof", signoff ? null : "current release signoff", deployment ? null : "current passing deployment proof", ].filter(Boolean); if (missing.length) { console.error("Cannot create release evidence dossier: missing required current artifacts."); for (const item of missing) console.error(`- ${item}`); console.error("Run: PROJECTBRAIN_RELEASE_DEPLOYMENT_PROOF=1 pnpm run release:bundle"); process.exit(1); } const dossierPath = dossierOut || createDefaultDossierPath(currentCommit); const dossier = [ "# ProjectBrain Release Evidence Dossier", "", `Generated: ${new Date().toISOString()}`, `Commit: ${currentCommit}`, `Verdict: PASS`, "", "## Included Artifacts", "", "| Artifact | Generated | Source |", "| --- | --- | --- |", `| Release proof | ${proof.generated} | ${proof.displayPath} |`, `| Operator signoff | ${signoff.generated} | ${signoff.displayPath} |`, `| Deployment proof | ${deployment.generated} | ${deployment.displayPath} |`, "", "## Operator Summary", "", "- Release operator smoke suite passed for the current commit.", "- Release proof freshness is current.", "- Operator signoff file exists for the current commit.", "- Remote deployment proof passed for the current commit.", "- Tower health and ProjectBrain containers were proven by the deployment proof.", "", "## Release Proof", "", indentArtifact(proof), "", "## Operator Signoff", "", indentArtifact(signoff), "", "## Remote Deployment Proof", "", indentArtifact(deployment), "", "## Tower Commands", "", "```bash", "cd /mnt/pool/appdata/brain", "docker compose build --no-cache frontend", "docker compose up -d", "```", ].join("\n"); fs.mkdirSync(path.dirname(dossierPath), { recursive: true }); fs.writeFileSync(dossierPath, `${dossier}\n`, "utf8"); console.log(dossier); console.log(`\nRelease evidence dossier written to ${dossierPath}`);