Cursor Rules for Clean Architecture: Boundaries, Modules, and Checks

Adaptable Cursor rules for clean architecture projects: dependency direction, module responsibilities, interface rules, and architecture checks, with fenbs preserving the reasons and exceptions behind them.

Updated 4 min read

Cursor rules for clean architecture prevent a coding assistant from crossing the dependency boundaries your team has drawn. Without them, an assistant writes working code that imports a database adapter in the domain layer, calls a framework function from a use case, or adds an exception to a module rule without knowing an exception already exists. The boundary violations compile and pass tests but accumulate into the architecture problem the rules were meant to prevent. fenbs preserves the decisions behind those rules, including the exceptions that were deliberately allowed, so the next assistant reads both the rule and its context before proposing a change.

The rules in this guide are written to be adaptable. Clean architecture projects differ in naming, layer structure, and technology. Adjust the layer names and import paths to match your project before using these templates.

What clean architecture rules need to say

The core of clean architecture is the dependency rule: inner layers know nothing about outer ones. An entity does not import a repository implementation. A use case does not know which HTTP framework delivers its input. Rules that enforce this need to be specific about which directories represent which layers and which imports are forbidden.

  • Dependency direction: which layer may import from which; forbidden import paths stated explicitly.
  • Module responsibilities: what each module is responsible for and what it is not allowed to do.
  • Interface placement: where interfaces and ports are defined, and where implementations live.
  • Exception policy: how to record a deliberate boundary exception, who approved it, and why.
  • Architecture check: whether the project uses a tool to verify boundaries automatically.

Dependency direction rules

The most useful dependency rule is one that names actual import paths, not layer concepts. "The domain layer must not import from the infrastructure layer" is correct but hard for an assistant to apply if it does not know which files are domain and which are infrastructure. Name the directories.

Example dependency direction rules for .cursor/rules/architecture-dependencies.mdc
# Dependency direction rules

## Layer directories
- domain/        Core business entities and domain services. No framework or library imports.
- application/   Use cases and application services. May import from domain/ only.
- infrastructure/ Adapters, repositories, external services. May import from domain/ and application/.
- presentation/  Controllers, routes, UI. May import from application/ and infrastructure/.

## Forbidden imports
- domain/ must never import from application/, infrastructure/, or presentation/.
- application/ must never import from infrastructure/ or presentation/.
- Do not import a repository implementation class in a use case; use the repository interface.

## Cross-layer communication
Use interfaces (ports) defined in application/ or domain/.
Implementations in infrastructure/ implement those interfaces.
Dependency injection wires the implementation to the interface at startup.

## Checking
This project uses dependency-cruiser. Run `npx depcruise src` before a PR.
If the cruiser reports a violation, fix the import or record an approved exception in .dependency-cruiser.cjs.

Module responsibility rules

Even within a layer, modules can drift. A repository that formats output for a view, a use case that validates raw HTTP input, or a domain service that logs to a framework logger all cross an invisible line. Rules that describe what each module is responsible for and what it is explicitly not responsible for give an assistant a clear decision boundary.

Example module responsibility rules
# Module responsibilities

## Domain entities
Responsible for: encapsulating business rules, validation of core invariants, identity.
Not responsible for: persistence, serialization to API formats, logging.
Example: an Order entity validates that line items are not empty. It does not write to the database.

## Use cases
Responsible for: orchestrating domain objects to complete one business operation,
  calling repository interfaces, publishing domain events.
Not responsible for: HTTP request parsing, response formatting, authentication.
Example: a PlaceOrderUseCase validates the order, calls the inventory repository, and publishes an
  OrderPlaced event. It does not parse a request body.

## Repositories
Responsible for: translating between domain objects and persistence storage.
Not responsible for: business validation, authorization, or calling other use cases.
A repository method name reflects a domain operation (findByCustomerId), not a SQL operation (selectWhere).

Interface and abstraction rules

Interfaces defined in the right layer are what make the architecture testable. An application layer interface for a payment gateway means a use case test can use a fake gateway without standing up the real service. Rules that state where interfaces live and what they should look like make testing a natural consequence of the architecture, not an afterthought.

Example interface rules
# Interfaces and ports

## Interface placement
Define interfaces (ports) in the same layer as their consumers:
  - Repository interfaces in application/ (used by use cases).
  - Event publisher interfaces in domain/ (used by domain services).
  - External service interfaces in application/ (email, payment, storage).

## Interface naming
Suffix interfaces with Port or Repository as appropriate: CustomerRepository, EmailPort.
Name methods from the consumer's perspective: send(message), not callSendgridAPI(message).

## Implementation placement
Implementations (adapters) live in infrastructure/.
Name them with the technology: PostgresCustomerRepository, SendgridEmailAdapter.

## Testing
Provide a fake or in-memory implementation for every interface used by use cases.
Place fakes in tests/fakes/. Use these in unit tests; use the real implementation in integration tests.

Keeping rules in sync with tooling

A Cursor rule that describes dependency direction is most effective when it is backed by a static analysis tool that enforces the same rule automatically. Tools like dependency-cruiser for Node.js, ArchUnit for Java, or import-linter for Python can check actual imports against a defined policy and fail a build when a rule is violated. The Cursor rule tells the assistant what to write; the tool catches what slipped through. Mention the tool in the rule itself: "Before completing any architecture change, run the dependency checker and fix all violations." This makes the check part of the workflow rather than a separate review step.

Recording exceptions to architecture rules

Every long-running project has deliberate boundary exceptions: a performance optimization that required a direct database call in a use case, a third-party library that does not fit neatly into a layer. The problem is not the exception but the undocumented exception. An assistant reading the rules without knowing about the exception will either flag it as wrong or silently copy the pattern. Record exceptions where they can be found.

In the rule file, add an exception section with the violation, the reason, and a reference to the decision. In fenbs, record it as a Decision with the person who approved it. The rule file and fenbs together give any assistant the current state: the boundary, and the approved deviations from it.

How fenbs helps

Keep the reasons behind your architecture boundaries in fenbs

fenbs records Decisions and rules with the person who made them and why. When the team decides that a use case may call a caching layer directly for performance reasons, record it as a Decision in fenbs with the reason and any conditions. The Cursor rule file says what the boundary is; fenbs explains why it is there and what exceptions are approved. The next session sees both.

Create a free account

Free for one person · No credit card · Works with Claude, ChatGPT, Cursor and more

How to do this with fenbs

  1. Create a free account and add a project for the codebase.
  2. Record the core architecture decisions in fenbs: which directories represent which layers, how interfaces are named, and which exceptions are deliberately approved. Each decision should name who agreed to it and why.
  3. Add the Cursor rule files to .cursor/rules/. Start with the dependency direction rules, since those are the most frequently violated. Add the module responsibility rules next.
  4. Connect Cursor to fenbs over MCP from Integrations. Ask it to read the project context before proposing a change that touches an interface or a layer boundary.
  5. When a new exception to the architecture is agreed on, record it in fenbs and update the rule file. The two should always match.

Questions people ask.

Do these rules work with any clean architecture variant?

Yes, with adjustments to directory names and layer labels. Hexagonal architecture, ports and adapters, and onion architecture all follow the same dependency inversion principle. Rename the layer directories in the rules to match your project, and the dependency direction rules apply directly.

Can Cursor enforce architecture rules the way a linter does?

Cursor rules make an assistant less likely to introduce a boundary violation, but they do not prevent one. Use a static analysis tool like dependency-cruiser or ArchUnit alongside the rules. Run it in CI or as a pre-commit check. The Cursor rule should tell the assistant to run the check and not proceed if it reports a violation.

How specific should the forbidden import rules be?

Name actual import paths, not just layer names. An assistant can check an import path against a rule far more reliably than it can decide whether a module conceptually belongs to the infrastructure layer. The more specific the rule, the more consistently it is followed.

What happens when a legitimate use case needs to cross a layer boundary?

Record it as an approved exception. Add a comment in the code explaining the reason, record the decision in fenbs with who approved it, and add the exception to the rule file so the next assistant does not flag it as a mistake. Undocumented exceptions are the main way architecture rules slowly stop meaning anything.

Start with one thing.

Free for one person · No credit card · Works with Claude, ChatGPT, Cursor and more

There is nothing to set up first. Write one line and you’ve started.