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
Teaches AI coding agents to write idiomatic Kotlin instead of Java-in-a-.kt-file — AND to make the right stack choices on JVM. Thirteen alwaysApply rules cover both the highest-leverage Kotlin idioms and the canonical library + tooling defaults (Kotlin 2.3, JDK 21, Gradle Kotlin DSL, Ktor, coroutines, DJL, JavaCV, Koog). Four skills handle the common conversions and an API-design review covering the situational guidance that doesn't earn always-on context cost, plus a CI script that gates the JUnit-to-Kotest migration deterministically.
A coding agent without Kotlin context produces one of two failures:
requests, because that's the median answer in training data — even when you're in a Kotlin Gradle project.class instead of data class, manual equals / hashCode, var properties everywhere, Optional<String> for nullables, JUnit assertions in test files. Compiles, runs, but it's Java in a .kt file.One tessl install closes both gaps. The agent learns: (a) the JVM has first-class Kotlin libraries for the tasks you actually need (Ktor for HTTP, DJL for ML, JavaCV for vision, Koog for agents) so the answer to "turn on my bulb" is a Kotlin program, not a Python script; and (b) when it writes that Kotlin, it writes idiomatic Kotlin.
tessl install jbaruch/kotlin-tutor.kt| ID | Rule | Summary |
|---|---|---|
| K-1 | prefer-val | Properties default to val; var is opt-in for mutation |
| K-2 | nullable-question-mark | String? not Optional<String>; lean on ?. and ?: |
| K-3 | use-data-class | Value types are data class; never hand-roll equals/hashCode/toString |
| K-4 | kotest-over-junit | Tests use Kotest matchers and specs, not assertEquals and @Test |
| K-5 | prefer-stdlib-scope | Use .let / .also / .apply over imperative Java patterns |
| K-6 | extension-over-util | Extension functions, not *Utils classes with static methods |
| K-7 | stateflow-over-atomic-polling | MutableStateFlow<T?> (or MutableStateFlow<T> with a default) for single-writer / many-reader reactive state — not AtomicReference<T?> + a delay-poll loop |
The idiom rules govern application and internal code — the per-line idioms worth their always-on context cost. Broader API-design judgement (boolean args, read-only collections, validation, sealed hierarchies, toString, backward compatibility, multiplatform, documentation) is situational, so it lives in the kotlin-api-review skill rather than always-on rules. Two rules — use-data-class (K-3) and nullable-question-mark (K-2) — carry a one-line public-API exception that hands off to that skill, because their always-on advice can break binary compatibility on a published surface.
| ID | Rule | Summary |
|---|---|---|
| S-1 | kotlin-stack-defaults | Kotlin 2.3, JDK 21, Gradle Kotlin DSL — these are the defaults, not optional |
| S-2 | coroutines-for-concurrency | kotlinx-coroutines over Thread/Future/RxJava; Dispatchers.IO for I/O, Default for CPU |
| S-3 | ktor-for-http | Ktor client + server (CIO engine) for HTTP — not OkHttp, not java.net.HttpURLConnection |
| S-4 | djl-for-jvm-ml | DJL for on-JVM ML inference — not Python subprocesses, not hand-rolled JNI |
| S-5 | javacv-for-vision | JavaCV (OpenCV bindings) for camera + image processing — not cv2 via subprocess |
| S-6 | koog-for-agents | Koog for AI agent orchestration on JVM — not Python-only frameworks |
| Skill | Description |
|---|---|
| kotlinify-tests | Convert JUnit-style test classes to idiomatic Kotest specs; delegates to verify-no-junit-assertions.sh for the deterministic gate |
| pojoify-to-dataclass | Refactor a Java-style POJO (manual equals/hashCode/toString, bean accessors) into an idiomatic Kotlin data class |
| nullable-cleanup | Replace Optional<T> with T? and the ?. / ?: / ?.let { } operators |
| kotlin-api-review | Review an API you're designing or exposing — function, class, or published library — for design concerns: simplicity, readability, consistency, predictability, debuggability, testability, plus (published surfaces only) backward compatibility, multiplatform, documentation |
| Script | Purpose |
|---|---|
| verify-no-junit-assertions.sh | Fails CI if any test file under src/test/ still uses JUnit-style assertions. Delegated from the kotlinify-tests skill; runs standalone as a CI gate. Enforces rule K-4. |
| Scenario | Targets skill | Purpose |
|---|---|---|
| scenario-0, scenario-1, scenario-2 | kotlinify-tests | Real evals (auto-generated, curated to remove bleeding) |
| scenario-pojoify-subscription | pojoify-to-dataclass | Java-style POJO → idiomatic data class refactor |
| scenario-nullable-user-service | nullable-cleanup | Optional<T> API → Kotlin nullable types, caller updated alongside |
| trial-1-task-leaks-language | — | Pedagogical — slide-59 demo, task leaks language, baseline 100% |
| trial-2-rubric-grades-features | — | Pedagogical — slide-59 demo, rubric still grades features only, baseline still 100% |
| trial-3-rubric-weights-language | — | Pedagogical — slide-59 demo, rubric weights idiomatic Kotlin 80%, lift becomes measurable |
The pedagogical scenarios are intentionally bad eval design — they ship publicly so audiences of the talk "You're Absolutely Right (and Other Lies My AI Told Me)" can browse them in the registry and see why the failures land at 100% / 100% / ~20%-then-~95%.
The kotlinify-tests skill demonstrates the script-delegation pattern in miniature: the skill does the judgment work (which Kotest matcher fits this JUnit call? what spec style preserves the original structure?), and verify-no-junit-assertions.sh does the deterministic gate (did any JUnit assertion slip through?). Judgment lives in the skill; mechanism lives in the shell. The script also runs when the agent isn't present — human commits go through the same gate in CI.
alwaysApply: true); agents see them on every promptdescription fieldK-1 through K-7, S-1 through S-6) are talk-shorthand for the descriptive filenames; the registry uses the filenamesSee CHANGELOG.md for version history.