import type { GiteaClient } from './gitea.ts'; import { parseStatus, type Status, type StatusEmoji } from './parser.ts'; export interface RepoRef { owner: string; repo: string; } export interface TaskRecord { slug: string; project: string; project_owner: string; status: Status; status_emoji: StatusEmoji; title: string; where_stopped: string | null; next_action: string | null; blocker: string | null; branch: string | null; last_commit_iso: string | null; raw_url: string; } export async function readBoard( client: GiteaClient, repos: ReadonlyArray, ): Promise { const records: TaskRecord[] = []; for (const { owner, repo } of repos) { const status = await client.getFile(owner, repo, '.tasks/STATUS.md'); if (status === null) continue; const blocks = parseStatus(status); // Parallel fan-out for per-task commit-iso fetches. Preserves block order // because Promise.all resolves arrays positionally. const isoList = await Promise.all( blocks.map((b) => client.getLatestCommitIso(owner, repo, `.tasks/${b.slug}.md`)), ); for (let i = 0; i < blocks.length; i++) { const block = blocks[i]!; const taskPath = `.tasks/${block.slug}.md`; records.push({ slug: block.slug, project: repo, project_owner: owner, status: block.status, status_emoji: block.status_emoji, title: block.title, where_stopped: block.where_stopped, next_action: block.next_action, blocker: block.blocker, branch: block.branch, last_commit_iso: isoList[i]!, raw_url: client.rawUrl(owner, repo, taskPath), }); } } return records; }