Cursor Rules for Kotlin: Coroutines, Modules, and Tests

Kotlin projects need rules about coroutine ownership, Gradle commands, and how multiplatform source sets are divided. Here is a template that covers the most important conventions and the decisions that belong alongside them in fenbs.

Updated 5 min read

Cursor rules for Kotlin tell the AI assistant which coroutine dispatcher to use in which layer, how the Gradle build is structured, and where the multiplatform source boundaries are. Without them, an assistant writing Kotlin code will make plausible choices that compile but violate the threading model or cross source set boundaries in ways that break the build on a different platform.

The architecture decisions behind those boundaries — why coroutine scope is managed at the ViewModel layer, why the data layer is in commonMain rather than platform-specific code — belong in fenbs as Decisions. An assistant that reads both the rules and the project context starts each session with the architectural intent, not just the file structure.

What Kotlin rules should cover

  • Coroutine conventions: which dispatcher (IO, Default, Main) is used in which layer, and who owns the CoroutineScope.
  • Gradle commands: the exact commands for building, testing, and running the project on each platform.
  • Kotlin version and language features: the target Kotlin version and any enabled language features or compiler flags.
  • Multiplatform source sets: what goes in commonMain, androidMain, iosMain, and other targets.
  • Dependency injection: Hilt, Koin, or manual DI, and where module definitions live.
  • Test framework: Kotlin test, JUnit 5, or Kotest, and how to run unit and integration tests separately.

A starting template

Cursor rules for a Kotlin Multiplatform project
# Kotlin project rules

## Build
- Build: ./gradlew build
- Test (all targets): ./gradlew allTests
- Android tests: ./gradlew testDebugUnitTest
- iOS framework: ./gradlew linkDebugFrameworkIosSimulatorArm64 (ask first)

## Kotlin version
- Kotlin 2.0. Do not use deprecated APIs without asking.

## Coroutines
- Use Dispatchers.IO for network and disk operations.
- Use Dispatchers.Default for CPU-bound work.
- Do not create a new CoroutineScope in a function; inject or receive one.
- viewModelScope is for ViewModels only.

## Multiplatform source sets
- Shared business logic: commonMain
- Android-specific: androidMain
- iOS-specific: iosMain
- Do not import platform APIs in commonMain.

## Dependency injection
- Using Koin. Module definitions live in di/ directories.
- Do not add a new module file without asking.

## Tests
- Unit tests use the kotlin.test library.
- Use runTest {} for coroutine tests.

Coroutine rules in practice

Coroutine rules are the single most important category for Kotlin projects. An assistant that creates a new CoroutineScope in a repository class instead of injecting one will produce code that cannot be tested or canceled cleanly. An assistant that uses Dispatchers.Main in a data layer will produce code that blocks the UI on some API calls.

Write the coroutine rules as specific as your project needs. If your architecture uses a custom AppScope provided through DI, name it. If certain classes must be called from viewModelScope only, say so. Generic rules about dispatchers are better than none, but project-specific rules are better.

Testing coroutine-based code

Coroutine testing in Kotlin requires specific setup. An assistant that writes tests for coroutine-based code without runTest {} and the test dispatcher will produce tests that pass in isolation but fail under parallel execution or with timing-sensitive assertions. Add the test conventions explicitly so the assistant gets them right from the first attempt.

  • Wrap all coroutine tests in runTest {} from the kotlinx-coroutines-test library.
  • Use TestCoroutineDispatcher or UnconfinedTestDispatcher depending on your coroutine test setup.
  • Do not use Thread.sleep() in coroutine tests; use advanceTimeBy() or runCurrent() instead.
  • Flows under test should be collected with turbine or the standard toList() in a coroutine scope.

Null safety and type conventions

Kotlin's null safety is one of its strongest features, but it relies on consistent use. An assistant that uses the !! not-null assertion operator instead of proper null handling produces code that crashes at runtime rather than failing to compile. Add a rule: use !! only when a null value would indicate a programming error that should throw an exception, and never in production code paths that handle user input.

For sealed classes and when expressions, add a rule that every when on a sealed class must be exhaustive — either covering all subclasses or including an else branch that logs or throws. An assistant that leaves a when expression non-exhaustive on a sealed class is relying on Kotlin's compiler warning rather than a runtime guarantee.

Architecture decisions and fenbs

Why did the team choose Koin over Hilt? Why is the domain layer in commonMain instead of per-platform? Those decisions shape every new file an assistant creates. Recording them in fenbs as Decisions means an assistant connected to fenbs can answer "should I put this in commonMain or androidMain?" correctly without needing a human to review every placement.

How fenbs helps

Keep your architecture decisions with the work

Add your Kotlin architecture choices to fenbs as Decisions: the DI framework, the coroutine ownership model, the multiplatform source set strategy. When the architecture changes, record a new Decision with the reason. Every connected assistant reads the current decisions before it creates a new file.

Create a free account

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

How to set up Kotlin rules with fenbs

  1. Create a free account and add your Kotlin project to fenbs. Record the architecture decisions as Decisions: DI framework choice, coroutine scope ownership, multiplatform strategy.
  2. Add the template above to your project's .cursor/rules/ directory and fill in your real Gradle commands and Kotlin version.
  3. Connect Cursor to fenbs over MCP from Integrations. Ask the assistant to read the project context at session start before writing any code.
  4. When the architecture evolves — new platform targets, a different DI approach — update the Decision in fenbs and the rules file together.

What to watch for

iOS-specific Gradle tasks (linking frameworks, running iOS tests) can be time-consuming and require a macOS machine. Add explicit rules requiring the assistant to ask before running any task that targets iOS or runs the native compiler. The same caution applies to any task that touches the Xcode project generated by Kotlin Multiplatform.

Kotlin Multiplatform is still evolving rapidly. If your project uses a newer API that was experimental in an earlier Kotlin version, note the minimum version in the rules file so the assistant does not attempt to use it against a different version.

Data classes in Kotlin are generated with equals, hashCode, and toString implementations based on the properties in the constructor. An assistant that adds a property to a data class will change its equality behavior, which can affect collections, caches, and tests that compare instances. Add a rule: flag changes to data class constructors for review, because they affect equality.

Questions people ask.

Should Kotlin rules go in a single file or split by module?

Split them if your modules have different conventions. Place shared rules at the project root and module-specific rules in each module's .cursor/rules/ directory scoped with a glob matching that module's path.

How do I write coroutine rules for a project that uses Flow?

Add a section for Flow: where StateFlow is used versus SharedFlow, who collects flows and on which dispatcher, and whether the assistant should use callbackFlow when wrapping callbacks. Specific rules prevent the most common Flow mistakes.

Can Cursor rules cover both Android and iOS targets in a KMP project?

Yes. Write separate sections for each target in the rules file, or create separate scoped rules files for the androidMain and iosMain directories. State which Gradle tasks apply to each.

Where should decisions about which features go in commonMain live?

In fenbs as Decisions. The rules file can state that network code goes in commonMain; the reasoning and any exceptions should be in fenbs so the team can find and update them without editing the rules file.

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.