1. Признаки действительно полезного hook
После знакомства с event, matcher и handler очень хочется сразу нажать внутреннюю красную кнопку «автоматизировать всё». Это нормальное желание: раз инструмент умеет реагировать на события, значит, на него можно повесить что угодно. Но здесь важно вовремя притормозить — одни задачи hook решает хорошо, другие только добавляют шума.
Как понять, что перед вами хороший кандидат на автоматизацию? У полезного hook почти всегда четыре признака: он срабатывает в повторяемой ситуации, даёт предсказуемый результат, не требует тонкого человеческого вкуса и легко откатывается. Форматирование после редактирования — хороший кандидат, подсказка «вы задели чувствительный путь, запустите проверку» — тоже. А «оценить, насколько архитектурно красив мой код» — уже не hook-задача: ни детерминизма, ни дешёвого отката, ни гарантии, что автоматизация не пойдёт философствовать вместо помощи.
Для Workflow Kit команды Commerce OS полезнее всего начинать с очень земных сценариев: formatter приводит код к одному стилю; linter ищет подозрительные конструкции до ручной проверки; узкий защитный hook не даёт полезть в чувствительный путь; report-hook напоминает, какие проверки важны после конкретного модуля. Сначала маленький сигнал, потом осознанное действие — а не «я повесил automation, дальше сама разберётся».
Точные имена событий, полей matcher и флагов блокировки могут отличаться в вашей версии Claude Code. Ориентируйтесь на /hooks и актуальную документацию. Важна логика, а не конкретный JSON-синтаксис, высеченный в камне.
2. Безопасные сценарии для первых hooks
Когда вы только начинаете работать с hooks, первый hook — не время геройствовать. Это как нож на кухне: сначала вы режете хлеб, а не пытаетесь сразу жонглировать тремя клинками. Берите узкие сценарии, где побочный эффект легко заметить и убрать.
Ниже удобная карта первых сценариев:
| Сценарий | Что делает hook | Почему это хороший старт |
|---|---|---|
| Форматирование после редактирования | Прогоняет форматтер по одному изменённому файлу | Изменение локальное, понятное, обычно легко откатить |
| Мягкий lint/report | Показывает предупреждение или запускает короткую проверку | Даёт быстрый сигнал, но не блокирует работу |
| Напоминание про чувствительный модуль | Сообщает, что после изменения стоит запустить дополнительные проверки | Не лезет в код, но снижает шанс забыть важный шаг |
| Узкая защита пути | Блокирует запись в конкретную опасную зону | Полезно, но только после аккуратной раскатки |
| Контекст на старте сессии | Печатает краткую памятку или команды проекта | Не вмешивается в код, зато уменьшает бытовые ошибки |
Форматирование — почти идеальная первая автоматизация. Если у команды уже выбран formatter, hook не «думает», а просто применяет заранее согласованное правило. Не замена review, а способ не тратить ревью на споры о пробелах и кавычках. Новичку заодно легче читать одинаково оформленный код.
Мягкий lint или report-hook хорош именно потому, что не строит из себя начальника. Он не говорит «стоять, не пущу», а говорит «я заметил полезный сигнал». Особенно в Commerce OS, где фронтенд на Next.js и бэкенд на Java живут рядом: у разных частей проекта проверки разные — hook здесь лучше сначала использовать как ранний радар, а не как мини-охрану на проходной.
С напоминаниями для чувствительных модулей получается вообще очень человеческий сценарий. Представьте, что вы редактировали путь про возвраты или платежи; hook не обязан гонять тяжёлые тесты, но может напомнить: «Вы задели модуль refunds, не забудьте targeted checks». Новичку такой сигнал особенно полезен: инженерная мышечная память формируется не афоризмами, а повторением маленьких ритуалов.
3. Non-blocking и blocking: разный характер
Снаружи два hook могут выглядеть почти как близнецы: тот же event, похожий matcher, почти тот же handler. Но по реальному эффекту это два разных зверя. Non-blocking — вежливый коллега, заглянул через плечо: «Тут, кажется, стоит проверить ещё кое-что». Blocking — турникет, который не пустит дальше, пока условие не выполнено. Ошибка в первом раздражает, во втором — останавливает работу.
Разницу удобно увидеть на контрасте:
| Режим | Что происходит при срабатывании | Где использовать первым |
|---|---|---|
| Non-blocking | Hook пишет лог, форматирует, показывает сигнал, но не отменяет действие | Почти всегда это лучший старт |
| Blocking | Hook может запретить действие или потребовать дополнительного шага | Только в очень узких и проверенных сценариях |
Вот минимальный non-blocking hook для стадии наблюдения:
{
"event": "afterFileEdit",
"matcher": { "paths": ["frontend/src/**/*.{ts,tsx}"] },
"handler": {
"command": "scripts/log-format-candidate.sh {{file}}",
"blocking": false
}
}
А вот узкий blocking-пример для чувствительного пути:
{
"event": "beforeToolUse",
"matcher": { "tool": "Write", "paths": ["**/payments/**"] },
"handler": {
"command": "scripts/block-payment-write.sh {{file}}"
}
}
Смысл здесь не в синтаксисе, а в характере вмешательства. Ошибся non-blocking — лишний шум в логах, переживаемо. Ошибся blocking — начнёте спорить со своим же automation-слоем, как с упрямым шлагбаумом на парковке: и машина ваша, и двор ваш, а шлагбаум всё равно уверен, что вы здесь посторонний.
Именно поэтому на раннем этапе почти всё должно быть non-blocking. Blocking включайте только там, где matcher очень узкий, сценарий стабилен, а цена ложного срабатывания команде понятна заранее. Иначе вы строите не safety rail, а случайный генератор фрустрации.
4. Лестница раскатки: hook по шагам
Самая полезная мысль этого раздела звучит так: хороший hook редко рождается сразу «боевым». Он взрослеет по стадиям: сначала вы смотрите, срабатывает ли он правильно, потом — не шумит ли часто, потом даёте право на безопасное действие, и только затем думаете о жёстком вмешательстве.
Эту идею удобно держать в виде лестницы раскатки:
flowchart TD
A["Log-only"] --> B["Non-blocking"]
B --> C["Formatting"]
C --> D["Test / report"]
D --> E["Narrow blocking"]
E --> F["Policy-managed"]
На первой ступени log-only hook вообще ничего не меняет, только пишет: «Я сработал на таком-то файле». Скучно, но бесценно: сразу видно, не слишком ли широк matcher.
На второй ступени non-blocking hook уже может делать мягкое действие: подсказка, небольшой report, дешёвая проверка. Это всё ещё наблюдение, но уже прикладное: видно, помогает automation или просто украшает терминал новыми строками.
Третья ступень — formatting. Она кажется скромной, но именно здесь hook впервые начинает менять файлы. Значит, форматирование должно быть детерминированным и узким: только там, где команда согласовала инструмент и стиль. Никакой «творческой интерпретации прекрасного».
Четвёртая ступень — test/report, подключение к ранним проверкам. Здесь важно не перепутать полезную валидацию с персональным CI, который запускается на каждый чих и съедает темп. Хороший test/report-hook либо гоняет короткую локальную проверку, либо напоминает, какую не забыть.
Пятая ступень — narrow blocking. Вот здесь automation уже может сказать «стоп», но очень локально: запись в чувствительный путь без подтверждения, опасная команда в защищённой зоне. Расползётся широко — команда начнёт ненавидеть не риск, а сам инструмент.
Шестая ступень — policy-managed. Это уже не личный эксперимент, а зрелый артефакт Workflow Kit: понятен, обкатан, документирован, легко отключается, не пугает команду. До этой ступени дорастают, а не перепрыгивают через три пролёта сразу.
5. format-on-edit для Workflow Kit на практике
Теперь соберём всё в один сквозной пример и зафиксируем рабочий baseline для format-on-edit: узкий matcher, non-blocking handler и guard для крайних случаев. Цель — вырастить живучий артефакт .claude/hooks/format-on-edit.json, который команда Commerce OS раскатит без ежедневных молитв о стабильности.
Сначала мы вообще не форматируем код — проверяем только, что matcher ловит нужные файлы фронтенда и не лезет в бэкенд, generated-файлы и случайные артефакты. Для этого hook идёт в log-only:
{
"event": "afterFileEdit",
"matcher": { "paths": ["frontend/src/**/*.{ts,tsx}"] },
"handler": {
"command": "scripts/log-format-candidate.sh {{file}}",
"blocking": false
}
}
Сам скрипт может быть совсем маленьким:
#!/usr/bin/env bash
FILE="$1"
echo "[hook] кандидат на форматирование: $FILE" >&2
# [hook] кандидат на форматирование: frontend/src/app/page.tsx
exit 0
Здесь нет никакой магии. Несколько рабочих сессий я просто наблюдаю, действительно ли hook срабатывает там, где нужно. Поймал лишние файлы — вот идеальный момент поправить matcher, пока он никому ничего не ломает.
Когда стало ясно, что matcher ведёт себя прилично, можно дать hook право на реальное форматирование:
{
"event": "afterFileEdit",
"matcher": { "paths": ["frontend/src/**/*.{ts,tsx}"] },
"handler": {
"command": "pnpm prettier --write {{file}}",
"blocking": false
}
}
Если проекту нужна дополнительная защита от лишних файлов, лучше завернуть команду в свой скрипт и сделать ранний выход для исключений:
#!/usr/bin/env bash
FILE="$1"
case "$FILE" in
*.generated.ts|*.snap) exit 0 ;;
esac
pnpm prettier --write "$FILE"
Именно такая связка — узкий matcher плюс guard в handler — обычно и становится живучим baseline для проекта.
На этом месте многие разработчики воодушевляются и хотят сразу добавить ещё и ESLint, и тесты, и проверку импортов и, наверное, напоминание пить воду. Лучше остановиться. Один hook на пять дел превращается в комбайн, который сложнее понять, чем отключить. Хороший Workflow Kit живёт наоборот: один артефакт — одна внятная ответственность.
Зрелый format-on-edit выглядит скучно — и это комплимент. Скучная автоматизация — лучшая автоматизация.
6. Hooks для проверки и тестов: быстрый сигнал, не CI
После форматирования следующий соблазн — проверки. Кажется: раз hook умеет запускать команду, значит после каждого редактирования надо гонять тесты, линтер, сборку и весь проект через статический анализ. Практически одно редактирование превращается в маленькую очередь на посадку в аэропорту.
Поэтому для validation и testing hooks действует простое правило: ранний сигнал должен быть дешевле, чем ошибка, которую он помогает заметить. Проверка тяжёлая, редкая или шумная — ей не место в hook первого уровня, выносите на поздний этап workflow. Hook здесь — быстрый сенсор, а не переносной дата-центр.
Для фронтенд-файлов можно начать с мягкого lint на изменённый файл:
{
"event": "afterFileEdit",
"matcher": { "paths": ["frontend/src/**/*.{ts,tsx}"] },
"handler": {
"command": "pnpm eslint {{file}}",
"blocking": false
}
}
А для чувствительных зон бэкенда чаще полезнее не запускать дорогие тесты автоматически, а выдать точную подсказку:
{
"event": "afterFileEdit",
"matcher": { "paths": ["backend/src/main/java/**/refund/**"] },
"handler": {
"command": "scripts/report-refund-checks.sh {{file}}",
"blocking": false
}
}
Скрипт-подсказка может быть очень простым:
#!/usr/bin/env bash
FILE="$1"
echo "Изменён чувствительный файл: $FILE" >&2
echo "Проверьте targeted tests для refund-сценариев" >&2
# Проверьте targeted tests для refund-сценариев
Здесь hook не притворяется умнее разработчика. Он просто вовремя напоминает: «Вы задели модуль, где цена забывчивости выше обычной». Новичку такой мягкий сигнал часто полезнее автоматического запуска тяжёлой suite — он ещё и приучает думать о риске изменения.
Хороший testing hook — не тот, что запускает всё. Это hook, который приносит нужный сигнал в нужный момент и не съедает темп. Боретесь после его включения не с багами, а со временем ожидания — значит, автоматизация ушла дальше своей реальной ценности.
7. Признаки того, что hook помогает, а не мешает
У любой автоматизации очень простой экзамен: после недели использования команда должна работать спокойнее, а не напряжённее. Если hook срабатывает не там, пишет слишком много, неожиданно меняет файлы, маскирует исходную ошибку своей собственной или требует отдельного ритуала отключения — он не созрел. Не трагедия — сигнал откатиться на предыдущую ступень лестницы.
Полезно иногда задавать hook три вопроса. Первый: можно ли объяснить его смысл одним абзацем без шаманства? Второй: можно ли отключить его одним понятным действием? Третий: делает ли он проблему меньше, чем сам добавляет шума? Хоть один мутный ответ — сузьте matcher, ослабьте вмешательство или верните hook в log-only.
Особенно важно помнить это в Workflow Kit, который со временем становится общим артефактом команды. Личный эксперимент можно молча пережить. Общий hook, который капризничает, быстро подрывает доверие ко всему automation-слою. А доверие в инженерном workflow хрупкое: теряется за один странный вечер, возвращается неделями.
Поэтому зрелость hook почти всегда выглядит скромно. Он решает маленькую повторяемую боль. Его легко понять и легко выключить. Он не пытается быть умнее всего процесса. Лучший комплимент для automation: она настолько к месту, что о ней почти перестают думать.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ