Documentation
Complete technical reference for DevLens — scoring algorithm, API, self-hosting, and integration guides.
Quick Start
1. Analyze any repo
Paste a GitHub URL or
owner/name slug (e.g. vercel/next.js) into the search bar on the home page and click Analyze. The result is fetched live from the GitHub API — no cache for custom-weighted runs.2. Read the report
The health report shows a weighted Health Score / 100 plus a breakdown across all 9 dimensions with color-coded bars (green ≥ 80, amber ≥ 50, red below 50). A trend chart appears if historical snapshots exist for the repo. Actionable suggestions are listed for every dimension scoring below 80.
3. Adjust weights
Expand the Adjust Weights panel before analyzing. Drag each slider to redistribute importance across the 9 dimensions. Weights are normalized to sum to 100% automatically. Custom weights bypass the Redis cache so you always get a fresh score.
4. Badge & README marker
Click Add to your repo → on any report to copy the README badge markdown and the optional GitHub Actions workflow that re-runs DevLens on every push.
5. Org analysis
Visit /org and enter an org slug (e.g.
vercel). DevLens fetches up to 30 public repos, scores each one concurrently, and ranks them by health score.API Reference
GET /api/analyze?repo=owner/name
Returns a
Query params:
Response shape:
RepoReport JSON object. Cached in Redis for 15 minutes unless weights param is present.Query params:
repo(required) —owner/nameor full GitHub URLweights(optional) — URL-encoded JSON of{readme:0.2, activity:0.2, ...}
Response shape:
{
repo: "vercel/next.js",
owner: "vercel",
name: "next.js",
description: string | null,
stars: number,
forks: number,
language: string | null,
avatar: string, // owner avatar URL
url: string, // github.com URL
healthScore: number, // 0–100 weighted final score
scores: {
readme: number, activity: number, freshness: number,
docs: number, ci: number, issues: number,
community: number, pr_velocity: number, security: number
},
suggestions: [{ dim: string, message: string }],
badgeUrl: string, // shields.io badge URL
generatedAt: string // ISO 8601
}GET /api/compare?a=owner/a&b=owner/b
Runs two concurrent
/api/analyze calls and returns both reports.{ a: RepoReport, b: RepoReport }GET /api/history?repo=owner/name
Returns up to 12 historical weekly snapshots stored in Redis whenever the repo is analyzed (without custom weights).
{
history: [
{ week: "W04-07", score: 74, date: "2026-04-07T..." },
...
]
}GET /api/watchlist
Returns the last 100 repos analyzed by any DevLens visitor — powers the Checked and home page recent lists.
{ list: [{ slug, score, description, language, savedAt }] }GET /api/org-watchlist
Returns the last 50 orgs analyzed, with per-org repo count, avg score, and top repo.
{ list: [{ org, repoCount, avgScore, topRepo, savedAt }] }GET /api/stats
Returns live usage stats aggregated from Redis: total analyses, unique visitors, daily activity (last 30 days), top repos by hit count, top orgs, avg health score, and top language.
{
totalAnalyses: number,
analysesToday: number,
uniqueVisitors: number,
totalReposChecked: number,
totalOrgsChecked: number,
avgScore: number | null,
topLanguage: string | null,
topRepos: [{ slug, count, score, lastSeen }],
topOrgs: [{ org, repoCount, avgScore, topRepo, savedAt }],
dailyActivity: [{ date: "YYYY-MM-DD", count: number }]
}GET /api/leaderboard
Returns repos ranked by health score from the watchlist.
{ list: [{ slug, score, description, language, savedAt }] }GET /api/badge?repo=owner/name
Returns a JSON object with a
badgeUrl (shields.io) and the current healthScore. Embeddable directly in any README.Scoring Algorithm
How the final score is calculated
Each of the 9 dimensions returns a raw score 0–100. The final
healthScore is a weighted average:healthScore = Σ (dimScoreᵢ × weightᵢ) where Σ weightᵢ = 1Default weights are listed below. Users can override them via the UI weight sliders or the
weights query param — they are re-normalized automatically.| Dimension | Key | Default Weight | Data Source | Score Logic |
|---|---|---|---|---|
| README Quality | readme | 20% | repos/{owner}/{name}/readme | Scores length (10 + 5 + 5 pts for 500 / 1500 / 3000 chars), presence of keywords install, usage, license, contributing, feature, example (6 pts each), code blocks (8), images (6), ## headings (4), list items (4), setup / roadmap / sponsor / discord mentions (4 each). Max 100. |
| Commit Activity | activity | 20% | repos/.../commits?since=90d | Counts commits to the default branch in the last 90 days via the GitHub Commits API. ≥30 = 100 · ≥15 = 75 · ≥5 = 50 · ≥1 = 25 · 0 = 0. |
| Repo Freshness | freshness | 15% | repo.pushed_at | Days since last push to the default branch (pushed_at field). ≤7 days = 100 · ≤30 = 80 · ≤90 = 55 · ≤180 = 30 · older = 10. |
| Documentation | docs | 15% | git/trees/HEAD?recursive | Walks the full repo tree (git/trees/HEAD?recursive=1) looking for: LICENSE, CONTRIBUTING.md, CHANGELOG.md, CODE_OF_CONDUCT.md, SECURITY.md, docs/ folder — 16 pts each, max 100. |
| CI/CD Setup | ci | 10% | actions/workflows | Counts GitHub Actions workflow files via the Actions Workflows API. ≥3 workflows = 100 · ≥1 = 60 · 0 = 0. |
| Issue Response | issues | 10% | issues?state=closed | Fetches up to 50 closed issues and compares against open_issues_count. Score = round(closed / total × 100). No issues at all = 100. |
| Community Signal | community | 5% | repo.stargazers_count | Math.min(Math.floor(log1p(stars) × 15) + Math.floor(log1p(forks) × 10), 100). Rewards repos with organic momentum. |
| PR Velocity | pr_velocity | 3% | pulls?state=closed | Fetches last 20 closed PRs, filters to merged ones, averages (merged_at − created_at). <1 day = 100 · <3 = 85 · <7 = 65 · <14 = 45 · <30 = 25 · else = 10. No merged PRs = 50. |
| Security | security | 2% | git/trees/HEAD?recursive | Walks the repo tree for SECURITY.md (+30), .github/dependabot.yml (+35), and any workflow containing codeql / trivy / snyk (+35). Max 100. |
Caching
Successful analyses (without custom weights) are cached in Upstash Redis for 15 minutes under
cache:{owner}:{name}. Historical snapshots are appended to history:{owner}:{name} and kept for the last 12 data points. Custom-weight runs bypass the cache entirely to return a fresh score.Rate limiting
Unauthenticated GitHub API calls are limited to 60 req/hour per IP. Signing in with GitHub (“Sign in with GitHub” button on rate-limit error) grants 5,000 req/hour via OAuth token. Self-hosters can set
GITHUB_TOKEN in env vars as a fallback token for server-side calls.Self-Hosting
Prerequisites
Node.js ≥20, an Upstash Redis database (free tier works), a GitHub OAuth App for auth, and optionally a
GITHUB_TOKEN for server-side API calls.Clone & install
git clone https://github.com/SamoTech/devlens cd devlens/dashboard npm install
Environment variables
Copy
.env.example to .env.local and fill in:# GitHub OAuth (for Sign in with GitHub) AUTH_GITHUB_ID=your_oauth_app_client_id AUTH_GITHUB_SECRET=your_oauth_app_client_secret AUTH_SECRET=any_random_32char_string # Upstash Redis (required for watchlist, history, stats) UPSTASH_REDIS_REST_URL=https://... UPSTASH_REDIS_REST_TOKEN=... # Optional — server-side fallback GitHub token (60 → 5000 req/hr) GITHUB_TOKEN=ghp_...
Run locally
npm run dev # → http://localhost:3000
Deploy to Vercel
vercel --cwd dashboard # Then add all env vars in Vercel → Project → Settings → Environment VariablesThe project root is
dashboard/. Set the Root Directory to dashboard in Vercel project settings.GitHub OAuth App setup
Go to GitHub → Settings → Developer settings → OAuth Apps → New OAuth App. Set Homepage URL to your domain and Authorization callback URL to
https://yourdomain.com/api/auth/callback/github. Copy the Client ID and Secret into your env vars.Integrations
README badge
Get a live shields.io badge from
/api/badge?repo=owner/name and paste into your README:[] (https://devlens-io.vercel.app/?repo=owner/name)
GitHub Actions workflow
Add the following workflow to re-score your repo on every push to main:
# .github/workflows/devlens.yml
name: DevLens Health Check
on:
push:
branches: [main]
schedule:
- cron: "0 9 * * 1" # every Monday at 09:00 UTC
jobs:
score:
runs-on: ubuntu-latest
steps:
- name: Fetch DevLens score
run: |
curl -s "https://devlens-io.vercel.app/api/analyze?repo=${{ github.repository }}" | jq '.healthScore'Org-level analysis
The /org page (or
/api/org?org=orgname if you build a custom client) scores up to 30 public repos concurrently and ranks by health score, giving a portfolio-level view of repo health.Data & Privacy
What DevLens stores
DevLens stores only public GitHub data in Upstash Redis:
- Repo slug, health score, description, language, and timestamp — for the recently-checked and leaderboard lists
- Per-repo hit counts and daily analysis counts — for the stats page
- Hashed/raw visitor IPs in a Redis set — for unique visitor count only
- Historical score snapshots per repo — for the trend chart
Retention
Cached reports expire after 15 minutes. Watchlist and stats data have no automatic TTL but are bounded (watchlist capped at 100 entries, history at 12 points per repo).