Darwinius finds the files in your Python codebase that are most likely to cause pain: the ones that change constantly and are tightly coupled to everything else.
It does this by combining two signals from your Git history:
- Churn — how often a file changes (from
git log) - Coupling — how many other files import it, and how many things it depends on (from
ast.parse)
Files that score high on both are your hotspots — the first places to refactor.
Running darwinius analyze produces:
report.html— an interactive report with core hotspots, stable hubs, test hotspots, registry/aggregator detection, risk histogram, coupling evolution chart, and dependency graphfiles.csv— every Python file ranked by risk score, with acategoryofsource,test, orregistrycoupling.csv— every import relationship and when it first appearedchanges.csv— full commit history per filedarwinius.db— the raw SQLite database (re-run reports without re-analyzing)
instability = fan_out / (fan_in + fan_out) # Robert Martin's I metric
churn_score = commit_count / max_commit_count # normalized 0–1
risk_score = sqrt(churn_score × instability)
A file scores high only when it's both high-churn and unstably coupled — not just one or the other. Pure utilities that never change score low even if everything imports them. Frequently changed files that nobody depends on also score low.
Files that look like import registries or aggregators — for example files with unusually high fan-out such as extractor registries — are shown in a separate report section instead of being mixed into core refactor candidates. Test hotspots and stable hubs are also separated so the main table stays focused on source files that are plausible refactor targets.
Requirements: Python 3.9+, Git
git clone https://github.com/yourname/darwinius
cd darwinius
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e .To render the dependency graph SVG in the HTML report, also install Graphviz:
brew install graphviz # macOS
sudo apt install graphviz # Ubuntu/Debiandarwinius analyze /path/to/your/repoOutput goes to /path/to/your/repo/darwinius_out/. Open report.html in a browser.
# Custom output directory
darwinius analyze /path/to/repo -o ./results
# Only look at commits since a date
darwinius analyze /path/to/repo --since 2023-01-01
# Analyze a specific branch
darwinius analyze /path/to/repo --branch main
# Skip temporal coupling analysis (faster, no evolution chart)
darwinius analyze /path/to/repo --skip-temporal
# Show 100 files instead of 50, require at least 10 commits to count as a hotspot
darwinius analyze /path/to/repo --top-n 100 --min-churn 10
# Focus the report on production code only
darwinius analyze /path/to/repo --exclude-tests
# CSV only, no HTML
darwinius analyze /path/to/repo --no-html
# Re-run everything from scratch
darwinius analyze /path/to/repo --forceDarwinius prefers Git-tracked Python files when analyzing a Git repo. Outside a Git repo, it skips common generated and environment directories such as .venv/, venv/, .git/, build/, dist/, .pytest_cache/, and __pycache__/.
Re-runs the report without re-parsing Git history:
darwinius report ./results/darwinius.db -o ./results --format htmlAdd --exclude-tests to regenerate a production-only report from the same database.
darwinius inspect src/models/user.py --db ./results/darwinius.dbPrints the file's risk score, fan-in, fan-out, what it imports internally, and what imports it.
Git Repo
↓
git_analyzer.py — walks git log, extracts commits + per-file churn → SQLite
↓
python_analyzer.py — parses .py files with ast.parse(), extracts imports → SQLite
↓
temporal_analyzer.py — replays commits chronologically, tracks when coupling
edges appear and disappear → SQLite
↓
metrics.py — computes risk scores per file → SQLite
↓
report_generator.py — renders report.html + CSV files
Each step writes to a SQLite database (darwinius.db). Re-running skips already-complete steps automatically. Use --force to re-run everything.
On a 50k-commit repo, the Git and import phases run in under 5 minutes. The temporal replay (tracking coupling over every commit) is the expensive step — use --skip-temporal to skip it if you only need the current-state hotspot ranking.
pip install -e ".[dev]"
pytest tests/ -v- Multi-language support — Python only (uses
ast.parse). Non-Python files are ignored. - Dynamic imports —
importlib.import_module("x")and string-based imports are not detected. - Real-time analysis — it's a batch tool; re-run it when you want fresh data.
- Fully offline reports —
report.htmlis a single local file, but charts load Chart.js from jsDelivr.