KO

kotlin-multiplatform

Assists with KMP architectural decisions, including source set placement and managing cross-platform abstraction patterns.

Install

mkdir -p .claude/skills/kotlin-multiplatform && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/2190" && unzip -o skill.zip -d .claude/skills/kotlin-multiplatform && rm skill.zip

Installs to .claude/skills/kotlin-multiplatform

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Platform abstraction decision-making for Amethyst KMP project. Guides when to abstract vs keep platform-specific,
source set placement (commonMain, jvmAndroid, platform-specific), expect/actual patterns. Covers primary targets
(Android, JVM/Desktop, iOS) with web/wasm future considerations. Integrates with gradle-expert for dependency issues.
Triggers on: abstraction decisions ("should I share this?"), source set placement questions, expect/actual creation,
build.gradle.kts work, incorrect placement detection, KMP dependency suggestions.
543 chars · catalog description✓ has a “when” triggerlonger than Claude Code's old 250-char listing cap (fine on current versions)
Advanced

Key capabilities

  • Advises on abstraction vs. duplication tradeoffs
  • Determines appropriate source set placement
  • Manages expect/actual pattern implementation
  • Detects platform-specific code in common modules
  • Suggests KMP dependency strategies

How it works

It applies a decision tree to evaluate if code should reside in `commonMain` or platform-specific sets based on platform API dependency and reuse frequency.

Inputs & outputs

You give it
Platform code issue or abstraction question
You get back
Decision recommendation and source set guidance

When to use kotlin-multiplatform

  • Decide if code should be shared
  • Determine source set placement
  • Create expect/actual implementations
  • Detect incorrect platform code placement

About this skill

Kotlin Multiplatform: Platform Abstraction Decisions

Expert guidance for KMP architecture in Amethyst - deciding what to share vs keep platform-specific.

When to Use This Skill

Making platform abstraction decisions:

  • "Should I create expect/actual or keep Android-only?"
  • "Can I share this ViewModel logic?"
  • "Where does this crypto/JSON/network implementation belong?"
  • "This uses Android Context - can it be abstracted?"
  • "Is this code in the wrong module?"
  • Preparing for iOS/web/wasm targets
  • Detecting incorrect placements

Abstraction Decision Tree

Central question: "Should this code be reused across platforms?"

Follow this decision path (< 1 minute):

Q: Is it used by 2+ platforms?
├─ NO  → Keep platform-specific
│         Example: Android-only permission handling
│
└─ YES → Continue ↓

Q: Is it pure Kotlin (no platform APIs)?
├─ YES → commonMain
│         Example: Nostr event parsing, business rules
│
└─ NO  → Continue ↓

Q: Does it vary by platform or by JVM vs non-JVM?
├─ By platform (Android ≠ iOS ≠ Desktop)
│  → expect/actual
│  Example: Secp256k1Instance (uses different security APIs)
│
├─ By JVM (Android = Desktop ≠ iOS/web)
│  → jvmAndroid
│  Example: Jackson JSON parsing (JVM library)
│
└─ Complex/UI-related
   → Keep platform-specific
   Example: Navigation (Activity vs Window too different)

Final check:
Q: Maintenance cost of abstraction < duplication cost?
├─ YES → Proceed with abstraction
└─ NO  → Duplicate (simpler)

Real Examples from Codebase

Crypto → expect/actual:

// commonMain - expect declaration
expect object Secp256k1Instance {
    fun signSchnorr(data: ByteArray, privKey: ByteArray): ByteArray
}

// androidMain - uses Android Keystore
// jvmMain - uses Desktop JVM crypto
// iosMain - uses iOS Security framework

Why: Each platform has different security APIs.

JSON parsing → jvmAndroid:

// quartz/build.gradle.kts
val jvmAndroid = create("jvmAndroid") {
    api(libs.jackson.module.kotlin)
}

Why: Jackson is JVM-only, works on Android + Desktop, not iOS/web.

Navigation → platform-specific:

  • Android: MainActivity (Activity + Compose Navigation)
  • Desktop: Window + sidebar + MenuBar Why: UI paradigms fundamentally different.

Mental Model: Source Sets as Dependency Graph

Think of source sets as a dependency graph, not folders.

┌─────────────────────────────────────────────┐
│ commonMain = Contract (pure Kotlin)         │
│ - Business logic, protocol, data models     │
│ - No platform APIs                          │
└────────────┬────────────────────────────────┘
             │
             ├──────────────────────┬────────────────────
             │                      │
             ▼                      ▼
   ┌───────────────────┐  ┌──────────────────┐
   │ jvmAndroid        │  │ iosMain          │
   │ JVM libs shared   │  │ iOS common       │
   │ - Jackson         │  │                  │
   │ - OkHttp          │  └────┬─────────────┘
   └───┬───────────┬───┘       │
       │           │           │
       ▼           ▼           ├─→ iosArm64Main
  ┌─────────┐ ┌──────────┐     └─→ iosSimulatorArm64Main
  │android  │ │jvmMain   │
  │Main     │ │(Desktop) │
  └─────────┘ └──────────┘

Future: jsMain, wasmMain

Key insight: jvmAndroid is NOT a platform - it's a shared JVM layer.

The jvmAndroid Pattern

Unique to Amethyst. Shares JVM libraries between Android + Desktop.

When to Use jvmAndroid

Use jvmAndroid when:

  • ✅ JVM-specific libraries (Jackson, OkHttp, url-detector)
  • ✅ Android implementation = Desktop implementation (same JVM)
  • ✅ Library doesn't work on iOS/web

Do NOT use jvmAndroid for:

  • ❌ Pure Kotlin code (use commonMain)
  • ❌ Platform-specific APIs (use androidMain/jvmMain)
  • ❌ Code that should work on all platforms

Example from quartz/build.gradle.kts

// Must be defined BEFORE androidMain and jvmMain
val jvmAndroid = create("jvmAndroid") {
    dependsOn(commonMain.get())

    dependencies {
        api(libs.jackson.module.kotlin)  // JSON parsing - JVM only
        api(libs.url.detector)            // URL extraction - JVM only
        implementation(libs.okhttp)       // HTTP client - JVM only
    }
}

// Both depend on jvmAndroid
jvmMain { dependsOn(jvmAndroid) }
androidMain { dependsOn(jvmAndroid) }

Why Jackson in jvmAndroid, not commonMain?

  • Jackson is JVM-specific library
  • Works on Android (runs on JVM)
  • Works on Desktop (runs on JVM)
  • Does NOT work on iOS (not JVM) or web (not JVM)

Web/wasm consideration: For future web support, consider migrating from Jackson → kotlinx.serialization (see Target-Specific Guidance).

What to Abstract vs Keep Platform-Specific

Quick decision guidelines based on codebase patterns:

Always Abstract

  • Crypto (Secp256k1, encryption, signing)
  • Core protocol logic (Nostr events, NIPs)
  • Why: Needed everywhere, platform security APIs vary

Often Abstract

  • I/O operations (file reading, caching)
  • Logging (platform logging systems differ)
  • Serialization (if using kotlinx.serialization)
  • Why: Commonly reused, platform implementations available

Sometimes Abstract

  • Business logic: YES - state machines, data processing
  • ViewModels: YES - state + business logic shareable (StateFlow/SharedFlow)
  • Screen layouts: NO - platform-native (Window vs Activity)
  • Why: ViewModels contain platform-agnostic state; Screens render differently per platform

Rarely Abstract

  • Complex UI components (composables with heavy platform dependencies)
  • Why: Platform paradigms can differ significantly

Never Abstract

  • Navigation (Activity vs Window fundamentally different)
  • Permissions (Android vs iOS APIs incompatible)
  • Platform UX patterns
  • Why: Too platform-specific, abstraction creates leaky APIs

Evidence from shared-ui-analysis.md

ComponentShared?Rationale
PubKeyFormatter, ZapFormatter✅ YESPure Kotlin, no platform APIs
TimeAgoFormatter⚠️ ABSTRACTEDNeeds StringProvider for localized strings
ViewModels (state + logic)✅ YESStateFlow/SharedFlow platform-agnostic, Compose Multiplatform lifecycle compatible
Screen layouts (Scaffold, nav)❌ NOWindow vs Activity, sidebar vs bottom nav fundamentally different
Image loading (Coil)⚠️ ABSTRACTEDCoil 3.x supports KMP, needs expect/actual wrapper

expect/actual Mechanics

When to use: Code needed by 2+ platforms, varies by platform.

Pattern Categories from Codebase

Objects (singletons):

// 24 expect declarations found, common pattern:
expect object Secp256k1Instance { ... }
expect object Log { ... }
expect object LibSodiumInstance { ... }

Classes (instantiable):

expect class AESCBC { ... }
expect class DigestInstance { ... }

Functions (utilities):

expect fun platform(): String
expect fun currentTimeSeconds(): Long

See references/expect-actual-catalog.md for complete catalog with rationale.

Target-Specific Guidance

Android, JVM (Desktop), iOS - Current Primary Targets

Status: Mature patterns, stable APIs

Android (androidMain):

  • Uses Android framework (Activity, Context, etc.)
  • secp256k1-kmp-jni-android (0.23.0 in libs.versions.toml) for crypto
  • AndroidX libraries

Desktop JVM (jvmMain):

  • Uses Compose Desktop (Window, MenuBar, etc.)
  • secp256k1-kmp-jni-jvm (same 0.23.0 line) for crypto
  • Pure JVM libraries

iOS (iosMain):

  • Mature target — actively built and tested
  • Architecture targets: iosArm64, iosSimulatorArm64, iosX64 (plus macosArm64 for host tooling)
  • Platform APIs via platform.posix, Security framework

Web, wasm - Future Targets

Status: Not yet implemented, consider for future-proofing

Constraints to know:

  • ❌ No platform.posix (file I/O different)
  • ❌ No JVM libraries (Jackson, OkHttp won't work)
  • ❌ Different async model (JS event loop vs threads)

Future-proofing tips:

  1. Prefer pure Kotlin in commonMain
  2. Use kotlinx.* libraries:
    • kotlinx.serialization instead of Jackson
    • ktor instead of OkHttp (ktor supports web)
    • kotlinx.datetime instead of custom date handling
  3. Avoid platform.posix for file operations
  4. Test abstractions work without JVM assumptions

Example migration path:

// Current: jvmAndroid (JVM-only)
api(libs.jackson.module.kotlin)

// Future: commonMain (all platforms)
api(libs.kotlinx.serialization.json)

Integration: When to Invoke Other Skills

Invoke gradle-expert

Trigger gradle-expert skill when encountering:

  • Dependency conflicts (e.g., secp256k1-android vs secp256k1-jvm version mismatch)
  • Build errors related to source sets
  • Version catalog issues (libs.versions.toml)
  • "Duplicate class" errors
  • Performance/build time issues

Example trigger:

Error: Duplicate class found: fr.acinq.secp256k1.Secp256k1

→ Invoke gradle-expert for dependency conflict resolution.

Flags to Raise

Platform code in commonMain:

// ❌ INCORRECT - Android API in commonMain
expect fun getContext(): Context  // Context is Android-only!

→ Flag: "Android API in commonMain won't compile on other platforms"

Duplicated business logic:

// ❌ INCORRECT - Same logic in both
// androidMain/.../CryptoUtils.kt
fun validateSignature(...) { ... }

// jvmMain/.../CryptoUtils.kt
fun validateSignature(...) { ... }  // Duplicated!

→ Flag: "Business logic duplicated, should be in commonMain or expect/actual"

Reinventing wheel - suggest KMP alternatives:

  • Custom date/time → kotlinx.datetime
  • OkHttp → ktor (supports web)
  • Jackson → kotlinx.serialization
  • Custom UUID → kotlinx.uuid (when stable)

Common Pitfalls

1. Over-Abstraction

Problem: Creating expect/actual for UI components

// ❌ BAD
expect fun Navigatio

---

*Content truncated.*

When not to use it

  • Pure Android-only application development
  • Projects not intended for multiplatform sharing
  • Cases where abstraction costs exceed duplication benefits

Limitations

  • Abstraction recommendations are subjective to project goals
  • Requires knowledge of the specific target platforms
  • Complex UI logic remains difficult to abstract

How it compares

It provides architectural guidance specific to the KMP ecosystem rather than general coding advice.

Compared to similar skills

kotlin-multiplatform side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
kotlin-multiplatform (this skill)323moReviewAdvanced
android-clean-architecture01moNo flagsIntermediate
android-kotlin-development2685moReviewAdvanced
backend-microservice-development23moNo flagsIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

More by vitorpamplona

View all by vitorpamplona

compose-expert

vitorpamplona

Advanced Compose Multiplatform UI patterns for shared composables. Use when working with visual UI components, state management patterns (remember, derivedStateOf, produceState), recomposition optimization (@Stable/@Immutable visual usage), Material3 theming, custom ImageVector icons, or determining whether to share UI in commonMain vs keep platform-specific. Delegates navigation to android-expert/desktop-expert. Complements kotlin-expert (handles Kotlin language aspects of state/annotations).

632

kotlin-expert

vitorpamplona

Advanced Kotlin patterns for AmethystMultiplatform. Flow state management (StateFlow/SharedFlow), sealed hierarchies (classes vs interfaces), immutability (@Immutable, data classes), DSL builders (type-safe fluent APIs), inline functions (reified generics, performance). Use when working with: (1) State management patterns (StateFlow/SharedFlow/MutableStateFlow), (2) Sealed classes or sealed interfaces, (3) @Immutable annotations for Compose, (4) DSL builders with lambda receivers, (5) inline/reified functions, (6) Kotlin performance optimization. Complements kotlin-coroutines agent (async patterns) - this skill focuses on Amethyst-specific Kotlin idioms.

526

kotlin-coroutines

vitorpamplona

Advanced Kotlin coroutines patterns for AmethystMultiplatform. Use when working with: (1) Structured concurrency (supervisorScope, coroutineScope), (2) Advanced Flow operators (flatMapLatest, combine, merge, shareIn, stateIn), (3) Channels and callbackFlow, (4) Dispatcher management and context switching, (5) Exception handling (CoroutineExceptionHandler, SupervisorJob), (6) Testing async code (runTest, Turbine), (7) Nostr relay connection pools and subscriptions, (8) Backpressure handling in event streams. Delegates to kotlin-expert for basic StateFlow/SharedFlow patterns. Complements nostr-expert for relay communication.

311

nostr-expert

vitorpamplona

Nostr protocol implementation patterns in Quartz (AmethystMultiplatform's KMP Nostr library). Use when working with: (1) Nostr events (creating, parsing, signing), (2) Event kinds and tags, (3) NIP implementations (57 NIPs in quartz/), (4) Event builders and TagArrayBuilder DSL, (5) Nostr cryptography (secp256k1, NIP-44 encryption), (6) Relay communication patterns, (7) Bech32 encoding (npub, nsec, note, nevent). Complements nostr-protocol agent (NIP specs) - this skill provides Quartz codebase patterns and implementation details.

323

gradle-expert

vitorpamplona

Build optimization, dependency resolution, and multi-module KMP troubleshooting for AmethystMultiplatform. Use when working with: (1) Gradle build files (build.gradle.kts, settings.gradle), (2) Version catalog (libs.versions.toml), (3) Build errors and dependency conflicts, (4) Module dependencies and source sets, (5) Desktop packaging (DMG/MSI/DEB), (6) Build performance optimization, (7) Proguard/R8 configuration, (8) Common KMP + Android Gradle issues (Compose conflicts, secp256k1 JNI variants, source set problems).

19

Search skills

Search the agent skills registry