Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"hooks": [
{
"type": "command",
"command": "printf '\\n📦 Agent Friendly Code — current release: 0.5.0\\n • Read AGENTS.md for conventions, CONTRIBUTING.md for the PR workflow.\\n • Roadmap: 0.6.0 (auto-refresh + smarter matching — webhook rescoring + alternatives v2) → 0.7.0 (maintainer ownership + at-scale discovery — OAuth opt-out + package overlay at scale) → 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'"
"command": "printf '\\n📦 Agent Friendly Code — current release: 0.6.0\\n • Read AGENTS.md for conventions, CONTRIBUTING.md for the PR workflow.\\n • Roadmap: 0.7.0 (maintainer ownership + at-scale discovery — OAuth opt-out + package overlay at scale) → 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'"
}
]
}
Expand Down
54 changes: 54 additions & 0 deletions .github/workflows/scheduled-rescore.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Scheduled rescore

on:
schedule:
- cron: "0 */6 * * *"
workflow_dispatch:

concurrency:
group: scheduled-rescore
cancel-in-progress: false

permissions:
contents: write

jobs:
rescore:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.16

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "22"

- name: Install
run: bun install --frozen-lockfile

- name: Snapshot pre-rescore overall scores
run: sqlite3 data/rank.db "SELECT url, overall_score FROM repo ORDER BY url;" > /tmp/scores-before.txt

- name: Rescore curated seeds
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: bun run seed

- name: Commit and push if any overall score changed
run: |
sqlite3 data/rank.db "SELECT url, overall_score FROM repo ORDER BY url;" > /tmp/scores-after.txt
if diff -q /tmp/scores-before.txt /tmp/scores-after.txt > /dev/null; then
echo "No overall-score changes — skipping commit (last_scored_at churn ignored)."
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add data/rank.db
git commit -m "chore: scheduled rescore $(date -u +%Y-%m-%dT%H:%MZ)"
git push
9 changes: 7 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ app/
api/score/route.ts # /api/score?host=&repo=owner/name — public lookup for external integrators (siblings vendor the scorer; they don't call this)
api/badge/[host]/[owner]/[name]/route.ts # SVG badge for README embeds (?model=<id> for per-model)
api/package/[registry]/[name]/route.ts # npm/PyPI/Cargo lookup → source-repo score
opengraph-image.tsx # next/og convention — home OG image, 1200×630 (auto-wired)
twitter-image.tsx # next/og convention — twitter:image, re-exports opengraph-image (auto-wired)
repo/[id]/opengraph-image.tsx # next/og convention — per-repo OG image (auto-wired)
repo/[id]/twitter-image.tsx # next/og convention — per-repo twitter:image, re-exports (auto-wired)
package/page.tsx # explainer + try-it examples
package/[registry]/[name]/page.tsx # scored | not_scored | unresolved states
action/page.tsx # PR-diff GitHub Action explainer + install snippet (SEO landing for the sibling action repo)
Expand Down Expand Up @@ -87,9 +90,11 @@ lib/
scorer.ts # signals × weights, topImprovements
clients/
git.ts, github.ts, registries.ts # registries.ts: npm/PyPI/Cargo package → source-repo URL
types/
db.ts # shared row-shape types for lib/db.ts (RepoRow, LeaderboardRow, …)
package-lookup.ts # shared registry → repo lookup (used by /api/package + /package page)
db.ts # better-sqlite3 schema + queries
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
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)
changelog.ts # typed ChangelogEntry[]
roadmap.ts # typed RoadmapVersion[]
skill-content.ts # SKILL_FAQ + SCORE_BANDS + hook snippets — content for /skill page
Expand All @@ -108,7 +113,7 @@ tasks/
0.3.0/ # released — embeddable scores + broader coverage (badge, more agents, alternatives, package lookup)
0.4.0/ # released — credible scores + discoverability (docs-cited rationales + agent-specific signals + About/llms.txt/OG)
0.5.0/ # released — quick wins (PR score-diff action + agent skill)
0.6.0/ # planned — auto-refresh + smarter matching (webhook rescoring + alternatives v2)
0.6.0/ # released — auto-refresh (scheduled rescoring)
0.7.0/ # planned — maintainer ownership + at-scale discovery (OAuth opt-out + package overlay at scale)
1.0.0/ # planned — production cut (Postgres + at-scale indexing + benchmark harness)
.claude/
Expand Down
4 changes: 3 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ bun run dev # http://localhost:3000
bun run test # unit tests (node --test + tsx) — requires Node ≥20.9.0
```

> **About `data/rank.db`** — a GitHub Actions cron (`.github/workflows/scheduled-rescore.yml`) re-runs `bun run seed` every six hours and commits the refreshed database to `main`. If your branch touches `scripts/seed-list.ts` and you also commit a re-seeded `rank.db`, you may hit a merge conflict against a cron commit. Easiest resolution: rebase, `git checkout --theirs -- data/rank.db`, and re-run `bun run seed` before pushing.

## Branch naming

Lowercase, kebab-case, prefixed by intent:
Expand Down Expand Up @@ -121,7 +123,7 @@ A PR is ready to merge when:

## Security

See the "Security / threat surface" section of [AGENTS.md](./AGENTS.md). For vulnerability reports that shouldn't be public, email hsnice16@gmail.com rather than opening an issue.
See the "Security / threat surface" section of [AGENTS.md](./AGENTS.md). For vulnerability reports that shouldn't be public, email <hsnice16@gmail.com> rather than opening an issue.

## Code of conduct

Expand Down
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Agent Friendly Code

[![Release](https://img.shields.io/badge/release-0.5.0-blue?style=flat-square)](./lib/changelog.ts)
[![Release](https://img.shields.io/badge/release-0.6.0-blue?style=flat-square)](./lib/changelog.ts)
[![License: MIT](https://img.shields.io/badge/license-MIT-green?style=flat-square)](./LICENSE)
[![Next.js 16](https://img.shields.io/badge/Next.js-16-black?style=flat-square)](https://nextjs.org)
[![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)
Expand All @@ -9,7 +9,7 @@

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

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

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

Expand Down Expand Up @@ -64,7 +64,7 @@ Not pretending the idea is free of risk:
- **Factory.ai is already in this space.** Differentiation has to stay sharp.
- **Public-shaming risk.** Ranking #47,823 without consent invites angry maintainers. Planned via `tasks/0.7.0/01-opt-out-claim-flow.md`.
- **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.
- **Freshness.** Scores decay with every push. Webhook-driven rescoring is roadmap.
- **Freshness.** Scores decay with every push. A 6-hourly GitHub Actions cron rescores the curated set; webhook-driven sub-minute refresh is deferred until the claim flow lands in 0.7.0.

See `/methodology` in the running app for a candid walkthrough of what's measured today and what isn't.

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

## Versioning

`lib/version.ts` and `package.json` carry the current release number (currently **0.5.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.
`lib/version.ts` and `package.json` carry the current release number (currently **0.6.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.

## Stack & rationale

Expand Down Expand Up @@ -200,7 +200,6 @@ See `/roadmap` in the running app or the per-version `tasks/` folders for the fu

Versions are sequenced cheap-first so the highest-impact small additions don't get gated on heavy infra:

- **0.6.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).
- **0.7.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).
- **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.

Expand Down
6 changes: 3 additions & 3 deletions app/about/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import Link from "next/link";
import { BreadcrumbJsonLd } from "@/components/BreadcrumbJsonLd";
import { ExternalLink } from "@/components/ExternalLink";
import { Panel, PanelHeading } from "@/components/Panel";
import { APP_NAME, APP_URL, REPO_URL } from "@/lib/version";
import { APP_NAME, APP_URL, OG_DEFAULTS, REPO_URL, TWITTER_DEFAULTS } from "@/lib/version";

export const metadata: Metadata = {
title: "About",
alternates: { canonical: "/about" },
twitter: { title: `About — ${APP_NAME}` },
openGraph: { title: `About — ${APP_NAME}`, url: "/about", type: "article" },
twitter: { ...TWITTER_DEFAULTS, title: `About — ${APP_NAME}` },
openGraph: { ...OG_DEFAULTS, title: `About — ${APP_NAME}`, url: "/about", type: "article" },
description: `Who built ${APP_NAME}, why it exists, and what it isn't. Independent, MIT-licensed, no affiliation with any AI agent vendor.`,
};

Expand Down
14 changes: 11 additions & 3 deletions app/action/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@ import type { Metadata } from "next";
import { ActionEmbed } from "@/components/ActionEmbed";
import { ExternalLink } from "@/components/ExternalLink";
import { Panel, PanelHeading } from "@/components/Panel";
import { ACTION_REPO_URL, ACTION_USES, APP_KEYWORDS, APP_NAME, APP_URL } from "@/lib/version";
import {
ACTION_REPO_URL,
ACTION_USES,
APP_KEYWORDS,
APP_NAME,
APP_URL,
OG_DEFAULTS,
TWITTER_DEFAULTS,
} from "@/lib/version";

const PAGE_TITLE = "Agent Friendly Action — PR-diff GitHub Action for AI agent-friendliness";
const PAGE_DESCRIPTION =
Expand All @@ -27,8 +35,8 @@ export const metadata: Metadata = {
keywords: PAGE_KEYWORDS,
description: PAGE_DESCRIPTION,
alternates: { canonical: "/action" },
twitter: { title: PAGE_TITLE, description: PAGE_DESCRIPTION },
openGraph: { title: PAGE_TITLE, description: PAGE_DESCRIPTION, url: "/action", type: "article" },
twitter: { ...TWITTER_DEFAULTS, title: PAGE_TITLE, description: PAGE_DESCRIPTION },
openGraph: { ...OG_DEFAULTS, title: PAGE_TITLE, description: PAGE_DESCRIPTION, url: "/action", type: "article" },
};

const FAQ = [
Expand Down
5 changes: 3 additions & 2 deletions app/changelog/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import Link from "next/link";
import { BreadcrumbJsonLd } from "@/components/BreadcrumbJsonLd";
import { Panel } from "@/components/Panel";
import { CHANGELOG } from "@/lib/changelog";
import { OG_DEFAULTS, TWITTER_DEFAULTS } from "@/lib/version";

export const metadata: Metadata = {
title: "Changelog",
twitter: { title: "Changelog" },
alternates: { canonical: "/changelog" },
openGraph: { title: "Changelog", url: "/changelog", type: "article" },
twitter: { ...TWITTER_DEFAULTS, title: "Changelog" },
openGraph: { ...OG_DEFAULTS, title: "Changelog", url: "/changelog", type: "article" },
description:
"What's shipped in each release of Agent Friendly Code — user-facing capabilities, not internal churn. Every bullet corresponds to a roadmap item that landed.",
};
Expand Down
36 changes: 22 additions & 14 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import { BackToTop } from "@/components/BackToTop";
import { GoogleAnalytics } from "@/components/GoogleAnalytics";
import { MobileNav } from "@/components/MobileNav";
import { VersionPill } from "@/components/VersionPill";
import { APP_DESCRIPTION, APP_KEYWORDS, APP_NAME, APP_URL, REPO_URL } from "@/lib/version";

const OG_IMAGE = {
width: 1200,
height: 630,
url: "/demo/light.png",
alt: `${APP_NAME} — public leaderboard`,
};
import {
APP_DESCRIPTION,
APP_KEYWORDS,
APP_NAME,
APP_URL,
OG_DEFAULTS,
REPO_URL,
TWITTER_DEFAULTS,
} from "@/lib/version";

export const metadata: Metadata = {
keywords: APP_KEYWORDS,
Expand All @@ -24,22 +25,29 @@ export const metadata: Metadata = {
authors: [{ name: "Himanshu Singh", url: REPO_URL }],
title: { default: APP_NAME, template: `%s · ${APP_NAME}` },
openGraph: {
...OG_DEFAULTS,
url: "/",
locale: "en_US",
title: APP_NAME,
type: "website",
images: [OG_IMAGE],
siteName: APP_NAME,
description: APP_DESCRIPTION,
},
twitter: {
...TWITTER_DEFAULTS,
title: APP_NAME,
images: [OG_IMAGE.url],
card: "summary_large_image",
description: APP_DESCRIPTION,
},
alternates: { canonical: "/" },
robots: { index: true, follow: true },
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
"max-snippet": -1,
"max-video-preview": -1,
"max-image-preview": "large",
},
},
other: { "google-adsense-account": "ca-pub-8901860576820221" },
};

Expand Down
7 changes: 4 additions & 3 deletions app/methodology/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import { ExternalLink } from "@/components/ExternalLink";
import { Panel, PanelHeading } from "@/components/Panel";
import { SIGNALS } from "@/lib/scoring/signals";
import { MODELS } from "@/lib/scoring/weights";
import { OG_DEFAULTS, TWITTER_DEFAULTS } from "@/lib/version";

export const metadata: Metadata = {
title: "Methodology",
twitter: { title: "Methodology" },
alternates: { canonical: "/methodology" },
openGraph: { title: "Methodology", url: "/methodology", type: "article" },
twitter: { ...TWITTER_DEFAULTS, title: "Methodology" },
openGraph: { ...OG_DEFAULTS, title: "Methodology", url: "/methodology", type: "article" },
description:
"How scores are computed today: the signals checked, the per-model weight profiles, the scoring formula, and what the static-heuristic approach deliberately doesn't measure yet.",
};
Expand Down Expand Up @@ -47,7 +48,7 @@ const FAQ = [
},
{
q: "How often is the data refreshed?",
a: "Manually for nowrepositories are re-scored when the seed list changes or the rubric is updated. Webhook-driven rescoring on every push is planned for v0.6.0.",
a: "Every six hoursa GitHub Actions cron runs the full scorer over the curated seed list and commits the refreshed database to the repo, which auto-deploys. Repositories are also re-scored whenever the seed list changes or the rubric is updated.",
},
{
q: "Which forges are supported?",
Expand Down
Loading
Loading