ИИ-ассистент говорит, что добавил поиск по названию в список книг. git diff показывает новый импорт и несколько изменённых строк. Реализации поиска в выводе нет, а код не находит книгу, которая есть в каталоге. Прежде чем оценивать патч, выясните, что именно сравнил Git.

Скачайте пример Git diff: приложение с шестью книгами, учебный патч и тесты. Нужны Node.js 22 или новее и Git, без дополнительных зависимостей и доступа к сети.

Выберите два состояния до чтения строк

Git хранит рабочее дерево, индекс с подготовленным снимком и зафиксированные снимки. Команды отвечают на разные вопросы:

Команда Что сравнивает
git diff Индекс → рабочее дерево: неиндексированные правки отслеживаемых файлов
git diff --staged HEAD → индекс: подготовленное для следующего коммита
git diff HEAD HEAD → рабочее дерево: суммарное изменение отслеживаемых файлов, включая индексированные и неиндексированные правки
git diff base HEAD Два зафиксированных снимка base и HEAD
git diff base...HEAD Общий предок → HEAD: изменения ветки head с момента расхождения

Обычные неотслеживаемые файлы отсутствуют в первых трёх сравнениях. Файл может одновременно иметь индексированные и неиндексированные правки. Формы сравнения описаны в справочнике Git diff.

Diff описывает состояния, а не авторство. Если ручные правки смешаны с изменениями ассистента, патч не определит написанные им строки. При необходимости сохраняйте состояние до задачи или ID коммитов. Сравнение веток также требует нужной базы: другая база меняет вопрос.

Найдите файл, пропущенный обычным diff

Распакуйте архив и перейдите в git-diff-reading-lab. Создайте временный учебный репозиторий:

node scripts/create-workspace.mjs review-workspace
cd review-workspace
git status --short --untracked-files=all

Скрипт настройки отказывается использовать существующее назначение. Он коммитит исходный список, помечает коммит base и оставляет предложение незакоммиченным. Вывод воспроизводим без изменения существующего репозитория.

 M src/list.mjs
?? src/search.mjs
?? tests/search.test.mjs

В обычном состоянии без конфликтов первый столбец описывает индекс, второй — рабочее дерево. Начальный пробел в M важен: list.mjs изменён, но не индексирован. ?? обозначает неотслеживаемые файлы. Правила описаны в коротком формате Git status.

Теперь выполните:

git diff --name-status
git diff --stat
git diff -- src/list.mjs

Появляется только src/list.mjs. Отдельно прочитайте src/search.mjs и tests/search.test.mjs в редакторе. На macOS или Linux можно показать новый файл как добавление через git diff --no-index -- /dev/null src/search.mjs; код 1 означает различие файлов. patches/review.patch содержит все три изменённых файла, а обычный diff в этом неотслеживаемом состоянии — нет.

git diff --staged пуст, потому что ничего не индексировано. git diff base...HEAD тоже пуст: предложение не закоммичено, а base ещё равен HEAD. Ни одна команда не рассмотрела рабочее предложение.

Прочитайте полный блок изменений

Часть list.mjs в приложенном патче:

diff --git a/src/list.mjs b/src/list.mjs
index f9ed85e..b551297 100644
--- a/src/list.mjs
+++ b/src/list.mjs
@@ -1,6 +1,8 @@
 import { pageBooks } from "./page.mjs";
+import { searchBooks } from "./search.mjs";
 // Keep the caller's array unchanged.
-export function listBooks(books, { page = 1, pageSize = 2 } = {}) {
+export function listBooks(books, { page = 1, pageSize = 2, query = "" } = {}) {
   const sorted = [...books].sort((a, b) => a.title.localeCompare(b.title, "en"));
-  return pageBooks(sorted, { page, pageSize });
+  const currentPage = pageBooks(sorted, { page, pageSize });
+  return searchBooks(currentPage, query);
 }

Прочитайте заголовки перед функцией:

  • a/ и b/ обозначают старую и новую стороны, а не имена веток.
  • index f9ed85e..b551297 содержит сокращённые ID объектов содержимого файлов, а не двух коммитов. 100644 — неизменённый режим обычного неисполняемого файла.
  • --- обозначает старый файл, +++ — новый. У добавленного файла на старой стороне указан /dev/null, как показывает следующий файл в review.patch.
  • @@ -1,6 +1,8 @@ начинается со старой строки 1 и охватывает 6 старых строк, затем с новой строки 1 и охватывает 8 новых. Это диапазоны, а не количества удалений и добавлений.
  • Внутри блока пробел отмечает сохранённый контекст, - — удалённую строку, + — добавленную. Здесь 4 строки контекста и 2 удаления дают старый диапазон; 4 строки контекста и 4 добавления — новый. Сохранённый комментарий тоже считается контекстом.

Документация формата патча Git объясняет заголовки. Арифметику диапазонов можно проверить по базовому файлу из шести строк и предложению из восьми. Это обычные двусторонние блоки; комбинированный merge diff имеет другой формат.

Проследите новый вызов до входных данных

Требование конкретно: искать по названию во всём каталоге без учёта регистра, затем разбивать найденные книги на страницы. Сохранить порядок названий и не менять вход.

Новая функция фильтрует переданный ей список:

export function searchBooks(books, query) {
  const needle = query.toLowerCase();
  return books.filter((book) => book.title.toLowerCase().includes(needle));
}

Функция может работать правильно, но получать неверный список. Откройте неизменённый src/page.mjs: он обрезает отсортированные книги до одной страницы. В предложенном блоке currentPage уже содержит этот срез при передаче в searchBooks.

При размере страницы два исходная первая страница содержит Amber Atlas и Moon Garden. Star Notes находится дальше. Поиск sTaR на первой странице даёт [], хотя первая страница результатов должна содержать b5. Исправление в patches/fix.patch сначала ищет в sorted, затем передаёт совпадения в pageBooks.

Именно это поведение нужно проверить. Для большого изменения со многими потребителями продолжите с анализом влияния кода: список изменённых файлов не перечислит их всех.

Проверьте, что действительно вызвал успешный тест

Вернитесь из review-workspace в корневую папку примера и выполните команды отдельно:

cd ..
node scripts/run-tests.mjs proposed author
node scripts/run-tests.mjs proposed requirement
node scripts/run-tests.mjs fixed requirement
node scripts/example.mjs

Предложенный авторский тест проходит: он передаёт searchBooks полный массив двух книг и проверяет запрос со смешанным регистром. Он не вызывает listBooks и не проверяет пагинацию перед поиском. Старые утверждения не удалены и не ослаблены.

Три одинаковых требования к публичному списку выполняются для каждой версии. Архив также содержит две неизменённые регрессионные проверки порядка, пагинации и отсутствия мутации:

Версия Существующие регрессионные проверки Новые требования списка Код выхода требований
base: без поиска 2 успешно 0 успешно, 3 сбоя 1
proposed: поиск после пагинации 2 успешно 1 успешно, 2 сбоя 1
fixed: поиск до пагинации 2 успешно 3 успешно 0

Выполните node scripts/run-tests.mjs base requirement или замените версию и набор для других строк. Сбои базы означают отсутствие новой функции, а не регрессию старого списка. Предложение падает на полной странице результатов и совпадении за пределами исходной первой страницы. Оба неуспешных набора реально завершаются кодом 1; && остановит дальнейшие команды.

scripts/example.mjs печатает JSON-объект для каждой версии. В results значение sTaR равно ["b1","b2"] для base, [] для proposed и ["b5"] для fixed. Узкие проверки отвечают на вопрос порядка поиска, но не сертифицируют производственную реализацию. Если патч меняет утверждения, используйте руководство по ревью тестов ИИ.

Почему при пустом diff ещё есть патч для ревью

Изучив три файла, индексируйте их только в учебном репозитории:

cd review-workspace
git add -- src/list.mjs src/search.mjs tests/search.test.mjs
git diff
git diff --staged --stat
git diff --staged

Обычный diff теперь пуст, поскольку рабочие файлы совпадают с индексом. Индексированный diff содержит все три файла, включая новую функцию и тест. Упражнение меняет индекс, но не коммитит предложение. В реальном ревью не индексируйте посторонние файлы лишь ради их появления в diff.

Сравните предложение как две зафиксированные версии

Для примера в просмотре Git diff Yenpo для Mac создайте отдельный учебный репозиторий с обеими закоммиченными версиями. Вернитесь в git-diff-reading-lab — выполните cd .., если ещё в review-workspace, — затем запустите:

node scripts/create-workspace.mjs committed-workspace --committed
cd committed-workspace
git status --short --untracked-files=all
git rev-parse base
git rev-parse proposal
git diff base proposal --name-status
git diff base proposal

Новый режим коммитит исходные файлы как base, копирует и индексирует три правки и создаёт второй коммит с тегом proposal. Он печатает фактические ID обоих коммитов. proposal — HEAD, base — его родитель. Прежний review-workspace не меняется.

Команда статуса не должна ничего выводить. Оба снимка закоммичены, поэтому git diff и git diff --staged тоже пусты. Явное сравнение base → proposal всё равно содержит изменённый src/list.mjs, добавленные src/search.mjs и tests/search.test.mjs. Откройте также неизменённый src/page.mjs: список всё ещё передаёт поиску уже разбитое на страницы подмножество.

Коммиты содержат тот же код, что versions/base и versions/proposed. Наблюдение sTaR остаётся ["b1","b2"] до предложения и [] после; требуется ["b5"]. Коммит сохраняет проблему порядка поиска. Отдельный снимок fixed с успешными проверками остаётся в примере; режим не создаёт коммит fixed.

Настройка принимает только новый дочерний каталог и отклоняет существующие файлы, папки и символические ссылки. Использует явно вымышленную учебную личность и фиксированные даты, игнорирует унаследованные настройки Git и пишет только в новый репозиторий. Глобальная личность Git и другие репозитории не меняются.

Откройте committed-workspace в Yenpo. По двум выведенным ID проверьте старую и новую версии с руководством сравнения Git: старая сторона base, новая proposal. Сравнение относится к этим коммитам. Оно не включает индексированные или неиндексированные правки и не определяет изменения отдельного хода чата ИИ.