@leejungkiin/awkit 1.6.0 → 1.6.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/bin/awk.js +319 -0
- package/package.json +1 -1
- package/skills/gitnexus-intelligence/SKILL.md +25 -0
- package/skills/nm-memory-sync/SKILL.md +0 -10
package/bin/awk.js
CHANGED
|
@@ -2051,6 +2051,18 @@ function cmdHelp() {
|
|
|
2051
2051
|
log(` ${C.gray} --parse-mode <md|html>${C.reset} Formatting mode`);
|
|
2052
2052
|
log('');
|
|
2053
2053
|
|
|
2054
|
+
// GitNexus
|
|
2055
|
+
log(`${C.bold}🔍 GitNexus${C.reset}`);
|
|
2056
|
+
log(line);
|
|
2057
|
+
log(` ${C.green}gitnexus list${C.reset} List all indexed repositories`);
|
|
2058
|
+
log(` ${C.green}gitnexus status${C.reset} Show current project index info`);
|
|
2059
|
+
log(` ${C.green}gitnexus clean${C.reset} Interactive cleanup of old indexes`);
|
|
2060
|
+
log(` ${C.green}gitnexus clean${C.reset} ${C.gray}<name...>${C.reset} Remove specific repo(s) by name`);
|
|
2061
|
+
log(` ${C.gray} --stale${C.reset} Auto-remove repos with missing paths`);
|
|
2062
|
+
log(` ${C.gray} --all${C.reset} Remove all except current project`);
|
|
2063
|
+
log(` ${C.gray} Alias: gn${C.reset}`);
|
|
2064
|
+
log('');
|
|
2065
|
+
|
|
2054
2066
|
// Available packs
|
|
2055
2067
|
const packsDir = path.join(AWK_ROOT, 'skill-packs');
|
|
2056
2068
|
if (fs.existsSync(packsDir)) {
|
|
@@ -3383,6 +3395,309 @@ function checkAutoUpdate() {
|
|
|
3383
3395
|
}
|
|
3384
3396
|
}
|
|
3385
3397
|
|
|
3398
|
+
// ─── GitNexus Management ──────────────────────────────────────────────────────
|
|
3399
|
+
|
|
3400
|
+
const GITNEXUS_REGISTRY = path.join(HOME, '.gitnexus', 'registry.json');
|
|
3401
|
+
|
|
3402
|
+
function readGitnexusRegistry() {
|
|
3403
|
+
if (!fs.existsSync(GITNEXUS_REGISTRY)) return [];
|
|
3404
|
+
try {
|
|
3405
|
+
return JSON.parse(fs.readFileSync(GITNEXUS_REGISTRY, 'utf8'));
|
|
3406
|
+
} catch (_) {
|
|
3407
|
+
return [];
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
|
|
3411
|
+
function writeGitnexusRegistry(registry) {
|
|
3412
|
+
fs.mkdirSync(path.dirname(GITNEXUS_REGISTRY), { recursive: true });
|
|
3413
|
+
fs.writeFileSync(GITNEXUS_REGISTRY, JSON.stringify(registry, null, 2) + '\n');
|
|
3414
|
+
}
|
|
3415
|
+
|
|
3416
|
+
function cmdGitnexus(args) {
|
|
3417
|
+
const sub = args[0] || 'list';
|
|
3418
|
+
|
|
3419
|
+
switch (sub) {
|
|
3420
|
+
case 'list':
|
|
3421
|
+
case 'ls': {
|
|
3422
|
+
const registry = readGitnexusRegistry();
|
|
3423
|
+
if (registry.length === 0) {
|
|
3424
|
+
info('No GitNexus indexed repositories found.');
|
|
3425
|
+
dim('Run `npx gitnexus analyze` in a project to index it.');
|
|
3426
|
+
return;
|
|
3427
|
+
}
|
|
3428
|
+
|
|
3429
|
+
log('');
|
|
3430
|
+
log(`${C.cyan}${C.bold}🔍 GitNexus Indexed Repositories (${registry.length})${C.reset}`);
|
|
3431
|
+
log(`${C.gray}${'─'.repeat(56)}${C.reset}`);
|
|
3432
|
+
|
|
3433
|
+
const cwd = process.cwd();
|
|
3434
|
+
for (const repo of registry) {
|
|
3435
|
+
const isCurrent = repo.path === cwd;
|
|
3436
|
+
const marker = isCurrent ? `${C.green}◉` : `${C.gray}○`;
|
|
3437
|
+
const nameColor = isCurrent ? C.green : C.reset;
|
|
3438
|
+
const stats = repo.stats || {};
|
|
3439
|
+
const indexDate = repo.indexedAt ? new Date(repo.indexedAt).toLocaleDateString() : 'unknown';
|
|
3440
|
+
const pathExists = fs.existsSync(repo.path);
|
|
3441
|
+
const pathStatus = pathExists ? '' : ` ${C.red}(path missing!)${C.reset}`;
|
|
3442
|
+
|
|
3443
|
+
log(` ${marker} ${nameColor}${C.bold}${repo.name}${C.reset}${pathStatus}`);
|
|
3444
|
+
dim(` Path: ${repo.path}`);
|
|
3445
|
+
dim(` Indexed: ${indexDate} | ${stats.nodes || 0} symbols, ${stats.edges || 0} edges, ${stats.processes || 0} flows`);
|
|
3446
|
+
}
|
|
3447
|
+
log('');
|
|
3448
|
+
break;
|
|
3449
|
+
}
|
|
3450
|
+
|
|
3451
|
+
case 'status': {
|
|
3452
|
+
const cwd = process.cwd();
|
|
3453
|
+
const registry = readGitnexusRegistry();
|
|
3454
|
+
const match = registry.find(r => r.path === cwd);
|
|
3455
|
+
|
|
3456
|
+
if (!match) {
|
|
3457
|
+
warn('Current directory is not indexed by GitNexus.');
|
|
3458
|
+
dim('Run `npx gitnexus analyze` to index this project.');
|
|
3459
|
+
return;
|
|
3460
|
+
}
|
|
3461
|
+
|
|
3462
|
+
const stats = match.stats || {};
|
|
3463
|
+
const indexDate = match.indexedAt ? new Date(match.indexedAt) : null;
|
|
3464
|
+
const ageMs = indexDate ? Date.now() - indexDate.getTime() : 0;
|
|
3465
|
+
const ageDays = Math.floor(ageMs / (1000 * 60 * 60 * 24));
|
|
3466
|
+
const stale = ageDays > 7;
|
|
3467
|
+
|
|
3468
|
+
log('');
|
|
3469
|
+
log(`${C.cyan}${C.bold}🔍 GitNexus: ${match.name}${C.reset}`);
|
|
3470
|
+
log(`${C.gray}${'─'.repeat(56)}${C.reset}`);
|
|
3471
|
+
dim(`Path: ${match.path}`);
|
|
3472
|
+
dim(`Storage: ${match.storagePath}`);
|
|
3473
|
+
dim(`Indexed: ${indexDate ? indexDate.toLocaleString() : 'unknown'} (${ageDays}d ago)`);
|
|
3474
|
+
dim(`Last Commit: ${match.lastCommit || 'n/a'}`);
|
|
3475
|
+
dim(`Symbols: ${stats.nodes || 0}`);
|
|
3476
|
+
dim(`Relations: ${stats.edges || 0}`);
|
|
3477
|
+
dim(`Communities: ${stats.communities || 0}`);
|
|
3478
|
+
dim(`Processes: ${stats.processes || 0}`);
|
|
3479
|
+
dim(`Embeddings: ${stats.embeddings || 0}`);
|
|
3480
|
+
|
|
3481
|
+
if (stale) {
|
|
3482
|
+
log('');
|
|
3483
|
+
warn(`Index is ${ageDays} days old. Consider refreshing:`);
|
|
3484
|
+
dim(' npx gitnexus analyze');
|
|
3485
|
+
}
|
|
3486
|
+
|
|
3487
|
+
// Show projectId from .project-identity if available
|
|
3488
|
+
const identityPath = path.join(cwd, '.project-identity');
|
|
3489
|
+
const identity = readJsonFile(identityPath);
|
|
3490
|
+
if (identity && identity.projectId) {
|
|
3491
|
+
dim(`Project ID: ${identity.projectId}`);
|
|
3492
|
+
}
|
|
3493
|
+
log('');
|
|
3494
|
+
break;
|
|
3495
|
+
}
|
|
3496
|
+
|
|
3497
|
+
case 'clean': {
|
|
3498
|
+
const registry = readGitnexusRegistry();
|
|
3499
|
+
if (registry.length === 0) {
|
|
3500
|
+
info('No GitNexus indexed repositories to clean.');
|
|
3501
|
+
return;
|
|
3502
|
+
}
|
|
3503
|
+
|
|
3504
|
+
const cwd = process.cwd();
|
|
3505
|
+
const isStaleMode = args.includes('--stale');
|
|
3506
|
+
const isAllMode = args.includes('--all');
|
|
3507
|
+
|
|
3508
|
+
// Named repos: awkit gn clean <name1> <name2> ...
|
|
3509
|
+
const namedArgs = args.slice(1).filter(a => !a.startsWith('--'));
|
|
3510
|
+
if (namedArgs.length > 0 && !isStaleMode && !isAllMode) {
|
|
3511
|
+
const nameLookup = new Map(registry.map(r => [r.name.toLowerCase(), r]));
|
|
3512
|
+
const matched = [];
|
|
3513
|
+
const notFound = [];
|
|
3514
|
+
|
|
3515
|
+
for (const name of namedArgs) {
|
|
3516
|
+
const entry = nameLookup.get(name.toLowerCase());
|
|
3517
|
+
if (entry) matched.push(entry);
|
|
3518
|
+
else notFound.push(name);
|
|
3519
|
+
}
|
|
3520
|
+
|
|
3521
|
+
if (notFound.length > 0) {
|
|
3522
|
+
warn(`Not found in registry: ${notFound.join(', ')}`);
|
|
3523
|
+
dim('Available repos:');
|
|
3524
|
+
for (const r of registry) dim(` • ${r.name}`);
|
|
3525
|
+
}
|
|
3526
|
+
|
|
3527
|
+
if (matched.length === 0) {
|
|
3528
|
+
info('No matching repos to clean.');
|
|
3529
|
+
return;
|
|
3530
|
+
}
|
|
3531
|
+
|
|
3532
|
+
log('');
|
|
3533
|
+
log(`${C.yellow}${C.bold}🧹 Will remove ${matched.length} repo(s):${C.reset}`);
|
|
3534
|
+
for (const r of matched) {
|
|
3535
|
+
log(` ${C.red}✗${C.reset} ${r.name} — ${r.path}`);
|
|
3536
|
+
}
|
|
3537
|
+
log('');
|
|
3538
|
+
|
|
3539
|
+
if (!promptYN('Confirm?')) {
|
|
3540
|
+
info('Cancelled.');
|
|
3541
|
+
return;
|
|
3542
|
+
}
|
|
3543
|
+
|
|
3544
|
+
const removeNames = new Set(matched.map(r => r.name));
|
|
3545
|
+
for (const r of matched) {
|
|
3546
|
+
if (r.storagePath && fs.existsSync(r.storagePath)) {
|
|
3547
|
+
try {
|
|
3548
|
+
fs.rmSync(r.storagePath, { recursive: true, force: true });
|
|
3549
|
+
dim(`Deleted: ${r.storagePath}`);
|
|
3550
|
+
} catch (e) {
|
|
3551
|
+
warn(`Failed to delete ${r.storagePath}: ${e.message}`);
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
|
|
3556
|
+
const remaining = registry.filter(r => !removeNames.has(r.name));
|
|
3557
|
+
writeGitnexusRegistry(remaining);
|
|
3558
|
+
ok(`Removed ${matched.length} repos. ${remaining.length} remaining.`);
|
|
3559
|
+
return;
|
|
3560
|
+
}
|
|
3561
|
+
|
|
3562
|
+
if (isStaleMode) {
|
|
3563
|
+
// Auto-clean repos whose paths no longer exist
|
|
3564
|
+
const stale = registry.filter(r => !fs.existsSync(r.path));
|
|
3565
|
+
if (stale.length === 0) {
|
|
3566
|
+
ok('No stale repos found. All paths are valid.');
|
|
3567
|
+
return;
|
|
3568
|
+
}
|
|
3569
|
+
|
|
3570
|
+
log('');
|
|
3571
|
+
log(`${C.yellow}${C.bold}🧹 Found ${stale.length} stale repo(s):${C.reset}`);
|
|
3572
|
+
for (const r of stale) {
|
|
3573
|
+
log(` ${C.red}✗${C.reset} ${r.name} — ${r.path}`);
|
|
3574
|
+
}
|
|
3575
|
+
log('');
|
|
3576
|
+
|
|
3577
|
+
if (!promptYN('Remove these stale entries?')) {
|
|
3578
|
+
info('Cancelled.');
|
|
3579
|
+
return;
|
|
3580
|
+
}
|
|
3581
|
+
|
|
3582
|
+
for (const r of stale) {
|
|
3583
|
+
// Remove .gitnexus storage if it exists
|
|
3584
|
+
if (r.storagePath && fs.existsSync(r.storagePath)) {
|
|
3585
|
+
try {
|
|
3586
|
+
fs.rmSync(r.storagePath, { recursive: true, force: true });
|
|
3587
|
+
dim(`Deleted: ${r.storagePath}`);
|
|
3588
|
+
} catch (e) {
|
|
3589
|
+
warn(`Failed to delete ${r.storagePath}: ${e.message}`);
|
|
3590
|
+
}
|
|
3591
|
+
}
|
|
3592
|
+
}
|
|
3593
|
+
|
|
3594
|
+
const remaining = registry.filter(r => fs.existsSync(r.path));
|
|
3595
|
+
writeGitnexusRegistry(remaining);
|
|
3596
|
+
ok(`Removed ${stale.length} stale entries. ${remaining.length} repos remaining.`);
|
|
3597
|
+
return;
|
|
3598
|
+
}
|
|
3599
|
+
|
|
3600
|
+
if (isAllMode) {
|
|
3601
|
+
const toRemove = registry.filter(r => r.path !== cwd);
|
|
3602
|
+
if (toRemove.length === 0) {
|
|
3603
|
+
info('Only current project is indexed. Nothing to clean.');
|
|
3604
|
+
return;
|
|
3605
|
+
}
|
|
3606
|
+
|
|
3607
|
+
log('');
|
|
3608
|
+
log(`${C.yellow}${C.bold}🧹 Will remove ${toRemove.length} repo(s) (keeping current project):${C.reset}`);
|
|
3609
|
+
for (const r of toRemove) {
|
|
3610
|
+
log(` ${C.red}✗${C.reset} ${r.name} — ${r.path}`);
|
|
3611
|
+
}
|
|
3612
|
+
log('');
|
|
3613
|
+
|
|
3614
|
+
if (!promptYN('Proceed?')) {
|
|
3615
|
+
info('Cancelled.');
|
|
3616
|
+
return;
|
|
3617
|
+
}
|
|
3618
|
+
|
|
3619
|
+
for (const r of toRemove) {
|
|
3620
|
+
if (r.storagePath && fs.existsSync(r.storagePath)) {
|
|
3621
|
+
try {
|
|
3622
|
+
fs.rmSync(r.storagePath, { recursive: true, force: true });
|
|
3623
|
+
dim(`Deleted: ${r.storagePath}`);
|
|
3624
|
+
} catch (e) {
|
|
3625
|
+
warn(`Failed to delete ${r.storagePath}: ${e.message}`);
|
|
3626
|
+
}
|
|
3627
|
+
}
|
|
3628
|
+
}
|
|
3629
|
+
|
|
3630
|
+
const remaining = registry.filter(r => r.path === cwd);
|
|
3631
|
+
writeGitnexusRegistry(remaining);
|
|
3632
|
+
ok(`Removed ${toRemove.length} repos. Current project preserved.`);
|
|
3633
|
+
return;
|
|
3634
|
+
}
|
|
3635
|
+
|
|
3636
|
+
// Interactive mode: show numbered list, user selects
|
|
3637
|
+
log('');
|
|
3638
|
+
log(`${C.cyan}${C.bold}🔍 Select repos to remove:${C.reset}`);
|
|
3639
|
+
log(`${C.gray}${'─'.repeat(56)}${C.reset}`);
|
|
3640
|
+
|
|
3641
|
+
for (let i = 0; i < registry.length; i++) {
|
|
3642
|
+
const r = registry[i];
|
|
3643
|
+
const isCurrent = r.path === cwd;
|
|
3644
|
+
const pathExists = fs.existsSync(r.path);
|
|
3645
|
+
const tag = isCurrent ? ` ${C.green}(current)${C.reset}` : !pathExists ? ` ${C.red}(missing)${C.reset}` : '';
|
|
3646
|
+
log(` ${C.yellow}${i + 1}${C.reset}. ${r.name}${tag}`);
|
|
3647
|
+
dim(` ${r.path}`);
|
|
3648
|
+
}
|
|
3649
|
+
log('');
|
|
3650
|
+
|
|
3651
|
+
const input = promptChoice('Enter numbers to remove (e.g. 2,3,5)', '').trim();
|
|
3652
|
+
if (!input) {
|
|
3653
|
+
info('No selection. Cancelled.');
|
|
3654
|
+
return;
|
|
3655
|
+
}
|
|
3656
|
+
|
|
3657
|
+
const indices = input.split(',').map(s => parseInt(s.trim(), 10) - 1).filter(i => i >= 0 && i < registry.length);
|
|
3658
|
+
if (indices.length === 0) {
|
|
3659
|
+
info('Invalid selection. Cancelled.');
|
|
3660
|
+
return;
|
|
3661
|
+
}
|
|
3662
|
+
|
|
3663
|
+
const selected = indices.map(i => registry[i]);
|
|
3664
|
+
log('');
|
|
3665
|
+
log(`${C.yellow}Will remove:${C.reset}`);
|
|
3666
|
+
for (const r of selected) {
|
|
3667
|
+
log(` ${C.red}✗${C.reset} ${r.name}`);
|
|
3668
|
+
}
|
|
3669
|
+
log('');
|
|
3670
|
+
|
|
3671
|
+
if (!promptYN('Confirm?')) {
|
|
3672
|
+
info('Cancelled.');
|
|
3673
|
+
return;
|
|
3674
|
+
}
|
|
3675
|
+
|
|
3676
|
+
const removeNames = new Set(selected.map(r => r.name));
|
|
3677
|
+
for (const r of selected) {
|
|
3678
|
+
if (r.storagePath && fs.existsSync(r.storagePath)) {
|
|
3679
|
+
try {
|
|
3680
|
+
fs.rmSync(r.storagePath, { recursive: true, force: true });
|
|
3681
|
+
dim(`Deleted: ${r.storagePath}`);
|
|
3682
|
+
} catch (e) {
|
|
3683
|
+
warn(`Failed to delete ${r.storagePath}: ${e.message}`);
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
3686
|
+
}
|
|
3687
|
+
|
|
3688
|
+
const remaining = registry.filter(r => !removeNames.has(r.name));
|
|
3689
|
+
writeGitnexusRegistry(remaining);
|
|
3690
|
+
ok(`Removed ${selected.length} repos. ${remaining.length} remaining.`);
|
|
3691
|
+
break;
|
|
3692
|
+
}
|
|
3693
|
+
|
|
3694
|
+
default:
|
|
3695
|
+
err(`Unknown gitnexus sub-command: ${sub}`);
|
|
3696
|
+
log(` Available: list, status, clean`);
|
|
3697
|
+
break;
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
|
|
3386
3701
|
// ─── Native HTTP Server ───────────────────────────────────────────────────────
|
|
3387
3702
|
|
|
3388
3703
|
function cmdServe(args) {
|
|
@@ -3545,6 +3860,10 @@ const [, , command, ...args] = process.argv;
|
|
|
3545
3860
|
case 'serve':
|
|
3546
3861
|
cmdServe(args);
|
|
3547
3862
|
break;
|
|
3863
|
+
case 'gitnexus':
|
|
3864
|
+
case 'gn':
|
|
3865
|
+
cmdGitnexus(args);
|
|
3866
|
+
break;
|
|
3548
3867
|
case 'gate':
|
|
3549
3868
|
cmdGate(args);
|
|
3550
3869
|
break;
|
package/package.json
CHANGED
|
@@ -41,6 +41,31 @@ INDEX STALE (tool trả warning):
|
|
|
41
41
|
|
|
42
42
|
---
|
|
43
43
|
|
|
44
|
+
## 🔑 Auto-Repo Detection Protocol (BẮT BUỘC)
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
TRƯỚC KHI gọi bất kỳ GitNexus MCP tool nào:
|
|
48
|
+
|
|
49
|
+
1. Gọi gitnexus_list_repos() → lấy danh sách repos đã index
|
|
50
|
+
2. Match project directory (từ .project-identity hoặc cwd) với repo paths
|
|
51
|
+
3. Dùng matched repo name làm `repo` param cho MỌI tool call
|
|
52
|
+
|
|
53
|
+
VÍ DỤ:
|
|
54
|
+
gitnexus_list_repos() trả về repo path "/Users/.../FitWitnessSimple"
|
|
55
|
+
→ project đang mở = FitWitnessSimple
|
|
56
|
+
→ gitnexus_query({query: "weather", repo: "FitWitnessSimple"})
|
|
57
|
+
|
|
58
|
+
FALLBACK (nếu không match path):
|
|
59
|
+
1. So sánh projectId (từ .project-identity) với repo names
|
|
60
|
+
2. Nếu vẫn không match → hỏi user chọn repo
|
|
61
|
+
|
|
62
|
+
⚠️ KHÔNG BAO GIỜ gọi GitNexus tool mà THIẾU `repo` param khi có >1 repo indexed.
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
> **Quản lý repos:** `awkit gitnexus list` để xem, `awkit gitnexus clean` để dọn repos cũ.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
44
69
|
## 🎯 When to Activate
|
|
45
70
|
|
|
46
71
|
| Trigger | Action |
|
|
@@ -187,16 +187,6 @@ If `nmem` is not available AND auto-install fails, falls back to flat-file behav
|
|
|
187
187
|
- Shows warning once: "⚠️ NeuralMemory not installed. Using flat-file fallback."
|
|
188
188
|
```
|
|
189
189
|
|
|
190
|
-
## CLI Fallback (MCP Unresponsive)
|
|
191
|
-
|
|
192
|
-
If the `neural-memory` MCP server is unresponsive or fails, you MUST fallback to using the `nmem` CLI directly using the `run_command` tool. The CLI offers full parity with the MCP tools:
|
|
193
|
-
- `nmem_remember` -> `nmem remember "..."`
|
|
194
|
-
- `nmem_recall` -> `nmem recall "..."`
|
|
195
|
-
- `nmem_context` -> `nmem recap` or `nmem recall "..."`
|
|
196
|
-
- `nmem_auto` -> `nmem auto process --text "..."`
|
|
197
|
-
|
|
198
|
-
Always check the `nmem` CLI help (`nmem --help`) if unsure of the syntax.
|
|
199
|
-
|
|
200
190
|
---
|
|
201
191
|
|
|
202
192
|
## Integration
|