A Claude Code skill for finding the cause of a regression by binary search — over commits, inputs, config, dependencies, environment, or test ordering.
"It worked yesterday." — the space is commits. "Only this 40 MB payload crashes it." — the space is the input. "Passes alone, fails in the suite." — the space is the test order.
Same shape every time: a set of candidates, and a predicate that says whether a candidate reproduces the bug. This skill packages that method, plus the tooling that makes it reliable.
Delta debugging has been well understood since Zeller & Hildebrandt's 1999
ddmin paper, and good standalone reducers exist (picire, cvise, jsdelta).
But the method is barely used in practice, because it demands exactly what
people are worst at: running the same test forty times and keeping honest
records of every verdict.
That is precisely what an agent is good at. This skill gives one the method, the polarity conventions, and working reducers.
| File | Purpose |
|---|---|
SKILL.md |
The method: pick an axis, validate a predicate, search, confirm |
scripts/ddmin.py |
Delta debugging — shrinks a failing input to a 1-minimal core |
scripts/check-predicate.sh |
Proves a predicate is stable before you spend an hour on it |
references/recipes.md |
Per-axis recipes: dependencies, test ordering, env, perf, flakiness |
Zero dependencies — Python 3 standard library and bash.
Either the whole evidence plugin, which is all four skills:
/plugin marketplace add lkc-studio/claude-plugins
/plugin install evidence@lkc-plugins
Or this skill on its own:
git clone https://github.com/lkc-studio/claude-skill-bisect.git ~/.claude/skills/bisectThen start a new session and ask something like "this test started failing sometime last week — find out which commit did it"
Both scripts are standalone and useful without Claude.
Every predicate follows the git bisect run convention:
exit 0 = bug absent, non-zero = bug present.
# Shrink a 200-line config to the one line that breaks the build.
scripts/ddmin.py --predicate 'make build 2>&1 | grep -q ERROR' --in-place config.yaml
# Shrink an input file; {} is replaced with the candidate's path.
scripts/ddmin.py --predicate 'node app.js {} 2>&1 | grep -q TypeError' payload.json
# Check a predicate is not flaky, and project the cost of the search.
scripts/check-predicate.sh --runs 7 --space 4000 -- pytest -q -x tests/test_login.pyReal run — 200 lines down to the single culprit in 12 probes:
$ ddmin.py --predicate '! grep -q "timeout = -1" {}' conf.txt
[ 1] 200 units -> BAD (reproduces)
...
[ 12] 1 units -> BAD (reproduces)
ddmin: 200 -> 1 lines (99.5% removed) in 12 predicate runs -> stdout
timeout = -1 # the poison
python3 -m unittest discover -s tests -v17 tests covering the algorithm (1-minimality, conjunctions, order preservation, probe count) and the CLI (polarity guards, in-place restore, granularity).
ddmin.py returns a 1-minimal result: removing any single remaining unit
stops the reproduction. That is not the globally smallest input — see
references/recipes.md for what that implies, along with handling of structured
inputs and flaky predicates.
MIT