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

Предыдущая статья разобрала паттерн explore → plan → code → commit как архитектурное решение для работы с агентом. Здесь — детальный разбор двух инструментов, которые делают этот паттерн надёжным: план-режима и TDD-петли. Оба решают одну проблему: агент сам не знает, когда работа сделана правильно, если вы не дали ему способ это проверить.


Плановый режим: защита от преждевременного кода

Плановый режим (plan mode) — это режим разрешений, в котором Claude может читать файлы, выполнять read-only команды и предлагать изменения, но не может ничего записать на диск. Никаких Edit, никаких Write, никаких деструктивных Bash-команд.

Включить его можно тремя способами:

# Запуск сразу в план-режиме
claude --permission-mode plan

# Переключение в середине текущей сессии
# Shift+Tab — циклически переключает режимы разрешений

# Или явно из командной строки внутри сессии:
/plan

Переключение через Shift+Tab работает как тоггл: default → acceptEdits → plan → default. Полезно, когда уже находишься в сессии и хочешь безопасно исследовать, прежде чем дать команду на изменения.

Что конкретно происходит в план-режиме:

✅ Разрешено:           ❌ Заблокировано:
──────────────────────────────────────────────
Read (чтение файлов)   Edit (правка файлов)
Grep, Glob             Write (создание файлов)
Bash (read-only)       Bash (с побочными эффектами)
Анализ и планирование  Все деструктивные команды

После того как Claude составил план, нажмите Ctrl+G — план откроется в вашем редакторе. Можно убрать ненужные шаги, добавить ограничения, исправить приоритеты. Claude будет следовать именно этой версии плана при выходе из план-режима.

Check yourself
В план-режиме вы написали запрос: «прочитай src/auth.ts и объясни, как работает refresh-токен». Claude ответил и теперь предлагает внести правку в файл. Что произойдёт, если вы напишете «окей, вноси»?

Что делает хороший план

Агент в план-режиме — это архитектор без молотка. Используйте это: задавайте вопросы, которые требуют понимания структуры, а не реализации.

# Слабый запрос для план-режима:
добавь Google OAuth

# Сильный запрос:
мне нужно добавить Google OAuth. Прочитай @src/auth/ и разберись,
как сейчас устроен login-flow. Затем:
1. Покажи, какие файлы нужно изменить и почему
2. Какие новые файлы появятся
3. Как изменится flow сессии после OAuth callback
4. Есть ли риски обратной совместимости с текущими сессиями
Составь план с конкретными именами файлов и шагами.

Чем конкретнее вопросы — тем точнее план. Хороший план содержит:

  • Файлы с конкретными изменениями (src/auth/handlers.ts — добавить функцию handleOAuthCallback)
  • Последовательность шагов с обоснованием порядка
  • Точки верификации («после шага 3 запустить npm test auth»)
  • Явные ограничения («не менять существующий sessionMiddleware»)

Есть и практический трюк для сложных фич: дать Claude провести «интервью» перед планом.

хочу реализовать систему уведомлений. Проведи детальное интервью
с помощью AskUserQuestion. Спроси про технические подходы,
UX, крайние случаи и trade-offs — особенно те, о которых я
мог не подумать. После интервью напиши полный spec в SPEC.md.

Получив SPEC.md, начните чистую сессию для реализации — контекст планирования не смешивается с контекстом имплементации.


TDD-петля: тест как оракул для агента

Теперь самое важное. Документация Anthropic формулирует это так:

> Give Claude a check it can run: tests, a build, a screenshot to compare. It's the difference between a session you watch and one you walk away from.

Без проверки Claude останавливается, когда «выглядит готово». С тестом он останавливается, когда тест зелёный. Это принципиальная разница.

ТДД в контексте Claude — не обязательно строгий Red-Green-Refactor из книжки. Это паттерн: сначала определить, как выглядит «правильно», а потом дать агенту итерировать до этого состояния.

flowchart TD A(["Задача: написать функцию X"]) --> B["📝 Написать failing тест\n(в план-режиме или вручную)"] B --> C["▶ Запустить тест\n→ FAIL"] C --> D["🤖 Claude: реализовать X\n(в default-режиме)"] D --> E["▶ Запустить тест"] E --> F{"Тест\nпрошёл?"} F -->|"❌ FAIL"| G["Claude: прочитать ошибку\nи исправить"] G --> E F -->|"✅ PASS"| H["▶ Запустить полный suite"] H --> I{"Всё зелёное?"} I -->|"❌ Регрессия"| G I -->|"✅ OK"| J(["Готово: коммит и PR"]) style A fill:#e8f4f8,stroke:#2980b9 style J fill:#e8f8e8,stroke:#27ae60 style F fill:#fff3cd,stroke:#f39c12 style I fill:#fff3cd,stroke:#f39c12
flowchart TD
    A(["Задача: написать функцию X"]) --> B["📝 Написать failing тест\n(в план-режиме или вручную)"]
    B --> C["▶ Запустить тест\n→ FAIL"]
    C --> D["🤖 Claude: реализовать X\n(в default-режиме)"]
    D --> E["▶ Запустить тест"]
    E --> F{"Тест\nпрошёл?"}
    F -->|"❌ FAIL"| G["Claude: прочитать ошибку\nи исправить"]
    G --> E
    F -->|"✅ PASS"| H["▶ Запустить полный suite"]
    H --> I{"Всё зелёное?"}
    I -->|"❌ Регрессия"| G
    I -->|"✅ OK"| J(["Готово: коммит и PR"])

    style A fill:#e8f4f8,stroke:#2980b9
    style J fill:#e8f8e8,stroke:#27ae60
    style F fill:#fff3cd,stroke:#f39c12
    style I fill:#fff3cd,stroke:#f39c12
TDD-петля с агентом: тест как оракул, агент итерирует до зелёного

Практически это выглядит так:

# Шаг 1: напишите falling test (или попросите Claude написать его)
# Шаг 2: дайте Claude задачу с явной точкой остановки

claude --permission-mode plan
> Прочитай src/utils/tokenRefresh.ts и напиши failing тест,
  который проверяет: если refresh-токен истёк, функция должна
  выбросить TokenExpiredError, а не вернуть null.
  Не реализуй ничего — только тест.

После того как тест написан и запущен — вы видите красный результат:

FAIL src/utils/tokenRefresh.test.ts
  ✕ throws TokenExpiredError when refresh token is expired
    Expected: TokenExpiredError
    Received: null

Теперь выходите из план-режима и даёте Claude полную задачу:

# Выход из план-режима: Shift+Tab

реализуй функцию refreshToken в src/utils/tokenRefresh.ts так,
чтобы она проходила написанный тест. После каждого изменения
запускай `npm test -- tokenRefresh` и итерируй до зелёного.
Если тест падает три раза подряд с одной ошибкой — остановись
и объясни проблему, не продолжай угадывать.

Ключевые элементы хорошего TDD-промпта для агента:

1. Конкретная команда проверки (npm test -- tokenRefresh, не просто «запусти тесты»)

2. Итерация до результата («итерируй до зелёного»)

3. Стоп-условие (чтобы агент не уходил в бесконечный цикл при застревании)

Check yourself
Вы пишете промпт: «реализуй функцию parseDate». Агент реализует, говорит «готово» — и дожидается вашей реакции. Назовите одно конкретное изменение в промпте, которое позволит агенту самому определить, что работа сделана правильно.

Таблица верификаторов

Тест — не единственный способ дать агенту обратную связь. Вот что работает для разных типов задач:

Тип задачиВерификаторПример команды
Логика / алгоритмыЮнит-тестpytest tests/unit/test_parser.py
РефакторингТест + тайпчекtsc --noEmit && npm test
Баг-фиксFailing test → fix → passНаписать тест, воспроизводящий баг
UI-измененияСкриншот + сравнениеclaude --chrome + визуальный diff
МиграцияЛинтер + типы + тестыeslint . && tsc && pytest
API / интеграцияEnd-to-endcurl + проверка статус-кода
ИнфраструктураDry-run или planterraform plan

Командная строка верификатора должна быть в промпте явно. «Запусти тесты» — слишком неоднозначно: Claude может запустить весь suite вместо одного файла, что медленно и шумно.


Комбинация план-режима и TDD

Оба инструмента работают вместе: план-режим отвечает за понимание и проектирование, TDD — за безопасную итеративную реализацию.

Фаза 1 (план-режим): понять → спроектировать → написать тест
Фаза 2 (default-режим): реализовать → запустить тест → итерировать → зелёный

Полный промпт для сложной задачи:

[В план-режиме]
Изучи @src/payments/ и разберись, как мы обрабатываем
возвраты. Затем:
1. Составь план добавления частичного возврата
2. Напиши failing тест в tests/unit/test_refunds.py,
   который проверяет: частичный возврат на сумму больше
   оригинального платежа должен выбрасывать OverRefundError
3. Не реализуй ничего — только план и тест
[Выход из план-режима: Shift+Tab]
Реализуй план из шага 1. После каждого изменения
запускай `pytest tests/unit/test_refunds.py -v`.
Если все тесты зелёные, запусти полный suite:
`pytest tests/ -x` и убедись, что ничего не сломалось.
Потом открой PR с описательным сообщением.

Stop Hook: детерминированный барьер

Для автономных сессий без постоянного надзора есть следующий уровень — Stop Hook. Это shell-команда, которая запускается при каждой попытке агента остановить сессию. Если команда завершается с ненулевым кодом, агент не может завершить работу.

// .claude/settings.json
{
  "hooks": {
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "npm test -- --passWithNoTests"
          }
        ]
      }
    ]
  }
}

С таким хуком Claude не может завершить работу, пока npm test не вернёт 0. Это детерминированный барьер: в отличие от инструкции в CLAUDE.md (которую агент может «забыть»), хук выполняется всегда, независимо от состояния контекста.

Важно: документация указывает, что Claude Code завершает сессию принудительно после 8 последовательных блокировок хуком — защита от бесконечного цикла. Поэтому Stop Hook хорош для задач, где тест реально должен пройти, а не как вечный сторож.

Check yourself
Вы настроили Stop Hook, который запускает полный test suite. Агент работает над задачей и застрял: каждая попытка завершить работу блокируется, потому что один тест не проходит уже 10 итераций. Что произойдёт автоматически?

Когда план-режим и TDD не нужны

Оба инструмента добавляют накладные расходы. Не каждая задача их оправдывает.

Пропустите план-режим, если:

  • Задача умещается в одно предложение: «добавь null-check в строку 42»
  • Вы хорошо знаете изменяемый код
  • Diff будет трогать один файл в одном месте

Пропустите TDD, если:

  • Задача чисто косметическая (переименование, форматирование)
  • Изменение — это документация или конфиг
  • Вы сами быстрее проверите результат визуально, чем напишете тест

Правило: если вы не уверены в подходе или не знаете изменяемый код — включайте план-режим. Если результат сложно проверить визуально и есть что тестировать — давайте тест.


Quick recall
Как работает TDD в контексте Claude и чем это отличается от классического Red-Green-Refactor?
Quick recall
Какие компоненты входят в хороший план для план-режима?
Quick recall
Что блокирует план-режим в Claude Code?

See also

Sources

  1. Common workflows — Claude Code
  2. Best practices for Claude Code