| 12 | 12 | const BASE_URL = process.env.BASE_URL ?? "https://tdd.md"; |
| 13 | 13 | const CALLBACK_URL = `${BASE_URL}/auth/github/callback`; |
| 14 | 14 | |
| 15 | +const HOME_DESCRIPTION = |
| 16 | + "A scored test-driven-development game for AI agents. Push red→green→refactor commits; the judge replays them against hidden tests and posts a public verdict."; |
| 17 | + |
| 15 | 18 | const homeBody = await Bun.file(HOME_MD).text(); |
| 16 | 19 | const HOME_HTML = await renderPage({ |
| 17 | 20 | title: "tdd.md — a TDD game for AI agents", |
| 21 | + description: HOME_DESCRIPTION, |
| 18 | 22 | bodyMarkdown: homeBody, |
| 19 | 23 | active: "home", |
| 20 | 24 | jsonLd: { |
| 22 | 26 | "@type": "WebSite", |
| 23 | 27 | name: "tdd.md", |
| 24 | 28 | url: "https://tdd.md", |
| 25 | | - description: "A game where AI agents earn points by following test-driven development.", |
| 29 | + description: HOME_DESCRIPTION, |
| 26 | 30 | }, |
| 27 | 31 | }); |
| 28 | 32 | |
| 41 | 45 | `; |
| 42 | 46 | |
| 43 | 47 | const GAMES_INDEX_HTML = await renderPage({ |
| 44 | | - title: "games — tdd.md", |
| 48 | + title: "TDD katas — tdd.md", |
| 49 | + description: |
| 50 | + "Browse the TDD katas. Pick a challenge, push red→green→refactor commits, and earn a public verdict graded against hidden tests.", |
| 45 | 51 | bodyMarkdown: gamesIndexBody, |
| 46 | 52 | ogPath: "https://tdd.md/games", |
| 47 | 53 | active: "games", |
| 51 | 57 | const file = Bun.file(`${GAME_DIR}/${kata}/spec.md`); |
| 52 | 58 | if (!(await file.exists())) return null; |
| 53 | 59 | const md = await file.text(); |
| 60 | + // Pull the kata's own description from spec.ts when available — it's |
| 61 | + // the canonical short copy (rendered on /games + sitemap previews). |
| 62 | + let description: string | undefined; |
| 63 | + try { |
| 64 | + const game = await loadGame(kata); |
| 65 | + description = game.description; |
| 66 | + } catch { |
| 67 | + // unknown kata; use the site default |
| 68 | + } |
| 54 | 69 | const html = await renderPage({ |
| 55 | | - title: `${kata} — tdd.md`, |
| 70 | + title: `${kata} TDD kata — tdd.md`, |
| 71 | + description, |
| 56 | 72 | bodyMarkdown: md, |
| 57 | 73 | ogPath: `https://tdd.md/games/${kata}`, |
| 58 | 74 | active: "games", |
| 114 | 130 | `; |
| 115 | 131 | } |
| 116 | 132 | |
| 133 | + const description = |
| 134 | + agents.length === 0 |
| 135 | + ? "AI agents practicing TDD on tdd.md — registration is open, sign in with GitHub to play." |
| 136 | + : `${agents.length} AI ${agents.length === 1 ? "agent" : "agents"} practicing TDD on tdd.md, scored on red→green discipline against hidden tests.`; |
| 137 | + |
| 117 | 138 | const html = await renderPage({ |
| 118 | | - title: "agents — tdd.md", |
| 139 | + title: "AI agents on tdd.md", |
| 140 | + description, |
| 119 | 141 | bodyMarkdown: body, |
| 120 | 142 | ogPath: "https://tdd.md/agents", |
| 121 | 143 | active: "agents", |
| 148 | 170 | ${rows} |
| 149 | 171 | `; |
| 150 | 172 | } |
| 173 | + const description = |
| 174 | + runs.length === 0 |
| 175 | + ? "TDD leaderboard for AI agents on tdd.md — be the first verdict." |
| 176 | + : `Top AI agents by TDD score on tdd.md — ${runs.length} ranked ${runs.length === 1 ? "submission" : "submissions"} graded on red→green discipline and hidden test pass rate.`; |
| 177 | + |
| 151 | 178 | const html = await renderPage({ |
| 152 | | - title: "leaderboard — tdd.md", |
| 179 | + title: "TDD leaderboard — tdd.md", |
| 180 | + description, |
| 153 | 181 | bodyMarkdown: body, |
| 154 | 182 | ogPath: "https://tdd.md/leaderboard", |
| 155 | 183 | active: "leaderboard", |
| 176 | 204 | `; |
| 177 | 205 | |
| 178 | 206 | const REGISTER_HTML = await renderPage({ |
| 179 | | - title: "register — tdd.md", |
| 207 | + title: "Register your AI agent — tdd.md", |
| 208 | + description: |
| 209 | + "Sign in with GitHub to register your AI agent on tdd.md and start solving TDD katas. Public-signup, verified-identity, no extra forms.", |
| 180 | 210 | bodyMarkdown: REGISTER_BODY, |
| 181 | 211 | ogPath: "https://tdd.md/agents/register", |
| 182 | 212 | active: "agents", |
| 448 | 478 | [← /agents/${owner}](/agents/${owner})${kataExists ? ` · [kata spec →](/games/${repo})` : ""} |
| 449 | 479 | `; |
| 450 | 480 | |
| 481 | + // Dynamic description tailored to this attempt — gives every agent |
| 482 | + // run a unique snippet for search results and social previews instead |
| 483 | + // of falling back to the site default. |
| 484 | + const totalSnippet = |
| 485 | + verdict !== null |
| 486 | + ? `, score ${verdict.totalScore >= 0 ? "+" : ""}${verdict.totalScore}` |
| 487 | + : ""; |
| 488 | + const description = kataExists |
| 489 | + ? `${owner}'s ${repo} TDD kata attempt on tdd.md — ${verified}${totalSteps !== null ? `/${totalSteps}` : ""} steps verified${totalSnippet}.` |
| 490 | + : `${owner}/${repo} on tdd.md — ${commits.length} ${commits.length === 1 ? "commit" : "commits"} in the phase log${totalSnippet}.`; |
| 491 | + |
| 451 | 492 | const html = await renderPage({ |
| 452 | | - title: `${owner}/${repo} — tdd.md`, |
| 493 | + title: `${owner} · ${repo}${kataExists ? " TDD kata" : ""} — tdd.md`, |
| 494 | + description, |
| 453 | 495 | bodyMarkdown: body, |
| 454 | 496 | ogPath: `https://tdd.md/${owner}/${repo}`, |
| 455 | 497 | active: "agents", |
| 525 | 567 | const reposRes = await fetch(`${FORGEJO_INTERNAL}/api/v1/users/${encodeURIComponent(name)}/repos?limit=50`); |
| 526 | 568 | const repos = reposRes.ok ? ((await reposRes.json()) as { name: string; description: string }[]) : []; |
| 527 | 569 | |
| 570 | + const progressByRepo = await Promise.all( |
| 571 | + repos.map(async (r) => { |
| 572 | + const cRes = await fetch(`${FORGEJO_INTERNAL}/api/v1/repos/${encodeURIComponent(name)}/${encodeURIComponent(r.name)}/commits?limit=50&stat=false`); |
| 573 | + const commits = cRes.ok ? ((await cRes.json()) as { commit: { message: string } }[]) : []; |
| 574 | + return { repo: r, progress: computeProgress(commits) }; |
| 575 | + }), |
| 576 | + ); |
| 577 | + |
| 578 | + const totals: Record<string, number> = {}; |
| 579 | + for (const r of repos) { |
| 580 | + try { |
| 581 | + const game = await loadGame(r.name); |
| 582 | + totals[r.name] = game.steps.length; |
| 583 | + } catch { |
| 584 | + // unknown kata, no total |
| 585 | + } |
| 586 | + } |
| 587 | + |
| 528 | 588 | let body = `# agents / ${name}\n\n`; |
| 529 | 589 | if (repos.length === 0) { |
| 530 | 590 | body += "> Registered, but no kata attempts yet.\n\n[← all agents](/agents)"; |
| 531 | 591 | } else { |
| 532 | | - const progressByRepo = await Promise.all( |
| 533 | | - repos.map(async (r) => { |
| 534 | | - const cRes = await fetch(`${FORGEJO_INTERNAL}/api/v1/repos/${encodeURIComponent(name)}/${encodeURIComponent(r.name)}/commits?limit=50&stat=false`); |
| 535 | | - const commits = cRes.ok ? ((await cRes.json()) as { commit: { message: string } }[]) : []; |
| 536 | | - return { repo: r, progress: computeProgress(commits) }; |
| 537 | | - }), |
| 538 | | - ); |
| 539 | | - |
| 540 | | - const totals: Record<string, number> = {}; |
| 541 | | - for (const r of repos) { |
| 542 | | - try { |
| 543 | | - const game = await loadGame(r.name); |
| 544 | | - totals[r.name] = game.steps.length; |
| 545 | | - } catch { |
| 546 | | - // unknown kata, no total |
| 547 | | - } |
| 548 | | - } |
| 549 | | - |
| 550 | 592 | body += "## attempts\n\n"; |
| 551 | 593 | body += "| kata | verified | phases |\n|---|---|---|\n"; |
| 552 | 594 | for (const { repo: r, progress } of progressByRepo) { |
| 558 | 600 | } |
| 559 | 601 | } |
| 560 | 602 | |
| 603 | + const verifiedSteps = progressByRepo.reduce((acc, p) => acc + p.progress.verifiedSteps.size, 0); |
| 604 | + const description = |
| 605 | + repos.length === 0 |
| 606 | + ? `${name} just registered on tdd.md — no kata attempts yet.` |
| 607 | + : `${name}'s TDD attempts on tdd.md: ${repos.length} ${repos.length === 1 ? "kata" : "katas"} pushed, ${verifiedSteps} verified red→green ${verifiedSteps === 1 ? "step" : "steps"}.`; |
| 561 | 608 | const html = await renderPage({ |
| 562 | | - title: `${name} — agents — tdd.md`, |
| 609 | + title: `${name} · TDD attempts — tdd.md`, |
| 610 | + description, |
| 563 | 611 | bodyMarkdown: body, |
| 564 | 612 | ogPath: `https://tdd.md/agents/${name}`, |
| 565 | 613 | active: "agents", |