@code-migration/wow-migrator 0.2.1 → 0.2.4

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.
Files changed (23) hide show
  1. package/package.json +1 -1
  2. package/skills/android-to-kmp-migrator/SKILL.md +32 -6
  3. package/skills/android-to-kmp-migrator/bind.md +15 -0
  4. package/skills/android-to-kmp-migrator/output-contract.md +61 -12
  5. package/skills/android-to-kmp-migrator/references/kmp-expert.md +260 -0
  6. package/skills/android-to-kmp-migrator/references/kmp-mvi-flowredux.md +344 -0
  7. package/skills/android-to-kmp-migrator/references/kmp-mvvm.md +328 -0
  8. package/skills/android-to-kmp-migrator/roles/completion-report.md +2 -1
  9. package/skills/android-to-kmp-migrator/roles/global-migration-phase.md +7 -2
  10. package/skills/android-to-kmp-migrator/roles/migration-planning-gate.md +7 -2
  11. package/skills/android-to-kmp-migrator/roles/migration-prep.md +12 -2
  12. package/skills/android-to-kmp-migrator/roles/migration-verification.md +5 -3
  13. package/skills/android-to-kmp-migrator/roles/module-implementation.md +20 -2
  14. package/skills/android-to-kmp-migrator/roles/module-node-review-fix.md +19 -3
  15. package/skills/android-to-kmp-migrator/roles/target-project-assistant.md +10 -1
  16. package/skills/android-to-kmp-migrator/workflow.md +23 -2
  17. package/skills/kmp-test-validator/SKILL.md +3 -3
  18. package/skills/kmp-test-validator/bind.md +3 -2
  19. package/skills/kmp-test-validator/dependencies.yaml +15 -2
  20. package/skills/kmp-test-validator/output-contract.md +92 -8
  21. package/skills/kmp-test-validator/roles/validation-code-gate.md +53 -7
  22. package/skills/kmp-test-validator/roles/validation-workspace-state.md +7 -2
  23. package/skills/kmp-test-validator/workflow.md +3 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-migration/wow-migrator",
3
- "version": "0.2.1",
3
+ "version": "0.2.4",
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",
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  name: android-to-kmp-migrator
3
3
  description: |
4
- Module-first Swarm Skill that migrates Legacy Android into an existing KMP target using upstream analyst P6 artifacts, target-project-assistant alignment, planning/prep/implementation roles, global integrate+align phase, and mandatory kmp-test-validator handoff — without full-project build during migration.
5
- Use only after android-project-analyst has finished and package P6 is ready, when the user wants module-first porting then whole-system assembly followed by validation.
6
- Do NOT invoke before android-project-analyst completes P6. Do NOT treat migrator completion as final without invoking kmp-test-validator at V0. Do NOT use for Legacy Android analysis only, KMP-only feature work, or non-migration refactors.
7
- version: "0.8"
4
+ Module-first Swarm Skill that migrates Legacy Android into an existing KMP target by editing target KMP source after analyst P6 understanding planning/prep/implementation roles create and update files under kmp_target_project_path, global integrate wires cross-module glue, then mandatory kmp-test-validator handoff — without full-project build during migration.
5
+ Use only after android-project-analyst has finished and package P6 is ready, when the user wants module-first porting with real target code changes then whole-system assembly followed by validation.
6
+ Do NOT invoke before android-project-analyst completes P6. Do NOT treat planning or analysis artifacts alone as migration success — target KMP files MUST be edited. Do NOT treat migrator completion as final without invoking kmp-test-validator at V0. Do NOT use for Legacy Android analysis only, KMP-only feature work, or non-migration refactors.
7
+ version: "0.9"
8
8
  kind: swarm-skill
9
9
  disable-model-invocation: false
10
10
  roles:
@@ -57,13 +57,13 @@ roles:
57
57
 
58
58
  # Android To KMP Migrator Swarm Skill
59
59
 
60
- Module-first migrator for Legacy Android → KMP target assembly.
60
+ Module-first migrator for Legacy Android → KMP target assembly. **Upstream analyst P6 is read-only input; this skill's job is to edit the target KMP project** under `kmp_target_project_path`.
61
61
 
62
62
  **Canonical contract**: [output-contract.md](output-contract.md)
63
63
 
64
64
  ## Protocol Summary
65
65
 
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`.
66
+ 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
67
  1. Verify analyst **P6**; `run_manifest.json`, `upstream_analyst_index.json`.
68
68
  2. Migration inventory + `modules_migration_index.json`.
69
69
  3. Workspace state init.
@@ -73,6 +73,23 @@ Module-first migrator for Legacy Android → KMP target assembly.
73
73
  7. Global representation + completion-report `report` mode.
74
74
  8. **kmp-test-validator** — **mandatory** when **V0** ready (MG17).
75
75
 
76
+ ## Design Mode (architecture pattern)
77
+
78
+ 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`.
79
+
80
+ | `design_mode` | Architecture reference | When chosen |
81
+ |---|---|---|
82
+ | `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` |
83
+ | `mvvm` | [references/kmp-mvvm.md](references/kmp-mvvm.md) | User mentions MVVM, shared `ViewModel`, `StateFlow`/`uiState`, `viewModelScope`, `collectAsStateWithLifecycle`, KMP-ObservableViewModel, SKIE |
84
+
85
+ Both modes also follow [references/kmp-expert.md](references/kmp-expert.md) for base KMP/CMP conventions.
86
+
87
+ **Rules**:
88
+ - **Default is `mvi`** — when the user input contains no explicit or implied architecture signal, the Leader MUST select `mvi`.
89
+ - Record the decision as `design_mode: { value, source: "user_input | default", signals: [] }` in `run_manifest.json` at **MG0**.
90
+ - 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.
91
+ - `design_mode` is **fixed for the run**; a mid-run change requires a fresh run, not in-place mutation.
92
+
76
93
  ## Skill Chain (mandatory)
77
94
 
78
95
  ```text
@@ -108,6 +125,7 @@ android-project-analyst (P6) → android-to-kmp-migrator (M0–V0) → kmp-test-
108
125
  | [bind.md](bind.md) | Limits, constraints, failures |
109
126
  | [dependencies.yaml](dependencies.yaml) | CLI + optional MCP per role |
110
127
  | [roles/](roles/) | Role specs |
128
+ | [references/](references/) | Architecture references: `kmp-mvi-flowredux.md` (MVI, default), `kmp-mvvm.md` (MVVM), `kmp-expert.md` (base KMP/CMP) |
111
129
 
112
130
  ## Handoff Gates
113
131
 
@@ -123,6 +141,14 @@ android-project-analyst (P6) → android-to-kmp-migrator (M0–V0) → kmp-test-
123
141
  ## Shared Rules
124
142
 
125
143
  - **Skill chain**: `android-project-analyst` **P6** before migrator; `kmp-test-validator` **after** migrator **V0** — both mandatory.
144
+ - **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`).
145
+ - **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.
146
+ - **Roles that edit target** (record `changed_files[]` or `integration_changed_files[]`):
147
+ - `migration-prep` — optional scaffold edits (theme, resources, routes, models) when planning allows
148
+ - `module-implementation` `ui` / `logic` — required per-module UI and logic port
149
+ - `module-node-review-fix` `fix` — scoped remediation in `allowed_files`
150
+ - `global-migration-phase` `integrate` — cross-module glue and entry-point wiring
151
+ - **Read-only on target**: `target-project-assistant`, `migration-planning-gate`, `migration-verification`, `global-migration-phase` `align`, `completion-report`.
126
152
  - Analyst **P6** required; TPA owns all target Q&A.
127
153
  - Mode boundaries non-negotiable: `ui`/`logic`, `integrate`/`align`, `review`/`fix`.
128
154
  - No full project build in migrator.
@@ -16,11 +16,20 @@
16
16
  - **Forbidden**: `incremental_build` in migrator.
17
17
  - **kmp-test-validator**: full compile/build/test at package **V0**.
18
18
 
19
+ ## Target KMP Edit Mandate
20
+
21
+ - Analyst **P6** is read-only input. Migrator success requires **editing the target KMP project** at `kmp_target_project_path`.
22
+ - **Edit-owning roles**: `migration-prep` (optional scaffold), `module-implementation` `ui`/`logic` (required), `module-node-review-fix` `fix`, `global-migration-phase` `integrate`.
23
+ - **Read-only on target**: TPA, `migration-planning-gate`, `migration-verification`, `global-migration-phase` `align`, `completion-report`.
24
+ - When planning tasks require file changes, `changed_files[]` MUST be non-empty and paths MUST resolve under `kmp_target_project_path`.
25
+ - `migration_report.json` MUST aggregate `target_changed_files[]` before **V0**.
26
+
19
27
  ## Behavioral Constraints
20
28
 
21
29
  - **Skill chain (mandatory)**:
22
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.
23
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`.
24
33
  - **Role schedule**: dispatch only role IDs listed in [SKILL.md](SKILL.md).
25
34
  - **Mode discipline**:
26
35
  - `module-implementation`: `ui` then `logic` — separate invocations
@@ -36,6 +45,9 @@
36
45
  | Failure | Response |
37
46
  |---|---|
38
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 |
39
51
  | `ui` and `logic` combined | Reject invocation |
40
52
  | `integrate` and `align` combined | Reject invocation |
41
53
  | Verification restoration failed | Rerun `module-implementation` or `migration-prep`; no completion record |
@@ -44,6 +56,9 @@
44
56
  | Build requested in migrator | Block; route to kmp-test-validator |
45
57
  | Migrator invoked before analyst P6 | Block; dispatch `android-project-analyst` first |
46
58
  | V0 ready but validator not invoked | Block; dispatch `kmp-test-validator` at MG17 |
59
+ | Analysis/planning only — no target edits when required | Block package M3; rerun `module-implementation` or `migration-prep` |
60
+ | `changed_files` outside `kmp_target_project_path` | Block; rerun owning edit role |
61
+ | Empty `target_changed_files` in `migration_report.json` when scope required edits | Block package V0 |
47
62
 
48
63
  ## Dependencies
49
64
 
@@ -128,13 +128,26 @@ output_root = <output_dir or ~/.a2c_agents/migration>/android-to-kmp-migrator
128
128
  - **`incremental_build` is forbidden** in `migration-verification` during migrator runs.
129
129
  - **Full compile/build/preview/behavioral tests** are delegated to **`kmp-test-validator`** after `migration_report.*` handoff package **`V0`** is ready.
130
130
 
131
+ ### Target KMP Edit Mandate (mandatory)
132
+
133
+ - **Purpose**: `android-project-analyst` **P6** supplies Legacy Android understanding only. The migrator MUST translate that understanding into **concrete edits** in the existing KMP target at `kmp_target_project_path`.
134
+ - **Analysis alone is not migration**: planning gates, prep handoffs, TPA alignment, and representations do **not** satisfy migration without target file changes where tasks require implementation.
135
+ - **Edit-owning roles** (each MUST record paths under `kmp_target_project_path`):
136
+ - `migration-prep` → `migration_prep.json` → `changed_files[]` (optional scaffold when planning allows)
137
+ - `module-implementation` `ui` / `logic` → `module_implementation_ui.json` / `module_implementation_logic.json` → `changed_files[]`, `target_edit_summary`
138
+ - `module-node-review-fix` `fix` → `module_node_fix.json` → `changed_files[]`
139
+ - `global-migration-phase` `integrate` → `global_system_integration.json` → `integration_changed_files[]`, `entry_point_wiring[]`
140
+ - **Read-only on target**: `target-project-assistant`, `migration-planning-gate`, `migration-verification`, `global-migration-phase` `align`, `completion-report`.
141
+ - **Fail closed**: when `migration_planning_gate.json` → `planning.tasks[]` includes file-changing work for a module, package **M3** is false if both `module_implementation_ui.json` and `module_implementation_logic.json` have empty `changed_files[]` or paths resolve outside `kmp_target_project_path`.
142
+ - **Aggregation**: `migration_report.json` MUST include `target_changed_files[]` — deduplicated union of all module and global integrate target paths with `owning_role` and `migration_module_id` (or `global` for integrate).
143
+
131
144
  ---
132
145
 
133
146
  ## Write Order (Leader Schedule)
134
147
 
135
148
  | Step | Gate | Required artifacts before next step |
136
149
  |---|---|---|
137
- | `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` |
138
151
  | `MG1` | Workspace init | global `migration_workspace_state.*` |
139
152
  | `MG2` | Migration index | `migration_module_inventory.*`, `modules_migration_index.json`, per-module `module_brief.json` |
140
153
  | `MG3` | Target baseline | global `target-project-assistant/*` (`mode: global_baseline`) + `target_alignment_revision.*` |
@@ -184,8 +197,9 @@ output_root = <output_dir or ~/.a2c_agents/migration>/android-to-kmp-migrator
184
197
  | `migration-planning-gate/migration_planning_gate.json` |
185
198
  | `migration-prep/migration_prep.json` |
186
199
  | `module-implementation/ui/module_implementation_ui.json`, `module-implementation/logic/module_implementation_logic.json` + approved reviews |
187
- | `migration_verification.json` with all required `check_ids` passed |
188
- | `module_completion_record.json` with `ui_restoration` and `logic_restoration` passed |
200
+ | **Target edits**: when planning tasks require file changes, both UI and logic implementation artifacts MUST have non-empty `changed_files[]` under `kmp_target_project_path` |
201
+ | `migration_verification.json` with all required `check_ids` passed (including `target_files_exist` when `changed_files` non-empty) |
202
+ | `module_completion_record.json` with `ui_restoration` and `logic_restoration` passed and `target_changed_files[]` listing module target paths |
189
203
 
190
204
  ### Package `M4` — All modules migrated
191
205
 
@@ -199,7 +213,7 @@ output_root = <output_dir or ~/.a2c_agents/migration>/android-to-kmp-migrator
199
213
  | Required paths |
200
214
  |---|
201
215
  | Package `M4` |
202
- | `global/node-results/global-migration-phase/integrate/global_system_integration.json` |
216
+ | `global/node-results/global-migration-phase/integrate/global_system_integration.json` with non-empty `integration_changed_files[]` when cross-module glue or entry-point wiring is required |
203
217
  | `global_migration_representation.json` |
204
218
 
205
219
  ### Package `M6` — Post-integration alignment passed
@@ -215,7 +229,7 @@ output_root = <output_dir or ~/.a2c_agents/migration>/android-to-kmp-migrator
215
229
  | Required paths |
216
230
  |---|
217
231
  | Package `M6` |
218
- | `report/migration_report.json` |
232
+ | `report/migration_report.json` with non-empty `target_changed_files[]` when any scheduled module required implementation |
219
233
  | `global_migration_representation.json` |
220
234
  | analyst `SPEC/*` paths recorded in `run_manifest.json` |
221
235
 
@@ -227,6 +241,30 @@ output_root = <output_dir or ~/.a2c_agents/migration>/android-to-kmp-migrator
227
241
 
228
242
  ## Key Artifact Content Requirements
229
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
+
230
268
  ### `upstream_analyst_index.json`
231
269
 
232
270
  ```json
@@ -262,8 +300,10 @@ Machine lookup: `migration_module_id` → `legacy_module_id`, `module_output_roo
262
300
  {
263
301
  "migration_module_id": "",
264
302
  "legacy_module_id": "",
303
+ "kmp_target_project_path": "",
265
304
  "completion_status": "completed | needs_rerun | blocked",
266
305
  "verification_ref": "",
306
+ "target_changed_files": [{ "path": "", "owning_role": "migration-prep | module-implementation | module-node-review-fix", "mode": "ui | logic | fix | null" }],
267
307
  "ui_restoration": { "status": "passed | failed", "gaps": [] },
268
308
  "logic_restoration": { "status": "passed | failed", "gaps": [] },
269
309
  "upstream_match": { "module_representation_path": "", "matched_claims": [], "missing_claims": [] },
@@ -274,6 +314,7 @@ Machine lookup: `migration_module_id` → `legacy_module_id`, `module_output_roo
274
314
 
275
315
  ### `migration_verification.json` — required `check_ids` (migrator only)
276
316
 
317
+ - `target_files_exist` — every path in aggregated module `changed_files[]` exists on disk under `kmp_target_project_path`
277
318
  - `source_set` — files in allowed source sets
278
319
  - `syntax_check` — Kotlin/syntax validity on changed files (static; no full project compile)
279
320
  - `api_contract` — API/model shape vs planning + analyst data contracts
@@ -311,14 +352,16 @@ Human/agent-readable synthesis of align mode; includes `entry_point_alignment_re
311
352
 
312
353
  ## Leader Obligations
313
354
 
314
- 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.
315
356
  2. Dispatch `target-project-assistant` for all target-project questions; other roles MUST reference TPA artifacts instead of re-analyzing target ad hoc.
316
- 3. Write `module_completion_record.json` after each module passes `migration-verification`.
317
- 4. Run `global-migration-phase` `integrate` only after package `M4`.
318
- 5. Run `global-migration-phase` `align` only after integrate; **no code changes** in align mode.
319
- 6. Dispatch only role IDs listed in [SKILL.md](SKILL.md).
320
- 7. Set `handoff_gates` (`M0`–`M6`, `V0`) in workspace ledger and `migration_report.json`.
321
- 8. **MUST** invoke `kmp-test-validator` when `V0` is true (MG17). Do not end the migration workflow without validator dispatch or explicit validator blockers in `migration_report.json`.
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`.
358
+ 4. Write `module_completion_record.json` after each module passes `migration-verification`; include aggregated `target_changed_files[]` for the module.
359
+ 5. Run `global-migration-phase` `integrate` only after package `M4`; integrate MUST edit target glue when assembly requires it.
360
+ 6. Run `global-migration-phase` `align` only after integrate; **no code changes** in align mode.
361
+ 7. Dispatch only role IDs listed in [SKILL.md](SKILL.md).
362
+ 8. Set `handoff_gates` (`M0`–`M6`, `V0`) in workspace ledger and `migration_report.json`.
363
+ 9. Aggregate all module and global integrate target paths into `migration_report.json` → `target_changed_files[]`.
364
+ 10. **MUST** invoke `kmp-test-validator` when `V0` is true (MG17). Do not end the migration workflow without validator dispatch or explicit validator blockers in `migration_report.json`.
322
365
 
323
366
  ## Invalid Artifact Handling
324
367
 
@@ -331,3 +374,9 @@ Human/agent-readable synthesis of align mode; includes `entry_point_alignment_re
331
374
  | `module_completion_record` failed | Re-enter module loop from routed node |
332
375
  | `post_integration_alignment` omissions | Rerun listed modules or `global-migration-phase integrate` |
333
376
  | Full build requested during migrator | Reject — route to `kmp-test-validator` |
377
+ | Planning complete but `changed_files[]` empty when tasks require edits | `needs_rerun` → `module-implementation` or `migration-prep` |
378
+ | `changed_files` paths outside `kmp_target_project_path` | `blocked` — reject artifact; rerun owning role |
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.