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*")
# ЗБІРКА УСПІШНА
(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. відхиляйте зміни поза scope;
2. cherry-pick лише ізольовані безпечні коміти;
3. створіть подальшу задачу для решти.
Фрагмент маленький, а робить дві речі: людині — менше імпровізації під стресом, Claude Code — рамку, де «корисно» не означає «можна безконтрольно вливати». Ліки від надто старанного ШІ.
6. Конфлікт — підказка про проблему вище
У конфліктів є корисний бік: вони чесно показують, де зламалася не гілка, а більш ранній шар процесу. Якщо повторюється один і той самий semantic conflict, справа рідко в останньому merge. checkout і promo стикаються щоразу — ймовірно, нечітка межа між розрахунком суми та знижками. Треки постійно лізуть у чужі файли «зовсім трішки» — scope і ownership у task brief розмиті. Конфлікти вилізають лише після merge, локально все зелене — бракує інтеграційних або контрактних перевірок на стику зон відповідальності.
Тому після кожного серйозного конфлікту корисно ставити собі не лише запитання «як це змержити», а й «чому він узагалі був можливий». Відповідь — змінити decomposition, оновити CLAUDE.md або уточнити шаблон треку в parallel-session-log.md. І це нормальне зростання процесу. Паралельна робота не зобов’язана бути безконфліктною. Вона зобов’язана бути зрозумілою, ремонтопридатною та достатньо дисциплінованою, щоб будь-який конфлікт перетворювався не на драму, а на акуратне інженерне рішення.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ