Реалізація під контролем: від плану до PR

План затверджено, кроки відомі - і здається, що далі найпростіше. Насправді саме тут найчастіше втрачають контроль: модель охоче робить більше, ніж просили, а бадьоре "готово, все працює" на вигляд нічим не відрізняється від результату, який розвалиться на чужому review.

Розберемо це на одному завданні: від затвердженого плану до відправленого PR. Дорогою - п'ять речей, які зазвичай дізнаються на власних PR:

Сьогодні пройдемо: рівень 18. Controlled implementation, debugging, commits, PR і code review.

Затверджений план ще не результат

План на три кроки виглядає як гарантія. Але "реалізуй план" повертає зміни в шести файлах, і злого наміру тут немає: дорогою знайшовся дубльований код, сусідній метод просив уніфікації, а поруч трапився bug, який гріх не виправити.

Далі тонке місце. Claude не просто пропонує - він пропонує впевнено: у відповіді сказано, що зроблено і чому це правильно, і текст завжди виглядає переконливіше за сам diff. Читати треба diff, а не пояснення до нього - розбіжність між ними і є головним ризиком цього етапу.

Керований цикл повертає контроль однією рамкою: Claude вільний усередині кроку, а не всередині завдання. Тривожне "що він там переробив?" перетворюється на спокійне "цей diff відповідає поточному кроку?".

flowchart TD A["Один крок плану"] --> B["Обмежений запит"] B --> C["Локальні зміни"] C --> D["Прицільна перевірка"] D --> E["Читаємо diff"] E --> F{"Рішення"} F -->|Прийняти| G["Фіксуємо крок"] F -->|Відхилити і повторити| B F -->|Зупинитися| H["Повернення до плану"]

Зворотна стрілка тут одна: відхилений diff повертає до того самого кроку, а не рухає вперед. Зупинка з циклу виводить - це теж рішення, а не збій. Кожне коло закінчується вибором, а не відчуттям "наче нормально".

На однорядковій правці цикл згортається до "правка - diff - commit", і це нормально. Повна форма потрібна там, де заздалегідь не скажеш, яким вийде diff.
Чесно: короткими кроками йти повільніше, ніж одним великим запитом. Різниця з'являється на review і при відкаті - там ви забираєте цей час назад із відсотками.

Запит на крок тримають три обмеження

У дослідженні розпливчастий запит дає розпливчасту відповідь. У реалізації він дає розпливчастий diff - а його потім ще й розгрібати. Тому в запиті на крок завжди є три речі:

І одна умова до запиту: робоче дерево чисте. Інакше правки кроку не відділити від учорашніх, і доводити нічим.

Перший крок плану для issue #731 - відтворити bug тестом, production-код не чіпати. Ось як це виглядає в запиті:

Реалізуй тільки крок 1. До кроку 2 не переходь.
Додай падаючий API-тест: GET /api/deliveries?status=active
не має повертати скасовані доставки.
Змінюй тільки test/api/deliveries.test.ts.
Якщо знадобиться інший файл - спершу назви його і поясни навіщо.
Після цього запусти тільки цей тест і покажи зведення diff.

І що повернулося:

git diff --stat
# test/api/deliveries.test.ts | 14 +++++++++++++
# 1 file changed, 14 insertions(+)

npm test -- deliveries.test.ts -t "cancelled"
# FAIL  excludes cancelled from active list
#   expected 2 deliveries, received 3

Червоний тест тут - хороший знак: крок був саме про відтворення. І якби у відповідь прийшло "мені знадобиться ще ось цей файл, тому що..." - це теж не збій, а саме те, заради чого додали третє обмеження. Ознаку поганого кроку видно раніше: формулювання зачіпає controller, service, repository і принагідно конфіг - отже це не крок, а завдання, яке коротко назвали.

Прицільна перевірка - масштабу кроку, а не всього продукту. Один тест або компіляція одного модуля дають швидкий зворотний зв'язок; повний прогін після кожної дрібної правки вбиває ритм, і перевірки починають пропускати.
Доказ - вивід у вашому терміналі. Модель може повідомити про зелений тест, процитувавши прогін, який був до правки, - формально не збрехавши. Просіть команду разом із виводом, а ключову перевірку проганяйте самі.

Diff вирішує: прийняти, повторити, зупинитися

Зелена перевірка заспокоює, але відповідає вона лише на питання "не розвалилося одразу". Чи відповідає правка кроку плану - показує тільки diff. Читаєте його не на красу коду, а на кілька питань:

Питання до diffЩо захищає
Це все ще поточний крок плану?межі scope
Змінено тільки очікувані файли?від випадкових побічних правок
Чи немає принагідного refactoring?від "корисних бонусів"
Чи можу я пояснити кожен рядок за хвилину?від сліпого прийняття
Я б написав це так само, якби робив руками?від правильного на вигляд рішення не в тому шарі

Перші чотири питання ловлять розповзання. П'яте ловить те, що розповзанням не є: файл потрібний, розмір нормальний, тема збігається - а спосіб обрано чужий. Саме такі правки проходять самоперевірку і розвалюються на review.

Далі - одне з трьох рішень: прийняти і зафіксувати крок, відхилити diff і повторити той самий крок, зупинитися і повернутися до плану. Четвертого варіанта "ну майже нормально, поїхали далі" немає - саме так накопичується хаос, де кожен наступний запит лагодить наслідки попереднього.

А щоб у момент утоми не торгуватися із собою, стоп-правила пишете заздалегідь:

## Stop rules

- змінилися файли поза поточним кроком - не приймати diff
- одна перевірка падає двічі поспіль - припинити правки
- diff не пояснюється за хвилину - не приймати
/goal звільняє від наступного ручного prompt, але не перетворює роботу на детермінований gate. Після кожного turn окрема evaluator-модель визначає на підставі transcript, чи досягнуто condition; сама вона команди не запускає і файли не читає. Тому задайте точну перевірку, заборону змінювати тести, межу turns і вимогу показати вивід команди. Фінальний diff усе одно читаєте ви; для авторизації, платежів і міграцій лишається покроковий режим.

Ваш хід: три diff

Критерії здаються однозначними лише до зустрічі з реальним diff. Ось три результати одного й того самого кроку - відтворити bug тестом. Ззовні сигнал однаковий: тест червоний, expected/actual схожі. Однаковий вивід - ще не однакова причина.

# A  2 files changed, 9 insertions(+)
#    Claude змінив DeliveryController, хоча запит дозволяв тільки test-файл

# B  2 files changed, 18 insertions(+)
#    другий файл Claude назвав до правки, ви перевірили причину і дозволили

# C  1 file changed, 24 insertions(+)
#    тест написано, але в ньому обхід: контракт репозиторію довелося підмінити,
#    інакше відтворити bug на цьому рівні неможливо

Що ви робите в кожному випадку - приймаєте, відхиляєте diff і повторюєте крок чи зупиняєтесь і повертаєтесь до плану? Вирішуйте до того, як перегорнемо сторінку.


Розбір: чому рішення різні

ВипадокРішенняЧому
Aповторитиобмеження вже було явним, але Claude його порушив: diff відхиляємо, файли повертаємо і повторюємо крок
Bприйнятирозширення зони пройшло через вас: названо до правки, причину перевірено, дозвіл дано
Cзупинитисятест падає через підмінений контракт, а не через bug: на цьому рівні bug не відтворюється чесно

Випадок B - єдиний, де "пояснив" перетворюється на "можна". Якби Claude назвав файл і тут же його виправив, рішення було б іншим: пояснення моделі не замінює вашого дозволу, інакше обмеження не працює.

А між A і C проходить межа, яку легко пропустити, бо обидва diff ви не приймаєте. У A план і крок так само правильні, порушено виконання: повертаєте файли і повторюєте той самий крок. Зупиняєтесь, коли робота розкрила знання, що ламає сам план: у C обраний рівень тесту не відтворює bug чесно, і повертатися треба до декомпозиції.


Червоний тест - це ще не діагноз

Другий крок плану - передати status у repository query. Правка на один рядок, цільовий тест позеленів. І тут же почервонів сусідній.

npm test -- deliveries.test.ts
# PASS  excludes cancelled from active list
# FAIL  returns delayed deliveries in active list
#   expected 1 delivery, received 0

Зауважте, чому цільовий тест зелений: список став порожнім, а в порожньому списку скасованої доставки справді немає. Класичний хибний зелений - перевірка формально пройшла, поведінку зламано.

Цієї миті найпростіше написати "полагоди". Отримаєте менш галасливу помилку і нуль розуміння, а bug повернеться пізніше і в іншому місці. Тому спершу розводимо рівні, які легко склеїти в один:

РівеньПитанняУ нашому випадку
Симптомщо видно ззовні?Active-список порожній: expected 1, received 0
Гіпотезащо могло це спричинити?"active" - не значення поля status, а група статусів
Коренева причинащо підтверджено?поки нічого: гіпотезу ще не перевіряли

Зауважте, куди вказує червоний: упав тест про delayed, хоча чіпали ми фільтрацію active. Місце падіння і місце причини розійшлися на один шар - і якщо лагодити там, де почервоніло, правка поїде в тест або в сусідній сценарій, а причина залишиться.

Перша гіпотеза - це майже завжди просто здогад, і частина з них не підтвердиться. Це не провал: ви перевіряєте її і спокійно відкидаєте, якщо не зійшлося.
Реалізація закінчилася і почалося налагодження - це різні заняття. У реалізації ви змінюєте код, у налагодженні спершу пояснюєте собі, чому він ламається.

Докази збирають до першого питання

"У мене не працює, розберись" - і Claude починає будувати здогади з повітря. А повітря дороге, особливо коли модель уже править файли.

Пакет доказів збирають не для повноти, а для напрямку - і в цьому ж його ризик. Доклали diff - гіпотези зберуться навколо змінених рядків. Назвали підозрілий файл - навколо нього. Тому в пакет ідуть факти: вивід перевірки, що впала, команда відтворення, останній diff. Здогад "як на мене, річ у сервісі" в пакет не йде - він повернеться ранжованим списком гіпотез, кожна з яких буде про сервіс.

І сам запит відрізняється від звичайного однією деталлю - забороною на правки:

Розберись, чому падає тест. Файли не змінюй.
Вхід: вивід тесту вище, останній diff у DeliveryService.ts,
fixtures у test/fixtures/deliveries.json.
Поверни: гіпотези за спаданням імовірності, докази до кожної,
мінімальну перевірку для гіпотези 1
і те, що поки лишається невідомим.
flowchart TD A["Перевірка впала"] --> B["Зібрали докази"] B --> C["Запит без правок"] C --> D["Гіпотези і докази"] D --> E["Мінімальна перевірка"] E -->|Підтверджено| F["Записали причину"] E -->|Не підтверджено| D

Гіпотеза, яка не підтвердилася, повертає вас не на початок, а до списку гіпотез - докази і відтворення залишаються. І зауважте, чого у схемі немає: кроку "змінити три файли і подивитися, чи стало краще". Діагностична сесія має бути нуднішою, ніж сесія реалізації.

Мінімальна підтверджувальна перевірка не зобов'язана бути красивою, вона зобов'язана бути маленькою:

rg -o '"status": "[a-z_]+"' test/fixtures/deliveries.json | sort -u
# "status": "cancelled"
# "status": "delayed"
# "status": "in_transit"
# "status": "pending"

Значення "active" у даних немає взагалі. Гіпотеза зі здогаду перетворилася на підтверджений факт, і на це пішло менше хвилини.


Patch loop упізнається за однією ознакою

Зовні ви все ще лагодите bug. За фактом - додаєте правку за правкою, а розуміння не додається. Ознака проста: лагодимо двадцять хвилин, а причину однією фразою сформулювати не можемо.

Здорове налагодження тримається на одній провідній гіпотезі, відтворенні і diff, що майже не росте. У петлі навпаки: правки випадкові, diff розповзається по сусідніх файлах, а замість доказу - "наче має допомогти".

Робочий лічильник: дві спроби на одну гіпотезу - норма. Третя, якщо нової інформації не з'явилося, - це вже не налагодження, а надія.

git restore і git stash повертають робоче дерево, але не розмову. У /rewind інший вибір: повернути code, conversation або обидва стани до checkpoint. Межа важлива: rewind відстежує прямі edits Claude, але не зміни з Bash-команд і зовнішніх процесів, тому Git лишається джерелом істини для дерева.

Якщо файли повернули через Git, відкинута гіпотеза все ще звучить у session context. Тоді або відкотіть conversation через /rewind, або почніть чисту сесію з компактним пакетом доказів, або прямо зафіксуйте, що гіпотеза не підтвердилася, і додайте доказ.

git diff --stat
git restore src/server/DeliveryService.ts

Коли провідну гіпотезу підтверджено, причину записуєте явно - до того, як почнете лагодити:

## Root cause

`DeliveryService` передає "active" у repository як значення поля status,
хоча такого статусу в даних немає: Active - це група pending, in_transit, delayed.
Точне порівняння повернуло порожній список.

## Confirmation

- fixtures містять тільки pending, in_transit, delayed, cancelled
- після правки падає тест "returns delayed deliveries in active list"

Такий запис можна передати іншій сесії, показати колезі і через годину не згадувати заново, "що ми там наче зрозуміли".


Спершу падаючий тест, потім фікс

Порядок "тест раніше за фікс" захищає не від поганого фіксу, а від доброго фіксу не тієї проблеми. Згадайте хибний зелений: список став порожнім, і перевірка на скасовані пройшла. Напиши ми тест після правки - він закріпив би саме цей стан і залишився б зеленим назавжди, бо писався б під уже зламану поведінку.

Поки тест не падав на старій поведінці, у вас немає точки відліку: він доводить лише те, що новий код узгоджений сам із собою. Узгодженість буває і в помилки.

Промацати фікс руками і лише потім написати тест - живий і нормальний шлях. Важливо одне: переконатися, що тест падає на старій поведінці. Для цього досить тимчасово прибрати правку.
flowchart LR A["Відтворили"] --> B["Записали причину"] B --> C["Падаючий тест"] C --> D["Мінімальний fix"] D --> E["Прицільні перевірки"] E --> F["Сусідні перевірки"] F --> G["Прочитали diff"]

Ланцюжок іде зліва направо і закінчується не зеленим тестом, а читанням diff: зелений сигнал говорить про поведінку, diff - про те, якою ціною її отримано. Сам фікс б'є саме по знайденій причині:

// Active - це група статусів, а не значення поля status
const ACTIVE_STATUSES = ["pending", "in_transit", "delayed"];

class DeliveryService {
  list(status?: string) {
    if (status === "active") {
      return this.repo.findByStatuses(ACTIVE_STATUSES);
    }
    return this.repo.findAll();
  }
}

Він хороший не геніальністю, а тим, що його легко пояснити: контракт endpoint не змінювався, сусідні маршрути не зачеплені, нових залежностей немає.

npm test -- deliveries.test.ts
# PASS  excludes cancelled from active list
# PASS  returns delayed deliveries in active list
# Tests: 5 passed, 5 total
Слово "красивіше" в завданні на bugfix працює як запрошення розповзтися за межі завдання. Забороняйте прямо: не виносити спільну валідацію, не чіпати сусідні endpoint, не додавати залежності.

Одного зеленого тесту мало

Тест позеленів - і хочеться закрити завдання. Але reviewer спитає не "що стало зеленим", а "чому цьому можна вірити". Переконливим виправлення роблять кілька незалежних ознак:

ДоказЩо підтверджуєУ нашому PR
red з очікуваної причинитест відтворює вихідну поведінкуexcludes cancelled отримав 3 доставки через cancelled
той самий сценарій red → greenфікс змінив відтворену поведінкуcancelled більше не повертається
сусідні перевіркипокриті сценарії поруч збереглися5 тестів файлу зелені
повний прогінпокриті сценарії поза файлом збереглисяувесь набір один раз перед відправленням
diff звірено з планомscope не розповзсятільки test і service, без принагідних правок

Прицільні перевірки економлять час усередині циклу, але перед відправленням набір проганяєте цілком один раз - інакше поломка в сусідньому модулі спливе вже у reviewer.

І ще тонкість. Сесія, яка писала фікс, збирає докази з пам'яті про власну роботу, а не з repository: перелічить перевірки, які збиралася запустити, тести, які вважає дотичними до справи, файли, які пам'ятає зміненими. Виходить правдоподібно і майже правда - найгірший вид звіту, бо очима його ніхто не перевіряє. Тому тест, фікс і збір доказів просіть окремими запитами, а в останньому вимагайте факти і забороняйте оцінки:

Збери докази, що bug закрито: який тест падав до фіксу,
що змінилося після, які додаткові перевірки запускалися,
які файли змінено.
Тільки факти. Якість рішення не оцінюй.
Для маленького виправлення це не окремий документ, а п'ять рядків, які потім переїжджають в опис PR.

Commit - точка відкату, а не збереження

Review-ready commit має бути таким, що його легко пояснити, і залишатися робочим після checkout чи revert. Regression-тест і фікс тут змінюють один контракт і відкочуються разом. Червоний test commit показує послідовність TDD, але не дає deployable-точку: без наступного commit CI залишиться червоним.

git add test/api/deliveries.test.ts src/server/DeliveryService.ts
git commit -m "fix(deliveries): filter active list by status group"

git show --stat --oneline HEAD
# 4c9f21a fix(deliveries): filter active list by status group
# 2 files changed, 20 insertions(+), 1 deletion(-)

Один рядок історії зберігає і доказ regression, і виправлення, тому лишається зеленим в обидва боки історії. Якщо команда зберігає TDD-етапи і допускає червоні проміжні commits, test і fix можна залишити окремо. Інакше локальні WIP/fixup-точки перед PR об'єднують.

Файли додано поіменно, і це не педантизм: git add . в AI-сесії затягує все, що модель зачепила дорогою, - від форматування в сусідньому файлі до незапланованого конфіга.

Повідомлення Claude запропонує охоче, але джерело правди - diff:

Подивись поточний diff і запропонуй один commit message.
Не вигадуй змін, яких немає.
Якщо бачиш файли поза поточним кроком плану - спершу перелічи їх окремо.
Якщо формат повідомлень у команді усталився, його місце - у CLAUDE.md, щоб стиль не винаходився заново в кожному завданні. А attribution Claude для commits і PR налаштовується окремо - секцією attribution в settings.json.

PR - запакована розповідь, а не набір commits

Reviewer відкриває ваш PR між двома зустрічами. Він не починає з git log і не хоче бути археологом - йому треба швидко зрозуміти, що змінилося, навіщо, як це перевірити і де межі завдання.

Після діагностики крок 3 про consumers виявився окремим результатом. Tech lead погодив розбиття: поточний PR закриває server-контракт issue #731, а перевірка consumer-поведінки йде у follow-up #742. Без такого рішення запис у Out of scope лише ховає незавершений крок затвердженого плану.

# PR walkthrough - issue #731

## What changed
- `DeliveryService.list`: запит зі status=active фільтрує за групою статусів
- `deliveries.test.ts`: regression-тест на скасовану доставку в Active

## Why
Service ігнорував status і кликав findAll(), тому скасована доставка
поверталася в Active після оновлення сторінки.

## Verification performed
- `npm test -- deliveries.test.ts` → 5 passed
- `npm test -- mobile-deliveries.contract.test.ts` → 3 passed
- `curl -s http://localhost:3000/api/deliveries?status=active`
  → pending, in_transit, delayed; cancelled відсутній

## Risks
Mobile використовує той самий endpoint; contract-тест зелений, форма відповіді не змінювалася.
UI-level regression-тест лишається у follow-up #742.

## Out of scope
- фільтр у frontend-компоненті
- поведінка при невідомому значенні status
- consumer-аудит: погоджений follow-up #742

Пишеться цей опис не тільки для reviewer. Це останній дешевий момент упіймати розбіжність: якщо, заповнюючи "що змінилося", ви знаходите пункт, якого не було в плані, - знахідка, а не завада. Пізніше вона обійдеться дорожче.

Verification performed - це evidence автора: тільки реально запущені команди і результат, який спостерігали. Інструкції для reviewer живуть окремо у How to verify; одне не підміняє інше. А Out of scope показує погоджені межі. PR розрізають не за довжиною опису, а коли всередині два результати, які можна незалежно перевірити і відкотити.

Файл - це чернетка, сам PR створюється з неї. gh тут - консольний клієнт GitHub:

git push -u origin bugfix/issue-731-cancelled-in-active

gh pr create --title "Не показувати скасовані доставки в Active" \
             --body-file PR_DESCRIPTION.md
# https://github.com/parcelflow/parcelflow/pull/812

Заголовок описує результат, а не реалізацію: "Не показувати скасовані доставки в Active" корисніше reviewer, ніж "змінити фільтр у DeliveryService".

Якщо опис каже одне, а diff показує інше, довіра до PR падає миттєво. Чернетку пише Claude - фінальну звірку з git diff і git log робите ви.

Один diff, кілька проходів

Локальні тести зелені, опис зібрано - і хочеться швидше натиснути "створити PR". Саме тут найдешевше впіймати зайвий файл, випадковий rename і правку "заразом".

Локальне review - це кілька проходів по одному й тому самому diff, поки його не побачив хтось інший.

flowchart TD A["Approved plan і diff"] --> B["Self-review"] B --> C["Claude-assisted review"] C --> D["Fresh context"] D --> E["Review notes"] E --> F["Рішення людини"]

Схема читається згори вниз як маршрут однієї зміни: кожен прохід додає знахідки, але жоден із них не виносить вердикт замість вас. Опціональні прискорювачі на схему не винесені - їхнє місце між свіжим поглядом і нотатками, коли diff того вартий.

ПрохідХто дивитьсяЩо дає
Self-reviewви як авторзвірка diff з планом і сенсом завдання
Claude-assistedпоточна сесіяшвидкий список знахідок за scope і тестами
Fresh contextінша сесія або reviewer-агентпогляд без історії вашої сесії
Плагін або хмарне reviewопціонально, за розміром і ризиком diffще один автоматичний прожектор
Рішеннявиgo або no-go

Порядок тут не декоративний, і річ не лише у відповідальності. Сесія, яка писала код, бачила, як він народжувався, і поділяє ваші передумови: вона перевірить, чи добре реалізовано задум, але не спитає, чи вірний сам задум. Це робить fresh context не розкішшю, а єдиним проходом, де задум узагалі перевіряється.

І про міру: для правки на два рядки чесного self-review цілком достатньо - решту підключаєте, коли diff багатошаровий, зачіпає чужий код або ви в цьому модулі вперше. А якщо пропустити self-review і одразу спитати модель "ну як там?", ви тихо призначаєте Claude заступником з відповідальності. Кадрове рішення так собі.

Коли горить, першим відвалюється свіжий погляд - і це нормально. Не ріжеться ніколи інше: читання власного diff і перевірка, яка доводить, що ви полагодили саме те, що горіло.

Спершу ви, потім Claude

Diff ви читаєте звичними командами - git diff --stat, по файлах, git status, повний прогін перед відправленням. Цікавіше те, чого у звичайному self-review не шукають, а в AI-diff шукати доводиться завжди:

Фінальна самоперевірка звучить так: якби цей PR відкрив колега, чи зрозуміли б ви за diff, що сталося і навіщо?

Тільки після цього підключається Claude - і не вільно, а з фокусом. Без списку зон уваги відповідь передбачувана: "загалом добре, можна покращити читабельність". Фокус потрібен не моделі, а вам - він перетворює думку на перевірюваний список, де по кожному пункту видно, згодні ви чи ні:

Перевір поточний diff за затвердженим планом.
Дивись тільки на: розповзання scope, відсутні тести, крайні випадки,
зворотну сумісність, ризиковані зміни валідації.
Згрупуй знахідки як blocker / major / minor і вказуй file:line.
Файли не редагуй.
ЗнахідкаРівеньРішення
для mobile UI немає regression-сценарію, але API contract-тест зеленийminorfollow-up #742; server-fix не блокує
ім'я тесту варто уточнитиminorприйнято
додати логування в serviceminorвідхилено, поза scope

Відхилені зауваження фіксуються нарівні з прийнятими: Claude звучить однаково впевнено і коли має рацію, і коли пропонує вам роботу із сусіднього PR.

Команда /diff відкриває інтерактивний перегляд uncommitted changes і правок за окремими turns, а готовий skill /code-review розбирає поточний diff на обраному рівні глибини. Інструмент допомагає помітити - рішення все одно за людиною.

Фінальне рішення лишається за вами

Після двох годин у завданні ви бачите в diff не те, що написано, а те, що хотіли написати. Свіжа сесія цього багажу не має - вона дивиться на PR приблизно як майбутній reviewer, і дають їй тільки артефакти: план, опис PR, diff і список перевірок.

Fresh context звіряє обіцянки з evidence: ручний GET прогнано, mobile-контракт покрито тестом, перенесення consumer-кроку прив'язане до погодженого follow-up. Результат "blocker не знайдено" цінний лише разом із цими підставами.

# REVIEW_NOTES.md - issue #731

## Self-review
- diff збігається з переузгодженим планом, змінено два файли

## Claude-assisted
- minor: немає UI-level regression-тесту mobile; API contract зелений - follow-up #742
- minor: логування - відхилено, поза scope

## Fresh context
- команди і результати, які спостерігали, записано у Verification performed
- перенесення consumer-кроку підтверджено рішенням tech lead і follow-up #742

## Final decision
PR готовий до зовнішнього review. Mobile follow-up не блокує server-fix:
contract-тест зелений, форма відповіді не змінювалася, розбиття погоджено.

Розумних результатів кілька: PR готовий; потрібна ще одна невелика правка; PR краще розбити надвоє, бо в нього затесалася зайва ідея; review повертає в налагодження, бо причину описано слабко. Ненормальний тільки п'ятий - "нічого не зрозумів, але відправлю так".

Критерій фіналу: назвіть, що саме перевірить reviewer і на що подивиться першим. Не виходить - PR не готовий. А розділяти його треба не за розміром, а коли результати можна незалежно перевірити і відкотити.

І про обсяг: за сьогодні прозвучали чотири записи - причина, докази, опис PR, нотатки review. Це чотири факти, а не чотири файли: для правки на два файли вони цілком уміщаються в опис PR. Окремі документи з'являються, коли завдання довше за день або його підхоплює хтось інший.

Коли diff великий або зачіпає одразу кілька шарів, свіжий погляд виносять у хмару командою /code-review ultra. Там на один diff паралельно дивляться кілька спеціалізованих агентів, а знахідки приїжджають одним звітом. Не для маленьких PR, не для термінових виправлень і не там, де код не можна вивантажувати назовні.