Skip to content

Commit 394cd6f

Browse files
committed
feat: release v0.3.0
1 parent 4f058bd commit 394cd6f

75 files changed

Lines changed: 1550 additions & 236 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"hooks": [
77
{
88
"type": "command",
9-
"command": "printf '\\n📦 Agent Friendly Code — current release: 0.2.0\\n • Read AGENTS.md for conventions, CONTRIBUTING.md for the PR workflow.\\n • Roadmap: 0.3.0 (benchmark harness) → 0.4.0 (ecosystem integration) → 0.5.0 (alternatives) → 0.6.0 (registry overlay) → 0.7.0 (history-aware signals) → 1.0.0 (production stability) → 1.1.0 (at-scale GitHub indexing).\\n • Changelog rule: user-facing capabilities only. Codebase hygiene (CI / linter / tests / CONTRIBUTING) does NOT go in lib/changelog.ts.\\n'"
9+
"command": "printf '\\n📦 Agent Friendly Code — current release: 0.3.0\\n • Read AGENTS.md for conventions, CONTRIBUTING.md for the PR workflow.\\n • Roadmap: 0.4.0 (quick wins — history-aware signals + PR score-diff) → 0.5.0 (auto-refresh + smarter matching) → 0.6.0 (maintainer ownership + at-scale discovery) → 1.0.0 (production cut — Postgres + at-scale indexing + benchmark harness).\\n • Changelog rule: user-facing capabilities only. Codebase hygiene (CI / linter / tests / CONTRIBUTING) does NOT go in lib/changelog.ts.\\n'"
1010
}
1111
]
1212
}

AGENTS.md

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ See `README.md` for the full product narrative. See `tasks/` for per-version wor
2929
bun install
3030
bun run prepare-hooks # once — installs lefthook git hooks (Biome + tsc + test + file-length on pre-commit)
3131
bun run init-db # optional — auto-runs on first score
32-
bun run seed # score the curated set (155 repos) across GH / GL / BB
32+
bun run seed # score the curated set across GH / GL / BB
3333
bun run dev # http://localhost:3000
3434
bun run score <url> # score a single repo
3535
bun run test # unit tests (node --test + tsx) — requires Node ≥20.9.0
@@ -49,6 +49,10 @@ app/
4949
sitemap.ts # /sitemap.xml — static routes + every repo detail page
5050
api/repos/route.ts
5151
api/repo/[id]/route.ts
52+
api/badge/[host]/[owner]/[name]/route.ts # SVG badge for README embeds (?model=<id> for per-model)
53+
api/package/[registry]/[name]/route.ts # npm/PyPI/Cargo lookup → source-repo score
54+
package/page.tsx # explainer + try-it examples
55+
package/[registry]/[name]/page.tsx # scored | not_scored | unresolved states
5256
globals.css # Tailwind import + @theme tokens (no custom utilities)
5357
components/ # Tailwind-styled React components
5458
Panel.tsx, ScoreBar.tsx, ScoreNumber.tsx, ScoreCell.tsx,
@@ -70,7 +74,8 @@ lib/
7074
weights.ts # per-model weight tables
7175
scorer.ts # signals × weights, topImprovements
7276
clients/
73-
git.ts, github.ts
77+
git.ts, github.ts, registries.ts # registries.ts: npm/PyPI/Cargo package → source-repo URL
78+
package-lookup.ts # shared registry → repo lookup (used by /api/package + /package page)
7479
db.ts # better-sqlite3 schema + queries
7580
version.ts # APP_NAME, APP_VERSION, IS_PRE_RELEASE, APP_URL, APP_DESCRIPTION, REPO_URL
7681
changelog.ts # typed ChangelogEntry[]
@@ -87,13 +92,11 @@ tasks/
8792
README.md
8893
0.1.0/ # released — shipped record
8994
0.2.0/ # released — dogfood complete (tests, self-score, row-click)
90-
0.3.0/ # planned — real per-model weights (benchmark harness)
91-
0.4.0/ # planned — ecosystem integration (badge, PR diff, webhook, opt-out)
92-
0.5.0/ # planned — discovery (alternative recommender)
93-
0.6.0/ # planned — discovery (package-registry overlay)
94-
0.7.0/ # planned — history-aware signals (closes the shallow-clone gap)
95-
1.0.0/ # planned — production stability (Postgres + stable API)
96-
1.1.0/ # planned — at-scale GitHub indexing (crawl + rank, not seed-only)
95+
0.3.0/ # released — embeddable scores + broader coverage (badge, more agents, alternatives, package lookup)
96+
0.4.0/ # planned — quick wins (history-aware signals + PR score-diff action)
97+
0.5.0/ # planned — auto-refresh + smarter matching (webhook rescoring + alternatives v2)
98+
0.6.0/ # planned — maintainer ownership + at-scale discovery (OAuth opt-out + package overlay at scale)
99+
1.0.0/ # planned — production cut (Postgres + at-scale indexing + benchmark harness)
97100
.claude/
98101
settings.json # SessionStart + Stop hooks (Stop → hooks/stop-guard.sh)
99102
hooks/
@@ -175,7 +178,7 @@ Hooks docs: <https://docs.claude.com/en/docs/claude-code/hooks.html>.
175178
- SQL: all queries parameterised. No interpolation.
176179
- HTML: React auto-escapes; no `dangerouslyInnerHTML` anywhere.
177180
- Local-path mode reads files; never writes outside `data/` and `tmp-clones/`.
178-
- No auth yet (read-only dashboard). When auth lands (`tasks/0.4.0/04-opt-out-claim-flow.md`), do it via OAuth and gate DB writes per user.
181+
- No auth yet (read-only dashboard). When auth lands (`tasks/0.6.0/01-opt-out-claim-flow.md`), do it via OAuth and gate DB writes per user.
179182

180183
**Operational concerns** (not code-level security) worth flagging before public launch:
181184

@@ -185,7 +188,7 @@ Hooks docs: <https://docs.claude.com/en/docs/claude-code/hooks.html>.
185188

186189
## Things to leave alone
187190

188-
- Per-model weights are illustrative. Don't tune without `tasks/0.3.0/01-benchmark-harness.md`.
191+
- Per-model weights are illustrative. Don't tune without `tasks/1.0.0/03-benchmark-harness.md`.
189192
- SQLite schema is intentionally simple. Flag before restructuring.
190193
- The I/O boundary. Scoring stays pure; DB stays in `lib/db.ts`.
191194
- `APP_VERSION` — don't bump without a release.

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Read [AGENTS.md](./AGENTS.md) first — it's the source of truth for stack, conv
99
```bash
1010
bun install
1111
bun run prepare-hooks # once — installs lefthook pre-commit (Biome + tsc + test + file-length)
12-
bun run seed # optional: populate the DB with the curated set (155 public repos)
12+
bun run seed # optional: populate the DB with the curated set
1313
bun run dev # http://localhost:3000
1414
bun run test # unit tests (node --test + tsx) — requires Node ≥20.9.0
1515
```

README.md

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
# Agent Friendly Code
22

3-
[![Release](https://img.shields.io/badge/release-0.2.0-blue?style=flat-square)](./lib/changelog.ts)
3+
[![Release](https://img.shields.io/badge/release-0.3.0-blue?style=flat-square)](./lib/changelog.ts)
44
[![License: MIT](https://img.shields.io/badge/license-MIT-green?style=flat-square)](./LICENSE)
55
[![Next.js 16](https://img.shields.io/badge/Next.js-16-black?style=flat-square)](https://nextjs.org)
66
[![Node ≥20.9](https://img.shields.io/badge/node-%E2%89%A520.9-43853d?style=flat-square&logo=node.js&logoColor=white)](https://nodejs.org)
7+
[![Sponsor](https://img.shields.io/badge/sponsor-%E2%9D%A4-db61a2?style=flat-square&logo=githubsponsors&logoColor=white)](https://github.com/sponsors/hsnice16)
8+
[![Agent Friendly](https://www.agentfriendlycode.com/api/badge/github/hsnice16/agent-friendly-code.svg)](https://www.agentfriendlycode.com)
79

810
**A public dashboard that ranks open-source repos by how friendly they are for AI coding agents — per model.**
911

10-
Next.js 16 + SQLite (`better-sqlite3`), styled with Tailwind CSS 4. Spans GitHub, GitLab, and Bitbucket out of the box. Current release: **0.2.0**.
12+
Next.js 16 + SQLite (`better-sqlite3`), styled with Tailwind CSS 4. Spans GitHub, GitLab, and Bitbucket out of the box. Current release: **0.3.0**.
1113

1214
![Agent Friendly Code — leaderboard](./public/demo/light.png)
1315

@@ -58,9 +60,9 @@ Two audiences:
5860

5961
Not pretending the idea is free of risk:
6062

61-
- **Per-model scoring is the hardest part and the easiest to fake.** Today the weights are illustrative. Real "Claude ranks this higher than GPT-5" requires actually running each agent on each repo. That's `tasks/0.3.0/01-benchmark-harness.md`.
63+
- **Per-model scoring is the hardest part and the easiest to fake.** Today the weights are illustrative. Real "Claude ranks this higher than GPT-5" requires actually running each agent on each repo. That's `tasks/1.0.0/03-benchmark-harness.md`.
6264
- **Factory.ai is already in this space.** Differentiation has to stay sharp.
63-
- **Public-shaming risk.** Ranking #47,823 without consent invites angry maintainers. Planned via `tasks/0.4.0/04-opt-out-claim-flow.md`.
65+
- **Public-shaming risk.** Ranking #47,823 without consent invites angry maintainers. Planned via `tasks/0.6.0/01-opt-out-claim-flow.md`.
6466
- **Score gaming.** Once public, people add boilerplate `AGENTS.md` to pass the rubric without being useful. Dynamic (actually-run-an-agent) checks are the counter — see benchmark harness.
6567
- **Freshness.** Scores decay with every push. Webhook-driven rescoring is roadmap.
6668

@@ -82,14 +84,14 @@ Short answer: **low risk**. The app:
8284
- Rate limiting the public API.
8385
- Sandbox the cloner in a container (future-proofing against hypothetical git CVEs).
8486

85-
Auth and per-maintainer controls land with the opt-out / claim flow in v0.4.0.
87+
Auth and per-maintainer controls land with the opt-out / claim flow in v0.6.0.
8688

8789
## Quickstart
8890

8991
```bash
9092
bun install
9193
bun run prepare-hooks # once — installs lefthook pre-commit (Biome + tsc + test + file-length)
92-
bun run seed # score the curated set (155 repos) across GH / GL / BB
94+
bun run seed # score the curated set across GH / GL / BB
9395
bun run dev # http://localhost:3000
9496
```
9597

@@ -108,7 +110,7 @@ Run the unit tests with `bun run test` (uses `node --test` + `tsx`; requires Nod
108110

109111
## Versioning
110112

111-
`lib/version.ts` and `package.json` carry the current release number (currently **0.2.0**). Bumps happen only when we actually cut a release — never when merging intermediate work. The version pill in the header surfaces the number directly; `/changelog` lists what each release shipped.
113+
`lib/version.ts` and `package.json` carry the current release number (currently **0.3.0**). Bumps happen only when we actually cut a release — never when merging intermediate work. The version pill in the header surfaces the number directly; `/changelog` lists what each release shipped.
112114

113115
## Stack & rationale
114116

@@ -169,13 +171,12 @@ LICENSE MIT
169171

170172
See `/roadmap` in the running app or the per-version `tasks/` folders for the full picture.
171173

172-
- **0.3.0 — real per-model weights**: benchmark harness actually runs agents on scoped tasks per repo; current illustrative weights get replaced with measured ones.
173-
- **0.4.0 — ecosystem integration**: badge endpoint for READMEs, GitHub Action that comments score delta on PRs, webhook-driven rescoring, OAuth-gated opt-out / claim flow for maintainers.
174-
- **0.5.0 — alternative recommender**: "repo Y does the same thing and ranks higher for your model."
175-
- **0.6.0 — package-registry overlay**: rank npm / PyPI / Cargo packages by source-repo friendliness.
176-
- **0.7.0 — history-aware signals**: extend the scorer with maintenance recency, commit velocity, and contributor activity — closing the gap that the shallow clone leaves today.
177-
- **1.0.0 — production stability**: Postgres migration for concurrent writers; from here on, breaking API changes require a MAJOR bump.
178-
- **1.1.0 — at-scale GitHub indexing**: flip from a curated seed list to an auto-discovered crawl (GitHub search + trending + submissions). Target 10k repos on first delivery.
174+
Versions are sequenced cheap-first so the highest-impact small additions don't get gated on heavy infra:
175+
176+
- **0.4.0 — quick wins**: history-aware signals (maintenance recency, commit velocity, contributor activity) + a GitHub Action that comments the score delta on every PR. No new infra.
177+
- **0.5.0 — auto-refresh + smarter matching**: webhook-driven rescoring (keep scores fresh on every push) + alternatives via README embeddings (cross-language matches the v0.3.0 SQL heuristic misses).
178+
- **0.6.0 — maintainer ownership + at-scale discovery**: OAuth opt-out / claim flow for maintainers + at-scale package overlay (per-registry leaderboards + userscript that renders the badge inline on npmjs.com / PyPI / crates.io).
179+
- **1.0.0 — production cut**: Postgres migration for concurrent writers + auto-discovered crawl (target 10k repos) + benchmark harness that derives per-model weights from measured agent success. From here on, breaking API changes require a MAJOR bump.
179180

180181
## Defensibility
181182

@@ -188,3 +189,7 @@ MIT — see [LICENSE](./LICENSE).
188189
## Contributing
189190

190191
See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup, branch/commit style, the PR description template, and the changelog discipline.
192+
193+
## Sponsor
194+
195+
If this project is useful to you, consider sponsoring its development: [github.com/sponsors/hsnice16](https://github.com/sponsors/hsnice16).
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { getModelScores, getRepoByHostOwnerName } from "@/lib/db";
2+
import { MODEL_BY_ID, type ModelId } from "@/lib/scoring/weights";
3+
import { badgeSvg } from "@/lib/utils/badge";
4+
5+
export const dynamic = "force-dynamic";
6+
7+
const HEADERS = {
8+
"Content-Type": "image/svg+xml; charset=utf-8",
9+
"Cache-Control": "public, max-age=3600, s-maxage=3600",
10+
};
11+
12+
export async function GET(req: Request, ctx: { params: Promise<{ host: string; owner: string; name: string }> }) {
13+
const { host, owner, name: rawName } = await ctx.params;
14+
const name = rawName.replace(/\.svg$/i, "");
15+
16+
const url = new URL(req.url);
17+
const modelParam = url.searchParams.get("model");
18+
19+
const repo = getRepoByHostOwnerName(host, owner, name);
20+
if (!repo) {
21+
return new Response(badgeSvg("agent friendly", "not scored", null), {
22+
headers: HEADERS,
23+
});
24+
}
25+
26+
let label = "agent friendly";
27+
let score = repo.overall_score;
28+
29+
if (modelParam && modelParam in MODEL_BY_ID) {
30+
const m = modelParam as ModelId;
31+
const ms = getModelScores(repo.id).find((s) => s.modelId === m);
32+
33+
score = ms ? ms.score : null;
34+
label = `agent friendly · ${MODEL_BY_ID[m].label.toLowerCase()}`;
35+
}
36+
37+
const value = score == null ? "—" : score.toFixed(1);
38+
return new Response(badgeSvg(label, value, score), { headers: HEADERS });
39+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { NextResponse } from "next/server";
2+
3+
import { isRegistry } from "@/lib/clients/registries";
4+
import { lookupPackage } from "@/lib/package-lookup";
5+
import { APP_URL } from "@/lib/version";
6+
7+
export const dynamic = "force-dynamic";
8+
9+
const HEADERS = { "Cache-Control": "public, max-age=3600, s-maxage=3600" };
10+
11+
export async function GET(_req: Request, ctx: { params: Promise<{ registry: string; name: string }> }) {
12+
const { registry, name } = await ctx.params;
13+
14+
if (!isRegistry(registry)) {
15+
return NextResponse.json({ error: "unsupported registry", supported: ["npm", "pypi", "cargo"] }, { status: 400 });
16+
}
17+
18+
const result = await lookupPackage(registry, name);
19+
20+
if (result.status === "scored") {
21+
return NextResponse.json(
22+
{
23+
status: result.status,
24+
package: result.package,
25+
registry: result.registry,
26+
per_model: result.per_model,
27+
overall: result.repo.overall_score,
28+
repo: {
29+
id: result.repo.id,
30+
host: result.repo.host,
31+
name: result.repo.name,
32+
owner: result.repo.owner,
33+
},
34+
badge_url: `${APP_URL}/api/badge/${result.repo.host}/${result.repo.owner}/${result.repo.name}.svg`,
35+
},
36+
{ headers: HEADERS },
37+
);
38+
}
39+
40+
return NextResponse.json(result, { headers: HEADERS });
41+
}

app/globals.css

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,10 @@ html {
6767
html,
6868
body {
6969
margin: 0;
70-
background: var(--color-bg);
70+
line-height: 1.6;
7171
color: var(--color-ink);
72+
background: var(--color-bg);
7273
font-family: var(--font-sans);
73-
line-height: 1.6;
7474
-webkit-font-smoothing: antialiased;
7575
}
7676

@@ -99,9 +99,9 @@ textarea,
9999
}
100100

101101
*:focus-visible {
102-
outline: 2px solid var(--color-accent);
103-
outline-offset: 2px;
104102
border-radius: 4px;
103+
outline-offset: 2px;
104+
outline: 2px solid var(--color-accent);
105105
}
106106

107107
input:focus,
@@ -118,10 +118,11 @@ select:focus-visible {
118118
html {
119119
scroll-behavior: auto;
120120
}
121+
121122
*,
122123
*::before,
123124
*::after {
124-
transition: none !important;
125125
animation: none !important;
126+
transition: none !important;
126127
}
127128
}

app/layout.tsx

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ export const metadata: Metadata = {
2626
"Cursor",
2727
"Devin",
2828
"GPT-5 Codex",
29+
"Gemini CLI",
30+
"Aider",
31+
"OpenHands",
32+
"Pi",
2933
"AGENTS.md",
3034
"agent readiness",
3135
"open source",
@@ -53,6 +57,36 @@ export const metadata: Metadata = {
5357
robots: { index: true, follow: true },
5458
};
5559

60+
const JSON_LD = {
61+
"@context": "https://schema.org",
62+
"@graph": [
63+
{
64+
"@type": "Organization",
65+
"@id": `${APP_URL}/#org`,
66+
url: APP_URL,
67+
name: APP_NAME,
68+
sameAs: [REPO_URL],
69+
logo: `${APP_URL}/icon.svg`,
70+
},
71+
{
72+
"@type": "WebSite",
73+
"@id": `${APP_URL}/#site`,
74+
url: APP_URL,
75+
name: APP_NAME,
76+
description: APP_DESCRIPTION,
77+
publisher: { "@id": `${APP_URL}/#org` },
78+
potentialAction: {
79+
"@type": "SearchAction",
80+
target: {
81+
"@type": "EntryPoint",
82+
urlTemplate: `${APP_URL}/?q={search_term_string}`,
83+
},
84+
"query-input": "required name=search_term_string",
85+
},
86+
},
87+
],
88+
};
89+
5690
export const viewport: Viewport = {
5791
themeColor: [
5892
{ media: "(prefers-color-scheme: light)", color: "#ffffff" },
@@ -67,10 +101,25 @@ const NAV_LINKS = [
67101
{ href: "/changelog", label: "Changelog" },
68102
];
69103

104+
const FOOTER_LINKS = [
105+
{ href: "/", label: "Home" },
106+
{ href: "/methodology", label: "Methodology" },
107+
{ href: "/roadmap", label: "Roadmap" },
108+
{ href: "/changelog", label: "Changelog" },
109+
{ href: "/package", label: "Packages" },
110+
];
111+
70112
export default function RootLayout({ children }: { children: React.ReactNode }) {
71113
return (
72114
<html lang="en">
73115
<body>
116+
<script
117+
type="application/ld+json"
118+
// biome-ignore lint/security/noDangerouslySetInnerHtml: JSON-LD requires raw script content; payload is server-controlled and `<` is escaped
119+
dangerouslySetInnerHTML={{
120+
__html: JSON.stringify(JSON_LD).replace(/</g, "\\u003c"),
121+
}}
122+
/>
74123
<GoogleAnalytics />
75124

76125
<header className="sticky top-0 z-20 border-b border-line/80 bg-bg/60 backdrop-blur-md backdrop-saturate-150">
@@ -105,6 +154,13 @@ export default function RootLayout({ children }: { children: React.ReactNode })
105154
<div className="mx-auto max-w-[1080px] px-3 pb-20 pt-8 sm:px-6 sm:pt-10">
106155
<main id="main">{children}</main>
107156
<footer className="mt-12 border-t border-line pt-5 text-[13px] leading-[1.7] text-muted">
157+
<nav aria-label="Secondary" className="mb-2 flex flex-wrap gap-x-4 gap-y-1">
158+
{FOOTER_LINKS.map((l) => (
159+
<Link key={l.href} href={l.href} className="text-ink-dim hover:text-ink-soft">
160+
{l.label}
161+
</Link>
162+
))}
163+
</nav>
108164
Signals are static heuristics — no agent is actually run. Per-model weights are illustrative, not yet
109165
empirically derived.
110166
</footer>

0 commit comments

Comments
 (0)