Нужен был способ найти неиспользуемые npm-пакеты и мёртвый код в Next.js-проекте, не перебирая вручную каждый импорт. Стандартная рекомендация в русскоязычных гайдах — depcheck, но этот пакет официально архивирован в 2025 году, а актуальная замена (knip) — статический анализатор без понимания контекста задачи: находит неиспользуемое, но не объясняет, безопасно ли удалять, и не проверяет transitive-зависимости. Решение — плагин ponytail для Claude Code с командой аудита /ponytail-audit, которая сканирует весь репозиторий через LLM-агента и выдаёт список того, что можно смело удалить, с обоснованием каждой находки. За один прогон на своём лендинге я вычистил 43 неиспользуемые зависимости, 2985 строк мёртвого кода — и заодно обнаружил security-валидатор блога, который год как ничего не проверял.
Ниже — установка ponytail за три команды, разбор всех шести команд плагина с примерами использования, и полная история аудита на реальном проекте: от первой чистки package.json до найденного бага в собственных скриптах.
Стек AI-инструментов
- Claude Code — CLI-агент, в котором запускался весь аудит
- ponytail — плагин-маркетплейс с командами
/ponytail,/ponytail-audit,/ponytail-review,/ponytail-debt,/ponytail-gain,/ponytail-help - grep / pnpm — для верификации находок агента вручную, прежде чем что-то удалять
Установка ponytail за три команды
Ponytail устанавливается прямо в диалоге с Claude Code, без правки конфигов вручную:
/plugin marketplace add DietrichGebert/ponytail
/plugin install ponytail@ponytail
/reload-plugins
После перезагрузки плагинов становятся доступны все команды ниже. По умолчанию активен режим full — сбалансированная строгость между "ничего не трогать" и "удалять всё подряд".
Гайд по командам ponytail для аудита кода
/ponytail — постоянный режим для кодинга
Переключатель поведения агента на всю сессию: перед тем как писать любой код, агент проходит через цепочку вопросов — нужна ли эта абстракция вообще, есть ли уже готовое решение в кодовой базе, закрывает ли задачу стандартная библиотека, хватит ли одной строчки. Три уровня интенсивности:
/ponytail lite — построит что просили, но подскажет более простую альтернативу
/ponytail — режим по умолчанию, вся цепочка вопросов обязательна
/ponytail ultra — максимальный YAGNI, оспаривает само требование в ответе
Пример разницы на задаче "добавь кэш для этих API-ответов":
- lite: сделает кэш-класс, но добавит — "кстати,
functools.lru_cacheрешает это в одну строку". - full: сразу поставит
@lru_cache(maxsize=1000), ничего лишнего не напишет. - ultra: откажется добавлять кэш вообще, пока профайлер не покажет, что он нужен.
/ponytail-review — ревью текущего diff на переусложнение
Смотрит только на незакоммиченные изменения и ищет исключительно избыточную сложность — не баги, не безопасность. Формат вывода:
L42: yagni: factory на один продукт. Инлайн.
L88: stdlib: свой email-валидатор на 30 строк. re.match с одним regex.
Ничего не правит, только показывает список — удобно прогонять перед каждым коммитом.
/ponytail-audit — аудит всего репозитория на мёртвый код
Ключевая команда для этой истории. В отличие от /ponytail-review, сканирует не diff, а весь проект целиком: неиспользуемые зависимости в package.json, мёртвые файлы, дублирующийся код, конфиги без единого читателя. Тоже только список, ничего не применяет автоматически.
/ponytail-debt — реестр отложенных решений
Когда агент в режиме full/ultra сознательно срезает угол, он обязан оставить маркер:
# ponytail: global lock, per-account locks if throughput mattersКоманда /ponytail-debt собирает все такие пометки по проекту в один список — честный технический долг вместо забытых компромиссов.
/ponytail-gain — сводка измеренного эффекта
Показывает усреднённые цифры с бенчмарков авторов плагина — на сколько в среднем ponytail сокращает код и стоимость на типовых задачах. Не про конкретно ваш проект, но даёт порядок величин.
/ponytail-help — шпаргалка по командам
Выводит ту же таблицу команд прямо в чате.
Как выглядит реальный аудит неиспользуемых зависимостей
Дальше — что происходило у меня при прогоне /ponytail-audit на лендинге 79z.ru.
Раунд 1: находим 43 мёртвые зависимости в package.json
Первым делом агент открыл package.json и увидел 26 пакетов @radix-ui/react-* — accordion, dialog, dropdown-menu, tooltip, весь набор компонентов. Мой сайт — терминал в стиле Pip-Boy из Fallout, без модалок и тултипов. Явное подозрение на мёртвый вес, но проверять нужно строго: одно неверное удаление ломает сборку.
Агент прогнал grep по каждому пакету отдельно, чтобы получить доказательство, а не догадку:
for pkg in accordion alert-dialog dialog select tabs tooltip; do
grep -rl "@radix-ui/react-$pkg" --include="*.tsx" . | grep -v node_modules
doneНоль совпадений по всем 26 пакетам. Плюс ещё 15 сверху — recharts, zod, react-hook-form, date-fns, sonner — тоже без единого импорта. Это был остаток UI-конструктора от v0.dev-скаффолда, которым я так и не начал пользоваться. Даже конфиг components.json указывал на папку @/components/ui, которой физически не существовало на диске.
41 зависимость и мёртвый конфиг ушли одним коммитом.
Раунд 2: находим сломанный security-валидатор блога
На втором проходе я попросил ponytail не искать зависимости, а посмотреть на код репозитория целиком — восемь параллельных под-агентов, каждый со своим фокусом: корректность логики, повторное использование, эффективность, соответствие внутренним правилам проекта.
Один из них зацепился за три кастомных валидатора, встроенных в pnpm build: проверку канонических ссылок, HTML-безопасность блога, стилевую политику. Мой блог когда-то переехал с формата .html на .mdx. Валидаторы — нет:
const htmlPath = join(blogPostsDir, dir.name, "index.html")
try {
readFileSync(htmlPath, "utf-8")
validateHtmlFile(htmlPath)
} catch {
// File doesn't exist or can't be read, skip
}Три последних поста существовали только как .mdx, без .html рядом. Валидатор пытался прочитать несуществующий файл, ловил исключение — и молча пропускал пост. Я запустил проверку вручную и увидел зелёную строку "All blog posts passed security validation" — притом что треть контента вообще не сканировалась на встроенный исполняемый код и обработчики событий в разметке.
Как чинили: обе стороны разрыва, а не одну
Проверка дерева показала: семь из десяти директорий постов тащили мёртвый index.html рядом с рабочим .mdx — остаток старой миграции. Удалил все семь, переписал оба валидатора на чтение frontmatter через gray-matter:
function extractUrlsFromMdxFrontmatter(content: string): string[] {
const { data } = matter(content)
const urls: string[] = []
if (typeof data.canonical === "string") urls.push(data.canonical)
if (typeof data.ogImage === "string") urls.push(data.ogImage)
return urls
}Заодно всплыл третий валидатор, который вообще никогда не мог провалить сборку: при найденном расхождении стилей он печатал предупреждение и всё равно завершался успешно.
Раунды 3–5: дублирование, orphan-файлы, мёртвые компоненты
Каждый следующий прогон /ponytail-audit находил что-то новое — не потому что код портился на глазах, а потому что первый проход искал явно мёртвые зависимости, а не связи между файлами.
Раунд 3 вскрыл дублирование в хелпере блога: две функции параллельно читали один и тот же файл поста с разным кастингом типов. Свёл к одной внутренней функции с двумя тонкими публичными вызовами. Раунд 4 нашёл зависимость rehype-raw, ни разу не подключённую к рендерингу markdown, и orphan-файл стилей на 125 строк. Раунд 5 — две функции с идентичным телом в конфиге сайта и компонент-обёртку темы, ни разу не смонтированную ни в одном layout.
Итог по цифрам
Пять коммитов, каждый проверен полной сборкой перед публикацией:
- -43 зависимости из
package.json - -2985 строк в репозитории целиком, -839 без служебного lock-файла
- 7 мёртвых файлов постов и 5 неиспользуемых картинок-заглушек
- 1 настоящий security-баг — два валидатора, которые год как молча пропускали треть контента блога
Похожая история с ловлей неочевидного бага в проде описана в посте про плагин opencode-rate-limit — там баг был не в мёртвом коде, а в API-лимитах моделей.
Pro Tips: Prompting and Execution
- Не верь находке агента на слово — воспроизведи её. Перед тем как чинить баг с валидатором, я запустил его вручную и увидел ложную зелёную галочку своими глазами.
- Гоняй сборку после каждого удаления, а не после всех сразу. Откатить один неудачный коммит проще, чем распутывать пачку из сорока изменений.
- Повторяй
/ponytail-auditнесколько раз подряд. Первый проход ловит явные зависимости, настоящие структурные находки всплывают только после того, как убран верхний слой мусора.
Common Pitfalls
- Удалить зависимость, не проверив transitive peer. Библиотека для подсветки кода в постах выглядела неиспользуемой по прямому импорту, но была обязательным peer-условием для другого пакета — удали её не глядя, сломались бы все посты с кодом.
- Принять зелёную галочку валидатора за факт, а не за гипотезу. "Все посты прошли проверку" может означать "все файлы, которые я умею находить, прошли" — а не "все посты реально проверены".
- Полагаться только на автоматическое удаление без ручного grep. Агент даёт список кандидатов, но финальное решение и проверку компиляции стоит делать самостоятельно.
- Ждать от статического анализатора того же, что даёт LLM-агент. Инструменты вроде knip находят неиспользуемые файлы и экспорты быстро и без токенов, но не объясняют, почему что-то мёртвое и безопасно ли его удалять — для этого решения нужен контекст, который статический анализ не видит.