Imported from MykhailoDmytriakha/my-preacher-helper (
.claude/skills/bugs-fix/SKILL.md). Install upstream withnpx skills add MykhailoDmytriakha/my-preacher-helper --skill bugs-fix. Copyright stays with the author.
bugs — жизненный цикл бага
Скилл держит порядок и границы ролей: чтобы находка не терялась, закрытие не объявлялось раньше доказательства, а вопрос про баги не превращался в правку трекера.
Кто пользуется этим приложением — читать ПЕРВЫМ
Без этого разбор уезжает в инженерные дебри: агент начинает решать задачу про распределённые системы там, где живой человек просто дописывает мысль к проповеди.
Пользователь — проповедник. Один человек, один аккаунт, несколько устройств — но по очереди, а не одновременно: поработал на телефоне, потом сел за компьютер, потом вернулся к телефону. Совместного редактирования вдвоём здесь не бывает: два человека одну проповедь одновременно не пишут.
Что из этого следует для разбора любого бага:
- «Одновременная работа» — не основной сценарий, а край. Механизмы свежести, блокировок и разрешения конфликтов нужны не для гонок между людьми, а для последовательного переключения устройств одним человеком. Проектировать защиту от параллельной войны двух редакторов — значит решать не ту задачу.
- Своё изменение никогда не «чужое». Всё, что человек только что сделал в этой сессии, для него — своё, даже если технически оно прошло через сервер и вернулось обратно.
- Цена ошибки меряется в его работе, а не в тактах. «Потерял абзац, который надиктовал» — тяжело. «Лишний запрос к базе» — не его беда, пока он этого не чувствует.
- Он в середине служения, а не за отладкой. Сообщение, которое требует от него разбираться в устройстве системы, — само по себе дефект.
⚠️ Признак, что разбор ушёл не туда: ты обсуждаешь производительность, консистентность и гонки, а сказать, что именно почувствует проповедник, не можешь. Вернись и скажи это первым.
Две оси
ОСЬ 1 · РОЛЬ ЗАПУСКА ОСЬ 2 · ГДЕ ЛЕЖИТ ЗАПИСЬ
STATUS читает и отвечает подозрение → «🔍 Требует перепроверки»
QA ищет, воспроизводит, пишет открыт → «🔴 P1 / P2 — открыто»
FIX чинит и закрывает в работе → НЕ двигается; прогресс в журнале
TEST покрывает регрессиями закрыт → записи в BUGS.md больше нет
TRIAGE наводит порядок
Роль задаётся входом, а не прогрессом бага: иначе тот, кто воспроизвёл, тут же идёт чинить — и диагноз подгоняется под уже придуманный фикс.
Что разрешено каждому режиму
Действие, которого нет в строке, в этом режиме не делается.
| режим | production-код | тестовый код | тесты гоняет | BUGS.md |
закрывает |
|---|---|---|---|---|---|
| STATUS | нет | нет | нет | только читает | нет |
| QA | нет | только временный reproducer, не коммитится | да, чтобы воспроизвести | добавляет, уточняет | нет |
| FIX | да | да | да, регрессия обязательна | правит, удаляет при закрытии | да |
| TEST | нет | да | да | дописывает доказательство | нет |
| TRIAGE | нет | нет | только проверить, жив ли баг | нормализует, сливает дубли, двигает | только административно (ниже) |
Выбор режима
| он говорит | режим |
|---|---|
| «что у нас по багам», «что сломано», «сколько открытых» | STATUS |
| «найди баги», «протестируй X», «проверь, работает ли», «поищи регрессии» | QA |
| «почини», «исправь», «займись P1» — явный глагол изменения | FIX |
| «покрой тестами», «напиши регрессию» | TEST |
| «наведи порядок», «разбери трекер», «есть дубли?», «пересмотри severity» | TRIAGE |
⚠️ Неоднозначно — по умолчанию STATUS. Фразы вроде «по фиксам багов» читаются двояко: и «покажи, что там по фиксам», и «займись фиксами». Ответь как STATUS и спроси одной строкой, браться ли. Вопрос про баги не должен молча превращаться в правку трекера.
⚠️ Диагностика — это не FIX. «Разберись, почему падает», «что тут не так», «покопай» просят понять, а не менять: это QA (или STATUS, если даже трогать ничего не надо). В FIX переходят по явному глаголу изменения либо по прямому «да» человека.
«Найди и почини» — два прохода, а не один. Сначала QA до записи включительно, затем явная передача: «QA закончил, найдено столько-то; перехожу в FIX по такому-то». Разделение сохраняется, а человек получает весь цикл за один заход, а не половину.
Режим объявляется вслух первой строкой: режим QA — ищу и записываю, не чиню. Смена — тоже вслух, с причиной.
Сверка с протоколом — такт, а не разовое чтение
⚠️ Скилл прочитан один раз — этого хватает на первые двадцать минут. Дальше начинается дрейф: работа идёт из последней реплики, шаги пропускаются по одному, и со стороны это неотличимо от работы по порядку. Лечится не силой воли, а вопросом себе вслух в трёх местах.
1. На входе в КАЖДУЮ новую единицу работы. Единица — это любой отдельный предмет: баг, замечание, просьба доработать, найденная по ходу поломка. Начиная её, скажи вслух три вещи: что беру · в каком режиме · где это записано. Не записано — запиши прежде, чем трогать код: находка, оставшаяся в разговоре, исчезает вместе с ним. ⚠️ Признак пропуска: ты уже открыл файл, а записи о том, что чинишь, ещё нет.
2. По ходу, примерно каждые десять действий. Один вопрос: какой шаг протокола я сейчас пропускаю? Не «всё ли хорошо» — на такой вопрос ответ всегда «да», — а именно «что пропущено». Типовые ответы: не завёл запись · не снял замер «до» · не прочесал класс · чиню, не воспроизведя · тест написан после кода и мутацией не проверен.
3. На закрытии. Пройди список вслух и назови, что сделано и что нет: воспроизведение · запись · корень доказан · класс прочёсан · красный тест был красным по правильной причине · гейты · чистый код · живая проверка в модальности болезни · артефакты, чья посылка изменилась. Пропущенное называется пропущенным, а не замалчивается.
И перечитывай сам скилл физически, когда: работа идёт больше часа · сменилась единица работы · ты поймал себя на «кажется, тут можно короче». Память о правиле и правило расходятся молча.
Журнал дела открывается ПЕРВЫМ
Любой режим кроме STATUS начинается с el — она печатает, где дело стоит, что дальше и что не на месте (CLAUDE.md §3). Дальше по ходу в журнал дела пишется командой el log: режим · ID багов в работе · что проверено и что не проверено · доказательства · следующий шаг.
Без этого прогресс живёт только в голове и умирает вместе с сессией — а следующий заход начинает с нуля. Одно дело обслуживает несколько багов; отдельное дело на баг не заводится.
⚠️ Единственная дверь записи — el. README / TODO / JOURNAL внутри дела руками не трогай: ручная правка ловится отпечатком.
⚠️ .cases/ не отслеживается git (строка .cases в .gitignore). Это локальная память, а не архив: в другом клоне её нет. Поэтому долговременный след закрытого бага живёт в сообщении коммита, который удаляет запись (см. «Закрытие»).
STATUS — только ответить
Прочитай BUGS.md, посчитай по секциям, ответь: сколько открытых по severity · что выглядит самым дорогим и почему · что изменилось, если видно. Ничего не правь. Заметил дубль или устаревшую запись — скажи, но не чини: это TRIAGE, и на него нужен отдельный заход.
Стенд для ручной проверки — поднимается ДО всего
Выведено из живого прогона 2026-08-09; без этого раздела каждый заход начинается с одних и тех же граблей.
- Сначала посмотри, не поднят ли уже чужой сервер:
ps aux | grep "next dev" | grep -v grepиlsof -nP -iTCP -sTCP:LISTEN | grep :300. Два сервера на разных портах дают «страница не грузится» там, где код цел. - Бери ту конфигурацию, где баг наблюдали, а не самую чистую. Прод работает с service worker → воспроизводить надо
npm run dev(SW включён).npm run dev:no-pwa— это второй замер: если баг есть и там, service worker невиновен, и это уже улика, а не просто «чисто». Сравнение двух конфигураций стоит одного перезапуска и часто закрывает половину гипотез. - Дождись
✓ Readyв логе сервера, а не первого скриншота: до этого страница показывает спиннер, и его легко принять за зависание. - Первая компиляция маршрута идёт секундами —
Compiled / in 3.4sв логе. Пустой экран в этот момент — норма, а не симптом. - Замер «до» снимается ДО первого действия и записывается числом: сколько элементов на экране, есть ли плашка, что в счётчике. Без него «стало хуже» доказать нечем.
- Окно браузера может сменить размер между шагами — координаты кнопок уезжают. Перед кликом по той же кнопке во второй раз сделай свежий скриншот, иначе клик уйдёт в пустоту и это будет выглядеть как «кнопка не работает».
QA — находит и записывает, не чинит
- Тест-план по рискам, а не по списку. Возьми классы риска, относящиеся к этой области (карта рисков ниже), и назови вслух, какие берёшь. Универсальный чек-лист на каждый косметический баг — церемония, из-за которой скилл начнут обходить.
- Прогон. «Запустил, оно открылось» проверкой не считается. Ручная проверка идёт под dev-пользователем
testuser@example.com(кнопка на лендинге в dev-режиме,CLAUDE.md§3) — не гостем и никогда не на проде. - Воспроизведение. Повтори своими руками ещё раз. ⚠️ Не повторилось — это не «не баг». Для недетерминированного (гонка, таймаут, сеть, порядок вкладок) запиши частоту: «1 из 5». Отсутствие второго срабатывания снижает уверенность, но не отменяет наблюдённый факт. Место такой записи — «🔍 Требует перепроверки» с указанием частоты и исходного доказательства. ⚠️ Баг, сообщённый владельцем, — не догадка. Он видел это своими глазами, и «Требует перепроверки» здесь не место: там живёт противоречивое, а не «я ещё не успел проверить». Такая запись идёт в свою severity-секцию сразу, но с honest-пометкой в поле «Найден»: «воспроизведение — со слов владельца, агентом не проверялось», и с названным замером, который надо снять перед фиксом. Пометка снимается, когда прогнал сам.
- Снимай ЖИЗНЕННЫЙ ЦИКЛ симптома, а не только его появление. Один вопрос «появилось?» даёт одну строку; четыре вопроса дают спецификацию, по которой потом пишется фикс:
- когда появляется — после какого именно действия, сразу или с задержкой;
- уходит ли сам — или висит, пока не тронешь;
- что делает кнопка на нём — и не теряются ли при этом данные;
- возвращается ли после того, как его убрали, на следующем таком же действии. Разница цены нулевая, а различать «показалось один раз» и «повторяется каждый раз» без этого нельзя.
- Урежь условия до минимальных. Владелец описывает сценарий так, как он у него случился, — но часть шагов может быть лишней. Убирай по одному и смотри, воспроизводится ли: сценарий «iPad → компьютер → запись» на проверке свёлся к «одна своя запись в одной вкладке». Это меняет и severity, и место поиска корня.
- Reproducer: исходное состояние → действие → что видно. Плюс стенд: dev или prod, ветка, браузер, данные, флаги, включён ли service worker.
- Карта класса — СОБИРАЕТСЯ ЗДЕСЬ, пока механизм перед глазами. Воспроизведение — единственный момент, когда видно, как дефект устроен; после него внимание уходит в правку, и прочёс превращается в «ну, вроде остальное нормально». Назови механизм словами, найди
grep-ом все места, где он живёт (клиент и сервер и офлайн-путь), и по каждому вынеси вердикт. ⚠️ Это работа QA, а не FIX. Тот, кто уже придумал фикс, ищет подтверждение своему фиксу, а не границы болезни. - Запись в
BUGS.mdпо форме ниже — вместе с картой класса. Её место именно в записи, а не в журнале:.cases/не в git и умирает вместе с сессией, а карта нужна как чек-лист закрытия — пока в ней есть непочиненные строки, запись не удаляется. Это и есть проверка «везде ли закончил», и она работает даже если чинить будет другой заход или другой агент. - Стоп. Причину не ищем; гипотеза допустима одной строкой и помечается гипотезой.
Думать до кристаллизации — прежде чем звать человека
⚠️ Самый частый провал этого скилла — не в поиске и не в починке, а здесь: агент доходит до «есть два варианта» и несёт развилку человеку, не додумав. Это выглядит как уважение к его решению, а на деле — переложенная работа.
Кристаллизация — это состояние, когда стало ясно не только ЧТО делать, но и ПОЧЕМУ остальные ходы не годятся. Проверяется одним признаком: можешь ли ты отвергнуть каждый альтернативный вариант его собственной ценой — что он не чинит, какую сущность добавляет, какой новый класс ошибок вносит. Отвергаешь «на вкус» или «мне кажется» — не выкристаллизовалось.
Кристаллизация не приходит от усилия воли, она приходит от работы инструментами думания. Возьми те, что подходят задаче:
- идеальный конечный результат — как выглядело бы, если бы нужное происходило само, без новых частей и затрат? Иди от идеала назад, а не от текущего вперёд;
- противоречие — сформулируй «чтобы X, нужно A; но A ломает Y» и разреши, а не торгуйся посередине. Часто разрешение звучит как «а нужно ли вообще A?»;
- картография и локализация — где именно живёт болезнь: слой данных, слой сравнения, слой отображения? Пока не локализовал, любой фикс — угадывание;
- узкое горлышко и рычаг — какая одна правка снимает больше всего симптомов;
- отсечение — что можно убрать, чтобы проблема исчезла. Решение, которое только добавляет, обычно обстройка, а не решение.
Итог думания записывается в запись бага, в раздел «Фикс (направление)»: выбранный ход · почему именно он · и чем отсечён каждый отвергнутый. Отвергнутое без причины возвращается на следующем круге.
К человеку идёшь в одном случае — НЕ выкристаллизовалось. Тогда несёшь не список вариантов, а что именно не сходится: какого факта не хватает, какие два требования конфликтуют, где ты не можешь оценить цену. Выкристаллизовалось — работай, не спрашивая.
FIX — чинит и закрывает
⚠️ Код и всё вокруг него — по-английски. Разговор идёт по-русски, но комментарии, имена, названия тестов (describe/it) и сообщения коммитов пишутся на английском (CLAUDE.md §1). Русский остаётся только в пользовательских строках frontend/locales/. Легко забыть именно при фиксе: думаешь по-русски и так же пишешь комментарий.
⚠️ После фикса остаётся ЧИСТЫЙ код. Починка, которая оставляет за собой костыль, дублирование или «пока так», порождает следующий баг — и он придёт как новый, без связи с этим. Чинить по лучшим практикам, с рефакторингом затронутого места, а не заплаткой поверх заплатки. Если чистого пути не видно — это сигнал, что корень назван неверно, а не повод положить костыль.
⚠️ Тест здесь — не бюрократия, а гарантия невозврата. Баг случился один раз — значит место хрупкое. Тест фиксирует: это больше не повторится, и ты не уронишь это случайно следующей правкой. Один встреченный баг = один тест, который его сторожит.
-
ПЕРЕЧИТАЙ ЭТОТ ФАЙЛ ЦЕЛИКОМ — до первой правки кода, каждый раз. Не «помню по прошлому разу»: помнится общий смысл, а работают детали — какой шаг обязателен, что считается доказательством, чего нельзя объявлять готовым. Идти по памяти значит идти вольно, и вольный проход выглядит изнутри точно так же, как строгий: разница видна только на выходе, когда чего-то не хватает. Скажи вслух, что прочитал. Особенно это касается шага «прочеши класс»: именно его пропускают, когда торопятся к правке. ⚠️ Поймано живьём 2026-08-10: агент починил четыре экрана плана и назвал это закрытием, хотя тот же механизм — переписывание целого поля из устаревшего снимка — жил ещё и на сервере (каскад удаления тега). Правило про класс было в скилле с самого начала; его не нарушили сознательно, его просто не перечитали.
-
Чистое дерево.
git statusдо начала. Есть чужие незакоммиченные правки — работай только в своих файлах и никогда не откатывай чужое. -
Перечитай запись. Нет якоря
file:line— сначала найди место. -
Воспроизведи отказ сам, старым путём. Не воспроизводится — стоп: возможно, починено соседней правкой; это административное закрытие через TRIAGE, а не фикс.
-
Красный тест — до фикса (раздел «Тест идёт до фикса»).
-
Найди корень. Проверка одна: трогаешь корень — симптом появляется и исчезает предсказуемо. Не управляется — не корень.
-
ПРОЧЕШИ КЛАСС — обязательный шаг, а не добрая воля. Найденный баг почти никогда не единственный: он образец, а не граница работы. Тот же приём написан в соседних местах теми же руками и по той же привычке.
- Назови класс словами, через механизм, а не через симптом: не «плашка выскакивает на проповеди», а «свежесть сравнивает массив порядко-зависимым отпечатком, пока писатель и экран строят разный порядок».
- Найди все места, где живёт этот механизм — по имени функции, по импорту, по конструкции.
grepпо имени, а не по симптому: симптом в других местах выглядит иначе. - Для каждого скажи вслух: поражено · не поражено и почему именно · не проверено. «Не проверено» — законный ответ, «не смотрел» — нет.
- Поражённое того же класса чини сразу, в этом же заходе: отдельный заход по той же болезни стоит дороже и обычно не случается. Поражённое, но требующее другого решения, — отдельной записью в трекер, со ссылкой на этот баг. ⚠️ Если исправление ушло в общий слой (хук, утилита, провайдер), проверь и обратное: не сломал ли ты соседей, которые опирались на прежнее поведение. Общий фикс лечит всех разом и ломает всех разом.
Карта класса собирается в QA, на воспроизведении, и живёт В ЗАПИСИ
BUGS.md(форма ниже). Здесь, в FIX, она не составляется заново — здесь по ней идут и вычёркивают. Если запись пришла без карты (заведена до этого правила или другим заходом) — составь её сейчас и впиши в запись, а не в журнал.Вердикт по классу — ПИСЬМЕННАЯ таблица, а не намерение. Без неё шаг не считается сделанным, и закрытие невозможно. Форма:
**Класс (механизмом):** <одна фраза про механизм, не про симптом> | место (`file:line`) | вердикт | почему | |---|---|---| | … | поражено → чиню здесь | … | | … | не поражено | <конкретная причина, а не «вроде норм»> | | … | не проверено | <что мешает проверить> |⚠️ «Найдено ещё одно место» ≠ «класс прочёсан». Пока таблица не покрывает все места, где живёт механизм — и клиент, и сервер, и офлайн-путь, — работа не закончена. Особенно легко забывается вторая сторона: исправили клиент и объявили победу, а тот же приём написан в серверном маршруте, где ни базы, ни транзакции нет.
⚠️ Чинить часть класса можно — молча объявлять это закрытием нельзя. Если поражённых мест больше, чем помещается в заход, скажи это ЧИСЛОМ («класс из семи мест, закрыто четыре, три остались, вот они»), заведи остаток записями в трекер и не называй запись закрытой. Разница между «починил класс» и «починил кусок класса» — это разница между «больше не повторится» и «повторится там, куда не посмотрели».
-
Почини.
-
Зелёный на том же тесте. ⚠️ Докажи ОБЕ стороны, иначе «починил» неотличимо от «заглушил». Симптом пропал — это половина. Вторая половина: то, ради чего механизм существует, по-прежнему срабатывает. Ложная тревога ушла — проверь, что настоящая приходит; лишний запрос исчез — проверь, что нужный идёт. Обе проверки делаются в той же модальности, где болело. ⚠️ Красный тест должен краснеть на ПОВЕДЕНИИ. Если он падает на «cannot find module» или на синтаксисе, он не доказал ничего. Когда фикс требует нового модуля — сперва вынеси код в текущем, сломанном виде (чистый рефакторинг без смены поведения), убедись, что тест краснеет ровно на болезни, и только потом чини. ⚠️ Ожидание в тесте сверяется с кодом. Написал спецификацию «должно быть так» и она красная — сперва выясни, не сознательное ли это решение с объяснением в комментарии. Иногда чинить надо тест, а не код.
-
Гейты — базовые плюс те, что требует карта рисков.
-
Review. Дифф нетривиальный — независимый взгляд до закрытия. Тривиальный (несколько строк, одно место, обратимо) — review не нужен, но в закрытии так и пишется: «review не требовался, дифф на N строк».
-
Закрытие по процедуре.
TEST — регрессии и покрытие
Работает в двух разных ситуациях, и путать их нельзя — от этого зависит, каким должен быть тест на выходе:
- баг открыт → пишется красный тест, воспроизводящий болезнь, и он таким и остаётся: зеленеть нечему, production-код в этом режиме не трогают. Тест — артефакт для FIX; путь к нему вписывается в запись бага, и режим передаёт работу дальше: «регрессия готова и красная, дальше FIX»;
- баг уже закрыт → пишется зелёное покрытие на то же поведение: записи в трекере уже нет, поэтому путь к тесту идёт в журнал сессии со ссылкой на ID закрытого бага.
- Что покрыто и о чём покрытие молчит — молчание тоже факт.
- Тест пишется на поведение и сломанный контракт, не на форму вызова: тест, проверяющий, что функция вызвана, остаётся зелёным при мёртвой фиче.
- Проверка мутацией — только для ЗЕЛЁНОГО теста. Красный тест на открытый баг ею не проверяется: он и так красный, и «покраснел от мутации» доказать нечем. Красный доказывает себя иначе — тем, что краснеет на живом баге и зеленеет после фикса. Мутация нужна там, где тест зелёный и надо убедиться, что он вообще что-то сторожит. Протокол:
снять
git diffдо → сломать механизм одной изолированной правкой → прогнать точечный тест, убедиться, что краснеет именно он → вернуть свою правку → сравнитьgit diffс исходным: они должны совпасть текстом. Сравниватьgit statusмало — у уже изменённого файла статус останется тем жеM, а содержимое испортится.
TRIAGE — порядок в трекере
Ничего не чинит.
- Инвентаризация — сколько записей и где. Это чтение: ID здесь не выдаются, форма не переписывается.
- Нормализация — приводить к общей форме только те записи, которые реально трогаешь. Массовое переписывание всего файла — шумный diff и порча
git blame, это отдельная работа и решение владельца. - Живость — якоря ещё указывают на существующий код? дефект ещё воспроизводится?
- Дубли — схлопывать по общей причине, не по похожему заголовку. Выживший сохраняет ID, слитые перечисляются в журнале.
- Severity — по последствию для человека, не по трудоёмкости фикса.
- Спорное — в «🔍 Требует перепроверки»: если запись говорит «открыт», а работа рядом говорит «закрыт», это неизвестность, а не состояние (
BUGS.md:9). - Не-баги — «хорошо бы» в
FEATURE_CONCEPTS.md; журналы ревью и замеры — в журнал дела (el log); принятый риск остаётся в своей секции. - Административное закрытие — для записей, которые уже не воспроизводятся, но их не чинили в этом заходе. Красного теста в истории нет, поэтому критерии другие: дефект не воспроизводится на текущем SHA (проверено, а не предположено) · названо, чем он снят — коммит, соседний фикс, изменившееся требование · это записано в журнал. Не удаётся показать ни то ни другое — запись едет в «Требует перепроверки», а не удаляется.
- Итог сводкой: подтверждено · на перепроверку · слито дублей · закрыто · осталось непонятным.
Форма записи
### BUG-20260809-scratch-overwrite · <наблюдаемый симптом, не название фикса>
**Severity.** P1 — <что теряет человек>
**Найден.** 2026-08-09 · <ручная проверка | review | тест | телеметрия | пользователь>
**Якоря.** `path/to/file.ts:123`
**Стенд.** <dev/prod, ветка или SHA, браузер, флаги, данные>
**Ожидалось.** <одно проверяемое поведение>
**Получилось.** <что произошло вместо него>
**Воспроизведение.**
1. <исходное состояние>
2. <действие>
3. <что видно> (недетерминированный — добавь частоту: «3 из 10»)
**Доказательство.** <падающий тест, лог, ошибка, скриншот, якорь на журнал>
**Эффект.** <что теряет проповедник и как часто — на его языке, не в тактах>
**Как эта фича должна работать — с точки зрения пользователя.** ⬅ обязательный раздел
- *Зачем она вообще нужна:* какую задачу проповедника решает, одной фразой.
- *Когда обязана срабатывать:* перечисли случаи.
- *Когда обязана молчать:* перечисли случаи — **это ровно там, где чаще всего и ломается**.
- *Что человек видит и что может сделать:* текст, кнопки, чем кончается каждый выбор.
- *Как это отличается технически:* по какому признаку система различает «сработать» и «молчать».
**Класс (механизмом).** <одна фраза про МЕХАНИЗМ, не про симптом>
| место (`file:line`) | вердикт | почему |
|---|---|---|
| … | поражено | … |
| … | не поражено | <конкретная причина> |
| … | не проверено | <что мешает> |
**Корень.** _не исследован — QA остановился после подтверждения_
**Фикс (направление).** _заполняется в FIX после диагностики_
**Regression proof.** _заполняется в FIX/TEST: red → green, путь к тесту_
⚠️ Карта класса — часть записи, а не приложение к ней. Она собирается в QA (шаг 5) и служит чек-листом закрытия: FIX идёт по строкам и отмечает починенные, а запись удаляется только когда непочиненных не осталось. Отсюда же берётся честный ответ на вопрос «везде ли закончил» — не по памяти, а по таблице. Если часть строк остаётся, они переезжают отдельными записями со ссылкой на этот ID, и тогда таблица считается исчерпанной.
Обязательно при заведении: ID · заголовок-симптом · severity с причиной · дата и кем найден · якоря · стенд · ожидалось/получилось · воспроизведение · доказательство · эффект. Позже: корень · фикс · regression proof.
Стадия. открыт → починено локально → на проде, ждёт верификации → (верифицировано ⇒ запись удаляется)
⚠️ Секция задаёт СРОЧНОСТЬ (P1/P2), стадия — ПРОГРЕСС. Это разные оси, и они не спорят. Раньше здесь было написано, что поля статуса нет намеренно; правило изменено владельцем 2026-08-10, потому что у починенного-но-не-выкаченного бага не было места: в секции «открыто» он выглядел нетронутым, а удалять его нельзя — на проде он ещё живой. Запись — история болезни: в ней лежит всё, что про этот баг известно, включая то, на каком шаге он сейчас.
Что дописывается на каждой стадии, прямо в запись:
- починено локально — что сделано · корень · путь к регрессии и что она была красной по правильной причине · гейты с числами · что отказ больше не воспроизводится тем же способом, каким воспроизводился · что осталось недоказанным;
- на проде, ждёт верификации — SHA коммита · что именно проверять на проде и как;
- верифицировано — чем проверено живьём, и только тогда запись уходит.
⚠️ «Тесты зелёные» — это не «починено», это «починено локально». Между ними лежит выкатка, и в этом промежутке баг для человека всё ещё существует: он пользуется продом, а не твоим рабочим деревом.
ID — BUG-ГГГГММДД-краткий-slug, например BUG-20260809-scratch-overwrite. Сквозная нумерация здесь работать не может: при active-only записи удаляются, и «максимальный плюс один» рано или поздно выдаст уже использованный номер — список, из которого удаляют, не может быть источником номеров.
⚠️ Дата плюс slug почти уникальны: два бага одного дня с похожим именем совпадут. Проверять надо в трёх местах, потому что закрытые записи из трекера исчезают:
grep -r "BUG-ГГГГММДД-" BUGS.md .cases/ # живое и локальный журнал дела
git log --all --grep="BUG-ГГГГММДД-" # закрытые — они живут в сообщениях коммитов
Второе обязательно: в свежем клоне .cases/ нет вовсе, и без git-истории новый баг получит ID уже закрытого — а вместе с ним чужие тесты и коммиты. Совпало — добавь различающее слово или суффикс -2. «Уникально по построению» тут неправда, и полагаться на это нельзя. ID не меняется никогда: после удаления записи он остаётся единственной ниткой между журналом, тестом, коммитом и разговором. Новый баг получает ID сразу; старая запись — при первом настоящем касании (фикс, слияние дубля), но не при инвентаризации.
Порядок в трекере
Секции (BUGS.md): 🔴 P1 — открыто · 🔴 P2 — открыто · 🟠 Решение владельца — архитектура · 🔍 Требует перепроверки · 🟡 Открыто, но не «код-фикс» · ⚪ Осознанно НЕ чиним. Нужен P0 — секция заводится выше P1.
Новая запись — в начало своей секции, свежее сверху. ⚠️ Это правило, которое вводит скилл, а не сложившийся порядок: сейчас записи лежат вперемешку по датам. Смысл — детерминированное место вместо «куда по смыслу подошло».
Во время работы запись между секциями не прыгает — остаётся в своей severity-секции, прогресс идёт в журнал. Промежуточные состояния в трекере превращаются в мусор, который потом никто не вычистит.
Закрытие
Трекер отвечает на вопрос «что сломано сейчас», поэтому починенного в нём не остаётся: «Починил → УДАЛИ запись целиком» (BUGS.md:5).
⚠️ Закрытие = ВЕРИФИЦИРОВАНО НА ПРОДЕ, а не «тесты зелёные». Зелёный локальный прогон переводит запись в стадию починено локально и не более: человек живёт на проде, и до выкатки баг для него существует. Порядок один: починено локально → коммит и push → проверено живьём на проде → и только теперь журнал + удаление записи. Всё, что накопилось в записи по дороге, переезжает в журнал дела — это и есть след, который переживает удаление.
Закрывать можно только при наличии всего:
- доказанный корень;
- регрессия красная до фикса и зелёная после — либо зафиксированное исключение (см. «Тест идёт до фикса»): ручное или телеметрическое доказательство плюс названная причина, почему автотест до фикса был невозможен;
- пройденные гейты — базовые плюс по карте рисков;
- review, если дифф нетривиальный; иначе строка «review не требовался, дифф на N строк»;
- проверка там же, где болело.
(Для административного закрытия — свои критерии, см. TRIAGE.)
Порядок, в одном заходе:
- Запись в журнал дела — ОДНОЙ строкой, поля через
·:
el log RESULT "закрыт BUG-20260809-scratch-overwrite · <симптом> · корень: … · что сделано: … · regression: <путь к тесту> красный→зелёный · гейты: test:fast·lint·compile · проверено: <где и чем, тем же стендом, где болело> · review: <кто смотрел, что нашёл> · риск: нет"
⚠️ Переносы строк внутри кавычек не сохраняются — el сам режет длинный текст на заголовок и строки тела и склеивает всё в сплошной поток, так что поле-на-строку превращается в кашу. Разделяй ·: они перенос переживают. Проверено прогоном 2026-09-14.
Если баг вёлся пунктом TODO — тем же заходом el todo done N.M run:"<путь к тесту> → зелёный" "BUG-… закрыт".
-
🛑 Гейт человека — один, и он про две вещи сразу: покажи journal-запись, точный блок, который будет удалён, и готовое сообщение коммита. Спроси прямо: удаляем и коммитим сейчас? Удаление записи и коммит — необратимые действия, а
CLAUDE.md§2 требует подтверждения человека наdeleteиcommit. -
Сказал «да» — удаление записи и коммит идут ОДНИМ change set. Сообщение несёт ID и суть:
fix(scope): <симптом> — закрыт BUG-20260809-scratch-overwrite. Это единственный след, который переживает клон: журнал дела в git не попадает, а обещание шапки трекера «история живёт в git» держится именно коммитами. -
Коммит сейчас не делаем — запись из трекера НЕ удаляется. Она остаётся на месте, а в журнал пишется «починено, ждёт коммита». Удалить сейчас, а закоммитить когда-нибудь — худший из вариантов: в трекере бага уже нет, в истории ещё нет, и единственный след лежит в журнале дела, которого нет в git. Удаление всегда едет вместе с коммитом или не едет вовсе.
Тест идёт до фикса
Воспроизвести → написать регрессию на поведение → убедиться, что она красная из-за этой болезни → починить → зелёная → гейты.
Причина местная, а не идеологическая: в этом проекте уже ловили зелёные тесты при мёртвой фиче — тест проверял форму вызова вместо поведения. Тест, написанный после фикса, подтверждает реализацию, но не доказывает, что поймал бы исходную болезнь. Образцы, на которые стоит равняться: frontend/__tests__/pages/sermonDetail.test.tsx:456 и frontend/__tests__/utils/feedbackPayload.test.ts:104 — оба описывают сломанный контракт, а не устройство кода.
Исключение — когда автотест до фикса физически невозможен: гонка без детерминированного шва, поведение только на проде, внешний AI-провайдер, вёрстка в браузере. Тогда: точный ручной или телеметрический reproducer до фикса · автотест на ближайшем детерминированном шве · причина исключения записывается в закрытии. «Сначала починил, потом придумал тест» по умолчанию не проходит.
Порядок работы и форма отчёта — ОБЯЗАТЕЛЬНЫЙ ШАБЛОН
Задан владельцем 2026-08-10. Шаги идут в этом порядке, и каждый виден человеку. Причина: из рассказа не видно, что осталось недоказанным, а из нумерованной таблицы видно сразу — и по номерам легко сказать «возьми третий и пятый».
Шаг 1 · ЗАФИКСИРУЙ БАГ. Запись в BUGS.md заводится до любой работы. Нет записи — нет работы.
Шаг 2 · ВОСПРОИЗВЕДИ. Своими руками, тем способом, каким его встретил человек. Не воспроизвёл — так и скажи, дальше идёшь с пометкой «со слов».
Шаг 3 · НАЙДИ ПРИЧИНУ (root cause). Механизм, а не симптом. Проверка одна: трогаешь причину — симптом появляется и исчезает предсказуемо.
Шаг 3.5 · НАЙДИ ПОХОЖИЕ — где ещё живёт этот же механизм. Причина названа — теперь grep по механизму, а не по симптому: по имени функции, по конструкции, по приёму. Смотреть обязательно в трёх местах, иначе прочёс неполон по построению: клиент · сервер (Admin SDK правила обходит) · офлайн-путь. По каждому найденному месту выносится вердикт: поражено · не поражено и почему именно · не проверено.
⚠️ Этот шаг — источник шага 4. Список проблем не выдумывается из головы и не равен одному найденному симптому: он вырастает из прочёса. Пропустил прочёс — получишь список из одного пункта, починишь его и объявишь класс закрытым, а он останется жив там, куда не посмотрел. Ровно так и произошло 2026-08-10: починили план на четырёх экранах, а тот же приём переписывания целого массива жил ещё и в серверном каскаде удаления тега — по всем проповедям сразу.
Шаг 4 · НУМЕРОВАННЫЙ СПИСОК ПРОБЛЕМ — до первой правки кода. Из прочёса разворачивается список того, что предстоит починить: одна строка на проблему, пронумерованная. Это и есть объём захода, и он показывается человеку заранее — чтобы он мог сузить, расширить или сказать «этого не трогай». Класс шире списка — назови числом, сколько мест всего и сколько берёшь.
Шаг 5 · РАБОТА. По списку, по порядку. Красный тест → фикс → зелёный → гейты.
Шаг 6 · ИТОГОВАЯ НУМЕРОВАННАЯ ТАБЛИЦА. Номера строк совпадают с номерами из шага 4 — так видно, что взято и что из этого доведено:
| № | проблема | пофикшено | покрыто тестами | валидировано в браузере / вручную |
|---|
- пофикшено — ✅ / ❌ / частично, и тогда чего именно не хватает;
- покрыто тестами — путь к файлу, а не слово «да». Косвенное покрытие называется косвенным;
- валидировано — где и чем смотрел живьём. Не смотрел — пиши «нет»: это самая частая честная клетка и самая полезная, потому что именно она отделяет «тесты зелёные» от «работает у человека».
⚠️ Пустая клетка запрещена. Либо факт, либо явное «нет» с причиной. Отчёт, где про живую проверку умолчали, читается как «проверено» — так и закрываются баги, которые на самом деле живы.
Гейты
Запускать из frontend/ — в корне есть прокси для трёх первых, но не для test:rules.
npm run test:fast # быстрый прогон jest
npm run lint # eslint
npm run compile # tsc --noEmit
Полный npm run test (с coverage) — только для общей инфраструктуры, рискованных изменений и перед выкаткой: на каждую мелкую правку он слишком дорог.
Карта рисков этого репозитория
Берётся по затронутой области, а не целиком. Каждая строка — «если трогали это, то дешёвый гейт не полон».
| трогали | добавь к проверке |
|---|---|
| route, конфиг, серверный компонент, App Router | npm run build — линт и типы не ловят ошибки сборки и статического анализа |
| UI-текст, любые подписи | npm run test:translations — локали всегда три: en, ru, uk |
| правила Firestore | npm run test:rules (поднимает emulator) + матрица доступа: без входа · владелец · чужой пользователь · Admin SDK |
| чтение и запись данных | multi-tab (две вкладки), offline и переподключение, повтор той же операции, холодный старт |
| React Query, мутации | status × fetchStatus × isPaused × online × после восстановления кэша — здесь offline-first и persisted paused mutations; простой «loading/error» уже пропускал вечный pending |
| service worker, кэш, PWA | проверить и с SW, и без (dev:pwa / dev:no-pwa), плюс поведение после обновления: залипший SW отдаёт старый бандл |
| прод-специфика: таймауты, холодный старт, переменные окружения | закрыть до выкатки нельзя. Состояния: починено локально → проверено на preview → проверено на проде. До последнего запись остаётся открытой |
Пределы
Скилл держит порядок, а не думает за тебя. Разделение такое: severity скилл предлагает, человек переопределяет — цена дефекта видна тому, кто отвечает за продукт. Корень скилл обязан доказать (управляемостью: трогаешь — симптом появляется и гаснет), а не отдавать наверх как мнение. «Баг ли это вообще» на спорной границе — решение человека; скилл кладёт такое в «🔍 Требует перепроверки», а не выбирает за него.
Ощущение («выглядит криво», «неудобно») багом здесь не считается, пока не превратилось в проверяемое ожидалось/получилось — но и не выбрасывается: ему место в FEATURE_CONCEPTS.md.