Kotlin
Execute these commands after EVERY implementation (see AGENT_AUTOMATION module for full workflow).
11 stars
byhivellm
Best use case
Kotlin is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Execute these commands after EVERY implementation (see AGENT_AUTOMATION module for full workflow).
Teams using Kotlin should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
$curl -o ~/.claude/skills/kotlin/SKILL.md --create-dirs "https://raw.githubusercontent.com/hivellm/rulebook/main/templates/skills/languages/kotlin/SKILL.md"
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/kotlin/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How Kotlin Compares
| Feature / Agent | Kotlin | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Execute these commands after EVERY implementation (see AGENT_AUTOMATION module for full workflow).
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
<!-- KOTLIN:START -->
# Kotlin Project Rules
## Agent Automation Commands
**CRITICAL**: Execute these commands after EVERY implementation (see AGENT_AUTOMATION module for full workflow).
```bash
# Complete quality check sequence (Gradle):
./gradlew ktlintCheck # Format check
./gradlew detekt # Linting
./gradlew test # All tests (100% pass)
./gradlew build # Build verification
./gradlew koverVerify # Coverage (95%+ required)
# Security audit:
./gradlew dependencyCheckAnalyze # Vulnerability scan
./gradlew dependencyUpdates # Check outdated deps
```
## Kotlin Configuration
**CRITICAL**: Use Kotlin 2.0+ with strict null safety.
- **Version**: Kotlin 2.0+
- **JVM Target**: 17+
- **Language Features**: All enabled
- **Compiler**: K2 compiler
- **Null Safety**: Strict
### build.gradle.kts Requirements
```kotlin
plugins {
kotlin("jvm") version "2.0.0"
id("org.jetbrains.dokka") version "1.9.20"
id("io.gitlab.arturbosch.detekt") version "1.23.5"
id("org.jlleitschuh.gradle.ktlint") version "12.1.0"
`maven-publish`
signing
}
group = "io.github.your-username"
version = "1.0.0"
repositories {
mavenCentral()
}
dependencies {
implementation(kotlin("stdlib"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
testImplementation(kotlin("test"))
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0")
testImplementation("io.mockk:mockk:1.13.9")
}
kotlin {
jvmToolchain(17)
compilerOptions {
freeCompilerArgs.add("-Xjsr305=strict")
freeCompilerArgs.add("-Xcontext-receivers")
allWarningsAsErrors.set(true)
}
}
tasks.test {
useJUnitPlatform()
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions {
jvmTarget = "17"
freeCompilerArgs = listOf(
"-Xjsr305=strict",
"-Xcontext-receivers"
)
allWarningsAsErrors = true
}
}
detekt {
config.setFrom(files("$rootDir/detekt.yml"))
buildUponDefaultConfig = true
allRules = false
}
ktlint {
version.set("1.1.0")
android.set(false)
ignoreFailures.set(false)
}
```
## Code Quality Standards
### Mandatory Quality Checks
**CRITICAL**: After implementing ANY feature, you MUST run these commands in order.
**IMPORTANT**: These commands MUST match your GitHub Actions workflows to prevent CI/CD failures!
```bash
# Pre-Commit Checklist (MUST match .github/workflows/*.yml)
# 1. Format check (matches workflow - use Check, not Format!)
./gradlew ktlintCheck
# 2. Lint (matches workflow)
./gradlew detekt
# 3. Build (MUST pass with no warnings - matches workflow)
./gradlew build -x test
# 4. Run all tests (MUST pass 100% - matches workflow)
./gradlew test
# 5. Check coverage (MUST meet threshold)
./gradlew koverVerify
# If ANY fails: ❌ DO NOT COMMIT - Fix first!
```
**If ANY of these fail, you MUST fix the issues before committing.**
**Why This Matters:**
- CI/CD failures happen when local commands differ from workflows
- Example: Using `ktlintFormat` locally but `ktlintCheck` in CI = failure
- Example: Using `koverHtmlReport` locally but `koverVerify` in CI = coverage failures
### Code Style
Use ktlint with `.editorconfig`:
```ini
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
max_line_length = 120
tab_width = 4
[*.{kt,kts}]
ij_kotlin_allow_trailing_comma = true
ij_kotlin_allow_trailing_comma_on_call_site = true
# Imports
ij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,^
# Wrapping
ij_kotlin_line_break_after_multiline_when_entry = true
ij_kotlin_wrap_expression_body_functions = 1
ij_kotlin_wrap_first_method_in_call_chain = false
# Spacing
ij_kotlin_space_after_type_colon = true
ij_kotlin_space_before_type_colon = false
```
### Static Analysis
Use Detekt. Configuration in `detekt.yml`:
```yaml
build:
maxIssues: 0
weights:
complexity: 2
LongParameterList: 1
style: 1
comments: 1
complexity:
active: true
ComplexMethod:
threshold: 15
LongMethod:
threshold: 60
LongParameterList:
functionThreshold: 6
TooManyFunctions:
thresholdInFiles: 15
naming:
active: true
FunctionNaming:
active: true
ClassNaming:
active: true
VariableNaming:
active: true
style:
active: true
MagicNumber:
active: true
ReturnCount:
max: 3
coroutines:
active: true
GlobalCoroutineUsage:
active: true
SuspendFunWithFlowReturnType:
active: true
```
### Testing
- **Framework**: JUnit 5 (Jupiter)
- **Mocking**: MockK
- **Coroutines**: kotlinx-coroutines-test
- **Coverage**: Kover
- **Coverage Threshold**: 95%+
Example test:
```kotlin
import io.mockk.*
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import kotlin.test.assertEquals
class DataProcessorTest {
@Test
fun `process valid input returns uppercase`() {
val processor = DataProcessor()
val result = processor.process("hello")
assertEquals("HELLO", result)
}
@Test
fun `process empty input throws exception`() {
val processor = DataProcessor()
assertThrows<IllegalArgumentException> {
processor.process("")
}
}
@Test
fun `processAsync works correctly`() = runTest {
val processor = DataProcessor()
val result = processor.processAsync("test")
assertEquals("TEST", result)
}
@Test
fun `test with mocking`() {
val repository = mockk<UserRepository>()
every { repository.findById(1) } returns User(1, "John")
val service = UserService(repository)
val user = service.getUser(1)
assertEquals("John", user?.name)
verify { repository.findById(1) }
}
}
```
### Null Safety
- Use non-null types by default
- Use `?` for nullable types
- Use safe calls `?.` and Elvis operator `?:`
- Avoid `!!` operator (use only when absolutely necessary)
Example:
```kotlin
data class User(
val id: Int,
val name: String,
val email: String?,
val phone: String? = null
)
class UserService(private val repository: UserRepository) {
fun findUser(id: Int): User? {
return repository.findById(id)
}
fun getUserName(id: Int): String {
val user = findUser(id) ?: throw UserNotFoundException(id)
return user.name
}
fun getUserEmail(id: Int): String {
val user = findUser(id) ?: return "unknown@example.com"
return user.email ?: "no-email@example.com"
}
fun processUsers(ids: List<Int>): List<String> {
return ids.mapNotNull { id ->
findUser(id)?.name
}
}
}
```
### Coroutines
- Use structured concurrency
- Prefer `suspend` functions over callbacks
- Use `Flow` for reactive streams
- Handle cancellation properly
Example:
```kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
class DataService(private val api: ApiClient) {
suspend fun fetchData(id: Int): Result<Data> = withContext(Dispatchers.IO) {
try {
val data = api.getData(id)
Result.success(data)
} catch (e: Exception) {
Result.failure(e)
}
}
fun observeData(id: Int): Flow<Data> = flow {
while (currentCoroutineContext().isActive) {
val data = api.getData(id)
emit(data)
delay(1000)
}
}.flowOn(Dispatchers.IO)
suspend fun fetchMultiple(ids: List<Int>): List<Data> = coroutineScope {
ids.map { id ->
async { fetchData(id).getOrNull() }
}.awaitAll().filterNotNull()
}
}
```
### Data Classes & Sealed Classes
- Use `data class` for value objects
- Use `sealed class`/`sealed interface` for restricted hierarchies
- Use `value class` for single-property wrappers
Example:
```kotlin
// Data class for DTOs
data class User(
val id: Int,
val name: String,
val email: String
)
// Sealed hierarchy for results
sealed interface Result<out T> {
data class Success<T>(val data: T) : Result<T>
data class Error(val exception: Exception) : Result<Nothing>
data object Loading : Result<Nothing>
}
// Value class for type safety
@JvmInline
value class UserId(val value: Int)
@JvmInline
value class Email(val value: String) {
init {
require(value.contains("@")) { "Invalid email format" }
}
}
```
### Documentation
- Use KDoc for documentation
- Document all public APIs
- Include examples with `@sample`
Example:
```kotlin
/**
* A processor that transforms input data.
*
* This class provides methods for processing strings synchronously and asynchronously.
* All processing is done in a thread-safe manner.
*
* @property config Configuration for the processor
* @constructor Creates a processor with the given configuration
*/
class DataProcessor(private val config: ProcessorConfig = ProcessorConfig()) {
/**
* Processes the input string synchronously.
*
* @param input The string to process. Must not be empty.
* @return The processed string in uppercase.
* @throws IllegalArgumentException if [input] is empty.
* @sample samples.DataProcessorSamples.processExample
*/
fun process(input: String): String {
require(input.isNotEmpty()) { "Input cannot be empty" }
return input.uppercase()
}
/**
* Processes the input string asynchronously.
*
* This is a suspending function that can be called from a coroutine.
*
* @param input The string to process.
* @return The processed string in uppercase.
*/
suspend fun processAsync(input: String): String = withContext(Dispatchers.Default) {
process(input)
}
}
// Sample code for documentation
object samples {
object DataProcessorSamples {
fun processExample() {
val processor = DataProcessor()
val result = processor.process("hello")
println(result) // Prints: HELLO
}
}
}
```
## Project Structure
```
project/
├── build.gradle.kts # Gradle build configuration
├── settings.gradle.kts # Gradle settings
├── detekt.yml # Detekt configuration
├── .editorconfig # EditorConfig for ktlint
├── README.md # Project overview (allowed in root)
├── CHANGELOG.md # Version history (allowed in root)
├── LICENSE # Project license (allowed in root)
├── src/
│ ├── main/
│ │ └── kotlin/
│ │ └── com/yourorg/yourproject/
│ │ └── YourClass.kt
│ └── test/
│ └── kotlin/
│ └── com/yourorg/yourproject/
│ └── YourClassTest.kt
└── docs/ # Project documentation
```
## CI/CD Requirements
Must include GitHub Actions workflows for:
1. **Testing** (`kotlin-test.yml`):
- Test on ubuntu-latest, windows-latest
- Test on Java 17, 21
- Upload coverage reports
2. **Linting** (`kotlin-lint.yml`):
- Detekt: `./gradlew detekt`
- ktlint: `./gradlew ktlintCheck`
- Build with warnings as errors
## Package Publication
### Publishing to Maven Central
Same process as Java (see JAVA.md), but with Kotlin-specific configuration.
**Publishing Checklist:**
- ✅ All tests passing
- ✅ Detekt passes
- ✅ ktlint passes
- ✅ Build succeeds with warnings as errors
- ✅ Version updated in build.gradle.kts
- ✅ CHANGELOG.md updated
- ✅ README.md up to date
- ✅ LICENSE file present
- ✅ Dokka documentation generated
- ✅ Artifacts signed with GPG
**Dokka Documentation:**
```kotlin
tasks.dokkaHtml.configure {
outputDirectory.set(buildDir.resolve("dokka"))
}
```
<!-- KOTLIN:END -->Related Skills
We are still matching the closest adjacent skills for this page. In the meantime, continue through the full directory.