From skainet-contributor-skills
Use ONLY when editing Kotlin (`.kt`) source files INSIDE the SKaiNET repository (i.e. you're contributing to SKaiNET itself, not consuming it as a library). Enforces project idioms: explicit-API mode, package layout under `sk.ainet.*`, sealed hierarchies, `value class` for type-safe wrappers, no Java-style getters in Kotlin code. Do NOT fire when the user is writing application code that depends on SKaiNET as a library — that's the consumer plugin's territory. Does not fire on DSL invocation sites, build files, or test files within SKaiNET either (those are covered by other contributor skills).
How this skill is triggered — by the user, by Claude, or both
Slash command
/skainet-contributor-skills:kotlinThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Idiomatic Kotlin coding rules for production code in SKaiNET. Covers package layout, API stability, nullability, sealed hierarchies, and the `expect`/`actual` boundary. Style topics that are project-specific.
Idiomatic Kotlin coding rules for production code in SKaiNET. Covers package layout, API stability, nullability, sealed hierarchies, and the expect/actual boundary. Style topics that are project-specific.
.kt file under any module's commonMain/, jvmMain/, or platform-specific main source set.@PublishedApi discipline).data class, value class, sealed class, sealed interface, or object.tensor { }, pipeline<...>(), sequential<...> { }, or dag { } block — those are the DSL skills.build.gradle.kts, settings.gradle.kts, libs.versions.toml, or files under build-logic/ — that's gradle-multimodule.skainet-testing.kmp.explicitApi() is on for every kotlin { } block in this project. Every public top-level declaration MUST carry an explicit visibility modifier (public, internal, private). Do not write fun foo() at top level — write public fun foo() or internal fun foo().sk.ainet.<area>[.<sub-area>]. New files MUST live under src/<sourceset>/kotlin/sk/ainet/.... Do not introduce new top-level packages.val, var) — never getFoo() / setFoo() on a Kotlin class. Java consumers reach Kotlin through the dedicated facades in sk/ainet/java/ (covered by skainet-java-interop).value class for any wrapper around a primitive that has semantic meaning (e.g. tensor IDs, layer names, axis indices). Not for things that need equality on multiple fields — those are data class.sealed class / sealed interface. Do not use enum class if the variants carry data (see InitializationType in TensorDSL.kt).T? type means "callers must handle absence." Never use !! to silence the compiler outside a documented invariant — prefer requireNotNull(x) { "<reason>" } so the failure is loud.CoroutineScope provided by the caller; never launch into GlobalScope. Hot streams use Flow; cold one-shot APIs use suspend functions.*.api changed, regenerate the dump (./gradlew apiDump) and own the change in the same commit — don't suppress the check.@PublishedApi internal is the only way to expose internals to inline functions. Don't widen visibility just to satisfy inline fun.sk.ainet.<area> package the change belongs to. Don't create a new package without strong justification.skainet-java-interop for the facade — keep the Kotlin definition idiomatic.*.api dump, regenerate it (./gradlew :module:apiDump) and include the diff in the same change.Explicit API + sealed hierarchy (used for tensor initialisation):
public sealed class InitializationType<out V> {
public object Zeros : InitializationType<Nothing>()
public object Ones : InitializationType<Nothing>()
public data class Fill<V>(val value: Number) : InitializationType<V>()
public data class Normal<V>(val mean: Float, val std: Float, val random: Random) : InitializationType<V>()
public data class Uniform<V>(val min: Float, val max: Float, val random: Random) : InitializationType<V>()
public data class Custom<V>(val generator: (indices: IntArray) -> V) : InitializationType<V>()
public data class RandomCustom<V>(val generator: (random: Random) -> V, val random: Random) :
InitializationType<V>()
}
// from: SKaiNET/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/dsl/TensorDSL.kt:272-281
Data-shape DSL with explicit visibility on every entry point:
@TensorDsl
public fun <T : DType, V> tensor(
executionContext: ExecutionContext,
dtype: KClass<T>,
content: TensorDefineDsl<T, V>.() -> Tensor<T, V>
): Tensor<T, V> { ... }
// from: SKaiNET/skainet-lang/skainet-lang-core/src/commonMain/kotlin/sk/ainet/lang/tensor/dsl/TensorDSL.kt:17-25
Module-level KMP plugins (so explicit-API is enforced):
kotlin {
explicitApi()
// ... targets ...
}
// from: SKaiNET/skainet-lang/skainet-lang-core/build.gradle.kts:14-16
commonMain vs jvmMain vs iosArm64Main) — see ../kmp/SKILL.md.../gradle-multimodule/SKILL.md.../skainet-java-interop/SKILL.md.// WRONG — implicit visibility, will fail explicit-API check
fun computeDiff(expected: Float, actual: Float): Float = expected - actual
// RIGHT
public fun computeDiff(expected: Float, actual: Float): Float = expected - actual
// WRONG — Java-style accessors on a Kotlin class
public class Layer { fun getInChannels(): Int = inChannels }
// RIGHT — Kotlin property
public class Layer { public val inChannels: Int get() = ... }
// WRONG — variants with payload as enum
public enum class InitKind { ZEROS, ONES, FILL /* value? */ }
// RIGHT — sealed hierarchy carries data
public sealed class InitializationType<out V> { ... }
// WRONG — `!!` silently asserts a hidden invariant
val x = map[id]!!
// RIGHT — surface the invariant in the failure message
val x = requireNotNull(map[id]) { "missing layer id=$id" }
references/style-rules.md — explicit-API, package, naming, KDoc rules with one-line examples.references/api-stability.md — binary-compatibility-validator workflow and @PublishedApi discipline.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.