From skainet-contributor-skills
Use ONLY when configuring KMP targets, source-set hierarchies, or `expect`/`actual` placement in a module INSIDE the SKaiNET repository. Trigger tokens include `kotlin { ... }`, `iosArm64()`, `commonMain`, `jvmMain`, `expect fun`, `actual fun`, `androidNative`, `wasmJs`, source-set dependency edits in `SKaiNET/skainet-*/build.gradle.kts`. Do NOT fire on KMP setup in a CONSUMER project (an app that depends on SKaiNET) — those concerns are simpler and live in `skainet-consumer-setup`.
How this skill is triggered — by the user, by Claude, or both
Slash command
/skainet-contributor-skills:kmpThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Rules for configuring Kotlin Multiplatform targets and source-set hierarchies in SKaiNET. Pure-logic code lives in `commonMain` and runs on every target; platform-specific implementations are confined to the narrowest source-set that needs them.
Rules for configuring Kotlin Multiplatform targets and source-set hierarchies in SKaiNET. Pure-logic code lives in commonMain and runs on every target; platform-specific implementations are confined to the narrowest source-set that needs them.
commonMain / jvmMain / a native target / a JS or WASM target.expect / actual declarations.dependencies { } blocks per source set.gradle-multimodule.kotlin.jvmMain/sk.ainet.java) — coordinate with skainet-java-interop.skainet-lang-core's set unless there's a documented reason not to):
jvm()android { namespace = "sk.ainet.<area>"; compileSdk = libs.versions.android.compileSdk.get().toInt(); minSdk = libs.versions.android.minSdk.get().toInt(); compilerOptions { jvmTarget.set(JvmTarget.JVM_11) } }iosArm64(), iosSimulatorArm64()macosArm64()linuxX64(), linuxArm64()androidNativeArm32(), androidNativeArm64() (vendor-native backends)js { browser() }@OptIn(ExperimentalWasmDsl::class) wasmJs { browser() }, @OptIn(ExperimentalWasmDsl::class) wasmWasi { nodejs() }expect lives in commonMain. actual lives in the narrowest source set that needs the platform API — jvmMain for java.io, iosArm64Main for an iOS-specific call, etc.commonMain is the default home for Kotlin code. Move a file out only when it imports a platform API that commonMain cannot resolve.commonTest uses kotlin.test. jvmTest may add Kotest. Kotest's runner is JVM-only — never import it from a commonTest source set.commonMain.dependencies { }, jvmMain.dependencies { }), or the sourceSets { commonMain { dependencies { ... } } } form. Don't add dependencies to the bare dependencies { } block at the top level of a KMP module — that's a JVM-only Gradle pattern.api(...) only when the dependency's types appear in the public Kotlin signatures of the consuming source set. Otherwise implementation(...). The KSP annotations module is a deliberate api(...) because KSP-generated code references those annotations from commonMain.commonMain are added explicitly to commonMain.kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin"). The tasks.configureEach { … dependsOn("kspCommonMainKotlinMetadata") } block at the bottom of the build script is required — copy it verbatim from skainet-lang-core/build.gradle.kts:72-77 when KSP is involved.iosMain aggregating iosArm64Main + iosSimulatorArm64Main; nativeMain aggregating all native targets) only when at least two siblings actually share code.build.gradle.kts and copy its target list — divergence from the canonical set MUST be justified in the change description.commonMain.jvmMain, iosArm64Main, wasmJsMain).expect in commonMain, actual in each platform source set that the module targets.commonMain.dependencies { }, etc.).commonTest for cross-target, jvmTest for JVM-only tooling like Kotest)../gradlew :module:assemble to validate every target compiles. If a Native target fails, the source set probably leaked a JVM API — move it to jvmMain.Full target list with explicit-API mode:
kotlin {
explicitApi()
android {
namespace = "sk.ainet.lang.core"
compileSdk = libs.versions.android.compileSdk.get().toInt()
minSdk = libs.versions.android.minSdk.get().toInt()
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
}
iosArm64()
iosSimulatorArm64()
macosArm64()
linuxX64()
linuxArm64()
androidNativeArm32()
androidNativeArm64()
jvm()
js {
browser()
}
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
browser()
}
@OptIn(ExperimentalWasmDsl::class)
wasmWasi {
nodejs()
}
// ... source sets ...
}
// from: SKaiNET/skainet-lang/skainet-lang-core/build.gradle.kts:14-50
Per-source-set dependencies and KSP-generated source folder:
sourceSets {
commonMain {
kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin")
dependencies {
api(project(":skainet-lang:skainet-lang-ksp-annotations"))
}
}
jvmMain.dependencies {
implementation(libs.kotlinx.benchmark.runtime)
}
commonTest.dependencies {
implementation(libs.kotlin.test)
}
}
// from: SKaiNET/skainet-lang/skainet-lang-core/build.gradle.kts:52-68
KSP wiring required when commonMain consumes generated code:
tasks.configureEach {
if (name != "kspCommonMainKotlinMetadata" &&
(name.startsWith("compileKotlin") || name.startsWith("ksp") || name.contains("ourcesJar"))) {
dependsOn("kspCommonMainKotlinMetadata")
}
}
dependencies {
add("kspCommonMainMetadata", project(":skainet-lang:skainet-lang-ksp-processor"))
}
// from: SKaiNET/skainet-lang/skainet-lang-core/build.gradle.kts:72-83
libs.plugins.kotlinMultiplatform, libs.kotlinx.io.core) and module registration — see ../gradle-multimodule/SKILL.md.../kotlin/SKILL.md.jvmMain under sk/ainet/java/ — see ../skainet-java-interop/SKILL.md.// WRONG — top-level dependencies block on a KMP module
dependencies {
implementation(libs.kotlinx.io.core)
}
// RIGHT — per-source-set
sourceSets {
commonMain.dependencies { implementation(libs.kotlinx.io.core) }
}
// WRONG — `actual` in commonMain
// commonMain/.../FileLoader.kt
public actual fun loadModelFile(path: String): ByteArray = TODO() // commonMain has no platform API
// RIGHT — expect in commonMain, actual in jvmMain
// commonMain/.../FileLoader.kt
public expect fun loadModelFile(path: String): ByteArray
// jvmMain/.../FileLoader.kt
public actual fun loadModelFile(path: String): ByteArray = java.io.File(path).readBytes()
// WRONG — Kotest in commonTest
// commonTest/.../FooSpec.kt
import io.kotest.core.spec.style.StringSpec // Kotest runner is JVM-only
// RIGHT — Kotest in jvmTest, kotlin.test in commonTest
// commonTest/.../FooTest.kt
import kotlin.test.Test
// jvmTest/.../FooSpec.kt
import io.kotest.core.spec.style.StringSpec
references/target-matrix.md — every KMP target SKaiNET ships, what it's used for, and the per-target source-set name.references/sourceset-rules.md — the commonMain → platform source-set hierarchy and expect/actual placement decision tree.npx claudepluginhub skainet-developers/skainet-coding-skills --plugin skainet-contributor-skillsProvides a checklist for code reviews covering functionality, security, performance, maintainability, tests, and quality. Use for pull requests, audits, team standards, and developer training.