@mmerterden/multi-agent-pipeline 20.1.0 → 20.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/docs/facts.json +5 -5
  3. package/manifest.json +52 -31
  4. package/package.json +1 -1
  5. package/pipeline/skills/.skill-manifest.json +36 -20
  6. package/pipeline/skills/.skills-index.json +75 -9
  7. package/pipeline/skills/shared/README.md +13 -7
  8. package/pipeline/skills/shared/external/android-architecture/SKILL.md +71 -0
  9. package/pipeline/skills/shared/external/android-architecture/references/patterns.md +142 -0
  10. package/pipeline/skills/shared/external/android-build-quality-gates/SKILL.md +314 -0
  11. package/pipeline/skills/shared/external/android-build-quality-gates/references/patterns.md +432 -0
  12. package/pipeline/skills/shared/external/android-datastore/SKILL.md +236 -0
  13. package/pipeline/skills/shared/external/android-datastore/references/patterns.md +297 -0
  14. package/pipeline/skills/shared/external/android-design-tokens-codegen/SKILL.md +249 -0
  15. package/pipeline/skills/shared/external/android-design-tokens-codegen/references/patterns.md +270 -0
  16. package/pipeline/skills/shared/external/android-jetpack-compose-expert/SKILL.md +62 -0
  17. package/pipeline/skills/shared/external/android-mvi-viewmodel/SKILL.md +255 -0
  18. package/pipeline/skills/shared/external/android-mvi-viewmodel/references/patterns.md +257 -0
  19. package/pipeline/skills/shared/external/android-performance/SKILL.md +86 -602
  20. package/pipeline/skills/shared/external/android-performance/references/patterns.md +659 -0
  21. package/pipeline/skills/shared/external/android-security/SKILL.md +117 -430
  22. package/pipeline/skills/shared/external/android-security/references/patterns.md +690 -0
  23. package/pipeline/skills/shared/external/{android_ui_verification → android-ui-verification}/SKILL.md +1 -1
  24. package/pipeline/skills/shared/external/api-security-best-practices/SKILL.md +35 -733
  25. package/pipeline/skills/shared/external/api-security-best-practices/references/auth.md +299 -0
  26. package/pipeline/skills/shared/external/api-security-best-practices/references/input-validation.md +255 -0
  27. package/pipeline/skills/shared/external/api-security-best-practices/references/rate-limiting.md +167 -0
  28. package/pipeline/skills/shared/external/app-intents/SKILL.md +39 -174
  29. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +178 -0
  30. package/pipeline/skills/shared/external/compose-components/SKILL.md +48 -0
  31. package/pipeline/skills/shared/external/compose-components/references/patterns.md +200 -0
  32. package/pipeline/skills/shared/external/compose-navigation/SKILL.md +66 -3
  33. package/pipeline/skills/shared/external/compose-navigation/references/patterns.md +191 -0
  34. package/pipeline/skills/shared/external/compose-testing/SKILL.md +107 -397
  35. package/pipeline/skills/shared/external/compose-testing/references/patterns.md +631 -0
  36. package/pipeline/skills/shared/external/gradle-kotlin-dsl/SKILL.md +121 -449
  37. package/pipeline/skills/shared/external/gradle-kotlin-dsl/references/patterns.md +715 -0
  38. package/pipeline/skills/shared/external/kotlin-coroutines-expert/SKILL.md +143 -0
  39. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +27 -102
  40. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +42 -0
  41. package/pipeline/skills/shared/external/retrofit-networking/SKILL.md +94 -383
  42. package/pipeline/skills/shared/external/retrofit-networking/references/patterns.md +640 -0
  43. package/pipeline/skills/shared/external/room-database/SKILL.md +101 -440
  44. package/pipeline/skills/shared/external/room-database/references/patterns.md +614 -0
  45. package/pipeline/skills/shared/external/storekit/SKILL.md +69 -343
  46. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +371 -0
  47. package/pipeline/skills/shared/external/widgetkit/SKILL.md +25 -101
  48. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +107 -0
  49. package/pipeline/skills/skills-index.md +8 -2
@@ -0,0 +1,715 @@
1
+ # Gradle Kotlin DSL Patterns
2
+
3
+ Full code examples backing the guidance in `SKILL.md`. Headings mirror the
4
+ SKILL.md sections.
5
+
6
+ ## Contents
7
+
8
+ - [Version Catalog](#version-catalog)
9
+ - [Convention Plugins](#convention-plugins)
10
+ - [App Module Configuration](#app-module-configuration)
11
+ - [Library Module Configuration](#library-module-configuration)
12
+ - [Compose Configuration](#compose-configuration)
13
+ - [Build Variants and Flavors](#build-variants-and-flavors)
14
+ - [Signing Config](#signing-config)
15
+ - [R8 and ProGuard](#r8-and-proguard)
16
+ - [Build Performance](#build-performance)
17
+ - [Typed BuildConfig Injection](#typed-buildconfig-injection)
18
+ - [Typed Module Paths and Flavors](#typed-module-paths-and-flavors)
19
+ - [Convention Plugin Composition](#convention-plugin-composition)
20
+ - [Fast-Feedback Tasks](#fast-feedback-tasks)
21
+
22
+ ## Version Catalog
23
+
24
+ Define all versions, libraries, and plugins in `gradle/libs.versions.toml`:
25
+
26
+ ```toml
27
+ [versions]
28
+ agp = "8.7.3"
29
+ kotlin = "2.1.0"
30
+ ksp = "2.1.0-1.0.29"
31
+ compose-bom = "2024.12.01"
32
+ hilt = "2.53.1"
33
+ room = "2.6.1"
34
+ retrofit = "2.11.0"
35
+ okhttp = "4.12.0"
36
+ coroutines = "1.9.0"
37
+ lifecycle = "2.8.7"
38
+ navigation = "2.8.5"
39
+ serialization = "1.7.3"
40
+ coil = "2.7.0"
41
+ turbine = "1.2.0"
42
+ roborazzi = "1.32.2"
43
+
44
+ [libraries]
45
+ # Compose
46
+ compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
47
+ compose-ui = { group = "androidx.compose.ui", name = "ui" }
48
+ compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
49
+ compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
50
+ compose-material3 = { group = "androidx.compose.material3", name = "material3" }
51
+ compose-runtime = { group = "androidx.compose.runtime", name = "runtime" }
52
+
53
+ # Lifecycle
54
+ lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" }
55
+ lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle" }
56
+
57
+ # Navigation
58
+ navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" }
59
+
60
+ # Hilt
61
+ hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
62
+ hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }
63
+ hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version = "1.2.0" }
64
+
65
+ # Room
66
+ room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
67
+ room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
68
+ room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
69
+ room-testing = { group = "androidx.room", name = "room-testing", version.ref = "room" }
70
+
71
+ # Network
72
+ retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
73
+ okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
74
+ okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" }
75
+ serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serialization" }
76
+ retrofit-serialization = { group = "com.squareup.retrofit2", name = "converter-kotlinx-serialization", version = "2.11.0" }
77
+
78
+ # Coroutines
79
+ coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines" }
80
+ coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" }
81
+ coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" }
82
+
83
+ # Image loading
84
+ coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" }
85
+
86
+ # Testing
87
+ junit = { group = "junit", name = "junit", version = "4.13.2" }
88
+ turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine" }
89
+ truth = { group = "com.google.truth", name = "truth", version = "1.4.4" }
90
+ compose-ui-test = { group = "androidx.compose.ui", name = "ui-test-junit4" }
91
+ compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
92
+ roborazzi = { group = "io.github.takahirom.roborazzi", name = "roborazzi", version.ref = "roborazzi" }
93
+ roborazzi-compose = { group = "io.github.takahirom.roborazzi", name = "roborazzi-compose", version.ref = "roborazzi" }
94
+
95
+ [bundles]
96
+ compose = ["compose-ui", "compose-ui-tooling-preview", "compose-material3", "compose-runtime"]
97
+ compose-debug = ["compose-ui-tooling", "compose-ui-test-manifest"]
98
+ lifecycle = ["lifecycle-runtime-compose", "lifecycle-viewmodel-compose"]
99
+ room = ["room-runtime", "room-ktx"]
100
+ network = ["retrofit", "okhttp", "okhttp-logging", "serialization-json", "retrofit-serialization"]
101
+
102
+ [plugins]
103
+ android-application = { id = "com.android.application", version.ref = "agp" }
104
+ android-library = { id = "com.android.library", version.ref = "agp" }
105
+ kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
106
+ kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
107
+ compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
108
+ ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
109
+ hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
110
+ room = { id = "androidx.room", version.ref = "room" }
111
+ ```
112
+
113
+ ## Convention Plugins
114
+
115
+ Create a `build-logic` module to share common configurations:
116
+
117
+ ### settings.gradle.kts (root)
118
+
119
+ ```kotlin
120
+ pluginManagement {
121
+ includeBuild("build-logic")
122
+ repositories {
123
+ google()
124
+ mavenCentral()
125
+ gradlePluginPortal()
126
+ }
127
+ }
128
+ ```
129
+
130
+ ### build-logic/settings.gradle.kts
131
+
132
+ ```kotlin
133
+ dependencyResolutionManagement {
134
+ repositories {
135
+ google()
136
+ mavenCentral()
137
+ }
138
+ versionCatalogs {
139
+ create("libs") {
140
+ from(files("../gradle/libs.versions.toml"))
141
+ }
142
+ }
143
+ }
144
+
145
+ include(":convention")
146
+ ```
147
+
148
+ ### build-logic/convention/build.gradle.kts
149
+
150
+ ```kotlin
151
+ plugins {
152
+ `kotlin-dsl`
153
+ }
154
+
155
+ dependencies {
156
+ compileOnly(libs.android.gradlePlugin)
157
+ compileOnly(libs.kotlin.gradlePlugin)
158
+ compileOnly(libs.compose.gradlePlugin)
159
+ }
160
+ ```
161
+
162
+ ### Android Library Convention Plugin
163
+
164
+ ```kotlin
165
+ // build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt
166
+ import com.android.build.gradle.LibraryExtension
167
+ import org.gradle.api.Plugin
168
+ import org.gradle.api.Project
169
+ import org.gradle.kotlin.dsl.configure
170
+
171
+ class AndroidLibraryConventionPlugin : Plugin<Project> {
172
+ override fun apply(target: Project) {
173
+ with(target) {
174
+ with(pluginManager) {
175
+ apply("com.android.library")
176
+ apply("org.jetbrains.kotlin.android")
177
+ }
178
+
179
+ extensions.configure<LibraryExtension> {
180
+ compileSdk = 35
181
+ defaultConfig {
182
+ minSdk = 26
183
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
184
+ }
185
+ compileOptions {
186
+ sourceCompatibility = JavaVersion.VERSION_17
187
+ targetCompatibility = JavaVersion.VERSION_17
188
+ }
189
+ }
190
+ }
191
+ }
192
+ }
193
+ ```
194
+
195
+ ### Register in build-logic/convention/build.gradle.kts
196
+
197
+ ```kotlin
198
+ gradlePlugin {
199
+ plugins {
200
+ register("androidLibrary") {
201
+ id = "app.android.library"
202
+ implementationClass = "AndroidLibraryConventionPlugin"
203
+ }
204
+ register("androidCompose") {
205
+ id = "app.android.compose"
206
+ implementationClass = "AndroidComposeConventionPlugin"
207
+ }
208
+ register("androidHilt") {
209
+ id = "app.android.hilt"
210
+ implementationClass = "AndroidHiltConventionPlugin"
211
+ }
212
+ }
213
+ }
214
+ ```
215
+
216
+ ### Usage in Feature Module
217
+
218
+ ```kotlin
219
+ // feature/home/build.gradle.kts
220
+ plugins {
221
+ id("app.android.library")
222
+ id("app.android.compose")
223
+ id("app.android.hilt")
224
+ }
225
+
226
+ dependencies {
227
+ implementation(project(":core:model"))
228
+ implementation(project(":core:data"))
229
+ implementation(project(":core:ui"))
230
+ }
231
+ ```
232
+
233
+ ## App Module Configuration
234
+
235
+ ```kotlin
236
+ // app/build.gradle.kts
237
+ plugins {
238
+ alias(libs.plugins.android.application)
239
+ alias(libs.plugins.kotlin.android)
240
+ alias(libs.plugins.compose.compiler)
241
+ alias(libs.plugins.hilt)
242
+ alias(libs.plugins.ksp)
243
+ }
244
+
245
+ android {
246
+ namespace = "com.example.app"
247
+ compileSdk = 35
248
+
249
+ defaultConfig {
250
+ applicationId = "com.example.app"
251
+ minSdk = 26
252
+ targetSdk = 35
253
+ versionCode = 1
254
+ versionName = "1.0.0"
255
+ testInstrumentationRunner = "com.example.app.HiltTestRunner"
256
+ }
257
+
258
+ buildTypes {
259
+ release {
260
+ isMinifyEnabled = true
261
+ isShrinkResources = true
262
+ proguardFiles(
263
+ getDefaultProguardFile("proguard-android-optimize.txt"),
264
+ "proguard-rules.pro",
265
+ )
266
+ }
267
+ debug {
268
+ isMinifyEnabled = false
269
+ applicationIdSuffix = ".debug"
270
+ }
271
+ }
272
+
273
+ compileOptions {
274
+ sourceCompatibility = JavaVersion.VERSION_17
275
+ targetCompatibility = JavaVersion.VERSION_17
276
+ }
277
+
278
+ kotlinOptions {
279
+ jvmTarget = "17"
280
+ }
281
+ }
282
+
283
+ dependencies {
284
+ implementation(platform(libs.compose.bom))
285
+ implementation(libs.bundles.compose)
286
+ implementation(libs.bundles.lifecycle)
287
+ implementation(libs.navigation.compose)
288
+ implementation(libs.hilt.android)
289
+ ksp(libs.hilt.compiler)
290
+ implementation(libs.hilt.navigation.compose)
291
+
292
+ debugImplementation(libs.bundles.compose.debug)
293
+
294
+ testImplementation(libs.junit)
295
+ testImplementation(libs.truth)
296
+ testImplementation(libs.coroutines.test)
297
+ testImplementation(libs.turbine)
298
+
299
+ androidTestImplementation(platform(libs.compose.bom))
300
+ androidTestImplementation(libs.compose.ui.test)
301
+ }
302
+ ```
303
+
304
+ ## Library Module Configuration
305
+
306
+ ```kotlin
307
+ // core/data/build.gradle.kts
308
+ plugins {
309
+ alias(libs.plugins.android.library)
310
+ alias(libs.plugins.kotlin.android)
311
+ alias(libs.plugins.hilt)
312
+ alias(libs.plugins.ksp)
313
+ }
314
+
315
+ android {
316
+ namespace = "com.example.core.data"
317
+ compileSdk = 35
318
+
319
+ defaultConfig {
320
+ minSdk = 26
321
+ }
322
+ }
323
+
324
+ dependencies {
325
+ implementation(project(":core:model"))
326
+ implementation(project(":core:network"))
327
+ implementation(project(":core:database"))
328
+ implementation(libs.hilt.android)
329
+ ksp(libs.hilt.compiler)
330
+ implementation(libs.coroutines.core)
331
+
332
+ testImplementation(libs.junit)
333
+ testImplementation(libs.truth)
334
+ testImplementation(libs.coroutines.test)
335
+ }
336
+ ```
337
+
338
+ ## Compose Configuration
339
+
340
+ ```kotlin
341
+ // Convention plugin or inline
342
+ android {
343
+ buildFeatures {
344
+ compose = true
345
+ }
346
+ }
347
+
348
+ // With Kotlin 2.0+, use the Compose Compiler plugin:
349
+ plugins {
350
+ alias(libs.plugins.compose.compiler)
351
+ }
352
+
353
+ // Optional: Compose compiler metrics for performance debugging
354
+ // In gradle.properties or via command line:
355
+ // composeCompiler.reportsDestination=build/compose-reports
356
+ // composeCompiler.metricsDestination=build/compose-metrics
357
+ ```
358
+
359
+ ## Build Variants and Flavors
360
+
361
+ ```kotlin
362
+ android {
363
+ buildTypes {
364
+ release {
365
+ isMinifyEnabled = true
366
+ isShrinkResources = true
367
+ proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
368
+ }
369
+ debug {
370
+ isMinifyEnabled = false
371
+ applicationIdSuffix = ".debug"
372
+ isDebuggable = true
373
+ }
374
+ create("staging") {
375
+ initWith(getByName("release"))
376
+ applicationIdSuffix = ".staging"
377
+ isMinifyEnabled = true
378
+ matchingFallbacks += "release"
379
+ }
380
+ }
381
+
382
+ flavorDimensions += "environment"
383
+ productFlavors {
384
+ create("dev") {
385
+ dimension = "environment"
386
+ applicationIdSuffix = ".dev"
387
+ buildConfigField("String", "BASE_URL", "\"https://dev-api.example.com/\"")
388
+ }
389
+ create("prod") {
390
+ dimension = "environment"
391
+ buildConfigField("String", "BASE_URL", "\"https://api.example.com/\"")
392
+ }
393
+ }
394
+ }
395
+ ```
396
+
397
+ ## Signing Config
398
+
399
+ ```kotlin
400
+ android {
401
+ signingConfigs {
402
+ create("release") {
403
+ storeFile = file(System.getenv("KEYSTORE_PATH") ?: "release.keystore")
404
+ storePassword = System.getenv("KEYSTORE_PASSWORD") ?: ""
405
+ keyAlias = System.getenv("KEY_ALIAS") ?: ""
406
+ keyPassword = System.getenv("KEY_PASSWORD") ?: ""
407
+ }
408
+ }
409
+
410
+ buildTypes {
411
+ release {
412
+ signingConfig = signingConfigs.getByName("release")
413
+ }
414
+ }
415
+ }
416
+ ```
417
+
418
+ Never commit keystore passwords. Use environment variables or `local.properties` (gitignored).
419
+
420
+ ## R8 and ProGuard
421
+
422
+ ### proguard-rules.pro
423
+
424
+ ```proguard
425
+ # Kotlin serialization
426
+ -keepattributes *Annotation*, InnerClasses
427
+ -dontnote kotlinx.serialization.AnnotationsKt
428
+
429
+ -keepclassmembers class kotlinx.serialization.json.** {
430
+ *** Companion;
431
+ }
432
+ -keepclasseswithmembers class kotlinx.serialization.json.** {
433
+ kotlinx.serialization.KSerializer serializer(...);
434
+ }
435
+ -keep,includedescriptorclasses class com.example.app.data.dto.**$$serializer { *; }
436
+ -keepclassmembers class com.example.app.data.dto.** {
437
+ *** Companion;
438
+ }
439
+ -keepclasseswithmembers class com.example.app.data.dto.** {
440
+ kotlinx.serialization.KSerializer serializer(...);
441
+ }
442
+
443
+ # Retrofit
444
+ -keepattributes Signature
445
+ -keepattributes Exceptions
446
+ -keepclassmembers,allowshrinking,allowobfuscation interface * {
447
+ @retrofit2.http.* <methods>;
448
+ }
449
+ -dontwarn javax.annotation.**
450
+ -dontwarn kotlin.Unit
451
+ -dontwarn retrofit2.KotlinExtensions
452
+ -dontwarn retrofit2.KotlinExtensions$*
453
+
454
+ # OkHttp
455
+ -dontwarn okhttp3.internal.platform.**
456
+ -dontwarn org.conscrypt.**
457
+ -dontwarn org.bouncycastle.**
458
+ -dontwarn org.openjsse.**
459
+
460
+ # Hilt
461
+ -keep class dagger.hilt.** { *; }
462
+ ```
463
+
464
+ ## Build Performance
465
+
466
+ ### gradle.properties
467
+
468
+ ```properties
469
+ # Parallel execution
470
+ org.gradle.parallel=true
471
+
472
+ # Configuration cache
473
+ org.gradle.configuration-cache=true
474
+
475
+ # Build cache
476
+ org.gradle.caching=true
477
+
478
+ # JVM memory
479
+ org.gradle.jvmargs=-Xmx4g -XX:+UseG1GC -XX:MaxMetaspaceSize=1g
480
+
481
+ # Kotlin daemon
482
+ kotlin.daemon.jvmargs=-Xmx2g
483
+
484
+ # Non-transitive R classes (reduces rebuild scope)
485
+ android.nonTransitiveRClass=true
486
+
487
+ # Disable unused features
488
+ android.defaults.buildfeatures.buildconfig=false
489
+ android.defaults.buildfeatures.aidl=false
490
+ android.defaults.buildfeatures.renderscript=false
491
+ ```
492
+
493
+ ### Dependency Locking
494
+
495
+ ```kotlin
496
+ dependencyLocking {
497
+ lockAllConfigurations()
498
+ }
499
+
500
+ // Generate lock files: ./gradlew dependencies --write-locks
501
+ ```
502
+
503
+ ## Typed BuildConfig Injection
504
+
505
+ Replace scattered `buildConfigField("String", "BASE_URL", "\"...\"")` calls with a
506
+ typed key enum, per-module contributor objects, and a central resolver that
507
+ filters by module scope and de-dupes.
508
+
509
+ ```kotlin
510
+ // build-logic/convention/src/main/kotlin/buildconfig/BuildConfigKey.kt
511
+ enum class BuildConfigKey(val javaType: String) {
512
+ BASE_URL("String"),
513
+ ANALYTICS_ENABLED("boolean"),
514
+ API_TIMEOUT_SECONDS("int"),
515
+ }
516
+
517
+ enum class ModuleScope { APP, FEATURE, ALL }
518
+
519
+ data class BuildConfigEntry(
520
+ val key: BuildConfigKey,
521
+ val value: String,
522
+ val scope: ModuleScope = ModuleScope.ALL,
523
+ )
524
+
525
+ interface BuildConfigContributor {
526
+ fun entries(flavor: Flavor): List<BuildConfigEntry>
527
+ }
528
+
529
+ object BuildConfigResolver {
530
+ fun resolve(
531
+ contributors: List<BuildConfigContributor>,
532
+ scope: ModuleScope,
533
+ flavor: Flavor,
534
+ ): List<BuildConfigEntry> =
535
+ contributors
536
+ .flatMap { it.entries(flavor) }
537
+ .filter { it.scope == scope || it.scope == ModuleScope.ALL }
538
+ .distinctBy { it.key }
539
+ }
540
+ ```
541
+
542
+ A contributor lives next to the module it belongs to:
543
+
544
+ ```kotlin
545
+ object NetworkBuildConfig : BuildConfigContributor {
546
+ override fun entries(flavor: Flavor) = listOf(
547
+ BuildConfigEntry(
548
+ key = BuildConfigKey.BASE_URL,
549
+ value = "\"${flavor.baseUrl}\"",
550
+ ),
551
+ BuildConfigEntry(
552
+ key = BuildConfigKey.API_TIMEOUT_SECONDS,
553
+ value = "30",
554
+ scope = ModuleScope.ALL,
555
+ ),
556
+ )
557
+ }
558
+ ```
559
+
560
+ The convention plugin materializes the resolved entries once per flavor, so the
561
+ `javaType` is always correct and no module hand-writes a `buildConfigField`:
562
+
563
+ ```kotlin
564
+ internal fun VariantDimension.applyBuildConfig(
565
+ resolver: BuildConfigResolver,
566
+ contributors: List<BuildConfigContributor>,
567
+ scope: ModuleScope,
568
+ flavor: Flavor,
569
+ ) {
570
+ resolver.resolve(contributors, scope, flavor).forEach { entry ->
571
+ buildConfigField(entry.key.javaType, entry.key.name, entry.value)
572
+ }
573
+ }
574
+ ```
575
+
576
+ ## Typed Module Paths and Flavors
577
+
578
+ Model module paths and flavors as sealed/enum types so builds reference
579
+ `Module.Core.Domain.toProject()` instead of the stringly-typed
580
+ `project(":core:domain")`.
581
+
582
+ ```kotlin
583
+ // build-logic/convention/src/main/kotlin/Module.kt
584
+ sealed interface Module {
585
+ val path: String
586
+
587
+ sealed interface Core : Module {
588
+ data object Domain : Core { override val path = ":core:domain" }
589
+ data object Data : Core { override val path = ":core:data" }
590
+ data object Ui : Core { override val path = ":core:ui" }
591
+ }
592
+
593
+ sealed interface Feature : Module {
594
+ data object Home : Feature { override val path = ":feature:home" }
595
+ data object Profile : Feature { override val path = ":feature:profile" }
596
+ }
597
+ }
598
+
599
+ fun DependencyHandler.toProject(module: Module): ProjectDependency =
600
+ project(mapOf("path" to module.path)) as ProjectDependency
601
+
602
+ context(DependencyHandler)
603
+ fun Module.toProject(): ProjectDependency = toProject(this)
604
+
605
+ enum class Flavor(val flavorName: String, val dimension: String, val baseUrl: String) {
606
+ DEV("dev", "environment", "https://dev-api.example.com/"),
607
+ PROD("prod", "environment", "https://api.example.com/"),
608
+ }
609
+ ```
610
+
611
+ ```kotlin
612
+ // feature/home/build.gradle.kts
613
+ dependencies {
614
+ implementation(Module.Core.Domain.toProject())
615
+ implementation(Module.Core.Ui.toProject())
616
+ }
617
+ ```
618
+
619
+ ## Convention Plugin Composition
620
+
621
+ Higher-level plugins apply lower ones. A single `feature-impl` plugin applies
622
+ library + hilt + compose + test and wires the standard dependency set, so a
623
+ feature module's `plugins {}` block is one line.
624
+
625
+ ```kotlin
626
+ // build-logic/convention/src/main/kotlin/FeatureImplConventionPlugin.kt
627
+ class FeatureImplConventionPlugin : Plugin<Project> {
628
+ override fun apply(target: Project) = with(target) {
629
+ with(pluginManager) {
630
+ apply("app.android.library")
631
+ apply("app.android.hilt")
632
+ apply("app.android.compose")
633
+ apply("app.android.test")
634
+ }
635
+ dependencies {
636
+ add("implementation", Module.Core.Ui.toProject(this))
637
+ add("implementation", Module.Core.Domain.toProject(this))
638
+ }
639
+ }
640
+ }
641
+ ```
642
+
643
+ Shared toolchain and compiler flags live in one extension both the library and
644
+ app plugins call:
645
+
646
+ ```kotlin
647
+ // build-logic/convention/src/main/kotlin/KotlinAndroid.kt
648
+ internal fun Project.configureKotlinAndroid(
649
+ extension: CommonExtension<*, *, *, *, *, *>,
650
+ ) {
651
+ extension.apply {
652
+ compileSdk = 35
653
+ defaultConfig { minSdk = 26 }
654
+ compileOptions {
655
+ sourceCompatibility = JavaVersion.VERSION_17
656
+ targetCompatibility = JavaVersion.VERSION_17
657
+ }
658
+ }
659
+ extensions.configure<KotlinAndroidProjectExtension> {
660
+ compilerOptions {
661
+ jvmTarget.set(JvmTarget.JVM_17)
662
+ freeCompilerArgs.addAll("-Xexplicit-api=strict")
663
+ }
664
+ }
665
+ }
666
+ ```
667
+
668
+ Alias the convention plugins in the version catalog so modules apply
669
+ `libs.plugins.*` instead of a raw id string:
670
+
671
+ ```toml
672
+ [plugins]
673
+ app-android-feature = { id = "app.android.feature", version = "unspecified" }
674
+ app-android-library = { id = "app.android.library", version = "unspecified" }
675
+ app-android-compose = { id = "app.android.compose", version = "unspecified" }
676
+ ```
677
+
678
+ ```kotlin
679
+ // feature/home/build.gradle.kts
680
+ plugins {
681
+ alias(libs.plugins.app.android.feature)
682
+ }
683
+ ```
684
+
685
+ ## Fast-Feedback Tasks
686
+
687
+ Install git hooks through a Gradle task wired as a `preBuild` dependency, so a
688
+ fresh clone gets the hooks on its first build. Resolve the git directory
689
+ correctly when `.git` is a worktree pointer file rather than a directory.
690
+
691
+ ```kotlin
692
+ // build-logic/convention or root build.gradle.kts
693
+ fun resolveGitDir(root: File): File {
694
+ val dotGit = root.resolve(".git")
695
+ if (dotGit.isDirectory) return dotGit
696
+ val pointer = dotGit.readText().substringAfter("gitdir:").trim()
697
+ return File(pointer).let { if (it.isAbsolute) it else root.resolve(pointer).canonicalFile }
698
+ }
699
+
700
+ val installGitHooks by tasks.registering(Copy::class) {
701
+ from(rootDir.resolve("scripts/git-hooks"))
702
+ into(resolveGitDir(rootDir).resolve("hooks"))
703
+ filePermissions { unix("rwxr-xr-x") }
704
+ }
705
+
706
+ subprojects {
707
+ tasks.matching { it.name == "preBuild" }.configureEach {
708
+ dependsOn(rootProject.tasks.named("installGitHooks"))
709
+ }
710
+ }
711
+ ```
712
+
713
+ The build cache (`org.gradle.caching=true`) belongs in the same perf-flag set as
714
+ parallel execution and the configuration cache; see
715
+ [Build Performance](#build-performance).