diff --git a/.claude/settings.json b/.claude/settings.json index 5fbb810..81ddc98 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -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'" } ] } diff --git a/.github/workflows/scheduled-rescore.yml b/.github/workflows/scheduled-rescore.yml new file mode 100644 index 0000000..1d46e1d --- /dev/null +++ b/.github/workflows/scheduled-rescore.yml @@ -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 diff --git a/AGENTS.md b/AGENTS.md index 923ce5b..ccd2c65 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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= 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) @@ -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 @@ -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/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cfe3994..d8a2083 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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: @@ -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 rather than opening an issue. ## Code of conduct diff --git a/README.md b/README.md index f2d062f..0ad7d09 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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) @@ -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. @@ -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 @@ -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. diff --git a/app/about/page.tsx b/app/about/page.tsx index 5c0c700..5c85a01 100644 --- a/app/about/page.tsx +++ b/app/about/page.tsx @@ -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.`, }; diff --git a/app/action/page.tsx b/app/action/page.tsx index 78d6b62..ef90464 100644 --- a/app/action/page.tsx +++ b/app/action/page.tsx @@ -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 = @@ -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 = [ diff --git a/app/changelog/page.tsx b/app/changelog/page.tsx index c2e8521..89fc524 100644 --- a/app/changelog/page.tsx +++ b/app/changelog/page.tsx @@ -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.", }; diff --git a/app/layout.tsx b/app/layout.tsx index 918f2f9..5a8ad9c 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -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, @@ -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" }, }; diff --git a/app/methodology/page.tsx b/app/methodology/page.tsx index b4944e8..2bd6d78 100644 --- a/app/methodology/page.tsx +++ b/app/methodology/page.tsx @@ -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.", }; @@ -47,7 +48,7 @@ const FAQ = [ }, { q: "How often is the data refreshed?", - a: "Manually for now โ€” repositories 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 hours โ€” a 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?", diff --git a/app/opengraph-image.tsx b/app/opengraph-image.tsx new file mode 100644 index 0000000..45c30f8 --- /dev/null +++ b/app/opengraph-image.tsx @@ -0,0 +1,102 @@ +import { ImageResponse } from "next/og"; + +import { getLeaderboardStats } from "@/lib/db"; +import { APP_NAME } from "@/lib/version"; + +export const contentType = "image/png"; +export const alt = `${APP_NAME} โ€” public agent-friendliness leaderboard`; +export const size = { width: 1200, height: 630 }; + +export default function Image() { + const stats = getLeaderboardStats(); + + return new ImageResponse( +
+
+
+ A +
+
{APP_NAME}
+
+ +
+
+ Which public repos are +
+
+ friendliest to AI coding agents? +
+
+ Per-model leaderboard across GitHub, GitLab, and Bitbucket โ€” ranked for Claude Code, Cursor, Devin, Codex, + Gemini, Aider, OpenHands, and Pi. +
+
+ +
+
+ {stats.count > 0 ? `${stats.count} repos scored ยท per-model` : "Per-model ยท static heuristics"} +
+
agentfriendlycode.com
+
+
, + { ...size }, + ); +} diff --git a/app/package/[registry]/[name]/page.tsx b/app/package/[registry]/[name]/page.tsx index d738429..e311b30 100644 --- a/app/package/[registry]/[name]/page.tsx +++ b/app/package/[registry]/[name]/page.tsx @@ -8,7 +8,7 @@ import { Panel, PanelHeading } from "@/components/Panel"; import { ScoreNumber } from "@/components/ScoreNumber"; import { isRegistry, type Registry } from "@/lib/clients/registries"; import { lookupPackage } from "@/lib/package-lookup"; -import { APP_URL } from "@/lib/version"; +import { APP_URL, OG_DEFAULTS, TWITTER_DEFAULTS } from "@/lib/version"; const cachedLookup = cache((registry: Registry, name: string) => lookupPackage(registry, name)); @@ -31,9 +31,9 @@ export async function generateMetadata({ return { title, description, - twitter: { title, description }, + twitter: { ...TWITTER_DEFAULTS, title, description }, alternates: { canonical: `/package/${registry}/${name}` }, - openGraph: { title, description, url: `/package/${registry}/${name}`, type: "article" }, + openGraph: { ...OG_DEFAULTS, title, description, url: `/package/${registry}/${name}`, type: "article" }, ...(isThin ? { robots: { index: false, follow: true } } : {}), }; } diff --git a/app/package/page.tsx b/app/package/page.tsx index 1fea8a2..4a0809f 100644 --- a/app/package/page.tsx +++ b/app/package/page.tsx @@ -5,12 +5,13 @@ import { BreadcrumbJsonLd } from "@/components/BreadcrumbJsonLd"; import { PackageLookupForm } from "@/components/PackageLookupForm"; import { Panel, PanelHeading } from "@/components/Panel"; import { getTopPackagesByRegistry } from "@/lib/db"; +import { OG_DEFAULTS, TWITTER_DEFAULTS } from "@/lib/version"; export const metadata: Metadata = { title: "Packages", - twitter: { title: "Packages" }, + twitter: { ...TWITTER_DEFAULTS, title: "Packages" }, alternates: { canonical: "/package" }, - openGraph: { title: "Packages", url: "/package" }, + openGraph: { ...OG_DEFAULTS, title: "Packages", url: "/package", type: "website" }, description: "Look up any npm, PyPI, or Cargo package to see how agent-friendly its source repo is โ€” the score, per-model breakdown, and an embeddable badge.", }; diff --git a/app/page.tsx b/app/page.tsx index 7ba64a5..a563b46 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -13,10 +13,11 @@ import { SortSelect } from "@/components/SortSelect"; import { type Host, isHost } from "@/lib/constants/hosts"; import { LEADERBOARD_PAGE_SIZE, LEADERBOARD_PAGE_SIZE_MOBILE } from "@/lib/constants/scoring"; import { DEFAULT_DIR, DEFAULT_SORT, isSortDir, isSortKey, type SortDir, type SortKey } from "@/lib/constants/sort"; -import { getLeaderboardStats, type LeaderboardRow, listLeaderboard, listLeaderboardOverall } from "@/lib/db"; +import { getLeaderboardStats, listLeaderboard, listLeaderboardOverall } from "@/lib/db"; import { MODEL_BY_ID, MODELS, type ModelId } from "@/lib/scoring/weights"; +import type { LeaderboardRow } from "@/lib/types/db"; import { compactStars, relativeTime } from "@/lib/utils/format"; -import { APP_URL } from "@/lib/version"; +import { APP_URL, OG_DEFAULTS, TWITTER_DEFAULTS } from "@/lib/version"; const HOME_TITLE = "Agent Friendly Code โ€” AI coding agent friendliness leaderboard for Claude Code, Cursor, Devin, Codex, Gemini, Aider, OpenHands, Pi"; @@ -27,8 +28,8 @@ export const metadata: Metadata = { title: HOME_TITLE, description: HOME_DESCRIPTION, alternates: { canonical: "/" }, - twitter: { title: HOME_TITLE, description: HOME_DESCRIPTION }, - openGraph: { title: HOME_TITLE, description: HOME_DESCRIPTION, url: "/" }, + twitter: { ...TWITTER_DEFAULTS, title: HOME_TITLE, description: HOME_DESCRIPTION }, + openGraph: { ...OG_DEFAULTS, title: HOME_TITLE, description: HOME_DESCRIPTION, url: "/", type: "website" }, }; type SearchParams = { diff --git a/app/privacy/page.tsx b/app/privacy/page.tsx index e97c6a6..94a338f 100644 --- a/app/privacy/page.tsx +++ b/app/privacy/page.tsx @@ -4,15 +4,15 @@ import Link from "next/link"; import { BreadcrumbJsonLd } from "@/components/BreadcrumbJsonLd"; import { ExternalLink } from "@/components/ExternalLink"; import { Panel, PanelHeading } from "@/components/Panel"; -import { APP_NAME, CONTACT_EMAIL } from "@/lib/version"; +import { APP_NAME, CONTACT_EMAIL, OG_DEFAULTS, TWITTER_DEFAULTS } from "@/lib/version"; const LAST_UPDATED = "May 19, 2026"; export const metadata: Metadata = { title: "Privacy Policy", alternates: { canonical: "/privacy" }, - twitter: { title: `Privacy Policy โ€” ${APP_NAME}` }, - openGraph: { title: `Privacy Policy โ€” ${APP_NAME}`, url: "/privacy", type: "article" }, + twitter: { ...TWITTER_DEFAULTS, title: `Privacy Policy โ€” ${APP_NAME}` }, + openGraph: { ...OG_DEFAULTS, title: `Privacy Policy โ€” ${APP_NAME}`, url: "/privacy", type: "article" }, description: `How ${APP_NAME} handles visitor data: what we collect, what we don't, the third-party services we embed, and how to contact us about your data.`, }; @@ -33,10 +33,10 @@ export default function PrivacyPage() { Overview

- {APP_NAME} (the “Service”) is operated by Himanshu Singh as an independent, MIT-licensed project. - There are no user accounts. The Service is a public, read-only dashboard that ranks publicly accessible source - repositories. This policy explains the limited information that is collected when you visit and how it is - used. + {APP_NAME} (the “Service”) is operated by Himanshu Singh as an + independent, MIT-licensed project. There are no user accounts. The Service is a public, read-only dashboard + that ranks publicly accessible source repositories. This policy explains the limited information that is + collected when you visit and how it is used.

diff --git a/app/repo/[id]/page.tsx b/app/repo/[id]/page.tsx index ac9bced..38ad673 100644 --- a/app/repo/[id]/page.tsx +++ b/app/repo/[id]/page.tsx @@ -16,7 +16,7 @@ import { STRENGTHS_GAPS_VISIBLE_LIMIT } from "@/lib/constants/scoring"; import { getAlternatives, getModelScores, getRepo, getSignalResults } from "@/lib/db"; import { topImprovements } from "@/lib/scoring/scorer"; import { MODEL_BY_ID, MODELS, type ModelId } from "@/lib/scoring/weights"; -import { ACTION_USES, APP_KEYWORDS, APP_URL } from "@/lib/version"; +import { ACTION_USES, APP_KEYWORDS, APP_URL, OG_DEFAULTS, TWITTER_DEFAULTS } from "@/lib/version"; export async function generateMetadata({ params }: { params: Promise<{ id: string }> }): Promise { const { id: idStr } = await params; @@ -54,9 +54,9 @@ export async function generateMetadata({ params }: { params: Promise<{ id: strin title, description, keywords: repoKeywords, - twitter: { title, description }, alternates: { canonical: `/repo/${id}` }, - openGraph: { title, description, url: `/repo/${id}`, type: "article" }, + twitter: { ...TWITTER_DEFAULTS, title, description }, + openGraph: { ...OG_DEFAULTS, title, description, url: `/repo/${id}`, type: "article" }, }; } diff --git a/app/repo/[id]/twitter-image.tsx b/app/repo/[id]/twitter-image.tsx new file mode 100644 index 0000000..8f726ce --- /dev/null +++ b/app/repo/[id]/twitter-image.tsx @@ -0,0 +1 @@ +export { alt, contentType, default, size } from "./opengraph-image"; diff --git a/app/roadmap/page.tsx b/app/roadmap/page.tsx index 7db2282..a65c342 100644 --- a/app/roadmap/page.tsx +++ b/app/roadmap/page.tsx @@ -3,15 +3,15 @@ import type { Metadata } from "next"; import { BreadcrumbJsonLd } from "@/components/BreadcrumbJsonLd"; import { Panel, PanelHeading } from "@/components/Panel"; import { ROADMAP } from "@/lib/roadmap"; -import { REPO_URL } from "@/lib/version"; +import { OG_DEFAULTS, REPO_URL, TWITTER_DEFAULTS } from "@/lib/version"; export const metadata: Metadata = { title: "Roadmap", - twitter: { title: "Roadmap" }, + twitter: { ...TWITTER_DEFAULTS, title: "Roadmap" }, alternates: { canonical: "/roadmap" }, - openGraph: { title: "Roadmap", url: "/roadmap", type: "article" }, + openGraph: { ...OG_DEFAULTS, title: "Roadmap", url: "/roadmap", type: "article" }, description: - "What's planned for Agent Friendly Code: dogfooding, benchmark-derived weights, ecosystem integration (badges, PR diffs, webhooks, opt-out), discovery surfaces, production stability, and at-scale GitHub indexing.", + "What's planned for Agent Friendly Code: dogfooding, benchmark-derived weights, ecosystem integration (badges, PR diffs, scheduled rescoring, opt-out), discovery surfaces, production stability, and at-scale GitHub indexing.", }; export default function RoadmapPage() { diff --git a/app/sitemap.ts b/app/sitemap.ts index 4cd2038..4a4c311 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -5,6 +5,7 @@ import { getLeaderboardStats, getTopPackagesByRegistry, listLeaderboardOverall } import { APP_URL } from "@/lib/version"; const SITEMAP_PACKAGE_LIMIT_PER_REGISTRY = 10000; +const LEGAL_LAST_UPDATED = new Date("2026-05-19"); export default function sitemap(): MetadataRoute.Sitemap { const now = new Date(); @@ -45,20 +46,20 @@ export default function sitemap(): MetadataRoute.Sitemap { { priority: 0.5, lastModified: now, - changeFrequency: "monthly", url: `${APP_URL}/about`, + changeFrequency: "monthly", }, { priority: 0.3, - lastModified: now, changeFrequency: "yearly", url: `${APP_URL}/privacy`, + lastModified: LEGAL_LAST_UPDATED, }, { priority: 0.3, - lastModified: now, url: `${APP_URL}/terms`, changeFrequency: "yearly", + lastModified: LEGAL_LAST_UPDATED, }, { priority: 0.6, @@ -72,6 +73,12 @@ export default function sitemap(): MetadataRoute.Sitemap { changeFrequency: "weekly", url: `${APP_URL}/changelog`, }, + { + priority: 0.4, + lastModified: lastScored, + changeFrequency: "weekly", + url: `${APP_URL}/llms.txt`, + }, ]; const repoRoutes: MetadataRoute.Sitemap = listLeaderboardOverall().map((r) => ({ diff --git a/app/skill/page.tsx b/app/skill/page.tsx index 4fb85dc..026c5e4 100644 --- a/app/skill/page.tsx +++ b/app/skill/page.tsx @@ -4,7 +4,16 @@ import { CopySnippet } from "@/components/CopySnippet"; import { ExternalLink } from "@/components/ExternalLink"; import { Panel, PanelHeading } from "@/components/Panel"; import { CLAUDE_HOOK_SNIPPET, CODEX_HOOK_SNIPPET, SCORE_BANDS, SKILL_FAQ } from "@/lib/skill-content"; -import { ACTION_REPO_URL, APP_KEYWORDS, APP_NAME, APP_URL, SKILL_INSTALL_CMD, SKILL_REPO_URL } from "@/lib/version"; +import { + ACTION_REPO_URL, + APP_KEYWORDS, + APP_NAME, + APP_URL, + OG_DEFAULTS, + SKILL_INSTALL_CMD, + SKILL_REPO_URL, + TWITTER_DEFAULTS, +} from "@/lib/version"; const PAGE_TITLE = "Agent Friendly Skill โ€” score your repo locally and pick the right model"; const PAGE_DESCRIPTION = @@ -17,8 +26,8 @@ export const metadata: Metadata = { keywords: PAGE_KEYWORDS, description: PAGE_DESCRIPTION, alternates: { canonical: "/skill" }, - twitter: { title: PAGE_TITLE, description: PAGE_DESCRIPTION }, - openGraph: { title: PAGE_TITLE, description: PAGE_DESCRIPTION, url: "/skill", type: "article" }, + twitter: { ...TWITTER_DEFAULTS, title: PAGE_TITLE, description: PAGE_DESCRIPTION }, + openGraph: { ...OG_DEFAULTS, title: PAGE_TITLE, description: PAGE_DESCRIPTION, url: "/skill", type: "article" }, }; const FAQ_JSON_LD = { diff --git a/app/terms/page.tsx b/app/terms/page.tsx index 6c7a0cd..4abbd7e 100644 --- a/app/terms/page.tsx +++ b/app/terms/page.tsx @@ -4,15 +4,15 @@ import Link from "next/link"; import { BreadcrumbJsonLd } from "@/components/BreadcrumbJsonLd"; import { ExternalLink } from "@/components/ExternalLink"; import { Panel, PanelHeading } from "@/components/Panel"; -import { APP_NAME, CONTACT_EMAIL, REPO_URL } from "@/lib/version"; +import { APP_NAME, CONTACT_EMAIL, OG_DEFAULTS, REPO_URL, TWITTER_DEFAULTS } from "@/lib/version"; const LAST_UPDATED = "May 19, 2026"; export const metadata: Metadata = { title: "Terms of Use", alternates: { canonical: "/terms" }, - twitter: { title: `Terms of Use โ€” ${APP_NAME}` }, - openGraph: { title: `Terms of Use โ€” ${APP_NAME}`, url: "/terms", type: "article" }, + twitter: { ...TWITTER_DEFAULTS, title: `Terms of Use โ€” ${APP_NAME}` }, + openGraph: { ...OG_DEFAULTS, title: `Terms of Use โ€” ${APP_NAME}`, url: "/terms", type: "article" }, description: `Terms of Use for ${APP_NAME}: what the Service is, how you may use it, the limits of the scoring data, and the operator's disclaimers.`, }; @@ -32,8 +32,8 @@ export default function TermsPage() { Acceptance

- By accessing or using {APP_NAME} (the “Service”), you agree to these Terms of Use. If you do not - agree, please do not use the Service. + By accessing or using {APP_NAME} (the “Service”), you agree to + these Terms of Use. If you do not agree, please do not use the Service.

diff --git a/app/twitter-image.tsx b/app/twitter-image.tsx new file mode 100644 index 0000000..8f726ce --- /dev/null +++ b/app/twitter-image.tsx @@ -0,0 +1 @@ +export { alt, contentType, default, size } from "./opengraph-image"; diff --git a/components/AlternativesStrip.tsx b/components/AlternativesStrip.tsx index cfea693..60994fb 100644 --- a/components/AlternativesStrip.tsx +++ b/components/AlternativesStrip.tsx @@ -1,6 +1,6 @@ import Link from "next/link"; -import type { AlternativeRow } from "@/lib/db"; +import type { AlternativeRow } from "@/lib/types/db"; import { compactStars } from "@/lib/utils/format"; import { Panel, PanelHeading } from "./Panel"; @@ -23,7 +23,7 @@ export function AlternativesStrip({ language, alternatives, selectedModelLabel }

Same-language repos scored for {selectedModelLabel}. Heuristic v1 - (same language + same host); cross-language matches are refined in v0.6.0. + (same language + same host).