Files
Vitor PamplonaandClaude Opus 5.5 395af4dc9c chore(skills): consolidate 33 skills into 14, verified against the tree
The 33 descriptions (~16k chars, ~19k with paths) overran Codex's skill-list
budget (2% of the context window, or 8,000 chars when unknown), and a review
found most bodies stale: paths from before the commons/commonsUI moves, the
deleted legacy desktop app, a callback signer API that no longer exists, and
example APIs that were never in the tree.

Kept, rewritten and checked against the code (every path, class and command):
account-state (+feeds), amy-expert, android-expert, auth-signers,
compose-expert (+API style, Navigation 3 back stacks), desktop-expert
(+run/package), event-store-semantics (+NIP-50 search), find-missing-
translations, kotlin-coroutines (+structured concurrency, flow modeling),
kotlin-multiplatform (+Gradle, composable extraction), ngit-pr,
nip85-trusted-assertions, nostr-expert (+NIP lookup, generated NIP catalog),
relay-client.

Dropped: textbook Compose/Kotlin technique skills, kotlin-expert,
kotlin-types-value-class, gradle-expert, quartz-integration (external-facing;
see the Quartz docs commit), find-non-lambda-logs (audit finished; the rule is
now in AGENTS.md Kotlin Style), and the /nip, /desktop-run, /extract commands.
The skill list is now ~4k chars. Also drops .claude/core-skills-plan.md
(stale history) and repoints SearchableEvent's KDoc to event-store-semantics.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:21:01 -04:00

5.5 KiB
Raw Permalink Blame History


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 16: amethyst, nappletHost, benchmark, baselineprofile, quartz, geode, commons, commonsUI, quic, nestsClient, marmotQuic, desktopApp, cli, relayBench, marmotBench, quic-interop (project dir quic/interop).

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: Jackson (jackson.databind), OkHttp 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. quic, 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 quartz/build.gradle.kts). Prefer the most common set that compiles: commonMain → jvmAndroid → platform.

Jackson and OkHttp are the house JSON and HTTP stack (AGENTS.md › Tech Stack). Don't migrate JVM code to kotlinx.serialization or Ktor. They are only 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 objects 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 quartz, commons and commonsUI 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-quartz-ios job (:quartz:iosSimulatorArm64Test, :quartz:compileTestKotlinIosArm64) is authoritative. Run ./gradlew :quartz:verifyKmpPurity :commons:verifyKmpPurity :commonsUI:verifyKmpPurity after touching commonMain.

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. :quartz:testAndroidHostTest is a separate suite that nothing runs; 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, as quartz/build.gradle.kts does: 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.