kotlin-multiplatform — скилл для Claude Code: KMP и Gradle
Скилл подсказывает агенту, в какой модуль и source set положить код в Amethyst, и не даёт сломать сборку на гейте чистоты KMP.
npx skills add vitorpamplona/amethyst --skill kotlin-multiplatform -a claude-codeЧто делает kotlin-multiplatform
Скилл разбирает, куда положить код в проекте Amethyst и как должно выглядеть Gradle-окружение. Внутри карта модулей из settings.gradle.kts, назначение source set'ов (commonMain, jvmAndroid, skikoMain и остальных) и правило выбора между expect/actual, портом и слотом. Отдельный раздел — гейт verifyKmpPurity: он сканирует всё, что компилируется под iOS и native, и валит сборку на okhttp3, com.fasterxml.jackson, System.currentTimeMillis, Thread.sleep, java.util.UUID и synchronized(.
Ещё здесь грабли Gradle. Версии живут только в gradle/libs.versions.toml, test — это алиас jvmTest, а testFixtures в KMP-блоке зависимостей не резолвится, нужен блок верхнего уровня со строковым именем конфигурации. Плюс варианты secp256k1 для commonMain, androidMain и jvmMain: Android-артефакт на JVM-класспути падает с UnsatisfiedLinkError. И правила R8 из quartz/consumer-rules.pro.
Скилл написан под репозиторий Amethyst, он ссылается на AGENTS.md, commons/ARCHITECTURE.md, commonsUI/ARCHITECTURE.md и tools/r8-verify/. В чужом проекте этих файлов нет, и агенту не на что опереться — проще форкнуть скилл и переписать ссылки под свою раскладку. После правок в commonMain автор советует прогнать ./gradlew :commons:verifyKmpPurity :commonsUI:verifyKmpPurity.
Какие задачи решает
- выбрать source set для нового класса в
commons - решить, делать
expect/actualили порт - починить падение
verifyKmpPurityнаThread.sleep - добавить библиотеку через
libs.versions.tomlбез хардкода версии - вынести Android-композабл в
commonsUI
Примеры запросов агенту
- Куда положить этот класс — в
commonMainилиjvmAndroid? - Прогони
./gradlew :commons:verifyKmpPurity :commonsUI:verifyKmpPurityи объясни, что упало. - Вынеси этот Android-композабл в
commonsUIпо шагам изreferences/extract-composable.md.
Как установить kotlin-multiplatform
1Через Skills CLI (Vercel)
npx skills add vitorpamplona/amethyst --skill kotlin-multiplatform -a claude-code2Вручную: положить файлы
~/.claude/skills/kotlin-multiplatform/SKILL.mdПапка скилла целиком; для одного проекта — .claude/skills/kotlin-multiplatform/
1Через Skills CLI (Vercel)
npx skills add vitorpamplona/amethyst --skill kotlin-multiplatform -a codex2Вручную: положить файлы
.agents/skills/kotlin-multiplatform/SKILL.mdДля всех проектов — ~/.agents/skills/
1Через Skills CLI (Vercel)
npx skills add vitorpamplona/amethyst --skill kotlin-multiplatform -a cursor2Вручную: положить файлы
.cursor/skills/kotlin-multiplatform/SKILL.mdCursor читает и .agents/skills/, и .claude/skills/
1GitHub CLI
gh skill install vitorpamplona/amethyst kotlin-multiplatform2Вручную: положить файлы
.github/skills/kotlin-multiplatform/SKILL.md1Через Skills CLI (Vercel)
npx skills add vitorpamplona/amethyst --skill kotlin-multiplatform -a gemini-cli2Командой платформы
gemini skills install https://github.com/vitorpamplona/amethyst --path .claude/skills/kotlin-multiplatformВручную: положить файлы
.devin/skills/kotlin-multiplatform/SKILL.mdСтарый путь .windsurf/skills/ тоже читается
Через Skills CLI (Vercel)
npx skills add vitorpamplona/amethyst --skill kotlin-multiplatform -a opencodeClaude.ai
Упакуйте папку в zip и загрузите в Settings → Capabilities → Skills
По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов
Вопросы о kotlin-multiplatform
Как установить kotlin-multiplatform в Claude Code?
Выполните npx skills add vitorpamplona/amethyst --skill kotlin-multiplatform -a claude-code. Скилл появится в ~/.claude/skills/kotlin-multiplatform/SKILL.md.
Что делает скилл kotlin-multiplatform?
Разбирает размещение кода по модулям и source set'ам в Amethyst, объясняет разницу между expect/actual, портом и слотом, а также закрывает грабли Gradle и R8.
Нужен ли репозиторий Amethyst, чтобы скилл работал?
Да. Он опирается на AGENTS.md, commons/ARCHITECTURE.md, commonsUI/ARCHITECTURE.md и задачу verifyKmpPurity в сборке. Без этих файлов агенту нечего читать.
Исходный текст
SKILL.md · MIT
---
name: kotlin-multiplatform
description: KMP placement and Gradle in Amethyst: which module or source set, expect/actual vs ports, verifyKmpPurity, extracting Android composables to commonsUI, build.gradle.kts/libs.versions.toml edits, secp256k1 JNI variants, R8.
---
# Kotlin Multiplatform and Gradle in Amethyst
Module roles, the "share vs keep native" rule and the CLI-safe boundary are in
`AGENTS.md` (Architecture, Feature Workflow). Package placement inside `commons` /
`commonsUI` is in `commons/ARCHITECTURE.md` ("Where does my code go?", "Source sets")
and `commonsUI/ARCHITECTURE.md` ("Platform slots"). Read those first; this skill
adds the source-set mechanics and the build traps.
## Modules
`settings.gradle.kts` includes 13: `amethyst`, `nappletHost`, `benchmark`,
`baselineprofile`, `geode`, `commons`, `commonsUI`, `nestsClient`,
`marmotQuic`, `desktopApp`, `cli`, `relayBench`, `marmotBench`. Quartz is the
external `libs.quartz` (vitorpamplona/quartz) and QUIC the external `libs.quic`
(vitorpamplona/quic-kmp); `-PquartzDir=../quartz` / `-PquicKmpDir=../quic-kmp` build a
local checkout instead (an included build, so quic's own quartz dependency follows).
## Source sets
| Source set | Holds | Targets |
|---|---|---|
| `commonMain` | Pure Kotlin. Compiled for iOS/native too, so no JVM APIs | all |
| `jvmAndroid` | JVM libraries shared by Android + Desktop: OkHttp, `nestsClient`, NWC/LNURL | android, jvm |
| `androidMain` / `jvmMain` | Platform actuals (Keystore/DataStore vs keyring, Coil bridges) | one each |
| `iosMain`, `appleMain`, `nativeMain`, `linuxMain`, `macosMain` | quartz only (plus `iosMain` in commons/commonsUI) | Apple / native |
| `skikoMain` | commonsUI code shared by Desktop and iOS | jvm, ios |
Targets: `quartz` builds android, jvm, iosArm64, iosSimulatorArm64, linuxX64,
macosArm64. `commons` and `commonsUI` build android, jvm, iosArm64,
iosSimulatorArm64. `nestsClient`, `marmotQuic` are android + jvm. There is
no `iosX64`.
`jvmAndroid` is a custom intermediate set created with `create("jvmAndroid")`
before `androidMain`/`jvmMain` depend on it (see `commons/build.gradle.kts`).
Prefer the most common set that compiles: `commonMain` → `jvmAndroid` → platform.
kotlinx.serialization is the house JSON stack and OkHttp the house HTTP stack
(`AGENTS.md` › Tech Stack). Jackson is left only inside quartz's JVM code and reaches
the app modules transitively; don't add new Jackson use here. Don't migrate JVM HTTP
to Ktor. Jackson and OkHttp are banned in iOS-compiled sets, which the purity gate
enforces.
## expect/actual vs ports and slots
- **expect/actual** for a small platform verb or value with no app state:
`KmpLock`, `ConcurrentSet`, `SecureKeyStorage`, `isDebug`, `formattedDateTime`
(commons), `rememberTextSharer`, `rememberShortNotice` (commonsUI). Put the actual
in `jvmAndroid` when Android and Desktop share it.
- **A port** (interface the shell implements) when the platform side needs app
state or services: `AccountViewModelHost`, `UserNoticeSink`, `LocalCacheHost`.
- **A slot** (CompositionLocal the front end installs) for things only a front end
can draw: `LocalNotePlatform`, `LocalRichTextPlatform`.
- Not every quartz singleton is expect: `Secp256k1Instance`, `Log` and
`LibSodiumInstance` are plain `object`s in `quartz/.../utils/`.
- Don't `expect` a whole screen or navigation; share it and put only the leaf
behind a port or slot.
## Purity gate: `verifyKmpPurity`
`gradle/kmp-purity.gradle.kts`, applied by `commons` and `commonsUI` (quartz keeps a copy) and
wired into `check`, scans every iOS/native-compiled source set (`commonMain`,
`iosMain`, `skikoMain`, …, and their tests) and fails on `com.fasterxml.jackson`,
`okhttp3`, `System.currentTimeMillis` (use `TimeUtils.now()`), `Thread.sleep`,
`java.util.UUID` (use `kotlin.uuid.Uuid`), `kotlin.jvm.Synchronized` or bare
`synchronized(` (use `KmpLock`), `kotlin.jvm.Volatile` (use
`kotlin.concurrent.Volatile`). CI runs it on Linux in the lint job; the macOS
`test-commons-ios` / `test-commonsui-ios` jobs are authoritative. Run
`./gradlew :commons:verifyKmpPurity :commonsUI:verifyKmpPurity`
after touching `commonMain`. Quartz has its own copy of the task and its own iOS CI.
## Gradle traps
- **Versions live only in `gradle/libs.versions.toml`.** Add the library there and
reference `libs.*`; never hardcode a version or copy one into docs. Check the
license first (`AGENTS.md` › Dependency Licensing).
- **`test` is an alias for each KMP module's `jvmTest`.** Other targets
(`:commonsUI:testAndroidHostTest`, the native ones) run only when named; see
`AGENTS.md` › Build Commands.
- **testFixtures in KMP:** the source-set `dependencies { }` block
(`KotlinDependencyHandler`) has no `testFixtures(...)`. Use a top-level block with
the configuration name as a string:
`dependencies { "jvmAndroidTestImplementation"(testFixtures(project(":geode"))) }`.
- **secp256k1 variants:** `secp256k1-kmp-common` in `commonMain`,
`secp256k1-kmp-jni-android` in `androidMain`, `secp256k1-kmp-jni-jvm` in `jvmMain`
and JVM tests. All three share one `version.ref`. A JVM classpath with the
android variant fails at load with `UnsatisfiedLinkError`.
- **R8:** library keep rules ship in `quartz/consumer-rules.pro`. Never add a blanket
`-keep class com.vitorpamplona.quartz.** { *; }`. R8 can't see reflection, so a
moved/renamed reflective class is a silent release break: before tagging, run
`python3 tools/r8-verify/verify_reflection_contract.py amethyst/build/outputs/mapping/<variant>/`
(the release workflow checks `playRelease` and `fdroidRelease`) and update
`tools/r8-verify/reflection-contract.txt` when you move a listed class.
## Extracting Android code to shared modules
Steps and the Android → multiplatform replacement table:
[references/extract-composable.md](references/extract-composable.md).
© vitorpamplona · MIT · vitorpamplona/amethyst