DevLens

← Home

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 RepoReport JSON object. Cached in Redis for 15 minutes unless weights param is present.

Query params:
  • repo (required)owner/name or full GitHub URL
  • weights (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ᵢ = 1
Default weights are listed below. Users can override them via the UI weight sliders or the weights query param — they are re-normalized automatically.
DimensionKeyDefault WeightData SourceScore Logic
README Qualityreadme20%repos/{owner}/{name}/readmeScores 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 Activityactivity20%repos/.../commits?since=90dCounts 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 Freshnessfreshness15%repo.pushed_atDays since last push to the default branch (pushed_at field). ≤7 days = 100 · ≤30 = 80 · ≤90 = 55 · ≤180 = 30 · older = 10.
Documentationdocs15%git/trees/HEAD?recursiveWalks 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 Setupci10%actions/workflowsCounts GitHub Actions workflow files via the Actions Workflows API. ≥3 workflows = 100 · ≥1 = 60 · 0 = 0.
Issue Responseissues10%issues?state=closedFetches up to 50 closed issues and compares against open_issues_count. Score = round(closed / total × 100). No issues at all = 100.
Community Signalcommunity5%repo.stargazers_countMath.min(Math.floor(log1p(stars) × 15) + Math.floor(log1p(forks) × 10), 100). Rewards repos with organic momentum.
PR Velocitypr_velocity3%pulls?state=closedFetches 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.
Securitysecurity2%git/trees/HEAD?recursiveWalks 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 Variables
The 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:
[![DevLens Health](https://devlens-io.vercel.app/api/badge?repo=owner/name)]
(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
No private repo data, no user emails, no personal data beyond IP for visit counting.

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).