Cursor rules are project-specific instructions, stored as .mdc files in a .cursor/rules/ folder, that Cursor's AI reads before it writes code. They are how you stop the agent from defaulting to whatever SwiftUI it saw most in training and get it following your project's conventions instead. With a good rule in place, Cursor reaches for the @Observable macro and your networking client rather than ObservableObject and a raw URLSession, without you correcting it every time.
This guide covers the current rules format, how to scope a rule to the right files with globs, what actually belongs in a SwiftUI rule, and how to keep your Cursor rules from drifting out of sync with a shared AGENTS.md.
On this page
- What Cursor rules are
- The MDC format
- How a rule gets applied
- What to put in a SwiftUI rule
- Scope rules with globs
- One source of truth with AGENTS.md
- Keeping rules honest
- FAQ
What Cursor rules are
A rule is a small markdown file that tells Cursor how to behave in this repository. Modern Cursor keeps them in a .cursor/rules/ directory, one .mdc file per rule, which replaced the single legacy .cursorrules file. Splitting rules into separate files means you can scope each one to the code it applies to instead of dumping everything into a global blob.
The reason they matter for SwiftUI specifically is drift. SwiftUI has shipped several state-management patterns since 2019, and an agent's training data mixes all of them, so it picks differently each time unless told otherwise. A rule pins the choice, and the same applies to networking, assets, and lifecycle. You write the convention once, in the place Cursor already reads, and the guessing stops.
The MDC format
An .mdc file is markdown with a small frontmatter block at the top. Three fields control when and how the rule is used:
---
description: SwiftUI conventions for this project
globs: ["**/*.swift"]
alwaysApply: true
---
Follow these conventions when writing SwiftUI:
- Use the `@Observable` macro for view models, `@State` to own them.
- Never use `ObservableObject`, `@Published`, or `@StateObject`.descriptionis a short summary of what the rule covers. Cursor uses it to decide whether an otherwise-optional rule is relevant to the current task.globsscopes the rule to matching files, so a Swift rule only loads when Swift files are in play.alwaysApplyforces the rule into context for every request when true, regardless of globs.
The body below the frontmatter is the actual instruction, written as plain, direct rules. Keep it terse; a wall of prose gets diluted.
How a rule gets applied
The frontmatter decides one of a few behaviors, and picking the right one keeps the right rules in context without flooding every request.
- Always applied.
alwaysApply: trueputs the rule in context for every request. Use it for the core conventions that are true everywhere in the codebase. - Auto attached. With globs and
alwaysApply: false, the rule loads only when a file it matches is part of the task. A tests-only rule attaches when you are editing tests and stays out of the way otherwise. - Agent requested. With a clear
descriptionand no forcing, the agent decides whether to pull the rule in based on what you asked. Good for narrower guidance that is not needed on every edit.
For most SwiftUI projects you want one small always-applied rule for the non-negotiable conventions, and a few auto-attached rules scoped to areas like tests or networking.
What to put in a SwiftUI rule
Cover the decisions your project has already made that the agent would otherwise re-decide. The pattern that works is "do X, not Y", naming the blessed approach and ruling out the tempting wrong ones, because agents follow that far more reliably than a vague "use modern patterns".
---
description: Core SwiftUI conventions
globs: ["**/*.swift"]
alwaysApply: true
---
- State: use the `@Observable` macro for view models and `@State` to own
them. Do NOT use `ObservableObject`, `@Published`, or `@StateObject`.
- Networking: all requests go through `APIClient.shared.send(_:)`, with
endpoints defined as `enum` cases. Never call `URLSession` directly.
- Assets and strings: reference images and colors as typed `Asset` values
and user-facing text as typed `L10n` values, never string literals.
- Subscriptions: check entitlements with
`SubscriptionManager.shared.hasEntitlement("pro")`, never `Purchases.shared`.
- Lifecycle: load async work in `.task { }`, not `.onAppear { Task { } }`.
- Tests: use Swift Testing (`@Test`, `#expect`), not XCTest.That single file catches the drift that causes most of the inconsistency. Anything more specific, a rule about how your networking layer is structured, say, is better as its own scoped file than piled into this one.
Scope rules with globs
Globs are what make multiple rules manageable. Instead of one giant always-on rule, you split guidance by area and let each attach only where it is relevant.
---
description: Test conventions
globs: ["**/*Tests.swift"]
alwaysApply: false
---
- Use Swift Testing: `@Test` functions and `#expect` / `#require`.
- One behavior per test. Name tests for the behavior, not the method.
- Do not add XCTest imports to new test files.A test rule scoped to **/*Tests.swift only enters context when the agent is working on tests, so your core rule stays lean and the testing guidance shows up exactly when it is needed. The same works for a networking rule scoped to your services folder, or a widgets rule scoped to the widget extension. Scope keeps each rule small and relevant, which is what keeps the agent actually following them.
One source of truth with AGENTS.md
If you use more than one AI tool, and most people now do, you do not want your conventions living in three slightly different places. The clean setup is a single AGENTS.md at the repo root that holds the real rules, with each tool's own file pointing back at it.
For Cursor that means a thin rule that defers rather than restates:
---
description: Project conventions
globs: ["**/*.swift"]
alwaysApply: true
---
Follow AGENTS.md at the repo root for all conventions. In short: @Observable
+ @State, networking through APIClient.shared, typed Asset and L10n, and
entitlements via SubscriptionManager.shared.Now Cursor, Claude Code, and Codex all read the same canonical rules, and you edit one file when a convention changes. For the full pattern, see AGENTS.md for iOS, and for the Claude Code side of the same setup, Claude Code for iOS development.
Keeping rules honest
Rules are only useful while they match the code. A few habits keep them that way.
- Keep them short. Every line competes for the agent's attention. Include only real, recurring decisions and cut the rest.
- Update on change. The moment a rule disagrees with the codebase, the agent gets conflicting signals and picks one at random. A stale rule is worse than none.
- Do not contradict AGENTS.md. If the Cursor rule and the shared file say different things, you are back to the coin toss. The rule should defer, not restate.
- Still review the output. Rules raise the floor on consistency; they do not remove the need to read what the agent wrote.
FAQ
What are Cursor rules?
Cursor rules are project-specific instructions in .mdc files under a .cursor/rules/ folder. Cursor's AI reads the relevant ones before writing code, so it follows your project's conventions instead of its generic defaults.
Where do Cursor rules live now?
In a .cursor/rules/ directory at your repo root, one .mdc file per rule. This replaced the single legacy .cursorrules file, and the folder approach lets you scope each rule to the files it applies to.
How do I keep Cursor writing consistent SwiftUI?
Add a rule that names your state pattern (@Observable + @State), your networking client, typed assets and strings, and your testing framework, and explicitly rules out the old patterns. Scope it to Swift files and mark it always-applied so it is in context on every edit.
What is the difference between Cursor rules and AGENTS.md?
AGENTS.md is a single, tool-agnostic file any agent can read. Cursor rules are Cursor-specific and support scoping to file globs. The best setup is to keep the real conventions in AGENTS.md and make the Cursor rule a thin shim that defers to it.
What do globs do in a Cursor rule?
Globs scope a rule to matching files, so it only loads when relevant files are part of the task. A rule with globs: ["**/*Tests.swift"] attaches when you edit tests and stays out of context otherwise, keeping your always-on rules lean.
Should every rule be always applied?
No. Use alwaysApply: true for the core conventions that hold everywhere, and scope narrower guidance with globs so it auto-attaches only where it is relevant. Flooding every request with every rule dilutes the ones that matter.
Spaceport ships this setup in every generated project. The Xcode project comes with .cursor/rules/ already written, a shared AGENTS.md, a CLAUDE.md that points at it, and open-source Swift skill packs, plus typed Asset and L10n constants and wrapper singletons, so Cursor writes consistent SwiftUI from the first prompt instead of learning your conventions the hard way. From an indie iOS dev, for indie iOS devs.