import { spawnSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; const archiveDir = process.env.PROJECTBRAIN_PROOF_ARCHIVE_DIR ?? "checkpoints/release-proof"; const signoffOut = process.env.PROJECTBRAIN_SIGNOFF_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 parseProof = (filePath) => { const text = fs.readFileSync(filePath, "utf8"); return { filePath, displayPath: displayPath(filePath), fileName: path.basename(filePath), generated: parseField(text, "Generated"), commit: parseField(text, "Commit"), baseUrl: parseField(text, "Base URL"), project: parseField(text, "Project"), verdict: parseField(text, "Verdict"), smokeExecution: text.match(/^- Smoke execution:\s*(.+)$/m)?.[1]?.trim() ?? "unknown", }; }; const listProofFiles = () => { 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 createDefaultSignoffPath = (commit) => { const stamp = new Date().toISOString().replace(/[:.]/g, "-"); return `checkpoints/release-signoff/projectbrain-release-signoff-${commit}-${stamp}.md`; }; const currentCommit = getGitCommit(); const proofs = listProofFiles() .map(parseProof) .sort((a, b) => Date.parse(b.generated) - Date.parse(a.generated) || b.fileName.localeCompare(a.fileName)); const latestPassing = proofs.find((proof) => proof.verdict === "PASS"); const freshness = latestPassing?.commit === currentCommit ? "current" : latestPassing ? "stale" : "missing"; if (freshness !== "current") { console.error("Cannot create release signoff file: latest passing proof is not current."); console.error(`Current commit: ${currentCommit}`); console.error(`Proof commit: ${latestPassing?.commit ?? "none"}`); console.error("Run: PROJECTBRAIN_SMOKE_URL=http://127.0.0.1:1209 pnpm run release:bundle"); process.exit(1); } const signoffPath = signoffOut || createDefaultSignoffPath(currentCommit); const signoff = [ "# ProjectBrain Release Operator Signoff", "", `Generated: ${new Date().toISOString()}`, `Commit: ${currentCommit}`, `Project: ${latestPassing.project}`, `Base URL: ${latestPassing.baseUrl}`, `Proof snapshot: ${latestPassing.displayPath}`, `Proof generated: ${latestPassing.generated}`, `Proof verdict: ${latestPassing.verdict}`, `Smoke execution: ${latestPassing.smokeExecution}`, `Freshness: ${freshness}`, "", "## Technical Gates", "", "- [x] Release proof report exists for the current commit.", "- [x] Release operator smoke suite passed.", "- [x] Release proof freshness is current.", "- [x] Proof snapshot is stored as a local ignored artifact.", "", "## Manual Operator Signoff", "", "- [ ] Confirm intended release commit/tag.", "- [ ] Confirm deployment target and public URL.", "- [ ] Confirm environment-specific notes are captured.", "- [ ] Confirm rollback/restart command is known.", "- [ ] Confirm final human go/no-go decision.", "", "## Tower Commands", "", "```bash", "cd /mnt/pool/appdata/brain", "docker compose build --no-cache frontend", "docker compose up -d", "```", ].join("\n"); fs.mkdirSync(path.dirname(signoffPath), { recursive: true }); fs.writeFileSync(signoffPath, `${signoff}\n`, "utf8"); console.log(signoff); console.log(`\nRelease signoff file written to ${signoffPath}`);