import { type BoardEntry, boardStateFor, defaultBoardState, readBoardState, } from "./board-state.ts"; import type { DocumentMetadata } from "./documents.ts"; import { loadProjectIndex } from "./project-index.ts"; const LISTED_ITEMS = 20; const TRACKED = ["spec", "plan", "issue"] as const; type TrackedType = (typeof TRACKED)[number]; const age = (updatedAt: string, now: number): string => { const days = Math.max( 0, Math.floor((now - Date.parse(updatedAt)) / 86_400_000), ); if (days === 0) return "today"; return days === 1 ? "1 day ago" : `${days} days ago`; }; const describe = ( metadata: DocumentMetadata, entry: BoardEntry | undefined, now: number, ): string => { const state = entry?.state ?? defaultBoardState(metadata); const origin = entry ? `${entry.source} ${age(entry.updatedAt, now)}` : "default"; const note = entry?.note ? ` · ${entry.note}` : ""; return `- ${metadata.id} · ${metadata.title} · ${state} · ${origin}${note}`; }; export function boardSummaryBlock( cwd: string, now = Date.now(), ): string | undefined { let documents: { metadata: DocumentMetadata }[]; let items: Readonly>; try { const index = loadProjectIndex(cwd); items = readBoardState(index.root, index.prefix).items; documents = index.documents; } catch { return undefined; } const tracked = documents.filter( ( document, ): document is { metadata: DocumentMetadata & { type: TrackedType }; } => TRACKED.includes(document.metadata.type as TrackedType), ); if (tracked.length === 0) return undefined; const counts = new Map( TRACKED.map((type) => [type, { open: 0, done: 0 }]), ); const unfinished: { metadata: DocumentMetadata; type: TrackedType }[] = []; for (const document of tracked) { const type = document.metadata.type; const state = boardStateFor({ schemaVersion: 0, items }, document.metadata); const count = counts.get(type) as { open: number; done: number }; if (state === "done") count.done += 1; else { count.open += 1; unfinished.push({ metadata: document.metadata, type }); } } const totals = TRACKED.map((type) => { const count = counts.get(type) as { open: number; done: number }; const label = type === "issue" ? "open" : "todo"; return `${type}s ${count.open} ${label}, ${count.done} done`; }).join(". "); unfinished.sort( (left, right) => TRACKED.indexOf(left.type) - TRACKED.indexOf(right.type) || left.metadata.id.localeCompare(right.metadata.id), ); const listed = unfinished .slice(0, LISTED_ITEMS) .map((item) => describe(item.metadata, items[item.metadata.id], now)); const overflow = unfinished.length - listed.length; return [ "# The System board state", "", `${totals}.`, ...(listed.length === 0 ? ["", "No unfinished items."] : [ "", "Unfinished:", ...listed, ...(overflow > 0 ? [`+${overflow} more`] : []), ]), ].join("\n"); }