Metadata-Version: 2.4
Name: ranit
Version: 0.1.0
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: 20/20 conditions across `tenacity`, `click`, `markupsafe` and `attrs`, zero configuration — including the case where a symbol is referenced by a test that never runs.

The check itself is also tested, by running deliberately broken builds through it and requiring them to be rejected. Two exist so far and both are rejected, but by *disjoint* controls — so the honest claim is that the check catches those two, not broken builds in general.

## License

MIT © Nucleus Team
