Why your AI assistant doesn't know your framework
Your assistant keeps writing generic PHP even though you're on Laravel / Symfony / UserSpice / WordPress. Here's why — and the cheap fix that stops it.
You're on UserSpice / Laravel / Symfony / CodeIgniter / WordPress / your own internal framework. You ask Claude / Cursor / Copilot to add a form. What you get back is generic PHP that ignores every helper your framework ships, every convention your team established, and every pattern the rest of the codebase uses. You correct it. You re-prompt. You get the same generic answer in a different paint job.
This is a real, structural phenomenon, not "the model being lazy." It's worth understanding so you can fix it instead of fighting it.
The training distribution is not your codebase
A foundation model trained on the open internet has seen, roughly in order of frequency: tutorial PHP from 2008, Stack Overflow answers, WordPress code, Laravel code, generic "build a CRUD app in PHP" blog posts, and — way down the list — code from your particular framework. If you're on something niche or proprietary, it has seen approximately none of it.
When the model has to write code, it draws from what it has seen most often. Its priors are weighted by frequency, not by correctness for your project. "Generic 2015-era PHP" is heavily represented in the training data; "the way you actually do it on UserSpice in 2025" is not.
This is the same reason it reaches for md5(uniqid()) when you ask for a
random token, or mysqli_query() with concatenation when you ask for a query
— those patterns dominated the corpus. The framework-specific better-version is in there
too, but as a needle in a much larger haystack.
Even worse: it doesn't know which framework you're on
The second compounding problem. Even when the assistant has been trained on plenty of your framework's code, it doesn't know that's the framework you're using right now. The conversation gives it a snippet of PHP and a question. It has no signal — beyond what's in the surrounding context window — about whether this is Laravel, Symfony, UserSpice, raw PHP, or something built in-house.
So it guesses. Usually the guess is "Laravel" because Laravel is loud in the training data. If you're on something else, you get Laravel-flavored code that doesn't work, and the assistant looks confident about it because it has no way to know it guessed wrong.
The signs you've hit this:
- Suggested code uses helpers / classes that don't exist in your project.
- Suggested code uses an import / namespace from a framework you don't have.
- Code "almost works" but with subtle differences from your other files.
- The assistant invents method names that sound right (
$user->hasPermission()) when the real one ishasPerm()or vice versa.
The cost adds up
Each individual correction is small. Over a week of vibe-coding it adds up:
- Time spent reading code, spotting "that's not how we do it," prompting again.
- Bugs from almost-right code that passes a casual review but breaks on edge cases (security bugs, hilariously, are the most common shape of this).
- Codebase drift: half of your files are in your framework's idiom, the other half are in generic PHP, and over time it gets harder to know which way is "the way" on this project.
- Onboarding friction: a new developer (human or AI) can't pattern-match because there's no consistent pattern.
A team that vibe-codes through this for six months ends up with a codebase that has more footprints than convictions. It works, but every change has to re-decide first principles.
The fix: tell the assistant
The mechanical solution is unglamorous and works. Write down your framework's conventions somewhere your assistant will read them, and tell your assistant to read them.
On Claude Code, the convention is a CLAUDE.md file at the project root. Claude
reads it on every session. Cursor has .cursorrules. Copilot has
.github/copilot-instructions.md. Every assistant has some equivalent.
The content goes like this, roughly in order of usefulness:
- What framework / stack we're on, by name and version. ("This is a UserSpice 6.0 project using MariaDB and Apache.")
- Which helpers to use first. Name them. CSRF, SQL, escaping, redirects, hashing. ("For CSRF, emit with
tokenHere()and verify withToken::check(Input::get('csrf')).") - Which patterns are wrong for this project. Concrete anti-patterns. ("Never call
mt_rand()for security tokens. Never use$_POSTdirectly; useInput::get().") - Where things live. Where do helpers live, where do AJAX endpoints live, where does config live, what's the override directory.
- Project-specific conventions. Database naming, permission ID meanings, internal API quirks, the things that aren't in any framework manual.
A two-hundred-line CLAUDE.md is the cheapest security upgrade you'll do this
year. The model gets better with every interaction because every interaction has the
right context.
"But I'd have to write it myself"
For a custom in-house framework, yes — only you can write it. (Though you can hand the
task to the assistant: "read the codebase under src/ and draft a CLAUDE.md
describing the conventions and helpers." It'll get most of it right; you edit the rest.)
For a popular framework, someone else has probably done it already. Look for the framework name plus "CLAUDE.md" or "cursor rules" — there are growing collections on GitHub.
For UserSpice specifically, we ship one. The AI
Prompts plugin drops a folder of agent-readable docs into usersc/plugins/ai_prompts/
that covers the five non-negotiables, the secure-page pattern, permissions, debugging, the
file map, and the common gotchas (Input::exists() doesn't take a field name,
tokenHere() emits name="csrf" not csrf_token, and
so on). Same idea, framework-specific, maintained alongside the framework so it stays
current.
Same-named files in custom_prompts/ override the shipped ones — drop your own
project conventions there and they'll be read alongside the framework ones, surviving
future updates.
What "good" framework context looks like
A concrete example. Here's a stub of the kind of content that goes in a project-level prompt for a UserSpice site:
# UserSpice Project — AI Assistant Guidelines ## The five non-negotiables 1. Never edit anything in `users/`. That's framework code. 2. Customizations go in `usersc/` only. 3. Use framework helpers, not raw PHP: - Input::get() / Input::exists(), NOT $_POST directly - tokenHere() emits CSRF, Token::check(Input::get('csrf')) verifies - safeReturn($v) escapes for HTML - $db is global; use global $db inside functions 4. Every mutating endpoint re-checks CSRF and auth. 5. AJAX endpoints live in `parsers/` subfolders. They are separate requests and inherit nothing from the calling page. ## Common gotchas - Input::exists() takes a TYPE ('post'/'get'), NOT a field name. - $db->insert() returns bool, not the new ID. Get it via $db->lastId(). - hasPerm() is a global function, NOT $user->hasPermission(). - tokenHere() emits name="csrf", NOT name="csrf_token". ## Project-specific conventions - permission_id 1 = standard user, 2 = admin, 5 = read-only auditor - Custom tables prefixed with `app_` - All emails use the spicyEmail() helper, never raw mail()
This file lives at ./CLAUDE.md. Every Claude session reads it. Every code
suggestion now has the right framework prior to draw from. The patterns the model knew
but wouldn't have reached for by default suddenly become the natural defaults.
The compounding effect
The reason this is worth doing is that the leverage is durable. A single corrected prompt improves one diff. A CLAUDE.md improves every prompt in the project, forever. The assistant doesn't get distracted, doesn't forget across sessions, doesn't need re-explaining next week. You write the context once and reap the benefit until the framework's conventions change.
Paired with an audit step (/userspice-audit
on UserSpice, equivalent on other stacks) and a scanner pass before deploy
(Security Scanner on UserSpice, the
individual tools elsewhere),
this is most of what's needed to vibe-code safely. The expensive part is writing the
context once; everything after that runs itself.
Want a custom prompt set for your team?
If your stack is custom enough that you need a project-specific CLAUDE.md (and you'd rather not write it from scratch), we'll read your codebase and draft one. Send the repo URL below.