{"owner":"InsertKoinIO","repo":"koin","hasSkills":true,"totalSkillsCount":2,"totalTokensCount":6296,"categories":["claude-rule","root-instruction"],"hasMcp":false,"mcpConfig":null,"found":["projects/CLAUDE.md","projects/llms.txt"],"skills":{"projects/CLAUDE.md":"# Koin\n\nKoin is a pragmatic, lightweight dependency injection framework for Kotlin Multiplatform (Android, JVM, iOS/native, JS, WasmJS). This directory (`projects/`) is the Gradle build root.\n\nShared agent notes (layout, build commands, contributing rules, how to write Koin code): [`../AGENTS.md`](../AGENTS.md). This file adds the Claude-specific detail; keep both consistent.\n\n## Doctrine\n\n- **Evidence-based claims.** \"fixed/verified/done\" must cite file:line, test output, or a repro run — never \"looks right + tests pass\".\n- **Falsify, don't confirm.** A test must try to BREAK the implementation. Prove RED before GREEN: a regression test must fail on the unfixed code.\n- **Resolution behavior is a contract.** Koin's resolution order/matching semantics are user-observable API — treat any change there as a compatibility event, not an implementation detail.\n\n> **SURPRISE RULE — mandatory.** If the project surprises you or contradicts these docs, STOP, tell the user, and record it here (or in the closest module doc). \"Surprising\" = not inferable from the code in one grep.\n\n## Module Map\n\n| Group | Modules | Notes |\n|---|---|---|\n| `core/` | **koin-core** (engine), koin-core-coroutines, koin-core-viewmodel, koin-core-annotations, koin-annotations, koin-test, koin-test-junit4/5, benchmark | KMP |\n| `android/` | koin-android, koin-android-compat, koin-androidx-navigation, koin-androidx-workmanager, koin-androidx-startup, koin-dagger-bridge, koin-android-test | Android |\n| `compose/` | koin-compose, koin-compose-viewmodel, koin-compose-navigation3, koin-compose-viewmodel-navigation, koin-androidx-compose(-navigation) | Compose MP + AndroidX |\n| `ktor/` | koin-ktor, koin-logger-slf4j | JVM server |\n| `plugins/` | koin-gradle-plugin | |\n| `bom/` | koin-bom | Aligns all artifact versions |\n\n### Key engine areas (`core/koin-core/src/commonMain/kotlin/org/koin/`)\n\n- `core/resolution/` — instance resolution. `CoreResolverV2.kt` is the 4.2+ engine: injected params → stacked params → registry (scope source → linked scopes → archetype)\n  - **Qualified lookups are registry-only.** A `get(named(...))` skips the stacked-parameter path entirely — parameters carry no qualifier (`ParametersHolder` matches by type only), so a stacked param can never satisfy a qualified request. Removing this guard re-opens #2370/#2408 (a `parametersOf` value shadowing a qualified dependency of the same type). Only unqualified resolution reads the param stack — that's the #2387 ViewModel path; don't break it.\n- `core/registry/` — definition & instance registries (indexing by type + qualifier)\n- `core/scope/` — `Scope`, scope archetypes, linked scopes\n- `core/module/` — module DSL (`single`, `factory`, `scoped`, `bind`, `includes`)\n- `mp/` — multiplatform abstractions (`KoinPlatform`, platform tools)\n\n## Build & Test (run from `projects/`)\n\n```bash\n./gradlew :core:koin-core:jvmTest              # one module, JVM only (fast) — KMP modules have no plain `test` task\n./gradlew :core:koin-core:allTests             # one module, all KMP targets\n./test.sh                                      # clean JVM-side run: `test` (Android/JVM) + `jvmTest` (KMP modules); non-JVM targets need allTests / test-macos.sh\n./test-macos.sh                                # Apple/native targets\n./gradlew :core:koin-core:compileKotlinWasmJs  # quick non-JVM compile check\n./install.sh                                   # publish all to Maven Local\n./benchmark.sh                                 # core benchmarks (core/benchmark)\n./compare_apis.sh                              # public API diff (binary compatibility)\n./clean.sh                                     # clean all builds\n```\n\n## PR Guards — check on EVERY pull request\n\nBefore opening (or approving) any PR, both guards must pass:\n\n### 1. Don't break the current API\n- `./gradlew apiCheck` must pass (kotlinx binary-compatibility-validator, applied at the root — `.api` dumps per module, e.g. `core/koin-core/api/koin-core.api`).\n- If the PR intentionally **adds** public API: regenerate dumps with `./gradlew apiDump`, commit the `.api` diff, and review it with `./compare_apis.sh` (diffs `.api` files vs `main`).\n- **Removals or signature changes of public API are not acceptable in patch releases** — additions only. Breaking changes need a minor/major version and an explicit deprecation cycle (`@Deprecated` with `ReplaceWith` first).\n- Behavioral compatibility counts as API: a PR that changes resolution order/matching semantics must state the old vs new behavior in its description, even if no signature changed.\n\n### 2. New tests for every new case or fixed use-case\n- **Bug fix** → a regression test that reproduces the issue: fails before the fix, passes after. Place it in `commonTest` whenever the code under test is common.\n- **New case/feature** → tests covering the new behavior, including the edge that motivated it (qualifiers, scopes, params, platform-specific paths).\n- A PR touching `commonMain` without an accompanying test change needs an explicit justification in the description.\n- KMP: tests must run beyond JVM when the code is common (`allTests`, or at minimum compile-check one native target + `wasmJs`).\n\n## Conventions\n\n- **KMP discipline:** `commonMain` changes must be verified beyond JVM — compile at least one native target and `wasmJs` before considering a change done. Several bug classes (qualifiedName, klib, JS) only surface off-JVM.\n- **Every bug fix ships a regression test**, in `commonTest` whenever the code is common.\n- **Commit style:** `Fix #NNNN - description` referencing the GitHub issue (e.g. `Fix #2387 - CoreResolverV2 lost stacked-params lookup on linked scopes`).\n- **Public API changes:** run `./compare_apis.sh`; avoid breaking binary compatibility in patch releases.\n- **Resolution-semantics changes** (anything in `core/resolution/`): describe the behavioral delta (old vs new resolution order/matching) in the PR description — resolver behavior changes have historically caused regressions.\n- **User-visible changes need a docs touch** — documentation lives in `../docs/` (published to insert-koin.io).\n\n### KMP test traps (hard rules)\n\n- **Never weaken `test.sh` to scoped tasks.** In KMP projects `./gradlew test` resolves only to Android/JVM unit tests and **silently skips** native, JS, and WASM tests. Full verification = `allTests` (or per-target tasks explicitly). Do not add `-x` exclusions to make a run green — fix the failing test at the root cause.\n- **No backtick test names in `commonTest`** — backtick names (`` fun `my test`() ``) break `compileTestKotlinJs`/`compileTestKotlinWasmJs` (invalid JS identifiers). Use snake_case in `commonTest`; backticks are fine in `jvmTest` only.\n- **`runTest {}` in `commonTest` must use block-body syntax** — on wasmJs, expression-body forms (`fun foo() = runTest { }`) fail with return-type mismatch. Use `fun testX() { runTest { ... } }`.\n\n#### Known pre-existing off-JVM failures (not yours — don't chase them)\n\n`allTests` is currently red on two counts unrelated to any recent resolver work (verified by stash-comparison on the `4.2.2` branch, 2026-06-08). If you touch `commonMain` and run `allTests`, expect these and confirm your diff doesn't change them:\n\n- **`createdAtStart` on wasmJs** — `CreateOnStart`, `OverrideAndCreateatStartTest` fail with `AssertionError: Expected value to be true`. Core `single(createdAtStart = true)` eager-init doesn't fire on wasmJs. This is *core*, **not** the compiler-plugin `createdAtStart` drop (#2425/#2415) — same symptom family, different layer.\n- **`DynamicModulesTest` on native** — its backtick names contain `\" - \"` (e.g. `` `should unload one module definition - factory` ``), crashing the Kotlin/Native test reporter (`ServiceMessagesParser`) and aborting `macosArm64Test`. This is the backtick-name trap above, already shipped. Fix = rename to snake_case.\n\n## Versioning\n\n- Version is set in `gradle.properties` (`koinVersion`).\n- Branch lines: `4.2.x` = stable maintenance (regressions/bug fixes only), next minor in development on `main`.\n- `koin-bom` must stay aligned with all published modules.\n\n### Release notes: Added vs Fixed\n\nThe test: **\"Would this affect a user on the previous supported version range?\"**\n- Yes → `Fixed` (genuine regression or pre-existing bug)\n- No, only relevant to a new Kotlin/AGP/library range → `Added` (new support story), as ONE umbrella entry with sub-bullets — users care that \"Kotlin X works\", not how many internal changes it took.\n- Internal refactor with identical user-visible behavior → internal changes, not `Changed`.\n","projects/llms.txt":"# Koin — Kotlin Dependency Injection Framework\n\n> Pragmatic DI framework for Kotlin and Kotlin Multiplatform. Two styles, one container: Koin DSL or Koin Annotations (JSR-330 compatible). Compile safe in both styles with the Koin Compiler Plugin — no KSP, no kapt, no reflection at its core. Runs on all Kotlin Multiplatform targets.\n\nConcise index: [llms.txt](https://github.com/InsertKoinIO/koin/blob/main/llms.txt). This file is the API cheat sheet.\n\n## Key Facts\n\n- **Version**: Koin 4.2.2, Koin Compiler Plugin 1.2.1, Kotlin 2.3.20 or later\n- **Language**: Kotlin (100%)\n- **Platforms**: Android, iOS, Desktop, Web (JS/Wasm), Backend (Ktor) via Kotlin Multiplatform\n- **License**: Apache 2.0\n- **Repository**: https://github.com/InsertKoinIO/koin\n- **Website**: https://insert-koin.io\n- **Documentation**: https://insert-koin.io/docs/intro/what-is-koin\n- **Koin skills (AI agents)**: https://github.com/InsertKoinIO/koin-skills\n- **Kotzilla MCP (runtime debugging)**: https://mcp.kotzilla.io/mcp\n\n## IDE Plugin\n\nOfficial plugin for Android Studio & IntelliJ IDEA — code navigation between definitions and injection points, live safety checks, dependency graph visualization.\n\n**Download:** https://plugins.jetbrains.com/plugin/26131-koin-dependency-injection-official-\n\n## Gradle Dependencies\n\n```kotlin\n// BOM (recommended)\nplugins {\n    id(\"io.insert-koin.compiler.plugin\") version \"1.2.1\"     // Koin Compiler Plugin (recommended — compile-time safety)\n}\n\nimplementation(platform(\"io.insert-koin:koin-bom:4.2.2\"))   // BOM resolves every Koin artifact version\nimplementation(\"io.insert-koin:koin-core\")                   // Core (all platforms)\nimplementation(\"io.insert-koin:koin-annotations\")            // Annotations style (with the Compiler Plugin)\nimplementation(\"io.insert-koin:koin-android\")                // Android (Context, ViewModel, Scope)\nimplementation(\"io.insert-koin:koin-compose\")                // Compose integration\nimplementation(\"io.insert-koin:koin-compose-viewmodel\")      // Compose ViewModel (koinViewModel)\nimplementation(\"io.insert-koin:koin-core-coroutines\")        // Coroutines (lazy modules)\nimplementation(\"io.insert-koin:koin-ktor\")                   // Ktor server\nimplementation(\"io.insert-koin:koin-test\")                   // Testing\nimplementation(\"io.insert-koin:koin-test-junit5\")            // JUnit 5 testing\n\n// Never Koin KSP: no com.google.devtools.ksp for Koin, no koin-ksp-compiler, no koin-annotations:2.x pin\n```\n\n---\n\n## Starting Koin\n\n### Android\n\n```kotlin\nimport org.koin.android.ext.koin.androidContext\nimport org.koin.android.ext.koin.androidLogger\nimport org.koin.core.context.startKoin\nimport org.koin.androidx.workmanager.koin.workManagerFactory\n\nclass MyApplication : Application() {\n    override fun onCreate() {\n        super.onCreate()\n        startKoin {\n            androidLogger()\n            androidContext(this@MyApplication)\n            workManagerFactory()              // if using WorkManager\n            modules(appModule, featureModule)\n        }\n    }\n}\n```\n\n### Annotations (with compile-time graph validation)\n\n```kotlin\nimport org.koin.core.annotation.KoinApplication\nimport org.koin.plugin.module.dsl.startKoin   // reified startKoin<App> from the Compiler Plugin\n\n@KoinApplication\nclass MyApplication : Application() {\n    override fun onCreate() {\n        super.onCreate()\n        startKoin<MyApplication> {    // triggers A3 full graph validation\n            androidLogger()\n            androidContext(this@MyApplication)\n            workManagerFactory()\n        }\n    }\n}\n```\n\n### Ktor\n\n```kotlin\nimport org.koin.ktor.plugin.Koin\n\nfun Application.module() {\n    install(Koin) {\n        modules(appModule)\n    }\n}\n```\n\n### KMP / Non-Android\n\n```kotlin\nimport org.koin.core.context.startKoin\n\nstartKoin {\n    modules(commonModule, platformModule)\n}\n```\n\n---\n\n## Defining Dependencies\n\n### Compiler Plugin DSL (recommended)\n\nPick one style per project — Koin DSL or Koin Annotations — and don't mix them unless needed (then one style per module).\n\n```kotlin\nimport org.koin.dsl.module\nimport org.koin.dsl.bind\nimport org.koin.plugin.module.dsl.*\n\nval appModule = module {\n    // Singleton — one instance for app lifetime\n    single<Database>()\n    single<UserRepository>()\n\n    // Factory — new instance each time\n    factory<GetUserUseCase>()\n\n    // ViewModel — Android ViewModel lifecycle\n    viewModel<UserViewModel>()\n\n    // Worker — WorkManager\n    worker<SyncWorker>()\n\n    // Interface binding\n    single<UserRepositoryImpl>() bind UserRepository::class\n\n    // Multiple bindings\n    single<MyServiceImpl>() binds arrayOf(ServiceA::class, ServiceB::class)\n\n    // Module composition\n    includes(networkModule, databaseModule)\n}\n```\n\n### Compiler Plugin DSL with Function Builders (create)\n\n```kotlin\nimport org.koin.dsl.module\nimport org.koin.plugin.module.dsl.create\n\n// Builder functions — for wrapping external libraries (Room, Retrofit, etc.)\nfun database(context: Context): AppDatabase =\n    Room.databaseBuilder(context, AppDatabase::class.java, \"my-db\").build()\n\nfun topicDao(db: AppDatabase): TopicDao = db.topicDao()\n\nfun json(): Json = Json { ignoreUnknownKeys = true }\n\nval databaseModule = module {\n    single { create(::database) }    // parameters auto-resolved from DI\n    single { create(::topicDao) }\n    single { create(::json) }\n}\n```\n\n### Constructor DSL and Classic DSL\n\nConstructor DSL (`singleOf(::T)`) suits projects without the Compiler Plugin. Classic DSL (`single { T(get()) }`) is for custom expressions a constructor or function builder can't express. Both are also checked by the Compiler Plugin when it is applied.\n\n```kotlin\nimport org.koin.dsl.module\nimport org.koin.core.module.dsl.*\n\nval appModule = module {\n    // Constructor DSL — constructor reference\n    singleOf(::Database)\n    singleOf(::UserRepository)\n    factoryOf(::GetUserUseCase)\n    viewModelOf(::UserViewModel)\n\n    // Classic DSL — lambda (manual wiring)\n    single { Database() }\n    single { UserRepository(get()) }\n    factory { GetUserUseCase(get()) }\n    viewModel { UserViewModel(get()) }\n}\n```\n\n### Annotations\n\n```kotlin\nimport org.koin.core.annotation.*\n\n@Singleton\nclass Database\n\n@Singleton\nclass UserRepository(private val db: Database)\n\n@Factory\nclass GetUserUseCase(private val repo: UserRepository)\n\n@KoinViewModel\nclass UserViewModel(private val useCase: GetUserUseCase) : ViewModel()\n\n// Module with component scan\n@Module\n@ComponentScan(\"com.myapp\")\nclass AppModule\n\n// Module with provider functions (for external libraries)\n@Module\ninternal object DatabaseModule {\n    @Singleton\n    fun providesDatabase(context: Context): AppDatabase =\n        Room.databaseBuilder(context, AppDatabase::class.java, \"my-db\").build()\n}\n```\n\n### Annotated Top-Level Functions\n\n```kotlin\nimport org.koin.core.annotation.Singleton\nimport org.koin.core.annotation.Factory\n\n@Singleton\nfun provideDatabase(context: Context): AppDatabase =\n    Room.databaseBuilder(context, AppDatabase::class.java, \"my-db\").build()\n\n@Factory\nfun provideCache(db: AppDatabase): CacheService = CacheServiceImpl(db)\n```\n\nDiscovered by `@ComponentScan` like annotated classes.\n\n---\n\n## Injecting Dependencies\n\n### In Android Activity / Fragment\n\n```kotlin\nimport org.koin.android.ext.android.inject\nimport org.koin.android.ext.android.get\nimport org.koin.androidx.viewmodel.ext.android.viewModel\n\nclass MainActivity : ComponentActivity() {\n    // Lazy injection\n    private val viewModel: UserViewModel by viewModel()\n    private val service: MyService by inject()\n\n    // Eager injection\n    val eagerService: MyService = get()\n}\n```\n\n### In Activity with Scope\n\n```kotlin\nimport org.koin.android.scope.AndroidScopeComponent\nimport org.koin.androidx.scope.activityScope\nimport org.koin.android.ext.android.inject\nimport org.koin.androidx.viewmodel.ext.android.viewModel\n\nclass MainActivity : ComponentActivity(), AndroidScopeComponent {\n    override val scope: Scope by activityScope()\n\n    private val viewModel: MainViewModel by viewModel()\n    private val tracker: ActivityTracker by inject()   // scoped to activity lifecycle\n}\n```\n\n### In Compose\n\n```kotlin\nimport org.koin.compose.viewmodel.koinViewModel\nimport org.koin.compose.koinInject\n\n@Composable\nfun HomeScreen(viewModel: HomeViewModel = koinViewModel()) {\n    val analytics: AnalyticsHelper = koinInject()\n    // ...\n}\n```\n\n### In Compose with Parameters (Navigation Arguments)\n\n```kotlin\nimport org.koin.compose.viewmodel.koinViewModel\nimport org.koin.core.parameter.parametersOf\n\n@Composable\nfun DetailScreen(\n    newsId: String,\n    viewModel: DetailViewModel = koinViewModel(key = newsId) { parametersOf(newsId) }\n) {\n    // Each unique newsId gets its own ViewModel instance\n}\n```\n\n### In Ktor\n\n```kotlin\nimport org.koin.ktor.ext.inject\n\nfun Route.userRoutes() {\n    val service: UserService by inject()\n\n    get(\"/users/{id}\") {\n        val user = service.getUser(call.parameters[\"id\"]!!)\n        call.respond(user)\n    }\n}\n```\n\n### Constructor Injection (Recommended for Business Logic)\n\n```kotlin\n// No Koin imports needed — dependencies resolved by the framework\nclass UserRepository(\n    private val database: Database,\n    private val networkClient: NetworkClient\n)\n\nclass GetUserUseCase(\n    private val repository: UserRepository\n)\n```\n\n---\n\n## Qualifiers\n\n### @Named (String Qualifier)\n\n```kotlin\nimport org.koin.core.annotation.Named\nimport org.koin.core.qualifier.named\n\n// Definition\n@Singleton @Named(\"local\")\nclass LocalDatabase : Database\n\n@Singleton @Named(\"remote\")\nclass RemoteDatabase : Database\n\n// Injection\n@Singleton\nclass UserRepository(\n    @Named(\"local\") private val localDb: Database,\n    @Named(\"remote\") private val remoteDb: Database\n)\n\n// Compiler Plugin DSL — the qualifier stays on the class (@Named above), the definition stays plain\nsingle<LocalDatabase>()\nsingle<RemoteDatabase>()\n\n// Classic DSL only\nsingle<Database>(named(\"local\")) { LocalDatabase() }\n```\n\n### Custom @Qualifier (with Parameters)\n\n```kotlin\nimport org.koin.core.annotation.Qualifier\n\n@Qualifier\n@Retention(AnnotationRetention.RUNTIME)\nannotation class Dispatcher(val type: AppDispatchers)\n\nenum class AppDispatchers { Default, IO }\n\n// Provider\n@Singleton @Dispatcher(AppDispatchers.IO)\nfun ioDispatcher(): CoroutineDispatcher = Dispatchers.IO\n\n// Consumer\n@Singleton\nclass MyRepository(\n    @Dispatcher(AppDispatchers.IO) private val dispatcher: CoroutineDispatcher\n)\n```\n\n---\n\n## Scopes\n\n### DSL Scopes\n\n```kotlin\nimport org.koin.dsl.module\nimport org.koin.plugin.module.dsl.scoped\nimport org.koin.androidx.scope.dsl.activityScope\nimport org.koin.androidx.scope.dsl.fragmentScope\n\nval appModule = module {\n    // Activity-scoped\n    activityScope {\n        scoped<ActivityTracker>()\n    }\n\n    // Fragment-scoped\n    fragmentScope {\n        scoped<FragmentPresenter>()\n    }\n\n    // Custom scope\n    scope<UserSession> {\n        scoped<UserRepository>()\n        scoped<UserPreferences>()\n    }\n}\n```\n\n### Annotation Scopes\n\n```kotlin\nimport org.koin.core.annotation.Scoped\nimport org.koin.core.annotation.Scope\nimport org.koin.android.annotation.ActivityScope\nimport org.koin.android.annotation.FragmentScope\n\n@ActivityScope\nclass ActivityTracker(val activity: ComponentActivity)\n\n@FragmentScope\nclass FragmentPresenter(val fragment: Fragment)\n\n@Scope(UserSession::class)\n@Scoped\nclass UserRepository(val db: Database)\n```\n\n---\n\n## ViewModel\n\n### With SavedStateHandle\n\n```kotlin\n// SavedStateHandle is auto-injected (whitelisted type)\n@KoinViewModel\nclass HomeViewModel(\n    private val savedStateHandle: SavedStateHandle,\n    private val repository: UserRepository,\n) : ViewModel()\n\n// DSL\nviewModel<HomeViewModel>()\n```\n\n### With @InjectedParam (Navigation Arguments)\n\n```kotlin\nimport org.koin.core.annotation.InjectedParam\n\n@KoinViewModel\nclass DetailViewModel(\n    private val repository: NewsRepository,\n    @InjectedParam val newsId: String,\n) : ViewModel()\n\n// Compose usage:\nkoinViewModel(key = newsId) { parametersOf(newsId) }\n```\n\n---\n\n## WorkManager\n\n```kotlin\nimport org.koin.android.annotation.KoinWorker\n\n// Annotations\n@KoinWorker\nclass SyncWorker(\n    context: Context,\n    params: WorkerParameters,\n    private val repository: NewsRepository,\n    @Dispatcher(AppDispatchers.IO) private val dispatcher: CoroutineDispatcher,\n) : CoroutineWorker(context, params)\n\n// DSL\nval syncModule = module {\n    single<WorkManagerSyncManager>() bind SyncManager::class\n    worker<SyncWorker>()\n}\n\n// Bootstrap — add workManagerFactory()\nstartKoin {\n    workManagerFactory()\n    modules(appModule)\n}\n```\n\n---\n\n## Module Organization\n\n### Annotations — Auto-Discovery\n\n```kotlin\nimport org.koin.core.annotation.*\n\n// Modules with @Configuration are auto-discovered by startKoin<T>\n@Module @ComponentScan(\"com.myapp.core\") @Configuration\nclass CoreModule\n\n@Module @ComponentScan(\"com.myapp.feature\") @Configuration\nclass FeatureModule\n\n@Module(includes = [DatabaseModule::class]) @Configuration\nclass DataModule\n\n// App entry point — assembles all @Configuration modules\n@KoinApplication\nclass MyApplication : Application()\n```\n\n### DSL — Explicit Composition\n\n```kotlin\nval coreModule = module { single<Database>(); single<NetworkClient>() }\nval featureModule = module { includes(coreModule); viewModel<HomeViewModel>() }\n\nval appModule = module {\n    includes(coreModule, featureModule, syncModule)\n}\n\nstartKoin { modules(appModule) }\n```\n\n### @Configuration Labels (Environment-Specific)\n\n```kotlin\n@Module @ComponentScan(\"com.myapp.core\") @Configuration(\"prod\")\nclass ProdCoreModule\n\n@Module @ComponentScan(\"com.myapp.core\") @Configuration(\"test\")\nclass TestCoreModule\n\n// Modules with the same label see each other's definitions\n```\n\n---\n\n## Dynamic Modules\n\n```kotlin\nimport org.koin.core.context.loadKoinModules\nimport org.koin.core.context.unloadKoinModules\n\n// Load at runtime (feature flags, A/B testing)\nloadKoinModules(premiumModule)\n\n// Unload when no longer needed\nunloadKoinModules(premiumModule)\n```\n\n---\n\n## Lazy Modules (Background Loading)\n\n```kotlin\nimport org.koin.core.context.startKoin\nimport org.koin.core.lazyModules\n\nstartKoin {\n    modules(criticalModule)\n    lazyModules(heavyModule, analyticsModule)  // loaded in background\n}\n```\n\n---\n\n## Properties\n\n```kotlin\nimport org.koin.core.annotation.Property\n\n@Singleton\nclass ApiClient(\n    @Property(\"api_url\") val url: String,\n    @Property(\"api_key\") val key: String\n)\n\n// Provide properties\nstartKoin {\n    properties(mapOf(\"api_url\" to \"https://api.example.com\", \"api_key\" to \"secret\"))\n}\n\n// DSL\nsingle { ApiClient(getProperty(\"api_url\"), getProperty(\"api_key\")) }\n```\n\n---\n\n## Optional / Nullable Dependencies\n\n```kotlin\n// Nullable param → resolved with getOrNull()\n@Singleton\nclass MyService(\n    val required: RequiredDep,\n    val optional: OptionalDep?     // null if not in graph\n)\n\n// DSL\nsingle<MyService>()  // compiler plugin handles getOrNull() automatically\n```\n\n---\n\n## Lazy Injection\n\n```kotlin\n// Lazy<T> → deferred creation\n@Singleton\nclass MyService(\n    val heavy: Lazy<HeavyDependency>   // created on first access\n)\n```\n\n---\n\n## List Injection\n\n```kotlin\n// List<T> → collects all definitions of type T\n@Singleton\nclass LoggerAggregator(\n    val loggers: List<LoggerDataSource>   // all LoggerDataSource definitions\n)\n```\n\n---\n\n## Testing\n\n```kotlin\nimport org.koin.test.KoinTest\nimport org.koin.test.inject\n\nclass MyTest : KoinTest {\n    private val service: MyService by inject()\n\n    @Test\n    fun testService() {\n        // service is resolved from Koin\n    }\n}\n```\n\nGraph verification is done at compile time by the Koin Compiler Plugin — don't write `checkModules()` / `verify()` tests (on their way to deprecation).\n\n---\n\n## Compile-Time Safety (with Koin Compiler Plugin)\n\nThe Koin Compiler Plugin validates your dependency graph at build time:\n\n- **A2 — Per-Module:** validates definitions against visible scope per module\n- **A3 — Full Graph:** validates ALL modules at `startKoin<T>()`\n- **A4 — Call-Site:** validates every `get<T>()`, `inject<T>()`, `koinViewModel<T>()` call\n\nWorks with both DSL and Annotations. Replaces `verify()` / `checkModules()`. If it compiles, it works.\n\n```kotlin\nkoinCompiler {\n    compileSafety = true       // default\n    skipDefaultValues = true   // default\n    unsafeDslChecks = true     // default\n}\n```\n\nSee https://insert-koin.io/docs/reference/koin-compiler/compile-safety\n\n---\n\n## Architecture\n\n- O(1) resolution via `HashMap.get()` — no reflection, no iteration\n- `ConcurrentHashMap` (JVM) / `ConcurrentMutableMap` (Native)\n- Fully compatible with R8/ProGuard\n- Pure common Kotlin runtime — all KMP targets\n\n## Project Structure\n\n```\nkoin/projects/\n├── core/      # koin-core (engine), koin-core-viewmodel, koin-core-coroutines, koin-annotations, koin-test*\n├── android/   # koin-android, koin-androidx-workmanager, koin-androidx-startup, koin-dagger-bridge…\n├── compose/   # koin-compose, koin-compose-viewmodel, koin-compose-navigation3…\n├── ktor/      # koin-ktor, koin-logger-slf4j\n├── plugins/   # koin-gradle-plugin\n└── bom/       # koin-bom\n```\n\nBuilding and contributing: https://github.com/InsertKoinIO/koin/blob/main/AGENTS.md\n\n## Related Repositories\n\n- **Compiler Plugin**: https://github.com/InsertKoinIO/koin-compiler-plugin\n- **Koin skills (AI agents)**: https://github.com/InsertKoinIO/koin-skills — Claude Code: `/plugin marketplace add InsertKoinIO/koin-skills` then `/plugin install koin@koin-skills`. Other agents (Cursor, Codex, Copilot, Windsurf, Gemini…): `npx skills add InsertKoinIO/koin-skills` (Node.js 22+)\n- **Kotzilla MCP**: https://mcp.kotzilla.io/mcp — Koin error diagnosis and runtime debugging (graph behavior, scopes, memory, performance)\n- **Playground Apps**: https://github.com/InsertKoinIO/playground-apps\n- **Website**: https://insert-koin.io\n"},"files":{"projects/CLAUDE.md":"# Koin\n\nKoin is a pragmatic, lightweight dependency injection framework for Kotlin Multiplatform (Android, JVM, iOS/native, JS, WasmJS). This directory (`projects/`) is the Gradle build root.\n\nShared agent notes (layout, build commands, contributing rules, how to write Koin code): [`../AGENTS.md`](../AGENTS.md). This file adds the Claude-specific detail; keep both consistent.\n\n## Doctrine\n\n- **Evidence-based claims.** \"fixed/verified/done\" must cite file:line, test output, or a repro run — never \"looks right + tests pass\".\n- **Falsify, don't confirm.** A test must try to BREAK the implementation. Prove RED before GREEN: a regression test must fail on the unfixed code.\n- **Resolution behavior is a contract.** Koin's resolution order/matching semantics are user-observable API — treat any change there as a compatibility event, not an implementation detail.\n\n> **SURPRISE RULE — mandatory.** If the project surprises you or contradicts these docs, STOP, tell the user, and record it here (or in the closest module doc). \"Surprising\" = not inferable from the code in one grep.\n\n## Module Map\n\n| Group | Modules | Notes |\n|---|---|---|\n| `core/` | **koin-core** (engine), koin-core-coroutines, koin-core-viewmodel, koin-core-annotations, koin-annotations, koin-test, koin-test-junit4/5, benchmark | KMP |\n| `android/` | koin-android, koin-android-compat, koin-androidx-navigation, koin-androidx-workmanager, koin-androidx-startup, koin-dagger-bridge, koin-android-test | Android |\n| `compose/` | koin-compose, koin-compose-viewmodel, koin-compose-navigation3, koin-compose-viewmodel-navigation, koin-androidx-compose(-navigation) | Compose MP + AndroidX |\n| `ktor/` | koin-ktor, koin-logger-slf4j | JVM server |\n| `plugins/` | koin-gradle-plugin | |\n| `bom/` | koin-bom | Aligns all artifact versions |\n\n### Key engine areas (`core/koin-core/src/commonMain/kotlin/org/koin/`)\n\n- `core/resolution/` — instance resolution. `CoreResolverV2.kt` is the 4.2+ engine: injected params → stacked params → registry (scope source → linked scopes → archetype)\n  - **Qualified lookups are registry-only.** A `get(named(...))` skips the stacked-parameter path entirely — parameters carry no qualifier (`ParametersHolder` matches by type only), so a stacked param can never satisfy a qualified request. Removing this guard re-opens #2370/#2408 (a `parametersOf` value shadowing a qualified dependency of the same type). Only unqualified resolution reads the param stack — that's the #2387 ViewModel path; don't break it.\n- `core/registry/` — definition & instance registries (indexing by type + qualifier)\n- `core/scope/` — `Scope`, scope archetypes, linked scopes\n- `core/module/` — module DSL (`single`, `factory`, `scoped`, `bind`, `includes`)\n- `mp/` — multiplatform abstractions (`KoinPlatform`, platform tools)\n\n## Build & Test (run from `projects/`)\n\n```bash\n./gradlew :core:koin-core:jvmTest              # one module, JVM only (fast) — KMP modules have no plain `test` task\n./gradlew :core:koin-core:allTests             # one module, all KMP targets\n./test.sh                                      # clean JVM-side run: `test` (Android/JVM) + `jvmTest` (KMP modules); non-JVM targets need allTests / test-macos.sh\n./test-macos.sh                                # Apple/native targets\n./gradlew :core:koin-core:compileKotlinWasmJs  # quick non-JVM compile check\n./install.sh                                   # publish all to Maven Local\n./benchmark.sh                                 # core benchmarks (core/benchmark)\n./compare_apis.sh                              # public API diff (binary compatibility)\n./clean.sh                                     # clean all builds\n```\n\n## PR Guards — check on EVERY pull request\n\nBefore opening (or approving) any PR, both guards must pass:\n\n### 1. Don't break the current API\n- `./gradlew apiCheck` must pass (kotlinx binary-compatibility-validator, applied at the root — `.api` dumps per module, e.g. `core/koin-core/api/koin-core.api`).\n- If the PR intentionally **adds** public API: regenerate dumps with `./gradlew apiDump`, commit the `.api` diff, and review it with `./compare_apis.sh` (diffs `.api` files vs `main`).\n- **Removals or signature changes of public API are not acceptable in patch releases** — additions only. Breaking changes need a minor/major version and an explicit deprecation cycle (`@Deprecated` with `ReplaceWith` first).\n- Behavioral compatibility counts as API: a PR that changes resolution order/matching semantics must state the old vs new behavior in its description, even if no signature changed.\n\n### 2. New tests for every new case or fixed use-case\n- **Bug fix** → a regression test that reproduces the issue: fails before the fix, passes after. Place it in `commonTest` whenever the code under test is common.\n- **New case/feature** → tests covering the new behavior, including the edge that motivated it (qualifiers, scopes, params, platform-specific paths).\n- A PR touching `commonMain` without an accompanying test change needs an explicit justification in the description.\n- KMP: tests must run beyond JVM when the code is common (`allTests`, or at minimum compile-check one native target + `wasmJs`).\n\n## Conventions\n\n- **KMP discipline:** `commonMain` changes must be verified beyond JVM — compile at least one native target and `wasmJs` before considering a change done. Several bug classes (qualifiedName, klib, JS) only surface off-JVM.\n- **Every bug fix ships a regression test**, in `commonTest` whenever the code is common.\n- **Commit style:** `Fix #NNNN - description` referencing the GitHub issue (e.g. `Fix #2387 - CoreResolverV2 lost stacked-params lookup on linked scopes`).\n- **Public API changes:** run `./compare_apis.sh`; avoid breaking binary compatibility in patch releases.\n- **Resolution-semantics changes** (anything in `core/resolution/`): describe the behavioral delta (old vs new resolution order/matching) in the PR description — resolver behavior changes have historically caused regressions.\n- **User-visible changes need a docs touch** — documentation lives in `../docs/` (published to insert-koin.io).\n\n### KMP test traps (hard rules)\n\n- **Never weaken `test.sh` to scoped tasks.** In KMP projects `./gradlew test` resolves only to Android/JVM unit tests and **silently skips** native, JS, and WASM tests. Full verification = `allTests` (or per-target tasks explicitly). Do not add `-x` exclusions to make a run green — fix the failing test at the root cause.\n- **No backtick test names in `commonTest`** — backtick names (`` fun `my test`() ``) break `compileTestKotlinJs`/`compileTestKotlinWasmJs` (invalid JS identifiers). Use snake_case in `commonTest`; backticks are fine in `jvmTest` only.\n- **`runTest {}` in `commonTest` must use block-body syntax** — on wasmJs, expression-body forms (`fun foo() = runTest { }`) fail with return-type mismatch. Use `fun testX() { runTest { ... } }`.\n\n#### Known pre-existing off-JVM failures (not yours — don't chase them)\n\n`allTests` is currently red on two counts unrelated to any recent resolver work (verified by stash-comparison on the `4.2.2` branch, 2026-06-08). If you touch `commonMain` and run `allTests`, expect these and confirm your diff doesn't change them:\n\n- **`createdAtStart` on wasmJs** — `CreateOnStart`, `OverrideAndCreateatStartTest` fail with `AssertionError: Expected value to be true`. Core `single(createdAtStart = true)` eager-init doesn't fire on wasmJs. This is *core*, **not** the compiler-plugin `createdAtStart` drop (#2425/#2415) — same symptom family, different layer.\n- **`DynamicModulesTest` on native** — its backtick names contain `\" - \"` (e.g. `` `should unload one module definition - factory` ``), crashing the Kotlin/Native test reporter (`ServiceMessagesParser`) and aborting `macosArm64Test`. This is the backtick-name trap above, already shipped. Fix = rename to snake_case.\n\n## Versioning\n\n- Version is set in `gradle.properties` (`koinVersion`).\n- Branch lines: `4.2.x` = stable maintenance (regressions/bug fixes only), next minor in development on `main`.\n- `koin-bom` must stay aligned with all published modules.\n\n### Release notes: Added vs Fixed\n\nThe test: **\"Would this affect a user on the previous supported version range?\"**\n- Yes → `Fixed` (genuine regression or pre-existing bug)\n- No, only relevant to a new Kotlin/AGP/library range → `Added` (new support story), as ONE umbrella entry with sub-bullets — users care that \"Kotlin X works\", not how many internal changes it took.\n- Internal refactor with identical user-visible behavior → internal changes, not `Changed`.\n","projects/llms.txt":"# Koin — Kotlin Dependency Injection Framework\n\n> Pragmatic DI framework for Kotlin and Kotlin Multiplatform. Two styles, one container: Koin DSL or Koin Annotations (JSR-330 compatible). Compile safe in both styles with the Koin Compiler Plugin — no KSP, no kapt, no reflection at its core. Runs on all Kotlin Multiplatform targets.\n\nConcise index: [llms.txt](https://github.com/InsertKoinIO/koin/blob/main/llms.txt). This file is the API cheat sheet.\n\n## Key Facts\n\n- **Version**: Koin 4.2.2, Koin Compiler Plugin 1.2.1, Kotlin 2.3.20 or later\n- **Language**: Kotlin (100%)\n- **Platforms**: Android, iOS, Desktop, Web (JS/Wasm), Backend (Ktor) via Kotlin Multiplatform\n- **License**: Apache 2.0\n- **Repository**: https://github.com/InsertKoinIO/koin\n- **Website**: https://insert-koin.io\n- **Documentation**: https://insert-koin.io/docs/intro/what-is-koin\n- **Koin skills (AI agents)**: https://github.com/InsertKoinIO/koin-skills\n- **Kotzilla MCP (runtime debugging)**: https://mcp.kotzilla.io/mcp\n\n## IDE Plugin\n\nOfficial plugin for Android Studio & IntelliJ IDEA — code navigation between definitions and injection points, live safety checks, dependency graph visualization.\n\n**Download:** https://plugins.jetbrains.com/plugin/26131-koin-dependency-injection-official-\n\n## Gradle Dependencies\n\n```kotlin\n// BOM (recommended)\nplugins {\n    id(\"io.insert-koin.compiler.plugin\") version \"1.2.1\"     // Koin Compiler Plugin (recommended — compile-time safety)\n}\n\nimplementation(platform(\"io.insert-koin:koin-bom:4.2.2\"))   // BOM resolves every Koin artifact version\nimplementation(\"io.insert-koin:koin-core\")                   // Core (all platforms)\nimplementation(\"io.insert-koin:koin-annotations\")            // Annotations style (with the Compiler Plugin)\nimplementation(\"io.insert-koin:koin-android\")                // Android (Context, ViewModel, Scope)\nimplementation(\"io.insert-koin:koin-compose\")                // Compose integration\nimplementation(\"io.insert-koin:koin-compose-viewmodel\")      // Compose ViewModel (koinViewModel)\nimplementation(\"io.insert-koin:koin-core-coroutines\")        // Coroutines (lazy modules)\nimplementation(\"io.insert-koin:koin-ktor\")                   // Ktor server\nimplementation(\"io.insert-koin:koin-test\")                   // Testing\nimplementation(\"io.insert-koin:koin-test-junit5\")            // JUnit 5 testing\n\n// Never Koin KSP: no com.google.devtools.ksp for Koin, no koin-ksp-compiler, no koin-annotations:2.x pin\n```\n\n---\n\n## Starting Koin\n\n### Android\n\n```kotlin\nimport org.koin.android.ext.koin.androidContext\nimport org.koin.android.ext.koin.androidLogger\nimport org.koin.core.context.startKoin\nimport org.koin.androidx.workmanager.koin.workManagerFactory\n\nclass MyApplication : Application() {\n    override fun onCreate() {\n        super.onCreate()\n        startKoin {\n            androidLogger()\n            androidContext(this@MyApplication)\n            workManagerFactory()              // if using WorkManager\n            modules(appModule, featureModule)\n        }\n    }\n}\n```\n\n### Annotations (with compile-time graph validation)\n\n```kotlin\nimport org.koin.core.annotation.KoinApplication\nimport org.koin.plugin.module.dsl.startKoin   // reified startKoin<App> from the Compiler Plugin\n\n@KoinApplication\nclass MyApplication : Application() {\n    override fun onCreate() {\n        super.onCreate()\n        startKoin<MyApplication> {    // triggers A3 full graph validation\n            androidLogger()\n            androidContext(this@MyApplication)\n            workManagerFactory()\n        }\n    }\n}\n```\n\n### Ktor\n\n```kotlin\nimport org.koin.ktor.plugin.Koin\n\nfun Application.module() {\n    install(Koin) {\n        modules(appModule)\n    }\n}\n```\n\n### KMP / Non-Android\n\n```kotlin\nimport org.koin.core.context.startKoin\n\nstartKoin {\n    modules(commonModule, platformModule)\n}\n```\n\n---\n\n## Defining Dependencies\n\n### Compiler Plugin DSL (recommended)\n\nPick one style per project — Koin DSL or Koin Annotations — and don't mix them unless needed (then one style per module).\n\n```kotlin\nimport org.koin.dsl.module\nimport org.koin.dsl.bind\nimport org.koin.plugin.module.dsl.*\n\nval appModule = module {\n    // Singleton — one instance for app lifetime\n    single<Database>()\n    single<UserRepository>()\n\n    // Factory — new instance each time\n    factory<GetUserUseCase>()\n\n    // ViewModel — Android ViewModel lifecycle\n    viewModel<UserViewModel>()\n\n    // Worker — WorkManager\n    worker<SyncWorker>()\n\n    // Interface binding\n    single<UserRepositoryImpl>() bind UserRepository::class\n\n    // Multiple bindings\n    single<MyServiceImpl>() binds arrayOf(ServiceA::class, ServiceB::class)\n\n    // Module composition\n    includes(networkModule, databaseModule)\n}\n```\n\n### Compiler Plugin DSL with Function Builders (create)\n\n```kotlin\nimport org.koin.dsl.module\nimport org.koin.plugin.module.dsl.create\n\n// Builder functions — for wrapping external libraries (Room, Retrofit, etc.)\nfun database(context: Context): AppDatabase =\n    Room.databaseBuilder(context, AppDatabase::class.java, \"my-db\").build()\n\nfun topicDao(db: AppDatabase): TopicDao = db.topicDao()\n\nfun json(): Json = Json { ignoreUnknownKeys = true }\n\nval databaseModule = module {\n    single { create(::database) }    // parameters auto-resolved from DI\n    single { create(::topicDao) }\n    single { create(::json) }\n}\n```\n\n### Constructor DSL and Classic DSL\n\nConstructor DSL (`singleOf(::T)`) suits projects without the Compiler Plugin. Classic DSL (`single { T(get()) }`) is for custom expressions a constructor or function builder can't express. Both are also checked by the Compiler Plugin when it is applied.\n\n```kotlin\nimport org.koin.dsl.module\nimport org.koin.core.module.dsl.*\n\nval appModule = module {\n    // Constructor DSL — constructor reference\n    singleOf(::Database)\n    singleOf(::UserRepository)\n    factoryOf(::GetUserUseCase)\n    viewModelOf(::UserViewModel)\n\n    // Classic DSL — lambda (manual wiring)\n    single { Database() }\n    single { UserRepository(get()) }\n    factory { GetUserUseCase(get()) }\n    viewModel { UserViewModel(get()) }\n}\n```\n\n### Annotations\n\n```kotlin\nimport org.koin.core.annotation.*\n\n@Singleton\nclass Database\n\n@Singleton\nclass UserRepository(private val db: Database)\n\n@Factory\nclass GetUserUseCase(private val repo: UserRepository)\n\n@KoinViewModel\nclass UserViewModel(private val useCase: GetUserUseCase) : ViewModel()\n\n// Module with component scan\n@Module\n@ComponentScan(\"com.myapp\")\nclass AppModule\n\n// Module with provider functions (for external libraries)\n@Module\ninternal object DatabaseModule {\n    @Singleton\n    fun providesDatabase(context: Context): AppDatabase =\n        Room.databaseBuilder(context, AppDatabase::class.java, \"my-db\").build()\n}\n```\n\n### Annotated Top-Level Functions\n\n```kotlin\nimport org.koin.core.annotation.Singleton\nimport org.koin.core.annotation.Factory\n\n@Singleton\nfun provideDatabase(context: Context): AppDatabase =\n    Room.databaseBuilder(context, AppDatabase::class.java, \"my-db\").build()\n\n@Factory\nfun provideCache(db: AppDatabase): CacheService = CacheServiceImpl(db)\n```\n\nDiscovered by `@ComponentScan` like annotated classes.\n\n---\n\n## Injecting Dependencies\n\n### In Android Activity / Fragment\n\n```kotlin\nimport org.koin.android.ext.android.inject\nimport org.koin.android.ext.android.get\nimport org.koin.androidx.viewmodel.ext.android.viewModel\n\nclass MainActivity : ComponentActivity() {\n    // Lazy injection\n    private val viewModel: UserViewModel by viewModel()\n    private val service: MyService by inject()\n\n    // Eager injection\n    val eagerService: MyService = get()\n}\n```\n\n### In Activity with Scope\n\n```kotlin\nimport org.koin.android.scope.AndroidScopeComponent\nimport org.koin.androidx.scope.activityScope\nimport org.koin.android.ext.android.inject\nimport org.koin.androidx.viewmodel.ext.android.viewModel\n\nclass MainActivity : ComponentActivity(), AndroidScopeComponent {\n    override val scope: Scope by activityScope()\n\n    private val viewModel: MainViewModel by viewModel()\n    private val tracker: ActivityTracker by inject()   // scoped to activity lifecycle\n}\n```\n\n### In Compose\n\n```kotlin\nimport org.koin.compose.viewmodel.koinViewModel\nimport org.koin.compose.koinInject\n\n@Composable\nfun HomeScreen(viewModel: HomeViewModel = koinViewModel()) {\n    val analytics: AnalyticsHelper = koinInject()\n    // ...\n}\n```\n\n### In Compose with Parameters (Navigation Arguments)\n\n```kotlin\nimport org.koin.compose.viewmodel.koinViewModel\nimport org.koin.core.parameter.parametersOf\n\n@Composable\nfun DetailScreen(\n    newsId: String,\n    viewModel: DetailViewModel = koinViewModel(key = newsId) { parametersOf(newsId) }\n) {\n    // Each unique newsId gets its own ViewModel instance\n}\n```\n\n### In Ktor\n\n```kotlin\nimport org.koin.ktor.ext.inject\n\nfun Route.userRoutes() {\n    val service: UserService by inject()\n\n    get(\"/users/{id}\") {\n        val user = service.getUser(call.parameters[\"id\"]!!)\n        call.respond(user)\n    }\n}\n```\n\n### Constructor Injection (Recommended for Business Logic)\n\n```kotlin\n// No Koin imports needed — dependencies resolved by the framework\nclass UserRepository(\n    private val database: Database,\n    private val networkClient: NetworkClient\n)\n\nclass GetUserUseCase(\n    private val repository: UserRepository\n)\n```\n\n---\n\n## Qualifiers\n\n### @Named (String Qualifier)\n\n```kotlin\nimport org.koin.core.annotation.Named\nimport org.koin.core.qualifier.named\n\n// Definition\n@Singleton @Named(\"local\")\nclass LocalDatabase : Database\n\n@Singleton @Named(\"remote\")\nclass RemoteDatabase : Database\n\n// Injection\n@Singleton\nclass UserRepository(\n    @Named(\"local\") private val localDb: Database,\n    @Named(\"remote\") private val remoteDb: Database\n)\n\n// Compiler Plugin DSL — the qualifier stays on the class (@Named above), the definition stays plain\nsingle<LocalDatabase>()\nsingle<RemoteDatabase>()\n\n// Classic DSL only\nsingle<Database>(named(\"local\")) { LocalDatabase() }\n```\n\n### Custom @Qualifier (with Parameters)\n\n```kotlin\nimport org.koin.core.annotation.Qualifier\n\n@Qualifier\n@Retention(AnnotationRetention.RUNTIME)\nannotation class Dispatcher(val type: AppDispatchers)\n\nenum class AppDispatchers { Default, IO }\n\n// Provider\n@Singleton @Dispatcher(AppDispatchers.IO)\nfun ioDispatcher(): CoroutineDispatcher = Dispatchers.IO\n\n// Consumer\n@Singleton\nclass MyRepository(\n    @Dispatcher(AppDispatchers.IO) private val dispatcher: CoroutineDispatcher\n)\n```\n\n---\n\n## Scopes\n\n### DSL Scopes\n\n```kotlin\nimport org.koin.dsl.module\nimport org.koin.plugin.module.dsl.scoped\nimport org.koin.androidx.scope.dsl.activityScope\nimport org.koin.androidx.scope.dsl.fragmentScope\n\nval appModule = module {\n    // Activity-scoped\n    activityScope {\n        scoped<ActivityTracker>()\n    }\n\n    // Fragment-scoped\n    fragmentScope {\n        scoped<FragmentPresenter>()\n    }\n\n    // Custom scope\n    scope<UserSession> {\n        scoped<UserRepository>()\n        scoped<UserPreferences>()\n    }\n}\n```\n\n### Annotation Scopes\n\n```kotlin\nimport org.koin.core.annotation.Scoped\nimport org.koin.core.annotation.Scope\nimport org.koin.android.annotation.ActivityScope\nimport org.koin.android.annotation.FragmentScope\n\n@ActivityScope\nclass ActivityTracker(val activity: ComponentActivity)\n\n@FragmentScope\nclass FragmentPresenter(val fragment: Fragment)\n\n@Scope(UserSession::class)\n@Scoped\nclass UserRepository(val db: Database)\n```\n\n---\n\n## ViewModel\n\n### With SavedStateHandle\n\n```kotlin\n// SavedStateHandle is auto-injected (whitelisted type)\n@KoinViewModel\nclass HomeViewModel(\n    private val savedStateHandle: SavedStateHandle,\n    private val repository: UserRepository,\n) : ViewModel()\n\n// DSL\nviewModel<HomeViewModel>()\n```\n\n### With @InjectedParam (Navigation Arguments)\n\n```kotlin\nimport org.koin.core.annotation.InjectedParam\n\n@KoinViewModel\nclass DetailViewModel(\n    private val repository: NewsRepository,\n    @InjectedParam val newsId: String,\n) : ViewModel()\n\n// Compose usage:\nkoinViewModel(key = newsId) { parametersOf(newsId) }\n```\n\n---\n\n## WorkManager\n\n```kotlin\nimport org.koin.android.annotation.KoinWorker\n\n// Annotations\n@KoinWorker\nclass SyncWorker(\n    context: Context,\n    params: WorkerParameters,\n    private val repository: NewsRepository,\n    @Dispatcher(AppDispatchers.IO) private val dispatcher: CoroutineDispatcher,\n) : CoroutineWorker(context, params)\n\n// DSL\nval syncModule = module {\n    single<WorkManagerSyncManager>() bind SyncManager::class\n    worker<SyncWorker>()\n}\n\n// Bootstrap — add workManagerFactory()\nstartKoin {\n    workManagerFactory()\n    modules(appModule)\n}\n```\n\n---\n\n## Module Organization\n\n### Annotations — Auto-Discovery\n\n```kotlin\nimport org.koin.core.annotation.*\n\n// Modules with @Configuration are auto-discovered by startKoin<T>\n@Module @ComponentScan(\"com.myapp.core\") @Configuration\nclass CoreModule\n\n@Module @ComponentScan(\"com.myapp.feature\") @Configuration\nclass FeatureModule\n\n@Module(includes = [DatabaseModule::class]) @Configuration\nclass DataModule\n\n// App entry point — assembles all @Configuration modules\n@KoinApplication\nclass MyApplication : Application()\n```\n\n### DSL — Explicit Composition\n\n```kotlin\nval coreModule = module { single<Database>(); single<NetworkClient>() }\nval featureModule = module { includes(coreModule); viewModel<HomeViewModel>() }\n\nval appModule = module {\n    includes(coreModule, featureModule, syncModule)\n}\n\nstartKoin { modules(appModule) }\n```\n\n### @Configuration Labels (Environment-Specific)\n\n```kotlin\n@Module @ComponentScan(\"com.myapp.core\") @Configuration(\"prod\")\nclass ProdCoreModule\n\n@Module @ComponentScan(\"com.myapp.core\") @Configuration(\"test\")\nclass TestCoreModule\n\n// Modules with the same label see each other's definitions\n```\n\n---\n\n## Dynamic Modules\n\n```kotlin\nimport org.koin.core.context.loadKoinModules\nimport org.koin.core.context.unloadKoinModules\n\n// Load at runtime (feature flags, A/B testing)\nloadKoinModules(premiumModule)\n\n// Unload when no longer needed\nunloadKoinModules(premiumModule)\n```\n\n---\n\n## Lazy Modules (Background Loading)\n\n```kotlin\nimport org.koin.core.context.startKoin\nimport org.koin.core.lazyModules\n\nstartKoin {\n    modules(criticalModule)\n    lazyModules(heavyModule, analyticsModule)  // loaded in background\n}\n```\n\n---\n\n## Properties\n\n```kotlin\nimport org.koin.core.annotation.Property\n\n@Singleton\nclass ApiClient(\n    @Property(\"api_url\") val url: String,\n    @Property(\"api_key\") val key: String\n)\n\n// Provide properties\nstartKoin {\n    properties(mapOf(\"api_url\" to \"https://api.example.com\", \"api_key\" to \"secret\"))\n}\n\n// DSL\nsingle { ApiClient(getProperty(\"api_url\"), getProperty(\"api_key\")) }\n```\n\n---\n\n## Optional / Nullable Dependencies\n\n```kotlin\n// Nullable param → resolved with getOrNull()\n@Singleton\nclass MyService(\n    val required: RequiredDep,\n    val optional: OptionalDep?     // null if not in graph\n)\n\n// DSL\nsingle<MyService>()  // compiler plugin handles getOrNull() automatically\n```\n\n---\n\n## Lazy Injection\n\n```kotlin\n// Lazy<T> → deferred creation\n@Singleton\nclass MyService(\n    val heavy: Lazy<HeavyDependency>   // created on first access\n)\n```\n\n---\n\n## List Injection\n\n```kotlin\n// List<T> → collects all definitions of type T\n@Singleton\nclass LoggerAggregator(\n    val loggers: List<LoggerDataSource>   // all LoggerDataSource definitions\n)\n```\n\n---\n\n## Testing\n\n```kotlin\nimport org.koin.test.KoinTest\nimport org.koin.test.inject\n\nclass MyTest : KoinTest {\n    private val service: MyService by inject()\n\n    @Test\n    fun testService() {\n        // service is resolved from Koin\n    }\n}\n```\n\nGraph verification is done at compile time by the Koin Compiler Plugin — don't write `checkModules()` / `verify()` tests (on their way to deprecation).\n\n---\n\n## Compile-Time Safety (with Koin Compiler Plugin)\n\nThe Koin Compiler Plugin validates your dependency graph at build time:\n\n- **A2 — Per-Module:** validates definitions against visible scope per module\n- **A3 — Full Graph:** validates ALL modules at `startKoin<T>()`\n- **A4 — Call-Site:** validates every `get<T>()`, `inject<T>()`, `koinViewModel<T>()` call\n\nWorks with both DSL and Annotations. Replaces `verify()` / `checkModules()`. If it compiles, it works.\n\n```kotlin\nkoinCompiler {\n    compileSafety = true       // default\n    skipDefaultValues = true   // default\n    unsafeDslChecks = true     // default\n}\n```\n\nSee https://insert-koin.io/docs/reference/koin-compiler/compile-safety\n\n---\n\n## Architecture\n\n- O(1) resolution via `HashMap.get()` — no reflection, no iteration\n- `ConcurrentHashMap` (JVM) / `ConcurrentMutableMap` (Native)\n- Fully compatible with R8/ProGuard\n- Pure common Kotlin runtime — all KMP targets\n\n## Project Structure\n\n```\nkoin/projects/\n├── core/      # koin-core (engine), koin-core-viewmodel, koin-core-coroutines, koin-annotations, koin-test*\n├── android/   # koin-android, koin-androidx-workmanager, koin-androidx-startup, koin-dagger-bridge…\n├── compose/   # koin-compose, koin-compose-viewmodel, koin-compose-navigation3…\n├── ktor/      # koin-ktor, koin-logger-slf4j\n├── plugins/   # koin-gradle-plugin\n└── bom/       # koin-bom\n```\n\nBuilding and contributing: https://github.com/InsertKoinIO/koin/blob/main/AGENTS.md\n\n## Related Repositories\n\n- **Compiler Plugin**: https://github.com/InsertKoinIO/koin-compiler-plugin\n- **Koin skills (AI agents)**: https://github.com/InsertKoinIO/koin-skills — Claude Code: `/plugin marketplace add InsertKoinIO/koin-skills` then `/plugin install koin@koin-skills`. Other agents (Cursor, Codex, Copilot, Windsurf, Gemini…): `npx skills add InsertKoinIO/koin-skills` (Node.js 22+)\n- **Kotzilla MCP**: https://mcp.kotzilla.io/mcp — Koin error diagnosis and runtime debugging (graph behavior, scopes, memory, performance)\n- **Playground Apps**: https://github.com/InsertKoinIO/playground-apps\n- **Website**: https://insert-koin.io\n"},"items":[{"name":"llms.txt","path":"projects/llms.txt","rawUrl":"https://raw.githubusercontent.com/InsertKoinIO/koin/HEAD/projects/llms.txt","title":"projects - LLM Index & Context Digest","category":"root-instruction","format":"text","content":"# Koin — Kotlin Dependency Injection Framework\n\n> Pragmatic DI framework for Kotlin and Kotlin Multiplatform. Two styles, one container: Koin DSL or Koin Annotations (JSR-330 compatible). Compile safe in both styles with the Koin Compiler Plugin — no KSP, no kapt, no reflection at its core. Runs on all Kotlin Multiplatform targets.\n\nConcise index: [llms.txt](https://github.com/InsertKoinIO/koin/blob/main/llms.txt). This file is the API cheat sheet.\n\n## Key Facts\n\n- **Version**: Koin 4.2.2, Koin Compiler Plugin 1.2.1, Kotlin 2.3.20 or later\n- **Language**: Kotlin (100%)\n- **Platforms**: Android, iOS, Desktop, Web (JS/Wasm), Backend (Ktor) via Kotlin Multiplatform\n- **License**: Apache 2.0\n- **Repository**: https://github.com/InsertKoinIO/koin\n- **Website**: https://insert-koin.io\n- **Documentation**: https://insert-koin.io/docs/intro/what-is-koin\n- **Koin skills (AI agents)**: https://github.com/InsertKoinIO/koin-skills\n- **Kotzilla MCP (runtime debugging)**: https://mcp.kotzilla.io/mcp\n\n## IDE Plugin\n\nOfficial plugin for Android Studio & IntelliJ IDEA — code navigation between definitions and injection points, live safety checks, dependency graph visualization.\n\n**Download:** https://plugins.jetbrains.com/plugin/26131-koin-dependency-injection-official-\n\n## Gradle Dependencies\n\n```kotlin\n// BOM (recommended)\nplugins {\n    id(\"io.insert-koin.compiler.plugin\") version \"1.2.1\"     // Koin Compiler Plugin (recommended — compile-time safety)\n}\n\nimplementation(platform(\"io.insert-koin:koin-bom:4.2.2\"))   // BOM resolves every Koin artifact version\nimplementation(\"io.insert-koin:koin-core\")                   // Core (all platforms)\nimplementation(\"io.insert-koin:koin-annotations\")            // Annotations style (with the Compiler Plugin)\nimplementation(\"io.insert-koin:koin-android\")                // Android (Context, ViewModel, Scope)\nimplementation(\"io.insert-koin:koin-compose\")                // Compose integration\nimplementation(\"io.insert-koin:koin-compose-viewmodel\")      // Compose ViewModel (koinViewModel)\nimplementation(\"io.insert-koin:koin-core-coroutines\")        // Coroutines (lazy modules)\nimplementation(\"io.insert-koin:koin-ktor\")                   // Ktor server\nimplementation(\"io.insert-koin:koin-test\")                   // Testing\nimplementation(\"io.insert-koin:koin-test-junit5\")            // JUnit 5 testing\n\n// Never Koin KSP: no com.google.devtools.ksp for Koin, no koin-ksp-compiler, no koin-annotations:2.x pin\n```\n\n---\n\n## Starting Koin\n\n### Android\n\n```kotlin\nimport org.koin.android.ext.koin.androidContext\nimport org.koin.android.ext.koin.androidLogger\nimport org.koin.core.context.startKoin\nimport org.koin.androidx.workmanager.koin.workManagerFactory\n\nclass MyApplication : Application() {\n    override fun onCreate() {\n        super.onCreate()\n        startKoin {\n            androidLogger()\n            androidContext(this@MyApplication)\n            workManagerFactory()              // if using WorkManager\n            modules(appModule, featureModule)\n        }\n    }\n}\n```\n\n### Annotations (with compile-time graph validation)\n\n```kotlin\nimport org.koin.core.annotation.KoinApplication\nimport org.koin.plugin.module.dsl.startKoin   // reified startKoin<App> from the Compiler Plugin\n\n@KoinApplication\nclass MyApplication : Application() {\n    override fun onCreate() {\n        super.onCreate()\n        startKoin<MyApplication> {    // triggers A3 full graph validation\n            androidLogger()\n            androidContext(this@MyApplication)\n            workManagerFactory()\n        }\n    }\n}\n```\n\n### Ktor\n\n```kotlin\nimport org.koin.ktor.plugin.Koin\n\nfun Application.module() {\n    install(Koin) {\n        modules(appModule)\n    }\n}\n```\n\n### KMP / Non-Android\n\n```kotlin\nimport org.koin.core.context.startKoin\n\nstartKoin {\n    modules(commonModule, platformModule)\n}\n```\n\n---\n\n## Defining Dependencies\n\n### Compiler Plugin DSL (recommended)\n\nPick one style per project — Koin DSL or Koin Annotations — and don't mix them unless needed (then one style per module).\n\n```kotlin\nimport org.koin.dsl.module\nimport org.koin.dsl.bind\nimport org.koin.plugin.module.dsl.*\n\nval appModule = module {\n    // Singleton — one instance for app lifetime\n    single<Database>()\n    single<UserRepository>()\n\n    // Factory — new instance each time\n    factory<GetUserUseCase>()\n\n    // ViewModel — Android ViewModel lifecycle\n    viewModel<UserViewModel>()\n\n    // Worker — WorkManager\n    worker<SyncWorker>()\n\n    // Interface binding\n    single<UserRepositoryImpl>() bind UserRepository::class\n\n    // Multiple bindings\n    single<MyServiceImpl>() binds arrayOf(ServiceA::class, ServiceB::class)\n\n    // Module composition\n    includes(networkModule, databaseModule)\n}\n```\n\n### Compiler Plugin DSL with Function Builders (create)\n\n```kotlin\nimport org.koin.dsl.module\nimport org.koin.plugin.module.dsl.create\n\n// Builder functions — for wrapping external libraries (Room, Retrofit, etc.)\nfun database(context: Context): AppDatabase =\n    Room.databaseBuilder(context, AppDatabase::class.java, \"my-db\").build()\n\nfun topicDao(db: AppDatabase): TopicDao = db.topicDao()\n\nfun json(): Json = Json { ignoreUnknownKeys = true }\n\nval databaseModule = module {\n    single { create(::database) }    // parameters auto-resolved from DI\n    single { create(::topicDao) }\n    single { create(::json) }\n}\n```\n\n### Constructor DSL and Classic DSL\n\nConstructor DSL (`singleOf(::T)`) suits projects without the Compiler Plugin. Classic DSL (`single { T(get()) }`) is for custom expressions a constructor or function builder can't express. Both are also checked by the Compiler Plugin when it is applied.\n\n```kotlin\nimport org.koin.dsl.module\nimport org.koin.core.module.dsl.*\n\nval appModule = module {\n    // Constructor DSL — constructor reference\n    singleOf(::Database)\n    singleOf(::UserRepository)\n    factoryOf(::GetUserUseCase)\n    viewModelOf(::UserViewModel)\n\n    // Classic DSL — lambda (manual wiring)\n    single { Database() }\n    single { UserRepository(get()) }\n    factory { GetUserUseCase(get()) }\n    viewModel { UserViewModel(get()) }\n}\n```\n\n### Annotations\n\n```kotlin\nimport org.koin.core.annotation.*\n\n@Singleton\nclass Database\n\n@Singleton\nclass UserRepository(private val db: Database)\n\n@Factory\nclass GetUserUseCase(private val repo: UserRepository)\n\n@KoinViewModel\nclass UserViewModel(private val useCase: GetUserUseCase) : ViewModel()\n\n// Module with component scan\n@Module\n@ComponentScan(\"com.myapp\")\nclass AppModule\n\n// Module with provider functions (for external libraries)\n@Module\ninternal object DatabaseModule {\n    @Singleton\n    fun providesDatabase(context: Context): AppDatabase =\n        Room.databaseBuilder(context, AppDatabase::class.java, \"my-db\").build()\n}\n```\n\n### Annotated Top-Level Functions\n\n```kotlin\nimport org.koin.core.annotation.Singleton\nimport org.koin.core.annotation.Factory\n\n@Singleton\nfun provideDatabase(context: Context): AppDatabase =\n    Room.databaseBuilder(context, AppDatabase::class.java, \"my-db\").build()\n\n@Factory\nfun provideCache(db: AppDatabase): CacheService = CacheServiceImpl(db)\n```\n\nDiscovered by `@ComponentScan` like annotated classes.\n\n---\n\n## Injecting Dependencies\n\n### In Android Activity / Fragment\n\n```kotlin\nimport org.koin.android.ext.android.inject\nimport org.koin.android.ext.android.get\nimport org.koin.androidx.viewmodel.ext.android.viewModel\n\nclass MainActivity : ComponentActivity() {\n    // Lazy injection\n    private val viewModel: UserViewModel by viewModel()\n    private val service: MyService by inject()\n\n    // Eager injection\n    val eagerService: MyService = get()\n}\n```\n\n### In Activity with Scope\n\n```kotlin\nimport org.koin.android.scope.AndroidScopeComponent\nimport org.koin.androidx.scope.activityScope\nimport org.koin.android.ext.android.inject\nimport org.koin.androidx.viewmodel.ext.android.viewModel\n\nclass MainActivity : ComponentActivity(), AndroidScopeComponent {\n    override val scope: Scope by activityScope()\n\n    private val viewModel: MainViewModel by viewModel()\n    private val tracker: ActivityTracker by inject()   // scoped to activity lifecycle\n}\n```\n\n### In Compose\n\n```kotlin\nimport org.koin.compose.viewmodel.koinViewModel\nimport org.koin.compose.koinInject\n\n@Composable\nfun HomeScreen(viewModel: HomeViewModel = koinViewModel()) {\n    val analytics: AnalyticsHelper = koinInject()\n    // ...\n}\n```\n\n### In Compose with Parameters (Navigation Arguments)\n\n```kotlin\nimport org.koin.compose.viewmodel.koinViewModel\nimport org.koin.core.parameter.parametersOf\n\n@Composable\nfun DetailScreen(\n    newsId: String,\n    viewModel: DetailViewModel = koinViewModel(key = newsId) { parametersOf(newsId) }\n) {\n    // Each unique newsId gets its own ViewModel instance\n}\n```\n\n### In Ktor\n\n```kotlin\nimport org.koin.ktor.ext.inject\n\nfun Route.userRoutes() {\n    val service: UserService by inject()\n\n    get(\"/users/{id}\") {\n        val user = service.getUser(call.parameters[\"id\"]!!)\n        call.respond(user)\n    }\n}\n```\n\n### Constructor Injection (Recommended for Business Logic)\n\n```kotlin\n// No Koin imports needed — dependencies resolved by the framework\nclass UserRepository(\n    private val database: Database,\n    private val networkClient: NetworkClient\n)\n\nclass GetUserUseCase(\n    private val repository: UserRepository\n)\n```\n\n---\n\n## Qualifiers\n\n### @Named (String Qualifier)\n\n```kotlin\nimport org.koin.core.annotation.Named\nimport org.koin.core.qualifier.named\n\n// Definition\n@Singleton @Named(\"local\")\nclass LocalDatabase : Database\n\n@Singleton @Named(\"remote\")\nclass RemoteDatabase : Database\n\n// Injection\n@Singleton\nclass UserRepository(\n    @Named(\"local\") private val localDb: Database,\n    @Named(\"remote\") private val remoteDb: Database\n)\n\n// Compiler Plugin DSL — the qualifier stays on the class (@Named above), the definition stays plain\nsingle<LocalDatabase>()\nsingle<RemoteDatabase>()\n\n// Classic DSL only\nsingle<Database>(named(\"local\")) { LocalDatabase() }\n```\n\n### Custom @Qualifier (with Parameters)\n\n```kotlin\nimport org.koin.core.annotation.Qualifier\n\n@Qualifier\n@Retention(AnnotationRetention.RUNTIME)\nannotation class Dispatcher(val type: AppDispatchers)\n\nenum class AppDispatchers { Default, IO }\n\n// Provider\n@Singleton @Dispatcher(AppDispatchers.IO)\nfun ioDispatcher(): CoroutineDispatcher = Dispatchers.IO\n\n// Consumer\n@Singleton\nclass MyRepository(\n    @Dispatcher(AppDispatchers.IO) private val dispatcher: CoroutineDispatcher\n)\n```\n\n---\n\n## Scopes\n\n### DSL Scopes\n\n```kotlin\nimport org.koin.dsl.module\nimport org.koin.plugin.module.dsl.scoped\nimport org.koin.androidx.scope.dsl.activityScope\nimport org.koin.androidx.scope.dsl.fragmentScope\n\nval appModule = module {\n    // Activity-scoped\n    activityScope {\n        scoped<ActivityTracker>()\n    }\n\n    // Fragment-scoped\n    fragmentScope {\n        scoped<FragmentPresenter>()\n    }\n\n    // Custom scope\n    scope<UserSession> {\n        scoped<UserRepository>()\n        scoped<UserPreferences>()\n    }\n}\n```\n\n### Annotation Scopes\n\n```kotlin\nimport org.koin.core.annotation.Scoped\nimport org.koin.core.annotation.Scope\nimport org.koin.android.annotation.ActivityScope\nimport org.koin.android.annotation.FragmentScope\n\n@ActivityScope\nclass ActivityTracker(val activity: ComponentActivity)\n\n@FragmentScope\nclass FragmentPresenter(val fragment: Fragment)\n\n@Scope(UserSession::class)\n@Scoped\nclass UserRepository(val db: Database)\n```\n\n---\n\n## ViewModel\n\n### With SavedStateHandle\n\n```kotlin\n// SavedStateHandle is auto-injected (whitelisted type)\n@KoinViewModel\nclass HomeViewModel(\n    private val savedStateHandle: SavedStateHandle,\n    private val repository: UserRepository,\n) : ViewModel()\n\n// DSL\nviewModel<HomeViewModel>()\n```\n\n### With @InjectedParam (Navigation Arguments)\n\n```kotlin\nimport org.koin.core.annotation.InjectedParam\n\n@KoinViewModel\nclass DetailViewModel(\n    private val repository: NewsRepository,\n    @InjectedParam val newsId: String,\n) : ViewModel()\n\n// Compose usage:\nkoinViewModel(key = newsId) { parametersOf(newsId) }\n```\n\n---\n\n## WorkManager\n\n```kotlin\nimport org.koin.android.annotation.KoinWorker\n\n// Annotations\n@KoinWorker\nclass SyncWorker(\n    context: Context,\n    params: WorkerParameters,\n    private val repository: NewsRepository,\n    @Dispatcher(AppDispatchers.IO) private val dispatcher: CoroutineDispatcher,\n) : CoroutineWorker(context, params)\n\n// DSL\nval syncModule = module {\n    single<WorkManagerSyncManager>() bind SyncManager::class\n    worker<SyncWorker>()\n}\n\n// Bootstrap — add workManagerFactory()\nstartKoin {\n    workManagerFactory()\n    modules(appModule)\n}\n```\n\n---\n\n## Module Organization\n\n### Annotations — Auto-Discovery\n\n```kotlin\nimport org.koin.core.annotation.*\n\n// Modules with @Configuration are auto-discovered by startKoin<T>\n@Module @ComponentScan(\"com.myapp.core\") @Configuration\nclass CoreModule\n\n@Module @ComponentScan(\"com.myapp.feature\") @Configuration\nclass FeatureModule\n\n@Module(includes = [DatabaseModule::class]) @Configuration\nclass DataModule\n\n// App entry point — assembles all @Configuration modules\n@KoinApplication\nclass MyApplication : Application()\n```\n\n### DSL — Explicit Composition\n\n```kotlin\nval coreModule = module { single<Database>(); single<NetworkClient>() }\nval featureModule = module { includes(coreModule); viewModel<HomeViewModel>() }\n\nval appModule = module {\n    includes(coreModule, featureModule, syncModule)\n}\n\nstartKoin { modules(appModule) }\n```\n\n### @Configuration Labels (Environment-Specific)\n\n```kotlin\n@Module @ComponentScan(\"com.myapp.core\") @Configuration(\"prod\")\nclass ProdCoreModule\n\n@Module @ComponentScan(\"com.myapp.core\") @Configuration(\"test\")\nclass TestCoreModule\n\n// Modules with the same label see each other's definitions\n```\n\n---\n\n## Dynamic Modules\n\n```kotlin\nimport org.koin.core.context.loadKoinModules\nimport org.koin.core.context.unloadKoinModules\n\n// Load at runtime (feature flags, A/B testing)\nloadKoinModules(premiumModule)\n\n// Unload when no longer needed\nunloadKoinModules(premiumModule)\n```\n\n---\n\n## Lazy Modules (Background Loading)\n\n```kotlin\nimport org.koin.core.context.startKoin\nimport org.koin.core.lazyModules\n\nstartKoin {\n    modules(criticalModule)\n    lazyModules(heavyModule, analyticsModule)  // loaded in background\n}\n```\n\n---\n\n## Properties\n\n```kotlin\nimport org.koin.core.annotation.Property\n\n@Singleton\nclass ApiClient(\n    @Property(\"api_url\") val url: String,\n    @Property(\"api_key\") val key: String\n)\n\n// Provide properties\nstartKoin {\n    properties(mapOf(\"api_url\" to \"https://api.example.com\", \"api_key\" to \"secret\"))\n}\n\n// DSL\nsingle { ApiClient(getProperty(\"api_url\"), getProperty(\"api_key\")) }\n```\n\n---\n\n## Optional / Nullable Dependencies\n\n```kotlin\n// Nullable param → resolved with getOrNull()\n@Singleton\nclass MyService(\n    val required: RequiredDep,\n    val optional: OptionalDep?     // null if not in graph\n)\n\n// DSL\nsingle<MyService>()  // compiler plugin handles getOrNull() automatically\n```\n\n---\n\n## Lazy Injection\n\n```kotlin\n// Lazy<T> → deferred creation\n@Singleton\nclass MyService(\n    val heavy: Lazy<HeavyDependency>   // created on first access\n)\n```\n\n---\n\n## List Injection\n\n```kotlin\n// List<T> → collects all definitions of type T\n@Singleton\nclass LoggerAggregator(\n    val loggers: List<LoggerDataSource>   // all LoggerDataSource definitions\n)\n```\n\n---\n\n## Testing\n\n```kotlin\nimport org.koin.test.KoinTest\nimport org.koin.test.inject\n\nclass MyTest : KoinTest {\n    private val service: MyService by inject()\n\n    @Test\n    fun testService() {\n        // service is resolved from Koin\n    }\n}\n```\n\nGraph verification is done at compile time by the Koin Compiler Plugin — don't write `checkModules()` / `verify()` tests (on their way to deprecation).\n\n---\n\n## Compile-Time Safety (with Koin Compiler Plugin)\n\nThe Koin Compiler Plugin validates your dependency graph at build time:\n\n- **A2 — Per-Module:** validates definitions against visible scope per module\n- **A3 — Full Graph:** validates ALL modules at `startKoin<T>()`\n- **A4 — Call-Site:** validates every `get<T>()`, `inject<T>()`, `koinViewModel<T>()` call\n\nWorks with both DSL and Annotations. Replaces `verify()` / `checkModules()`. If it compiles, it works.\n\n```kotlin\nkoinCompiler {\n    compileSafety = true       // default\n    skipDefaultValues = true   // default\n    unsafeDslChecks = true     // default\n}\n```\n\nSee https://insert-koin.io/docs/reference/koin-compiler/compile-safety\n\n---\n\n## Architecture\n\n- O(1) resolution via `HashMap.get()` — no reflection, no iteration\n- `ConcurrentHashMap` (JVM) / `ConcurrentMutableMap` (Native)\n- Fully compatible with R8/ProGuard\n- Pure common Kotlin runtime — all KMP targets\n\n## Project Structure\n\n```\nkoin/projects/\n├── core/      # koin-core (engine), koin-core-viewmodel, koin-core-coroutines, koin-annotations, koin-test*\n├── android/   # koin-android, koin-androidx-workmanager, koin-androidx-startup, koin-dagger-bridge…\n├── compose/   # koin-compose, koin-compose-viewmodel, koin-compose-navigation3…\n├── ktor/      # koin-ktor, koin-logger-slf4j\n├── plugins/   # koin-gradle-plugin\n└── bom/       # koin-bom\n```\n\nBuilding and contributing: https://github.com/InsertKoinIO/koin/blob/main/AGENTS.md\n\n## Related Repositories\n\n- **Compiler Plugin**: https://github.com/InsertKoinIO/koin-compiler-plugin\n- **Koin skills (AI agents)**: https://github.com/InsertKoinIO/koin-skills — Claude Code: `/plugin marketplace add InsertKoinIO/koin-skills` then `/plugin install koin@koin-skills`. Other agents (Cursor, Codex, Copilot, Windsurf, Gemini…): `npx skills add InsertKoinIO/koin-skills` (Node.js 22+)\n- **Kotzilla MCP**: https://mcp.kotzilla.io/mcp — Koin error diagnosis and runtime debugging (graph behavior, scopes, memory, performance)\n- **Playground Apps**: https://github.com/InsertKoinIO/playground-apps\n- **Website**: https://insert-koin.io\n","isInternal":false,"tokens":4185,"sizeBytes":17895},{"name":"CLAUDE.md","path":"projects/CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/InsertKoinIO/koin/HEAD/projects/CLAUDE.md","title":"projects - Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"# Koin\n\nKoin is a pragmatic, lightweight dependency injection framework for Kotlin Multiplatform (Android, JVM, iOS/native, JS, WasmJS). This directory (`projects/`) is the Gradle build root.\n\nShared agent notes (layout, build commands, contributing rules, how to write Koin code): [`../AGENTS.md`](../AGENTS.md). This file adds the Claude-specific detail; keep both consistent.\n\n## Doctrine\n\n- **Evidence-based claims.** \"fixed/verified/done\" must cite file:line, test output, or a repro run — never \"looks right + tests pass\".\n- **Falsify, don't confirm.** A test must try to BREAK the implementation. Prove RED before GREEN: a regression test must fail on the unfixed code.\n- **Resolution behavior is a contract.** Koin's resolution order/matching semantics are user-observable API — treat any change there as a compatibility event, not an implementation detail.\n\n> **SURPRISE RULE — mandatory.** If the project surprises you or contradicts these docs, STOP, tell the user, and record it here (or in the closest module doc). \"Surprising\" = not inferable from the code in one grep.\n\n## Module Map\n\n| Group | Modules | Notes |\n|---|---|---|\n| `core/` | **koin-core** (engine), koin-core-coroutines, koin-core-viewmodel, koin-core-annotations, koin-annotations, koin-test, koin-test-junit4/5, benchmark | KMP |\n| `android/` | koin-android, koin-android-compat, koin-androidx-navigation, koin-androidx-workmanager, koin-androidx-startup, koin-dagger-bridge, koin-android-test | Android |\n| `compose/` | koin-compose, koin-compose-viewmodel, koin-compose-navigation3, koin-compose-viewmodel-navigation, koin-androidx-compose(-navigation) | Compose MP + AndroidX |\n| `ktor/` | koin-ktor, koin-logger-slf4j | JVM server |\n| `plugins/` | koin-gradle-plugin | |\n| `bom/` | koin-bom | Aligns all artifact versions |\n\n### Key engine areas (`core/koin-core/src/commonMain/kotlin/org/koin/`)\n\n- `core/resolution/` — instance resolution. `CoreResolverV2.kt` is the 4.2+ engine: injected params → stacked params → registry (scope source → linked scopes → archetype)\n  - **Qualified lookups are registry-only.** A `get(named(...))` skips the stacked-parameter path entirely — parameters carry no qualifier (`ParametersHolder` matches by type only), so a stacked param can never satisfy a qualified request. Removing this guard re-opens #2370/#2408 (a `parametersOf` value shadowing a qualified dependency of the same type). Only unqualified resolution reads the param stack — that's the #2387 ViewModel path; don't break it.\n- `core/registry/` — definition & instance registries (indexing by type + qualifier)\n- `core/scope/` — `Scope`, scope archetypes, linked scopes\n- `core/module/` — module DSL (`single`, `factory`, `scoped`, `bind`, `includes`)\n- `mp/` — multiplatform abstractions (`KoinPlatform`, platform tools)\n\n## Build & Test (run from `projects/`)\n\n```bash\n./gradlew :core:koin-core:jvmTest              # one module, JVM only (fast) — KMP modules have no plain `test` task\n./gradlew :core:koin-core:allTests             # one module, all KMP targets\n./test.sh                                      # clean JVM-side run: `test` (Android/JVM) + `jvmTest` (KMP modules); non-JVM targets need allTests / test-macos.sh\n./test-macos.sh                                # Apple/native targets\n./gradlew :core:koin-core:compileKotlinWasmJs  # quick non-JVM compile check\n./install.sh                                   # publish all to Maven Local\n./benchmark.sh                                 # core benchmarks (core/benchmark)\n./compare_apis.sh                              # public API diff (binary compatibility)\n./clean.sh                                     # clean all builds\n```\n\n## PR Guards — check on EVERY pull request\n\nBefore opening (or approving) any PR, both guards must pass:\n\n### 1. Don't break the current API\n- `./gradlew apiCheck` must pass (kotlinx binary-compatibility-validator, applied at the root — `.api` dumps per module, e.g. `core/koin-core/api/koin-core.api`).\n- If the PR intentionally **adds** public API: regenerate dumps with `./gradlew apiDump`, commit the `.api` diff, and review it with `./compare_apis.sh` (diffs `.api` files vs `main`).\n- **Removals or signature changes of public API are not acceptable in patch releases** — additions only. Breaking changes need a minor/major version and an explicit deprecation cycle (`@Deprecated` with `ReplaceWith` first).\n- Behavioral compatibility counts as API: a PR that changes resolution order/matching semantics must state the old vs new behavior in its description, even if no signature changed.\n\n### 2. New tests for every new case or fixed use-case\n- **Bug fix** → a regression test that reproduces the issue: fails before the fix, passes after. Place it in `commonTest` whenever the code under test is common.\n- **New case/feature** → tests covering the new behavior, including the edge that motivated it (qualifiers, scopes, params, platform-specific paths).\n- A PR touching `commonMain` without an accompanying test change needs an explicit justification in the description.\n- KMP: tests must run beyond JVM when the code is common (`allTests`, or at minimum compile-check one native target + `wasmJs`).\n\n## Conventions\n\n- **KMP discipline:** `commonMain` changes must be verified beyond JVM — compile at least one native target and `wasmJs` before considering a change done. Several bug classes (qualifiedName, klib, JS) only surface off-JVM.\n- **Every bug fix ships a regression test**, in `commonTest` whenever the code is common.\n- **Commit style:** `Fix #NNNN - description` referencing the GitHub issue (e.g. `Fix #2387 - CoreResolverV2 lost stacked-params lookup on linked scopes`).\n- **Public API changes:** run `./compare_apis.sh`; avoid breaking binary compatibility in patch releases.\n- **Resolution-semantics changes** (anything in `core/resolution/`): describe the behavioral delta (old vs new resolution order/matching) in the PR description — resolver behavior changes have historically caused regressions.\n- **User-visible changes need a docs touch** — documentation lives in `../docs/` (published to insert-koin.io).\n\n### KMP test traps (hard rules)\n\n- **Never weaken `test.sh` to scoped tasks.** In KMP projects `./gradlew test` resolves only to Android/JVM unit tests and **silently skips** native, JS, and WASM tests. Full verification = `allTests` (or per-target tasks explicitly). Do not add `-x` exclusions to make a run green — fix the failing test at the root cause.\n- **No backtick test names in `commonTest`** — backtick names (`` fun `my test`() ``) break `compileTestKotlinJs`/`compileTestKotlinWasmJs` (invalid JS identifiers). Use snake_case in `commonTest`; backticks are fine in `jvmTest` only.\n- **`runTest {}` in `commonTest` must use block-body syntax** — on wasmJs, expression-body forms (`fun foo() = runTest { }`) fail with return-type mismatch. Use `fun testX() { runTest { ... } }`.\n\n#### Known pre-existing off-JVM failures (not yours — don't chase them)\n\n`allTests` is currently red on two counts unrelated to any recent resolver work (verified by stash-comparison on the `4.2.2` branch, 2026-06-08). If you touch `commonMain` and run `allTests`, expect these and confirm your diff doesn't change them:\n\n- **`createdAtStart` on wasmJs** — `CreateOnStart`, `OverrideAndCreateatStartTest` fail with `AssertionError: Expected value to be true`. Core `single(createdAtStart = true)` eager-init doesn't fire on wasmJs. This is *core*, **not** the compiler-plugin `createdAtStart` drop (#2425/#2415) — same symptom family, different layer.\n- **`DynamicModulesTest` on native** — its backtick names contain `\" - \"` (e.g. `` `should unload one module definition - factory` ``), crashing the Kotlin/Native test reporter (`ServiceMessagesParser`) and aborting `macosArm64Test`. This is the backtick-name trap above, already shipped. Fix = rename to snake_case.\n\n## Versioning\n\n- Version is set in `gradle.properties` (`koinVersion`).\n- Branch lines: `4.2.x` = stable maintenance (regressions/bug fixes only), next minor in development on `main`.\n- `koin-bom` must stay aligned with all published modules.\n\n### Release notes: Added vs Fixed\n\nThe test: **\"Would this affect a user on the previous supported version range?\"**\n- Yes → `Fixed` (genuine regression or pre-existing bug)\n- No, only relevant to a new Kotlin/AGP/library range → `Added` (new support story), as ONE umbrella entry with sub-bullets — users care that \"Kotlin X works\", not how many internal changes it took.\n- Internal refactor with identical user-visible behavior → internal changes, not `Changed`.\n","isInternal":false,"tokens":2111,"sizeBytes":8654}],"systemPromptSnippet":"<yakaai_skills repo=\"InsertKoinIO/koin\">\n<!-- File: projects/llms.txt (Tokens: ~4185 | Category: root-instruction) -->\n# Koin — Kotlin Dependency Injection Framework\n\n> Pragmatic DI framework for Kotlin and Kotlin Multiplatform. Two styles, one container: Koin DSL or Koin Annotations (JSR-330 compatible). Compile safe in both styles with the Koin Compiler Plugin — no KSP, no kapt, no reflection at its core. Runs on all Kotlin Multiplatform targets.\n\nConcise index: [llms.txt](https://github.com/InsertKoinIO/koin/blob/main/llms.txt). This file is the API cheat sheet.\n\n## Key Facts\n\n- **Version**: Koin 4.2.2, Koin Compiler Plugin 1.2.1, Kotlin 2.3.20 or later\n- **Language**: Kotlin (100%)\n- **Platforms**: Android, iOS, Desktop, Web (JS/Wasm), Backend (Ktor) via Kotlin Multiplatform\n- **License**: Apache 2.0\n- **Repository**: https://github.com/InsertKoinIO/koin\n- **Website**: https://insert-koin.io\n- **Documentation**: https://insert-koin.io/docs/intro/what-is-koin\n- **Koin skills (AI agents)**: https://github.com/InsertKoinIO/koin-skills\n- **Kotzilla MCP (runtime debugging)**: https://mcp.kotzilla.io/mcp\n\n## IDE Plugin\n\nOfficial plugin for Android Studio & IntelliJ IDEA — code navigation between definitions and injection points, live safety checks, dependency graph visualization.\n\n**Download:** https://plugins.jetbrains.com/plugin/26131-koin-dependency-injection-official-\n\n## Gradle Dependencies\n\n```kotlin\n// BOM (recommended)\nplugins {\n    id(\"io.insert-koin.compiler.plugin\") version \"1.2.1\"     // Koin Compiler Plugin (recommended — compile-time safety)\n}\n\nimplementation(platform(\"io.insert-koin:koin-bom:4.2.2\"))   // BOM resolves every Koin artifact version\nimplementation(\"io.insert-koin:koin-core\")                   // Core (all platforms)\nimplementation(\"io.insert-koin:koin-annotations\")            // Annotations style (with the Compiler Plugin)\nimplementation(\"io.insert-koin:koin-android\")                // Android (Context, ViewModel, Scope)\nimplementation(\"io.insert-koin:koin-compose\")                // Compose integration\nimplementation(\"io.insert-koin:koin-compose-viewmodel\")      // Compose ViewModel (koinViewModel)\nimplementation(\"io.insert-koin:koin-core-coroutines\")        // Coroutines (lazy modules)\nimplementation(\"io.insert-koin:koin-ktor\")                   // Ktor server\nimplementation(\"io.insert-koin:koin-test\")                   // Testing\nimplementation(\"io.insert-koin:koin-test-junit5\")            // JUnit 5 testing\n\n// Never Koin KSP: no com.google.devtools.ksp for Koin, no koin-ksp-compiler, no koin-annotations:2.x pin\n```\n\n---\n\n## Starting Koin\n\n### Android\n\n```kotlin\nimport org.koin.android.ext.koin.androidContext\nimport org.koin.android.ext.koin.androidLogger\nimport org.koin.core.context.startKoin\nimport org.koin.androidx.workmanager.koin.workManagerFactory\n\nclass MyApplication : Application() {\n    override fun onCreate() {\n        super.onCreate()\n        startKoin {\n            androidLogger()\n            androidContext(this@MyApplication)\n            workManagerFactory()              // if using WorkManager\n            modules(appModule, featureModule)\n        }\n    }\n}\n```\n\n### Annotations (with compile-time graph validation)\n\n```kotlin\nimport org.koin.core.annotation.KoinApplication\nimport org.koin.plugin.module.dsl.startKoin   // reified startKoin<App> from the Compiler Plugin\n\n@KoinApplication\nclass MyApplication : Application() {\n    override fun onCreate() {\n        super.onCreate()\n        startKoin<MyApplication> {    // triggers A3 full graph validation\n            androidLogger()\n            androidContext(this@MyApplication)\n            workManagerFactory()\n        }\n    }\n}\n```\n\n### Ktor\n\n```kotlin\nimport org.koin.ktor.plugin.Koin\n\nfun Application.module() {\n    install(Koin) {\n        modules(appModule)\n    }\n}\n```\n\n### KMP / Non-Android\n\n```kotlin\nimport org.koin.core.context.startKoin\n\nstartKoin {\n    modules(commonModule, platformModule)\n}\n```\n\n---\n\n## Defining Dependencies\n\n### Compiler Plugin DSL (recommended)\n\nPick one style per project — Koin DSL or Koin Annotations — and don't mix them unless needed (then one style per module).\n\n```kotlin\nimport org.koin.dsl.module\nimport org.koin.dsl.bind\nimport org.koin.plugin.module.dsl.*\n\nval appModule = module {\n    // Singleton — one instance for app lifetime\n    single<Database>()\n    single<UserRepository>()\n\n    // Factory — new instance each time\n    factory<GetUserUseCase>()\n\n    // ViewModel — Android ViewModel lifecycle\n    viewModel<UserViewModel>()\n\n    // Worker — WorkManager\n    worker<SyncWorker>()\n\n    // Interface binding\n    single<UserRepositoryImpl>() bind UserRepository::class\n\n    // Multiple bindings\n    single<MyServiceImpl>() binds arrayOf(ServiceA::class, ServiceB::class)\n\n    // Module composition\n    includes(networkModule, databaseModule)\n}\n```\n\n### Compiler Plugin DSL with Function Builders (create)\n\n```kotlin\nimport org.koin.dsl.module\nimport org.koin.plugin.module.dsl.create\n\n// Builder functions — for wrapping external libraries (Room, Retrofit, etc.)\nfun database(context: Context): AppDatabase =\n    Room.databaseBuilder(context, AppDatabase::class.java, \"my-db\").build()\n\nfun topicDao(db: AppDatabase): TopicDao = db.topicDao()\n\nfun json(): Json = Json { ignoreUnknownKeys = true }\n\nval databaseModule = module {\n    single { create(::database) }    // parameters auto-resolved from DI\n    single { create(::topicDao) }\n    single { create(::json) }\n}\n```\n\n### Constructor DSL and Classic DSL\n\nConstructor DSL (`singleOf(::T)`) suits projects without the Compiler Plugin. Classic DSL (`single { T(get()) }`) is for custom expressions a constructor or function builder can't express. Both are also checked by the Compiler Plugin when it is applied.\n\n```kotlin\nimport org.koin.dsl.module\nimport org.koin.core.module.dsl.*\n\nval appModule = module {\n    // Constructor DSL — constructor reference\n    singleOf(::Database)\n    singleOf(::UserRepository)\n    factoryOf(::GetUserUseCase)\n    viewModelOf(::UserViewModel)\n\n    // Classic DSL — lambda (manual wiring)\n    single { Database() }\n    single { UserRepository(get()) }\n    factory { GetUserUseCase(get()) }\n    viewModel { UserViewModel(get()) }\n}\n```\n\n### Annotations\n\n```kotlin\nimport org.koin.core.annotation.*\n\n@Singleton\nclass Database\n\n@Singleton\nclass UserRepository(private val db: Database)\n\n@Factory\nclass GetUserUseCase(private val repo: UserRepository)\n\n@KoinViewModel\nclass UserViewModel(private val useCase: GetUserUseCase) : ViewModel()\n\n// Module with component scan\n@Module\n@ComponentScan(\"com.myapp\")\nclass AppModule\n\n// Module with provider functions (for external libraries)\n@Module\ninternal object DatabaseModule {\n    @Singleton\n    fun providesDatabase(context: Context): AppDatabase =\n        Room.databaseBuilder(context, AppDatabase::class.java, \"my-db\").build()\n}\n```\n\n### Annotated Top-Level Functions\n\n```kotlin\nimport org.koin.core.annotation.Singleton\nimport org.koin.core.annotation.Factory\n\n@Singleton\nfun provideDatabase(context: Context): AppDatabase =\n    Room.databaseBuilder(context, AppDatabase::class.java, \"my-db\").build()\n\n@Factory\nfun provideCache(db: AppDatabase): CacheService = CacheServiceImpl(db)\n```\n\nDiscovered by `@ComponentScan` like annotated classes.\n\n---\n\n## Injecting Dependencies\n\n### In Android Activity / Fragment\n\n```kotlin\nimport org.koin.android.ext.android.inject\nimport org.koin.android.ext.android.get\nimport org.koin.androidx.viewmodel.ext.android.viewModel\n\nclass MainActivity : ComponentActivity() {\n    // Lazy injection\n    private val viewModel: UserViewModel by viewModel()\n    private val service: MyService by inject()\n\n    // Eager injection\n    val eagerService: MyService = get()\n}\n```\n\n### In Activity with Scope\n\n```kotlin\nimport org.koin.android.scope.AndroidScopeComponent\nimport org.koin.androidx.scope.activityScope\nimport org.koin.android.ext.android.inject\nimport org.koin.androidx.viewmodel.ext.android.viewModel\n\nclass MainActivity : ComponentActivity(), AndroidScopeComponent {\n    override val scope: Scope by activityScope()\n\n    private val viewModel: MainViewModel by viewModel()\n    private val tracker: ActivityTracker by inject()   // scoped to activity lifecycle\n}\n```\n\n### In Compose\n\n```kotlin\nimport org.koin.compose.viewmodel.koinViewModel\nimport org.koin.compose.koinInject\n\n@Composable\nfun HomeScreen(viewModel: HomeViewModel = koinViewModel()) {\n    val analytics: AnalyticsHelper = koinInject()\n    // ...\n}\n```\n\n### In Compose with Parameters (Navigation Arguments)\n\n```kotlin\nimport org.koin.compose.viewmodel.koinViewModel\nimport org.koin.core.parameter.parametersOf\n\n@Composable\nfun DetailScreen(\n    newsId: String,\n    viewModel: DetailViewModel = koinViewModel(key = newsId) { parametersOf(newsId) }\n) {\n    // Each unique newsId gets its own ViewModel instance\n}\n```\n\n### In Ktor\n\n```kotlin\nimport org.koin.ktor.ext.inject\n\nfun Route.userRoutes() {\n    val service: UserService by inject()\n\n    get(\"/users/{id}\") {\n        val user = service.getUser(call.parameters[\"id\"]!!)\n        call.respond(user)\n    }\n}\n```\n\n### Constructor Injection (Recommended for Business Logic)\n\n```kotlin\n// No Koin imports needed — dependencies resolved by the framework\nclass UserRepository(\n    private val database: Database,\n    private val networkClient: NetworkClient\n)\n\nclass GetUserUseCase(\n    private val repository: UserRepository\n)\n```\n\n---\n\n## Qualifiers\n\n### @Named (String Qualifier)\n\n```kotlin\nimport org.koin.core.annotation.Named\nimport org.koin.core.qualifier.named\n\n// Definition\n@Singleton @Named(\"local\")\nclass LocalDatabase : Database\n\n@Singleton @Named(\"remote\")\nclass RemoteDatabase : Database\n\n// Injection\n@Singleton\nclass UserRepository(\n    @Named(\"local\") private val localDb: Database,\n    @Named(\"remote\") private val remoteDb: Database\n)\n\n// Compiler Plugin DSL — the qualifier stays on the class (@Named above), the definition stays plain\nsingle<LocalDatabase>()\nsingle<RemoteDatabase>()\n\n// Classic DSL only\nsingle<Database>(named(\"local\")) { LocalDatabase() }\n```\n\n### Custom @Qualifier (with Parameters)\n\n```kotlin\nimport org.koin.core.annotation.Qualifier\n\n@Qualifier\n@Retention(AnnotationRetention.RUNTIME)\nannotation class Dispatcher(val type: AppDispatchers)\n\nenum class AppDispatchers { Default, IO }\n\n// Provider\n@Singleton @Dispatcher(AppDispatchers.IO)\nfun ioDispatcher(): CoroutineDispatcher = Dispatchers.IO\n\n// Consumer\n@Singleton\nclass MyRepository(\n    @Dispatcher(AppDispatchers.IO) private val dispatcher: CoroutineDispatcher\n)\n```\n\n---\n\n## Scopes\n\n### DSL Scopes\n\n```kotlin\nimport org.koin.dsl.module\nimport org.koin.plugin.module.dsl.scoped\nimport org.koin.androidx.scope.dsl.activityScope\nimport org.koin.androidx.scope.dsl.fragmentScope\n\nval appModule = module {\n    // Activity-scoped\n    activityScope {\n        scoped<ActivityTracker>()\n    }\n\n    // Fragment-scoped\n    fragmentScope {\n        scoped<FragmentPresenter>()\n    }\n\n    // Custom scope\n    scope<UserSession> {\n        scoped<UserRepository>()\n        scoped<UserPreferences>()\n    }\n}\n```\n\n### Annotation Scopes\n\n```kotlin\nimport org.koin.core.annotation.Scoped\nimport org.koin.core.annotation.Scope\nimport org.koin.android.annotation.ActivityScope\nimport org.koin.android.annotation.FragmentScope\n\n@ActivityScope\nclass ActivityTracker(val activity: ComponentActivity)\n\n@FragmentScope\nclass FragmentPresenter(val fragment: Fragment)\n\n@Scope(UserSession::class)\n@Scoped\nclass UserRepository(val db: Database)\n```\n\n---\n\n## ViewModel\n\n### With SavedStateHandle\n\n```kotlin\n// SavedStateHandle is auto-injected (whitelisted type)\n@KoinViewModel\nclass HomeViewModel(\n    private val savedStateHandle: SavedStateHandle,\n    private val repository: UserRepository,\n) : ViewModel()\n\n// DSL\nviewModel<HomeViewModel>()\n```\n\n### With @InjectedParam (Navigation Arguments)\n\n```kotlin\nimport org.koin.core.annotation.InjectedParam\n\n@KoinViewModel\nclass DetailViewModel(\n    private val repository: NewsRepository,\n    @InjectedParam val newsId: String,\n) : ViewModel()\n\n// Compose usage:\nkoinViewModel(key = newsId) { parametersOf(newsId) }\n```\n\n---\n\n## WorkManager\n\n```kotlin\nimport org.koin.android.annotation.KoinWorker\n\n// Annotations\n@KoinWorker\nclass SyncWorker(\n    context: Context,\n    params: WorkerParameters,\n    private val repository: NewsRepository,\n    @Dispatcher(AppDispatchers.IO) private val dispatcher: CoroutineDispatcher,\n) : CoroutineWorker(context, params)\n\n// DSL\nval syncModule = module {\n    single<WorkManagerSyncManager>() bind SyncManager::class\n    worker<SyncWorker>()\n}\n\n// Bootstrap — add workManagerFactory()\nstartKoin {\n    workManagerFactory()\n    modules(appModule)\n}\n```\n\n---\n\n## Module Organization\n\n### Annotations — Auto-Discovery\n\n```kotlin\nimport org.koin.core.annotation.*\n\n// Modules with @Configuration are auto-discovered by startKoin<T>\n@Module @ComponentScan(\"com.myapp.core\") @Configuration\nclass CoreModule\n\n@Module @ComponentScan(\"com.myapp.feature\") @Configuration\nclass FeatureModule\n\n@Module(includes = [DatabaseModule::class]) @Configuration\nclass DataModule\n\n// App entry point — assembles all @Configuration modules\n@KoinApplication\nclass MyApplication : Application()\n```\n\n### DSL — Explicit Composition\n\n```kotlin\nval coreModule = module { single<Database>(); single<NetworkClient>() }\nval featureModule = module { includes(coreModule); viewModel<HomeViewModel>() }\n\nval appModule = module {\n    includes(coreModule, featureModule, syncModule)\n}\n\nstartKoin { modules(appModule) }\n```\n\n### @Configuration Labels (Environment-Specific)\n\n```kotlin\n@Module @ComponentScan(\"com.myapp.core\") @Configuration(\"prod\")\nclass ProdCoreModule\n\n@Module @ComponentScan(\"com.myapp.core\") @Configuration(\"test\")\nclass TestCoreModule\n\n// Modules with the same label see each other's definitions\n```\n\n---\n\n## Dynamic Modules\n\n```kotlin\nimport org.koin.core.context.loadKoinModules\nimport org.koin.core.context.unloadKoinModules\n\n// Load at runtime (feature flags, A/B testing)\nloadKoinModules(premiumModule)\n\n// Unload when no longer needed\nunloadKoinModules(premiumModule)\n```\n\n---\n\n## Lazy Modules (Background Loading)\n\n```kotlin\nimport org.koin.core.context.startKoin\nimport org.koin.core.lazyModules\n\nstartKoin {\n    modules(criticalModule)\n    lazyModules(heavyModule, analyticsModule)  // loaded in background\n}\n```\n\n---\n\n## Properties\n\n```kotlin\nimport org.koin.core.annotation.Property\n\n@Singleton\nclass ApiClient(\n    @Property(\"api_url\") val url: String,\n    @Property(\"api_key\") val key: String\n)\n\n// Provide properties\nstartKoin {\n    properties(mapOf(\"api_url\" to \"https://api.example.com\", \"api_key\" to \"secret\"))\n}\n\n// DSL\nsingle { ApiClient(getProperty(\"api_url\"), getProperty(\"api_key\")) }\n```\n\n---\n\n## Optional / Nullable Dependencies\n\n```kotlin\n// Nullable param → resolved with getOrNull()\n@Singleton\nclass MyService(\n    val required: RequiredDep,\n    val optional: OptionalDep?     // null if not in graph\n)\n\n// DSL\nsingle<MyService>()  // compiler plugin handles getOrNull() automatically\n```\n\n---\n\n## Lazy Injection\n\n```kotlin\n// Lazy<T> → deferred creation\n@Singleton\nclass MyService(\n    val heavy: Lazy<HeavyDependency>   // created on first access\n)\n```\n\n---\n\n## List Injection\n\n```kotlin\n// List<T> → collects all definitions of type T\n@Singleton\nclass LoggerAggregator(\n    val loggers: List<LoggerDataSource>   // all LoggerDataSource definitions\n)\n```\n\n---\n\n## Testing\n\n```kotlin\nimport org.koin.test.KoinTest\nimport org.koin.test.inject\n\nclass MyTest : KoinTest {\n    private val service: MyService by inject()\n\n    @Test\n    fun testService() {\n        // service is resolved from Koin\n    }\n}\n```\n\nGraph verification is done at compile time by the Koin Compiler Plugin — don't write `checkModules()` / `verify()` tests (on their way to deprecation).\n\n---\n\n## Compile-Time Safety (with Koin Compiler Plugin)\n\nThe Koin Compiler Plugin validates your dependency graph at build time:\n\n- **A2 — Per-Module:** validates definitions against visible scope per module\n- **A3 — Full Graph:** validates ALL modules at `startKoin<T>()`\n- **A4 — Call-Site:** validates every `get<T>()`, `inject<T>()`, `koinViewModel<T>()` call\n\nWorks with both DSL and Annotations. Replaces `verify()` / `checkModules()`. If it compiles, it works.\n\n```kotlin\nkoinCompiler {\n    compileSafety = true       // default\n    skipDefaultValues = true   // default\n    unsafeDslChecks = true     // default\n}\n```\n\nSee https://insert-koin.io/docs/reference/koin-compiler/compile-safety\n\n---\n\n## Architecture\n\n- O(1) resolution via `HashMap.get()` — no reflection, no iteration\n- `ConcurrentHashMap` (JVM) / `ConcurrentMutableMap` (Native)\n- Fully compatible with R8/ProGuard\n- Pure common Kotlin runtime — all KMP targets\n\n## Project Structure\n\n```\nkoin/projects/\n├── core/      # koin-core (engine), koin-core-viewmodel, koin-core-coroutines, koin-annotations, koin-test*\n├── android/   # koin-android, koin-androidx-workmanager, koin-androidx-startup, koin-dagger-bridge…\n├── compose/   # koin-compose, koin-compose-viewmodel, koin-compose-navigation3…\n├── ktor/      # koin-ktor, koin-logger-slf4j\n├── plugins/   # koin-gradle-plugin\n└── bom/       # koin-bom\n```\n\nBuilding and contributing: https://github.com/InsertKoinIO/koin/blob/main/AGENTS.md\n\n## Related Repositories\n\n- **Compiler Plugin**: https://github.com/InsertKoinIO/koin-compiler-plugin\n- **Koin skills (AI agents)**: https://github.com/InsertKoinIO/koin-skills — Claude Code: `/plugin marketplace add InsertKoinIO/koin-skills` then `/plugin install koin@koin-skills`. Other agents (Cursor, Codex, Copilot, Windsurf, Gemini…): `npx skills add InsertKoinIO/koin-skills` (Node.js 22+)\n- **Kotzilla MCP**: https://mcp.kotzilla.io/mcp — Koin error diagnosis and runtime debugging (graph behavior, scopes, memory, performance)\n- **Playground Apps**: https://github.com/InsertKoinIO/playground-apps\n- **Website**: https://insert-koin.io\n\n<!-- File: projects/CLAUDE.md (Tokens: ~2111 | Category: claude-rule) -->\n# Koin\n\nKoin is a pragmatic, lightweight dependency injection framework for Kotlin Multiplatform (Android, JVM, iOS/native, JS, WasmJS). This directory (`projects/`) is the Gradle build root.\n\nShared agent notes (layout, build commands, contributing rules, how to write Koin code): [`../AGENTS.md`](../AGENTS.md). This file adds the Claude-specific detail; keep both consistent.\n\n## Doctrine\n\n- **Evidence-based claims.** \"fixed/verified/done\" must cite file:line, test output, or a repro run — never \"looks right + tests pass\".\n- **Falsify, don't confirm.** A test must try to BREAK the implementation. Prove RED before GREEN: a regression test must fail on the unfixed code.\n- **Resolution behavior is a contract.** Koin's resolution order/matching semantics are user-observable API — treat any change there as a compatibility event, not an implementation detail.\n\n> **SURPRISE RULE — mandatory.** If the project surprises you or contradicts these docs, STOP, tell the user, and record it here (or in the closest module doc). \"Surprising\" = not inferable from the code in one grep.\n\n## Module Map\n\n| Group | Modules | Notes |\n|---|---|---|\n| `core/` | **koin-core** (engine), koin-core-coroutines, koin-core-viewmodel, koin-core-annotations, koin-annotations, koin-test, koin-test-junit4/5, benchmark | KMP |\n| `android/` | koin-android, koin-android-compat, koin-androidx-navigation, koin-androidx-workmanager, koin-androidx-startup, koin-dagger-bridge, koin-android-test | Android |\n| `compose/` | koin-compose, koin-compose-viewmodel, koin-compose-navigation3, koin-compose-viewmodel-navigation, koin-androidx-compose(-navigation) | Compose MP + AndroidX |\n| `ktor/` | koin-ktor, koin-logger-slf4j | JVM server |\n| `plugins/` | koin-gradle-plugin | |\n| `bom/` | koin-bom | Aligns all artifact versions |\n\n### Key engine areas (`core/koin-core/src/commonMain/kotlin/org/koin/`)\n\n- `core/resolution/` — instance resolution. `CoreResolverV2.kt` is the 4.2+ engine: injected params → stacked params → registry (scope source → linked scopes → archetype)\n  - **Qualified lookups are registry-only.** A `get(named(...))` skips the stacked-parameter path entirely — parameters carry no qualifier (`ParametersHolder` matches by type only), so a stacked param can never satisfy a qualified request. Removing this guard re-opens #2370/#2408 (a `parametersOf` value shadowing a qualified dependency of the same type). Only unqualified resolution reads the param stack — that's the #2387 ViewModel path; don't break it.\n- `core/registry/` — definition & instance registries (indexing by type + qualifier)\n- `core/scope/` — `Scope`, scope archetypes, linked scopes\n- `core/module/` — module DSL (`single`, `factory`, `scoped`, `bind`, `includes`)\n- `mp/` — multiplatform abstractions (`KoinPlatform`, platform tools)\n\n## Build & Test (run from `projects/`)\n\n```bash\n./gradlew :core:koin-core:jvmTest              # one module, JVM only (fast) — KMP modules have no plain `test` task\n./gradlew :core:koin-core:allTests             # one module, all KMP targets\n./test.sh                                      # clean JVM-side run: `test` (Android/JVM) + `jvmTest` (KMP modules); non-JVM targets need allTests / test-macos.sh\n./test-macos.sh                                # Apple/native targets\n./gradlew :core:koin-core:compileKotlinWasmJs  # quick non-JVM compile check\n./install.sh                                   # publish all to Maven Local\n./benchmark.sh                                 # core benchmarks (core/benchmark)\n./compare_apis.sh                              # public API diff (binary compatibility)\n./clean.sh                                     # clean all builds\n```\n\n## PR Guards — check on EVERY pull request\n\nBefore opening (or approving) any PR, both guards must pass:\n\n### 1. Don't break the current API\n- `./gradlew apiCheck` must pass (kotlinx binary-compatibility-validator, applied at the root — `.api` dumps per module, e.g. `core/koin-core/api/koin-core.api`).\n- If the PR intentionally **adds** public API: regenerate dumps with `./gradlew apiDump`, commit the `.api` diff, and review it with `./compare_apis.sh` (diffs `.api` files vs `main`).\n- **Removals or signature changes of public API are not acceptable in patch releases** — additions only. Breaking changes need a minor/major version and an explicit deprecation cycle (`@Deprecated` with `ReplaceWith` first).\n- Behavioral compatibility counts as API: a PR that changes resolution order/matching semantics must state the old vs new behavior in its description, even if no signature changed.\n\n### 2. New tests for every new case or fixed use-case\n- **Bug fix** → a regression test that reproduces the issue: fails before the fix, passes after. Place it in `commonTest` whenever the code under test is common.\n- **New case/feature** → tests covering the new behavior, including the edge that motivated it (qualifiers, scopes, params, platform-specific paths).\n- A PR touching `commonMain` without an accompanying test change needs an explicit justification in the description.\n- KMP: tests must run beyond JVM when the code is common (`allTests`, or at minimum compile-check one native target + `wasmJs`).\n\n## Conventions\n\n- **KMP discipline:** `commonMain` changes must be verified beyond JVM — compile at least one native target and `wasmJs` before considering a change done. Several bug classes (qualifiedName, klib, JS) only surface off-JVM.\n- **Every bug fix ships a regression test**, in `commonTest` whenever the code is common.\n- **Commit style:** `Fix #NNNN - description` referencing the GitHub issue (e.g. `Fix #2387 - CoreResolverV2 lost stacked-params lookup on linked scopes`).\n- **Public API changes:** run `./compare_apis.sh`; avoid breaking binary compatibility in patch releases.\n- **Resolution-semantics changes** (anything in `core/resolution/`): describe the behavioral delta (old vs new resolution order/matching) in the PR description — resolver behavior changes have historically caused regressions.\n- **User-visible changes need a docs touch** — documentation lives in `../docs/` (published to insert-koin.io).\n\n### KMP test traps (hard rules)\n\n- **Never weaken `test.sh` to scoped tasks.** In KMP projects `./gradlew test` resolves only to Android/JVM unit tests and **silently skips** native, JS, and WASM tests. Full verification = `allTests` (or per-target tasks explicitly). Do not add `-x` exclusions to make a run green — fix the failing test at the root cause.\n- **No backtick test names in `commonTest`** — backtick names (`` fun `my test`() ``) break `compileTestKotlinJs`/`compileTestKotlinWasmJs` (invalid JS identifiers). Use snake_case in `commonTest`; backticks are fine in `jvmTest` only.\n- **`runTest {}` in `commonTest` must use block-body syntax** — on wasmJs, expression-body forms (`fun foo() = runTest { }`) fail with return-type mismatch. Use `fun testX() { runTest { ... } }`.\n\n#### Known pre-existing off-JVM failures (not yours — don't chase them)\n\n`allTests` is currently red on two counts unrelated to any recent resolver work (verified by stash-comparison on the `4.2.2` branch, 2026-06-08). If you touch `commonMain` and run `allTests`, expect these and confirm your diff doesn't change them:\n\n- **`createdAtStart` on wasmJs** — `CreateOnStart`, `OverrideAndCreateatStartTest` fail with `AssertionError: Expected value to be true`. Core `single(createdAtStart = true)` eager-init doesn't fire on wasmJs. This is *core*, **not** the compiler-plugin `createdAtStart` drop (#2425/#2415) — same symptom family, different layer.\n- **`DynamicModulesTest` on native** — its backtick names contain `\" - \"` (e.g. `` `should unload one module definition - factory` ``), crashing the Kotlin/Native test reporter (`ServiceMessagesParser`) and aborting `macosArm64Test`. This is the backtick-name trap above, already shipped. Fix = rename to snake_case.\n\n## Versioning\n\n- Version is set in `gradle.properties` (`koinVersion`).\n- Branch lines: `4.2.x` = stable maintenance (regressions/bug fixes only), next minor in development on `main`.\n- `koin-bom` must stay aligned with all published modules.\n\n### Release notes: Added vs Fixed\n\nThe test: **\"Would this affect a user on the previous supported version range?\"**\n- Yes → `Fixed` (genuine regression or pre-existing bug)\n- No, only relevant to a new Kotlin/AGP/library range → `Added` (new support story), as ONE umbrella entry with sub-bullets — users care that \"Kotlin X works\", not how many internal changes it took.\n- Internal refactor with identical user-visible behavior → internal changes, not `Changed`.\n</yakaai_skills>"}