syntaxai/tdd.md · main · src / c14_real_tests.ts

c14_real_tests.ts 143 lines · 4908 bytes raw
// 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<LiveTestData> => {
  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<string, AgentReport["slug"] | null>();
  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<string, Stat>();
  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<TestStability>((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 ?? [],
  };
};