Paste a public GitHub repository β get explainable health scores, secret & CVE detection, an architecture graph with cycle detection, and a prioritized prescription.
Features Β· How scoring works Β· Quick start Β· API Β· Roadmap
RepoDoctor is a full-stack static-analysis tool β not another GitHub dashboard. It clones a repository, runs a dependency-free analysis engine over every source file, and produces a medical report for your codebase: vitals, diagnosis, and prescription.
| π Explainable scores | Every score ships with the exact penalties behind it. Click 43% and see why β no black box, no vibes. |
| π Real security signals | 12 secret patterns with entropy + placeholder filtering, committed credential files (.env, .pem, .tfstateβ¦), and 9 dangerous code patterns (eval, shell=True, verify=False, TLS bypassβ¦). |
| π¦ Dependency CVEs | Parses 8 lockfile formats and queries OSV.dev against your exact locked versions β the same CVE database powering GitHub's own advisory data. |
| π§ Architecture graph | JS/TS + Python import graph, aggregated by module, with Tarjan SCC cycle detection, fan-in hotspots and external-dependency mapping β rendered by a hand-rolled force layout, zero graph libraries. |
| π₯ Head-to-Head | Put two repositories in the ring: mirrored category bars, a winner per vital, unique-findings diff, and a shareable duel card. Rematches are instant thanks to result caching. |
| π©Ή Prescription | Every finding maps to an action with impact Γ effort ranking β the plan works with zero configuration; an optional LLM layer turns it into a written narrative. |
| π·οΈ Shareable output | shields-style SVG badge, full duel card as SVG, one-click PDF export via a print stylesheet, JSON export of the raw report. |
Five vitals, each fully explainable:
| Vital | What it actually measures |
|---|---|
| Code Quality | Cyclomatic complexity per function (branch-point approximation), deep nesting, god files, cross-file duplication, comment density |
| Security | Committed secrets (entropy-scored), credential files, dangerous patterns, and dependency vulnerabilities from OSV.dev |
| Testing | Test-to-code ratio curve, whether CI actually runs tests, coverage tooling, e2e presence |
| Documentation | README depth + required sections, LICENSE, docstring/JSDoc coverage from the AST, comment density |
| Maintainability | Linter/formatter hygiene, TODO debt per kLOC, bus factor, dependency weight, oversized directories |
Every category can be expanded to the exact list of penalties that moved it β including a critical-findings cap and a test-ratio curve, so the number is honest about what it knows and what it sampled (
171/217 files Β· partial).
Put two repos in the ring β express vs fastify, flask vs starlette, or any pairing:
Each duel generates a shareable SVG card for your README:
[](https://your-host/compare?a=<idA>&b=<idB>)- Every finding carries a severity-based penalty:
critical 30 Β· major 10 Β· minor 4 Β· info 1. category score = 100 β Ξ£ penalties, with two honesty mechanisms:- any critical finding caps the category at 55 (35 for two or more);
- Testing is scored on a test-to-code ratio curve (target β₯ 60%) with CI penalties on top.
overall = weighted meanβ quality 25% Β· security 25% Β· testing 20% Β· documentation 15% Β· maintainability 15% β mapped to grades A+ β F.- Confidence is reported: files sampled, languages covered, and a
partialflag when caps kicked in.
flowchart LR
A["POST /api/analyze"] --> B["Job queue\nconcurrency 2 Β· idempotency\ncache by repo"]
B --> C[Worker]
C --> D["GitHub API\nmetadata Β· contributors Β· bus factor"]
C --> E["codeload tarball\ncap 150 MB"]
E --> F["File walker\n.gitignore-aware\ncap 4 000 files"]
F --> G["Analysis engine\nzero dependencies"]
G --> G1["Quality\ncomplexity Β· duplication"]
G --> G2["Security\nsecrets Β· patterns"]
G --> G3["Dependencies\nOSV.dev batch"]
G --> G4["Docs Β· Testing Β· Maintainability"]
G --> G5["Graph\nimports Β· SCC cycles"]
G1 & G2 & G3 & G4 & G5 --> H["Scoring\npenalty model"]
H --> I[("data/ results")]
I --> J["SSE progress"]
I --> K["Report UI\nVitals Β· Findings Β· Graph Β· Prescription"]
The heart of the project is lib/analyzer/ β a zero-dependency TypeScript
engine: a comment/string-stripping state machine (template literals, triple-quoted strings,
regex-literal disambiguation) feeds heuristic parsers (brace matching for JS/TS, indentation
scopes for Python) that approximate cyclomatic complexity, detect clones with a sliding
7-line window, and resolve imports into a module graph that Tarjan's algorithm sweeps for cycles.
Limits & safety β archive β€ 150 MB, β€ 4 000 files, β€ 400 KB/file, β€ 60 MB of text; timeouts on every stage; results cached per repo for 24 h; only derived metrics are stored, never source code.
git clone https://github.com/<you>/repodoctor
cd repodoctor
npm install
npm run dev # β http://localhost:3000Optional environment (.env.local):
| Variable | Effect |
|---|---|
GITHUB_TOKEN |
Raises the GitHub API limit from 60 to 5 000 req/hour |
OPENAI_API_KEY Β· ANTHROPIC_API_KEY Β· GEMINI_API_KEY |
Enables the AI-written prescription (optional β everything works without it) |
RepoDoctor needs a long-running Node server (in-process job queue + SSE + disk cache), so serverless platforms (Vercel/Netlify functions, GitHub Pages) won't run it as-is.
The repo ships a Render Blueprint β one click:
Notes on the free plan:
- The service sleeps after ~15 min idle; the first visit wakes it (~50 s).
data/is ephemeral β the 24 h analysis cache resets on every deploy/restart.- Add a
GITHUB_TOKENenvironment variable in the Render dashboard (no scopes needed for public repos) if analyses start failing with GitHub rate-limit errors.
# Web
npm run dev # paste a URL, or open /compare for a duel
# CLI
npm run analyze -- https://github.com/owner/repo [--token=ghp_xxx]
# Offline selftest (21 seeded issues in a deliberately unhealthy fixture repo)
npm run selftestBadge in your README:
[](https://your-host/analysis/<analysis-id>)| Route | Description |
|---|---|
POST /api/analyze {url} |
Enqueue an analysis β 202 {job} |
GET /api/analyze/:id |
Job status (+ full result when done) |
GET /api/analyze/:id/events |
SSE progress stream |
POST /api/analyze/:id/prescribe |
LLM prescription (graceful fallback without a key) |
POST /api/compare {a, b} |
Enqueue both sides of a duel |
GET /api/analyses |
Recent diagnoses |
GET /api/badge/:id |
SVG badge (accepts result id or job id) |
GET /api/card?a=<id>&b=<id> |
Head-to-Head duel card as SVG |
repodoctor/
βββ app/
β βββ api/ # analyze Β· events (SSE) Β· compare Β· badge Β· card Β· prescribe
β βββ analysis/[id]/ # live progress + full report
β βββ compare/ # head-to-head duels
β βββ components/ # report views, graph, duel client, progress
β βββ page.tsx # landing
βββ lib/
β βββ analyzer/ # β the engine β zero dependencies
β β βββ text.ts # comment/string-stripping state machine
β β βββ quality.ts # complexity, nesting, duplication
β β βββ security.ts # secrets, credential files, dangerous patterns
β β βββ deps.ts # 8 lockfile parsers β OSV.dev querybatch
β β βββ docs.ts # README, license, docstrings
β β βββ testing.ts # ratio, CI, coverage, e2e
β β βββ maintainability.ts # hygiene, TODO debt, bus factor
β β βββ graph.ts # import graph, module aggregation, Tarjan SCC
β β βββ score.ts # penalty model + prescription builder
β β βββ index.ts # pipeline orchestration
β βββ github.ts # API + tarball downloader with caps
β βββ queue.ts # in-process job manager (BullMQ-ready)
β βββ store.ts # JSON persistence (Postgres-ready)
β βββ types.ts # shared contracts
βββ fixtures/sample-repo/ # deliberately unhealthy repo for the selftest
βββ scripts/ # analyze-cli.ts Β· selftest.ts
βββ docs/screenshots/
The engine is covered by an offline selftest that runs against
fixtures/sample-repo β a fixture with 21 seeded issues
(committed AWS example key, eval(), shell=True, a duplicated module, a missing lockfileβ¦)
and asserts each one is caught:
npm run selftest
# β
All checks passed β the fixture scores 49/100 (F), exactly as it should- Watch mode β GitHub webhook re-analysis + score history (needs auth + Postgres)
- Go / Java / Rust support via tree-sitter grammars
- Redis/BullMQ queue driver for horizontal scaling
- og-image generation for social sharing
- Multi-repo portfolio view ("diagnose all my repos")
Issues and PRs are welcome. The engine is deliberately dependency-free β new analyzers should
follow the Finding contract in lib/types.ts and register penalties in
lib/analyzer/score.ts.
MIT Β© 2026 Hajar Benhadj



