@leejungkiin/awkit 1.4.3 → 1.5.1

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 (64) hide show
  1. package/README.md +38 -14
  2. package/bin/awk.js +438 -86
  3. package/bin/claude-generators.js +3 -1
  4. package/bin/cline-generators.js +3 -1
  5. package/bin/codex-generators.js +7 -2
  6. package/core/orchestrator.md +17 -3
  7. package/core/skill-runtime-manifest.json +37 -0
  8. package/package.json +2 -2
  9. package/skill-packs/creator-studio/README.md +19 -0
  10. package/skill-packs/creator-studio/pack.json +10 -0
  11. package/skill-packs/marketing/README.md +64 -0
  12. package/skill-packs/marketing/pack.json +37 -0
  13. package/skill-packs/mobile-android/README.md +16 -0
  14. package/skill-packs/mobile-android/pack.json +10 -0
  15. package/skill-packs/mobile-ios/README.md +22 -0
  16. package/skill-packs/mobile-ios/pack.json +16 -0
  17. package/skill-packs/neural-memory/pack.json +8 -2
  18. package/skill-packs/superpowers/pack.json +1 -0
  19. package/skill-packs/superpowers/skills/single-flow-task-execution/SKILL.md +59 -358
  20. package/skill-packs/superpowers/skills/writing-skills/SKILL.md +60 -654
  21. package/skill-packs/superpowers/skills/writing-skills/examples/anti-rationalization.md +75 -0
  22. package/skill-packs/superpowers/skills/writing-skills/examples/cso-optimization.md +67 -0
  23. package/skill-packs/superpowers/skills/writing-skills/examples/tdd-for-skills.md +63 -0
  24. package/skills/CATALOG.md +49 -44
  25. package/skills/brainstorm-agent/SKILL.md +55 -315
  26. package/skills/brainstorm-agent/templates/brief-template.md +76 -0
  27. package/skills/codex-conductor/SKILL.md +55 -259
  28. package/skills/codex-conductor/examples/prompt-templates.md +72 -0
  29. package/skills/module-spec-writer/SKILL.md +38 -365
  30. package/skills/module-spec-writer/examples/port-migration-mode.md +40 -0
  31. package/skills/module-spec-writer/templates/module-spec-template.md +118 -0
  32. package/skills/orchestrator/SKILL.md +17 -8
  33. package/skills/single-flow-task-execution/SKILL.md +56 -363
  34. package/skills/single-flow-task-execution/examples/workflow-example.md +91 -0
  35. package/skills/smali-to-kotlin/SKILL.md +23 -416
  36. package/skills/smali-to-kotlin/examples/getting-started/tech-stack.md +58 -0
  37. package/skills/smali-to-kotlin/examples/pipeline/data-ui-parity.md +118 -0
  38. package/skills/smali-to-kotlin/examples/pipeline/scanner-and-bootstrap.md +106 -0
  39. package/skills/smali-to-swift/SKILL.md +18 -621
  40. package/skills/smali-to-swift/examples/getting-started/tech-stack.md +72 -0
  41. package/skills/smali-to-swift/examples/getting-started/toolchain.md +32 -0
  42. package/skills/smali-to-swift/examples/pipeline/core-logic.md +45 -0
  43. package/skills/smali-to-swift/examples/pipeline/data-layer.md +76 -0
  44. package/skills/smali-to-swift/examples/pipeline/framework-scanner.md +73 -0
  45. package/skills/smali-to-swift/examples/pipeline/project-bootstrap.md +76 -0
  46. package/skills/smali-to-swift/examples/pipeline/sdk-integration.md +66 -0
  47. package/skills/smali-to-swift/examples/pipeline/ui-viewmodel.md +96 -0
  48. package/skills/smali-to-swift/references/objc-to-swift-mapping.md +57 -0
  49. package/skills/spec-gate/SKILL.md +51 -265
  50. package/skills/spec-gate/templates/design-templates.md +93 -0
  51. package/skills/symphony-enforcer/SKILL.md +24 -555
  52. package/skills/symphony-enforcer/examples/startup-protocol.md +92 -0
  53. package/skills/symphony-enforcer/examples/task-completion.md +100 -0
  54. package/skills/symphony-enforcer/examples/three-phase.md +107 -0
  55. package/skills/symphony-enforcer/examples/trigger-points.md +99 -0
  56. package/skills/symphony-orchestrator/SKILL.md +1 -1
  57. package/skills/writing-skills/SKILL.md +82 -70
  58. package/skills/writing-skills/examples/anti-rationalization.md +53 -0
  59. package/skills/writing-skills/examples/cso-optimization.md +52 -0
  60. package/skills/writing-skills/examples/tdd-for-skills.md +48 -0
  61. package/templates/help.html +447 -0
  62. package/skills/memory-sync/SKILL.md +0 -424
  63. package/skills/memory-sync/memory-router.md +0 -185
  64. package/skills/memory-sync/memory-templates.md +0 -201
package/bin/awk.js CHANGED
@@ -5,7 +5,8 @@
5
5
  * Unified installer, updater, and manager for AI agent workflows.
6
6
  *
7
7
  * Usage:
8
- * awkit install Install AWK into ~/.gemini/antigravity/
8
+ * awkit install Install AWK into the active platform runtime
9
+ * awkit install --all Install AWK into every supported platform
9
10
  * awkit uninstall Remove AWK from system
10
11
  * awkit update Update to latest version
11
12
  * awkit init Init a new mobile project with Firebase setup
@@ -122,6 +123,9 @@ const SYNC_MAP = {
122
123
  'templates': 'templates',
123
124
  };
124
125
 
126
+ const SKILL_RUNTIME_MANIFEST = path.join(AWK_ROOT, 'core', 'skill-runtime-manifest.json');
127
+ const INSTALL_STATE_FILENAME = '.awkit-install-state.json';
128
+
125
129
  // ─── Colors ──────────────────────────────────────────────────────────────────
126
130
 
127
131
  const C = {
@@ -175,6 +179,156 @@ function promptChoice(question, defaultVal = '') {
175
179
 
176
180
  // ─── Utility Functions ──────────────────────────────────────────────────────
177
181
 
182
+ function readJsonFile(filePath, fallback = null) {
183
+ if (!fs.existsSync(filePath)) return fallback;
184
+ try {
185
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
186
+ } catch (_) {
187
+ return fallback;
188
+ }
189
+ }
190
+
191
+ function listSkillDirs(rootDir) {
192
+ if (!fs.existsSync(rootDir)) return [];
193
+ return fs.readdirSync(rootDir, { withFileTypes: true })
194
+ .filter(d => d.isDirectory() && fs.existsSync(path.join(rootDir, d.name, 'SKILL.md')))
195
+ .map(d => d.name)
196
+ .sort();
197
+ }
198
+
199
+ function loadSkillRuntimeManifest() {
200
+ const manifest = readJsonFile(SKILL_RUNTIME_MANIFEST);
201
+ if (!manifest?.profiles || !manifest.defaultProfile || !manifest.profiles[manifest.defaultProfile]) {
202
+ throw new Error(`Invalid skill runtime manifest: ${SKILL_RUNTIME_MANIFEST}`);
203
+ }
204
+ return manifest;
205
+ }
206
+
207
+ function getDefaultSkillProfileName() {
208
+ return loadSkillRuntimeManifest().defaultProfile;
209
+ }
210
+
211
+ function getDefaultRuntimeSkills() {
212
+ const manifest = loadSkillRuntimeManifest();
213
+ return manifest.profiles[manifest.defaultProfile].skills || [];
214
+ }
215
+
216
+ function getPackSkillNames(packName) {
217
+ const packSrc = path.join(AWK_ROOT, 'skill-packs', packName);
218
+ const config = readJsonFile(path.join(packSrc, 'pack.json'), {});
219
+ if (Array.isArray(config.skills) && config.skills.length > 0) {
220
+ return [...config.skills];
221
+ }
222
+ return listSkillDirs(path.join(packSrc, 'skills'));
223
+ }
224
+
225
+ function resolvePackSkillSources(packName) {
226
+ const packSrc = path.join(AWK_ROOT, 'skill-packs', packName);
227
+ const packSkillsDir = path.join(packSrc, 'skills');
228
+ const localSkills = listSkillDirs(packSkillsDir);
229
+
230
+ if (localSkills.length > 0) {
231
+ return localSkills.map(skill => ({
232
+ name: skill,
233
+ src: path.join(packSkillsDir, skill)
234
+ }));
235
+ }
236
+
237
+ return getPackSkillNames(packName)
238
+ .map(skill => ({
239
+ name: skill,
240
+ src: path.join(AWK_ROOT, 'skills', skill)
241
+ }))
242
+ .filter(item => fs.existsSync(path.join(item.src, 'SKILL.md')));
243
+ }
244
+
245
+ function getManagedSkillNames() {
246
+ const managed = new Set(listSkillDirs(path.join(AWK_ROOT, 'skills')));
247
+ const packsDir = path.join(AWK_ROOT, 'skill-packs');
248
+ if (!fs.existsSync(packsDir)) return managed;
249
+
250
+ const packs = fs.readdirSync(packsDir, { withFileTypes: true }).filter(d => d.isDirectory());
251
+ for (const pack of packs) {
252
+ for (const skill of getPackSkillNames(pack.name)) {
253
+ managed.add(skill);
254
+ }
255
+ }
256
+ return managed;
257
+ }
258
+
259
+ function getPlatformStateRoot(platform = activePlatform) {
260
+ const plat = PLATFORMS[platform];
261
+ switch (platform) {
262
+ case 'cline':
263
+ return path.join(plat.globalRoot, '.clinerules');
264
+ case 'claude':
265
+ return path.join(plat.globalRoot, '.claude');
266
+ default:
267
+ return plat.globalRoot;
268
+ }
269
+ }
270
+
271
+ function getPlatformBackupRoot(platform = activePlatform) {
272
+ return path.join(getPlatformStateRoot(platform), 'backup');
273
+ }
274
+
275
+ function getInstallStatePath(platform = activePlatform) {
276
+ return path.join(getPlatformStateRoot(platform), INSTALL_STATE_FILENAME);
277
+ }
278
+
279
+ function readInstallState(platform = activePlatform) {
280
+ const fallback = {
281
+ version: 1,
282
+ profile: getDefaultSkillProfileName(),
283
+ enabledPacks: []
284
+ };
285
+ return readJsonFile(getInstallStatePath(platform), fallback) || fallback;
286
+ }
287
+
288
+ function writeInstallState(state, platform = activePlatform) {
289
+ const filePath = getInstallStatePath(platform);
290
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
291
+ fs.writeFileSync(filePath, JSON.stringify(state, null, 2) + '\n');
292
+ }
293
+
294
+ function getDesiredSkillSet(enabledPacks = []) {
295
+ const desired = new Set(getDefaultRuntimeSkills());
296
+ for (const packName of enabledPacks) {
297
+ for (const skill of getPackSkillNames(packName)) {
298
+ desired.add(skill);
299
+ }
300
+ }
301
+ return desired;
302
+ }
303
+
304
+ function collectFileBasenames(dir, ext = '*') {
305
+ const result = new Set();
306
+ if (!fs.existsSync(dir)) return result;
307
+
308
+ function walk(current) {
309
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
310
+ if (entry.name === '.DS_Store') continue;
311
+ const fullPath = path.join(current, entry.name);
312
+ if (entry.isDirectory()) {
313
+ walk(fullPath);
314
+ } else if (ext === '*' || entry.name.endsWith(ext)) {
315
+ result.add(entry.name);
316
+ }
317
+ }
318
+ }
319
+
320
+ walk(dir);
321
+ return result;
322
+ }
323
+
324
+ function getPackWorkflowNames(packName) {
325
+ return collectFileBasenames(path.join(AWK_ROOT, 'skill-packs', packName, 'workflows'));
326
+ }
327
+
328
+ function getPackSchemaNames(packName) {
329
+ return collectFileBasenames(path.join(AWK_ROOT, 'skill-packs', packName, 'schemas'), '.json');
330
+ }
331
+
178
332
  /**
179
333
  * Recursively copy directory, preserving structure.
180
334
  * Does NOT overwrite files prefixed with `.` (user configs).
@@ -220,6 +374,49 @@ function copyDirRecursive(src, dest, options = {}) {
220
374
  return count;
221
375
  }
222
376
 
377
+ function copySelectedSkillDirs(srcDir, destDir, selectedSkills) {
378
+ const wanted = new Set(selectedSkills);
379
+ let count = 0;
380
+
381
+ if (!fs.existsSync(srcDir)) return count;
382
+ if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
383
+
384
+ for (const skill of wanted) {
385
+ const skillSrc = path.join(srcDir, skill);
386
+ if (!fs.existsSync(path.join(skillSrc, 'SKILL.md'))) continue;
387
+ const skillDest = path.join(destDir, skill);
388
+ count += copyDirRecursive(skillSrc, skillDest);
389
+ }
390
+
391
+ return count;
392
+ }
393
+
394
+ function backupAndPruneManagedSkills(destDir, desiredSkills) {
395
+ if (!fs.existsSync(destDir)) return 0;
396
+
397
+ const managed = getManagedSkillNames();
398
+ const installed = fs.readdirSync(destDir, { withFileTypes: true })
399
+ .filter(d => d.isDirectory())
400
+ .map(d => d.name);
401
+
402
+ const stale = installed.filter(skill => managed.has(skill) && !desiredSkills.has(skill));
403
+ if (stale.length === 0) return 0;
404
+
405
+ const backupRoot = path.join(getPlatformBackupRoot(), 'pruned-skills');
406
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
407
+ const backupDir = path.join(backupRoot, timestamp);
408
+ fs.mkdirSync(backupDir, { recursive: true });
409
+
410
+ for (const skill of stale) {
411
+ const from = path.join(destDir, skill);
412
+ const to = path.join(backupDir, skill);
413
+ fs.renameSync(from, to);
414
+ dim(`Archived optional skill: ${skill}`);
415
+ }
416
+
417
+ return stale.length;
418
+ }
419
+
223
420
  /**
224
421
  * Flatten workflow category dirs into single global_workflows dir.
225
422
  * workflows/lifecycle/code.md → global_workflows/code.md
@@ -335,15 +532,19 @@ function cmdInstall(args = []) {
335
532
  if (isUpdate) {
336
533
  selectedPlatforms = [getActivePlatform()];
337
534
  } else {
535
+ const platformOrder = ['antigravity', 'cline', 'codex', 'claude'];
536
+ const defaultPlatform = getActivePlatform();
537
+ const defaultChoice = String(Math.max(platformOrder.indexOf(defaultPlatform), 0) + 1);
338
538
  log(`${C.cyan}Select platforms to install (e.g., type "1,2", "all", or "1,2,3,4"):${C.reset}`);
339
539
  log(` 1. Gemini Code Assist (antigravity)`);
340
540
  log(` 2. Cline (VS Code)`);
341
541
  log(` 3. Codex CLI (codex)`);
342
542
  log(` 4. Claude Code (.claude/)`);
343
543
  log(` 5. All of the above`);
344
- const choice = promptChoice('Choice', '5').trim().toLowerCase();
544
+ log(`${C.gray}Press Enter to install only the active platform: ${PLATFORMS[defaultPlatform].name}.${C.reset}`);
545
+ const choice = promptChoice('Choice', defaultChoice).trim().toLowerCase();
345
546
 
346
- if (choice === '5' || choice === 'all' || choice === '') {
547
+ if (choice === '5' || choice === 'all') {
347
548
  selectedPlatforms = Object.keys(PLATFORMS);
348
549
  } else {
349
550
  if (choice.includes('1')) selectedPlatforms.push('antigravity');
@@ -374,6 +575,8 @@ function cmdInstall(args = []) {
374
575
 
375
576
  const plat = PLATFORMS[platform];
376
577
  const target = plat.globalRoot;
578
+ const coreSkills = getDefaultRuntimeSkills();
579
+ const previousState = readInstallState(platform);
377
580
 
378
581
  log('');
379
582
  info(`Installing for ${C.bold}${plat.name}${C.reset}...`);
@@ -419,7 +622,7 @@ function cmdInstall(args = []) {
419
622
 
420
623
  // Backup existing workflows
421
624
  if (fs.existsSync(wfDest)) {
422
- const backupDir = path.join(target, 'backup');
625
+ const backupDir = getPlatformBackupRoot(platform);
423
626
  if (!fs.existsSync(backupDir)) fs.mkdirSync(backupDir, { recursive: true });
424
627
 
425
628
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
@@ -451,24 +654,24 @@ function cmdInstall(args = []) {
451
654
  }
452
655
 
453
656
  // 5. Copy skills
454
- if (plat.dirs.skills) {
455
- info('Installing skills...');
456
- const skillsSrc = path.join(AWK_ROOT, 'skills');
457
- const skillsDest = path.join(target, plat.dirs.skills);
458
-
459
- if (platform === 'cline') {
460
- generateClineSkills(skillsSrc, skillsDest);
461
- } else if (platform === 'codex') {
462
- generateCodexSkills(skillsSrc, skillsDest);
463
- const agentsDest = path.join(target, plat.dirs.agents);
464
- generateCodexAgents(skillsSrc, agentsDest);
465
- } else if (platform === 'claude') {
466
- generateClaudeSkills(skillsSrc, skillsDest);
467
- } else {
468
- const skillCount = copyDirRecursive(skillsSrc, skillsDest);
469
- ok(`${skillCount} skill files installed`);
657
+ if (plat.dirs.skills) {
658
+ info('Installing skills...');
659
+ const skillsSrc = path.join(AWK_ROOT, 'skills');
660
+ const skillsDest = path.join(target, plat.dirs.skills);
661
+
662
+ if (platform === 'cline') {
663
+ generateClineSkills(skillsSrc, skillsDest, coreSkills);
664
+ } else if (platform === 'codex') {
665
+ generateCodexSkills(skillsSrc, skillsDest, coreSkills);
666
+ const agentsDest = path.join(target, plat.dirs.agents);
667
+ generateCodexAgents(skillsSrc, agentsDest, coreSkills);
668
+ } else if (platform === 'claude') {
669
+ generateClaudeSkills(skillsSrc, skillsDest, coreSkills);
670
+ } else {
671
+ const skillCount = copySelectedSkillDirs(skillsSrc, skillsDest, coreSkills);
672
+ ok(`${skillCount} core skill files installed`);
673
+ }
470
674
  }
471
- }
472
675
 
473
676
  // 6. Copy orchestrator
474
677
  if (platform === 'antigravity') {
@@ -501,10 +704,34 @@ function cmdInstall(args = []) {
501
704
  fs.writeFileSync(plat.versionFile, AWK_VERSION);
502
705
  ok(`Version ${AWK_VERSION} saved`);
503
706
 
504
- // 10. Install default skill packs
505
- const defaultPacks = installDefaultPacks();
707
+ // 10. Install default skill packs
708
+ const defaultPacks = installDefaultPacks();
709
+ const activePacks = [...new Set([...(previousState.enabledPacks || []), ...defaultPacks])].sort();
710
+
711
+ for (const packName of activePacks) {
712
+ if (defaultPacks.includes(packName)) continue;
713
+ info(`Re-enabling preserved pack: ${packName}`);
714
+ cmdEnablePack(packName, { autoMode: true });
715
+ }
716
+
717
+ const desiredSkills = getDesiredSkillSet(activePacks);
506
718
 
507
- // 11. Summary
719
+ if (plat.dirs.skills) {
720
+ const skillsDest = path.join(target, plat.dirs.skills);
721
+ const removedSkills = backupAndPruneManagedSkills(skillsDest, desiredSkills);
722
+ if (removedSkills > 0) {
723
+ ok(`${removedSkills} managed optional skill(s) archived from runtime`);
724
+ }
725
+ }
726
+
727
+ writeInstallState({
728
+ version: 1,
729
+ profile: getDefaultSkillProfileName(),
730
+ enabledPacks: activePacks
731
+ }, platform);
732
+ ok(`Install state saved (${desiredSkills.size} active skills)`);
733
+
734
+ // 11. Summary
508
735
  log('');
509
736
  log(`${C.gray}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}`);
510
737
  log(`${C.yellow}${C.bold}🎉 AWK v${AWK_VERSION} installed for ${plat.name}!${C.reset}`);
@@ -519,8 +746,8 @@ function cmdInstall(args = []) {
519
746
  if (plat.rulesFile) {
520
747
  dim(`Global Rules: ${plat.rulesFile}`);
521
748
  }
522
- if (defaultPacks.length > 0) {
523
- dim(`Packs: ${defaultPacks.join(', ')} (auto-enabled)`);
749
+ if (activePacks.length > 0) {
750
+ dim(`Packs: ${activePacks.join(', ')}`);
524
751
  }
525
752
  if (platform === 'antigravity') {
526
753
  dim(`Symphony: task tracking ready`);
@@ -662,6 +889,7 @@ function cmdDoctor() {
662
889
  log('');
663
890
 
664
891
  let issues = 0;
892
+ const installState = readInstallState();
665
893
 
666
894
  // 1. Check GEMINI.md
667
895
  if (fs.existsSync(TARGETS.geminiMd)) {
@@ -695,12 +923,20 @@ function cmdDoctor() {
695
923
  .map(d => d.name);
696
924
  ok(`${skills.length} skills found`);
697
925
 
698
- // Check essential skills
699
- const essentialSkills = ['orchestrator', 'symphony-orchestrator', 'awf-session-restore'];
700
- for (const s of essentialSkills) {
701
- if (!skills.includes(s)) {
702
- warn(`Essential skill missing: ${s}`); issues++;
926
+ // Check expected skills based on manifest and enabled packs
927
+ try {
928
+ const expectedSkills = getDesiredSkillSet(installState.enabledPacks || []);
929
+ let missingSkills = [];
930
+ for (const s of expectedSkills) {
931
+ if (!skills.includes(s)) missingSkills.push(s);
703
932
  }
933
+ if (missingSkills.length > 0) {
934
+ warn(`Missing ${missingSkills.length} expected skill(s): ${missingSkills.join(', ')}`); issues++;
935
+ } else {
936
+ ok('All expected skills are present');
937
+ }
938
+ } catch (e) {
939
+ warn(`Failed to validate expected skills: ${e.message}`); issues++;
704
940
  }
705
941
  } else {
706
942
  err('skills/ directory missing'); issues++;
@@ -726,6 +962,11 @@ function cmdDoctor() {
726
962
  warn('Version file missing. Run "awkit install" first.'); issues++;
727
963
  }
728
964
 
965
+ log('');
966
+ log(`${C.bold}Runtime Profile:${C.reset}`);
967
+ log(` Profile: ${C.cyan}${installState.profile}${C.reset}`);
968
+ log(` Optional packs: ${installState.enabledPacks?.length ? installState.enabledPacks.join(', ') : C.gray + 'none' + C.reset}`);
969
+
729
970
  // Summary
730
971
  log('');
731
972
  if (issues === 0) {
@@ -1142,16 +1383,14 @@ function cmdEnablePack(packName, { autoMode = false } = {}) {
1142
1383
  info(`Enabling skill pack: ${packName}`);
1143
1384
  let totalCount = 0;
1144
1385
 
1145
- // 1. Copy skills/ subdirs → ~/.gemini/antigravity/skills/
1146
- const packSkillsDir = path.join(packSrc, 'skills');
1147
- if (fs.existsSync(packSkillsDir)) {
1148
- const skillDirs = fs.readdirSync(packSkillsDir, { withFileTypes: true }).filter(d => d.isDirectory());
1149
- for (const skillDir of skillDirs) {
1150
- const src = path.join(packSkillsDir, skillDir.name);
1151
- const dest = path.join(TARGETS.antigravity, 'skills', skillDir.name);
1152
- const n = copyDirRecursive(src, dest);
1386
+ // 1. Copy pack skills into runtime.
1387
+ const packSkills = resolvePackSkillSources(packName);
1388
+ if (packSkills.length > 0) {
1389
+ for (const skill of packSkills) {
1390
+ const dest = path.join(TARGETS.antigravity, 'skills', skill.name);
1391
+ const n = copyDirRecursive(skill.src, dest);
1153
1392
  totalCount += n;
1154
- dim(`Skill: ${skillDir.name} (${n} files)`);
1393
+ dim(`Skill: ${skill.name} (${n} files)`);
1155
1394
  }
1156
1395
  }
1157
1396
 
@@ -1178,6 +1417,15 @@ function cmdEnablePack(packName, { autoMode = false } = {}) {
1178
1417
  // Handle pack.json requirements (pip deps, post-install, MCP setup)
1179
1418
  handlePackRequirements(packSrc, packName, { autoMode });
1180
1419
 
1420
+ const state = readInstallState();
1421
+ const enabledPacks = new Set(state.enabledPacks || []);
1422
+ enabledPacks.add(packName);
1423
+ writeInstallState({
1424
+ version: 1,
1425
+ profile: getDefaultSkillProfileName(),
1426
+ enabledPacks: [...enabledPacks].sort()
1427
+ });
1428
+
1181
1429
  log('');
1182
1430
  log(`${C.cyan}👉 Skills available: type skill name in your AI chat.${C.reset}`);
1183
1431
  log(`${C.cyan}👉 Workflows available: use /nm-recall, /memory-audit, etc.${C.reset}`);
@@ -1197,15 +1445,17 @@ function cmdDisablePack(packName) {
1197
1445
  return;
1198
1446
  }
1199
1447
 
1200
- // Get list of skill dirs from pack/skills/
1201
- const packSkillsDir = path.join(packSrc, 'skills');
1202
- const skillDirs = fs.existsSync(packSkillsDir)
1203
- ? fs.readdirSync(packSkillsDir, { withFileTypes: true }).filter(d => d.isDirectory()).map(d => d.name)
1204
- : [];
1448
+ const skillDirs = getPackSkillNames(packName);
1205
1449
 
1206
1450
  const target = path.join(TARGETS.antigravity, 'skills');
1207
- const backupDir = path.join(TARGETS.antigravity, 'backup', 'skills');
1451
+ const backupDir = path.join(getPlatformBackupRoot(), 'skills');
1208
1452
  if (!fs.existsSync(backupDir)) fs.mkdirSync(backupDir, { recursive: true });
1453
+ const coreSkills = new Set(getDefaultRuntimeSkills());
1454
+ const coreSkillsSrc = path.join(AWK_ROOT, 'skills');
1455
+ const workflowTarget = path.join(TARGETS.antigravity, 'global_workflows');
1456
+ const workflowBackupDir = path.join(getPlatformBackupRoot(), 'workflows');
1457
+ const schemaTarget = path.join(TARGETS.antigravity, 'schemas');
1458
+ const schemaBackupDir = path.join(getPlatformBackupRoot(), 'schemas');
1209
1459
 
1210
1460
  for (const skillDir of skillDirs) {
1211
1461
  const destPath = path.join(target, skillDir);
@@ -1213,8 +1463,39 @@ function cmdDisablePack(packName) {
1213
1463
  fs.renameSync(destPath, path.join(backupDir, skillDir));
1214
1464
  dim(`Moved to backup: ${skillDir}`);
1215
1465
  }
1466
+
1467
+ if (coreSkills.has(skillDir)) {
1468
+ const srcPath = path.join(coreSkillsSrc, skillDir);
1469
+ if (fs.existsSync(path.join(srcPath, 'SKILL.md'))) {
1470
+ copyDirRecursive(srcPath, destPath);
1471
+ dim(`Restored core skill: ${skillDir}`);
1472
+ }
1473
+ }
1474
+ }
1475
+
1476
+ if (!fs.existsSync(workflowBackupDir)) fs.mkdirSync(workflowBackupDir, { recursive: true });
1477
+ for (const workflowFile of getPackWorkflowNames(packName)) {
1478
+ const livePath = path.join(workflowTarget, workflowFile);
1479
+ if (!fs.existsSync(livePath)) continue;
1480
+ fs.renameSync(livePath, path.join(workflowBackupDir, workflowFile));
1481
+ dim(`Moved workflow to backup: ${workflowFile}`);
1482
+ }
1483
+
1484
+ if (!fs.existsSync(schemaBackupDir)) fs.mkdirSync(schemaBackupDir, { recursive: true });
1485
+ for (const schemaFile of getPackSchemaNames(packName)) {
1486
+ const livePath = path.join(schemaTarget, schemaFile);
1487
+ if (!fs.existsSync(livePath)) continue;
1488
+ fs.renameSync(livePath, path.join(schemaBackupDir, schemaFile));
1489
+ dim(`Moved schema to backup: ${schemaFile}`);
1216
1490
  }
1217
1491
 
1492
+ const state = readInstallState();
1493
+ writeInstallState({
1494
+ version: 1,
1495
+ profile: getDefaultSkillProfileName(),
1496
+ enabledPacks: (state.enabledPacks || []).filter(name => name !== packName)
1497
+ });
1498
+
1218
1499
  ok(`Skill pack "${packName}" disabled (skills backed up to ${backupDir})`);
1219
1500
  }
1220
1501
 
@@ -1240,11 +1521,16 @@ function cmdListPacks() {
1240
1521
 
1241
1522
  for (const pack of packs) {
1242
1523
  const readmePath = path.join(packsDir, pack.name, 'README.md');
1524
+ const configPath = path.join(packsDir, pack.name, 'pack.json');
1243
1525
  let desc = '';
1244
1526
  if (fs.existsSync(readmePath)) {
1245
1527
  const content = fs.readFileSync(readmePath, 'utf8');
1246
1528
  desc = content.split('\n').find(l => l.trim() && !l.startsWith('#')) || '';
1247
1529
  }
1530
+ if (!desc && fs.existsSync(configPath)) {
1531
+ const config = readJsonFile(configPath, {});
1532
+ desc = config.description || '';
1533
+ }
1248
1534
  log(` ${C.green}${pack.name}${C.reset} ${C.gray}${desc}${C.reset}`);
1249
1535
  }
1250
1536
 
@@ -1340,23 +1626,6 @@ function cmdLint() {
1340
1626
 
1341
1627
  // ─── Status: Diff repo vs installed ──────────────────────────────────────────
1342
1628
 
1343
- /**
1344
- * Collect all .md files under a directory (recursively, flat list of basenames)
1345
- */
1346
- function collectFiles(dir, ext = '.md') {
1347
- const result = new Set();
1348
- if (!fs.existsSync(dir)) return result;
1349
- function walk(current) {
1350
- for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
1351
- if (entry.name === '.DS_Store') continue;
1352
- if (entry.isDirectory()) { walk(path.join(current, entry.name)); }
1353
- else if (entry.name.endsWith(ext) || ext === '*') { result.add(entry.name); }
1354
- }
1355
- }
1356
- walk(dir);
1357
- return result;
1358
- }
1359
-
1360
1629
  function cmdStatus() {
1361
1630
  log('');
1362
1631
  log(`${C.cyan}${C.bold}📊 AWK Status — Repo vs Installed${C.reset}`);
@@ -1364,13 +1633,19 @@ function cmdStatus() {
1364
1633
 
1365
1634
  const repoWfDir = path.join(AWK_ROOT, 'workflows');
1366
1635
  const liveWfDir = path.join(TARGETS.antigravity, 'global_workflows');
1367
- const repoSkillDir = path.join(AWK_ROOT, 'skills');
1368
1636
  const liveSkillDir = path.join(TARGETS.antigravity, 'skills');
1637
+ const installState = readInstallState();
1638
+ const expectedSkills = getDesiredSkillSet(installState.enabledPacks || []);
1369
1639
 
1370
1640
  // ── Workflows ──────────────────────────────────────────────────────────
1371
1641
  log(`${C.bold}Workflows:${C.reset}`);
1372
- const repoWf = collectFiles(repoWfDir);
1373
- const liveWf = collectFiles(liveWfDir);
1642
+ const repoWf = collectFileBasenames(repoWfDir);
1643
+ for (const packName of installState.enabledPacks || []) {
1644
+ for (const wf of getPackWorkflowNames(packName)) {
1645
+ repoWf.add(wf);
1646
+ }
1647
+ }
1648
+ const liveWf = collectFileBasenames(liveWfDir);
1374
1649
 
1375
1650
  const onlyInRepo = [...repoWf].filter(f => !liveWf.has(f));
1376
1651
  const onlyInLive = [...liveWf].filter(f => !repoWf.has(f));
@@ -1393,15 +1668,14 @@ function cmdStatus() {
1393
1668
 
1394
1669
  // ── Skills ─────────────────────────────────────────────────────────────
1395
1670
  log(`${C.bold}Skills:${C.reset}`);
1396
- const repoSkills = fs.existsSync(repoSkillDir)
1397
- ? new Set(fs.readdirSync(repoSkillDir, { withFileTypes: true }).filter(d => d.isDirectory()).map(d => d.name))
1398
- : new Set();
1671
+ const repoSkills = new Set(expectedSkills);
1399
1672
  const liveSkills = fs.existsSync(liveSkillDir)
1400
1673
  ? new Set(fs.readdirSync(liveSkillDir, { withFileTypes: true }).filter(d => d.isDirectory()).map(d => d.name))
1401
1674
  : new Set();
1402
1675
 
1403
1676
  const skillsOnlyRepo = [...repoSkills].filter(s => !liveSkills.has(s));
1404
- const skillsOnlyLive = [...liveSkills].filter(s => !repoSkills.has(s));
1677
+ const managedSkills = getManagedSkillNames();
1678
+ const skillsOnlyLive = [...liveSkills].filter(s => managedSkills.has(s) && !repoSkills.has(s));
1405
1679
  const skillsInBoth = [...repoSkills].filter(s => liveSkills.has(s));
1406
1680
 
1407
1681
  log(` ${C.green}✅ In sync:${C.reset} ${skillsInBoth.length} skills`);
@@ -1417,6 +1691,9 @@ function cmdStatus() {
1417
1691
  log(` ${C.green}Perfect sync! ✨${C.reset}`);
1418
1692
  }
1419
1693
 
1694
+ log(` ${C.gray}Profile:${C.reset} ${installState.profile}`);
1695
+ log(` ${C.gray}Optional packs:${C.reset} ${installState.enabledPacks?.length ? installState.enabledPacks.join(', ') : 'none'}`);
1696
+
1420
1697
  log('');
1421
1698
 
1422
1699
  // ── Versions ───────────────────────────────────────────────────────────
@@ -1584,7 +1861,7 @@ function cmdHelp() {
1584
1861
  log(` ${C.green}init${C.reset} Init mobile project (Firebase) in CWD`);
1585
1862
  log(` ${C.gray} --force${C.reset} Overwrite existing files`);
1586
1863
  log(` ${C.gray} Generates: .project-identity, <Name>.code-workspace,${C.reset}`);
1587
- log(` ${C.gray} CODEBASE.md, .symphony/ (Symphony task DB)${C.reset}`);
1864
+ log(` ${C.gray} CODEBASE.md, thiết lập router (AGENTS.md, CLAUDE.md)${C.reset}`);
1588
1865
  log('');
1589
1866
 
1590
1867
  // Maintenance
@@ -1672,7 +1949,8 @@ function cmdHelp() {
1672
1949
  log(line);
1673
1950
  log(` ${C.cyan}# First time setup${C.reset}`);
1674
1951
  log(` ${C.gray}npm install -g @leejungkiin/awkit${C.reset}`);
1675
- log(` ${C.gray}awkit install${C.reset}`);
1952
+ log(` ${C.gray}awkit install # Active platform only${C.reset}`);
1953
+ log(` ${C.gray}awkit install --all # All supported platforms${C.reset}`);
1676
1954
  log(` ${C.gray}awkit doctor${C.reset}`);
1677
1955
  log('');
1678
1956
  log(` ${C.cyan}# Daily usage${C.reset}`);
@@ -1986,7 +2264,7 @@ function buildCodebaseMd(projectName, projectType, identity) {
1986
2264
  * .project-identity
1987
2265
  * <ProjectName>.code-workspace
1988
2266
  * CODEBASE.md
1989
- * .symphony/ (via Symphony)
2267
+ * Router files (AGENTS.md, CLAUDE.md)
1990
2268
  */
1991
2269
  async function cmdInit(forceFlag = false) {
1992
2270
  const cwd = process.cwd();
@@ -2141,21 +2419,95 @@ async function cmdInit(forceFlag = false) {
2141
2419
  ok('CODEBASE.md created');
2142
2420
  }
2143
2421
 
2144
- // ── 5. Symphony folder ───────────────────────────────────────────────────────
2145
- const symphonyDir = path.join(cwd, '.symphony');
2146
- if (fs.existsSync(symphonyDir) && !forceFlag) {
2147
- warn('.symphony/ folder already exists');
2422
+ // ── 4.1. IDE Agent Router Files (AGENTS.md & CLAUDE.md) ───────────────────
2423
+ const agentRouterContent = `# AI Agent Strategy
2424
+ > **NOTICE:** This project uses Antigravity (AWKit) as the central orchestrator.
2425
+ > All core system rules, behaviors, and workflows are defined globally.
2426
+
2427
+ **CRITICAL INSTRUCTION FOR ALL AI AGENTS:**
2428
+ Before reading any other context or starting work, you MUST read the global system instructions file located at:
2429
+
2430
+ \`\`\`bash
2431
+ cat ~/.gemini/antigravity/GEMINI.md
2432
+ \`\`\`
2433
+
2434
+ If you cannot read the file directly, you must ask the user to provide its contents.
2435
+ Please follow ALL rules defined in that global file.
2436
+
2437
+ Additional project context can be found in:
2438
+ - \`.project-identity\`
2439
+ - \`CODEBASE.md\`
2440
+ `;
2441
+
2442
+ const routerFiles = ['AGENTS.md', 'CLAUDE.md'];
2443
+ for (const file of routerFiles) {
2444
+ const filePath = path.join(cwd, file);
2445
+ if (fs.existsSync(filePath)) {
2446
+ const bakPath = path.join(cwd, `${file}.bak`);
2447
+ fs.copyFileSync(filePath, bakPath);
2448
+ warn(`Backed up existing ${file} to ${file}.bak`);
2449
+ }
2450
+ info(`Creating ${file} (Router to GEMINI.md)...`);
2451
+ fs.writeFileSync(filePath, agentRouterContent);
2452
+ ok(`${file} created/updated`);
2453
+ }
2454
+
2455
+ // ── 4.2. Help Documentation (help.html) ───────────────────────────────────
2456
+ const helpPath = path.join(cwd, 'help.html');
2457
+ if (fs.existsSync(helpPath) && !forceFlag) {
2458
+ warn('help.html already exists — skipping (use --force to overwrite)');
2148
2459
  } else {
2149
- info('Creating .symphony/ folder to mark project context...');
2150
- fs.mkdirSync(symphonyDir, { recursive: true });
2151
- // Create an empty .gitignore just in case
2152
- fs.writeFileSync(path.join(symphonyDir, '.gitignore'), '*\n');
2153
- ok('Symphony project marker created (.symphony/)');
2154
-
2155
- const symReady = checkSymphony({ silent: true });
2156
- if (!symReady) {
2157
- dim('Symphony CLI is not installed. Run: npm i -g @leejungkiin/awkit-symphony');
2460
+ info('Creating help.html...');
2461
+ const tmplHelpPath = path.join(AWK_ROOT, 'templates', 'help.html');
2462
+ if (fs.existsSync(tmplHelpPath)) {
2463
+ fs.copyFileSync(tmplHelpPath, helpPath);
2464
+ ok('help.html created');
2465
+ } else {
2466
+ warn('Template help.html not found, skipped.');
2467
+ }
2468
+ }
2469
+
2470
+ // ── 4.5. .gitignore ────────────────────────────────────────────────────────
2471
+ const gitignorePath = path.join(cwd, '.gitignore');
2472
+ const ignoreRules = ['log.txt', 'tmp/', '.gitnexus/', 'node_modules/'];
2473
+
2474
+ if (fs.existsSync(gitignorePath)) {
2475
+ let content = fs.readFileSync(gitignorePath, 'utf8');
2476
+ let added = 0;
2477
+ for (const rule of ignoreRules) {
2478
+ // Very simple check to avoid duplicates, might not be perfect for regexes but good for simple literal paths.
2479
+ if (!content.includes(rule)) {
2480
+ if (!content.endsWith('\n') && content.length > 0 && added === 0) content += '\n';
2481
+ content += `${rule}\n`;
2482
+ added++;
2483
+ }
2484
+ }
2485
+ if (added > 0) {
2486
+ fs.writeFileSync(gitignorePath, content);
2487
+ ok('Updated .gitignore with AWKit ignore rules');
2488
+ } else {
2489
+ dim('.gitignore already has AWKit ignore rules');
2490
+ }
2491
+ } else {
2492
+ info('Creating .gitignore...');
2493
+ fs.writeFileSync(gitignorePath, ignoreRules.join('\n') + '\n');
2494
+ ok('.gitignore created');
2495
+ }
2496
+
2497
+ // ── 5. Symphony CLI Initialization ───────────────────────────────────────────
2498
+ info('Checking Symphony CLI...');
2499
+ const symReady = checkSymphony({ silent: true });
2500
+ if (!symReady) {
2501
+ info('Symphony CLI is not installed. Installing automatically...');
2502
+ try {
2503
+ execSync('npm install -g @leejungkiin/awkit-symphony', { stdio: 'inherit' });
2504
+ ok('Symphony CLI installed successfully.');
2505
+ } catch (e) {
2506
+ err('Failed to auto-install Symphony CLI.');
2507
+ dim('Please install manually: npm i -g @leejungkiin/awkit-symphony');
2158
2508
  }
2509
+ } else {
2510
+ ok('Symphony CLI is ready.');
2159
2511
  }
2160
2512
 
2161
2513
  // ── 5.5. GitNexus: Code Intelligence Index ──────────────────────────────