🔍 BugSeer
Offline bug-risk prediction for code repositories. Static analysis + git intelligence + a local ML model — and an explanation for every number it shows you.
No cloud. No API keys. No telemetry. Your source code never leaves your machine.
🔥 97 ████████████████████ src/flask/app.py Learned model predicts elevated defect risk · Mutable global state
🔴 74 ███████████████░░░░░ src/payment.py High bug-fix density · No error handling · Deeply nested control flow
🟡 41 ████████░░░░░░░░░░░░ src/config.py Frequently modified file
🟢 12 ██░░░░░░░░░░░░░░░░░░ src/utils.py —
Why this exists
Most "AI code review" tools give you a number and expect you to trust it. Developers don't, and they're right not to.
BugSeer's rule is simple: every point of risk traces back to named, inspectable evidence.
🔥 src/payment.py 96/100 (critical)
████████████████████████████████████████
static 102 · git 70 · raw 172 pts
⎇ +40 High bug-fix density [bugfix-density]
9 of 10 commits (90%) look like bug fixes. Code that has needed
repeated repair tends to need more.
◆ +27 No error handling around risky operations [no-error-handling]
4 I/O, network, parsing or subprocess call(s) and zero try/catch
blocks. Failures here surface as unhandled exceptions in production.
◆ +25 Deeply nested control flow [deep-nesting]
Maximum loop nesting is 3 and maximum block depth is 7 (threshold 3).
↳ L12 (depth 7), L28 (depth 7), L11 (depth 6)
⎇ +14 Previously reverted [revert-history]
1 revert/rollback commit(s) touched this file. A revert is direct
evidence that a change here broke something in production.
You can argue with that. That's the point.
Install
pip install -e ".[all]" # everything
pip install -e . # core only — still fully functional
| Extra | Adds | Without it |
|---|---|---|
parsers |
tree-sitter for 19+ languages | Python via stdlib ast, others via heuristics |
server |
FastAPI dashboard | Use the self-contained HTML report |
ml |
XGBoost / LightGBM | scikit-learn gradient boosting (built in) |
env |
.env file loading |
Plain environment variables |
Every extra is optional. Core BugSeer needs no network access at any point.
Quick start
bugseer scan . # rank files by risk
bugseer explain src/payment.py # full reasoning for one file
bugseer heatmap . # colour-coded project tree
bugseer impact src/database.py # "what if I change this?"
bugseer train . # learn from your own bug history
bugseer serve . # interactive dashboard
bugseer report . -o risk.html # shareable offline HTML
The five phases
Phase 1 — Rule-based static analysis (no AI, no API)
Parsed with the best available backend: stdlib ast for Python (exact), tree-sitter for 19+ other languages, and a heuristic analyzer that never fails so an unknown language still gets scored.
| Rule | Points | Fires when |
|---|---|---|
no-error-handling |
20 | Risky I/O with no try/catch |
low-coverage |
20 | Measured coverage below threshold |
deep-nesting |
15 | Nested loops deeper than 3 |
global-state |
15 | Mutable module-level state |
high-complexity |
12 | Cyclomatic complexity > 20 |
long-function |
10 | Function longer than 100 lines |
high-branching |
10 | More than 15 conditional branches |
duplicate-code |
10 | Cloned blocks within or across files |
swallowed-exception |
8 | except: / catch(e){} that hides failures |
god-file |
8 | Over 600 lines |
| …plus magic numbers, TODO debt, mutable defaults, long parameter lists |
Phase 2 — Git intelligence
Parses git log locally. No API, no network.
- Change frequency — the strongest empirical defect predictor
- Bug-fix density — how many commits here were repairs
- Revert history — direct evidence a change broke production
- Fix-follow rate — how often an edit here needed a follow-up fix within 7 days
- Authorship spread and single-owner bus-factor risk
- Co-change coupling — files habitually committed together
Works on partial/blobless clones (
--filter=blob:none), wheregit log --numstatwould otherwise stall fetching blobs. BugSeer detects this, falls back to--name-only, and tells you churn is unavailable rather than silently reporting "no git history."
Phase 3 — Learn from past bugs
bugseer train . --label-window 180
Trains locally on your repository. Labels come from bug-fix commits in a recent window; features come from history before that window — a temporal split, so the model is genuinely predictive rather than circular.
✓ Model trained
Estimator sklearn.GradientBoostingClassifier
Samples 80 files (17 bug-fixed)
ROC AUC (5-fold) 0.7652
Label window 1095 days
What the model learned to look at:
largest parameter list █████████ 24.5%
comment ratio █████ 14.8%
ownership concentration ████ 10.0%
Honesty guarantees, because a prediction you can't trust is worse than none:
- Reported metrics are out-of-fold (5-fold CV), never training-set scores.
- Files in the training set get their cross-validated probability, not the memorised ~100% a fitted ensemble would return.
- Probabilities are clamped to 2–95%: a few dozen samples cannot justify certainty.
- Too little signal? It says so and keeps using rules, rather than fitting noise.
Phase 4 — Project heat map
bugseer heatmap in the terminal, or bugseer serve for the React dashboard. Click any file for its full evidence chain.
Phase 5 — "What if?" simulator
$ bugseer impact src/flask/app.py
If you modify src/flask/app.py, 25 file(s) are most likely to be affected.
Impact File Own risk Why
65 ██████░░ src/flask/sessions.py 66 directly imports `app.py`;
changed together in 22% of commits
52 █████░░░ tests/test_basic.py 93 depends on `globals.py` transitively
(2 hops); is itself high-risk
Combines the import graph with historical co-change, weighted by each candidate's own fragility — and explains every prediction.
Configuration
bugseer init # writes a starter .bugseer.toml
[bugseer]
history_days = 730
exclude = ["docs/*", "examples/*"]
# coverage_file = "coverage.xml"
[bugseer.weights]
no_error_handling = 20
change_frequency = 15
[bugseer.thresholds]
long_function_lines = 100
band_critical = 85
Precedence: CLI flags → .bugseer.toml → environment/.env → defaults.
Test coverage
BugSeer auto-detects coverage.xml, lcov.info, coverage.json, and Clover. Without one it falls back to a filename heuristic — and labels it as such, so a guess is never mistaken for a measurement.
CI usage
- run: pip install -e ".[parsers]"
- run: bugseer scan . --fail-over 85 --ignore-tests
Exits non-zero when any file exceeds the threshold. Add --json report.json to archive results.
The optional AI narrator
BugSeer needs no API key. All five phases above are deterministic and offline.
The only optional AI feature rewrites already-computed evidence as prose:
cp .env.example .env # then set one key
bugseer explain src/payment.py --narrate
Supports OpenAI, Anthropic (Claude), Gemini, and Ollama (local).
Safety properties, enforced by tests:
- It cannot change a score — it receives finished evidence, and the score is computed before it runs.
- It sends metrics and rule names only; source code requires explicit
BUGSEER_AI_SEND_SOURCE=1. BUGSEER_OFFLINE=1blocks all outbound requests even if a key is set.BUGSEER_AI_REDACT_PATHS=1hashes file paths before transmission.- Any failure degrades gracefully to the deterministic explanation.
See .env.example — every variable in it is optional.
Dashboard
bugseer serve . # http://127.0.0.1:8420
React + TypeScript, bound to localhost. The build is committed, so it runs without a node toolchain. If the bundle is missing, the server falls back to the static HTML report.
To develop the frontend:
cd frontend && npm install && npm run dev # proxies /api to :8420
Architecture
bugseer/
├── analysis/
│ ├── static.py Phase 1 — ast | tree-sitter | heuristic backends
│ ├── langspec.py Declarative grammar vocabularies (19+ languages)
│ └── duplication.py Rolling-hash clone detection
├── git_intel.py Phase 2 — git log parsing, coverage reports
├── rules.py Scoring engine — every hit carries its evidence
├── ml.py Phase 3 — local model, out-of-fold honesty
├── graph.py Phase 5 — dependency graph + impact simulation
├── scanner.py Orchestration (parallel, with serial fallback)
├── server.py Phase 4 — FastAPI dashboard
├── report.py Self-contained HTML output
├── narrate.py Optional AI narrator (stdlib urllib only)
└── cli.py Typer CLI
Adding a language is a few lines of data in langspec.py.
Adding a rule is one function in rules.py returning a RuleHit with its evidence.
Testing
pytest -q # 104 tests
Covers the analyzers, rule scoring, git parsing, coverage formats, the graph, impact simulation, ML degradation paths, the CLI, and the narrator's privacy guarantees. Verified end-to-end against a real repository (Flask: 84 files, 5,539 commits) and a battery of pathological inputs — binary files, invalid UTF-8, syntax errors, 800 KB files, broken symlinks, empty files.
Performance
Flask (84 files, 13k LOC, 3,812 commits analysed): ~1.0s full scan on 8 workers.
Design decisions worth knowing
- Additive scores, saturating curve. Raw points are summed then squashed to 0–100, so eight moderate problems and one catastrophic one both land sensibly.
- Rules over ML by default. The model is opt-in and additive; it only moves a score when it genuinely disagrees with the repo baseline.
- Naming heuristics are labelled. "No matching test file" never masquerades as measured coverage.
- Trivial files score zero. Empty files and constants-only modules generate no findings — noise is how a tool gets ignored.
- Graceful degradation everywhere. No git, no tree-sitter, no model, no network: BugSeer still produces a useful report and tells you what it couldn't do.
License
MIT
Release files for bugseer 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| bugseer-0.1.0.tar.gz | 142.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| bugseer-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 277.5 kB
Release files / bugseer-0.1.0.tar.gz
| Download URL | bugseer-0.1.0.tar.gz |
|---|---|
| Size | 142.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9f02bbb36f9cc889ee3d57fb8619c52f8acf94f123884be397640c50a5baafd3
|
|
BLAKE2b-256 checksum How to use checksums |
3bd0c6b3ad663be4b2c8f8be6b2be64fde0bd35b55c42de469d49c7387eaa5f2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 3, 2026.
Transparency logRelease files / bugseer-0.1.0-py3-none-any.whl
| Download URL | bugseer-0.1.0-py3-none-any.whl |
|---|---|
| Size | 135.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
901794122fe67d0af5136a5f009c037ce3bce5b1a243f2ae00108ddface698ac
|
|
BLAKE2b-256 checksum How to use checksums |
9387d48a5768d62696de424ffa8429485bcfbe01bb0c8851deb3aa35a202159c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 3, 2026.
Transparency log