Metadata-Version: 2.4
Name: slopcut
Version: 0.1.1
Summary: Cut the residue out of AI-generated patches: find the edits a repair actually needs and drop the rest
Author: slopcut contributors
License-Expression: MIT
Project-URL: Homepage, https://github.com/ujjwalredd/slopcut
Project-URL: Documentation, https://github.com/ujjwalredd/slopcut#readme
Project-URL: Issues, https://github.com/ujjwalredd/slopcut/issues
Project-URL: Source, https://github.com/ujjwalredd/slopcut
Keywords: ai,claude-code,code-quality,delta-debugging,developer-tools,patch-minimization
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Software Development :: Testing
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: bench
Requires-Dist: matplotlib; extra == "bench"
Provides-Extra: dev
Requires-Dist: build; extra == "dev"
Requires-Dist: matplotlib; extra == "dev"
Requires-Dist: pytest; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Requires-Dist: twine; extra == "dev"
Dynamic: license-file

# slopcut

Coding agents rarely clean up every failed attempt. When an agent tries three fixes
and the third one works, code from the first two can remain in the diff. The resulting
patch becomes the union of every hypothesis the agent tried, while the lines that
actually fixed the bug are buried inside it.

slopcut finds those two lines. It reads the session, then repeatedly asks: *do the
tests still pass without this group of edits?*

```
$ slopcut

session   0f3ab21e-session.jsonl
tests     python -m pytest -q
patch     47 lines across 12 edits in 4 attempts

  superseded  -3 edits (free, never reached the patch)
  scratch     -1 files (1 test run): repro_bug.py
  attempt     -5 edits
  file        -2 edits

result    47 -> 9 lines (81% removed), 2 edits remain
cost      9 test runs (2 cached)
```

The default mode writes nothing. Search and validation happen in a temporary Git
worktree, leaving the active working tree untouched.

## Install

Install it as an isolated command-line tool:

```bash
uv tool install slopcut
# or
pipx install slopcut
# or
python -m pip install slopcut
```

slopcut requires Python 3.10 or newer and Git.

To add the Claude Code command, register this repository as a plugin marketplace:

```bash
claude plugin marketplace add ujjwalredd/slopcut
claude plugin install slopcut@slopcut
```

The plugin calls the installed `slopcut` executable. After installation, run
`/slopcut` in Claude Code after a session where the agent edited code.

## Use

```bash
slopcut                              # dry run: show what it would remove
slopcut --apply                      # write the minimized patch (backs up first)
slopcut --level edit                 # search harder
slopcut --test "cargo test --lib"    # when the test command cannot be inferred
slopcut --json                       # machine-readable
```

By default, slopcut reads the most recent Claude Code session for the current
repository, finds the test command the agent ran, and prints a minimized diff.

### How hard to search

| `--level` | removes | test runs | recovery |
|---|---|---|---|
| `attempt` | whole abandoned attempts | 6.5 | 67% |
| `hybrid` | attempts, then whole files | 8.4 | 81% |
| `edit` **(default)** | individual edits | 10.7 | 100% |

`edit` is the default because the benchmark below shows that the cheaper levels cost
most of what `edit` costs while stopping short of the minimum. Use `hybrid` or
`attempt` when a single test run is genuinely expensive, such as a slow integration
suite or container rebuild.

`--exhaustive` repeats each level until nothing more comes out. It almost never
finds anything the single pass missed, so it is off by default.

## How it works

1. **Read the session** into attempts. Each attempt is a batch of edits followed by
   a test run. Everything else, including file reads, searches, and navigation, is dropped
   because it cannot affect the patch.
2. **Drop superseded edits.** If removing an edit leaves the final tree
   byte-identical, it was overwritten later and never reached the patch. Pure string
   work, zero test runs.
3. **Drop agent-created files.** One test run tries to discard files the agent
   created for debugging, such as reproduction scripts and temporary harnesses.
4. **Search coarse to fine:** attempt, file, then edit. Remove a unit, run the tests,
   keep the removal only if it passes *and* the patch got smaller. One success at the
   top level kills an entire abandoned attempt for the price of a single test run.

Candidates are rebuilt by replaying the surviving edits onto a clean checkout instead
of splicing the diff. If an edit depends on something that was removed, the test run
can reject that candidate.

## Benchmark

Real agent sessions can show that a patch became 30% smaller, but not whether every
removable edit was found. Without an answer key, a 30% reduction could be complete or
could miss most unnecessary edits.

So the benchmark builds its own. `bench/generate.py` writes small libraries with a
seeded bug and a synthetic session that fixes it using common agent behavior: wrong
hypotheses, debug prints, throwaway scripts, dead code, and the real fix buried in
the middle. Because the generator decides which edits are load-bearing, it can score
**recovery**: of the slop that *could* be removed, how much did each method find?

**700 tasks, 4900 runs, zero errors.** Every method judged against the same answer key:

| method | recovery (95% CI) | found the exact minimum | test runs | worst case | broke the build |
|---|---|---|---|---|---|
| no minimization | 0% | 0% | 0 | n/a | 0% |
| keep last attempt only | 59.0% [56.8, 61.1] | 0% | 1 | 1 | 0% |
| delta debugging over hunks | 71.8% [69.2, 74.3] | 20.4% | 3.4 | 12 | 0% |
| `slopcut --level attempt` | 67.3% [66.2, 68.5] | 0% | 6.5 | 11 | 0% |
| `slopcut --level hybrid` | 80.6% [79.1, 82.0] | 12.3% | 8.4 | 16 | 0% |
| **`slopcut --level edit`** | **100% [100, 100]** | **100%** | 10.7 | 17 | 0% |
| `slopcut --level edit --exhaustive` | 100% [100, 100] | 100% | 11.2 | 20 | 0% |

`slopcut --level edit` found the exact minimum on all 700 tasks, and never once
produced a patch that failed its tests.

Everything runs locally in about ten minutes. It needs no containers, API keys, or
network access:

```bash
pip install -e '.[bench]'
python bench/generate.py --tasks 100 --out bench/out/tasks
python bench/run.py bench/out/tasks --out bench/out/results.jsonl
python bench/report.py bench/out/results.jsonl
python bench/charts.py bench/out/results.jsonl --out docs/charts
```

<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ujjwalredd/slopcut/main/docs/charts/recovery-dark.png">
  <img alt="Recovery by method, with 95% confidence intervals" src="https://raw.githubusercontent.com/ujjwalredd/slopcut/main/docs/charts/recovery-light.png">
</picture>

<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ujjwalredd/slopcut/main/docs/charts/cost-quality-dark.png">
  <img alt="Cost against quality for each method" src="https://raw.githubusercontent.com/ujjwalredd/slopcut/main/docs/charts/cost-quality-light.png">
</picture>

### What the numbers say

**Delta debugging depends on hunk boundaries.** At 3.4 test runs it is by far the
cheapest real method, and when the slop sits in separate diff hunks it scores a
perfect 100%. When leftover code shares a hunk with the fix, it scores **0%**.
For example, an agent may add a stray local variable next to the repaired line.
Hunk-based reduction cannot remove one without removing the other, so it keeps both.

<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ujjwalredd/slopcut/main/docs/charts/separable-vs-tangled-dark.png">
  <img alt="Recovery when slop is separable versus tangled with the fix" src="https://raw.githubusercontent.com/ujjwalredd/slopcut/main/docs/charts/separable-vs-tangled-light.png">
</picture>

That is the whole case for reading the session. The two edits are indistinguishable
in the final diff and obvious in the history, because the agent made them at
different times for different reasons.

**"Just keep the last attempt" is a surprisingly good trick, and a dangerous one.**
One test run buys 59% recovery. But on tasks where the fix calls a helper an earlier
attempt introduced, it scores **0%**. Throwing away the earlier attempt breaks the
repair, so it has to keep everything. Cheap heuristics fail silently on exactly the
sessions that are most tangled.

**The middle levels do not earn their discount.** `hybrid` spends 8.4 runs to reach
80.6%, where `edit` spends 10.7 to reach 100%. Two more test runs for the last fifth
of the slop is almost always the right trade, which is why `edit` is the default.

**`--exhaustive` finds nothing.** Identical recovery, +0.5 runs, and a worse tail
(max 20 vs 17). A second pass essentially never finds what the first missed, so it
stays off.

**Cost grows gently with patch size.** The benchmark used roughly 5 test runs on an
8-line patch and 16 on a 64-line patch. Growth was sublinear because the coarse
levels removed abandoned attempts before the per-edit pass started.

<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ujjwalredd/slopcut/main/docs/charts/scaling-dark.png">
  <img alt="Test runs required as patch size grows" src="https://raw.githubusercontent.com/ujjwalredd/slopcut/main/docs/charts/scaling-light.png">
</picture>

### What this benchmark does not measure

The tasks are generated, not scraped from real sessions. That is the trade: a known
answer key in exchange for realism. It makes the *search* measurable because a
real-world corpus cannot provide recovery against a known optimum. The mix of slop
shapes is still a modelling choice, and the amount of slop produced by real agent
sessions is not measured here.

Generated slop is also cleaner than the real thing: every edit is anchored on text
that exists, and none of them interact in surprising ways. Treat 100% recovery as
"the search is not the bottleneck," not as a guarantee for every repository.

## Real-world behavior

The benchmark measures the search, not how much slop an agent produces. Sessions
that went directly to the fix have nothing to remove, and slopcut reports that result
plainly. An already minimal patch is a successful outcome.

The wins concentrate in the messy sessions: long ones, ones where the agent went down
a wrong path first, ones that left a `repro.py` behind.

## Limitations

- **The test suite defines required behavior.** slopcut can remove behavior that the
  configured tests do not cover. Limited coverage can therefore produce aggressive
  reductions. Every minimized diff should be reviewed before it is applied.
- **Flaky tests can authorize a bad removal.** slopcut checks the baseline before
  starting, but it cannot catch a test that fails 1 run in 20.
- **The smallest unit is one edit.** Redundancy *inside* a single edit is out of reach.
- **Reconstruction can drift.** slopcut rebuilds the patch from the session. Manual
  edits and changes made by unsupported tools can leave the reconstruction incomplete.
  slopcut detects this condition and refuses to apply the result.
- **Adapters:** Claude Code, plus SWE-agent and bash-only scaffolds. Adding one is
  small: emit `(file, before, after)` in order, grouped by test run.

## Contributing

See the
[contribution guide](https://github.com/ujjwalredd/slopcut/blob/main/CONTRIBUTING.md).
Start with `pip install -e '.[dev]' && pytest`.

Two numbers decide whether a change to the search is good: recovery must not drop,
and test runs must not rise.

## Security

slopcut executes test commands on the local machine. Inferred commands come from the
selected Claude Code transcript, so repositories and session files are trusted inputs.
The `--test` option can provide an explicit command. Candidate patches are isolated
in a Git worktree, but test processes are not containerized or sandboxed. See
[the security policy](https://github.com/ujjwalredd/slopcut/blob/main/SECURITY.md)
for the full trust model and private reporting process.

## Releasing

Maintainers can find the release checklist and PyPI Trusted Publishing setup in
[the release guide](https://github.com/ujjwalredd/slopcut/blob/main/RELEASING.md).

## Credits

The trajectory-minimization idea comes from
[TRIM: Reducing AI-Generated CodeSlop via Agent Trajectory Minimization](https://arxiv.org/abs/2607.18161)
(Mathai et al., 2026), which is well worth reading.

## License

MIT
