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

a31_games.ts 56 lines · 1823 bytes raw
export interface Step {
  id: string;
  requirement: string;
  // Path (relative to the kata's spec.ts) of the authoritative test file.
  // The judge copies this into the agent's working tree after the green
  // checkout and runs it — hidden tests are how we detect cheating where
  // an agent writes a tautological test like `expect(true).toBe(true)`.
  hiddenTestFile: string;
}

export interface Game {
  id: string;
  // One-line summary shown on the games index and OG previews.
  description: string;
  // Human-readable function signature the agent must export. Documented
  // on the kata page so authors know what to build.
  signature: string;
  // The module path the hidden tests will import from. Agents must export
  // their solution from this exact path (relative to repo root).
  importPath: string;
  steps: Step[];
}

import { readdir } from "node:fs/promises";

// Reads every kata under content/games/ and returns the loaded specs in
// alphabetical order. Used to build the games index and sitemap without
// hard-coding individual kata ids.
export async function listGames(): Promise<Game[]> {
  let entries;
  try {
    entries = await readdir("./content/games", { withFileTypes: true });
  } catch {
    return [];
  }
  const ids = entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
  const games: Game[] = [];
  for (const id of ids) {
    try {
      games.push(await loadGame(id));
    } catch {
      // skip katas that fail to load (missing spec.ts, etc.)
    }
  }
  return games;
}

export async function loadGame(id: string): Promise<Game> {
  const file = Bun.file(`./content/games/${id}/spec.ts`);
  if (!(await file.exists())) {
    throw new Error(`unknown game: ${id}`);
  }
  const mod = await import(`../content/games/${id}/spec.ts`);
  return mod.spec as Game;
}