Coding Agents are great and fast evolving. I personally use Claude Code on every project. It’s super powerful, but it still needs a lot of handholding, especially when it comes to code consistency. Often times the implementation they are coming up with works properly but the code it produces is not optimal. If you are a person that just wants something that works somehow, then you don’t need to care. But in most professional environments you want the codebase to have some kind of consistency in style and to be easy to maintain. So everyone follows some rules or principles on how certain things should be done.
Example: Layered Architecture in Laravel
One example of such a rule is following the layered architecture when building projects in Laravel. The layered architecture consists of a presentation layer, an application/service layer,
a domain layer and an infrastructure/persistence layer. In Laravel terms this means: Controller, Service, Repository, Model.
The controller (in other languages often called the handler) naturally stays thin and is just for validating the request and returning a success or error response. The service handles the actual business logic and calls the repository to update models.
In order to follow this architecture, you have a bunch of rules. Here are some of them:
- the controller should never execute business logic. It should always call a service.
- the service should never update a model or store something in the database. It should always call a repository for that.
- the repository should never execute business logic. It should only be responsible for editing/creating or deleting models.
CLAUDE.md rules don't stick
Naturally, I also include these rules in my Claude.md file (the file at your project root for project-specific instructions like coding conventions, architecture decisions and workflow preferences) and expect Claude Code to follow them. But despite being very clear about these rules, Claude still tries to sneak some business logic into the controller sometimes or wants to update a model inside a service. This can be quite annoying and slows me down a lot, since I need to constantly be aware what Claude is doing and steer it in the right direction. Even though it stores these corrections in the memory, mistakes like these keep happening.
Path-specific rules
I was quite upset about this and told a colleague. He suggested to me that I should try out rules.
Rules are a special set of instructions for files or paths that have a very high priority and are only loaded when accessing these files. So for example if I want Claude to not update models inside a service class, I can set a rule like this:
---
paths: "**/Services/*Service.php"
---
# Service Class Rules
## Rule
Service classes MUST NOT interact with the database directly. Delegate all persistence and query logic to a Repository.
## Forbidden in Service classes
- Eloquent model statics that hit the DB: `Model::create()`, `::find()`, `::findOrFail()`, `::where()`, `::first()`, `::firstOrCreate()`, `::updateOrCreate()`, `::all()`, `::query()`, `::destroy()`.
- Instance persistence: `$model->save()`, `->delete()`, `->update()`, `->forceDelete()`, `->restore()`, `->push()`.
- Relationship persistence: `->create()`, `->save()`, `->attach()`, `->detach()`, `->sync()`, `->associate()`, `->dissociate()`, `->updateExistingPivot()`.
- Query builder / raw SQL: `DB::table()`, `DB::select/insert/update/delete()`, `DB::statement()`.
- `DB::transaction()` allowed ONLY to compose multiple Repository calls atomically — reads/writes inside MUST still go through Repositories.
## Required pattern
```php
class ExampleService
{
public function __construct(
private readonly ExampleRepository $repository,
) {}
public function doThing(ExampleData $data): Example
{
return $this->repository->create($data);
}
}
```
- Inject Repository via constructor.
- Pass DTOs (Spatie Data) or typed params — never arrays.
- Service = business logic. Repository = persistence.
How it works
This file should be saved inside the .claude/rules folder and can be called anything you like. I tend to name the files according to the layer/type they describe the rules for. I name a rule file for services services.md.
The rule is always present in the context, but only the frontmatter. When a file that matches the path: ... is loaded, the full rule is loaded into context and Claude uses these rules with a higher priority.
Results
Since I implemented a couple of these rules, Claude makes fewer “mistakes” and follows the principles more strictly.
Previously I found that Claude tried to sneak in some violations of the rules approximately once every feature.
Since I added some files to the .claude/rules folder, I worked on five features and haven’t seen Claude trying to sneak in some violation so far.
If you want to try out rules and want to take a deeper look at what is possible you can visit the Claude Code Documentation on Rules.
Originally published on jasu.dev
Top comments (0)