AI is allowed to build fast inside the architecture. It is not allowed to reinterpret the architecture.
The result first
A grep-based policy check found 104 real violations in a Laravel codebase I had been building for months. My own. It had a tight CLAUDE.md that named the exact patterns being caught, a stack of custom skills, and preflight checks on every deploy. None of it stopped the drift. The first scan that actually watched the code found it in under a second.
I’m not a coder. I plan and architect, and I have AI write the code. The bugs that kept appearing weren’t from a weak model or thin prompts. They came from AI doing one task well and breaking the big picture to do it: using a non-canonical function, hardcoding a value that belonged in an enum, inventing a near-duplicate helper, bypassing a service layer “just this once.” The task got done. The architecture got quietly reinterpreted.
This post is the framework that fixed it. AI Change Control is the enforcement layer that sits under your CLAUDE.md and your skills and makes misbehavior impossible to ship.
Three companion posts go deeper, and there’s a repo you can install today:
- Why Claude Code ignores your CLAUDE.md explains the failure at the root: docs describe intent, they don’t produce outcomes.
- Build the pre-commit policy check is the copy-paste tutorial:
.ai/policy.json, the Python scanner, the git hook. - 104 violations, caught is the full receipts run, every count and remediation.
- The ai-change-control repo has the files. Handing this to an AI to install? Point it at the repo’s INSTALL.md.
Why I’m calling it “AI Change Control”
I’m coining this name on purpose. “Change control” is an established term from ops and release engineering: a process that gates changes so they can’t land without review. That’s exactly what this does for AI-generated code. The “AI” prefix is a scope marker, not marketing. This is change control tuned to the failure modes of AI code assistants: canon bypass, hardcoded values, duplicate primitives, one-off logic in the wrong layer.
If you want to talk about it, use the name. “AI Change Control” is easier to reference than “that policy.json thing with the pre-commit hook.”
Soft enforcement vs hard enforcement
Your system is soft if the AI can still hardcode a value where canon exists, invent a near-duplicate helper, bypass the canonical service, or add one-off logic in a controller. If any of those are possible, drift isn’t likely. It’s guaranteed. The only question is how fast.
The fix is high velocity inside hard guardrails. AI should be free inside a lane and hit a wall when it leaves the lane, and the wall has to be machine enforced, not prompt enforced. Prompts shape behavior. Enforcement controls outcomes. I’d spent months treating those as the same thing.
Soft system: rules live in CLAUDE.md, skills, and prompts. AI is “encouraged” to follow canon.
Hard system: rules live in CI, static analysis, and pre-commit hooks. AI cannot ship code that violates canon.
Before the framework itself, the most common misread:
AI Change Control is: a cheap, grep-based first wall that catches 80% of drift in under a second, wired into pre-commit and CI. Zero dependencies. Works the same on Laravel, WordPress, Astro, or any stack.
AI Change Control is not: PHPStan. Not ESLint. Not Larastan. Not a replacement for AST analysis. Not semantic. Not a linter.
Why this matters: AST-based tools are more precise but expensive to set up and tune. Grep-based tools are imprecise but instant and portable. Ship the cheap wall first, let it catch the common drift, and only reach for AST tooling for the semantic patterns grep genuinely can’t see. Skip the cheap wall to go straight to the expensive one and you’ll spend weeks on config and still have hardcoded values in your commits.
The framework
Core principle
Fast by default. Strict where it matters.
Not every change needs ceremony. A blade tweak shouldn’t take 30 minutes. A shared service change should hit multiple walls before it lands.
The four layers
Layer 1: Canon. The blessed building blocks. Enums, DTOs, canonical helpers, query builders, service classes, component families, response shapes, event schemas. Rule: if something is used in 3+ places or affects shared behavior, it becomes canon.
Layer 2: Boundaries. What may talk to what. Controllers call actions or services, not raw DB. Blades use approved components. Feature code can’t bypass domain abstractions. Rule: every layer has allowed and forbidden dependencies.
Layer 3: Enforcement. The missing piece in most setups. PHPStan custom rules, pre-commit greps, Pint, architecture tests, CI failure on forbidden patterns, banned imports. Rule: if a rule matters, it must be machine enforced.
Layer 4: Verification. Catches what enforcement misses. Smoke tests, route tests, page load checks, critical flow tests, log checks. Rule: verification is the backstop, not the primary defense.
Change tiers
This is what prevents overkill. Not every change needs the same rigor.
| Tier | Scope | Examples | Requirements |
|---|---|---|---|
| Tier 1 | Local UI | Copy, blade tweaks, styling, presentational components | Existing components only. No new abstractions. Smoke test. No ceremony. |
| Tier 2 | Feature | New view logic, endpoints, form handling, data shaping | Must use canon. No bypassing service/action layer. Targeted tests. Static checks. Flow smoke test. |
| Tier 3 | Shared/core | Events, auth, billing, tracking, alerting, schema, shared services | Explicit canon review. Update/create enforcement if introducing new canon. Full static checks. Regression tests. Audit impacted surfaces. |
Simple rule: the more shared the blast radius, the less freedom the AI gets.
The non-negotiables
Architectural Non-Negotiables
Do
- One concept, one primitive. If EventType exists, raw event strings are banned
- One layer of responsibility. Controllers don’t quietly grow business logic
- No local convenience if canon exists. AI loves shortcuts, ban them
- No duplicated “almost same” helpers. This is where drift starts
- Every important convention must become enforcement. If it only lives in docs, it’s optional
Prompts vs enforcement
I kept conflating these. They do different jobs.
Prompts are for: intent, scope, style, process, caution, preferred path
Enforcement is for: forbidden patterns, architecture boundaries, canonical primitives, banned literals, banned imports, required usage of shared helpers
Why it matters: prompts shape behavior, enforcement controls outcomes. If you want a guarantee, you need enforcement.
The workflow
Step 6 is underrated. It surfaces the shortcuts you’d otherwise never see.
Metrics that actually matter
Don’t track vanity stuff like “more tests.” Track three things:
- Drift: hardcoded canon violations caught, banned pattern violations caught in CI, duplicate helpers introduced, legacy bypasses still present.
- Stability: regressions from AI changes, rollback count, bug count by tier, changes passing checks but failing at runtime.
- Velocity: time to ship Tier 1, 2, and 3, plus the percent of changes blocked by enforcement.
The target: drift down, regressions down, velocity preserved for Tier 1 and Tier 2.
How to actually implement it (the three-layer split)
The framework above is the what. This is the how. The key insight: you don’t pick one implementation mechanism. You use three, with clearly separated jobs. Trying to make any single layer do all three is exactly how I ended up with a tight system that still drifts.
CLAUDE.md: what matters. The constitution. Short, load-bearing principles.
Skills: how to work. Operating procedures. Thinking modes, workflows, audits.
Machine enforcement: what is allowed. The guardrails. CI, pre-commit, static analysis.
Layer 1, CLAUDE.md, stays narrow. Load-bearing defaults only: architecture first, extend before invent, canon over convenience, don’t bypass shared primitives, identify the existing pattern before coding. If it grows past ~30 lines of principles, it becomes wallpaper and the model stops weighting it. Resist the urge to fix enforcement problems by stuffing more rules into it.
Layer 2, skills, is process enforcement. /think to pause and assess, a /checkfirst-style pattern discovery skill, /audit, /fix, /todo, /verify. Skills improve odds. They shift the average. They cannot prevent a violation. A determined model or a tired orchestrator can skip them, rush them, or read them loosely.
Layer 3, machine enforcement, is the part I didn’t have. Start cheap: pre-commit grep bans on changed files, the same bans in CI on every push, one or two PHPStan custom rules for your most recurring violations, architecture tests later once you know which boundaries matter in practice.
The rules themselves stack in three tiers, and this matters once you run more than one repo. A universal core (debug leftovers, generated-file edits, raw env access) is the same everywhere. Stack packs tune to a framework’s failure modes: thin controllers and no env() in app code for Laravel, no $wpdb for WordPress, content-collection discipline for Astro. Project canon is where the real leverage lives, because it encodes what’s load-bearing about this codebase: your services, your enums, your named routes, your banned legacy paths. Universal rules keep things sane. Stack packs catch stack-specific drift. Project canon catches the bugs that are actually yours. The build tutorial walks through all three.
One more reframe worth naming, because I wasted weeks on it. The goal is not “AI needs to understand the whole codebase.” More context, more memory, bigger windows: wrong abstraction. Even humans don’t hold a whole system in working memory. The job is to make local changes obey global rules by default, which is a constraint problem, not a comprehension problem.
The blunt version
If you only do layers 1 and 2, you will improve behavior but still get drift. I’m living proof. If you add layer 3, you change the system. That’s the whole game.
The shortest version
AI Change Control, Compressed
Do
- Classify change by risk
- Discover canon before coding
- Extend, don’t invent
- Enforce architecture automatically
- Verify based on blast radius
- Report what canon was used
You do not need AI to understand the whole codebase. You need a codebase where understanding the whole codebase is not required to make a correct local change.
Go deeper
The framework is the shape. The three spokes are the detail.
- The why: Why Claude Code ignores your CLAUDE.md. The case study in why you can’t trust your own docs. I wrote the rules myself and my own code still broke them, because docs describe intent and enforcement produces outcomes.
- The build: Build the AI pre-commit policy check. The copy-paste tutorial.
.ai/policy.json, the ~140-line Python scanner, the git hook, and the universal vs project vs stack-pack rule structure. - The receipts: 104 violations in a codebase I built myself. The full run. 75 style violations, then 29 hardcoded routes, then the 4 real bugs the AST layer caught after grep was clean.
- Install it: the ai-change-control repo. The files, the INSTALL.md, ready to drop into any repo.
Related:
- What 17 Retrospectives Taught Me About Coding With AI: why these rules became a hook instead of a CLAUDE.md note
- Custom Skills for Claude Code: the skill layer (Layer 2) that complements this framework’s enforcement layer (Layer 3)