Skip to content

Commit 25cdb31

Browse files
hsnice16claude
andauthored
feat: surface badge-adoption tag for repos embedding their AFC badge (#8)
Detect when a repo embeds its own Agent Friendly Code badge in the README (`/api/badge/<slug>`) and show a "Badge" tag on the leaderboard and the repo detail page. Dashboard-only social-proof metadata — not a scored signal, and never vendored to the sibling scorers. - lib/badge-adoption.ts: host-agnostic detectBadgeEmbed over README candidates - lib/db.ts: badge_embedded column + in-place migration for pre-existing DBs - scripts/score.ts: detect on every score; seed flows through score - components/BadgeAdoptedTag.tsx + RepoHero/leaderboard render the tag - tests/badge-adoption.test.ts: 6 cases (match, host-agnostic, wrong-slug, …) - scripts/seed-list.ts: ~30 new curated seed repos - re-seeded data/rank.db Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 9570d6b commit 25cdb31

11 files changed

Lines changed: 183 additions & 4 deletions

File tree

AGENTS.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ components/ # Tailwind-styled React components
7373
RepoHero.tsx, ScoreDeltaPopover.tsx, SignalListCard.tsx, ModelSuggestions.tsx, PerModelScores.tsx,
7474
AlternativesStrip.tsx, BreadcrumbJsonLd.tsx, HomeJsonLd.tsx, ExternalLink.tsx,
7575
BadgeEmbed.tsx, ActionEmbed.tsx, PeerlistCard.tsx, CopySnippet.tsx, PackageLookupForm.tsx,
76-
BackToTop.tsx, GoogleAnalytics.tsx
76+
BadgeAdoptedTag.tsx, BackToTop.tsx, GoogleAnalytics.tsx
7777
lib/
7878
constants/
7979
scoring.ts # score thresholds, visible limits
@@ -93,6 +93,7 @@ lib/
9393
types/
9494
db.ts # shared row-shape types for lib/db.ts (RepoRow, LeaderboardRow, …)
9595
package-lookup.ts # shared registry → repo lookup (used by /api/package + /package page)
96+
badge-adoption.ts # detectBadgeEmbed — reads the cloned README for an embedded AFC badge (dashboard metadata, NOT a scored signal; never vendored to siblings)
9697
db.ts # better-sqlite3 schema + queries
9798
version.ts # APP_NAME, APP_VERSION, IS_PRE_RELEASE, APP_URL, APP_DESCRIPTION, REPO_URL, SIBLING_VERSION, ACTION_REPO_URL, ACTION_USES, SKILL_REPO_URL, SKILL_INSTALL_CMD, OG_DEFAULTS, TWITTER_DEFAULTS (spread into per-page openGraph / twitter — Next.js shallow-merges these objects so defaults must be re-spread on every page)
9899
changelog.ts # typed ChangelogEntry[]
@@ -105,6 +106,7 @@ tests/
105106
format.test.ts # compactStars, relativeTime, hostLabel
106107
parse-repo-url.test.ts # GH / GL / BB parsing + edge cases
107108
scorer.test.ts # scoreRepo, topImprovements
109+
badge-adoption.test.ts # detectBadgeEmbed — README badge-embed detection
108110
signals/ # one *.test.ts per signal
109111
tasks/
110112
README.md

app/page.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ArrowUpRight } from "@phosphor-icons/react/dist/ssr";
22
import type { Metadata } from "next";
33
import { headers } from "next/headers";
44
import Link from "next/link";
5+
import { BadgeAdoptedTag } from "@/components/BadgeAdoptedTag";
56
import { HomeJsonLd } from "@/components/HomeJsonLd";
67
import { HostPill } from "@/components/HostPill";
78
import { HostSelect } from "@/components/HostSelect";
@@ -246,6 +247,7 @@ export default async function Page({ searchParams }: { searchParams: Promise<Sea
246247
{r.owner}/{r.name}
247248
</Link>
248249
<HostPill host={r.host} />
250+
{r.badge_embedded ? <BadgeAdoptedTag /> : null}
249251
</td>
250252
<td className="text-right tabular-nums text-ink-dim">{compactStars(r.stars)}</td>
251253
<td className="text-right">

components/BadgeAdoptedTag.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { CheckCircle } from "@phosphor-icons/react/dist/ssr";
2+
3+
export function BadgeAdoptedTag() {
4+
return (
5+
<span
6+
title="This repo embeds its Agent Friendly Code badge in its README"
7+
className="ml-2 inline-flex items-center gap-1 rounded border border-ok/40 bg-ok/10 px-2 py-px align-middle text-[10.5px] font-semibold uppercase tracking-wider text-ok"
8+
>
9+
<CheckCircle size={12} weight="fill" aria-hidden="true" />
10+
Badge
11+
</span>
12+
);
13+
}

components/RepoHero.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { RepoRow } from "@/lib/types/db";
22
import { compactStars, relativeTime } from "@/lib/utils/format";
33
import { scoreTier, TIER_TEXT_CLASS } from "@/lib/utils/score";
44

5+
import { BadgeAdoptedTag } from "./BadgeAdoptedTag";
56
import { HostPill } from "./HostPill";
67
import { Panel } from "./Panel";
78
import { ScoreDeltaPopover } from "./ScoreDeltaPopover";
@@ -20,6 +21,7 @@ export function RepoHero({ repo }: { repo: RepoRow }) {
2021
<h1 className="m-0 break-words text-[20px] font-semibold tracking-tight sm:text-[22px]">
2122
{repo.owner}/{repo.name}
2223
<HostPill host={repo.host} />
24+
{repo.badge_embedded ? <BadgeAdoptedTag /> : null}
2325
</h1>
2426

2527
<a

data/rank.db

64 KB
Binary file not shown.

lib/badge-adoption.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { firstExisting, readSafe } from "./scoring/signals/helpers";
2+
3+
const README_CANDIDATES = ["README.md", "README.rst", "README.txt", "README"];
4+
5+
export function detectBadgeEmbed(repoPath: string, slug: string): boolean {
6+
const p = firstExisting(repoPath, README_CANDIDATES);
7+
if (!p) {
8+
return false;
9+
}
10+
11+
return readSafe(p).includes(`/api/badge/${slug}`);
12+
}

lib/db.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ db.exec(`
3939
overall_score REAL,
4040
previous_overall_score REAL,
4141
language TEXT,
42+
badge_embedded INTEGER,
4243
UNIQUE(host, owner, name)
4344
);
4445
CREATE TABLE IF NOT EXISTS model_score (
@@ -73,22 +74,24 @@ export function saveScoredRepo(args: {
7374
owner: string;
7475
overall: number;
7576
stars?: number | null;
77+
badgeEmbedded?: boolean;
7678
signals: SignalResult[];
7779
language?: string | null;
7880
modelScores: ModelScore[];
7981
defaultBranch?: string | null;
8082
}): number {
8183
const tx = db.transaction(() => {
8284
db.prepare(
83-
`INSERT INTO repo (host, owner, name, url, default_branch, stars, last_scored_at, overall_score, language)
84-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
85+
`INSERT INTO repo (host, owner, name, url, default_branch, stars, last_scored_at, overall_score, language, badge_embedded)
86+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
8587
ON CONFLICT(url) DO UPDATE SET
8688
default_branch = excluded.default_branch,
8789
stars = excluded.stars,
8890
last_scored_at = excluded.last_scored_at,
8991
previous_overall_score = repo.overall_score,
9092
overall_score = excluded.overall_score,
91-
language = COALESCE(excluded.language, repo.language)`,
93+
language = COALESCE(excluded.language, repo.language),
94+
badge_embedded = excluded.badge_embedded`,
9295
).run(
9396
args.host,
9497
args.owner,
@@ -99,6 +102,7 @@ export function saveScoredRepo(args: {
99102
Math.floor(Date.now() / 1000),
100103
args.overall,
101104
args.language ?? null,
105+
args.badgeEmbedded ? 1 : 0,
102106
);
103107

104108
const row = db.prepare("SELECT id FROM repo WHERE url = ?").get(args.url) as { id: number };

lib/types/db.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export type RepoRow = {
77
stars: number | null;
88
language: string | null;
99
overall_score: number | null;
10+
badge_embedded: number | null;
1011
default_branch: string | null;
1112
last_scored_at: number | null;
1213
previous_overall_score: number | null;

scripts/score.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { existsSync, mkdirSync, statSync } from "node:fs";
22
import { join } from "node:path";
33

4+
import { detectBadgeEmbed } from "../lib/badge-adoption";
45
import { shallowClone } from "../lib/clients/git";
56
import { fetchRepoMeta, parseRepoUrl } from "../lib/clients/github";
67
import { saveScoredRepo } from "../lib/db";
@@ -61,12 +62,14 @@ async function scoreCommand(target: string): Promise<void> {
6162

6263
console.log(`[score] scanning ${repoPath}`);
6364
const result = scoreRepo(repoPath);
65+
const badgeEmbedded = detectBadgeEmbed(repoPath, `${host}/${owner}/${name}`);
6466

6567
saveScoredRepo({
6668
url,
6769
host,
6870
name,
6971
owner,
72+
badgeEmbedded,
7073
stars: stars ?? null,
7174
overall: result.overall,
7275
signals: result.signals,

scripts/seed-list.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,26 @@ export const SEEDS: Seed[] = [
119119
url: "https://github.com/payloadcms/payload",
120120
},
121121
{ url: "https://github.com/strapi/strapi", note: "Strapi — Node.js headless CMS" },
122+
{ url: "https://github.com/solidjs/solid", note: "Solid — reactive UI library" },
123+
{ url: "https://github.com/preactjs/preact", note: "Preact — 3kB React alternative" },
124+
{ url: "https://github.com/mui/material-ui", note: "MUI — React component library" },
125+
{
126+
url: "https://github.com/chakra-ui/chakra-ui",
127+
note: "Chakra UI — accessible React component library",
128+
},
129+
{ url: "https://github.com/nrwl/nx", note: "Nx — smart monorepo build system" },
130+
{
131+
url: "https://github.com/directus/directus",
132+
note: "Directus — headless CMS + data platform",
133+
},
134+
{
135+
url: "https://github.com/medusajs/medusa",
136+
note: "Medusa — open-source commerce platform",
137+
},
138+
{
139+
url: "https://github.com/novuhq/novu",
140+
note: "Novu — open-source notification infrastructure",
141+
},
122142

123143
// --- GitHub, Python ---
124144
{
@@ -177,6 +197,15 @@ export const SEEDS: Seed[] = [
177197
url: "https://github.com/sqlfluff/sqlfluff",
178198
note: "SQLFluff — multi-dialect SQL linter / formatter",
179199
},
200+
{ url: "https://github.com/encode/httpx", note: "HTTPX — next-gen Python HTTP client" },
201+
{ url: "https://github.com/encode/starlette", note: "Starlette — lightweight ASGI framework" },
202+
{ url: "https://github.com/celery/celery", note: "Celery — distributed task queue" },
203+
{ url: "https://github.com/scrapy/scrapy", note: "Scrapy — web crawling framework" },
204+
{
205+
url: "https://github.com/python-poetry/poetry",
206+
note: "Poetry — Python packaging & dependency manager",
207+
},
208+
{ url: "https://github.com/mkdocs/mkdocs", note: "MkDocs — Markdown project documentation" },
180209

181210
// --- GitHub, Rust ---
182211
{
@@ -226,6 +255,19 @@ export const SEEDS: Seed[] = [
226255
note: "reqwest — ergonomic Rust HTTP client",
227256
url: "https://github.com/seanmonstar/reqwest",
228257
},
258+
{ url: "https://github.com/clap-rs/clap", note: "clap — Rust command-line argument parser" },
259+
{
260+
url: "https://github.com/alacritty/alacritty",
261+
note: "Alacritty — GPU-accelerated terminal emulator",
262+
},
263+
{
264+
url: "https://github.com/surrealdb/surrealdb",
265+
note: "SurrealDB — multi-model database (Rust)",
266+
},
267+
{
268+
url: "https://github.com/rustdesk/rustdesk",
269+
note: "RustDesk — open-source remote desktop",
270+
},
229271

230272
// --- GitHub, Go ---
231273
{
@@ -269,6 +311,16 @@ export const SEEDS: Seed[] = [
269311
url: "https://github.com/charmbracelet/bubbletea",
270312
note: "Bubble Tea — Go TUI framework based on The Elm Architecture",
271313
},
314+
{
315+
url: "https://github.com/caddyserver/caddy",
316+
note: "Caddy — web server with automatic HTTPS",
317+
},
318+
{
319+
url: "https://github.com/traefik/traefik",
320+
note: "Traefik — cloud-native reverse proxy / load balancer",
321+
},
322+
{ url: "https://github.com/minio/minio", note: "MinIO — high-performance object storage" },
323+
{ url: "https://github.com/go-gorm/gorm", note: "GORM — Go ORM library" },
272324

273325
// --- GitHub, C / C++ / systems ---
274326
{
@@ -295,6 +347,9 @@ export const SEEDS: Seed[] = [
295347
{ url: "https://github.com/postgres/postgres", note: "PostgreSQL — relational database (mirror)" },
296348
{ url: "https://github.com/duckdb/duckdb", note: "DuckDB — in-process analytical database" },
297349
{ url: "https://github.com/ml-explore/mlx", note: "MLX — Apple's array framework for ML on Apple silicon" },
350+
{ url: "https://github.com/fmtlib/fmt", note: "fmt — modern C++ formatting library" },
351+
{ url: "https://github.com/nlohmann/json", note: "JSON for Modern C++" },
352+
{ url: "https://github.com/opencv/opencv", note: "OpenCV — computer vision library" },
298353

299354
// --- GitHub, JVM (Java / Kotlin) ---
300355
{
@@ -317,13 +372,19 @@ export const SEEDS: Seed[] = [
317372
url: "https://github.com/Anuken/Mindustry",
318373
note: "Mindustry — open-source factory / tower-defense game (Java + libGDX)",
319374
},
375+
{ url: "https://github.com/square/okhttp", note: "OkHttp — HTTP client for JVM / Android" },
376+
{
377+
url: "https://github.com/netty/netty",
378+
note: "Netty — async event-driven network framework",
379+
},
320380

321381
// --- GitHub, Swift ---
322382
{ url: "https://github.com/apple/swift", note: "Swift language" },
323383
{
324384
note: "Vapor — Swift web framework",
325385
url: "https://github.com/vapor/vapor",
326386
},
387+
{ url: "https://github.com/Alamofire/Alamofire", note: "Alamofire — Swift HTTP networking" },
327388

328389
// --- GitHub, Ruby ---
329390
{
@@ -368,6 +429,10 @@ export const SEEDS: Seed[] = [
368429
url: "https://github.com/jellyfin/jellyfin",
369430
note: "Jellyfin — open-source media server (.NET)",
370431
},
432+
{
433+
url: "https://github.com/PowerShell/PowerShell",
434+
note: "PowerShell — cross-platform shell + scripting (.NET)",
435+
},
371436

372437
// --- GitHub, PHP ---
373438
{ url: "https://github.com/laravel/laravel", note: "Laravel — PHP web framework starter" },
@@ -467,6 +532,18 @@ export const SEEDS: Seed[] = [
467532
url: "https://github.com/earendil-works/pi",
468533
note: "Pi — self-extensible coding agent CLI + unified multi-provider LLM API (TypeScript)",
469534
},
535+
{
536+
url: "https://github.com/block/goose",
537+
note: "Goose — open-source on-machine AI coding agent",
538+
},
539+
{
540+
url: "https://github.com/cline/cline",
541+
note: "Cline — autonomous coding agent for VS Code",
542+
},
543+
{
544+
url: "https://github.com/continuedev/continue",
545+
note: "Continue — open-source AI code assistant for IDEs",
546+
},
470547

471548
// --- AI-native: models + infra ---
472549
{

0 commit comments

Comments
 (0)