@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 +1 -1
- package/skills/android-project-analyst/SKILL.md +7 -5
- package/skills/android-project-analyst/dependencies.yaml +11 -1
- package/skills/android-to-kmp-migrator/SKILL.md +31 -10
- package/skills/android-to-kmp-migrator/bind.md +4 -0
- package/skills/android-to-kmp-migrator/dependencies.yaml +14 -0
- package/skills/android-to-kmp-migrator/output-contract.md +29 -2
- package/skills/android-to-kmp-migrator/references/kmp-expert.md +260 -0
- package/skills/android-to-kmp-migrator/references/kmp-mvi-flowredux.md +344 -0
- package/skills/android-to-kmp-migrator/references/kmp-mvvm.md +328 -0
- package/skills/android-to-kmp-migrator/roles/global-migration-phase.md +7 -2
- package/skills/android-to-kmp-migrator/roles/migration-planning-gate.md +6 -2
- package/skills/android-to-kmp-migrator/roles/migration-prep.md +6 -2
- package/skills/android-to-kmp-migrator/roles/module-implementation.md +20 -2
- package/skills/android-to-kmp-migrator/roles/module-node-review-fix.md +18 -2
- package/skills/android-to-kmp-migrator/roles/target-project-assistant.md +10 -1
- package/skills/android-to-kmp-migrator/workflow.md +12 -2
- package/skills/kmp-test-validator/SKILL.md +7 -5
- package/skills/kmp-test-validator/dependencies.yaml +10 -0
- package/skills/migration-task-adapter/SKILL.md +5 -3
- package/skills/migration-task-adapter/dependencies.yaml +7 -0
- package/skills/operating-instructions/SKILL.md +55 -0
package/package.json
CHANGED
|
@@ -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:
|
|
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
|
|
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.
|