Teaches AI agents to write idiomatic Kotlin (data classes, val, scope fns, Kotest) AND to make the right stack choices on JVM: Kotlin 2.3 + JDK 21 + Gradle Kotlin DSL, Ktor for HTTP, kotlinx-coroutines, DJL for ML inference, JavaCV for vision, Koog for AI agent orchestration.
95
95%
Does it follow best practices?
Impact
95%
1.23xAverage score across 10 eval scenarios
Passed
No known issues
Process steps in order. Do not skip ahead.
Before (Java-style POJO):
class Customer {
var customerId: String? = null
var email: String? = null
fun getCustomerId(): String? = customerId
fun setCustomerId(value: String?) { customerId = value }
fun getEmail(): String? = email
fun setEmail(value: String?) { email = value }
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Customer) return false
return customerId == other.customerId && email == other.email
}
override fun hashCode(): Int = 31 * (customerId?.hashCode() ?: 0) + (email?.hashCode() ?: 0)
override fun toString(): String = "Customer(customerId=$customerId, email=$email)"
}After (idiomatic Kotlin):
data class Customer(
val customerId: String? = null,
val email: String? = null
)A class is a candidate when it has all of:
equals, hashCode, AND toString — all threeIf any of these are missing, stop and report what you found. Not every class with equals/hashCode is a data class candidate — some have legitimate inheritance or behaviour.
A data class is the wrong choice on a published API: the generated constructor, copy(), and componentN() signatures change when properties are added or reordered, breaking binary and behavioural compatibility. This is the public-API exception called out in the use-data-class rule.
So before converting, check whether the type is part of a stable public/binary API (library surface, multiplatform module, anything other consumers depend on). If it is, flag it and prefer keeping a regular class — or convert only with the operator's explicit OK and a compatibility plan; the kotlin-api-review skill covers the full public-API checklist. Internal / application-only types are safe to convert.
data class primary constructor parameters MUST appear in the same order — otherwise existing componentN() destructuring breaks for callersgetCustomerId → customerId)class Foo { val a: String; val b: Int; … } to data class Foo(val a: String, val b: Int)var to val UNLESS the field has a documented reason to mutate (flag any retained var in the conversion summary)val derived: T get() = …) into the class body, after the primary constructorequals, hashCode, and toString — the compiler generates them nowtoString was overridden to MASK a sensitive field (passwords, tokens), keep ONLY toString as a manual override and delete equals and hashCodeinit blocks that only validated equality contract assumptionsfoo.setX(value) with foo.copy(x = value) — the idiomatic mutation pattern from rule use-data-classFoo(a, b)) keeps working if Step 2 preserved field orderFinish here. Do not commit — that's the operator's call.