Find the holes your code already drew.
Project description
absentia
Find what you forgot to write. (The holes your code already drew.)
Most code analyzers find what's wrong. absentia finds what's missing.
Take 48 event handlers in your codebase. 47 call bus.unsubscribe() in their
cleanup paths. One doesn't. That one is a memory leak waiting for the user
who triggers the right interaction — and no linter, type-checker, or AI
reviewer will catch it, because nothing told them to expect that pattern.
Absentia learns the pattern from the 47 and flags the outlier with a
0.94-confidence score.
Pattern mining over your AST. No LLM, no rule database, deterministic — same
input, same gaps. The rules come from your code itself: if 9 of your 10 API
endpoints use @audit, absentia tells you about the 10th; if 8 of your 10
panels have a corresponding test file, absentia tells you about the 2 that
don't. Every gap traces back to the rule that produced it, and every rule
traces back to the members of your codebase that exhibit it.
GAPS confidence ≥ 0.80 3
▶ src/api/users.py:42 fn `delete_user` missing @audit 0.90
src/api/orders.py:15 fn `refund` missing test pair 0.80
panels/code_editor.py:842 fn `_render_gutter` missing @lru_cache 0.83
DETAIL: g-7c91
rule r-a3f2 · fns in src/api/ have @audit
support 9/10 (confidence 0.90)
exhibits create_user update_user list_users get_user …
violator ✗ delete_user
Real gap and rule IDs are seven characters after the prefix (
g-7c91234,r-a3f2bc7); the 4-char forms above are shortened for readability.
Why this exists
Most code-hygiene tools answer one of two questions:
-
"Does this code violate a rule someone wrote?" Linters and style checkers (ruff, ESLint, etc.). The rules come from a human or a config file.
-
"Is this code likely buggy?" Static analyzers (mypy, pyright, sonarqube). The rules come from compiler theory or hand-coded heuristics.
Neither answers the question that actually keeps codebases consistent over years: "Does this code follow the patterns the rest of this codebase follows?"
Most code drift isn't bugs and isn't style violations — it's a piece that diverged
from a convention nobody wrote down. absentia mines the conventions and finds the
divergences. It's the difference between "your if should have a space after it"
(a global rule) and "every other endpoint in this folder logs the user_id, this
one doesn't" (a local pattern your team established without writing it down).
Install
Two recommended paths — pick whichever your toolchain already has.
Both install absentia into an isolated environment and put the
absentia command on your PATH.
uv (fastest, modern Astral toolchain)
If you use uv:
uv tool install absentia
Equivalent of pipx but ~10–100× faster install. Pulls from the same PyPI. To use absentia as a library inside a project:
uv add absentia
If you don't have uv yet:
| OS | Install uv |
|---|---|
| macOS / Linux / WSL | curl -LsSf https://astral.sh/uv/install.sh | sh |
| macOS via Homebrew | brew install uv |
| Windows | powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" |
| Anywhere via pip | pip install --user uv |
pipx (the long-standing standard)
pipx install absentia
Behaviorally identical to uv tool install; slightly slower but
ships in most distro repos.
If you don't have pipx yet:
| OS | Install pipx |
|---|---|
| macOS | brew install pipx && pipx ensurepath |
| Debian / Ubuntu / WSL | sudo apt install pipx && pipx ensurepath |
| Fedora | sudo dnf install pipx && pipx ensurepath |
| Arch | sudo pacman -S python-pipx && pipx ensurepath |
| Windows | python -m pip install --user pipx && python -m pipx ensurepath |
After pipx ensurepath (or installing uv), open a new shell so
the PATH update takes effect.
Plain pip install (inside a venv)
python3 -m venv .venv
source .venv/bin/activate # PowerShell on Windows: .venv\Scripts\Activate.ps1
pip install absentia
Note: outside a venv, on modern Debian / Ubuntu / WSL / Fedora you'll get:
error: externally-managed-environment
× This environment is externally managed
That's PEP 668 — your distro's
system Python refuses to install packages directly. The right answer
for any CLI tool is uv tool install or pipx install (above).
For library use inside a project, the venv form above is the
canonical workaround.
Requirements
Python 3.13+. Cross-platform (macOS, Linux, Windows). Pre-built mypyc-compiled wheels for cp313 × {Linux x86_64, Linux aarch64, macOS arm64, Windows AMD64}; other platforms (including cp314 and Intel Mac) install from sdist and compile via mypyc locally at install time — same end result, slower first install.
From source (development)
git clone https://github.com/skbays03/absentia.git
cd absentia
pip install -e ".[dev]" # editable install + test deps
Quickstart
From any project directory:
absentia init # create absentia.toml + .absentia/
absentia # open the TUI
That's it. absentia scans your code, mines patterns, and shows you a navigable list
of gaps. Use j/k to move, ↵ to open in your editor, s to suppress with a
reason, e to see why a gap was flagged.
For CI and scripting:
absentia check # human-readable list; exit 1 if any gaps
absentia check --json # machine-readable
absentia check --max-gaps 5 # tolerate up to 5 gaps before failing the build
absentia check --cold # dev-time: ignore parse cache and re-parse the
# whole tree (or just `--cold src/foo.py` for one
# file). Useful when you suspect cache weirdness
# or are benchmarking the parse stage.
absentia check --language python,rust # restrict to specific languages
absentia check --exclude '**/vendor/**' # skip a glob pattern
absentia check --exclude tests --exclude docs # multiple --exclude allowed
absentia --debug check # diagnostic prints to stderr
absentia --no-color check # force-disable ANSI color
Symmetric flags: absentia est accepts the same --config, --jobs,
--json, --quiet, --language, --exclude, --cold as check,
so muscle memory transfers between the two.
The full flag list (including --config, --min-confidence, est's
--recalibrate / --use-synthetic / --history, top-level
--purge / --jobs-default, and others) lives in the
CLI reference.
What absentia finds
Examples of typical gaps:
Decorator inconsistency
src/api/users.py::delete_user
missing @audit decorator
why 9/10 fns in src/api/ have @audit
Missing sibling files
src/api/orders.py::refund
missing sibling test (tests/test_orders.py::test_refund)
why 8/10 fns in src/api/ have a sibling test
Inconsistent inheritance
panels/quirky_panel.py::QuirkyPanel
missing inherits from BasePanel
why 12/14 classes in panels/ inherit from BasePanel
Import omissions
tools/bug_cli.py
missing imports `from .common import setup_logging`
why 9/11 files in tools/ import setup_logging
Naming pattern breaks
tests/test_users.py::should_validate_email
missing starts with `test_`
why 142/150 fns in tests/ follow `test_*`
How it works
Four deterministic conceptual stages:
- Parse — tree-sitter walks your code and extracts entities (functions, classes, files, imports, decorators).
- Group — selectors organize entities into groups by directory, decorator, parent class, name pattern, or user-defined criteria.
- Mine — within each group, frequency analysis finds features appearing in most members. Features above your confidence threshold become rules.
- Compare — entities in a rule's group that don't satisfy its predicate become gaps.
Run absentia twice on the same code and you get the same output. The runtime
progress UI shows five stages — walk → parse → store → mine → finalize —
adding I/O bookends around the conceptual core; see
architecture and performance
for the full pipeline view, and how mining works
for the algorithm-deep walkthrough.
Performance
absentia scans the entire Linux kernel — 65,004 files / 686,923 entities across ~30 million lines of C — in ~24 seconds warm / ~48 seconds cold at default jobs on a 10-core M-series MacBook. Warm breakdown: parse ~8 s (cache hits) + mine ~12 s + store ~2 s. Cold breakdown: parse ~31 s + mine ~12 s + store ~3 s. Single-process baseline ~95 s cold. Most projects on this scale rarely apply — typical real-world codebases scan in seconds (and warm-rescan a small edited subset in fractions).
The mining stage is the headline optimization story: a name-indexed
find_symmetry_gaps (no more O(P×N) per-pair-per-entity scan) plus
mypyc-compiled mining.py and symmetry.py together cut
mining-stage wall-clock on the kernel from ~5 minutes to ~12 seconds
— a ~25× speedup, gap counts byte-identical to the pre-
optimization baseline.
If you're running on a free-threaded Python (3.13t / 3.14t), the ThreadPool worker cap rises automatically from 4 to 7 (one per mining strategy) — more headroom for mining-stage parallelism once the C-extension ecosystem catches up to the no-GIL ABI. No-op on regular CPython. (Speedup percentage isn't pinned: most tree-sitter wheels still ship GIL-only ABIs, so the path can't be benchmarked end-to-end as of early 2026.)
Full benchmark table covering all 17 built-in extractors (16 languages; TypeScript and TSX share a tree-sitter grammar but emit distinct extractors) — small smoke-test corpora plus the Linux kernel as the big-corpus case study (687 k entities, ~30 M LOC of C) — in architecture and performance.
Curious what your machine looks like? Run absentia est from any project
directory for a hardware-calibrated cold-scan estimate — see
the estimator methodology for the math.
What absentia is not
- Not a linter. Linters enforce rules someone else wrote. absentia enforces rules your codebase already follows.
- Not a code reviewer. absentia doesn't critique correctness, security, or design. It surfaces consistency gaps.
- Not a fixer. absentia finds; humans fix. Auto-patching is a different product with very different tradeoffs.
- Not a resource-leak detector. Patterns like
open()/close(),lock()/release()— anything where the language or runtime defines the pair — are the linter's job. Use pylint, flake8-resource-leak, or Python'swithstatement for those. absentia catches project-specific paired calls (your event-bussubscribe/unsubscribe, your custom auditbegin/commit) — conventions no off-the-shelf linter knows. - Not a control-flow analyzer. absentia's read is coarse: "this function calls A but not B." It won't verify that B is called along every code path. Type systems and resource-leak linters own that layer.
- Not AI. No LLM, no embeddings, no model. Rules are statistical facts about your code, computed by counting. See why no LLM.
TUI vs CLI
Bare absentia opens the TUI — the primary interface, built for exploration.
Drill from a gap to its rule, from a rule to its other members, from there to
their gaps. Filter live with /. Suppress with s. Watch mode (w) auto-
rescans every 2 seconds — incremental, so unchanged files hit the parse cache.
absentia check is the batch mode for CI, scripting, and editor integrations.
It honors --json, --max-gaps, --quiet, and exits with a meaningful status
code.
Configuration
Per-project config in absentia.toml:
[scan]
include = ["src/", "tests/"]
exclude = ["src/vendor/"]
languages = ["python"]
[mining]
min_confidence = 0.8
min_group_size = 5
max_predicate_size = 2
[selectors.directory]
enabled = true
[selectors.decorator]
enabled = true
exclude = ["@property", "@staticmethod"]
See the configuration reference for every option.
Status
Stable. v1.0 ships the public API + config format covered by SemVer; breaking changes go in major-version bumps. New gap detectors, extractor improvements, and TUI features land in minor versions.
Documentation
- Quickstart tutorial
- What is negative-space search?
- How mining works
- Why no LLM
- Configuration reference
- CLI reference
- TUI keybindings
License
Licensed under the Apache License, Version 2.0. See NOTICE for attribution.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file absentia-1.0.1.tar.gz.
File metadata
- Download URL: absentia-1.0.1.tar.gz
- Upload date:
- Size: 332.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
44593022eff335a3ae8632e177a932824b9d525c13ef875271b75aa9f4a74657
|
|
| MD5 |
b2bcbe4ccf8590d38338d37fdecd50a3
|
|
| BLAKE2b-256 |
fb095813037de08afbd98ec36febc5ec54e164e001303a0777cd6993b4362483
|
Provenance
The following attestation bundles were made for absentia-1.0.1.tar.gz:
Publisher:
publish.yml on skbays03/absentia
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
absentia-1.0.1.tar.gz -
Subject digest:
44593022eff335a3ae8632e177a932824b9d525c13ef875271b75aa9f4a74657 - Sigstore transparency entry: 1486367036
- Sigstore integration time:
-
Permalink:
skbays03/absentia@fa3d804e145ae59ec5347f2052e936054fda3a1c -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/skbays03
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@fa3d804e145ae59ec5347f2052e936054fda3a1c -
Trigger Event:
release
-
Statement type:
File details
Details for the file absentia-1.0.1-cp313-cp313-win_amd64.whl.
File metadata
- Download URL: absentia-1.0.1-cp313-cp313-win_amd64.whl
- Upload date:
- Size: 284.9 kB
- Tags: CPython 3.13, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
caf832ad8e17f947891f8121828087171a91111fc07848eb05052935f10feac2
|
|
| MD5 |
f039cfb76a7adef9e4e0c99f988b82ba
|
|
| BLAKE2b-256 |
b5c17098f9d0935f35ad1823b92f0fab5c82a0f98a642fdaec2ccfa272732ddd
|
Provenance
The following attestation bundles were made for absentia-1.0.1-cp313-cp313-win_amd64.whl:
Publisher:
publish.yml on skbays03/absentia
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
absentia-1.0.1-cp313-cp313-win_amd64.whl -
Subject digest:
caf832ad8e17f947891f8121828087171a91111fc07848eb05052935f10feac2 - Sigstore transparency entry: 1486367069
- Sigstore integration time:
-
Permalink:
skbays03/absentia@fa3d804e145ae59ec5347f2052e936054fda3a1c -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/skbays03
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@fa3d804e145ae59ec5347f2052e936054fda3a1c -
Trigger Event:
release
-
Statement type:
File details
Details for the file absentia-1.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: absentia-1.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 413.9 kB
- Tags: CPython 3.13, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3a3706e11e2d652a6a1627d57576b1af2b5e7ea7238181cc9b944a6fd67a5634
|
|
| MD5 |
c24aaaae3e54c4e17a1852e94334650d
|
|
| BLAKE2b-256 |
a91a2a1cd72c2d83cc9da0da61572e69cd044511f293dcab8556da8bae47fcc7
|
Provenance
The following attestation bundles were made for absentia-1.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
publish.yml on skbays03/absentia
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
absentia-1.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
3a3706e11e2d652a6a1627d57576b1af2b5e7ea7238181cc9b944a6fd67a5634 - Sigstore transparency entry: 1486367082
- Sigstore integration time:
-
Permalink:
skbays03/absentia@fa3d804e145ae59ec5347f2052e936054fda3a1c -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/skbays03
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@fa3d804e145ae59ec5347f2052e936054fda3a1c -
Trigger Event:
release
-
Statement type:
File details
Details for the file absentia-1.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: absentia-1.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 404.3 kB
- Tags: CPython 3.13, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0d589539e5868ac7f4d3209ab0962162c839213dbafef3783a031c5e5dc57976
|
|
| MD5 |
151c209bfd2f39d7c3606e39dbdff2e6
|
|
| BLAKE2b-256 |
747c93baf8c248d9ac51efe3d5291af4c60d7f408c32791dd8bc592f74a3896a
|
Provenance
The following attestation bundles were made for absentia-1.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:
Publisher:
publish.yml on skbays03/absentia
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
absentia-1.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -
Subject digest:
0d589539e5868ac7f4d3209ab0962162c839213dbafef3783a031c5e5dc57976 - Sigstore transparency entry: 1486367051
- Sigstore integration time:
-
Permalink:
skbays03/absentia@fa3d804e145ae59ec5347f2052e936054fda3a1c -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/skbays03
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@fa3d804e145ae59ec5347f2052e936054fda3a1c -
Trigger Event:
release
-
Statement type:
File details
Details for the file absentia-1.0.1-cp313-cp313-macosx_11_0_arm64.whl.
File metadata
- Download URL: absentia-1.0.1-cp313-cp313-macosx_11_0_arm64.whl
- Upload date:
- Size: 310.2 kB
- Tags: CPython 3.13, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3e3494ab055b17ff123b3488d3ce7ba1e46e553bac63f822952d46a6d701f8b8
|
|
| MD5 |
f8457e7861522904317b2c67ec2a81b4
|
|
| BLAKE2b-256 |
66c7bf0dee0502f5bf02052144dbe2f8e6af6c68d40226e4b111daa64070f37e
|
Provenance
The following attestation bundles were made for absentia-1.0.1-cp313-cp313-macosx_11_0_arm64.whl:
Publisher:
publish.yml on skbays03/absentia
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
absentia-1.0.1-cp313-cp313-macosx_11_0_arm64.whl -
Subject digest:
3e3494ab055b17ff123b3488d3ce7ba1e46e553bac63f822952d46a6d701f8b8 - Sigstore transparency entry: 1486367074
- Sigstore integration time:
-
Permalink:
skbays03/absentia@fa3d804e145ae59ec5347f2052e936054fda3a1c -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/skbays03
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@fa3d804e145ae59ec5347f2052e936054fda3a1c -
Trigger Event:
release
-
Statement type: