agentic-team-templates 0.13.2 → 0.14.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 (54) hide show
  1. package/README.md +6 -1
  2. package/package.json +1 -1
  3. package/src/index.js +22 -2
  4. package/src/index.test.js +5 -0
  5. package/templates/cpp-expert/.cursorrules/concurrency.md +211 -0
  6. package/templates/cpp-expert/.cursorrules/error-handling.md +170 -0
  7. package/templates/cpp-expert/.cursorrules/memory-and-ownership.md +220 -0
  8. package/templates/cpp-expert/.cursorrules/modern-cpp.md +211 -0
  9. package/templates/cpp-expert/.cursorrules/overview.md +87 -0
  10. package/templates/cpp-expert/.cursorrules/performance.md +223 -0
  11. package/templates/cpp-expert/.cursorrules/testing.md +230 -0
  12. package/templates/cpp-expert/.cursorrules/tooling.md +312 -0
  13. package/templates/cpp-expert/CLAUDE.md +242 -0
  14. package/templates/csharp-expert/.cursorrules/aspnet-core.md +311 -0
  15. package/templates/csharp-expert/.cursorrules/async-patterns.md +206 -0
  16. package/templates/csharp-expert/.cursorrules/dependency-injection.md +206 -0
  17. package/templates/csharp-expert/.cursorrules/error-handling.md +235 -0
  18. package/templates/csharp-expert/.cursorrules/language-features.md +204 -0
  19. package/templates/csharp-expert/.cursorrules/overview.md +92 -0
  20. package/templates/csharp-expert/.cursorrules/performance.md +251 -0
  21. package/templates/csharp-expert/.cursorrules/testing.md +282 -0
  22. package/templates/csharp-expert/.cursorrules/tooling.md +254 -0
  23. package/templates/csharp-expert/CLAUDE.md +360 -0
  24. package/templates/java-expert/.cursorrules/concurrency.md +209 -0
  25. package/templates/java-expert/.cursorrules/error-handling.md +205 -0
  26. package/templates/java-expert/.cursorrules/modern-java.md +216 -0
  27. package/templates/java-expert/.cursorrules/overview.md +81 -0
  28. package/templates/java-expert/.cursorrules/performance.md +239 -0
  29. package/templates/java-expert/.cursorrules/persistence.md +262 -0
  30. package/templates/java-expert/.cursorrules/spring-boot.md +262 -0
  31. package/templates/java-expert/.cursorrules/testing.md +272 -0
  32. package/templates/java-expert/.cursorrules/tooling.md +301 -0
  33. package/templates/java-expert/CLAUDE.md +325 -0
  34. package/templates/javascript-expert/.cursorrules/overview.md +5 -3
  35. package/templates/javascript-expert/.cursorrules/typescript-deep-dive.md +348 -0
  36. package/templates/javascript-expert/CLAUDE.md +34 -3
  37. package/templates/kotlin-expert/.cursorrules/coroutines.md +237 -0
  38. package/templates/kotlin-expert/.cursorrules/error-handling.md +149 -0
  39. package/templates/kotlin-expert/.cursorrules/frameworks.md +227 -0
  40. package/templates/kotlin-expert/.cursorrules/language-features.md +231 -0
  41. package/templates/kotlin-expert/.cursorrules/overview.md +77 -0
  42. package/templates/kotlin-expert/.cursorrules/performance.md +185 -0
  43. package/templates/kotlin-expert/.cursorrules/testing.md +213 -0
  44. package/templates/kotlin-expert/.cursorrules/tooling.md +258 -0
  45. package/templates/kotlin-expert/CLAUDE.md +276 -0
  46. package/templates/swift-expert/.cursorrules/concurrency.md +230 -0
  47. package/templates/swift-expert/.cursorrules/error-handling.md +213 -0
  48. package/templates/swift-expert/.cursorrules/language-features.md +246 -0
  49. package/templates/swift-expert/.cursorrules/overview.md +88 -0
  50. package/templates/swift-expert/.cursorrules/performance.md +260 -0
  51. package/templates/swift-expert/.cursorrules/swiftui.md +260 -0
  52. package/templates/swift-expert/.cursorrules/testing.md +286 -0
  53. package/templates/swift-expert/.cursorrules/tooling.md +285 -0
  54. package/templates/swift-expert/CLAUDE.md +275 -0
@@ -0,0 +1,185 @@
1
+ # Kotlin Performance
2
+
3
+ Profile first. Understand the JVM. Kotlin-specific optimizations matter.
4
+
5
+ ## Profile Before Optimizing
6
+
7
+ ```bash
8
+ # JFR (Java Flight Recorder) — works with Kotlin on JVM
9
+ java -XX:StartFlightRecording=filename=recording.jfr,duration=60s -jar app.jar
10
+
11
+ # Kotlin-specific: check for unnecessary boxing, allocations
12
+ # Use JMH for micro-benchmarks
13
+ ```
14
+
15
+ ## Inline Functions
16
+
17
+ ```kotlin
18
+ // inline eliminates lambda allocation overhead — use for hot paths
19
+ inline fun <T> measure(label: String, block: () -> T): T {
20
+ val start = System.nanoTime()
21
+ val result = block()
22
+ val elapsed = System.nanoTime() - start
23
+ logger.debug { "$label took ${elapsed / 1_000_000}ms" }
24
+ return result
25
+ }
26
+
27
+ // crossinline — prevent non-local returns in inlined lambdas
28
+ inline fun transaction(crossinline block: () -> Unit) {
29
+ beginTransaction()
30
+ try {
31
+ block()
32
+ commit()
33
+ } catch (e: Exception) {
34
+ rollback()
35
+ throw e
36
+ }
37
+ }
38
+
39
+ // reified — preserve generic type info at runtime (only with inline)
40
+ inline fun <reified T> parseJson(json: String): T {
41
+ return objectMapper.readValue(json, T::class.java)
42
+ }
43
+
44
+ // Rules:
45
+ // - Inline small, frequently-called higher-order functions
46
+ // - Don't inline large function bodies (code bloat)
47
+ // - Standard library uses inline extensively: let, apply, also, run, with, map, filter
48
+ ```
49
+
50
+ ## Value Classes
51
+
52
+ ```kotlin
53
+ // Zero overhead wrappers — no runtime allocation for the wrapper
54
+ @JvmInline
55
+ value class UserId(val value: String)
56
+
57
+ @JvmInline
58
+ value class Email(val value: String) {
59
+ init {
60
+ require(value.contains("@")) { "Invalid email" }
61
+ }
62
+ }
63
+
64
+ // At runtime, UserId("abc") is just the String "abc"
65
+ // But at compile time, you can't pass UserId where Email is expected
66
+ fun findUser(id: UserId): User // Type-safe, zero overhead
67
+ ```
68
+
69
+ ## Sequences vs Collections
70
+
71
+ ```kotlin
72
+ // Collections: eager, creates intermediate lists
73
+ val result = users
74
+ .filter { it.isActive } // Creates List<User>
75
+ .map { it.name } // Creates List<String>
76
+ .take(10) // Creates List<String>
77
+
78
+ // Sequences: lazy, no intermediate allocations
79
+ val result = users.asSequence()
80
+ .filter { it.isActive } // No intermediate list
81
+ .map { it.name } // No intermediate list
82
+ .take(10) // Stops after 10 matches
83
+ .toList() // Single terminal allocation
84
+
85
+ // Use sequences when:
86
+ // - Processing large collections (>1000 elements)
87
+ // - Chaining multiple operations
88
+ // - Using take/first (short-circuit)
89
+
90
+ // Use collections when:
91
+ // - Small datasets
92
+ // - Single operation
93
+ // - Need indexed access during processing
94
+ ```
95
+
96
+ ## Coroutine Performance
97
+
98
+ ```kotlin
99
+ // Channel buffers for throughput
100
+ val channel = Channel<Event>(capacity = 64) // Buffered, not unlimited
101
+
102
+ // Fan-out for parallel processing
103
+ val workers = List(Runtime.getRuntime().availableProcessors()) {
104
+ launch(Dispatchers.Default) {
105
+ for (item in channel) process(item)
106
+ }
107
+ }
108
+
109
+ // Avoid unnecessary suspension
110
+ // Bad: wrapping non-suspending code in withContext
111
+ suspend fun format(name: String): String = withContext(Dispatchers.Default) {
112
+ name.trim().lowercase() // This is instant — no need for withContext
113
+ }
114
+
115
+ // Good: only use withContext for actual blocking/CPU work
116
+ suspend fun hashPassword(password: String): String = withContext(Dispatchers.Default) {
117
+ BCrypt.hashpw(password, BCrypt.gensalt()) // Actually CPU-intensive
118
+ }
119
+ ```
120
+
121
+ ## Collection Optimization
122
+
123
+ ```kotlin
124
+ // Pre-size collections
125
+ val map = HashMap<String, User>(expectedSize)
126
+ val list = ArrayList<User>(expectedSize)
127
+
128
+ // buildList/buildMap — single allocation
129
+ val items = buildList(expectedSize) {
130
+ for (item in source) {
131
+ if (item.isValid) add(transform(item))
132
+ }
133
+ }
134
+
135
+ // Use appropriate collection types
136
+ val lookup = users.associateBy { it.id } // HashMap for O(1) lookup
137
+ val uniqueEmails = users.mapTo(HashSet()) { it.email } // HashSet for uniqueness
138
+ val sorted = users.sortedBy { it.name } // Single sort, not repeated
139
+
140
+ // Avoid: repeated list.contains() — use a Set
141
+ val activeIds = activeUsers.map { it.id }.toSet() // O(1) lookup
142
+ orders.filter { it.userId in activeIds } // Fast membership test
143
+ ```
144
+
145
+ ## String Performance
146
+
147
+ ```kotlin
148
+ // StringBuilder for complex concatenation
149
+ val result = buildString {
150
+ for (item in items) {
151
+ append(item.name)
152
+ append(": ")
153
+ appendLine(item.value)
154
+ }
155
+ }
156
+
157
+ // joinToString for simple cases
158
+ val csv = items.joinToString(",") { it.name }
159
+
160
+ // String templates are efficient — Kotlin compiles them to StringBuilder
161
+ val message = "User $name logged in from $ip" // Fine for most cases
162
+ ```
163
+
164
+ ## Anti-Patterns
165
+
166
+ ```kotlin
167
+ // Never: premature optimization without profiling
168
+ // "I think this allocation is slow" — prove it with JFR or JMH
169
+
170
+ // Never: creating coroutines for non-suspending work
171
+ launch { val x = 1 + 1 } // Overhead of coroutine machinery for nothing
172
+
173
+ // Never: using reflection in hot paths
174
+ val prop = User::class.memberProperties.find { it.name == "email" }
175
+ // Use direct property access
176
+
177
+ // Never: unnecessary boxing
178
+ val numbers: List<Int> = listOf(1, 2, 3) // Boxed integers
179
+ // For performance-critical code: IntArray(3) { it + 1 }
180
+
181
+ // Never: mutable shared state in coroutines without synchronization
182
+ var counter = 0
183
+ repeat(1000) { launch { counter++ } } // Data race
184
+ // Use: AtomicInteger, Mutex, or single-writer pattern
185
+ ```
@@ -0,0 +1,213 @@
1
+ # Kotlin Testing
2
+
3
+ Test behavior with expressive assertions. Coroutines require special test infrastructure.
4
+
5
+ ## Framework Stack
6
+
7
+ | Tool | Purpose |
8
+ |------|---------|
9
+ | JUnit 5 | Test framework |
10
+ | Kotest | Kotlin-native testing (property-based, data-driven) |
11
+ | MockK | Kotlin-native mocking |
12
+ | kotlinx-coroutines-test | Coroutine testing |
13
+ | Testcontainers | Real databases/services |
14
+ | Ktor Test | HTTP endpoint testing |
15
+ | ArchUnit | Architecture tests |
16
+
17
+ ## Unit Test Structure
18
+
19
+ ```kotlin
20
+ class OrderServiceTest {
21
+
22
+ private val orderRepo = mockk<OrderRepository>()
23
+ private val inventoryClient = mockk<InventoryClient>()
24
+ private val sut = OrderService(orderRepo, inventoryClient)
25
+
26
+ @Test
27
+ fun `create with valid items returns success`() = runTest {
28
+ // Arrange
29
+ val request = CreateOrderRequest(
30
+ customerId = "customer-1",
31
+ items = listOf(OrderItemRequest("SKU-001", 2))
32
+ )
33
+ coEvery { inventoryClient.checkAvailability("SKU-001", 2) } returns true
34
+ coEvery { orderRepo.save(any()) } answers { firstArg() }
35
+
36
+ // Act
37
+ val result = sut.create(request)
38
+
39
+ // Assert
40
+ assertThat(result).isInstanceOf(Result.Success::class.java)
41
+ val order = (result as Result.Success).value
42
+ assertThat(order.customerId).isEqualTo("customer-1")
43
+ assertThat(order.items).hasSize(1)
44
+ }
45
+
46
+ @Test
47
+ fun `create with insufficient inventory returns failure`() = runTest {
48
+ val request = CreateOrderRequest(
49
+ customerId = "customer-1",
50
+ items = listOf(OrderItemRequest("SKU-001", 100))
51
+ )
52
+ coEvery { inventoryClient.checkAvailability("SKU-001", 100) } returns false
53
+
54
+ val result = sut.create(request)
55
+
56
+ assertThat(result).isInstanceOf(Result.Failure::class.java)
57
+ val error = (result as Result.Failure).error
58
+ assertThat(error).isInstanceOf(AppError.Validation::class.java)
59
+
60
+ coVerify(exactly = 0) { orderRepo.save(any()) }
61
+ }
62
+ }
63
+ ```
64
+
65
+ ## Coroutine Testing
66
+
67
+ ```kotlin
68
+ // runTest provides a TestScope with virtual time
69
+ @Test
70
+ fun `polling retries on failure`() = runTest {
71
+ var attempts = 0
72
+ coEvery { service.fetch() } answers {
73
+ attempts++
74
+ if (attempts < 3) throw IOException("Temporary failure")
75
+ else Data("success")
76
+ }
77
+
78
+ val result = sut.fetchWithRetry(maxRetries = 3)
79
+
80
+ assertThat(result.value).isEqualTo("success")
81
+ assertThat(attempts).isEqualTo(3)
82
+ }
83
+
84
+ // Testing flows
85
+ @Test
86
+ fun `user updates emit state changes`() = runTest {
87
+ val viewModel = UserViewModel(mockUserService)
88
+
89
+ viewModel.state.test {
90
+ assertThat(awaitItem()).isEqualTo(UiState.Loading)
91
+
92
+ viewModel.loadUser(UserId("123"))
93
+
94
+ assertThat(awaitItem()).isEqualTo(UiState.Loading)
95
+ assertThat(awaitItem()).isInstanceOf(UiState.Success::class.java)
96
+ }
97
+ }
98
+
99
+ // advanceTimeBy for testing delays
100
+ @Test
101
+ fun `cache expires after TTL`() = runTest {
102
+ cache.set("key", "value")
103
+
104
+ assertThat(cache.get("key")).isEqualTo("value")
105
+
106
+ advanceTimeBy(cacheTtl + 1.seconds)
107
+
108
+ assertThat(cache.get("key")).isNull()
109
+ }
110
+ ```
111
+
112
+ ## MockK
113
+
114
+ ```kotlin
115
+ // Suspend function mocking
116
+ coEvery { userRepo.findById(any()) } returns User(id = "1", name = "Alice")
117
+ coEvery { emailService.send(any(), any(), any()) } just Runs
118
+
119
+ // Verification
120
+ coVerify(exactly = 1) { emailService.send("alice@test.com", any(), any()) }
121
+ coVerify(ordering = Ordering.ORDERED) {
122
+ userRepo.save(any())
123
+ emailService.send(any(), any(), any())
124
+ }
125
+
126
+ // Capturing
127
+ val slot = slot<User>()
128
+ coEvery { userRepo.save(capture(slot)) } answers { firstArg() }
129
+
130
+ sut.create(request)
131
+
132
+ assertThat(slot.captured.name).isEqualTo("Alice")
133
+
134
+ // Relaxed mocks — return defaults for all unmocked calls
135
+ val logger = mockk<Logger>(relaxed = true)
136
+ ```
137
+
138
+ ## Ktor Testing
139
+
140
+ ```kotlin
141
+ class UserRoutesTest {
142
+
143
+ @Test
144
+ fun `POST users returns created for valid request`() = testApplication {
145
+ application {
146
+ configureRouting()
147
+ configureSerialization()
148
+ }
149
+
150
+ val response = client.post("/api/v1/users") {
151
+ contentType(ContentType.Application.Json)
152
+ setBody("""{"name": "Alice", "email": "alice@test.com"}""")
153
+ }
154
+
155
+ assertEquals(HttpStatusCode.Created, response.status)
156
+ val user = response.body<UserResponse>()
157
+ assertEquals("Alice", user.name)
158
+ }
159
+
160
+ @Test
161
+ fun `GET users id returns not found for missing user`() = testApplication {
162
+ application { configureRouting() }
163
+
164
+ val response = client.get("/api/v1/users/nonexistent")
165
+
166
+ assertEquals(HttpStatusCode.NotFound, response.status)
167
+ }
168
+ }
169
+ ```
170
+
171
+ ## Data-Driven Tests (Kotest)
172
+
173
+ ```kotlin
174
+ class SlugifyTest : FunSpec({
175
+ withData(
176
+ "Hello World" to "hello-world",
177
+ " spaces " to "spaces",
178
+ "Special!@#Chars" to "specialchars",
179
+ "" to "",
180
+ ) { (input, expected) ->
181
+ slugify(input) shouldBe expected
182
+ }
183
+ })
184
+
185
+ // Property-based testing
186
+ class MoneyTest : FunSpec({
187
+ test("addition is commutative") {
188
+ checkAll(Arb.positiveLong(), Arb.positiveLong()) { a, b ->
189
+ Money(a, USD) + Money(b, USD) shouldBe Money(b, USD) + Money(a, USD)
190
+ }
191
+ }
192
+ })
193
+ ```
194
+
195
+ ## Anti-Patterns
196
+
197
+ ```kotlin
198
+ // Never: Thread.sleep in tests
199
+ Thread.sleep(5000) // Flaky and slow
200
+ // Use: runTest with advanceTimeBy, or Awaitility
201
+
202
+ // Never: testing internal implementation
203
+ verify { repo.save(any()) } // How, not what
204
+ // Test the observable outcome instead
205
+
206
+ // Never: shared mutable state between tests
207
+ companion object { val testUsers = mutableListOf<User>() }
208
+ // Use @BeforeEach to reset state
209
+
210
+ // Never: ignoring coroutine test infrastructure
211
+ @Test fun `test`() { runBlocking { sut.doWork() } }
212
+ // Use: runTest { } for proper virtual time and dispatcher control
213
+ ```
@@ -0,0 +1,258 @@
1
+ # Kotlin Tooling and Build System
2
+
3
+ Gradle with Kotlin DSL. Static analysis. CI/CD. The full production pipeline.
4
+
5
+ ## Gradle (Kotlin DSL)
6
+
7
+ ```kotlin
8
+ // build.gradle.kts
9
+ plugins {
10
+ kotlin("jvm") version "2.1.0"
11
+ kotlin("plugin.serialization") version "2.1.0"
12
+ id("io.ktor.plugin") version "3.0.0" // For Ktor projects
13
+ id("io.gitlab.arturbosch.detekt") version "1.23.7"
14
+ }
15
+
16
+ kotlin {
17
+ jvmToolchain(21)
18
+ compilerOptions {
19
+ allWarningsAsErrors = true
20
+ freeCompilerArgs.addAll(
21
+ "-Xjsr305=strict", // Strict null-safety for Java interop
22
+ "-Xcontext-receivers", // Enable context receivers (experimental)
23
+ )
24
+ }
25
+ }
26
+
27
+ dependencies {
28
+ // Ktor
29
+ implementation("io.ktor:ktor-server-core")
30
+ implementation("io.ktor:ktor-server-netty")
31
+ implementation("io.ktor:ktor-server-content-negotiation")
32
+ implementation("io.ktor:ktor-serialization-kotlinx-json")
33
+
34
+ // Coroutines
35
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
36
+
37
+ // DI
38
+ implementation("io.insert-koin:koin-ktor")
39
+
40
+ // Database
41
+ implementation("org.jetbrains.exposed:exposed-core")
42
+ implementation("org.jetbrains.exposed:exposed-jdbc")
43
+
44
+ // Testing
45
+ testImplementation(kotlin("test"))
46
+ testImplementation("io.ktor:ktor-server-test-host")
47
+ testImplementation("io.mockk:mockk")
48
+ testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test")
49
+ testImplementation("org.testcontainers:postgresql")
50
+ }
51
+
52
+ tasks.test {
53
+ useJUnitPlatform()
54
+ }
55
+ ```
56
+
57
+ ## Essential Commands
58
+
59
+ ```bash
60
+ # Build and test
61
+ ./gradlew build # Full build + tests
62
+ ./gradlew test # Tests only
63
+ ./gradlew check # Tests + static analysis
64
+
65
+ # Run
66
+ ./gradlew run # Run application
67
+ ./gradlew buildFatJar # Ktor fat JAR
68
+
69
+ # Dependencies
70
+ ./gradlew dependencies # Dependency tree
71
+ ./gradlew dependencyUpdates # Check for updates
72
+
73
+ # Static analysis
74
+ ./gradlew detekt # Detekt analysis
75
+ ./gradlew ktlintCheck # Code formatting check
76
+ ./gradlew ktlintFormat # Auto-format
77
+ ```
78
+
79
+ ## Detekt (Static Analysis)
80
+
81
+ ```yaml
82
+ # detekt.yml
83
+ complexity:
84
+ LongMethod:
85
+ threshold: 30
86
+ ComplexCondition:
87
+ threshold: 4
88
+ TooManyFunctions:
89
+ thresholdInFiles: 20
90
+
91
+ style:
92
+ ForbiddenComment:
93
+ values:
94
+ - "TODO:"
95
+ - "FIXME:"
96
+ - "HACK:"
97
+ allowedPatterns: "TODO\\(#\\d+\\)" # Allow TODO with issue number
98
+ MagicNumber:
99
+ active: true
100
+ ignoreNumbers:
101
+ - "-1"
102
+ - "0"
103
+ - "1"
104
+ - "2"
105
+ MaxLineLength:
106
+ maxLineLength: 120
107
+
108
+ exceptions:
109
+ TooGenericExceptionCaught:
110
+ active: true
111
+ exceptionNames:
112
+ - "Exception"
113
+ - "RuntimeException"
114
+ - "Throwable"
115
+ ```
116
+
117
+ ## kotlinx.serialization
118
+
119
+ ```kotlin
120
+ // Type-safe serialization — no reflection
121
+ @Serializable
122
+ data class CreateUserRequest(
123
+ val name: String,
124
+ val email: String,
125
+ val role: UserRole = UserRole.USER
126
+ )
127
+
128
+ @Serializable
129
+ enum class UserRole { USER, ADMIN, VIEWER }
130
+
131
+ // Custom serializer
132
+ @Serializable(with = InstantSerializer::class)
133
+ data class Event(val name: String, val occurredAt: Instant)
134
+
135
+ object InstantSerializer : KSerializer<Instant> {
136
+ override val descriptor = PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING)
137
+ override fun serialize(encoder: Encoder, value: Instant) = encoder.encodeString(value.toString())
138
+ override fun deserialize(decoder: Decoder): Instant = Instant.parse(decoder.decodeString())
139
+ }
140
+ ```
141
+
142
+ ## Docker
143
+
144
+ ```dockerfile
145
+ FROM gradle:8-jdk21 AS build
146
+ WORKDIR /app
147
+ COPY build.gradle.kts settings.gradle.kts ./
148
+ COPY gradle/ gradle/
149
+ RUN gradle dependencies --no-daemon
150
+
151
+ COPY src/ src/
152
+ RUN gradle buildFatJar --no-daemon
153
+
154
+ FROM eclipse-temurin:21-jre-alpine
155
+ RUN addgroup -S appgroup && adduser -S appuser -G appgroup
156
+ USER appuser
157
+ WORKDIR /app
158
+ COPY --from=build /app/build/libs/*-all.jar app.jar
159
+ EXPOSE 8080
160
+
161
+ ENV JAVA_OPTS="-XX:+UseContainerSupport \
162
+ -XX:MaxRAMPercentage=75.0 \
163
+ -XX:+UseZGC"
164
+
165
+ ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
166
+ ```
167
+
168
+ ## CI/CD (GitHub Actions)
169
+
170
+ ```yaml
171
+ name: CI
172
+
173
+ on:
174
+ push:
175
+ branches: [main]
176
+ pull_request:
177
+ branches: [main]
178
+
179
+ jobs:
180
+ build-and-test:
181
+ runs-on: ubuntu-latest
182
+
183
+ services:
184
+ postgres:
185
+ image: postgres:16-alpine
186
+ env:
187
+ POSTGRES_PASSWORD: test
188
+ POSTGRES_DB: testdb
189
+ ports:
190
+ - 5432:5432
191
+
192
+ steps:
193
+ - uses: actions/checkout@v4
194
+
195
+ - uses: actions/setup-java@v4
196
+ with:
197
+ distribution: temurin
198
+ java-version: 21
199
+
200
+ - uses: gradle/actions/setup-gradle@v4
201
+
202
+ - name: Build and test
203
+ run: ./gradlew check
204
+ env:
205
+ DATABASE_URL: jdbc:postgresql://localhost:5432/testdb
206
+
207
+ - name: Detekt
208
+ run: ./gradlew detekt
209
+
210
+ - name: Upload test results
211
+ if: always()
212
+ uses: actions/upload-artifact@v4
213
+ with:
214
+ name: test-results
215
+ path: build/reports/tests/
216
+ ```
217
+
218
+ ## Logging
219
+
220
+ ```kotlin
221
+ // kotlin-logging (SLF4J wrapper)
222
+ import io.github.oshai.kotlinlogging.KotlinLogging
223
+
224
+ private val logger = KotlinLogging.logger {}
225
+
226
+ // Lazy evaluation — message not constructed if level is disabled
227
+ logger.info { "Processing order ${order.id} with ${order.items.size} items" }
228
+ logger.error(exception) { "Failed to process order ${order.id}" }
229
+
230
+ // MDC for request context
231
+ MDC.put("requestId", requestId)
232
+ MDC.put("userId", userId)
233
+ try {
234
+ processRequest()
235
+ } finally {
236
+ MDC.clear()
237
+ }
238
+ ```
239
+
240
+ ## Anti-Patterns
241
+
242
+ ```kotlin
243
+ // Never: build.gradle (Groovy) for Kotlin projects
244
+ // Use: build.gradle.kts (Kotlin DSL) — type-safe, IDE support
245
+
246
+ // Never: skipping detekt in CI
247
+ // Static analysis catches real bugs and code smells
248
+
249
+ // Never: Jackson for Kotlin (use kotlinx.serialization)
250
+ // Jackson requires reflection and kotlin-module. kotlinx.serialization is compile-time.
251
+
252
+ // Never: JUnit 4 assertions
253
+ assertEquals(expected, actual) // No message, confusing order
254
+ // Use: assertThat(actual).isEqualTo(expected) // AssertJ, readable
255
+
256
+ // Never: hardcoded versions scattered across modules
257
+ // Use: version catalogs (gradle/libs.versions.toml)
258
+ ```