commit-delta
git bisect for a dirty working tree.
You have a known-good HEAD, a pile of uncommitted changes, and a
test that fails on the full working tree. commit-delta finds a
minimal failure-inducing change set — the smallest 1-minimal
subset of those hunks that still fails the test.
It will not tell you that one line is “the cause”. Several hunks may need each other. Another 1-minimal subset may also fail. The output is a reduced patch you can read, apply, and hand to a reviewer or an agent.
HEAD: PASS
Working tree: 14 files, 93 hunks, 567 lines, FAIL
$ commit-delta -- ./reproduce.sh
Minimal failure-inducing change set:
calc.py
hunk 2
- acc = acc + item
+ acc = acc - item
Same failure. 93 hunks → 1 hunk.
Download the demo as MP4 — live xfce4-terminal capture, 15s.
On a real multi-file Python tree (four feature commits left uncommitted): 28 files / 2230 lines → 2 files / 69 lines in about 8 seconds. That run returned a 1-minimal fail, not the only one. Two other independent fails lived in the same tree. That is the tool working as designed.
Install
Requires Git 2.5+ (git worktree) and Python 3.10+.
pip install commit-delta
From a clone, for development:
git clone https://github.com/robbyczgw-cla/commit-delta.git
cd commit-delta
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
Use
Run the same command that is already failing:
commit-delta -- ./reproduce.sh
commit-delta -- pytest -q
commit-delta --output reduced.patch --json reduced.json -- npm test
Everything after -- is your reproduction command. It runs inside
an isolated worktree. Your dirty tree is only read, never rewritten.
| Goal | Flag |
|---|---|
| Write a patch | --output reduced.patch |
| Write JSON (agents) | --json reduced.json |
| Log every trial | --verbose |
| Confirm flaky FAILs | --confirm 3 |
| Kill hung tests | --timeout 60s |
| Narrow the search | --include src / --exclude tests |
| Trust exit codes only | --strict-exits |
What your command should return
Same contract as git bisect run:
| Exit | Meaning |
|---|---|
0 |
PASS |
125 |
UNRESOLVED — this subset cannot build, parse, or import |
| other | FAIL — the failure you want isolated |
If “cannot compile” and “assertion failed” both exit 1, wrap the
command and map unbuildable subsets to 125. For Python,
SyntaxError / ImportError and (when no AssertionError appears)
TypeError / AttributeError are already treated as UNRESOLVED.
The command must work from a clean checkout plus the candidate
hunks. The probe tree does not include your node_modules or
local virtualenv.
How it works
- Verify
HEADis GOOD and the full dirty tree is BAD. - Snapshot
git diff HEAD -U0and untracked text files. - Reduce at file granularity, then hunk granularity (ddmin).
- Apply each candidate in a temporary worktree. Cache results.
- Print a report; optionally write
reduced.patchandreduced.json.
For coding agents
See AGENTS.md. After tests go red, run once:
commit-delta --output reduced.patch --json reduced.json -- <the-same-test>
Prefer reduced.json over scraping the text report. Work only on
that subset. Do not loop. The field unique_not_guaranteed is
always true.
Compared to nearby tools
| Tool | Answers |
|---|---|
git bisect |
Which commit in history broke? |
git add -p |
How do I stage hunks by hand? |
| Shrink Ray / C-Reduce | How do I shrink a failing input file? |
| commit-delta | Which uncommitted hunks still fail the test? |
Limitations (v0.1)
- Atoms are hunks, not statements. A 40-line rewrite is one piece.
- The result is a 1-minimal FAIL set. Uniqueness is not guaranteed.
- Text files only. No binaries, submodules, or ignored build trees.
- The isolated worktree has no local
node_modules/ venv unless your command creates them. - Renames and unusual encodings are best-effort.
npm test,tsc, andcargoneed an exit-125wrapper if they use1for both compile errors and test failures.
Details: docs/limitations.md.
Develop
pip install -e ".[dev]"
make test # unit + fixture e2e
make bench # candidate counts / reduction ratios
make demo # 567-line dirty tree → one hunk
| Fixture | Expected reduction |
|---|---|
| A | one bad hunk among 20+ noise hunks |
| B | A and B each pass; A+B fail — both kept |
| C | some subsets do not import — UNRESOLVED, still converges |
| D | two independent minima — returns one |
| E | --confirm 3 still finds the hunk |
| F | messy refactor, no exit 125 — fat hunk + new file |
| G | four bits must all flip — keeps all four, drops noise |
Docs
- AGENTS.md — when to run, what to do with the output
- docs/algorithm.md — ddmin, heuristics, apply
- docs/limitations.md — what v0.1 will not do
- docs/benchmark.md — trial counts and the demo
- docs/competition.md — why this is not git bisect
License
Apache-2.0. See LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
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 commit_delta-0.1.0.tar.gz.
File metadata
- Download URL: commit_delta-0.1.0.tar.gz
- Upload date:
- Size: 34.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b72652e616f15aec762a768ace210c8351eec3e55a4ebb9ee98b1fa26628ae32
|
|
| MD5 |
124ea50f4a9f81a260f0421bf968bae2
|
|
| BLAKE2b-256 |
1d275550260ede9d026bb4170583b736b205f0c0e31efb3555d42dd447a378f8
|
File details
Details for the file commit_delta-0.1.0-py3-none-any.whl.
File metadata
- Download URL: commit_delta-0.1.0-py3-none-any.whl
- Upload date:
- Size: 27.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ffa46d40726dc0b94e6740fca004b8d766f051132d9fff34c1d3486b3b8fa7af
|
|
| MD5 |
2bedb38b332cae82dbc80271ee930659
|
|
| BLAKE2b-256 |
0fc9b63fbba518bdbeade81f5fe0d23d84462eea5ab4aae333e54c2477627ea9
|