Code Entropy Engine — Visualize complexity, track churn, identify technical debt.
FORGE analyzes your codebase using AST parsing and machine learning to reveal high-risk files and track how functions evolve across commits. Built with FastAPI, Next.js, Tree-sitter, and Redis Streams.
- 🔍 Multi-Language AST Parsing — Python, JavaScript, Java, Go
- 📊 Interactive Heatmaps — Visual risk assessment via treemaps
- 🔥 Code Churn Analysis — Track modification frequency over time
- ⚙️ Complexity Metrics — Cyclomatic complexity per function
- 🧠 9-Feature ML Risk Prediction — Isolation Forest trained on churn, ownership entropy, file age, commit frequency, dependency count, and more
- 🔬 Function Evolution Tracking — SHA-256 AST identity hashing lets FORGE follow a function through moves, renames, and refactors
- ⚡ Incremental Indexing — Only new commits are scanned; a
last_indexed_shacheckpoint avoids re-walking the full history - 🛡️ At-Least-Once Task Delivery — Redis Streams with consumer groups; crashed workers are auto-recovered via
XAUTOCLAIM - 🔐 Private Repository Support — GitHub token authentication
- Docker & Docker Compose
- GitHub Personal Access Token (for private repos)
git clone https://github.com/abhijeetw035/FORGE.git
cd FORGE
cp .env.example .envEdit .env and add your token:
GITHUB_TOKEN=ghp_your_token_hereHow to get a GitHub token?
- Go to GitHub Settings → Developer settings → Personal access tokens
- Click "Generate new token (classic)"
- Select
reposcope (for private repository access) - Copy the token and paste it in
.env
docker-compose up -dServices will start:
- 🌐 Dashboard: http://localhost:3000
- 🔌 API: http://localhost:8000
- 📊 API Docs: http://localhost:8000/docs
curl -X POST http://localhost:8000/repositories/ \
-H "Content-Type: application/json" \
-d '{"url": "https://github.com/your-org/your-repo"}'Open http://localhost:3000 and click View Analysis to explore the heatmap, function evolution timeline, and ML risk forecast.
graph LR
A[Git Repository] -->|Clone / Pull delta| B[Miner Worker]
B -->|diff-tree changed files| C[Tree-sitter AST]
C -->|SHA-256 canonical_id| D[PostgreSQL]
D -->|9-feature FileMetrics| E[Isolation Forest]
E -->|Risk scores| F[Analytics API]
F -->|Visualize| G[Next.js Dashboard]
The Algorithm:
- Clone or pull delta — On first run the repo is cloned; subsequent runs use
git log <last_sha>..HEADto fetch only new commits - Changed-files-only AST parsing — Each commit's diff-tree limits Tree-sitter parsing to touched files (O(changed) instead of O(all))
- SHA-256 function identity hashing —
canonical_id = SHA-256(signature_hash + ":" + body_hash)[:16]; a function's identity survives moves and renames - 9-feature file metrics — Churn, complexity, author count, lines added/deleted, commit frequency, recent churn, ownership entropy (Shannon), dependency count
- Isolation Forest anomaly detection —
n_estimators=200,max_features=0.8; files that deviate across all 9 axes get a high anomaly score - Redis Streams task delivery —
XADDenqueues analysis tasks;XREADGROUP/XACKguarantee each task is processed at least once;XAUTOCLAIMre-delivers tasks from crashed workers after 60 s
Four production-quality improvements were made to address real weaknesses:
Problem: The original model used only 3 features (churn, complexity, author count) — essentially a weighted formula, not ML.
Fix: A FileMetrics table now stores 9 features per file. The Isolation Forest is retrained on every prediction request using all 9 axes:
| Feature | Why it matters |
|---|---|
churn |
Raw modification count |
complexity |
Cyclomatic complexity |
author_count |
Bus-factor risk |
lines_added |
Growth indicator |
lines_deleted |
Instability indicator |
commit_frequency |
How often the file is touched |
recent_churn |
Last-30-day activity window |
ownership_entropy |
Shannon entropy across authors |
dependency_count |
Import surface area |
Files that are statistical outliers across all 9 axes receive the highest anomaly scores.
Problem: Functions were tracked by file path + line number. Any rename, move, or reformat broke continuity and produced duplicate records.
Fix: Every function now has a canonical_id = SHA-256(signature_hash + ":" + body_hash)[:16].
signature_hash—SHA-256("<lang>:<name>(<params>)")— changes only on signature editsbody_hash—SHA-256(normalised_body)— comments stripped, whitespace collapsed; changes only on semantic edits
A function moved across files, or reformatted, keeps the same canonical_id. An intentional rewrite gets a new one. The Function Evolution page in the dashboard groups all versions of a function across commits and highlights moves, renames, and growing complexity.
Problem: Every analysis walked the entire commit history — catastrophic for repos with thousands of commits.
Fix: A last_indexed_sha column on the Repository model acts as a checkpoint.
first run : git log HEAD → all commits
next run : git log <sha>..HEAD → only new commits
- After each commit is processed the SHA is saved to DB, so a crash mid-run resumes correctly from the last checkpoint
- AST parsing is further limited to
diff-treechanged files per commit
Problem: The original queue used LPUSH/BRPOP. If the miner crashed between popping a task and finishing it, the task was lost forever.
Fix: Redis Streams with a consumer group:
XADD forge:tasks * payload <json> # enqueue
XREADGROUP GROUP miner-workers worker-X # dequeue (pending)
... process ...
XACK forge:tasks miner-workers <id> # acknowledge success
XAUTOCLAIM forge:tasks miner-workers worker-X 60000 0-0 # reclaim stuck tasks
Tasks stay in the pending-entries list until explicitly acknowledged. After 60 seconds of idle time any unacknowledged task is reclaimed and re-delivered to an available worker. A BLPOP fallback handles Redis instances that don't support Streams.
┌────────────────────────────────────────────────────┐
│ FORGE Platform │
├────────────────────────────────────────────────────┤
│ Dashboard (Next.js 14) │
│ - Repository list + status │
│ - Interactive heatmaps (Recharts treemap) │
│ - Risk Forecast (9-feature ML scores) │
│ - Function Evolution page (AST identity) │
│ - Re-analyse button │
├────────────────────────────────────────────────────┤
│ API (FastAPI) │
│ - /repositories/* CRUD + reanalyze │
│ - /analytics/heatmap treemap data │
│ - /analytics/risk-prediction Isolation Forest │
│ - /analytics/function-evolution canonical_id │
│ - Auto-generated OpenAPI docs │
├────────────────────────────────────────────────────┤
│ Miner Worker (Python) │
│ - Incremental git fetch (last_indexed_sha) │
│ - diff-tree scoped AST parsing (Tree-sitter) │
│ - SHA-256 canonical_id hashing │
│ - 9-feature FileMetrics extraction │
│ - Redis Streams XREADGROUP / XACK / XAUTOCLAIM │
├────────────────────────────────────────────────────┤
│ Storage Layer │
│ - PostgreSQL 16 (repos, commits, functions, │
│ file_metrics — migrations 1-5) │
│ - Redis Streams (forge:tasks, miner-workers CG) │
│ - Docker volumes (cloned repo storage) │
└────────────────────────────────────────────────────┘
POST /repositories/
Content-Type: application/json
{ "url": "https://github.com/owner/repo" }GET /repositories/{id}POST /repositories/{id}/reanalyzeWipes all derived data (functions, commits, file metrics) for the repo and re-queues it for a fresh analysis. Returns 409 Conflict if the repo is already being analyzed.
GET /analytics/repositories/{id}/heatmapReturns top 100 files sorted by churn:
[
{ "name": "src/parser.py", "size": 1587, "score": 142 }
]size— Total lines of codescore— Number of modifications (churn)
GET /analytics/repositories/{id}/risk-predictionReturns Isolation Forest anomaly scores across all 9 features:
[
{
"file_path": "src/core.py",
"risk_score": 0.87,
"churn": 142,
"complexity": 38,
"author_count": 7,
"lines_added": 3200,
"lines_deleted": 1800,
"commit_frequency": 0.9,
"recent_churn": 22,
"ownership_entropy": 2.14,
"dependency_count": 18
}
]GET /analytics/repositories/{id}/function-evolution?limit=50Groups all versions of each function (by canonical_id) across the commit history:
[
{
"canonical_id": "a3f1b9c2",
"name": "parse_token",
"file_path": "src/lexer.py",
"commit_count": 14,
"first_seen": "2023-01-10T09:00:00",
"last_seen": "2024-03-22T15:30:00",
"was_moved": true,
"was_renamed": false,
"complexity_trend": "increasing",
"versions": ["..."]
}
]| Color | Risk Level | Meaning |
|---|---|---|
| 🟢 Green | Low | Stable, infrequently modified |
| 🟡 Yellow | Moderate | Regular updates |
| 🟠 Orange | High | Frequent changes |
| 🔴 Red | Critical | Constantly evolving, high complexity |
Box Size = Total Lines of Code
Box Color = Modification Frequency (Churn Score)
forge/
├── api/ # FastAPI backend
│ ├── routes/ # repositories, analytics, auth
│ ├── services/ # predictor.py (Isolation Forest)
│ ├── models.py # SQLAlchemy models
│ ├── database.py # DB connection
│ └── alembic/ # Migrations 001-005
├── miner/ # Worker service
│ ├── services/ # git_service, ast_parser, entropy
│ ├── worker.py # Redis Streams consumer loop
│ └── build_languages.py
├── dashboard/ # Next.js frontend
│ └── src/
│ ├── app/ # Pages (repositories, functions, …)
│ ├── components/ # RiskPrediction, Contributors, …
│ ├── lib/ # api.ts — typed API client
│ └── types/ # TypeScript interfaces
└── docker-compose.yml # Orchestration
| Migration | Description |
|---|---|
| 001 | Initial schema (repositories, commits, functions) |
| 002 | Auth tables |
| 003 | file_metrics table (9 features) |
| 004 | body_hash, signature_hash, canonical_id on functions |
| 005 | last_indexed_sha on repositories |
Backend
- FastAPI 0.109.0
- SQLAlchemy 2.0.25
- scikit-learn (Isolation Forest)
- PostgreSQL 16
- Redis 7 (Streams)
Worker
- Tree-sitter 0.21.3
- GitPython 3.1.41
- Python 3.11
- hashlib (SHA-256)
Frontend
- Next.js 14 (App Router)
- TypeScript
- Tailwind CSS
- Recharts
- lucide-react
# Check database state
docker-compose exec api python check_db.py
# View miner logs (shows stream consumer activity)
docker-compose logs -f miner
# View API logs
docker-compose logs -f api| Variable | Description | Required |
|---|---|---|
GITHUB_TOKEN |
Personal access token for private repos | Optional* |
DATABASE_URL |
PostgreSQL connection string | Yes |
REDIS_URL |
Redis connection string | Yes |
STORAGE_PATH |
Path for cloned repositories | Yes |
*Required for private repositories
- Query Speed: < 1 second for top 100 files
- Scalability: Tested with 3.3M+ function records (217 K in
requestsalone) - Incremental indexing: New commits only — no full re-walk after first analysis
- Concurrency: Redis Streams consumer groups (horizontally scalable workers)
- Crash safety: Unacknowledged tasks auto-reclaimed after 60 s idle
Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Commit changes with clear messages
- Submit a pull request
MIT License — see LICENSE file for details
Built with:
- Tree-sitter — Parsing framework
- FastAPI — Modern Python API
- scikit-learn — Isolation Forest
- Next.js — React framework
- Recharts — Charting library
- Redis Streams — At-least-once task delivery
- 🐛 Issues: GitHub Issues
- 📧 Email: abhijeetw035@gmail.com
- 💬 Discussions: GitHub Discussions
Made with 🔥 by Abhijeet Waghmare
"Every codebase tells a story. FORGE helps you read it."
