@code-migration/wow-migrator 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-migration/wow-migrator",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Install KMP migration skills into Claude Code, Codex, Cursor, Gemini, OpenCode, OpenClaw, and JiuwenSwarm via npm install.",
5
5
  "keywords": [
6
6
  "android",
@@ -11,27 +11,27 @@ roles:
11
11
  - id: analysis-workspace-state
12
12
  kind: ai_agent
13
13
  purpose: Analysis ledger — module status, node output inventory, stale inputs, rerun/blocker history, artifact readiness, and next actions. No source analysis or SPEC writing.
14
- skills: []
14
+ skills: [operating-instructions]
15
15
  tools: [git]
16
16
  - id: presentation-resource
17
17
  kind: ai_agent
18
18
  purpose: Presentation and resource owner — screens, UI technologies, navigation, UI modules, local/remote image and media resources, safe downloads, and UI/resource migration implications.
19
- skills: []
19
+ skills: [operating-instructions]
20
20
  tools: [rg, curl]
21
21
  - id: project-architecture
22
22
  kind: ai_agent
23
23
  purpose: Project architecture owner — Gradle/module topology, architecture style, layer roles, dependency ecosystem, Jetpack/DI/platform services, generated tooling, and Android-only constraints.
24
- skills: []
24
+ skills: [operating-instructions]
25
25
  tools: [rg]
26
26
  - id: data-contract-flow
27
27
  kind: ai_agent
28
28
  purpose: Data contract and flow owner — network/local data contracts, models, consumers, repositories, streams, transformations, cache/error/pagination, write-back, and UI state propagation.
29
- skills: []
29
+ skills: [operating-instructions]
30
30
  tools: [rg]
31
31
  - id: behavior-logic
32
32
  kind: ai_agent
33
33
  purpose: Behavior and control-flow owner — user actions, lifecycle, state holders, business rules, side effects, state machines, navigation effects, gates, and cross-module interactions.
34
- skills: []
34
+ skills: [operating-instructions]
35
35
  tools: [rg]
36
36
  ---
37
37
 
@@ -41,6 +41,8 @@ This is the agent-facing registry and team definition for the `android-project-a
41
41
 
42
42
  **Canonical file recording system**: [output-contract.md](output-contract.md) defines every output path, required content, write order, and downstream **handoff package gates** (`P0`–`P6`). Downstream handlers (`migration-task-adapter`, `android-to-kmp-migrator`, `kmp-test-validator`) trigger only when the declared package artifacts exist, are non-empty, in-path, and not stale. The Leader MUST read `output-contract.md` before the first dispatch and MUST NOT claim completion without updating `handoff_gates` in the workspace ledger and `SPEC/verification.md`.
43
43
 
44
+ **Baseline operating instructions**: [../operating-instructions/SKILL.md](../operating-instructions/SKILL.md) is the shared conduct layer for this skill and every dispatched analyst role. The Leader MUST read it before pre-flight/module partitioning and include it in each role dispatch as baseline instructions; role files, workflow gates, and output contracts add to it, not replace it.
45
+
44
46
  The team is **module-first Mixed B+C with a workspace-state ledger**: the Leader partitions the project into `analysis_modules`, writes index artifacts and one `modules/<module_id>/` folder per module, maintains `analysis-workspace-state` before downstream consumption, runs three foundation nodes in **parallel** (B) inside each module, then a final **gated specialization** node (C) synthesizes module behavior from verified upstream outputs. The Leader writes a module representation per module, records cross-module architecture and data/logic separately under `global/`, then combines everything into `global_representation.*` and SPEC. The controller owns routing, strict output-path enforcement, reconciliation, workspace-state refreshes, and SPEC integration; nodes own bounded module analysis only.
45
47
 
46
48
  ## Module Partitioning Model
@@ -5,7 +5,17 @@
5
5
  # Android Studio MCP is optional auxiliary evidence for the Legacy Android source project —
6
6
  # it never replaces durable node artifacts or handoff package gates (P0–P6).
7
7
 
8
- skills: [] # No local domain-specific skill packages required for active roles.
8
+ skills:
9
+ - name: operating-instructions
10
+ required: true
11
+ used_by:
12
+ - Leader
13
+ - analysis-workspace-state
14
+ - presentation-resource
15
+ - project-architecture
16
+ - data-contract-flow
17
+ - behavior-logic
18
+ purpose: Shared baseline conduct layer loaded before analyst pre-flight, module partitioning, node dispatch, evidence synthesis, and final handoff work.
9
19
 
10
20
  optional_mcp:
11
21
  - server: jetbrains
@@ -11,47 +11,47 @@ roles:
11
11
  - id: migration-workspace-state
12
12
  kind: ai_agent
13
13
  purpose: Migration ledger — handoff gates M0–V0, plan-vs-code gaps, stale outputs, rerun hooks. No code edits.
14
- skills: []
14
+ skills: [operating-instructions]
15
15
  tools: [git]
16
16
  - id: target-project-assistant
17
17
  kind: ai_agent
18
18
  purpose: Target KMP owner — global baseline, per-module anchors, alignment revision, consult log.
19
- skills: []
19
+ skills: [operating-instructions]
20
20
  tools: [rg]
21
21
  - id: migration-planning-gate
22
22
  kind: ai_agent
23
23
  purpose: Planning and dependency/platform gate — SPEC deltas, source-to-target map, capability map, ready_for_implementation.
24
- skills: []
24
+ skills: [operating-instructions]
25
25
  tools: [rg]
26
26
  - id: migration-prep
27
27
  kind: ai_agent
28
28
  purpose: Presentation and state/data prep — tokens, resources, routes, state/models/API expectations.
29
- skills: []
29
+ skills: [operating-instructions]
30
30
  tools: [rg, curl]
31
31
  - id: module-implementation
32
32
  kind: ai_agent
33
33
  purpose: Target KMP module implementation by mode — edit/create KMP UI files first, then logic after UI approval.
34
- skills: []
34
+ skills: [operating-instructions]
35
35
  tools: [rg]
36
36
  - id: module-node-review-fix
37
37
  kind: ai_agent
38
38
  purpose: Review or scoped fix by mode; fresh re-review after every fix.
39
- skills: []
39
+ skills: [operating-instructions]
40
40
  tools: [rg, git]
41
41
  - id: migration-verification
42
42
  kind: ai_agent
43
43
  purpose: Module static checks + UI/logic restoration vs analyst — no full project build.
44
- skills: []
44
+ skills: [operating-instructions]
45
45
  tools: [rg, git]
46
46
  - id: global-migration-phase
47
47
  kind: ai_agent
48
48
  purpose: Target KMP global integrate (edit cross-module glue + entry point wiring) then align (read-only analyst vs target comparison incl. entry points) by mode.
49
- skills: []
49
+ skills: [operating-instructions]
50
50
  tools: [rg]
51
51
  - id: completion-report
52
52
  kind: ai_agent
53
53
  purpose: Readiness and migration_report modes; validation handoff to kmp-test-validator.
54
- skills: []
54
+ skills: [operating-instructions]
55
55
  tools: [rg, git]
56
56
  ---
57
57
 
@@ -61,9 +61,11 @@ Module-first migrator for Legacy Android → KMP target assembly. **Upstream ana
61
61
 
62
62
  **Canonical contract**: [output-contract.md](output-contract.md)
63
63
 
64
+ **Baseline operating instructions**: [../operating-instructions/SKILL.md](../operating-instructions/SKILL.md) is the shared conduct layer for this skill and every dispatched migrator role. The Leader MUST read it before pre-flight and include it in each role dispatch as baseline instructions; role files, architecture references, workflow gates, and output contracts add to it, not replace it.
65
+
64
66
  ## Protocol Summary
65
67
 
66
- 0. Pre-flight — [dependencies.yaml](dependencies.yaml): `rg` / `git` / `curl`, optional `jetbrains` MCP (`optional_mcp`), upstream analyst **P6** (`upstream_inputs`); record `dependency_preflight` in `run_manifest.json`.
68
+ 0. Pre-flight — [dependencies.yaml](dependencies.yaml): `rg` / `git` / `curl`, optional `jetbrains` MCP (`optional_mcp`), upstream analyst **P6** (`upstream_inputs`); **identify `design_mode` from user input (default `mvi`)**; record `dependency_preflight` and `design_mode` in `run_manifest.json`.
67
69
  1. Verify analyst **P6**; `run_manifest.json`, `upstream_analyst_index.json`.
68
70
  2. Migration inventory + `modules_migration_index.json`.
69
71
  3. Workspace state init.
@@ -73,6 +75,23 @@ Module-first migrator for Legacy Android → KMP target assembly. **Upstream ana
73
75
  7. Global representation + completion-report `report` mode.
74
76
  8. **kmp-test-validator** — **mandatory** when **V0** ready (MG17).
75
77
 
78
+ ## Design Mode (architecture pattern)
79
+
80
+ The migrator targets one presentation architecture per run, selected at **pre-flight (Step 0)** by the Leader from the **user input**, then frozen for the whole run and recorded in `run_manifest.json` → `design_mode`.
81
+
82
+ | `design_mode` | Architecture reference | When chosen |
83
+ |---|---|---|
84
+ | `mvi` **(default)** | [references/kmp-mvi-flowredux.md](references/kmp-mvi-flowredux.md) | Default when user input gives no clear signal; or user mentions MVI, FlowRedux, state machine, reducer, intent, unidirectional, sealed `State`/`Action`, `dispatch`, `inState`, `onEnter` |
85
+ | `mvvm` | [references/kmp-mvvm.md](references/kmp-mvvm.md) | User mentions MVVM, shared `ViewModel`, `StateFlow`/`uiState`, `viewModelScope`, `collectAsStateWithLifecycle`, KMP-ObservableViewModel, SKIE |
86
+
87
+ Both modes also follow [references/kmp-expert.md](references/kmp-expert.md) for base KMP/CMP conventions.
88
+
89
+ **Rules**:
90
+ - **Default is `mvi`** — when the user input contains no explicit or implied architecture signal, the Leader MUST select `mvi`.
91
+ - Record the decision as `design_mode: { value, source: "user_input | default", signals: [] }` in `run_manifest.json` at **MG0**.
92
+ - The Leader passes `design_mode` and the resolved `architecture_reference_path` to every architecture-producing role (planning-gate, prep, module-implementation, module-node-review-fix, global-migration-phase) and to TPA for target-pattern detection.
93
+ - `design_mode` is **fixed for the run**; a mid-run change requires a fresh run, not in-place mutation.
94
+
76
95
  ## Skill Chain (mandatory)
77
96
 
78
97
  ```text
@@ -108,6 +127,7 @@ android-project-analyst (P6) → android-to-kmp-migrator (M0–V0) → kmp-test-
108
127
  | [bind.md](bind.md) | Limits, constraints, failures |
109
128
  | [dependencies.yaml](dependencies.yaml) | CLI + optional MCP per role |
110
129
  | [roles/](roles/) | Role specs |
130
+ | [references/](references/) | Architecture references: `kmp-mvi-flowredux.md` (MVI, default), `kmp-mvvm.md` (MVVM), `kmp-expert.md` (base KMP/CMP) |
111
131
 
112
132
  ## Handoff Gates
113
133
 
@@ -123,6 +143,7 @@ android-project-analyst (P6) → android-to-kmp-migrator (M0–V0) → kmp-test-
123
143
  ## Shared Rules
124
144
 
125
145
  - **Skill chain**: `android-project-analyst` **P6** before migrator; `kmp-test-validator` **after** migrator **V0** — both mandatory.
146
+ - **Design mode**: identified from user input at pre-flight, **default `mvi`**; frozen for the run; architecture-producing roles MUST follow the resolved `architecture_reference_path` (`kmp-mvi-flowredux.md` for `mvi`, `kmp-mvvm.md` for `mvvm`).
126
147
  - **Target KMP edit mandate**: after analyst P6 understanding, migrator roles MUST create or update production files under `kmp_target_project_path`. Planning-only or artifact-only completion is invalid.
127
148
  - **Roles that edit target** (record `changed_files[]` or `integration_changed_files[]`):
128
149
  - `migration-prep` — optional scaffold edits (theme, resources, routes, models) when planning allows
@@ -29,6 +29,7 @@
29
29
  - **Skill chain (mandatory)**:
30
30
  - **Before migrator**: `android-project-analyst` MUST finish and produce package **P6** (`handoff_gates.P6.ready = true`). If P6 is missing or stale, return `blocked` and dispatch analyst — do not start migrator nodes.
31
31
  - **After migrator**: when package **V0** is ready, Leader MUST invoke `kmp-test-validator` (MG17). Migrator completion without validator dispatch is invalid.
32
+ - **Design mode**: at pre-flight the Leader identifies `design_mode` from user input — **default `mvi`** when no architecture signal is present. It is recorded in `run_manifest.json`, **frozen for the run**, and every architecture-producing role MUST follow the resolved `architecture_reference_path` (`references/kmp-mvi-flowredux.md` for `mvi`, `references/kmp-mvvm.md` for `mvvm`). Both modes also follow `references/kmp-expert.md`.
32
33
  - **Role schedule**: dispatch only role IDs listed in [SKILL.md](SKILL.md).
33
34
  - **Mode discipline**:
34
35
  - `module-implementation`: `ui` then `logic` — separate invocations
@@ -44,6 +45,9 @@
44
45
  | Failure | Response |
45
46
  |---|---|
46
47
  | Unknown or invalid role ID | Reject; use role from `SKILL.md` registry |
48
+ | `design_mode` not identified at pre-flight | Default to `mvi`; record `source: default` in `run_manifest.json` |
49
+ | Architecture-producing dispatch missing `design_mode` | Reject; re-dispatch with `design_mode` + `architecture_reference_path` |
50
+ | Code produced against wrong architecture vs `design_mode` | `needs_rerun` owning role with correct reference |
47
51
  | `ui` and `logic` combined | Reject invocation |
48
52
  | `integrate` and `align` combined | Reject invocation |
49
53
  | Verification restoration failed | Rerun `module-implementation` or `migration-prep`; no completion record |
@@ -8,6 +8,20 @@
8
8
 
9
9
  # Skill chain (mandatory — Leader enforces at Step 0 and MG17)
10
10
  skills:
11
+ - name: operating-instructions
12
+ required: true
13
+ used_by:
14
+ - Leader
15
+ - migration-workspace-state
16
+ - target-project-assistant
17
+ - migration-planning-gate
18
+ - migration-prep
19
+ - module-implementation
20
+ - module-node-review-fix
21
+ - migration-verification
22
+ - global-migration-phase
23
+ - completion-report
24
+ purpose: Shared baseline conduct layer loaded before migrator pre-flight, planning, implementation, review/fix, verification, integration, and reporting work.
11
25
  - name: android-project-analyst
12
26
  required: true
13
27
  phase: prerequisite
@@ -147,7 +147,7 @@ output_root = <output_dir or ~/.a2c_agents/migration>/android-to-kmp-migrator
147
147
 
148
148
  | Step | Gate | Required artifacts before next step |
149
149
  |---|---|---|
150
- | `MG0` | Run lock | `run_manifest.json`, `upstream-index/upstream_analyst_index.json` |
150
+ | `MG0` | Run lock | `run_manifest.json` (incl. `design_mode`), `upstream-index/upstream_analyst_index.json` |
151
151
  | `MG1` | Workspace init | global `migration_workspace_state.*` |
152
152
  | `MG2` | Migration index | `migration_module_inventory.*`, `modules_migration_index.json`, per-module `module_brief.json` |
153
153
  | `MG3` | Target baseline | global `target-project-assistant/*` (`mode: global_baseline`) + `target_alignment_revision.*` |
@@ -241,6 +241,30 @@ output_root = <output_dir or ~/.a2c_agents/migration>/android-to-kmp-migrator
241
241
 
242
242
  ## Key Artifact Content Requirements
243
243
 
244
+ ### `run_manifest.json` → `design_mode`
245
+
246
+ Records the presentation architecture pattern, identified from **user input** at pre-flight (Step 0a) and **frozen for the run**. Default is `mvi` when user input gives no clear signal.
247
+
248
+ ```json
249
+ {
250
+ "design_mode": {
251
+ "value": "mvi",
252
+ "source": "default",
253
+ "signals": [],
254
+ "architecture_reference_path": "references/kmp-mvi-flowredux.md"
255
+ }
256
+ }
257
+ ```
258
+
259
+ | Field | Meaning |
260
+ |---|---|
261
+ | `value` | `mvi` (default) \| `mvvm` |
262
+ | `source` | `user_input` when a signal was matched; `default` when none |
263
+ | `signals` | matched keywords/phrases from user input (empty when defaulted) |
264
+ | `architecture_reference_path` | `references/kmp-mvi-flowredux.md` for `mvi`, `references/kmp-mvvm.md` for `mvvm` |
265
+
266
+ The Leader MUST pass `design_mode.value` and `design_mode.architecture_reference_path` into every architecture-producing dispatch (`migration-planning-gate`, `migration-prep`, `module-implementation`, `module-node-review-fix`, `global-migration-phase`) and to `target-project-assistant` for target-pattern detection.
267
+
244
268
  ### `upstream_analyst_index.json`
245
269
 
246
270
  ```json
@@ -328,7 +352,7 @@ Human/agent-readable synthesis of align mode; includes `entry_point_alignment_re
328
352
 
329
353
  ## Leader Obligations
330
354
 
331
- 1. Verify analyst package `P6` before `MG0` completes.
355
+ 1. Verify analyst package `P6` before `MG0` completes; identify `design_mode` from user input (default `mvi`) and write it to `run_manifest.json` at `MG0`, then pass `design_mode` + `architecture_reference_path` into every architecture-producing dispatch.
332
356
  2. Dispatch `target-project-assistant` for all target-project questions; other roles MUST reference TPA artifacts instead of re-analyzing target ad hoc.
333
357
  3. Ensure each module produces **target KMP edits** via `module-implementation` (and optional `migration-prep` / `module-node-review-fix` `fix`) before writing `module_completion_record.json`.
334
358
  4. Write `module_completion_record.json` after each module passes `migration-verification`; include aggregated `target_changed_files[]` for the module.
@@ -353,3 +377,6 @@ Human/agent-readable synthesis of align mode; includes `entry_point_alignment_re
353
377
  | Planning complete but `changed_files[]` empty when tasks require edits | `needs_rerun` → `module-implementation` or `migration-prep` |
354
378
  | `changed_files` paths outside `kmp_target_project_path` | `blocked` — reject artifact; rerun owning role |
355
379
  | `target_files_exist` failed | `needs_rerun` → owning edit role |
380
+ | `design_mode` missing from `run_manifest.json` at MG0 | `blocked`, `reason: missing` — Leader must identify (default `mvi`) before module dispatch |
381
+ | Architecture-producing dispatch missing `design_mode` / `architecture_reference_path` | Reject dispatch; re-dispatch with `design_mode` injected |
382
+ | Implementation/review uses the wrong architecture vs `design_mode` | `needs_rerun` → owning role with correct `architecture_reference_path` |
@@ -0,0 +1,260 @@
1
+ ---
2
+ name: kmp-expert
3
+ description: >
4
+ Foundational Kotlin Multiplatform / Compose Multiplatform knowledge for KMP/CMP
5
+ development. Use when working on any KMP/CMP task: project setup, source set
6
+ hierarchy, expect/actual, Gradle/version-catalog config, choosing the library
7
+ stack (Ktor, SQLDelight, Room, Koin, serialization), iOS interop (SKIE,
8
+ KMP-NativeCoroutines), Wasm/web targets, or debugging build/source-set issues.
9
+ Triggers on Kotlin Multiplatform, KMP, CMP, Compose Multiplatform, commonMain,
10
+ expect/actual, shared module, iosMain, klib, Kotlin/Native.
11
+ ---
12
+
13
+ # Kotlin Multiplatform / Compose Multiplatform — expert skill
14
+
15
+ This is the foundational knowledge layer for KMP/CMP work. For presentation
16
+ architecture, see the companion `kmp-mvvm` and `kmp-mvi-flowredux` skills — this
17
+ skill covers everything beneath them: structure, source sets, interop, and the stack.
18
+
19
+ References:
20
+ - https://kotlinlang.org/docs/multiplatform/multiplatform-discover-project.html
21
+ - https://kotlinlang.org/docs/multiplatform/multiplatform-hierarchy.html
22
+ - https://blog.jetbrains.com/kotlin/2026/05/new-kmp-default-structure/
23
+
24
+ ## KMP vs CMP — know which one you mean
25
+
26
+ - **KMP (Kotlin Multiplatform)** — shares non-UI logic (networking, persistence,
27
+ business rules). You build the UI natively per platform (Compose on Android,
28
+ SwiftUI on iOS). Officially supported by Google for Android/iOS logic sharing.
29
+ - **CMP (Compose Multiplatform)** — a declarative UI framework layered on top of KMP
30
+ that lets you share the UI too. On iOS it renders via Skia (Skiko), not native widgets.
31
+
32
+ Decision rule: push as much as possible into `commonMain`. Use KMP-only when you need
33
+ the deepest native iOS fidelity; use CMP when one UI codebase outweighs that.
34
+
35
+ ---
36
+
37
+ ## Compilation targets
38
+
39
+ | Target | Compiler | Output |
40
+ |---|---|---|
41
+ | Android / JVM backend | Kotlin/JVM | JVM bytecode |
42
+ | iOS / macOS / watchOS / tvOS / Linux / Windows | Kotlin/Native (LLVM) | Native binary / `.framework` |
43
+ | Web | Kotlin/Wasm (or legacy Kotlin/JS) | WebAssembly / JS |
44
+
45
+ Kotlin/Native produces standalone binaries with no VM, which is how KMP gets native
46
+ performance on iOS.
47
+
48
+ ---
49
+
50
+ ## Project structure (JetBrains 2026 default)
51
+
52
+ The default structure changed in 2026 to give each module a single responsibility,
53
+ aligning with Android Gradle Plugin 9.0.
54
+
55
+ ```
56
+ project-root/
57
+ ├── shared/ ← KMP library: ALL shared code (the only multiplatform module)
58
+ │ └── src/
59
+ │ ├── commonMain/ ← 80–90% of code lives here
60
+ │ ├── androidMain/ ← Android actuals + JVM-only deps
61
+ │ ├── iosMain/ ← iOS actuals (intermediate; see hierarchy below)
62
+ │ ├── desktopMain/ ← JVM desktop actuals
63
+ │ ├── wasmJsMain/ ← web actuals
64
+ │ └── commonTest/
65
+ ├── androidApp/ ← thin Android entry point, depends on :shared
66
+ ├── iosApp/ ← Xcode project consuming the shared framework
67
+ ├── desktopApp/ ← JVM desktop entry point
68
+ └── webApp/ ← Wasm/JS entry point
69
+ ```
70
+
71
+ The old single `composeApp` module that mixed library + app concerns is replaced by a
72
+ clean `shared` library plus separate `*App` modules. Generate new projects at
73
+ `kmp.jetbrains.com`. Reference samples: `KMP-App-Template`, `kotlinconf-app`, RSS Reader.
74
+
75
+ For large apps, modularize further: feature modules (`:feature-x`) each split into
76
+ `domain`/`data`/`presentation`, plus shared `:core-network`, `:core-db`, `:core-ui`.
77
+
78
+ ---
79
+
80
+ ## Source set hierarchy — the mental model
81
+
82
+ Source sets form a tree. During compilation for a target, Kotlin combines **all** source
83
+ sets on the path from `commonMain` down to the platform leaf.
84
+
85
+ ```
86
+ commonMain
87
+ / \
88
+ appleMain jvmAndroidShared (you create if needed)
89
+ / \ / \
90
+ iosMain macosMain androidMain desktopMain
91
+ / \
92
+ iosArm64 iosSimulatorArm64
93
+ ```
94
+
95
+ Key facts:
96
+ - `commonMain` compiles to every target; code here may use only multiplatform APIs.
97
+ - **Intermediate source sets** (`iosMain`, `appleMain`, `nativeMain`) share code among a
98
+ subset of targets. Put `actual` declarations in the intermediate set (e.g. `iosMain`),
99
+ not in each leaf (`iosArm64Main`, `iosSimulatorArm64Main`).
100
+ - The **default hierarchy template** (modern Kotlin) auto-wires `nativeMain`, `appleMain`,
101
+ `iosMain`, etc. You rarely need manual `dependsOn` anymore.
102
+ - Kotlin does **not** auto-share a JVM+Android source set. If their deps overlap, create a
103
+ custom intermediate set and wire it with `dependsOn` (IDE intellisense may complain but
104
+ it compiles).
105
+
106
+ ---
107
+
108
+ ## expect / actual — the platform contract
109
+
110
+ The cornerstone mechanism. Declare an `expect` in `commonMain`; provide an `actual` per
111
+ target (or per intermediate source set).
112
+
113
+ ```kotlin
114
+ // commonMain
115
+ expect fun getPlatform(): Platform
116
+ interface Platform { val name: String }
117
+
118
+ // androidMain
119
+ actual fun getPlatform(): Platform = object : Platform {
120
+ override val name = "Android ${Build.VERSION.SDK_INT}"
121
+ }
122
+
123
+ // iosMain
124
+ actual fun getPlatform(): Platform = object : Platform {
125
+ override val name = UIDevice.currentDevice.systemName()
126
+ }
127
+ ```
128
+
129
+ Three forms:
130
+ 1. `expect fun` / `actual fun` — functions (most common)
131
+ 2. `expect class` / `actual class` — full classes
132
+ 3. **Interface + expect factory** (recommended) — declare an `interface` in common, an
133
+ `expect fun buildX(): Interface` factory, and platform `actual` factories returning
134
+ platform impls. This keeps common code free of platform types and is easier to test.
135
+
136
+ Prefer the interface+factory form over `expect class`; it avoids the strictness of
137
+ matching every member signature across platforms.
138
+
139
+ When NOT to use expect/actual: if a library already provides a multiplatform API (Ktor,
140
+ SQLDelight, kotlinx-datetime), use it directly in `commonMain` — no expect/actual needed.
141
+
142
+ ---
143
+
144
+ ## The standard 2026 library stack
145
+
146
+ Verify multiplatform support at `klibs.io` before adding any dependency.
147
+
148
+ | Concern | Library | Notes |
149
+ |---|---|---|
150
+ | Networking | **Ktor client** | `commonMain` core + per-platform engine (OkHttp/Android, Darwin/iOS, CIO/desktop) |
151
+ | Serialization | **kotlinx.serialization** | apply the plugin to the module that defines `@Serializable` types |
152
+ | Persistence | **SQLDelight** (typed SQL) or **Room KMP** (Google, DAO-style) | SQLDelight for control; Room for Android-team familiarity |
153
+ | Key-value | **multiplatform-settings** or **DataStore** | small prefs |
154
+ | DI | **Koin** | no codegen → fast multiplatform compile; `koin-compose-viewmodel` for CMP |
155
+ | Dates/time | **kotlinx-datetime** | multiplatform `Instant`, `LocalDate`, time zones |
156
+ | Coroutines | **kotlinx-coroutines-core** | the concurrency backbone |
157
+ | Image loading | **Coil 3** | multiplatform |
158
+ | Navigation | Navigation-Compose (KMP), **Voyager**, or **Decompose** | Decompose pairs well with KMP lifecycle |
159
+ | Testing | **kotlinx-coroutines-test** + **Turbine** | flow testing |
160
+
161
+ ---
162
+
163
+ ## Gradle configuration (modern, terse)
164
+
165
+ ```kotlin
166
+ // shared/build.gradle.kts
167
+ plugins {
168
+ kotlin("multiplatform")
169
+ kotlin("plugin.serialization")
170
+ id("com.android.library")
171
+ }
172
+
173
+ kotlin {
174
+ androidTarget()
175
+ iosX64(); iosArm64(); iosSimulatorArm64()
176
+ jvm("desktop")
177
+ wasmJs { browser() }
178
+
179
+ // Default hierarchy template auto-creates iosMain/appleMain/nativeMain.
180
+ sourceSets {
181
+ commonMain.dependencies {
182
+ implementation(libs.ktor.client.core)
183
+ implementation(libs.kotlinx.serialization.json)
184
+ implementation(libs.koin.core)
185
+ implementation(libs.kotlinx.coroutines.core)
186
+ }
187
+ androidMain.dependencies { implementation(libs.ktor.client.okhttp) }
188
+ iosMain.dependencies { implementation(libs.ktor.client.darwin) }
189
+ getByName("desktopMain").dependencies { implementation(libs.ktor.client.cio) }
190
+ commonTest.dependencies {
191
+ implementation(libs.turbine)
192
+ implementation(libs.kotlinx.coroutines.test)
193
+ }
194
+ }
195
+ }
196
+ ```
197
+
198
+ Use a `gradle/libs.versions.toml` version catalog for all dependency coordinates —
199
+ it is the standard for keeping versions consistent across modules.
200
+
201
+ ---
202
+
203
+ ## iOS interop — the hard part
204
+
205
+ Kotlin/Native exports through an Objective-C bridge, which is lossy: sealed classes lose
206
+ exhaustiveness, `Int?` becomes a boxed `KotlinInt`, and `suspend` functions become
207
+ completion-handler callbacks. Mitigations:
208
+
209
+ | Tool | What it fixes |
210
+ |---|---|
211
+ | **SKIE** | Maps Kotlin `Flow` → Swift `AsyncSequence`, sealed classes → Swift enums with associated values, default args. Easy setup, less verbose. |
212
+ | **KMP-NativeCoroutines** | Maps `suspend`/`Flow` to Swift `async/await`, Combine, or RxSwift with proper cancellation. The more battle-tested option. |
213
+ | **KMP-ObservableViewModel** | Lets SwiftUI observe Kotlin ViewModels and handles the iOS lifecycle/store-owner boilerplate. |
214
+ | **Swift Export (emerging)** | Direct Kotlin→Swift modules (suspend→async/await, sealed→enums) without the ObjC layer. Still experimental — don't build production architecture on it yet. |
215
+
216
+ Practical rules:
217
+ - Reduce the exported surface: mark internal code `internal`/`private` and enable
218
+ `explicitApi()` so you don't export everything by default.
219
+ - Annotate the flows/suspend functions you expose with the chosen tool's annotation
220
+ (`@NativeCoroutines`, `@NativeCoroutinesState`).
221
+ - iOS engineers should never see `KotlinInt` or completion handlers — that's a sign your
222
+ interop layer is missing.
223
+
224
+ ---
225
+
226
+ ## Common gotchas
227
+
228
+ | Symptom | Cause | Fix |
229
+ |---|---|---|
230
+ | `@Serializable` compiles but crashes at runtime | serialization plugin not on the module defining the type | Apply `kotlin("plugin.serialization")` to that module |
231
+ | IDE shows red but it compiles | IntelliSense lagging on intermediate source sets | Sync Gradle; the build is the source of truth |
232
+ | Can't share code across JVM + Android | Kotlin doesn't auto-create that intermediate set | Create a custom source set with `dependsOn` |
233
+ | iOS sees opaque classes, no exhaustive switch | Raw ObjC bridge without SKIE | Add SKIE or KMP-NativeCoroutines |
234
+ | Slow Kotlin/Native builds | Native compilation is inherently slower than JVM | Use `embedAndSign`/`SKIE` caching; iterate on Android/desktop, verify on iOS less often |
235
+ | `expect class` won't compile — member mismatch | strict signature matching across platforms | Switch to interface + `expect` factory function |
236
+ | Coroutine on iOS never cancels | exposed raw `suspend` without interop annotation | Annotate with `@NativeCoroutines` for proper cancellation |
237
+ | Android-only API leaked into commonMain | wrote `android.*` import in common code | Move it behind expect/actual or an interface |
238
+
239
+ ---
240
+
241
+ ## Build/run quick reference
242
+
243
+ ```bash
244
+ ./gradlew :shared:build # compile the shared library, all targets
245
+ ./gradlew :androidApp:installDebug # build + install Android app
246
+ ./gradlew :desktopApp:run # run desktop (JVM) app
247
+ ./gradlew :shared:iosSimulatorArm64Test # run iOS tests on simulator
248
+ # iOS app itself is built/run from Xcode (iosApp project) consuming the framework
249
+ ```
250
+
251
+ ---
252
+
253
+ ## When advising on KMP/CMP work
254
+
255
+ 1. Default to putting code in `commonMain`; only drop to platform source sets when an API
256
+ genuinely differs. Reach for expect/actual last, libraries first.
257
+ 2. Check `klibs.io` for multiplatform support before suggesting any dependency.
258
+ 3. For UI: ask whether they want shared UI (CMP) or native UI (KMP-only) before scaffolding.
259
+ 4. For presentation logic, defer to the `kmp-mvvm` or `kmp-mvi-flowredux` skill.
260
+ 5. Always consider the iOS interop cost of anything exposed across the Swift boundary.