Настройка рабочего окружения для отсечения базового boilerplate-кода, генерируемого ИИ-агентами
Стоит подключить ИИ-агента к реальной среде разработки, как его ограничения тут же вылезают наружу. Не понимая контекста предметной области, он начинает механически штамповать шаблон «Фабрика» или бесконечно генерировать абсолютно бесполезные утилитарные функции. Согласно отчету GitClear за 2024 год, основанному на анализе 623 миллионов строк коммитов, после широкого распространения ИИ-инструментов дублирование кода выросло на 81%, а доля рефакторинга, направленного на упорядочение существующего кода, упала с 25% до менее чем 10%. Из-за смещения контекстного окна в сторону свежести (recency bias) агент, пытаясь избежать рисков, постоянно сбегает к наиболее распространенным стандартным паттернам из обучающих данных. Чтобы подавить сбои в работе агента, необходимо явно зафиксировать архитектурные правила и логику синхронизации команды в виде файлов контекста.
1. Предотвращение отклонений от правил в легаси-кодбазе
Чтобы контролировать инструкции, которые различаются от инструмента к инструменту, первым делом следует разместить в корне проекта файл AGENTS.md, выступающий в роли единого источника правды (Single Source of Truth). Такие инструменты, как Cursor и Claude Code, читают эти правила. Единый файл .cursorrules уже давно устарел. Разбейте правила на MDC-файлы в директории .cursor/rules/, а в окружении Claude Code подключайте @AGENTS.md внутри CLAUDE.md, чтобы предотвратить «утечку» и игнорирование правил.
`yaml
description: Backend API and Domain Service architectural constraints
globs: apps/api//*.ts, services//*.ts
alwaysApply: false
Backend Architectural Constraints
Mandatory Patterns
- Use Canonical Response DTOs located in
apps/api/src/common/dto.
- All database mutations must go through the Unit of Work pattern defined in
services/shared/uow.
Explicit Negative Constraints
- NEVER create single-child base classes or single-implementation interfaces.
- NEVER write wrapper functions that merely pass arguments to lower-level services.
- NEVER implement strategy or factory patterns when standard conditional logic (if/switch) suffices.
- NEVER add defensive "just-in-case" try-catch blocks that return dummy fallback values without rethrowing.
`
Если общая длина контекста превышает 800 строк (приблизительно 2000 токенов), агент начинает игнорировать ограничения, записанные в самом начале. В AGENTS.md следует оставлять только действительно критические запреты, а подробные записи архитектурных решений (ADR) выносить в директорию docs/context/.
Порядок применения ограничений
- Суть задачи: Выбрать 1 ключевой модуль и определить для него запрещенные паттерны и правила-исключения.
- Порядок выполнения:
- Создать
AGENTS.md в корне проекта и прописать в нем запрет на интерфейсы с единственной реализацией и защитные блоки try-catch.
- В файле
.cursor/rules/backend-constraints.mdc настроить пути к целевым файлам (globs) и ограничения YAML Frontmatter.
- Добавить синтаксис
@AGENTS.md в CLAUDE.md для синхронизации окружения.
- Ожидаемый результат: Сокращается генерация ненужного boilerplate-кода, а время, затрачиваемое на правки кода, уменьшается примерно на 4 часа в неделю.
2. Пайплайн валидации кода ИИ-агента
За правдоподобным кодом агента часто скрывается ловушка. Согласно исследованию Veracode 2024 года, в 45% кода, предложенного ИИ, были обнаружены уязвимости безопасности уровня OWASP Top 10. Попытки скрыть ошибки, грубо обернув потенциально сбойную логику в try-catch и возвращая пустой объект или null, происходят на 47% чаще, чем при написании кода человеком. Также регулярно возникают проблемы с отсутствием оптимистичной блокировки в операциях Read-Modify-Write или проблемы N+1 из-за постоянных вызовов БД внутри циклов.
| Область проверки |
Детализируемые пункты проверки |
Опасные паттерны и ловушки агента |
Критерий блокировки мёрджа |
| Уязвимости безопасности |
Использование Parameterized Query, изоляция тенантов, проверка неавторизованных пакетов |
SQL-инъекции на основе конкатенации строк, вызовы непроверенных пакетов из-за галлюцинаций |
Блокировка при отсутствии валидации входных данных или добавлении сторонних зависимостей неизвестного происхождения |
| Узкие места производительности |
Ленивая загрузка ORM, вложенные циклы в Hot path, индексы БД |
Запросы с обходом отдельных сущностей внутри цикла, фильтрация всей таблицы в памяти |
Блокировка при наличии вызовов БД или внешних API внутри циклов, а также при отсутствии пагинации |
| Типобезопасность |
Строгая проверка типов (Strict Type), обработка исключений на границах, контроль конкурентности |
Злоупотребление as any, сокрытие ошибок с помощью пустых блоков Catch |
Блокировка при наличии any и необдуманного приведения типов as, а также блоков Catch без логирования |
Тестовый код, который пишет агент, часто сводится к банальному дублированию и копированию кода реализации. Такие тесты абсолютно не способны выявить реальные бизнес-дефекты. Необходимо жестко применять правило: «Если уже существует готовая утилита, агент должен удалить созданную им новую утилиту и повторно использовать существующий код».
Процедура ручной проверки
- Суть задачи: Внедрить чек-лист проверки безопасности, производительности и типов в шаблоны задач и CI-гейты.
- Порядок выполнения:
- Добавить в шаблон PR пункты о Parameterized Query, предотвращении N+1 запросов и запрете
any.
- Настроить Pre-commit Hook в Git с использованием инструментов статического анализа, чтобы отклонять коммиты при обнаружении
as any или await внутри циклов.
- При проведении код-ревью принудительно заменять вновь созданные агентом утилиты на существующие общие модули.
- Ожидаемый результат: Предотвращение попадания в продакшен дефектов вроде N+1 запросов или утечек памяти.
3. Декомпозированный промптинг для предотвращения «зависания» агента
Если попытаться затолкнуть сложную логику взаиморасчетов или обработку заказов на основе конечного автомата в один промпт, агент зациклится на вызове инструментов или выдаст пустую пустышку вместо кода. Это происходит потому, что порядок распределения токенов путается при попытке одновременно обработать проектирование схемы, API-интерфейсы, обработку исключений и бизнес-правила.
`
[Этап 1: Моделирование данных] -> Создание сущностей БД, схем Zod
│
▼ (передача результата в контекст)
[Этап 2: Определение интерфейсов] -> Определение API DTO, Custom Error, сигнатур сервисов
│
▼ (передача результатов этапов 1+2 в контекст)
[Этап 3: Реализация бизнес-логики] -> Завершение транзакций, переходов состояний, контроля конкурентности
`
Передавать всё это вручную довольно хлопотно. Намного эффективнее написать CLI-скрипт автоматизации (scripts/agent-decomposed-build.ts), который свяжет результат предыдущего шага с входным контекстом следующего.
`typescript
import { execSync } from 'child_process';
import * as fs from 'fs';
interface TaskPipeline {
featureName: string;
stage1Prompt: string;
stage2Prompt: string;
stage3Prompt: string;
}
async function runDecomposedAgentPipeline(pipeline: TaskPipeline) {
console.log([Stage 1] Executing Data Modeling for ${pipeline.featureName}...);
const stage1Output = execSync(claude --print "${pipeline.stage1Prompt}").toString();
fs.writeFileSync(./tmp/${pipeline.featureName}_stage1.ts, stage1Output);
console.log([Stage 2] Executing Interface Definition...);
const stage2InputPrompt = ${pipeline.stage2Prompt}\n\nContext Models:\n${stage1Output};
const stage2Output = execSync(claude --print "${stage2InputPrompt}").toString();
fs.writeFileSync(./tmp/${pipeline.featureName}_stage2.ts, stage2Output);
console.log([Stage 3] Executing Business Logic Implementation...);
const stage3InputPrompt = ${pipeline.stage3Prompt}\n\nContext Models:\n${stage1Output}\n\nContext Contracts:\n${stage2Output};
const stage3Output = execSync(claude --print "${stage3InputPrompt}").toString();
fs.writeFileSync(./src/services/${pipeline.featureName}.service.ts, stage3Output);
console.log([Pipeline Complete] Business logic generated cleanly without cognitive stagnation.);
}
`
Построение скрипта декомпозированного промптинга
- Суть задачи: Сформировать 3-этапный шаблон промптов и выложить CLI-скрипт для их последовательного выполнения.
- Порядок выполнения:
- Разбить промпт на 3 части: моделирование данных, определение интерфейсов и реализация бизнес-логики.
- Написать
scripts/agent-decomposed-build.ts, связывающий вывод предыдущего этапа с контекстом следующего промпта.
- Запускать этот скрипт при создании новых сложных модулей.
- Ожидаемый результат: Исчезает эффект «зависания» агента, когда он не может выдать ответ, а доля ручно переписываемого кода снижается с 20% до менее чем 5%.
4. Настройка сохранения причин принятых решений прямо в коде
Чем активнее используются ИИ-инструменты разработки, тем больше накапливается «кода только для записи» (write-only code), в котором отсутствуют причины принятия тех или иных решений. Код, в котором не зафиксированы компромиссы (trade-offs) архитектурного выбора, позже становится тяжелым обузой для разработчиков-людей. В AGENTS.md необходимо прописать требование, чтобы агент при генерации кода обязательно оставлял стандартные комментарии TSDoc прямо в исходном коде.
`typescript
/**
- @description Processes deferred settlement payouts for multi-vendor orders.
- @why Uses pessimistic database locking on the Wallet entity instead of optimistic locking because payout calculation involves high-frequency concurrent balance updates.
- @tradeoff Slight P95 latency increase under high contention in exchange for 0% financial drift.
- @complexity Time: O(N log N) due to vendor sorting | Space: O(N) for batch processing buffer.
*/
export async function processDeferredSettlement(orderId: string): Promise {
if (account.hasOutstandingBalance()) {
this.applySettlementHold(account);
}
}
`
Чтобы предотвратить расхождение настроек агента у разных разработчиков, следует сделать AGENTS.md единым источником правды и перед коммитом запускать скрипт синхронизации (tools/sync-agent-rules.ts), который обновляет CLAUDE.md и .cursor/rules/global.mdc.
`typescript
import * as fs from 'fs';
import * as path from 'path';
const AGENTS_MD_PATH = path.join(__dirname, '../AGENTS.md');
const CLAUDE_MD_PATH = path.join(__dirname, '../CLAUDE.md');
const CURSOR_RULE_PATH = path.join(__dirname, '../.cursor/rules/global.mdc');
function syncRules() {
if (!fs.existsSync(AGENTS_MD_PATH)) {
console.error('Error: AGENTS.md does not exist.');
process.exit(1);
}
const baseRules = fs.readFileSync(AGENTS_MD_PATH, 'utf-8');
const claudeContent = # AUTOMATICALLY GENERATED FROM AGENTS.md - DO NOT EDIT DIRECTLY\n\n${baseRules};
fs.writeFileSync(CLAUDE_MD_PATH, claudeContent);
const mdcHeader = ---\ndescription: Global Agent Rule Sync\nglobs: **/*\nalwaysApply: true\n---\n\n;
fs.writeFileSync(CURSOR_RULE_PATH, ${mdcHeader}${baseRules});
console.log('Successfully synchronized AGENTS.md to CLAUDE.md and Cursor MDC rules.');
}
syncRules();
`