Agent SkillsJetBrains/koog › split-jvm-nonjvm

split-jvm-nonjvm

GitHub

将Kotlin多平台项目commonMain中的类拆分为JVM和非JVM部分,生成API接口、实现类及expect声明,以支持JVM特定功能。

.claude/skills/split-jvm-nonjvm/SKILL.md JetBrains/koog

Trigger Scenarios

需要将通用类分离为JVM和非JVM实现 为Kotlin多平台项目添加JVM特定逻辑

Install

npx skills add JetBrains/koog --skill split-jvm-nonjvm -g -y
More Options

Non-standard path

npx skills add https://github.com/JetBrains/koog/tree/develop/.claude/skills/split-jvm-nonjvm -g -y

Use without installing

npx skills use JetBrains/koog@split-jvm-nonjvm

指定 Agent (Claude Code)

npx skills add JetBrains/koog --skill split-jvm-nonjvm -a claude-code -g -y

安装 repo 全部 skill

npx skills add JetBrains/koog --all -g -y

预览 repo 内 skill

npx skills add JetBrains/koog --list

SKILL.md

Frontmatter
{
    "name": "split-jvm-nonjvm",
    "description": "Splits a class or several classes in the common source set commonMain into JVM and non-JVM parts into jvmCommonMain and nonJvmCommonMain. \nThis is used to provide some JVM-specific functionality for a class on JVM targets (e.g. Kotlin\/JVM or Java backend or Android)."
}

The following example shows how to split a class into JVM and non-JVM parts. When using this skill, avoid unnecessarily looking at existing implementations of the split in the project, just follow this instruction. You can, however, check existing implementations for reference if you have a complex case or otherwise stuck.

Here's a sample class in a commonMain source set

package com.example

/**
* A sample class with an external dependency, some logic and public methods
 * @param name Some public property
 * @param myRepo Some external dependency, private property
*/
public class MyClass(
    public val name: String,
    private val myRepo: MyRepo,
) {
    /**
     * Public API method
     */
    public fun doSomething(): String {
        return fetchData()
    }
    
    /**
     * Private implementation logic
     */
    private fun fetchData(): String {
        return myRepo.getData()
    }
    
    /*
     * Internal method
     */
    internal fun doInternal(): String {
        return "Internal"
    }
}

In the same package as MyClass in commonMain, create the following files. The original MyClass.kt file will be overwritten with the expect class (see step three).

First, the API interface describing the contract. It should contain only public symbols from the MyClass. Take KDocs from the respective symbols in MyClass and add them to the interface.

package com.example

/**
 * API for [MyClass]
 */
public interface MyClassAPI {
    /**
     * Some public property
     */
    public val name: String
    
    /**
     * Public API method
     */
    public fun doSomething(): String
}

Second, MyClassImpl.kt — implementation of the API interface providing the implementation logic. The filename matches the class name.

internal class MyClassImpl(
    override val name: String,
    private val myRepo: MyRepo,
) : MyClassAPI {
    override fun doSomething(): String {
        return fetchData()
    }
    
    private fun fetchData(): String {
        return myRepo.getData()
    }
    
    internal fun doInternal(): String {
        return "Internal"
    }
}

Third, the original class becomes expect class MyClass should extend MyClassAPI and override all public symbols from MyClassAPI, without method bodies and property values, since expect classes can't have any. If MyClass had any internal symbols, these should also be listed in the expect class along with their KDocs. If MyClass had a KDoc, add it on top of the expect class without parameters/properties description. If MyClass original KDoc described certain parameters/properties that it accepted in its constructor, add these to KDoc of the expect class secondary constructor. This constructor KDoc can't have @property tag for parameters, use only @param for all parameters, even if the original was @property.

package com.example

/**
 * A sample class with an external dependency, some logic and public methods
 */
public expect class MyClass internal constructor(
    delegate: MyClassImpl,
) : MyClassAPI {
    /**
     * Constructs a new instance of [MyClass]
     * 
     * @param name Some public property
     * @param myRepo Some external dependency,
     */
    public constructor(
        name: String,
        myRepo: MyRepo,
    )
    
    internal val delegate: MyClassImpl
    
    override val name: String
    override fun doSomething(): String
    
    /**
     * Internal method
     */
    internal fun doInternal(): String
}

Fourth, ensure that jvmCommonMain and nonJvmCommonMain source sets have a package directory for MyClass, i.e., com.example. If not, create missing package directories. The source sets themselves are already declared in the Gradle convention plugin — you do NOT need to add them to the module's build.gradle.kts unless you need to add dependencies specifically for these source sets. Then, create MyClass.kt files in jvmCommonMain and nonJvmCommonMain source sets.

After these steps, the structure should look as follows, omitting irrelevant packages and files:

commonMain/
    kotlin/
        com/example/
            MyClass.kt
            MyClassAPI.kt
            MyClassImpl.kt
jvmCommonMain/
    kotlin/
        com/example/
            MyClass.kt
nonJvmCommonMain/
    kotlin/
        com/example/
            MyClass.kt

Fifth, in MyClass.kt files in jvmCommonMain and nonJvmCommonMain create actual declarations for MyClass. Initially, they should be identical for both source sets. It should use by delegate to delegate all public symbols from MyClassAPI to the implementation, and direct delegation to delegate internal symbols.

// Suppress IntelliJ warnings
@file:Suppress("MissingKDocForPublicAPI")

package com.example

public actual class MyClass internal actual constructor(
    internal actual val delegate: MyClassImpl,
) : MyClassAPI by delegate {
    public actual constructor(
        name: String,
        myRepo: MyRepo,
    ) : this(
        delegate = MyClassImpl(name, myRepo)
    )
    
    internal actual fun doInternal(): String = delegate.doInternal()
}

Sixth, optional, step. If the users asked for certain JVM-specific/non-JVM-specific functionality, these actual classes might contain additional public symbols not present in the API interface. Such additional public symbols should be documented properly and concisely with KDoc

For example, jvmCommonMain implementation that uses JVM-specific API

// Suppress IntelliJ warnings
@file:Suppress("MissingKDocForPublicAPI")

package com.example

public actual class MyClass internal actual constructor(
    internal actual val delegate: MyClassImpl,
) : MyClassAPI by delegate {
    public actual constructor(
        name: String,
        myRepo: MyRepo,
    ) : this(
        delegate = MyClassImpl(name, myRepo)
    )
    
    /**
     * Gets environment variable value by [key]
     */
    public fun getEnv(key: String): String = System.getenv(key)
}

Seventh, if MyClass had any tests in the original commonTest source set, use MyClassImpl in the test to verify common implementation. Do not rename the test, it should still be called e.g. MyClassTest. If you need to test some platform-specific functionality, create tests in jvmCommonTest and/or nonJvmCommonTest source sets. Kotlin tests for JVM-specific functionality should go to jvmCommonTest. IMPORTANT: if the test suite is written in Java, it MUST go to jvmTest, not jvmCommonTest. KMP only compiles Java code in jvmMain/jvmTest source sets.

Finally, ensure that the implementation compiles by running:

./gradlew :mymodule:compileKotlinJvm
./gradlew :mymodule:compileKotlinJs

Replace :mymodule with the actual Gradle module path derived from the file location (e.g., agents/agents-core:agents:agents-core). If both pass, the split is working correctly.

IntelliJ false positives

Due to the experimental state of expect/actual classifiers, IntelliJ may report false positive errors such as "no actual for expect" or "no expect for actual". These diagnostics should NOT be trusted. The only reliable verification is running the Gradle compile tasks listed above.

Important notes

Imports

When creating the new files (API interface, Impl, expect class, actual classes), carry over all relevant imports from the original class.

Annotations

Classes being split should not have annotations. If the original class has annotations (e.g., @Serializable, @Deprecated), ask the user explicitly how to handle them before proceeding.

Companion objects

Classes being split should not have companion objects. If the original class has a companion object, ask the user explicitly how to handle it before proceeding.

Version History

  • 1.1.1 Current 2026-07-25 09:35

Same Skill Collection

.claude/skills/add-java-code-snippets-in-docs/SKILL.md

Metadata

Files
0
Version
1.1.1
Hash
2fc3b537
Indexed
2026-07-25 09:35

- 위키
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-08-20 10:28
浙ICP备14020137号-1 $방문자$