Metadata-Version: 2.4
Name: ranit
Version: 0.1.1
Summary: Tells you which functions in your diff were executed by nothing — including by the test you just wrote.
Project-URL: Homepage, https://github.com/eidetic-works/ranit
Project-URL: Issues, https://github.com/eidetic-works/ranit/issues
Author: Nucleus Team
License-Expression: MIT
License-File: LICENSE
Keywords: ci,coverage,dead-code,diff,pytest,testing
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: Pytest
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Testing
Requires-Python: >=3.9
Requires-Dist: coverage>=7.0
Description-Content-Type: text/markdown

# ranit

**Which functions in your diff were executed by nothing — including by the test you just wrote for them.**

```bash
pip install ranit
coverage run -m pytest
```

That's it. `ranit` registers itself as a pytest plugin, so there is no new command to remember. When your suite finishes it either says nothing, or says this:

```
ranit: changed code that nothing executed
  src/billing/refunds.py:88  function _apply_partial_credit
```

Three lines you just wrote, a green suite, and not one of them ran.

---

## Why this exists

Linters prove your code parses. Type checkers prove it types. Coverage prints a percentage for a *file*. None of them tell you whether **the function you just changed actually ran**.

The gap shows up most sharply in a test that passes while executing nothing — mock out the subject, reimplement the logic in the test body, assert the mock you just created was called by yourself, print a green checkmark. It passes in 0.08s. Every tool above is satisfied.

`ranit` reads the coverage database your test run already produced, intersects it with your git diff and the AST, and reports per symbol.

## What it says

| verdict | meaning | exit |
|---|---|---|
| `PROVEN` | every changed symbol executed | 0 |
| `REFUTED` | at least one changed symbol executed by nothing | 1 |
| `INSUFFICIENT` | **not measurable** — stale database, unmeasured files, no per-test contexts | 2 |

`INSUFFICIENT` is the point. It is a distinct third state, never folded into a pass. If the tool cannot see, it says so:

```
ranit: no coverage active — nothing was measured, so nothing was checked.
Run `coverage run -m pytest` (or `pytest --cov`) to get a verdict.
```

It prints that **only when your diff actually contained something it would have judged**. A clean tree, a comment-only change, or a trivial edit stays silent.

## Usage

```bash
coverage run -m pytest          # plugin reports automatically
pytest --cov=.                  # also works

ranit                           # check the current diff by hand
ranit --tests                   # which TESTS executed zero product lines
ranit --quiet                   # print only real findings
ranit --allow-stale             # proceed on an older coverage database
ranit --install-hook            # install a pre-commit hook
```

`--tests` needs per-test attribution, which needs a coverage rcfile:

```ini
[run]
dynamic_context = test_function
```

Note that `coverage run --context=NAME` sets **one static label for the whole run** and cannot attribute anything to an individual test.

It never changes your suite's exit status. A reporting plugin that can fail someone else's build is worse than no plugin.

## Why not diff-cover, or Codecov patch coverage?

They answer a genuinely different question, and if line-level patch coverage is what you want, use them — they are good at it.

- **diff-cover / Codecov patch coverage** report *what percentage of your changed lines* are covered. The unit is the line, the output is a ratio, and the usual response to "87%" is nothing.
- **`ranit`** reports *which named functions in your diff executed nothing*. The unit is the symbol, the output is a list of names, and the response to `_apply_partial_credit ran zero times` is obvious and immediate.

A patch at 87% and a patch containing one entirely dead new function can be the same patch. The percentage is where that fact goes to hide.

Also: Codecov is a service with an account, a token and an upload. `ranit` is one dependency, runs locally, sends nothing anywhere, and needs no API key.

## Why not vulture, or a dead-code finder?

`vulture` and friends are **static** — they infer from the AST whether a symbol looks used. `ranit` reads what actually executed. Static analysis cannot see a function reached only through a plugin registry, and it cannot see one that is referenced everywhere but never called.

## Why diff-scoped

A repo-wide list of never-executed symbols runs to thousands of rows. Nobody acts on it, the same way nobody has acted on coverage's "Missing" column for thirty years. Scoped to the diff it becomes a fact with one obvious response, delivered while the code is still cheap to change.

## Design

- **One dependency** (`coverage`). No LLM, no vendor, no API key, no network, no telemetry.
- **Quiet by default.** A plugin that speaks on every run gets disabled, and then it protects nothing.
- **Never raises into the host.** Every failure path is a silent return.
- **Hunk-scoped.** Editing one function in a 600-line module does not report every other dead symbol in that file as yours.

Receipts are written to `.ranit/` only when you opt in via `RANIT_HOME`.

## Honest limits

- It needs a coverage run. No coverage, no verdict — and it tells you rather than passing.
- Symbols under 3 lines are skipped; flagging one-line accessors trains people to ignore the report.
- `--tests` needs the rcfile above; without it you get `INSUFFICIENT`, not a guess.
- It proves **execution, not correctness**. A function that ran is not a function that works.

## Verification

`ranit` is checked against third-party repositories it has never seen, with positive and negative controls. Latest run: **32/32 conditions** — eight controls across `tenacity`, `click`, `markupsafe` and `attrs`, zero configuration. The controls include a symbol referenced only by a test that never runs, a symbol dispatched dynamically so its name appears nowhere at the call site, and a symbol called from a branch the test never takes.

The check itself is tested, by running deliberately broken builds through it and requiring them to be rejected. **Seven exist and all seven are rejected** — five of them written specifically to defeat the controls that existed at the time, and three of those succeeded before a new control was added. The most recent passes seven of the eight controls and is caught only by the branch-coverage one.

That is the honest claim: the check catches those seven, not broken builds in general. Every fake is kept and re-run rather than retired, because a gate that no longer faces the thing it once failed has no evidence it still holds.

## License

MIT © Nucleus Team
