name: kmp-developer description: "General Kotlin Multiplatform development for implementing features, fixing bugs, and refactoring code. Use when: (1) Implementing new features with vertical slice architecture, (2) Fixing bugs in feature modules, (3) Adding repositories, ViewModels, or data layers, (4) Creating Compose UI screens, (5) Writing tests with Kotest. Keywords: feature implementation, bug fixes, refactoring, vertical slice, KMP development"
When to Use
Use this skill when working on:
- Implementing new features following vertical slice architecture
- Fixing bugs in existing feature modules (api, data, presentation, ui, wiring)
- Refactoring code within feature boundaries
- Adding or modifying repositories, ViewModels, and data layers
- Creating or updating Compose UI screens
- Writing or updating tests (Kotest in androidUnitTest/)
- Adding new dependencies via version catalog
- Making changes to Koin DI wiring modules
Do NOT use this skill for:
- Product/PRD decisions → switch to Product Design Mode
- Visual design/animations → switch to UI/UX Design Mode
- SwiftUI iOS screens → switch to SwiftUI Screen Implementation Mode
- Test planning/strategy → switch to Testing Strategy Mode
- Ktor backend changes → switch to Backend Development Mode
Decision Framework
Before implementing, ask yourself:
What layer needs work?
- New feature contracts →
:apimodule - Data fetching/storage →
:datamodule (repo + DTOs) - UI state management →
:presentationmodule (ViewModel) - User interface →
:ui-materialor:ui-unstyledmodules - Dependency wiring →
:wiringor:wiring-ui-*modules
- New feature contracts →
What patterns apply?
- Repository →
Either<RepoError, T>+ Impl+Factory - ViewModel → Pass scope to constructor, use
onStart()notinit - Tests → Property tests for mappers, Turbine for ViewModels, all error paths for repos
- Repository →
What breaks if I change this?
- Always run validation:
./gradlew :composeApp:assembleDebug test --continue - Check cross-module dependencies if modifying
:api - Verify iOS exports if changing
:apior:presentation
- Always run validation:
Essential Workflows
Workflow 1: Implement New Feature (Vertical Slice)
MANDATORY - READ ENTIRE FILE: Before implementing new features, you MUST read feature-workflow.md (~100 lines) for complete vertical slice implementation steps.
Quick summary: Create feature directory with 8 layers (api, data, presentation, ui-material, ui-unstyled, wiring, wiring-ui-material, wiring-ui-unstyled), implement repository with Either boundary, create lifecycle-aware ViewModel, wire with Koin, write tests, validate.
Workflow 2: Fix Bug in Existing Feature
To diagnose and fix bugs:
Identify the affected layer from error symptoms:
- UI issues → check
:uimodule screens - Data/loading issues → check
:presentationViewModel - Network/parsing errors → check
:datarepository implementation - DI/runtime errors → check
:wiringmodule configuration
- UI issues → check
Locate relevant test file in
androidUnitTest/:- Repositories →
:data/src/androidUnitTest - ViewModels →
:presentation/src/androidUnitTest - Mappers →
:data/src/androidUnitTest
- Repositories →
Reproduce the failing test case
Fix the implementation following canonical patterns:
- ViewModels: pass
viewModelScopeto constructor, useonStart()notinit - Repositories: return
Either<RepoError, T>, useEither.catch { }.mapLeft { } - Impl+Factory: internal implementation class, public factory function
- ViewModels: pass
Run tests to verify fix:
./gradlew :composeApp:assembleDebug test --continueAdd regression test for the bug fix
Workflow 3: Add or Update Tests
To maintain test coverage requirements (100% for mappers, 30-40% property tests):
For mapper tests (REQUIRED - 100% property coverage):
class <Feature>MapperSpec : FreeSpec({ "dto to domain preserves all properties" { checkAll(Arb.<Feature>Dto()) { dto -> val domain = dto.toDomain() domain.id shouldBe dto.id // verify all properties } } })For ViewModel tests (REQUIRED - Turbine for flows):
class <Feature>ViewModelSpec : FreeSpec({ lateinit var repository: <Feature>Repository lateinit var testScope: TestScope lateinit var viewModel: <Feature>ViewModel beforeTest { repository = mockk() testScope = TestScope() viewModel = <Feature>ViewModel(repository, testScope) } "state transitions correctly on success" { val data = listOf(<Feature>Data(...)) coEvery { repository.getData() } returns data.right() viewModel.uiState.test { awaitItem() shouldBe <Feature>UiState.Loading viewModel.onStart(owner) testScope.advanceUntilIdle() val content = awaitItem().shouldBeInstanceOf<<Feature>UiState.Content>() content.data shouldHaveSize 1 } } })For repository tests (REQUIRED - all error paths):
class <Feature>RepositorySpec : FreeSpec({ "returns Network error on IOException" { coEvery { api.getData() } throws IOException("network error") repository.getData().fold( ifLeft = { error -> error shouldBeInstanceOf RepoError.Network::class }, ifRight = { fail("Expected left") } ) } })Run tests:
./gradlew :composeApp:assembleDebug test --continue
Critical Guardrails
NEVER do work in ViewModel
initblock → overrideonStart(owner: LifecycleOwner)instead (lifecycle-aware initialization prevents premature work before UI attachment)NEVER store
CoroutineScopeas field → passviewModelScopeto constructor with default value (enables TestScope injection for deterministic testing)NEVER return nullable or
Resultfrom repositories → returnEither<RepoError, T>withEither.catch { }.mapLeft { }(type-safe error handling with exhaustive when clauses)NEVER export
:data,:ui,:wiringto iOS → only:apiand:presentationare exported via:sharedframework (iOS uses SwiftUI, not Compose; implementation details stay private)NEVER use star imports → always use explicit imports (prevents namespace pollution, enforced by .editorconfig)
NEVER skip tests when adding code → every production file requires a test file in
androidUnitTest/(current coverage: 114 tests passing, maintain this standard)NEVER create empty use cases → call repositories directly from ViewModels when no orchestration needed (reduces unnecessary abstraction layers)
NEVER swallow
CancellationException→Either.catchrespects cancellation automatically (cooperative cancellation is critical for coroutine correctness)NEVER skip Primary Validation before committing →
./gradlew :composeApp:assembleDebug test --continuemust pass (builds app + runs 114 tests in ~45s, catches integration issues early)NEVER mix feature concerns → each feature is a self-contained vertical slice (violating this breaks compilation avoidance and team autonomy)
Quick Reference
| Command | Purpose | When to Run |
|---|---|---|
./gradlew :composeApp:assembleDebug test --continue |
Primary validation (Android build + all tests) | Always, before committing |
./gradlew :composeApp:run |
Run desktop app | Local development |
./gradlew dependencyUpdates |
Check for dependency updates | Periodically |
./gradlew recordRoborazziDebug |
Record screenshot baselines | When adding UI tests |
./gradlew verifyRoborazziDebug |
Verify screenshots match baselines | Running UI tests |
./gradlew :features:<feature>:<layer>:testDebugUnitTest |
Run specific module tests | Focused testing |
Cross-References
Skills (by Category)
Architecture
| Skill | Purpose | Link |
|---|---|---|
| @kmp-architecture | Module structure, vertical slicing, feature boundaries | SKILL.md |
| @kmp-critical-patterns | 6 core patterns quick reference (ViewModel, Either, Impl+Factory, Navigation, Testing, Plugins) | SKILL.md |
Layer Implementation
| Skill | Purpose | Link |
|---|---|---|
| @kmp-presentation | ViewModels, lifecycle, SavedStateHandle, UI state management | SKILL.md |
| @kmp-data-layer | Repository patterns, Either<RepoError, T>, DTO mapping | SKILL.md |
| @kmp-domain | Domain models, use cases, domain exceptions | SKILL.md |
| @kmp-di | Koin dependency injection patterns and configuration | SKILL.md |
Platform & Navigation
| Skill | Purpose | Link |
|---|---|---|
| @kmp-ios | SwiftUI + KMP ViewModels Direct Integration, iOS export | SKILL.md |
| @kmp-navigation | Navigation 3 modular architecture, scoped routes | SKILL.md |
| @kmp-desktop | Desktop (JVM) SavedStateHandle, Koin, platform-specific patterns | SKILL.md |
Design & Build
| Skill | Purpose | Link |
|---|---|---|
| @kmp-design-systems | Design tokens, component library, icon strategy | SKILL.md |
| @kmp-compose-unstyled | Headless components, Compose Unstyled patterns | SKILL.md |
| @kmp-api-services | Ktor Client, DTOs, API service patterns | SKILL.md |
| @kmp-gradle | Convention plugins, Gradle build configuration | SKILL.md |
| @kmp-commands | CLI reference, build commands, validation scripts | SKILL.md |
Testing
| Skill | Purpose | Link |
|---|---|---|
| @kmp-testing-strategy | Testing philosophy, coverage guidelines, test planning | SKILL.md |
| @kmp-testing-patterns | Kotest, MockK, Turbine, property-based testing patterns | SKILL.md |
Documents
| Document | Purpose | Link |
|---|---|---|
| Architecture + conventions | Master reference for architecture, modules, DI | @kmp-architecture |
| iOS integration | SwiftUI + KMP ViewModels Direct Integration details | @kmp-ios |
| Dependency injection | Koin patterns and troubleshooting | @kmp-di |
Reference Implementation: pokemonlist feature demonstrates all patterns:
- API • Data • Presentation • UI • Wiring