@leejungkiin/awkit 1.6.0 → 1.6.2
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 +3 -2
- package/bin/awk.js +319 -0
- package/package.json +1 -1
- package/skills/gitnexus-intelligence/SKILL.md +34 -8
- package/skills/module-spec-writer/SKILL.md +1 -1
- package/skills/module-spec-writer/templates/module-spec-template.md +21 -0
- package/skills/nm-memory-sync/SKILL.md +0 -10
- package/skills/symphony-enforcer/examples/trigger-points.md +9 -5
package/README.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# AWKit — Antigravity Workflow Kit v12.5
|
|
2
2
|
|
|
3
|
-
> **v12.5
|
|
3
|
+
> **v12.5** · Single Source of Truth · Symphony-First · Mindful Execution
|
|
4
4
|
|
|
5
5
|
AWKit là framework điều phối AI agent chuyên nghiệp. Đây là **nơi duy nhất** chứa toàn bộ workflows, skills, GEMINI.md và cấu hình — không còn phân tán giữa nhiều repo.
|
|
6
|
+
AWKit giúp tối ưu context khi làm việc, có khả năng quản lý task, đồng bộ Trello và báo cáo lên Telegram, ngoài ra Awkit còn sử dụng cơ chế checkpoint để dev dừng đúng lúc tránh tâm lý tối ưu hóa quá mức (over-optimization) một vấn đề phổ biến khi làm việc với AI.
|
|
6
7
|
|
|
7
8
|
---
|
|
8
9
|
|
|
@@ -224,4 +225,4 @@ awkit enable-pack creator-studio
|
|
|
224
225
|
|
|
225
226
|
---
|
|
226
227
|
|
|
227
|
-
*AWKit
|
|
228
|
+
*AWKit Antigravity Workflow Kit · Created by leejungkiin*
|
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,13 +41,38 @@ 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 |
|
|
47
72
|
|---------|--------|
|
|
48
73
|
| Trước khi edit bất kỳ function/class/method | `gitnexus_impact` → check blast radius |
|
|
49
74
|
| Debug error / trace bug | `gitnexus_query` + `gitnexus_context` |
|
|
50
|
-
| Explore unfamiliar code | `
|
|
75
|
+
| Explore unfamiliar code | `CODEBASE.md` + `grep_search` → Tìm Anchor Class → `gitnexus_context` |
|
|
51
76
|
| Rename symbol | `gitnexus_rename` (dry_run trước) |
|
|
52
77
|
| Trước commit | `gitnexus_detect_changes` |
|
|
53
78
|
| Refactor / extract / split | `gitnexus_context` + `gitnexus_impact` |
|
|
@@ -124,14 +149,14 @@ INDEX STALE (tool trả warning):
|
|
|
124
149
|
|
|
125
150
|
---
|
|
126
151
|
|
|
127
|
-
## 📋 Workflow: Exploring Code
|
|
152
|
+
## 📋 Workflow: Exploring Code (Zero-Shotgun Protocol)
|
|
128
153
|
|
|
129
154
|
```
|
|
130
|
-
1.
|
|
131
|
-
2.
|
|
132
|
-
3. gitnexus_query
|
|
133
|
-
4. gitnexus_context({name: "<
|
|
134
|
-
5. READ gitnexus://repo/{name}/process/{name} → Full trace
|
|
155
|
+
1. Đọc CODEBASE.md để xác định cấu trúc module / Clean Architecture layers.
|
|
156
|
+
2. Dùng list_dir hoặc grep_search vào đúng thư mục dự đoán để tìm 1 "Anchor Class" (VD: ProfileViewModel).
|
|
157
|
+
3. KHÔNG dùng gitnexus_query rải rác trừ khi các bước 1-2 hoàn toàn thất bại.
|
|
158
|
+
4. Dùng gitnexus_context({name: "<AnchorClass>"}) để lấy 360° view các luồng dữ liệu xung quanh điểm neo này.
|
|
159
|
+
5. READ gitnexus://repo/{name}/process/{name} → Full trace luồng thực thi liên quan.
|
|
135
160
|
```
|
|
136
161
|
|
|
137
162
|
---
|
|
@@ -176,10 +201,11 @@ always_do:
|
|
|
176
201
|
- PHẢI chạy gitnexus_impact TRƯỚC khi edit bất kỳ symbol nào
|
|
177
202
|
- PHẢI chạy gitnexus_detect_changes TRƯỚC khi commit
|
|
178
203
|
- PHẢI cảnh báo user khi risk = HIGH hoặc CRITICAL
|
|
179
|
-
-
|
|
204
|
+
- PHẢI dùng GitNexus context sau khi xác định được "Anchor Class" qua heuristic search
|
|
180
205
|
- PHẢI dùng gitnexus_rename (dry_run trước) khi rename
|
|
181
206
|
|
|
182
207
|
never_do:
|
|
208
|
+
- KHÔNG gọi gitnexus_query (>1 lần) với các khái niệm bao quát ("appwrite feedback", "push notification") gây lãng phí token.
|
|
183
209
|
- KHÔNG edit function/class mà chưa check impact
|
|
184
210
|
- KHÔNG ignore HIGH/CRITICAL risk warnings
|
|
185
211
|
- KHÔNG rename bằng find-and-replace → dùng gitnexus_rename
|
|
@@ -84,7 +84,7 @@ OPTIONAL:
|
|
|
84
84
|
### Phase 2: Per-Module Spec Generation
|
|
85
85
|
- Gather context (BRIEF, source code, KI, NeuralMemory)
|
|
86
86
|
- Generate spec theo template → `templates/module-spec-template.md`
|
|
87
|
-
- Self-review checklist: screens đủ? flows rõ? edge cases?
|
|
87
|
+
- Self-review checklist: screens đủ? flows rõ? edge cases? **transitions & micro-interactions defined?**
|
|
88
88
|
|
|
89
89
|
### Phase 3: Cross-Module Consistency Check
|
|
90
90
|
- Dependency graph (circular? missing cross-refs?)
|
|
@@ -85,10 +85,31 @@
|
|
|
85
85
|
|
|
86
86
|
---
|
|
87
87
|
|
|
88
|
+
## Transitions & Micro-interactions (Mobile)
|
|
89
|
+
|
|
90
|
+
| Screen/Element | Animation Type | Details |
|
|
91
|
+
|----------------|---------------|---------|
|
|
92
|
+
| [List/Grid screen] | Staggered fade+slide | Items appear sequentially (50ms delay) |
|
|
93
|
+
| [Stats/Dashboard] | Number counter | Values roll/count up from 0 |
|
|
94
|
+
| [Navigation A→B] | Hero / Shared element | [which element animates across] |
|
|
95
|
+
| [Modal/Sheet] | Spring damping | Spring-based open/close |
|
|
96
|
+
| [Buttons/Cards] | Scale on press | Scale 0.95 + haptic impact(light) |
|
|
97
|
+
| [Success state] | Checkmark draw | Animated stroke white→green |
|
|
98
|
+
| [Error state] | Shake | Horizontal oscillation on invalid field |
|
|
99
|
+
| [Toggle/Switch] | Spring bounce | Spring physics + optional haptic |
|
|
100
|
+
| [Like/Favorite] | Pulse/Particle | Scale pulse or burst effect |
|
|
101
|
+
|
|
102
|
+
> Fill only relevant rows. Delete unused ones. Add custom animations as needed.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
88
106
|
## Acceptance Criteria
|
|
89
107
|
- [ ] [measurable criterion 1]
|
|
90
108
|
- [ ] [measurable criterion 2]
|
|
91
109
|
- [ ] [measurable criterion 3]
|
|
110
|
+
- [ ] All loading states use skeleton→content morph transitions (no instant pop-in)
|
|
111
|
+
- [ ] All tappable elements have scale-on-press feedback
|
|
112
|
+
- [ ] Haptic feedback integrated for key interactions
|
|
92
113
|
```
|
|
93
114
|
|
|
94
115
|
---
|
|
@@ -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
|
|
@@ -98,9 +98,13 @@ File patterns:
|
|
|
98
98
|
- Đưa hướng dẫn test cụ thể
|
|
99
99
|
- Gọi notify_user(BlockedOnUser=true) — DỪNG và CHỜ user response
|
|
100
100
|
|
|
101
|
-
=== TRƯỜNG HỢP 2: autoVerification = true (
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
101
|
+
=== TRƯỜNG HỢP 2: autoVerification = true (Autonomous UI Visual Verification) ===
|
|
102
|
+
AI tự động thực hiện chu trình 5 bước để kiểm tra hiển thị:
|
|
103
|
+
1. [ISOLATION]: Tạm thời chỉnh sửa Entry File (ContentView/MainActivity/App) để boot trực tiếp vào màn hình mục tiêu. Inject mock-data.
|
|
104
|
+
2. [BOOT]: Chạy Auto Build và Launch app lên Simulator/Emulator (hoặc dùng mcp_maestro_launch_app).
|
|
105
|
+
3. [VERIFY]: Call CLI / Maestro take_screenshot. AI gọi `view_file` lên ảnh để đối chiếu vs Thiết kế (Padding, Colors, Text, State).
|
|
106
|
+
4. [MANDATORY REVERT]: BẤT KỂ kết quả ra sao (hay build lỗi), PHẢI gỡ bỏ code Isolation, trả Entry File về phiên bản gốc (dùng Git stash hoặc revert tay).
|
|
107
|
+
5. [FEEDBACK LOOP]:
|
|
108
|
+
- Nếu PASS: BlockedOnUser=false. Lập tức đi tiếp!
|
|
109
|
+
- Nếu FAIL: AI tự log ra file kết quả chẩn đoán lỗi hiển thị -> Tự vòng lại sửa Code -> Start Bước 1. (Tối đa 3 vòng lặp mindful_pause).
|
|
106
110
|
```
|