Stop Burning Tokens: Force Your AI Coding Agent to Honor Your Boilerplate
Your AI coding assistant is eating your tokens and burning through context windows because it acts like your codebase was created five minutes ago. If you have ever watched Cursor, Claude Code, or Devin spent 10 minutes reimplementing a custom authentication wrapper, setting up duplicate Tailwind classes, or writing a custom database client that already exists in lib/db.ts, you are losing money and polluting your repository.
The problem isn't that current Large Language Models are bad at writing software. The real issue is that modern AI agents operate with short-term amnesia, favoring net-new code generation over structural discovery unless you explicitly restrict their context window and force strict architectural awareness.
When you prompt an agent on a massive, production-grade starter kit or company boilerplate, its default failure mode is non-destructive creation. It won't look for existing abstractions; it will take the path of least resistance and write a inline, duplicate helper function from scratch every single time.
The Problem Everyone Ignores
Most developers assume that giving an AI agent full access to a workspace via a vector search index or dynamic directory tree resolves context limitations. In practice, code generation models rely heavily on prompt recency and high-confidence patterns present in their training data. When an agent receives a prompt like "Add a Stripe checkout route," it matches the pattern against thousands of generic Stripe integrations learned during training, completely ignoring your boilerplate's specific lib/stripe/client.ts wrapper.
This dynamic creates a insidious architectural anti-pattern known as boilerplate drift. Over a few weeks of "vibe coding," your clean, highly opinionated starter kit transforms into a Frankenstein codebase. You end up with three separate database clients, four competing UI component patterns, and duplicate error-handling middlewares spread randomly across your API routes.
WITHOUT STRICT AGENT CONSTRAINTS
┌─────────────────────────────────────────┐
│ Your Production Starter │
│ (lib/db.ts, components/ui, auth.ts) │
└────────────────────┬────────────────────┘
│
Agent receives prompt:
"Add user profile settings"
│
▼
┌─────────────────────────────────────────┐
│ Agent Ignores Existing Structure │
│ - Writes new raw 'pg' client inline │
│ - Re-invents form validation helpers │
│ - Spawns duplicate CSS primitives │
└────────────────────┬────────────────────┘
│
▼
Codebase Fragmentation
Beyond the architectural mess, this behavior causes severe token inflation. Every time an agent recreates existing primitives, it generates 200 lines of code where 10 lines of imports and configuration would have sufficed. You end up paying real money in API costs for the privilege of having duplicate, unmaintainable code checked into your repository.
Worst of all, these duplicate patterns break your application's security and telemetry boundaries. If your boilerplate includes built-in rate limiting, request tracing, and audit logging wrapped inside a standard fetch client or ORM layer, an agent bypassing those modules to write raw queries bypasses your security guardrails completely without triggering a traditional linting failure.
What Actually Works
To force an agent to respect and utilize your existing abstractions, you must implement a system of architectural constraints and discovery rules. Instead of treating the agent as a rogue developer who reads the whole repo on every task, you configure a lightweight manifest file, strict file boundaries, and explicit system prompts that act as guardrails.
The core mechanism relies on exposing a deterministic directory manifest and structural contracts in your workspace configuration (e.g., .cursorrules, CLAUDE.md, or custom system instructions). By feeding the agent a curated schema of key primitives before it begins planning a task, you force the LLM to map incoming intent to pre-existing code modules.
// .agent/rules/boilerplate-manifest.ts
// This module provides a deterministic validation contract for AI agents.
// It maps core system features directly to existing implementation files.
export interface BoilerplateContract {
domain: string;
mustUsePath: string;
forbiddenImports: string[];
description: string;
}
export const SYSTEM_CONTRACTS: Record<string, BoilerplateContract> = {
database: {
domain: 'Data Persistence',
mustUsePath: '@/lib/db/client',
forbiddenImports: ['pg', 'mysql2', '@prisma/client'],
description: 'Always import the singleton db client. Do NOT instantiate raw connections.'
},
authentication: {
domain: 'Identity & Auth',
mustUsePath: '@/lib/auth/session',
forbiddenImports: ['jsonwebtoken', 'next-auth'],
description: 'Use custom session helpers. Do NOT implement native JWT parsing.'
},
uiComponents: {
domain: 'Design System',
mustUsePath: '@/components/ui',
forbiddenImports: ['@mui/material', 'bootstrap', 'inline-styles'],
description: 'Use local atomic UI primitives exclusively.'
}
};
This configuration module acts as a structural contract for both human engineers and AI agents. By running static inspection against these rules prior to code generation phases, your orchestration script intercepts prompt processing and injects exact import boundaries directly into the agent's active system prompt.
Step-by-Step: Let's Build It Together
Let's build an automated, zero-dependency validation and constraint framework that enforces boilerplate reuse. We will implement a file watcher, a runtime contract generator, and a custom linter that prevents agents from committing duplicate infrastructure.
Step 1: Define the Workspace System Prompt
We first establish a rigid system prompt file located at the root of the repository. This file serves as the ground truth instruction set for any AI model operating on the workspace, instructing it to run discovery prior to writing any new code.
<!-- CLAUDE.md / .cursorrules -->
# WORKSPACE ARCHITECTURAL RULES
## CRITICAL RULE: DISCOVERY BEFORE CREATION
Before creating ANY new file, utility, helper, or component:
1. Search `lib/` and `components/` for existing implementations.
2. If a utility exists, you MUST import and extend it.
3. NEVER install new dependencies without explicit confirmation.
## FORBIDDEN PATTERNS
- No inline database connection instantiations.
- No direct `fetch()` calls for API routes; use `@/lib/api/client`.
- No raw CSS or third-party UI components outside of `@/components/ui`.
## MANDATORY REUSE MAP
| Domain | Path | Allowed Usage |
| :--- | :--- | :--- |
| DB Client | `lib/db.ts` | `import { db } from "@/lib/db"` |
| Auth Check | `lib/auth.ts` | `import { requireSession } from "@/lib/auth"` |
| Logger | `lib/logger.ts` | `import { logger } from "@/lib/logger"` |
This configuration sets clear context boundaries, explicitly informing the agent which modules are off-limits for duplication and providing exact path mappings for standard tasks.
Step 2: Implement the Pre-Flight Static Inspector
Next, we write a lightweight Node.js script that runs before your AI tool executes code generation or during pre-commit hooks. This script scans staged files or agent-generated changes for violations of your boilerplate contract.
// scripts/verify-agent-compliance.ts
import fs from 'fs';
import path from 'path';
const FORBIDDEN_PATTERNS = [
{ pattern: /import.*from ['"]pg['"]/, fix: 'Use import { db } from "@/lib/db"' },
{ pattern: /import.*from ['"]jsonwebtoken['"]/, fix: 'Use import { verifySession } from "@/lib/auth"' },
{ pattern: /new Pool\(|new Client\(/, fix: 'Instantiating raw DB pools is forbidden.' }
];
function inspectFile(filePath: string): boolean {
if (!fs.existsSync(filePath)) return true;
const content = fs.readFileSync(filePath, 'utf-8');
let hasErrors = false;
FORBIDDEN_PATTERNS.forEach(({ pattern, fix }) => {
if (pattern.test(content)) {
console.error(`\x1b[31m[AGENT ERROR]\x1b[0m Violation in ${filePath}`);
console.error(` -> Found forbidden pattern matching: ${pattern}`);
console.error(` -> Fix: ${fix}\n`);
hasErrors = true;
}
});
return !hasErrors;
}
const targetFile = process.argv[2];
if (targetFile && !inspectFile(targetFile)) {
process.exit(1);
}
This script inspects files targeted or generated by the agent, flagging duplicate instantiations or unauthorized third-party imports before they are saved to your repository.
Step 3: Inject Dynamic Context via Pre-Prompt Hooks
Finally, we tie everything together using a pre-prompt hook script. This tool dynamically scans your project's export tree and injects available primitives directly into the dynamic prompt payload sent to the LLM agent.
// scripts/build-agent-context.ts
import fs from 'fs';
import path from 'path';
function generateExportMap(directory: string): string[] {
const exports: string[] = [];
const files = fs.readdirSync(directory, { recursive: true, withFileTypes: true });
for (const file of files) {
if (file.isFile() && (file.name.endsWith('.ts') || file.name.endsWith('.tsx'))) {
const fullPath = path.join(file.path, file.name);
const content = fs.readFileSync(fullPath, 'utf-8');
const matches = content.match(/export\s+(const|function|class|type)\s+([A-Za-z0-9_]+)/g);
if (matches) {
matches.forEach(m => exports.push(`${m} in ${fullPath.replace(process.cwd(), '')}`));
}
}
}
return exports;
}
const availablePrimitives = generateExportMap('./lib');
console.log('--- AVAILABLE BOILERPLATE PRIMITIVES ---');
console.log(availablePrimitives.slice(0, 20).join('\n'));
console.log('--- END PRIMITIVES MATRIX ---');
By generating a real-time matrix of available primitives, you can automatically append active workspace capabilities directly to your LLM system context, ensuring the model never forgets what already exists.
The Mistakes That Will Burn You
Even with rules and hooks in place, developers frequently make critical errors when attempting to constrain AI coding agents on large boilerplates.
- Mistake 1: Relying entirely on full-codebase vector search. Vector embeddings match semantic intent, not structural dependencies. When an agent searches for "database connection," vector search returns five different places where connections are mentioned, leading the agent to guess instead of enforcing your central client pattern.
- Mistake 2: Writing massive, 50-page instruction files. LLMs suffer from "lost in the middle" phenomena. If your instruction file is thousands of lines long, the agent will ignore mid-file constraint rules and prioritize its pre-trained biases. Keep instructions focused and modular.
- Mistake 3: Failing to enforce hard execution gates. If your validation scripts only issue warnings instead of blocking git commits or build pipelines, your codebase will degrade over time. Agent guardrails must be non-bypassable.
Production Checklist
Before letting an AI agent loose on your production boilerplate, ensure you have established these fundamental verification checkpoints:
-
Enforce strict root-level rule files: Maintain an updated
CLAUDE.mdor.cursorrulesthat lists non-negotiable architectural boundaries. -
Block raw third-party package installations: Prevent agents from executing
npm installoryarn addwithout manual confirmation. - Automate pre-commit static analysis: Set up Husky or custom git hooks to run pattern-matching checks against agent-generated files.
-
Expose clear index files: Maintain clean export barrels (e.g.,
lib/index.ts) so agents can easily locate primitives in a single file lookup. -
Never grant unmonitored write access to core infrastructure: Mark foundational directories like
lib/coreorapp/api/authas read-only for agent operations.
Key Takeaways
- AI agents favor creation over discovery unless explicitly constrained by rules and structural context.
- Unrestricted vibe coding causes boilerplate drift, producing duplicate utilities, security vulnerabilities, and inflated API token costs.
-
Explicit system contracts (
CLAUDE.md, static analysis scripts) force agents to reuse existing abstractions rather than building net-new components. - Automated enforcement gates must run locally to catch and block non-compliant agent output before it hits your primary branch.
Engr. Hamza | AI & MLOps Engineer | Building autonomous systems at the edge of possibility

Top comments (0)