Setting Up an Environment to Cut Out Predictable Boilerplate Generated by AI Agents
When you hook an AI agent up to a real-world development environment, its limitations quickly show. Without knowing the domain context, it often mechanically overuses factory patterns or endlessly outputs useless utility functions. According to GitClear's 2024 commit analysis report covering 623 million lines of code, code duplication increased by 81% after AI tools proliferated, while refactoring to clean up existing code dropped from 25% to under 10%. Due to the recency bias of context windows, agents repeatedly retreat to the most common standard patterns in their training data to avoid risk. You need to suppress agent malfunctions by explicitly specifying the team's shared architectural rules and synchronization logic in context files.
1. Blocking Rule Deviations in Legacy Codebases
To control guidelines that vary across development tools, you should first add AGENTS.md to the project root as a single source of truth. Tools like Cursor and Claude Code read these rules. The single .cursorrules file was deprecated long ago. Split them into MDC files inside the .cursor/rules/ directory, and in the Claude Code environment, load @AGENTS.md inside CLAUDE.md to prevent rules from leaking through.
`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.
`
When the total context length exceeds 800 lines (roughly 2,000 tokens), the agent starts ignoring the constraints listed at the top. Keep only non-negotiable prohibitions in AGENTS.md, and push detailed Architecture Decision Records out to the docs/context/ directory.
Constraint Enforcement Steps
- Task: Select 1 core module and specify forbidden patterns and exception rules.
- How to Execute:
- Create
AGENTS.md in the project root and state conditions forbidding single-implementation interfaces and defensive try-catch blocks.
- Set target file paths (
globs) and YAML Frontmatter constraints in .cursor/rules/backend-constraints.mdc.
- Add
@AGENTS.md syntax to CLAUDE.md to align environments.
- Expected Result: Unnecessary boilerplate generation drops, shaving off around 4 hours per week spent on code modifications.
2. Agent Code Verification Pipeline
Traps lurk behind an agent's plausible-looking code. According to Veracode's 2024 research findings, OWASP Top 10 security vulnerabilities were found in 45% of AI-suggested code. It hides errors by lazily wrapping failing logic in try-catch blocks and returning empty objects or nulls 47% more often than human writers. Missed optimistic locks in Read-Modify-Write operations and N+1 query problems from hitting the DB inside loops also surface frequently.
| Verification Area |
Detailed Checks |
Risk Patterns & Agent Traps |
Merge Block Criteria |
| Security Vulnerabilities |
Parameterized Query usage, tenant isolation, unauthorized packages |
String-concatenation-based SQL injection, unverified package calls from hallucinations |
Block on missing input validation or addition of external dependencies with unclear origins |
| Performance Bottlenecks |
ORM lazy loading, nested loops in hot paths, DB indexes |
Individual entity traversal queries inside loops, full table filtering in memory |
Block if DB and external API calls exist inside loops; block if pagination is missing |
| Type Safety |
Strict Type checking, boundary exception handling, concurrency control |
Indiscriminate as any, hiding errors with empty catch blocks |
Block on presence of any and reckless as casting; block unlogged catch blocks |
Test code written by agents easily degrades into tautological tests that merely copy-paste the implementation code. These tests fail completely at catching actual business defects. You must strictly enforce the rule: "If a utility already exists, delete the agent-created utility and reuse the existing code."
Manual Verification Procedure
- Task: Populate task templates and CI gates with checklists verifying security, performance, and types.
- How to Execute:
- Insert Parameterized Query, N+1 query prevention, and
any prohibition items into the PR template.
- Attach static analysis tools to Git Pre-commit Hooks to reject commits when catching
as any or await inside loops.
- During reviews, forcibly replace newly generated agent utilities with existing shared modules.
- Expected Result: Prevents defects like N+1 queries or memory leaks from slipping through into production.
3. Decomposed Prompting to Prevent Cognitive Stagnation
If you shove complex settlement logic or state-machine-based order processing into a single prompt, the agent gets stuck in tool-call loops or spits out hollow shell code. This happens because token allocation order gets tangled while trying to handle schema design, API interfaces, exception handling, and business rules all at once.
`
[Stage 1: Data Modeling] -> Generate DB entities, Zod schemas
│
▼ (Pass output as context)
[Stage 2: Interface Definition] -> Define API DTOs, Custom Errors, Service Signatures
│
▼ (Pass Stage 1+2 outputs as context)
[Stage 3: Business Logic Implementation] -> Complete transactions, state transitions, concurrency control
`
Handling this sequence manually back and forth is quite tedious. It is better to write a CLI automation script (scripts/agent-decomposed-build.ts) to wire output from previous stages as input context for subsequent stages.
`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.);
}
`
Building a Decomposed Prompting Script
- Task: Establish a 3-stage prompt template and publish a CLI script that executes them sequentially.
- How to Execute:
- Break prompts into 3 parts: Data Modeling, Interface Definition, and Business Logic Implementation.
- Write
scripts/agent-decomposed-build.ts to chain them so previous output enters the next prompt's context.
- Execute this script when creating complex new modules.
- Expected Result: Eliminates instances where agents freeze up without generating answers, dropping the rework rate of manually rewriting code from the 20% range down to under 5%.
4. Setup to Leave Rationale Inside the Code
The more you use AI coding tools, the more "write-only code" accumulates lacking explanation for why it was written that way. Code missing recorded architectural trade-offs becomes a burdensome liability for humans to maintain later. You must enforce via AGENTS.md that agents include standard TSDoc comments inside the source code when generating it.
`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);
}
}
`
To prevent agent configuration drift across developers, keep AGENTS.md as the single source of truth, and run a script (tools/sync-agent-rules.ts) upon committing to synchronize with CLAUDE.md and .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();
`