1. Конфликт — это не всегда конфликт строк
Когда разработчик слышит слово «конфликт», в голове почти автоматически всплывает экран Git с красными маркерами и ручным merge. Образ верный, но узкий. Git — честный бухгалтер: считает строки, файлы и коммиты, но не знает бизнес-смысл, ownership, API-контракты и ваши скрытые предположения. Часть опасных конфликтов он замечает поздно, часть — вовсе. В Commerce OS это видно на стыке checkout и promo: один трек упрощает расчёт итоговой суммы, другой дорабатывает скидки. Оба формально «правильные», а вместе дают дичь — купон применяется дважды, округление плывёт, интеграционный тест падает после merge.
| Ситуация | Git заметит проблему | Что реально сломано |
|---|---|---|
| Два трека редактируют одну и ту же строку в CartTotal.java | Да | Прямой конфликт строк |
| Один трек меняет сигнатуру метода расчёта, другой использует старый контракт в соседнем модуле | Не всегда | Семантический конфликт, который всплывёт в тестах или в рантайме |
| Один трек залез в файлы вне своего scope и «заодно улучшил» соседний модуль | Может и не заметить | Конфликт ownership и нарушение договорённостей команды |
| Один трек зелёный локально, но ломает сценарий другого после объединения | Нет | Конфликт предположений, а не строк |
Вот почему опасно мыслить так: «раз Git смержил без вопросов, значит всё хорошо». На деле это значит только, что он сложил текст. Параллельная разработка ломается не на символах — на смыслах: один трек считает, что налог после скидки, второй — что до. Git не читает ваши мысли. Полезно держать в голове простое правило: конфликт строк — технический симптом. Семантический — конфликт модели мира: кто владеет областью, какой контракт главный, где кончается чужой scope. Такие встречаются чаще всего.
2. Кто прав — решает ownership, а не красноречие
Когда два трека расходятся по смыслу, первая реакция новичка часто выглядит так: «давайте спросим Claude, какой вариант лучше». Звучит удобно, но это ловушка. Выберете по красоте ответа — проголосуете не за правильное решение, а за убедительный текст. Спрашивайте не про красоту, а по инженерным критериям:
| Критерий | Что спрашиваем | Почему это важно |
|---|---|---|
| Ownership | Чей трек отвечает за спорную область кода | Владелец области обычно лучше понимает её ограничения |
| Scope discipline | Вышел ли трек за согласованные границы | Результат вне scope менее надёжен, даже если выглядит полезным |
| Evidence | Есть ли тесты, логи, небольшой и понятный diff | Уверенность без evidence ничего не стоит |
| Цена ошибки | Сколько будет стоить неправильный merge | Иногда лучше выбрать более скучный, но безопасный вариант |
Шаблон таких правил удобно держать в Workflow Kit, а запись по конкретному треку — в parallel-session-log.md рядом с проектом. Тогда выбор доверенного результата перестаёт быть спором «мне кажется», а опирается на уже существующий контракт.
# parallel-session-log.md
Track: feature/checkout-refactor
Owner: session-checkout
Scope: src/checkout/*, tests/checkout/*
Out of scope: src/promo/*, src/payments/*
Stop condition: checkout tests green
Decision rule: human review before merge
Если второй трек внезапно полез в src/checkout/CartTotal.java, хотя scope был только src/promo/*, это уже серьёзный сигнал. Даже разумный код теряет доверие — границы нарушены. Как бригада, которую наняли красить стены, а она заодно передвинула электрику: люди талантливые, но вопросы появятся. И тут важно ещё одно уточнение: доверенный результат не всегда берут целиком — владелец области может принести слишком широкий diff или слабые проверки, и тогда доверена не вся ветка, а её часть.
3. Разбор конфликта: сначала diff, потом смысл
Когда два параллельных трека сталкиваются, хочется поскорее открыть merge tool и «как-нибудь договориться». Но правильный разбор начинается раньше: diff каждого трека относительно main, потом сравнение треков между собой, затем ownership, и только потом тесты и решение. Начнёте наоборот — легко перепутаете симптом с причиной. Схему ниже стоит держать как рабочий шаблон, она нарочно простая, чтобы ей можно было пользоваться прямо во время работы, а не любоваться в методичке:
flowchart TD
A[Обнаружили расхождение между треками] --> B[Сравнили diff каждого трека с main]
B --> C[Проверили ownership и границы scope]
C --> D[Посмотрели tests, logs и артефакты]
D --> E{Что безопаснее принять?}
E -->|Один трек годится целиком| F[accept]
E -->|Полезна только часть| G[cherry-pick]
E -->|Изменения смешаны| H[split]
E -->|Трек вреден или вне scope| I[reject или revert]
F --> J[Записали решение в session log]
G --> J
H --> J
I --> J
В Commerce OS разбор checkout против promo выглядит приземлённо:
git diff main...feature/checkout-refactor -- src/checkout/CartTotal.java
git diff main...feature/promo-discount -- src/promo/PromoEngine.java
git diff feature/checkout-refactor...feature/promo-discount -- src/checkout src/promo
(cd ../commerce-os.checkout-flow && ./gradlew test --tests "*Checkout*")
# BUILD SUCCESSFUL
(cd ../commerce-os.promo-flow && ./gradlew test --tests "*Promo*")
# 1 test failed: PromoDiscountContractTest
Эти команды полезны не потому, что в них есть магия, а потому что показывают конфликт с трёх сторон: три diff смотрят на него с трёх сторон, а targeted tests бьют по спорной области, не утопая в полном прогоне. Но даже зелёные тесты ещё не значат, что конфликтов нет: оба набора зелёные, потому что каждый трек проверял только своё. Тогда надо смотреть на контракт. Не сдвинулась ли сигнатура метода? Порядок скидки и налога? Не вынес ли один трек логику в helper, пока другой зовёт старую ветку? Семантический конфликт живёт как раз в зазоре между «моё работает» и «наше работает вместе».
Здесь очень частая ошибка звучит так: «Claude, слей конфликт». Если вы не указали, какой трек владеет областью, какой результат доверенный и что сохранить, вы по сути просите ИИ принять архитектурное решение за вас. Иногда угадает, иногда нет. И неприятнее всего — прозвучит уверенно в обоих случаях.
4. Стратегия слияния готовится заранее
Когда конфликт уже случился, придумывать правила с нуля поздно. Хорошая стратегия слияния — заранее понятный набор решений: не спорите о принципах, а применяете согласованную модель. Особенно там, где давит соблазн «слить всё, жалко же работу». На практике решений немного, но каждое уместно по-своему, и путать их не стоит.
| Решение | Когда выбирать | Что происходит |
|---|---|---|
| Принять целиком | Трек в своём scope, diff понятный, evidence хорошие | Ветка вливается как есть |
| Отклонить | Трек вне scope, ломает контракт или даёт лишний риск | Изменения не принимаются |
| Выборочно перенести коммит (cherry-pick) | Полезна только часть ветки | Берётся отдельный безопасный коммит |
| Разделить | Ветка смешала несколько разных типов изменений | Изменения разносятся по отдельным PR |
| Откатить (revert) | Проблемный merge уже попал в общую ветку | Создаётся явный откат |
Особенно полезно понимать логику cherry-pick. Новички то боятся его без причины, то жмут как волшебную кнопку. На деле это выборочный перенос изолированного коммита с атомарной безопасной пользой. Promo-трек принёс неудачный рефакторинг, но хорошие тесты? «Заберём тесты, рефакторинг — в отдельную задачу» — здоровая логика.
git checkout feature/integration-branch
git log --oneline feature/promo-discount
# 9ac12f4 promo tests for discount rounding
git cherry-pick 9ac12f4
# перенесли только коммит с тестами
Reject тоже не означает «всё зря». Отклонённый трек часто приносит ценность: нашёл скрытый риск, подсветил слабое место архитектуры, написал фрагмент документации, выявил отсутствующий тест. Просто ей не обязательно приезжать в main целой веткой — ценность меряется не количеством merged-кода. А вот стратегия «слить всё, потом починим» почти всегда выглядит заманчиво только первые пять минут. Дальше — смешанный diff, спорные контракты, невнятный follow-up и внеплановый PR на ремонт. Экономия та же, что от «не мыть посуду до приезда гостей».
5. Решение по конфликту — в артефакт
Даже верное решение теряет ценность, если осталось в голове или в одном длинном чате с Claude Code. Параллельная работа зреет не когда команда редко конфликтует, а когда каждое решение можно потом прочитать, понять и объяснить. Для этого нужен parallel-session-log.md — тот же лог трека, только с секцией Conflicts. Фиксируйте не только итог, но и причину: какой конфликт, какой трек признали доверенным, что сделали с другим, что вынесли в follow-up. Это гасит повторные споры через неделю, когда помнят уже не факты, а эмоции.
## Конфликт 2026-05-19 checkout-vs-promo
Type: semantic conflict
Trusted output: feature/checkout-refactor
Decision: cherry-pick tests from promo track
Reason: checkout owner, smaller diff, checkout tests green
Follow-up: PROMO-118 align PromoEngine contract
Такой фрагмент можно держать прямо внутри parallel-session-log.md по треку — главное, чтобы решение не растворилось: другой разработчик, свежая сессия Claude или вы сами через три дня увидите не «что сделали», а «почему именно так». Ещё один полезный уровень — общее правило в CLAUDE.md Workflow Kit, чтобы повторяющиеся конфликты не решались с нуля.
Если параллельный трек правит файлы вне своей зоны:
1. reject out-of-scope changes;
2. cherry-pick isolated safe commits only;
3. create a follow-up issue for the rest.
Фрагмент маленький, а делает две вещи: человеку — меньше импровизации под стрессом, Claude Code — рамку, где «полезно» не значит «можно бесконтрольно вливать». Лекарство от чересчур усердного ИИ.
6. Конфликт — подсказка о проблеме выше
У конфликтов есть полезная сторона: они честно показывают, где сломалась не ветка, а более ранний слой процесса. Если повторяется один и тот же semantic conflict, дело редко в последнем merge. checkout и promo сталкиваются каждый раз — вероятно, нечёткая граница между расчётом суммы и скидками. Треки постоянно лезут в чужие файлы «совсем чуть-чуть» — scope и ownership в task brief размыты. Конфликты вылезают только после merge, локально всё зелёное — не хватает интеграционных или контрактных проверок на стыке зон ответственности.
Поэтому после каждого серьёзного конфликта полезно задавать себе не только вопрос «как это смержить», но и «почему он вообще был возможен». Ответ — изменить decomposition, обновить CLAUDE.md или уточнить шаблон трека в parallel-session-log.md. И это нормальный рост процесса. Параллельная работа не обязана быть бесконфликтной. Она обязана быть понятной, ремонтопригодной и достаточно дисциплинированной, чтобы любой конфликт превращался не в драму, а в аккуратное инженерное решение.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ