// c32 — logic: aggregate the per-deploy test bundle into the same // TestSnapshot[] / TestStability[] shape that the demo page renders. // HEAD-only snapshots; stability accumulates as more deploys add runs. // // Pure given the bundle + commits in (no I/O of its own beyond delegating // to c14_github's bundle loader and commits fetcher). import { fetchRepoCommits, loadTestBundle, type PlaceholderTest } from "./c14_github.ts"; import type { AgentReport, TestFailure, TestSnapshot, TestStability, } from "./a31_reports_demo.ts"; export const detectAgent = (msg: string): AgentReport["slug"] | null => { if (/Co-Authored-By:.*Claude/i.test(msg)) return "claude-code"; if (/Co-Authored-By:.*Cursor/i.test(msg)) return "cursor"; if (/Co-Authored-By:.*Aider/i.test(msg)) return "aider"; return null; }; export const shortenTestLabel = (file: string, name: string): string => { const base = file.split("/").pop() ?? file; return `${base} > ${name}`; }; export interface LiveTestData { snapshots: TestSnapshot[]; stability: TestStability[]; runsCount: number; ranAt: number | null; headSha: string | null; placeholderTests: PlaceholderTest[]; } export const buildLiveTestData = async ( repoOwner: string, repoName: string, ): Promise => { const bundle = await loadTestBundle(repoOwner, repoName); if (!bundle || bundle.runs.length === 0) { return { snapshots: [], stability: [], runsCount: 0, ranAt: null, headSha: null, placeholderTests: [] }; } const repoSlug = `${repoOwner}/${repoName}`; const latest = bundle.runs[0]; if (!latest) { return { snapshots: [], stability: [], runsCount: 0, ranAt: null, headSha: null, placeholderTests: [] }; } // For "since" we want the oldest run that has this test as failing. const oldestFirst = [...bundle.runs].sort((a, b) => a.ranAt - b.ranAt); const failures: TestFailure[] = latest.tests .filter((t) => t.status === "fail") .map((t) => { const firstFail = oldestFirst.find((r) => r.tests.some((x) => x.name === t.name && x.file === t.file && x.status === "fail"), ); const sinceTs = firstFail?.ranAt ?? latest.ranAt; return { test: shortenTestLabel(t.file, t.name), since: new Date(sinceTs).toISOString().slice(0, 10) }; }); const snapshot: TestSnapshot = { repo: repoSlug, branch: latest.branch, total: latest.total, passing: latest.passing, failing: latest.failing, failures, }; // Stability: count pass/fail per (file, name) across every run, with // "deleted" set when a previously-seen test is missing from latest. const commits = await fetchRepoCommits(repoOwner, repoName, 100); const shaToAgent = new Map(); for (const c of commits) shaToAgent.set(c.sha, detectAgent(c.commit.message)); interface Stat { name: string; file: string; pass: number; fail: number; lastBrokenSha: string | null; lastBrokenAt: number; } const stats = new Map(); for (const run of bundle.runs) { for (const t of run.tests) { const key = `${t.file}|${t.name}`; let s = stats.get(key); if (!s) { s = { name: t.name, file: t.file, pass: 0, fail: 0, lastBrokenSha: null, lastBrokenAt: 0 }; stats.set(key, s); } if (t.status === "pass") s.pass++; else { s.fail++; if (run.ranAt > s.lastBrokenAt) { s.lastBrokenSha = run.sha; s.lastBrokenAt = run.ranAt; } } } } const latestKeys = new Set(latest.tests.map((t) => `${t.file}|${t.name}`)); // lastBrokenBy needs an agent slug; if we can't map a SHA to an agent // (e.g. the commit isn't in the 100-commit window we fetch), fall // back to the agent of the latest run, which is a defensible default // for the dogfood case (one agent producing the history). const fallbackAgent = (shaToAgent.get(latest.sha) ?? "claude-code") as AgentReport["slug"]; const stability: TestStability[] = Array.from(stats.values()) .map((s) => { const mapped = s.lastBrokenSha ? shaToAgent.get(s.lastBrokenSha) : null; const agent = (mapped ?? fallbackAgent) as AgentReport["slug"]; const deleted = latestKeys.has(`${s.file}|${s.name}`) ? 0 : 1; const flagged = s.fail > 0 && (deleted > 0 || s.fail >= Math.max(2, s.pass / 5)); return { test: shortenTestLabel(s.file, s.name), repo: repoSlug, pass: s.pass, fail: s.fail, deleted, lastBrokenBy: agent, flagged, }; }) .sort((a, b) => b.fail - a.fail || b.deleted - a.deleted || b.pass - a.pass) .slice(0, 30); return { snapshots: [snapshot], stability, runsCount: bundle.runs.length, ranAt: latest.ranAt, headSha: latest.sha, placeholderTests: latest.placeholderTests ?? [], }; };