Culprit
# Culprit — автоматический git bisect с изоляцией и точным доказательством
Знакомая боль: что-то сломалось, а виновный коммит потерялся среди сотен
изменений. Ручной `git bisect` работает, но дёргает вашу рабочую копию туда-
сюда на каждом шаге и требует вручную гонять тесты. Culprit делает то же
самое — **полностью изолированно** (через `git worktree`, ваша рабочая
копия не трогается вообще) и **резюмируемо** между вызовами.
## Доказано на настоящем репозитории, не гипотетически
```
15 реальных коммитов, баг внедрён ИМЕННО в commit 9
start_bisect(good=v-good, bad=HEAD, check="<your interpreter> -c '...'")
run_bisect(session_id)
… GOOD / BAD …
CULPRIT FOUND after ≤5 test(s): … "commit 9"
```
Вместо перебора кандидатов — ⌈log₂ n⌉ проб. HEAD и рабочая копия после
прогона **не меняются** — это проверяет интеграционный тест, а не README.
## Ключевая инженерная деталь: git worktree, а не checkout
Наивная автоматизация делает `git checkout <commit>` в рабочей копии —
переключает ветку, конфликтует с грязным деревом, оставляет detached HEAD.
Culprit для каждого кандидата поднимает **изолированный `git worktree`** и
сразу сносит его. Ваш checkout в процессе не участвует.
## Алгоритм: бинарный поиск с обработкой SKIP
Ядро — чистая `BisectAlgorithm.advance()` без git и I/O:
- SKIP на середине → берётся ближайший непротестированный сосед;
- весь диапазон SKIP → честный `ambiguous`, без бесконечного цикла;
- результаты можно копить в любом порядке — границы пересчитываются из
полной истории outcomes.
Код возврата **125** = SKIP, как у `git bisect run`. Таймаут check-команды
тоже становится SKIP.
## Архитектура (Clean Architecture)
```
src/culprit/
├── entities/ # CommitRef, BisectAlgorithm, BisectSession
├── use_cases/ # start / step / run-to-completion / status
├── interfaces/ # порты (Git, Runner, SessionRepository, Clock, Id)
├── infrastructure/ # real git worktree, subprocess+timeout, sqlite
└── presentation/ # composition root, MCP tools, response formatting
```
## Почему здесь есть `subprocess`
В отличие от Loom/Ward/Covenant, subprocess — **суть** инструмента (как у
`git bisect run`). **Trust boundary:** `check_command` задаёт **человек**
(тест/скрипт, который вы уже доверяете). Агент/LLM не должен изобретать
shell-пейлоады — только передать вашу команду в `start_bisect`.
## Линейность истории
Кандидаты берутся через `git log --first-parent good..bad`: бинарный поиск
идёт по mainline, а не по произвольному merge-DAG. Для типичного «сломалось
на main» этого достаточно; полный обход всех merge-веток не поддерживается.
## Установка
```bash
git clone <этот репозиторий>
cd culprit
pip install -e ".[dev]"
```
Проверка качества (lint, типы, тесты, покрытие, стена Мартина):
```powershell
.\make.ps1
```
## Подключение к Claude Desktop / Claude Code
```json
{
"mcpServers": {
"culprit": {
"command": "culprit-mcp",
"env": {
"CULPRIT_DB": "~/.culprit/sessions.db",
"CULPRIT_CHECK_TIMEOUT_SECONDS": "300"
}
}
}
}
```
`~` в `CULPRIT_DB` раскрывается через `os.path.expanduser` (и для env, и для
default). Невалидный timeout тихо откатывается к 300 секундам.
На Windows укажите полный путь к `culprit-mcp.exe` или к
`python -m culprit.presentation.mcp_server`, если Scripts не в PATH.
## check_command: кроссплатформенно
Используйте интерпретатор, который точно есть на машине:
```text
# Linux / macOS
python3 -c "…"
# Windows (или везде, если venv активирован)
python -c "…"
```
Контракт: `0` — бага нет (GOOD), ненулевой — бага есть (BAD), `125` — SKIP.
Таймаут check-команды тоже = SKIP. Команду передаёт человек, не модель.
## Пример разговора
> **Вы:** Начиная с v2.3.0 тест `test_login_timeout` стал падать. Найди,
> какой коммит это сломал.
>
> **Claude:** [start_bisect → run_bisect] Нашёл: `a3f8c21`
> "Refactor session timeout handling". Рабочая копия не трогалась.
## MCP Tools
| Tool | Назначение |
|---|---|
| `start_bisect(repo_path, good_ref, bad_ref, check_command)` | Начать сессию |
| `step_bisect(session_id)` | Один кандидат в worktree |
| `run_bisect(session_id)` | До конца (≤50 шагов) |
| `bisect_status(session_id)` | Состояние сессии |
| `list_bisect_sessions()` | Все сессии |
Ошибки инструментов приходят строкой `error: …` — удобно читать в чате.
## Тесты
```bash
pytest -v
```
Покрытие: чистый алгоритм, use cases на фейках (включая timeout→SKIP),
форматирование MCP-ответов, subprocess timeout, **реальный git** с
внедрённым багом и доказательством, что HEAD/файлы не изменились.
## Лицензия
MIT — см. файл `LICENSE`.
TDQS
Scored across 5 tools
Each tool has a clearly distinct role: start_bisect initializes a bisect, step_bisect tests one candidate, run_bisect drives stepping to completion, bisect_status reports the current session, and list_bisect_sessions enumerates stored sessions. There is no overlap that would cause misselection.
start_bisect, step_bisect, and run_bisect follow a consistent verb_bisect pattern, but bisect_status inverts to noun_verb and list_bisect_sessions inserts bisect in the middle. The deviations are minor and the names remain readable and predictable.
Five tools is well-scoped for git bisect automation, with each tool earning its place across initialization, stepping, driving, status, and session listing. No obvious bloat or thinness.
The core bisect lifecycle is covered: start, step, run, status, and list sessions. Minor gaps exist, such as no explicit abort/cleanup or delete-session operation, but agents can work around this by ignoring or overwriting sessions.