@code-migration/wow-migrator 0.1.0

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 (55) hide show
  1. package/README.md +113 -0
  2. package/bin/kmp-skills.js +407 -0
  3. package/package.json +48 -0
  4. package/scripts/sync-skills.js +74 -0
  5. package/skills/android-project-analyst/MIGRATION.md +37 -0
  6. package/skills/android-project-analyst/SKILL.md +115 -0
  7. package/skills/android-project-analyst/bind.md +49 -0
  8. package/skills/android-project-analyst/dependencies.yaml +16 -0
  9. package/skills/android-project-analyst/roles/android-ecosystem.md +141 -0
  10. package/skills/android-project-analyst/roles/api-list.md +136 -0
  11. package/skills/android-project-analyst/roles/architecture-pattern.md +131 -0
  12. package/skills/android-project-analyst/roles/data-flow.md +143 -0
  13. package/skills/android-project-analyst/roles/logic-understand.md +154 -0
  14. package/skills/android-project-analyst/roles/resource-understand.md +151 -0
  15. package/skills/android-project-analyst/roles/ui-understand.md +136 -0
  16. package/skills/android-project-analyst/workflow.md +132 -0
  17. package/skills/android-to-kmp-migrator/MIGRATION.md +44 -0
  18. package/skills/android-to-kmp-migrator/SKILL.md +203 -0
  19. package/skills/android-to-kmp-migrator/bind.md +54 -0
  20. package/skills/android-to-kmp-migrator/dependencies.yaml +21 -0
  21. package/skills/android-to-kmp-migrator/roles/api-contract-parity.md +95 -0
  22. package/skills/android-to-kmp-migrator/roles/dataflow-logic-implementation.md +130 -0
  23. package/skills/android-to-kmp-migrator/roles/dependency-resolution.md +106 -0
  24. package/skills/android-to-kmp-migrator/roles/incremental-build-check.md +105 -0
  25. package/skills/android-to-kmp-migrator/roles/legacy-spec-delta-review.md +104 -0
  26. package/skills/android-to-kmp-migrator/roles/migration-alignment.md +119 -0
  27. package/skills/android-to-kmp-migrator/roles/migration-report.md +108 -0
  28. package/skills/android-to-kmp-migrator/roles/migration-workspace-state.md +100 -0
  29. package/skills/android-to-kmp-migrator/roles/module-node-migration-fix.md +111 -0
  30. package/skills/android-to-kmp-migrator/roles/module-node-migration-review.md +108 -0
  31. package/skills/android-to-kmp-migrator/roles/navigation-migration.md +104 -0
  32. package/skills/android-to-kmp-migrator/roles/platform-api-replacement.md +104 -0
  33. package/skills/android-to-kmp-migrator/roles/prd-completion-check.md +124 -0
  34. package/skills/android-to-kmp-migrator/roles/resource-migration.md +109 -0
  35. package/skills/android-to-kmp-migrator/roles/source-set-placement-guard.md +95 -0
  36. package/skills/android-to-kmp-migrator/roles/state-model-mapping.md +109 -0
  37. package/skills/android-to-kmp-migrator/roles/target-project-understand.md +118 -0
  38. package/skills/android-to-kmp-migrator/roles/theme-design-system-mapping.md +101 -0
  39. package/skills/android-to-kmp-migrator/roles/ui-mockup-implementation.md +121 -0
  40. package/skills/android-to-kmp-migrator/roles/ui-render-fidelity-check.md +100 -0
  41. package/skills/android-to-kmp-migrator/workflow.md +180 -0
  42. package/skills/kmp-test-validator/MIGRATION.md +43 -0
  43. package/skills/kmp-test-validator/SKILL.md +137 -0
  44. package/skills/kmp-test-validator/bind.md +53 -0
  45. package/skills/kmp-test-validator/dependencies.yaml +17 -0
  46. package/skills/kmp-test-validator/roles/android-kmp-fidelity-audit.md +102 -0
  47. package/skills/kmp-test-validator/roles/build-preview-gate.md +109 -0
  48. package/skills/kmp-test-validator/roles/kmp-validation-plan.md +108 -0
  49. package/skills/kmp-test-validator/roles/test-case-decomposition.md +103 -0
  50. package/skills/kmp-test-validator/roles/test-execution.md +104 -0
  51. package/skills/kmp-test-validator/roles/validation-input-contract.md +111 -0
  52. package/skills/kmp-test-validator/roles/validation-remediation.md +112 -0
  53. package/skills/kmp-test-validator/roles/validation-report.md +114 -0
  54. package/skills/kmp-test-validator/roles/validation-workspace-state.md +102 -0
  55. package/skills/kmp-test-validator/workflow.md +151 -0
package/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # KMP Skills Installer
2
+
3
+ Install the KMP migration skills into common AI coding tools through `npm install`.
4
+
5
+ ## Install From This Repo
6
+
7
+ The package is not published to the npm registry yet. Install it from the local package directory:
8
+
9
+ ```bash
10
+ npm install -g /Users/winson/CodeBase/Online/cli-plugins/npx_skills
11
+ ```
12
+
13
+ Or, from the repository root:
14
+
15
+ ```bash
16
+ npm install -g ./npx_skills
17
+ ```
18
+
19
+ To install from a tarball:
20
+
21
+ ```bash
22
+ cd /Users/winson/CodeBase/Online/cli-plugins/npx_skills
23
+ npm pack
24
+ npm install -g ./code-migration-wow-migrator-0.1.0.tgz
25
+ ```
26
+
27
+ After publishing this package to npm, the registry install command will be:
28
+
29
+ ```bash
30
+ npm install -g @code-migration/wow-migrator
31
+ ```
32
+
33
+ The package runs `postinstall` and installs bundled skills into detected tools.
34
+ Set `KMP_SKILLS_SKIP_POSTINSTALL=1` to skip the automatic install.
35
+
36
+ ## Commands
37
+
38
+ ```bash
39
+ kmp-skills install --yes
40
+ kmp-skills install --target "Claude Code,Codex"
41
+ kmp-skills uninstall --target all --yes
42
+ kmp-skills list
43
+ kmp-skills config
44
+ ```
45
+
46
+ ## Supported Targets
47
+
48
+ | Tool | Detection | Skills directory |
49
+ | --- | --- | --- |
50
+ | OpenClaw | `~/.openclaw` or `openclaw` | `~/.openclaw/skills` |
51
+ | Claude Code | `~/.claude` or `claude` | `~/.claude/skills` |
52
+ | OpenCode | `~/.config/opencode` or `opencode` | `~/.config/opencode/skills` |
53
+ | Codex | `~/.codex` or `codex` | `~/.codex/skills` |
54
+ | Cursor | `~/.cursor` or `cursor` | `~/.cursor/skills` |
55
+ | Gemini | `~/.gemini` or `gemini` | `~/.gemini/skills` |
56
+ | JiuwenSwarm | `~/.jiuwenswarm` or Jiuwen CLI commands | `~/.jiuwenswarm/agent/workspace/skills` |
57
+
58
+ ## Configuration
59
+
60
+ The installer creates:
61
+
62
+ ```text
63
+ ~/.kmp-skills/config.json
64
+ ```
65
+
66
+ Edit it to add custom tools or paths. The shape is:
67
+
68
+ ```json
69
+ {
70
+ "tools": [
71
+ {
72
+ "name": "Claude Code",
73
+ "markerDir": "~/.claude",
74
+ "commands": ["claude"],
75
+ "skillsDir": "~/.claude/skills"
76
+ }
77
+ ]
78
+ }
79
+ ```
80
+
81
+ ## Publishing From This Repo
82
+
83
+ Before packing or publishing, sync the current plugin skills into this package:
84
+
85
+ ```bash
86
+ cd npx_skills
87
+ npm run sync:skills
88
+ npm pack
89
+ ```
90
+
91
+ `prepare` and `prepack` also run the sync script when the monorepo source is available.
92
+
93
+ Publish with the existing `code-migration` npm org scope:
94
+
95
+ ```bash
96
+ npm publish --access public
97
+ ```
98
+
99
+ If npm returns `E404 Scope not found`, confirm that the npm org exists and the
100
+ current npm user has publish access:
101
+
102
+ ```bash
103
+ npm whoami
104
+ npm org ls code-migration
105
+ ```
106
+
107
+ For this package, `npm org ls code-migration` should list your user with owner
108
+ or publish-capable access. After that, publish and install with:
109
+
110
+ ```bash
111
+ npm publish --access public
112
+ npm install -g @code-migration/wow-migrator
113
+ ```
@@ -0,0 +1,407 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execFile } from 'node:child_process';
4
+ import fs from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import readline from 'node:readline/promises';
9
+
10
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
11
+ const CONFIG_DIR = path.join(os.homedir(), '.kmp-skills');
12
+ const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json');
13
+ const isWindows = process.platform === 'win32';
14
+
15
+ function expandHome(input) {
16
+ if (!input) return input;
17
+ if (input === '~') return os.homedir();
18
+ if (input.startsWith('~/') || input.startsWith('~\\')) {
19
+ return path.join(os.homedir(), input.slice(2));
20
+ }
21
+ return input;
22
+ }
23
+
24
+ function prettyPath(input) {
25
+ const home = os.homedir();
26
+ const normalized = input.replace(/\\/g, '/');
27
+ const normalizedHome = home.replace(/\\/g, '/');
28
+ return normalized.startsWith(normalizedHome)
29
+ ? `~${normalized.slice(normalizedHome.length)}`
30
+ : normalized;
31
+ }
32
+
33
+ async function pathExists(input) {
34
+ try {
35
+ await fs.access(input);
36
+ return true;
37
+ } catch {
38
+ return false;
39
+ }
40
+ }
41
+
42
+ async function dirExists(input) {
43
+ try {
44
+ return (await fs.stat(input)).isDirectory();
45
+ } catch {
46
+ return false;
47
+ }
48
+ }
49
+
50
+ function execFileAsync(command, args, options = {}) {
51
+ return new Promise((resolve, reject) => {
52
+ execFile(command, args, options, (error, stdout, stderr) => {
53
+ if (error) {
54
+ reject(error);
55
+ return;
56
+ }
57
+ resolve({ stdout, stderr });
58
+ });
59
+ });
60
+ }
61
+
62
+ async function commandExists(command) {
63
+ if (!command) return false;
64
+ const hasPathSeparator = command.includes('/') || command.includes('\\');
65
+ if (hasPathSeparator) return pathExists(command);
66
+
67
+ try {
68
+ if (isWindows) {
69
+ await execFileAsync('where', [command], { windowsHide: true });
70
+ } else {
71
+ await execFileAsync('sh', ['-c', `command -v ${shellQuote(command)}`]);
72
+ }
73
+ return true;
74
+ } catch {
75
+ return false;
76
+ }
77
+ }
78
+
79
+ function shellQuote(value) {
80
+ return `'${String(value).replace(/'/g, `'\\''`)}'`;
81
+ }
82
+
83
+ function defaultJiuwenCommands() {
84
+ const home = os.homedir();
85
+ const bin = isWindows ? 'Scripts' : 'bin';
86
+ const exe = isWindows ? '.exe' : '';
87
+ const venv = process.env.VIRTUAL_ENV
88
+ ? [
89
+ path.join(process.env.VIRTUAL_ENV, bin, `jiuwenswarm-start${exe}`),
90
+ path.join(process.env.VIRTUAL_ENV, bin, `jiuwenswarm-acp${exe}`),
91
+ path.join(process.env.VIRTUAL_ENV, bin, `jiuwenswarm-tui${exe}`)
92
+ ]
93
+ : [];
94
+
95
+ return [
96
+ ...venv,
97
+ 'jiuwenswarm-start',
98
+ 'jiuwenswarm-acp',
99
+ 'jiuwenswarm-tui',
100
+ path.join(home, 'jiuwenclaw', 'bin', `jiuwenswarm-start${exe}`),
101
+ path.join(home, 'jiuwenclaw', 'bin', `jiuwenswarm-acp${exe}`),
102
+ path.join(home, 'jiuwenclaw', 'bin', `jiuwenswarm-tui${exe}`)
103
+ ];
104
+ }
105
+
106
+ function defaultTools() {
107
+ return [
108
+ {
109
+ name: 'OpenClaw',
110
+ markerDir: '~/.openclaw',
111
+ commands: ['openclaw'],
112
+ skillsDir: '~/.openclaw/skills'
113
+ },
114
+ {
115
+ name: 'Claude Code',
116
+ markerDir: '~/.claude',
117
+ commands: ['claude'],
118
+ skillsDir: '~/.claude/skills'
119
+ },
120
+ {
121
+ name: 'OpenCode',
122
+ markerDir: '~/.config/opencode',
123
+ commands: ['opencode'],
124
+ skillsDir: '~/.config/opencode/skills'
125
+ },
126
+ {
127
+ name: 'Codex',
128
+ markerDir: '~/.codex',
129
+ commands: ['codex'],
130
+ skillsDir: '~/.codex/skills'
131
+ },
132
+ {
133
+ name: 'Cursor',
134
+ markerDir: '~/.cursor',
135
+ commands: ['cursor'],
136
+ skillsDir: '~/.cursor/skills'
137
+ },
138
+ {
139
+ name: 'Gemini',
140
+ markerDir: '~/.gemini',
141
+ commands: ['gemini'],
142
+ skillsDir: '~/.gemini/skills'
143
+ },
144
+ {
145
+ name: 'JiuwenSwarm',
146
+ markerDir: '~/.jiuwenswarm',
147
+ commands: defaultJiuwenCommands(),
148
+ skillsDir: '~/.jiuwenswarm/agent/workspace/skills'
149
+ }
150
+ ];
151
+ }
152
+
153
+ function normalizeTool(raw) {
154
+ if (!raw || typeof raw !== 'object' || !raw.name) return null;
155
+ const skillsDir = raw.skillsDir ?? raw.targets?.skillsDir;
156
+ if (!skillsDir) return null;
157
+ return {
158
+ name: String(raw.name),
159
+ markerDir: raw.markerDir ? String(raw.markerDir) : '',
160
+ commands: Array.isArray(raw.commands) ? raw.commands.map(String).filter(Boolean) : [],
161
+ skillsDir: String(skillsDir)
162
+ };
163
+ }
164
+
165
+ async function loadConfig(options = {}) {
166
+ if (!(await pathExists(CONFIG_PATH))) {
167
+ const config = { tools: defaultTools() };
168
+ if (options.writeDefault) {
169
+ await fs.mkdir(CONFIG_DIR, { recursive: true });
170
+ await fs.writeFile(CONFIG_PATH, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
171
+ }
172
+ return config;
173
+ }
174
+
175
+ const raw = await fs.readFile(CONFIG_PATH, 'utf8');
176
+ const parsed = JSON.parse(raw);
177
+ const sourceTools = Array.isArray(parsed.tools)
178
+ ? parsed.tools
179
+ : Array.isArray(parsed.editors)
180
+ ? parsed.editors
181
+ : [];
182
+ const userTools = sourceTools.map(normalizeTool).filter(Boolean);
183
+ const merged = new Map(defaultTools().map((tool) => [tool.name, tool]));
184
+ for (const tool of userTools) merged.set(tool.name, tool);
185
+ return { tools: [...merged.values()] };
186
+ }
187
+
188
+ async function detectTool(tool) {
189
+ const markerFound = tool.markerDir ? await dirExists(expandHome(tool.markerDir)) : false;
190
+ const commandFound = (await Promise.all(tool.commands.map(commandExists))).some(Boolean);
191
+ return {
192
+ installed: markerFound || commandFound,
193
+ markerFound,
194
+ commandFound
195
+ };
196
+ }
197
+
198
+ async function findSkillsRoot() {
199
+ const candidates = [
200
+ path.join(ROOT, 'skills'),
201
+ path.join(process.env.INIT_CWD ?? '', 'claude-code-plugins', 'kmp-migration', 'skills'),
202
+ path.join(ROOT, '..', 'claude-code-plugins', 'kmp-migration', 'skills'),
203
+ path.join(ROOT, '..', '..', 'claude-code-plugins', 'kmp-migration', 'skills')
204
+ ].filter(Boolean);
205
+
206
+ for (const candidate of candidates) {
207
+ const names = await listSkillNames(candidate);
208
+ if (names.length > 0) return candidate;
209
+ }
210
+ return null;
211
+ }
212
+
213
+ async function listSkillNames(skillsRoot) {
214
+ try {
215
+ const entries = await fs.readdir(skillsRoot, { withFileTypes: true });
216
+ const names = [];
217
+ for (const entry of entries) {
218
+ if (!entry.isDirectory()) continue;
219
+ const skillPath = path.join(skillsRoot, entry.name);
220
+ if (await pathExists(path.join(skillPath, 'SKILL.md'))) names.push(entry.name);
221
+ }
222
+ return names.sort();
223
+ } catch {
224
+ return [];
225
+ }
226
+ }
227
+
228
+ async function copySkill(skillName, skillsRoot, targetRoot, dryRun) {
229
+ const source = path.join(skillsRoot, skillName);
230
+ const target = path.join(targetRoot, skillName);
231
+ if (dryRun) return;
232
+ await fs.mkdir(targetRoot, { recursive: true });
233
+ await fs.rm(target, { recursive: true, force: true });
234
+ await fs.cp(source, target, { recursive: true });
235
+ }
236
+
237
+ async function installToTool(tool, skillsRoot, skillNames, dryRun) {
238
+ const targetRoot = expandHome(tool.skillsDir);
239
+ for (const skillName of skillNames) {
240
+ await copySkill(skillName, skillsRoot, targetRoot, dryRun);
241
+ }
242
+ return targetRoot;
243
+ }
244
+
245
+ async function uninstallFromTool(tool, skillNames, dryRun) {
246
+ const targetRoot = expandHome(tool.skillsDir);
247
+ let removed = 0;
248
+ for (const skillName of skillNames) {
249
+ const target = path.join(targetRoot, skillName);
250
+ if (await dirExists(target)) {
251
+ removed++;
252
+ if (!dryRun) await fs.rm(target, { recursive: true, force: true });
253
+ }
254
+ }
255
+ return removed;
256
+ }
257
+
258
+ function parseArgs(argv) {
259
+ const flags = {
260
+ yes: false,
261
+ postinstall: false,
262
+ dryRun: false,
263
+ targets: null
264
+ };
265
+ const positional = [];
266
+
267
+ for (let index = 0; index < argv.length; index++) {
268
+ const arg = argv[index];
269
+ if (arg === '--yes' || arg === '-y') flags.yes = true;
270
+ else if (arg === '--postinstall') flags.postinstall = true;
271
+ else if (arg === '--dry-run') flags.dryRun = true;
272
+ else if (arg === '--target' || arg === '--targets') {
273
+ flags.targets = argv[++index]?.split(',').map((item) => item.trim()).filter(Boolean) ?? [];
274
+ } else if (arg.startsWith('--target=')) {
275
+ flags.targets = arg.slice('--target='.length).split(',').map((item) => item.trim()).filter(Boolean);
276
+ } else {
277
+ positional.push(arg);
278
+ }
279
+ }
280
+ return { command: positional[0] ?? 'install', flags };
281
+ }
282
+
283
+ function selectTools(tools, detections, flags) {
284
+ if (flags.targets?.length) {
285
+ const wanted = new Set(flags.targets.map((target) => target.toLowerCase()));
286
+ if (wanted.has('all')) return tools;
287
+ return tools.filter((tool) => wanted.has(tool.name.toLowerCase()));
288
+ }
289
+ if (flags.yes || flags.postinstall) {
290
+ return tools.filter((tool) => detections.get(tool.name)?.installed);
291
+ }
292
+ return tools;
293
+ }
294
+
295
+ async function confirm(message) {
296
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
297
+ try {
298
+ const answer = await rl.question(`${message} [y/N] `);
299
+ return /^y(es)?$/i.test(answer.trim());
300
+ } finally {
301
+ rl.close();
302
+ }
303
+ }
304
+
305
+ async function installCommand(flags) {
306
+ if (process.env.KMP_SKILLS_SKIP_POSTINSTALL === '1' && flags.postinstall) return;
307
+
308
+ const skillsRoot = await findSkillsRoot();
309
+ if (!skillsRoot) {
310
+ const message = 'No bundled KMP skills found. Run `npm run sync:skills` before publishing this package.';
311
+ if (flags.postinstall) {
312
+ console.warn(`[kmp-skills] ${message}`);
313
+ return;
314
+ }
315
+ throw new Error(message);
316
+ }
317
+
318
+ const skillNames = await listSkillNames(skillsRoot);
319
+ const { tools } = await loadConfig();
320
+ const detections = new Map();
321
+ for (const tool of tools) detections.set(tool.name, await detectTool(tool));
322
+
323
+ const selectedTools = selectTools(tools, detections, flags);
324
+ if (selectedTools.length === 0) {
325
+ console.log('[kmp-skills] No supported AI tools detected. Edit config with `kmp-skills config`.');
326
+ return;
327
+ }
328
+
329
+ if (!flags.yes && !flags.postinstall && !flags.dryRun) {
330
+ const toolList = selectedTools.map((tool) => tool.name).join(', ');
331
+ const ok = await confirm(`Install ${skillNames.length} KMP skills to: ${toolList}?`);
332
+ if (!ok) return;
333
+ }
334
+
335
+ for (const tool of selectedTools) {
336
+ const targetRoot = await installToTool(tool, skillsRoot, skillNames, flags.dryRun);
337
+ const detected = detections.get(tool.name)?.installed ? 'detected' : 'custom';
338
+ console.log(`[kmp-skills] ${flags.dryRun ? 'Would install' : 'Installed'} ${skillNames.length} skills -> ${tool.name} (${detected}) ${prettyPath(targetRoot)}`);
339
+ }
340
+ }
341
+
342
+ async function uninstallCommand(flags) {
343
+ const skillsRoot = await findSkillsRoot();
344
+ const skillNames = skillsRoot ? await listSkillNames(skillsRoot) : [];
345
+ if (skillNames.length === 0) throw new Error('No bundled skill names found; cannot uninstall safely.');
346
+
347
+ const { tools } = await loadConfig();
348
+ const detections = new Map();
349
+ for (const tool of tools) detections.set(tool.name, await detectTool(tool));
350
+ const selectedTools = selectTools(tools, detections, flags);
351
+
352
+ if (!flags.yes && !flags.dryRun) {
353
+ const ok = await confirm(`Remove bundled KMP skills from ${selectedTools.length} target(s)?`);
354
+ if (!ok) return;
355
+ }
356
+
357
+ for (const tool of selectedTools) {
358
+ const removed = await uninstallFromTool(tool, skillNames, flags.dryRun);
359
+ console.log(`[kmp-skills] ${flags.dryRun ? 'Would remove' : 'Removed'} ${removed} skills from ${tool.name}`);
360
+ }
361
+ }
362
+
363
+ async function listCommand() {
364
+ const skillsRoot = await findSkillsRoot();
365
+ const skillNames = skillsRoot ? await listSkillNames(skillsRoot) : [];
366
+ console.log(`Skills root: ${skillsRoot ? prettyPath(skillsRoot) : '(not found)'}`);
367
+ for (const skillName of skillNames) console.log(`- ${skillName}`);
368
+ }
369
+
370
+ async function configCommand() {
371
+ const config = await loadConfig({ writeDefault: true });
372
+ console.log(`Config path: ${CONFIG_PATH}`);
373
+ console.log(JSON.stringify(config, null, 2));
374
+ }
375
+
376
+ function printHelp() {
377
+ console.log(`kmp-skills
378
+
379
+ Usage:
380
+ kmp-skills install [--yes] [--target Claude Code,Codex] [--dry-run]
381
+ kmp-skills uninstall [--yes] [--target all] [--dry-run]
382
+ kmp-skills list
383
+ kmp-skills config
384
+
385
+ Environment:
386
+ KMP_SKILLS_SKIP_POSTINSTALL=1 Skip npm postinstall auto-install.
387
+ `);
388
+ }
389
+
390
+ async function main() {
391
+ const { command, flags } = parseArgs(process.argv.slice(2));
392
+ if (command === 'help' || command === '--help' || command === '-h') {
393
+ printHelp();
394
+ return;
395
+ }
396
+ if (command === 'install' || command === 'init') return installCommand(flags);
397
+ if (command === 'uninstall' || command === 'remove') return uninstallCommand(flags);
398
+ if (command === 'list') return listCommand();
399
+ if (command === 'config') return configCommand();
400
+ throw new Error(`Unknown command: ${command}`);
401
+ }
402
+
403
+ main().catch((error) => {
404
+ const message = error instanceof Error ? error.message : String(error);
405
+ console.error(`[kmp-skills] ${message}`);
406
+ process.exitCode = 1;
407
+ });
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@code-migration/wow-migrator",
3
+ "version": "0.1.0",
4
+ "description": "Install KMP migration skills into Claude Code, Codex, Cursor, Gemini, OpenCode, OpenClaw, and JiuwenSwarm via npm install.",
5
+ "keywords": [
6
+ "android",
7
+ "kmp",
8
+ "kotlin-multiplatform",
9
+ "skills",
10
+ "claude-code",
11
+ "codex",
12
+ "cursor",
13
+ "gemini",
14
+ "opencode"
15
+ ],
16
+ "license": "MIT",
17
+ "author": "winson-AI",
18
+ "type": "module",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/winson-AI/cli-plugins.git"
22
+ },
23
+ "bin": {
24
+ "kmp-skills": "bin/kmp-skills.js"
25
+ },
26
+ "files": [
27
+ "bin",
28
+ "scripts",
29
+ "skills",
30
+ "README.md"
31
+ ],
32
+ "scripts": {
33
+ "install:skills": "node ./bin/kmp-skills.js install",
34
+ "uninstall:skills": "node ./bin/kmp-skills.js uninstall",
35
+ "config": "node ./bin/kmp-skills.js config",
36
+ "list": "node ./bin/kmp-skills.js list",
37
+ "sync:skills": "node ./scripts/sync-skills.js",
38
+ "prepare": "node ./scripts/sync-skills.js --if-present",
39
+ "prepack": "node ./scripts/sync-skills.js",
40
+ "postinstall": "node ./bin/kmp-skills.js install --yes --postinstall"
41
+ },
42
+ "engines": {
43
+ "node": ">=18.0.0"
44
+ },
45
+ "publishConfig": {
46
+ "access": "public"
47
+ }
48
+ }
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
8
+ const ifPresent = process.argv.includes('--if-present');
9
+
10
+ async function pathExists(input) {
11
+ try {
12
+ await fs.access(input);
13
+ return true;
14
+ } catch {
15
+ return false;
16
+ }
17
+ }
18
+
19
+ async function listSkillNames(skillsRoot) {
20
+ try {
21
+ const entries = await fs.readdir(skillsRoot, { withFileTypes: true });
22
+ const names = [];
23
+ for (const entry of entries) {
24
+ if (!entry.isDirectory()) continue;
25
+ if (await pathExists(path.join(skillsRoot, entry.name, 'SKILL.md'))) {
26
+ names.push(entry.name);
27
+ }
28
+ }
29
+ return names.sort();
30
+ } catch {
31
+ return [];
32
+ }
33
+ }
34
+
35
+ async function findSourceSkillsRoot() {
36
+ const candidates = [
37
+ path.join(ROOT, '..', 'claude-code-plugins', 'kmp-migration', 'skills'),
38
+ path.join(ROOT, '..', '..', 'claude-code-plugins', 'kmp-migration', 'skills'),
39
+ path.join(process.env.INIT_CWD ?? '', 'claude-code-plugins', 'kmp-migration', 'skills')
40
+ ].filter(Boolean);
41
+
42
+ for (const candidate of candidates) {
43
+ if ((await listSkillNames(candidate)).length > 0) return candidate;
44
+ }
45
+ return null;
46
+ }
47
+
48
+ async function main() {
49
+ const source = await findSourceSkillsRoot();
50
+ const target = path.join(ROOT, 'skills');
51
+
52
+ if (!source) {
53
+ if (ifPresent) {
54
+ console.log('[sync-skills] source skills not found; skipping.');
55
+ return;
56
+ }
57
+ throw new Error('Unable to find claude-code-plugins/kmp-migration/skills.');
58
+ }
59
+
60
+ await fs.rm(target, { recursive: true, force: true });
61
+ await fs.mkdir(target, { recursive: true });
62
+
63
+ const skillNames = await listSkillNames(source);
64
+ for (const skillName of skillNames) {
65
+ await fs.cp(path.join(source, skillName), path.join(target, skillName), { recursive: true });
66
+ }
67
+
68
+ console.log(`[sync-skills] synced ${skillNames.length} skills from ${source} -> ${target}`);
69
+ }
70
+
71
+ main().catch((error) => {
72
+ console.error(`[sync-skills] ${error instanceof Error ? error.message : String(error)}`);
73
+ process.exitCode = 1;
74
+ });
@@ -0,0 +1,37 @@
1
+ # Conversion Note: `android-project-analyst` → Swarm Skill
2
+
3
+ This skill was converted from a single controller-support skill (a flat SKILL.md registry plus 7 sibling node-spec files) into a compliant **Swarm Skill** using `swarmskill-creator` convert mode.
4
+
5
+ ## Source structure (before)
6
+
7
+ - `SKILL.md` — controller registry describing convert mode, node contracts, dispatch order, and the SPEC output contract.
8
+ - 7 flat node specs at the skill root: `ui-understand.md`, `architecture-pattern.md`, `android-ecosystem.md`, `api-list.md`, `resource-understand.md`, `data-flow.md`, `logic-understand.md`. Each contained Role / Inputs / Mandatory Input Validation & Output Storage / Specific Task / Required Outputs / Return Format / Self-Check.
9
+
10
+ ## What was lost in the pre-swarm form
11
+
12
+ The registry already separated controller from nodes, but it did not encode the team as a first-class artifact: there were no per-role anti-convergence mottos, no `Forbidden`/`Mandatory` boundary blocks the validator could check, no pasteable `Inline Persona` (so each dispatch re-derived the contract by hand), no Mermaid topology making the parallel-then-pipeline shape explicit, and no resource/behavioral guardrails (`max_parallel_teammates`, token/wall-clock budgets, degraded modes). The handoff gates between stages lived only in prose.
13
+
14
+ ## Decomposition
15
+
16
+ - **Pattern: Mixed B + C.** Stage A (`ui-understand`, `architecture-pattern`, `android-ecosystem`, `api-list`) is parallel decomposition (B) over disjoint slices. Stage B (`resource-understand`, `data-flow`) and Stage C (`logic-understand`) form a specialization pipeline (C) with hard handoff gates — each consumes verified upstream outputs and must not rebuild them.
17
+ - **Disjointness check: PASS.** No node's deliverable can substitute for another's — UI surface vs. architecture style vs. platform ecosystem vs. API contracts vs. resources vs. data movement vs. control-flow behavior are mutually exclusive ownership domains, enforced by each role's `## Boundary > Forbidden` naming its siblings.
18
+
19
+ ## Content port map
20
+
21
+ | Source node-spec content | Ported to |
22
+ |---|---|
23
+ | `## Role` first paragraph | role `## Identity` (rewritten as a 1-line motto + context) |
24
+ | `## Specific Task` numbered steps | role `## Inline Persona for Teammate` HANDLER |
25
+ | `## Mandatory Input Validation And Output Storage` | role `## Boundary > Mandatory` + Inline Persona CONTROL block |
26
+ | `Do not:` lists + sibling routing | role `## Boundary > Forbidden` |
27
+ | `## Required Outputs` JSON/MD | role `## Output Schema` + Inline Persona OUTPUTS |
28
+ | `## Return Format` | role Inline Persona RETURN TO CONTROLLER |
29
+ | `## Self-Check` | role `## Success Criteria` |
30
+ | Controller dispatch order + verification | `workflow.md` (staged steps + gates) |
31
+ | Mandatory contract enforcement + agent-only rules | `bind.md` § Behavioral Constraints |
32
+ | Node failure / rerun handling | `bind.md` § Failure Handling |
33
+ | SPEC output contract + MCP context | `SKILL.md` body (preserved) |
34
+
35
+ ## Team-vs-single delta
36
+
37
+ The conversion preserves every source contract while adding: explicit parallel/pipeline topology with verifiable gates, per-role anti-overlap boundaries that name siblings, self-contained pasteable personas (no re-derivation per dispatch), resource/token/wall-clock budgets, and concrete degraded modes for large monorepos and missing tooling. The same-name controller subagent in `kmp-migration/agents/android-project-analyst.md` is unchanged in behavior; its `Control Nodes` table now points at `roles/<id>.md`.