#!/usr/bin/env node // Regenerates BOARD.md (human kanban) from board.json (source of truth). // No deps. Run: node warroom/build-board.mjs import { readFileSync, writeFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, join } from 'node:path'; const DIR = dirname(fileURLToPath(import.meta.url)); const board = JSON.parse(readFileSync(join(DIR, 'board.json'), 'utf8')); const STATUSES = ['backlog', 'todo', 'in_progress', 'review', 'done']; const PRIO = ['P0', 'P1', 'P2', 'P3']; const tickets = board.tickets ?? []; const byPrio = (a, b) => PRIO.indexOf(a.priority) - PRIO.indexOf(b.priority); const count = (pred) => tickets.filter(pred).length; let md = `# 🏢 War Room Board\n\n`; md += `_Generated from board.json — do not edit by hand. ${tickets.length} tickets._\n\n`; // Launch readiness if (board.readiness) { const r = board.readiness; md += `## 🚦 Launch readiness: ${r.verdict} (${r.score}/100)\n\n> ${r.summary}\n\n`; } // P0 blockers if (board.blockers?.length) { md += `## 🚨 P0 Blockers\n\n${board.blockers.map((b) => `- ${b}`).join('\n')}\n\n`; } // Critical path if (board.criticalPath?.length) { md += `## 🧭 Critical path to launch\n\n${board.criticalPath .map((s, i) => `${i + 1}. ${String(s).replace(/^\s*\d+\.\s*/, '')}`) .join('\n')}\n\n`; } // Sprint 0 if (board.sprint0?.length) { md += `## ⚡ Sprint 0 (do first)\n\n${board.sprint0.map((s) => `- [ ] ${s}`).join('\n')}\n\n`; } // Summary counts md += `## 📊 Summary\n\n`; md += `| Priority | Count | | Status | Count |\n|---|---|---|---|---|\n`; for (let i = 0; i < Math.max(PRIO.length, STATUSES.length); i++) { const p = PRIO[i] ? `${PRIO[i]} | ${count((t) => t.priority === PRIO[i])}` : ' | '; const s = STATUSES[i] ? `${STATUSES[i]} | ${count((t) => t.status === STATUSES[i])}` : ' | '; md += `| ${p} | | ${s} |\n`; } md += `\n`; // Tickets grouped by role const roles = [...new Set(tickets.map((t) => t.role))]; for (const role of roles) { const rt = tickets.filter((t) => t.role === role).sort(byPrio); const team = board.org?.find((o) => o.role === role)?.team ?? ''; md += `## ${role}${team ? ` — ${team}` : ''} (${rt.length})\n\n`; md += `| ID | P | Effort | Status | Title | Deps |\n|---|---|---|---|---|---|\n`; for (const t of rt) { md += `| ${t.id} | ${t.priority} | ${t.effort ?? '-'} | ${t.status ?? 'backlog'} | ${t.title} | ${(t.deps ?? []).join(', ') || '-'} |\n`; } md += `\n`; } writeFileSync(join(DIR, 'BOARD.md'), md); console.log(`BOARD.md regenerated — ${tickets.length} tickets across ${roles.length} roles.`);