@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.
- package/README.md +38 -14
- package/bin/awk.js +438 -86
- package/bin/claude-generators.js +3 -1
- package/bin/cline-generators.js +3 -1
- package/bin/codex-generators.js +7 -2
- package/core/orchestrator.md +17 -3
- package/core/skill-runtime-manifest.json +37 -0
- package/package.json +2 -2
- package/skill-packs/creator-studio/README.md +19 -0
- package/skill-packs/creator-studio/pack.json +10 -0
- package/skill-packs/marketing/README.md +64 -0
- package/skill-packs/marketing/pack.json +37 -0
- package/skill-packs/mobile-android/README.md +16 -0
- package/skill-packs/mobile-android/pack.json +10 -0
- package/skill-packs/mobile-ios/README.md +22 -0
- package/skill-packs/mobile-ios/pack.json +16 -0
- package/skill-packs/neural-memory/pack.json +8 -2
- package/skill-packs/superpowers/pack.json +1 -0
- package/skill-packs/superpowers/skills/single-flow-task-execution/SKILL.md +59 -358
- package/skill-packs/superpowers/skills/writing-skills/SKILL.md +60 -654
- package/skill-packs/superpowers/skills/writing-skills/examples/anti-rationalization.md +75 -0
- package/skill-packs/superpowers/skills/writing-skills/examples/cso-optimization.md +67 -0
- package/skill-packs/superpowers/skills/writing-skills/examples/tdd-for-skills.md +63 -0
- package/skills/CATALOG.md +49 -44
- package/skills/brainstorm-agent/SKILL.md +55 -315
- package/skills/brainstorm-agent/templates/brief-template.md +76 -0
- package/skills/codex-conductor/SKILL.md +55 -259
- package/skills/codex-conductor/examples/prompt-templates.md +72 -0
- package/skills/module-spec-writer/SKILL.md +38 -365
- package/skills/module-spec-writer/examples/port-migration-mode.md +40 -0
- package/skills/module-spec-writer/templates/module-spec-template.md +118 -0
- package/skills/orchestrator/SKILL.md +17 -8
- package/skills/single-flow-task-execution/SKILL.md +56 -363
- package/skills/single-flow-task-execution/examples/workflow-example.md +91 -0
- package/skills/smali-to-kotlin/SKILL.md +23 -416
- package/skills/smali-to-kotlin/examples/getting-started/tech-stack.md +58 -0
- package/skills/smali-to-kotlin/examples/pipeline/data-ui-parity.md +118 -0
- package/skills/smali-to-kotlin/examples/pipeline/scanner-and-bootstrap.md +106 -0
- package/skills/smali-to-swift/SKILL.md +18 -621
- package/skills/smali-to-swift/examples/getting-started/tech-stack.md +72 -0
- package/skills/smali-to-swift/examples/getting-started/toolchain.md +32 -0
- package/skills/smali-to-swift/examples/pipeline/core-logic.md +45 -0
- package/skills/smali-to-swift/examples/pipeline/data-layer.md +76 -0
- package/skills/smali-to-swift/examples/pipeline/framework-scanner.md +73 -0
- package/skills/smali-to-swift/examples/pipeline/project-bootstrap.md +76 -0
- package/skills/smali-to-swift/examples/pipeline/sdk-integration.md +66 -0
- package/skills/smali-to-swift/examples/pipeline/ui-viewmodel.md +96 -0
- package/skills/smali-to-swift/references/objc-to-swift-mapping.md +57 -0
- package/skills/spec-gate/SKILL.md +51 -265
- package/skills/spec-gate/templates/design-templates.md +93 -0
- package/skills/symphony-enforcer/SKILL.md +24 -555
- package/skills/symphony-enforcer/examples/startup-protocol.md +92 -0
- package/skills/symphony-enforcer/examples/task-completion.md +100 -0
- package/skills/symphony-enforcer/examples/three-phase.md +107 -0
- package/skills/symphony-enforcer/examples/trigger-points.md +99 -0
- package/skills/symphony-orchestrator/SKILL.md +1 -1
- package/skills/writing-skills/SKILL.md +82 -70
- package/skills/writing-skills/examples/anti-rationalization.md +53 -0
- package/skills/writing-skills/examples/cso-optimization.md +52 -0
- package/skills/writing-skills/examples/tdd-for-skills.md +48 -0
- package/templates/help.html +447 -0
- package/skills/memory-sync/SKILL.md +0 -424
- package/skills/memory-sync/memory-router.md +0 -185
- 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
|
|
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
|
-
|
|
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'
|
|
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 =
|
|
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
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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
|
-
|
|
505
|
-
|
|
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
|
-
|
|
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 (
|
|
523
|
-
dim(`Packs: ${
|
|
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
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
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
|
|
1146
|
-
const
|
|
1147
|
-
if (
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
const
|
|
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: ${
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
1373
|
-
const
|
|
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 =
|
|
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
|
|
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,
|
|
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
|
-
*
|
|
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
|
-
// ──
|
|
2145
|
-
const
|
|
2146
|
-
|
|
2147
|
-
|
|
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 .
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
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 ──────────────────────────────
|