Skip to main content
Glama
README.md
# 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

B3.4/5.0

Scored across 5 tools

Disambiguation5/5

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.

Naming Consistency4/5

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.

Tool Count5/5

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.

Completeness4/5

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.

Maintenance

ActivityMaintained
ResponsivenessNo issues