Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/the-system/board-summary.ts

Raw
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<Record<string, BoardEntry>>;
	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<TrackedType, { open: number; done: number }>(
		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");
}