Дополнительные источники: https://github.com/obra/superpowers/blob/main/skills/systematic-debugging/root-cause-tracing.md, https://github.com/obra/superpowers/blob/main/skills/systematic-debugging/defense-in-depth.md, https://github.com/obra/superpowers/blob/main/skills/systematic-debugging/condition-based-waiting.md, https://github.com/obra/superpowers/blob/main/skills/verification-before-completion/SKILL.md
Зачем это нужно
Сквозные дисциплины качества Superpowers (категория Debugging справочника — урок 17.5): systematic-debugging срабатывает на любой баг до предложения фиксов; verification-before-completion — перед любым заявлением «готово». Оба скилла — одно философское утверждение, четвертый пункт философии проекта (урок 17.1): evidence over claims — доказательства вместо заявлений.
Для агентов эти скиллы важнее, чем для людей: агент по природе оптимист — он «видит» код правильным, потому что писал его; догадка под давлением времени для него не отклонение, а норма. Отладка по гипотезам в курсе уже разбиралась (урок 9.5); здесь — перевод скиллов Superpowers с их техниками: трассировка корневой причины, защита в глубину, ожидание условий — и «железный закон» верификации.
Основная идея
Два железных закона:
NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
(систематическая отладка: никаких фиксов без расследования корневой причины)
NO COMPLETION CLAIMS WITHOUT FRESH VERIFICATION EVIDENCE
(верификация: никаких заявлений о завершении без свежих доказательств верификации)
И одинаковое вступление у обоих скиллов: «Violating the letter of this rule is violating the spirit of this rule» — нарушение буквы правила есть нарушение его духа. Это отрезает целый класс рационализаций «я следую духу, а не ритуалу» (тот же прием, что в TDD — урок 17.10).
Systematic-debugging: четыре фазы
Применяется к любому техническому вопросу: падение тестов, баги в продакшене, неожиданное поведение, проблемы производительности, падения сборки, интеграционные проблемы. Особенно — под давлением времени, когда «один быстрый фикс» кажется очевидным, уже попробовано несколько фиксов, предыдущий не сработал, проблема не до конца понятна. Каждая фаза завершается до перехода к следующей; не завершена фаза 1 — фиксы предлагать нельзя.
Фаза 1: расследование корневой причины
До любого фикса:
- Внимательно прочитать сообщения об ошибках — не проскальзывать мимо ошибок и предупреждений; они часто содержат точное решение; читать стек-трейсы целиком; фиксировать номера строк, пути, коды ошибок.
- Воспроизвести стабильно: запускается ли reliably; каковы точные шаги; происходит ли каждый раз. Невоспроизводимо — собирать больше данных, не догадаться.
- Проверить недавние изменения: что изменилось и могло стать причиной;
git diff, недавние коммиты, новые зависимости, изменения конфигурации, отличия окружения. - Собрать доказательства в мульти-компонентных системах: CI → build → signing, API → service → DB — до предложения фиксов добавить диагностическую инструментацию на каждую границу компонентов: что входит, что выходит, как распространяются env/config, состояние на каждом слое. Один прогон собирает доказательства, где ломается; потом анализ доказательств указывает компонент; потом исследуется этот компонент. Пример из скилла — послойная диагностика CI/сборки/подписи: echo секретов в workflow → env в build-скрипте → состояние keychain → сама подпись с
--verbose=4; вывод показывает, какой слой ломается. - Трассировать поток данных (ошибка глубоко в стеке) — полная техника в
root-cause-tracing.md(раздел ниже): откуда плохое значение, кто вызвал с ним; трассировать вверх до источника; чинить в источнике, не в симптоме.
Фаза 2: анализ паттерна
До фикса — найти паттерн:
- Найти работающие примеры: похожий работающий код в той же кодовой базе; что работает из похожего на сломанное?
- Сравнить с референсами: реализуете паттерн — прочитать референсную реализацию полностью, не по диагонали: каждую строку.
- Определить отличия: что различается между работающим и сломанным; выписать каждое, не полагаясь на «это же не может влиять».
- Понять зависимости: какие еще компоненты нужны, какие настройки/конфиг/окружение, какие предположения делает код.
Фаза 3: гипотеза и проверка
Научный метод:
- Одна гипотеза, сформулированная ясно: «Думаю, X — корневая причина, потому что Y»; записать; конкретно, не размыто.
- Минимальная проверка: малейшее возможное изменение для проверки гипотезы; одна переменная за раз; не чинить несколько вещей сразу.
- Верифицировать до продолжения: сработало — фаза 4; не сработало — новая гипотеза; не наслаивать фиксы поверх.
- Когда не знаете — сказать «не понимаю X»; не притворяться; попросить помощи; исследовать дальше.
Фаза 4: реализация
Чинить корневую причину, не симптом:
- Падающий тест-кейс: простейшее воспроизведение; автоматический тест, одноразовый скрипт при отсутствии фреймворка; обязателен до фикса (TDD — урок 17.10).
- Единственный фикс: адресует корневую причину; одно изменение за раз; никаких «заодно» улучшений; никакого bundled-рефакторинга.
- Верифицировать фикс: тест проходит; другие тесты не сломаны; вопрос действительно решен — под
verification-before-completion(раздел ниже). - Фикс не сработал: СТОП; посчитать фикс-попытки. Меньше трех — назад в фазу 1 с новой информацией. Три и больше — стоп и вопрос к архитектуре (пункт 5).
- 3+ фикса провалились — вопрос к архитектуре: паттерн (каждый фикс вскрывает новое общее состояние/связку/проблему в другом месте; фиксы требуют «массированного рефакторинга»; каждый фикс создает новые симптомы) — это не провалившаяся гипотеза, это неправильная архитектура. Остановиться и спросить: паттерн в принципе здравый? держимся за него по инерции? рефакторить архитектуру вместо продолжения затыкания симптомов? Обсудить с партнером до новых попыток.
Красные флаги
Поймали себя на: «быстрый фикс сейчас, расследование потом»; «попробую поменять X и посмотрим»; «несколько изменений сразу и прогон тестов»; «пропущу тест, проверю руками»; «наверняка X, поправлю это»; «не до конца понимаю, но может сработать»; «вот основные проблемы: [список фиксов без расследования]»; предложения решений до трассировки данных; «еще одна попытка фикса» (когда уже 2+); каждый фикс вскрывает новую проблему в новом месте — все это означает: СТОП, вернуться в фазу 1; при 3+ провалах — вопрос к архитектуре.
Техника: root-cause-tracing
Баги часто проявляются глубоко в стеке вызовов (git init в неправильном каталоге, файл не там, БД не с тем путем) — инстинкт чинить там, где ошибка видна, лечит симптом. Принцип: трассировать назад по цепочке вызовов до оригинального триггера, чинить в источнике.
Процесс (пример скилла, переведенный):
- Симптом:
Error: git init failed in ~/project/packages/core. - Непосредственная причина:
await execFileAsync('git', ['init'], { cwd: projectDir }). - Кто вызвал?:
WorktreeManager.createSessionWorktree(projectDir, sessionId)←Session.initializeWorkspace()←Session.create()← тест вProject.create(). - Какое значение передано?:
projectDir = ''— пустая строка! Пустая строка какcwdрезолвится вprocess.cwd()— каталог исходников. - Оригинальный триггер:
const context = setupCoreTest(); // возвращает { tempDir: '' }— тест обратился кcontext.tempDirдоbeforeEach.
Нельзя трассировать руками — добавить инструментацию: логировать до опасной операции (не после падения), в тестах — console.error (не logger, его могут гасить), с контекстом (каталог, cwd, env, таймстампы) и new Error().stack (полная цепочка вызовов). Неизвестно, какой тест «загрязняет» прогон — бисекция: скрипт find-polluter.sh запускает тесты по одному, останавливаясь на первом «загрязнителе».
Итог примера: корневая причина — top-level инициализация с пустым значением; фикс — геттер, бросающий при обращении до beforeEach; плюс защита в глубину (следующая техника).
Техника: defense-in-depth
Исправив баг плохих данных, добавить валидацию в одном месте кажется достаточным — но одиночная проверка обходится другими путями кода, рефакторингом и моками. Принцип: валидировать на каждом слое, через который проходят данные; сделать баг структурно невозможным. Разница: одиночная проверка — «мы починили баг»; слои — «мы сделали баг невозможным». Разные слои ловят разное: входная валидация — большинство багов; бизнес-логика — краевые случаи; охранные проверки окружения — контекстные опасности; debug-логирование — форензика, когда остальное не сработало.
Четыре слоя (по документу):
- Вход: отвергнуть очевидно некорректное на границе API (пустое → throw; не существует → throw; не каталог → throw).
- Бизнес-логика: данные имеют смысл для операции (
projectDirобязателен для инициализации workspace). - Стражи окружения: отказ от опасных операций в контексте (в тестах —
NODE_ENV=test—git initотказывается работать вне tmpdir). - Диагностическая инструментация: лог контекста со стеком до опасной операции.
Итог примера из скилла: 1847 тестов прошли, баг невозможно воспроизвести; и — ключевая мысль — все четыре слоя оказались необходимы: в тестировании каждый слой ловил баги, которые пропускали остальные: разные пути обходили вход; моки обходили логику; краевые случаи на других платформах ловили стражи; лог вскрывал структурное злоупотребление. Не останавливаться на одной точке валидации.
Техника: condition-based-waiting
Flaky-тесты угадывают время произвольными задержками — гонки, где на быстрых машинах проходит, под нагрузкой и в CI падает. Принцип: ждать фактическое условие, а не догадку о длительности.
// ❌ ДО: угадывание времени
await new Promise(r => setTimeout(r, 50));
const result = getResult();
// ✅ ПОСЛЕ: ожидание условия
await waitFor(() => getResult() !== undefined);
Быстрые паттерны: waitFor(() => events.find(e => e.type === 'DONE')) — событие; waitFor(() => machine.state === 'ready') — состояние; waitFor(() => items.length >= 5) — количество; waitFor(() => fs.existsSync(path)) — файл. Ошибки: слишком быстрый поллинг (разумно — 10 мс); без таймаута (вечный цикл; всегда таймаут с ясной ошибкой); устаревшие данные (геттер — внутри цикла). Когда произвольная задержка правильна: тестируется само время (debounce, интервалы) — и тогда с комментарием, почему (пример скилла: 200 мс = 2 тика по 100 мс, с комментарием). Итог из реальной сессии: 15 flaky-тестов в 3 файлах, 60% → 100% проход, выполнение на 40% быстрее.
Verification-before-completion
Второй скилл пары — ворота на любые заявления о статусе. Закон: «If you haven't run the verification command in this message, you cannot claim it passes» — не запустили команду верификации в этом сообщении — не можете утверждать, что она проходит.
Функция-ворота:
ДО заявления любого статуса или удовлетворения:
1. IDENTIFY: какая команда доказывает это утверждение?
2. RUN: выполнить ПОЛНУЮ команду (свежую, целиком)
3. READ: полный вывод, код выхода, посчитать падения
4. VERIFY: вывод подтверждает утверждение?
нет → назвать фактический статус с доказательствами
да → назвать утверждение С доказательствами
5. ТОЛЬКО ТОГДА: заявить
Пропуск любого шага = ложь, не верификация
Таблица требований из скилла — что заявлению недостаточно:
| Заявление | Требует | Недостаточно |
|---|---|---|
| «Тесты проходят» | вывод команды: 0 падений | прошлый прогон, «должны пройти» |
| «Линтер чист» | вывод линтера: 0 ошибок | частичная проверка, экстраполяция |
| «Сборка проходит» | exit 0 | линтер чист, «логи выглядят хорошо» |
| «Баг исправлен» | исходный симптом проходит тест | «код изменен, наверное исправлено» |
| «Агент завершил» | VCS-дифф показывает изменения | агент отчитался «success» |
| «Требования выполнены» | построчный чек-лист | «тесты проходят» |
Красные флаги скилла: слова «должно», «наверняка», «кажется»; выражение удовлетворения до верификации («Great!», «Perfect!», «Done!»); коммит/push/PR без верификации; доверие отчетам агентов об успехе; частичная верификация; «только этот раз»; усталость; любая формулировка, подразумевающая успех без запуска верификации.
Ключевые паттерны: регрессионный тест верифицируется циклом RED-GREEN («написал регрессионный тест» без проверки, что он падает до фикса и проходит после, — не верифицирован); делегирование агенту — «агент отчитался об успехе → проверить VCS-дифф → верифицировать изменения → отчитаться о фактическом состоянии»; требования — «перечитать план → чек-лист → проверить каждый → отчитаться о пробелах или завершении», а не «тесты проходят, фаза завершена».
Связь с остальным модулем
- TDD (урок 17.10): фаза 4 отладки — тот же цикл RED-GREEN; «нашли баг → падающий тест → фикс»; фиксы без теста запрещены обоими скиллами.
- Исполнение планов (урок 17.9): шаг не совпал с
Expected:→ код неверен =systematic-debugging, не подгонка вывода; контракт завершения задачи — подverification-before-completion. - Ревью (урок 17.11): находки ревьюера проверяются по правилам receiving — «проверить до исполнения» — тот же принцип.
- Диагностика Superpowers (урок 17.14): когда сессия пошла не так, diagnosing-скилл применяет ту же дисциплину к самому Superpowers — каждый вывод с
path:line.
Практика
- Возьмите учебный баг (или реальный из своего проекта) и проведите четыре фазы письменно: симптом → непосредственная причина → цепочка вызовов вверх → оригинальный триггер. Проверьте: чините источник или симптом?
- Для фикса спланируйте защиту в глубину: какие четыре слоя стоят на пути плохого значения в вашем случае?
- Сформулируйте для фикса верификацию по функции-воротам: какая команда доказывает «исправлено»? Что вы прочитаете в выводе? Что нельзя заявлять до этого?
Краткое резюме
Пара скиллов — одна философия «evidence over claims»: systematic-debugging запрещает фиксы без корневой причины (четыре фазы: расследование → паттерн → гипотеза → реализация; 3+ провала — вопрос к архитектуре), verification-before-completion — заявления без свежей верификации.
- Трассировка корневой причины: назад по стеку до оригинального триггера; чинить источник; инструментация — до опасной операции.
- Защита в глубину: валидация на каждом слое — вход, логика, стражи окружения, лог; все слои необходимы, «баг невозможен» вместо «баг починен».
- Ожидание условий:
waitFor(условие)вместо задержек; задержка — только с обоснованием, почему. - Функция-ворота верификации: определить команду → запустить → прочитать → сверить → заявить; пропуск шага — ложь, не верификация.
Вопросы для самопроверки
- Почему железный закон отладки стоит до предложений фиксов? Какие рационализации он отрезает?
- Опишите четыре фазы. Что происходит после трех провалившихся фиксов и почему это «вопрос к архитектуре», а не гипотеза?
- В чем разница «починили баг» и «сделали баг невозможным»? Почему все четыре слоя защиты оказались необходимы в примере скилла?
- Когда произвольная задержка в тесте правильна? Какие три требования сопровождают этот случай?
- Перечислите шаги функции-вороты верификации. Почему доверие отчету агента об успехе — красный флаг?
- Как «нарушение буквы есть нарушение духа» работает против аргумента «я следую духу, а не ритуалу»?
Что дальше
Отладка и верификация дисциплинируют код; следующий урок, 17.14, применяет ту же строгость к самому Superpowers: скилл diagnosing-superpowers — что делать, когда сессия пошла не так, и как собрать доказательства с построчными ссылками.