evnict-kit 0.2.2 → 0.2.4

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 (80) hide show
  1. package/README.md +39 -2
  2. package/bin/cli.js +7 -1
  3. package/package.json +1 -1
  4. package/scripts/patch-workflows.js +142 -0
  5. package/scripts/postinstall.js +1 -1
  6. package/src/commands/init.js +18 -9
  7. package/src/commands/sync.js +317 -0
  8. package/src/commands/upgrade.js +7 -1
  9. package/src/utils/config.js +2 -2
  10. package/templates/GETTING-STARTED.md +23 -2
  11. package/templates/content/skills/evnict-kit-bug-fix/SKILL.md +1 -1
  12. package/templates/content/skills/evnict-kit-git-worktrees/SKILL.md +0 -2
  13. package/templates/content/skills/evnict-kit-onboard/SKILL.md +0 -1
  14. package/templates/content/skills/evnict-kit-spec/SKILL.md +1 -1
  15. package/templates/content/skills/evnict-kit-wiki/SKILL.md +16 -54
  16. package/templates/content/workflows/evnict-kit-archive-wiki.md +4 -5
  17. package/templates/content/workflows/evnict-kit-attt.md +1 -1
  18. package/templates/content/workflows/evnict-kit-bug-fix.md +1 -1
  19. package/templates/content/workflows/evnict-kit-feature-large.md +2 -2
  20. package/templates/content/workflows/evnict-kit-feature-small.md +1 -1
  21. package/templates/content/workflows/evnict-kit-init-wiki.md +18 -47
  22. package/templates/content/workflows/evnict-kit-wiki-archive-feature.md +2 -3
  23. package/templates/content/workflows/evnict-kit-wiki-query.md +3 -3
  24. package/templates/content/workflows/evnict-kit-wiki-scan-project.md +3 -4
  25. package/templates/skills/evnict-kit-bug-fix/SKILL.md +1 -1
  26. package/templates/skills/evnict-kit-create-component/SKILL.md +23 -0
  27. package/templates/skills/evnict-kit-create-page/SKILL.md +23 -0
  28. package/templates/skills/evnict-kit-frontend-design/SKILL.md +161 -0
  29. package/templates/skills/evnict-kit-spec/SKILL.md +1 -1
  30. package/templates/skills/evnict-kit-wiki/SKILL.md +16 -54
  31. package/templates/wiki/AGENTS.md +280 -0
  32. package/templates/wiki/CLAUDE.md +280 -0
  33. package/templates/wiki/FAQ.md +168 -0
  34. package/templates/wiki/README.md +272 -35
  35. package/templates/wiki/config.example.yaml +145 -17
  36. package/templates/wiki/scripts/run-wiki.sh +17 -0
  37. package/templates/wiki/scripts/setup-scheduler.ps1 +29 -0
  38. package/templates/wiki/skills/llm-wiki/SKILL.md +341 -0
  39. package/templates/wiki/wiki/INDEX.md +9 -0
  40. package/templates/wiki/wiki/INDEX.template.md +29 -0
  41. package/templates/wiki/wiki/LOG.md +3 -0
  42. package/templates/wiki/wiki/LOG.template.md +7 -0
  43. package/templates/wiki/wiki-viewer.html +408 -0
  44. package/templates/workflows/antigravity/evnict-kit-archive-wiki.md +24 -5
  45. package/templates/workflows/antigravity/evnict-kit-attt.md +21 -1
  46. package/templates/workflows/antigravity/evnict-kit-bug-fix.md +24 -1
  47. package/templates/workflows/antigravity/evnict-kit-feature-large.md +26 -2
  48. package/templates/workflows/antigravity/evnict-kit-feature-small.md +24 -1
  49. package/templates/workflows/antigravity/evnict-kit-handoff.md +23 -0
  50. package/templates/workflows/antigravity/evnict-kit-implement.md +23 -0
  51. package/templates/workflows/antigravity/evnict-kit-init-check.md +20 -0
  52. package/templates/workflows/antigravity/evnict-kit-init-context.md +20 -0
  53. package/templates/workflows/antigravity/evnict-kit-init-rules.md +20 -0
  54. package/templates/workflows/antigravity/evnict-kit-init-wiki.md +26 -8
  55. package/templates/workflows/antigravity/evnict-kit-plan.md +24 -0
  56. package/templates/workflows/antigravity/evnict-kit-review.md +20 -0
  57. package/templates/workflows/antigravity/evnict-kit-spec-archive.md +20 -0
  58. package/templates/workflows/antigravity/evnict-kit-wiki-archive-feature.md +22 -3
  59. package/templates/workflows/antigravity/evnict-kit-wiki-query.md +23 -3
  60. package/templates/workflows/antigravity/evnict-kit-wiki-scan-project.md +23 -4
  61. package/templates/workflows/claude/evnict-kit-archive-wiki.md +24 -5
  62. package/templates/workflows/claude/evnict-kit-attt.md +21 -1
  63. package/templates/workflows/claude/evnict-kit-bug-fix.md +24 -1
  64. package/templates/workflows/claude/evnict-kit-feature-large.md +26 -2
  65. package/templates/workflows/claude/evnict-kit-feature-small.md +24 -1
  66. package/templates/workflows/claude/evnict-kit-handoff.md +23 -0
  67. package/templates/workflows/claude/evnict-kit-implement.md +23 -0
  68. package/templates/workflows/claude/evnict-kit-init-check.md +20 -0
  69. package/templates/workflows/claude/evnict-kit-init-context.md +20 -0
  70. package/templates/workflows/claude/evnict-kit-init-rules.md +20 -0
  71. package/templates/workflows/claude/evnict-kit-init-wiki.md +25 -7
  72. package/templates/workflows/claude/evnict-kit-plan.md +24 -0
  73. package/templates/workflows/claude/evnict-kit-review.md +20 -0
  74. package/templates/workflows/claude/evnict-kit-spec-archive.md +20 -0
  75. package/templates/workflows/claude/evnict-kit-wiki-archive-feature.md +22 -3
  76. package/templates/workflows/claude/evnict-kit-wiki-query.md +23 -3
  77. package/templates/workflows/claude/evnict-kit-wiki-scan-project.md +23 -4
  78. package/templates/wiki/package.json +0 -17
  79. package/templates/wiki/raw/notes/.gitkeep +0 -1
  80. package/templates/wiki/scripts/ingest.js +0 -66
package/README.md CHANGED
@@ -69,6 +69,29 @@ Mở AI Agent (Antigravity / Claude / Cursor / ...) trong thư mục project, sa
69
69
 
70
70
  ---
71
71
 
72
+ ## 🔄 Cập nhật phiên bản mới (Upgrade)
73
+
74
+ Khi có bản cập nhật mới của `evnict-kit`, bạn làm theo 2 bước sau để nâng cấp hoàn chỉnh:
75
+
76
+ ### 1. Nâng cấp công cụ trên máy (Global)
77
+ Cập nhật package cài đặt chung cho môi trường làm việc của bạn:
78
+ ```bash
79
+ npm update -g evnict-kit
80
+ # Hoặc an toàn hơn, dùng lệnh có sẵn:
81
+ evnict-kit upgrade
82
+ ```
83
+
84
+ ### 2. Đồng bộ file vào Project hiện tại (Sync)
85
+ Lưu ý: Các dự án của bạn (đã được `init` từ bản cũ) **sẽ không tự động nhận** các Workflows, Skills hay Rules mới.
86
+ Bạn cần di chuyển vào thư mục dự án và chạy lệnh:
87
+ ```bash
88
+ cd /path/to/workspace/your-project
89
+ evnict-kit sync
90
+ ```
91
+ > **💡 Tính năng lệnh Sync:** Hệ thống sẽ tự động phát hiện xem project đang dùng AI Tool nào (Antigravity/Claude/Cursor...) và cập nhật/ghi đè các file chuẩn của `evnict-kit`, trong khi vẫn **GIỮ NGUYÊN HOÀN TOÀN** các đoạn code hoặc Project Rules riêng mà bạn tự định nghĩa.
92
+
93
+ ---
94
+
72
95
  ## 🎯 Tính năng
73
96
 
74
97
  ### 🧠 Rules Engine — 5 Rule Sets
@@ -158,6 +181,7 @@ evnict-kit init-rules --tool=cursor
158
181
  | `evnict-kit doctor` | 🩺 Kiểm tra môi trường, phiên bản, cập nhật |
159
182
  | `evnict-kit info` | 📊 Thống kê chi tiết toolkit |
160
183
  | `evnict-kit upgrade` | 🔄 Kiểm tra & cập nhật phiên bản mới nhất |
184
+ | `evnict-kit sync` | 🔄 Re-deploy templates vào các project — ghi đè files evnict-kit, giữ nguyên files user |
161
185
 
162
186
  ### Lệnh Agent (gõ trong AI Agent chat)
163
187
 
@@ -281,7 +305,7 @@ FE: Đọc handoff.md → implement → update 🟢 Đã xử lý
281
305
  | Metric | Count |
282
306
  |--------|-------|
283
307
  | Rule Sets | 5 |
284
- | Skills | 22 |
308
+ | Skills | 23 |
285
309
  | Workflows | 17 |
286
310
  | Supported AI Tools | 5 |
287
311
  | Tech Stacks (BE) | SpringBoot · ASP.NET · Java EE |
@@ -305,7 +329,20 @@ FE: Đọc handoff.md → implement → update 🟢 Đã xử lý
305
329
 
306
330
  ## 📜 Changelog
307
331
 
308
- ### v0.2.2 (Current)
332
+ ### v0.2.4 (Current)
333
+ - 📚 **Native llm-wiki Integration**: Chuyển đổi toàn bộ quy trình Wiki sang chuẩn `llm-wiki` với Agent-driven ingestion — xoá bỏ sự phụ thuộc script Node.js (`scripts/ingest.js`)
334
+ - 🧹 **Cấu trúc Wiki chuẩn hóa**: Wiki project giờ đây được sắp xếp sạch sẽ trong `wiki/entities/`, `wiki/concepts/`, `wiki/sources/`, `wiki/syntheses/` (thay vì folder `processed/` cũ)
335
+ - 🤖 **Auto-Skills**: Agent tự động dùng skill `llm-wiki` (`SKILL.md`) để đọc code, sinh tri thức và trích xuất thực thể chuyên nghiệp
336
+ - 🗑️ **Refactoring**: Cập nhật toàn bộ Agent workflow (scan, query, archive) bám sát kiến trúc mới
337
+
338
+ ### v0.2.3
339
+ - 🎨 **Frontend Design Skill**: Skill mới `evnict-kit-frontend-design` — hướng dẫn tư duy thiết kế UI chất lượng cao cho Angular, tránh "AI slop" aesthetics
340
+ - 🧠 **Context Refresh**: Thêm section "Tuân thủ Rules & Context" vào TẤT CẢ 36 workflows/skills — cơ chế smart check (chỉ đọc lại nếu chưa đọc, tiết kiệm token)
341
+ - 🎯 **FE Design Hint**: 6 workflows có code FE được bổ sung nhắc tham chiếu skill frontend-design
342
+ - 🔄 **Sync Command**: Lệnh `evnict-kit sync` — re-deploy templates khi upgrade version, không xóa files user tự thêm
343
+ - 📦 Hỗ trợ cả Antigravity + Claude workflows
344
+
345
+ ### v0.2.2
309
346
  - 🧹 Removed obsolete standalone commands (`init-rules`, `init-context`, `init-workflow`, `init-check`)
310
347
  - 🩺 `doctor` command: health check, version check, workspace status
311
348
  - 📊 `info` command: toolkit statistics dashboard
package/bin/cli.js CHANGED
@@ -5,9 +5,10 @@ import { addCommand } from '../src/commands/add.js';
5
5
  import { doctorCommand } from '../src/commands/doctor.js';
6
6
  import { infoCommand } from '../src/commands/info.js';
7
7
  import { upgradeCommand } from '../src/commands/upgrade.js';
8
+ import { syncCommand } from '../src/commands/sync.js';
8
9
 
9
10
  const program = new Command();
10
- program.name('evnict-kit').description('EVNICT AI-Assisted Development Toolkit v0.2.2').version('0.2.2');
11
+ program.name('evnict-kit').description('EVNICT AI-Assisted Development Toolkit v0.2.3').version('0.2.3');
11
12
 
12
13
  program.command('init')
13
14
  .description('Khoi tao workspace + deploy rules/skills/workflows vao tung project')
@@ -43,4 +44,9 @@ program.command('upgrade')
43
44
  .option('-y, --yes', 'Auto-confirm upgrade')
44
45
  .action(upgradeCommand);
45
46
 
47
+ program.command('sync')
48
+ .description('Re-deploy templates (workflows/skills/rules) vao cac project da init — ghi de files evnict-kit, giu nguyen files user')
49
+ .option('-y, --yes', 'Auto-confirm sync (khong hoi)')
50
+ .action(syncCommand);
51
+
46
52
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "evnict-kit",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "EVNICT AI-Assisted Development Toolkit - Bộ công cụ hỗ trợ phát triển phần mềm với AI theo quy định QĐ-TTPM",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Patch script: Add Context Refresh + FE Design hint to all workflows
3
+ * Run: node scripts/patch-workflows.js
4
+ */
5
+ import { readFileSync, writeFileSync, readdirSync, existsSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+
8
+ const TEMPLATES_DIR = join(import.meta.dirname, '..', 'templates');
9
+
10
+ // ═══════════════════════════════════════════
11
+ // Context Refresh block (smart check mechanism)
12
+ // ═══════════════════════════════════════════
13
+ const CONTEXT_REFRESH = `
14
+ ---
15
+
16
+ ## ⚠️ NHẮC NHỞ: Tuân thủ Rules & Context
17
+
18
+ > **Agent dùng lâu có thể quên rules.** Trước khi bắt đầu code, tự kiểm tra:
19
+
20
+ ### Quick Check (agent tự đánh giá)
21
+ 1. ✅ Tôi đã đọc \`.agent/rules/\` trong phiên làm việc này chưa?
22
+ 2. ✅ Tôi đã đọc AGENTS.md (hoặc context file) chưa?
23
+ 3. ✅ Tôi nhớ rõ coding conventions của dự án này không?
24
+
25
+ **→ Nếu BẤT KỲ câu nào = "CHƯA" hoặc "KHÔNG CHẮC":**
26
+ - Đọc lại tất cả files trong \`.agent/rules/\` (hoặc thư mục rules tương ứng)
27
+ - Đọc lại AGENTS.md (hoặc context file tương ứng)
28
+ - Query wiki nếu có: \`grep -rl "{keyword}" {wiki_path}/processed/ --include="*.md"\`
29
+
30
+ **→ Nếu TẤT CẢ = "RỒI" → tiếp tục workflow, KHÔNG cần đọc lại.**
31
+
32
+ > **NGUYÊN TẮC:** Không chắc convention → ĐỌC LẠI rule file. KHÔNG đoán.
33
+ `;
34
+
35
+ // ═══════════════════════════════════════════
36
+ // FE Design hint block
37
+ // ═══════════════════════════════════════════
38
+ const FE_DESIGN_HINT = `
39
+ > 🎨 **FE UI Quality:** Khi tạo/sửa UI component, tham khảo skill \`evnict-kit-frontend-design\`
40
+ > để đảm bảo chất lượng thiết kế cao. Áp dụng Design Thinking (Purpose → Tone → Constraints → Differentiation) trước khi code UI.
41
+ `;
42
+
43
+ // ═══════════════════════════════════════════
44
+ // Files that need FE Design hint (in addition to Context Refresh)
45
+ // ═══════════════════════════════════════════
46
+ const FE_HINT_FILES = [
47
+ 'evnict-kit-feature-small.md',
48
+ 'evnict-kit-feature-large.md',
49
+ 'evnict-kit-implement.md',
50
+ 'evnict-kit-plan.md',
51
+ 'evnict-kit-handoff.md',
52
+ 'evnict-kit-bug-fix.md',
53
+ ];
54
+
55
+ // ═══════════════════════════════════════════
56
+ // Process workflow files in a tool directory
57
+ // ═══════════════════════════════════════════
58
+ function patchFile(filePath, addFeHint) {
59
+ let content = readFileSync(filePath, 'utf8');
60
+
61
+ // Skip if already patched
62
+ if (content.includes('NHẮC NHỞ: Tuân thủ Rules & Context')) {
63
+ console.log(` ⏭️ Already patched: ${filePath}`);
64
+ return false;
65
+ }
66
+
67
+ // Find the insertion point — before "## Checklist" or "## Tiêu chí" at the end
68
+ const checklistPatterns = [
69
+ /\n## Checklist[^\n]*\n/,
70
+ /\n## Checklist hoàn thành[^\n]*\n/,
71
+ /\n## Tiêu chí hoàn thành[^\n]*\n/,
72
+ /\n## Tiêu chí thành công[^\n]*\n/,
73
+ /\n## Output[^\n]*\n/,
74
+ ];
75
+
76
+ let insertPos = -1;
77
+ for (const pattern of checklistPatterns) {
78
+ const match = content.match(pattern);
79
+ if (match) {
80
+ insertPos = content.lastIndexOf(match[0]);
81
+ break;
82
+ }
83
+ }
84
+
85
+ let insertBlock = '';
86
+
87
+ // Add FE Design hint if applicable
88
+ if (addFeHint) {
89
+ insertBlock += FE_DESIGN_HINT;
90
+ }
91
+
92
+ // Add Context Refresh
93
+ insertBlock += CONTEXT_REFRESH;
94
+
95
+ if (insertPos >= 0) {
96
+ // Insert before checklist
97
+ content = content.slice(0, insertPos) + insertBlock + content.slice(insertPos);
98
+ } else {
99
+ // No checklist found — append at end
100
+ content = content.trimEnd() + '\n' + insertBlock + '\n';
101
+ }
102
+
103
+ writeFileSync(filePath, content, 'utf8');
104
+ console.log(` ✅ Patched: ${filePath}${addFeHint ? ' (+FE Design)' : ''}`);
105
+ return true;
106
+ }
107
+
108
+ // ═══════════════════════════════════════════
109
+ // Main
110
+ // ═══════════════════════════════════════════
111
+ const toolDirs = ['antigravity', 'claude'];
112
+ let patchedCount = 0;
113
+
114
+ for (const tool of toolDirs) {
115
+ const wfDir = join(TEMPLATES_DIR, 'workflows', tool);
116
+ if (!existsSync(wfDir)) {
117
+ console.log(`⚠️ Skipping ${tool} — directory not found`);
118
+ continue;
119
+ }
120
+
121
+ console.log(`\n══ Patching ${tool} workflows ══`);
122
+ const files = readdirSync(wfDir).filter(f => f.endsWith('.md') && f !== 'README.md');
123
+
124
+ for (const file of files) {
125
+ const addFeHint = FE_HINT_FILES.includes(file);
126
+ const patched = patchFile(join(wfDir, file), addFeHint);
127
+ if (patched) patchedCount++;
128
+ }
129
+ }
130
+
131
+ // Also patch skills
132
+ const skillDirs = ['evnict-kit-create-component', 'evnict-kit-create-page'];
133
+ console.log(`\n══ Patching skills ══`);
134
+ for (const skillDir of skillDirs) {
135
+ const skillPath = join(TEMPLATES_DIR, 'skills', skillDir, 'SKILL.md');
136
+ if (existsSync(skillPath)) {
137
+ const patched = patchFile(skillPath, true);
138
+ if (patched) patchedCount++;
139
+ }
140
+ }
141
+
142
+ console.log(`\n✅ Done! Patched ${patchedCount} files.`);
@@ -14,7 +14,7 @@ const c = {
14
14
  yellow: '\x1b[33m',
15
15
  };
16
16
 
17
- const version = '0.2.2';
17
+ const version = '0.2.3';
18
18
 
19
19
  console.log('');
20
20
  console.log(`${c.bold}${c.cyan} +====================================================+${c.reset}`);
@@ -54,7 +54,7 @@ async function initInteractive(options) {
54
54
 
55
55
  console.log(`
56
56
  ╔═══════════════════════════════════════════╗
57
- ║ 🚀 EVNICT-KIT v0.2.2: Init Setup ║
57
+ ║ 🚀 EVNICT-KIT v0.2.4: Init Setup ║
58
58
  ╚═══════════════════════════════════════════╝
59
59
  `);
60
60
 
@@ -223,7 +223,7 @@ async function deployWorkspace({ name, tool, repoConfigs, db, wikiEnabled, cwd }
223
223
 
224
224
  console.log(`
225
225
  ╔═══════════════════════════════════════════════════════╗
226
- ║ 🚀 EVNICT-KIT v0.2.2: Init Workspace ║
226
+ ║ 🚀 EVNICT-KIT v0.2.4: Init Workspace ║
227
227
  ╚═══════════════════════════════════════════════════════╝
228
228
 
229
229
  Project: ${name}
@@ -248,7 +248,7 @@ async function deployWorkspace({ name, tool, repoConfigs, db, wikiEnabled, cwd }
248
248
  writeFile(join(evnictDir, 'handoff/be-status.md'), `# BE Status\nstatus: idle\n`, {cwd,silent:true});
249
249
  writeFile(join(evnictDir, 'handoff/fe-status.md'), `# FE Status\nstatus: idle\n`, {cwd,silent:true});
250
250
 
251
- // v0.2.2: Tạo handoff.md template
251
+ // v0.2.3: Tạo handoff.md template
252
252
  const handoffTemplate = `# Agent Handoff Log
253
253
  > File này dùng để trao đổi giữa BE Agent và FE Agent.
254
254
  > Mỗi issue ghi theo format bên dưới.
@@ -304,15 +304,24 @@ async function deployWorkspace({ name, tool, repoConfigs, db, wikiEnabled, cwd }
304
304
  if (existsSync(wikiSrc)) {
305
305
  ensureDir(wikiPath, cwd);
306
306
  const count = copyTemplateDir(wikiSrc, wikiPath, cwd);
307
+
308
+ // Tạo thêm folder structure cho Agent
309
+ ensureDir(join(wikiPath, 'wiki/entities'), cwd);
310
+ ensureDir(join(wikiPath, 'wiki/concepts'), cwd);
311
+ ensureDir(join(wikiPath, 'wiki/sources'), cwd);
312
+ ensureDir(join(wikiPath, 'wiki/syntheses'), cwd);
313
+ ensureDir(join(wikiPath, 'outputs'), cwd);
314
+ ensureDir(join(wikiPath, '.discoveries'), cwd);
315
+
307
316
  const configPath = join(wikiPath, 'config.example.yaml');
308
317
  if (existsSync(configPath)) {
309
318
  let content = readFileSync(configPath, 'utf8');
310
- content = content.replaceAll('{{PROJECT_NAME}}', name);
319
+ content = content.replaceAll('My LLM Wiki', name);
311
320
  writeFileSync(join(wikiPath, 'config.yaml'), content, 'utf8');
312
321
  console.log(` ✅ config.yaml created`);
313
322
  }
314
323
  console.log(` ✅ Wiki template deployed (${count} files)`);
315
- console.log(` 💡 Run: cd ${name}-wiki && npm install`);
324
+ console.log(` 💡 Mở Agent trong ${name}-wiki/, chạy: /llm-wiki init "${name}"`);
316
325
  } else {
317
326
  console.log(` ⚠️ Wiki template not found — fallback to manual setup`);
318
327
  ensureDir(wikiPath, cwd);
@@ -363,7 +372,7 @@ async function deployWorkspace({ name, tool, repoConfigs, db, wikiEnabled, cwd }
363
372
 
364
373
  console.log(`
365
374
  ╔═══════════════════════════════════════════════════════╗
366
- ║ ✅ Workspace "${name}" v0.2.2 initialized! ║
375
+ ║ ✅ Workspace "${name}" v0.2.4 initialized! ║
367
376
  ╠═══════════════════════════════════════════════════════╣
368
377
  ║ ║
369
378
  ║ Projects: ║
@@ -468,7 +477,7 @@ function updateGitignore(projectPath, projectName) {
468
477
  }
469
478
 
470
479
  // ════════════════════════════════════════════════════════════════════
471
- // Deploy to individual project — v0.2.2 Multi-Tool Adapter Pattern
480
+ // Deploy to individual project — v0.2.3 Multi-Tool Adapter Pattern
472
481
  // ════════════════════════════════════════════════════════════════════
473
482
 
474
483
  function deployToProject(projectPath, cwd, opts) {
@@ -678,7 +687,7 @@ function deployShared(projectPath, cwd, opts) {
678
687
  writeFile(join(projectPath, 'Instruct-Agent-AI.md'), content, {cwd});
679
688
  }
680
689
 
681
- // ═══ GETTING-STARTED.md (v0.2.2) ═══
690
+ // ═══ GETTING-STARTED.md (v0.2.3) ═══
682
691
  const guideTemplate = join(TEMPLATES_DIR, 'GETTING-STARTED.md');
683
692
  if (existsSync(guideTemplate)) {
684
693
  let content = readFileSync(guideTemplate, 'utf8');
@@ -699,7 +708,7 @@ function genConfig(name, repoConfigs, db, tool, wiki) {
699
708
  ` - { folder: "${r.folder}", type: "${r.type}", tech: "${r.tech}" }`
700
709
  ).join('\n');
701
710
 
702
- return `# EVNICT-KIT v0.2.2 Config
711
+ return `# EVNICT-KIT v0.2.4 Config
703
712
  project:
704
713
  name: "${name}"
705
714
  repos:
@@ -0,0 +1,317 @@
1
+ import { existsSync, readFileSync, readdirSync, unlinkSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import readline from 'node:readline';
5
+ import { loadConfig, getToolMap, TECH_LABELS, SUPPORTED_TOOLS } from '../utils/config.js';
6
+ import { ensureDir, writeFile, TEMPLATES_DIR } from '../utils/file.js';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+
11
+ // ── Colors ──
12
+ const c = {
13
+ reset: '\x1b[0m',
14
+ bold: '\x1b[1m',
15
+ dim: '\x1b[2m',
16
+ green: '\x1b[32m',
17
+ yellow: '\x1b[33m',
18
+ red: '\x1b[31m',
19
+ cyan: '\x1b[36m',
20
+ magenta: '\x1b[35m',
21
+ };
22
+
23
+ // ════════════════════════════════════════════════════════════════════
24
+ // Detect evnict-kit managed files vs user files
25
+ // evnict-kit files always start with "evnict-kit-" prefix
26
+ // or numbered prefix like "01-evnict-kit-", "02-evnict-kit-"
27
+ // ════════════════════════════════════════════════════════════════════
28
+
29
+ function isEvnictKitFile(filename) {
30
+ return filename.startsWith('evnict-kit-') ||
31
+ /^\d{2}-evnict-kit-/.test(filename);
32
+ }
33
+
34
+ function isEvnictKitSkillDir(dirname) {
35
+ return dirname.startsWith('evnict-kit-');
36
+ }
37
+
38
+ // ════════════════════════════════════════════════════════════════════
39
+ // Sync templates into a project — overwrite evnict-kit files only
40
+ // ════════════════════════════════════════════════════════════════════
41
+
42
+ function syncRules(projectPath, cwd, tool, toolMap) {
43
+ const rulesSrc = join(TEMPLATES_DIR, 'rules', tool);
44
+ const rulesDest = join(projectPath, toolMap.rulesDir);
45
+ if (!existsSync(rulesSrc) || !toolMap.rulesDir) return { updated: 0, skipped: 0, userFiles: [] };
46
+
47
+ ensureDir(rulesDest, cwd);
48
+ const srcFiles = readdirSync(rulesSrc).filter(f => f.endsWith('.md') || f.endsWith('.mdc'));
49
+ let updated = 0, skipped = 0;
50
+ const userFiles = [];
51
+
52
+ // Detect user files in dest
53
+ if (existsSync(rulesDest)) {
54
+ const destFiles = readdirSync(rulesDest).filter(f => !f.startsWith('.'));
55
+ for (const f of destFiles) {
56
+ if (!isEvnictKitFile(f) && !srcFiles.includes(f)) {
57
+ userFiles.push(f);
58
+ }
59
+ }
60
+ }
61
+
62
+ // Overwrite evnict-kit rules
63
+ for (const file of srcFiles) {
64
+ const content = readFileSync(join(rulesSrc, file), 'utf8');
65
+ const destPath = join(rulesDest, file);
66
+ const existed = existsSync(destPath);
67
+ writeFile(destPath, content, { overwrite: true, cwd, silent: true });
68
+ if (existed) {
69
+ updated++;
70
+ console.log(` 🔄 ${file}`);
71
+ } else {
72
+ updated++;
73
+ console.log(` ✨ ${file} (new)`);
74
+ }
75
+ }
76
+
77
+ return { updated, skipped, userFiles };
78
+ }
79
+
80
+ function syncWorkflows(projectPath, cwd, tool, toolMap) {
81
+ const wfSrc = join(TEMPLATES_DIR, 'workflows', tool);
82
+ const wfDest = join(projectPath, toolMap.workflowsDir);
83
+ if (!existsSync(wfSrc) || !toolMap.workflowsDir) return { updated: 0, skipped: 0, userFiles: [] };
84
+
85
+ ensureDir(wfDest, cwd);
86
+ const srcFiles = readdirSync(wfSrc).filter(f => f.endsWith('.md'));
87
+ let updated = 0;
88
+ const userFiles = [];
89
+
90
+ // Detect user files in dest
91
+ if (existsSync(wfDest)) {
92
+ const destFiles = readdirSync(wfDest).filter(f => f.endsWith('.md'));
93
+ for (const f of destFiles) {
94
+ if (!isEvnictKitFile(f) && !srcFiles.includes(f) && f !== 'README.md') {
95
+ userFiles.push(f);
96
+ }
97
+ }
98
+ }
99
+
100
+ for (const file of srcFiles) {
101
+ const content = readFileSync(join(wfSrc, file), 'utf8');
102
+ const destPath = join(wfDest, file);
103
+ const existed = existsSync(destPath);
104
+ writeFile(destPath, content, { overwrite: true, cwd, silent: true });
105
+ if (existed) {
106
+ updated++;
107
+ console.log(` 🔄 ${file}`);
108
+ } else {
109
+ updated++;
110
+ console.log(` ✨ ${file} (new)`);
111
+ }
112
+ }
113
+
114
+ return { updated, userFiles };
115
+ }
116
+
117
+ function syncSkills(projectPath, cwd, toolMap) {
118
+ const skillsSrc = join(TEMPLATES_DIR, 'skills');
119
+ if (!existsSync(skillsSrc) || !toolMap.skillsDir) return { updated: 0, newSkills: 0, userSkills: [] };
120
+
121
+ const srcDirs = readdirSync(skillsSrc, { withFileTypes: true })
122
+ .filter(d => d.isDirectory());
123
+ let updated = 0, newSkills = 0;
124
+ const userSkills = [];
125
+
126
+ // Detect user skills in dest
127
+ const skillsDest = join(projectPath, toolMap.skillsDir);
128
+ if (existsSync(skillsDest)) {
129
+ const destDirs = readdirSync(skillsDest, { withFileTypes: true })
130
+ .filter(d => d.isDirectory())
131
+ .map(d => d.name);
132
+ const srcNames = srcDirs.map(d => d.name);
133
+ for (const d of destDirs) {
134
+ if (!srcNames.includes(d) && !isEvnictKitSkillDir(d)) {
135
+ userSkills.push(d);
136
+ }
137
+ }
138
+ }
139
+
140
+ for (const d of srcDirs) {
141
+ const dest = join(projectPath, toolMap.skillsDir, d.name);
142
+ const existed = existsSync(dest);
143
+ ensureDir(dest, cwd);
144
+
145
+ // Overwrite all files in skill dir
146
+ const skillFiles = readdirSync(join(skillsSrc, d.name), { withFileTypes: true });
147
+ for (const entry of skillFiles) {
148
+ if (entry.isFile()) {
149
+ const content = readFileSync(join(skillsSrc, d.name, entry.name), 'utf8');
150
+ writeFile(join(dest, entry.name), content, { overwrite: true, cwd, silent: true });
151
+ }
152
+ }
153
+
154
+ if (existed) {
155
+ updated++;
156
+ console.log(` 🔄 ${d.name}/`);
157
+ } else {
158
+ newSkills++;
159
+ console.log(` ✨ ${d.name}/ (new)`);
160
+ }
161
+ }
162
+
163
+ return { updated, newSkills, userSkills };
164
+ }
165
+
166
+ function syncSharedFiles(projectPath, cwd, toolMap) {
167
+ let updated = 0;
168
+
169
+ // GETTING-STARTED.md
170
+ const guideTemplate = join(TEMPLATES_DIR, 'GETTING-STARTED.md');
171
+ if (existsSync(guideTemplate)) {
172
+ const guideDest = join(projectPath, 'GETTING-STARTED.md');
173
+ if (existsSync(guideDest)) {
174
+ const content = readFileSync(guideTemplate, 'utf8');
175
+ // Preserve {{}} placeholders — user may have customized
176
+ writeFile(guideDest, content, { overwrite: true, cwd, silent: true });
177
+ console.log(` 🔄 GETTING-STARTED.md`);
178
+ updated++;
179
+ }
180
+ }
181
+
182
+ return { updated };
183
+ }
184
+
185
+ // ════════════════════════════════════════════════════════════════════
186
+ // Main sync command
187
+ // ════════════════════════════════════════════════════════════════════
188
+
189
+ export async function syncCommand(options) {
190
+ const cwd = process.cwd();
191
+
192
+ // Load config
193
+ const config = loadConfig(cwd);
194
+ if (!config) {
195
+ console.error('❌ Không tìm thấy .evnict/config.yaml. Chạy evnict-kit init trước.');
196
+ process.exit(1);
197
+ }
198
+
199
+ const name = config.project?.name;
200
+ const tool = config.ai_tool || 'antigravity';
201
+ const repos = config.repos || [];
202
+
203
+ if (!SUPPORTED_TOOLS.includes(tool)) {
204
+ console.error(`❌ Tool "${tool}" chưa hỗ trợ.`);
205
+ process.exit(1);
206
+ }
207
+
208
+ const toolMap = getToolMap(tool);
209
+ const pkgPath = join(__dirname, '..', '..', 'package.json');
210
+ const version = JSON.parse(readFileSync(pkgPath, 'utf8')).version;
211
+
212
+ console.log(`
213
+ ${c.bold}${c.cyan}╔═══════════════════════════════════════════════════════╗
214
+ ║ 🔄 EVNICT-KIT v${version}: Sync Templates ║
215
+ ╚═══════════════════════════════════════════════════════╝${c.reset}
216
+
217
+ Project: ${c.bold}${name}${c.reset}
218
+ Tool: ${tool} → ${toolMap.agentDir}/
219
+ Repos: ${repos.map(r => r.folder).join(', ')}
220
+ `);
221
+
222
+ // Confirm unless --yes
223
+ if (!options.yes) {
224
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
225
+ const answer = await new Promise(r => rl.question(
226
+ `${c.yellow}⚠️ Sync sẽ GHI ĐÈ tất cả files evnict-kit (workflows, skills, rules).${c.reset}
227
+ Files do user tự thêm sẽ KHÔNG bị xóa.
228
+ Tiếp tục? (y/N): `, r
229
+ ));
230
+ rl.close();
231
+
232
+ if (answer.trim().toLowerCase() !== 'y' && answer.trim().toLowerCase() !== 'yes') {
233
+ console.log('\n ⏭️ Đã hủy sync.');
234
+ return;
235
+ }
236
+ }
237
+
238
+ console.log('');
239
+
240
+ // ── Sync each project ──
241
+ const summary = { totalUpdated: 0, totalNew: 0, userFiles: [] };
242
+
243
+ for (const repo of repos) {
244
+ const projectPath = join(cwd, repo.folder);
245
+ if (!existsSync(projectPath)) {
246
+ console.log(` ⚠️ ${repo.folder}/ không tồn tại — skip`);
247
+ continue;
248
+ }
249
+
250
+ console.log(`${c.bold}── ${repo.type.toUpperCase()}: ${repo.folder} ──${c.reset}`);
251
+
252
+ // Rules
253
+ if (toolMap.rulesDir) {
254
+ console.log(` 📏 Rules → ${toolMap.rulesDir}/`);
255
+ const r = syncRules(projectPath, cwd, tool, toolMap);
256
+ summary.totalUpdated += r.updated;
257
+ if (r.userFiles.length > 0) {
258
+ summary.userFiles.push(...r.userFiles.map(f => `${repo.folder}/${toolMap.rulesDir}/${f}`));
259
+ console.log(` ${c.green}✅ ${r.userFiles.length} user files preserved${c.reset}`);
260
+ }
261
+ }
262
+
263
+ // Workflows
264
+ if (toolMap.workflowsDir) {
265
+ console.log(` 📋 Workflows → ${toolMap.workflowsDir}/`);
266
+ const w = syncWorkflows(projectPath, cwd, tool, toolMap);
267
+ summary.totalUpdated += w.updated;
268
+ if (w.userFiles.length > 0) {
269
+ summary.userFiles.push(...w.userFiles.map(f => `${repo.folder}/${toolMap.workflowsDir}/${f}`));
270
+ console.log(` ${c.green}✅ ${w.userFiles.length} user files preserved${c.reset}`);
271
+ }
272
+ }
273
+
274
+ // Skills
275
+ if (toolMap.skillsDir) {
276
+ console.log(` 🎯 Skills → ${toolMap.skillsDir}/`);
277
+ const s = syncSkills(projectPath, cwd, toolMap);
278
+ summary.totalUpdated += s.updated;
279
+ summary.totalNew += s.newSkills;
280
+ if (s.userSkills.length > 0) {
281
+ summary.userFiles.push(...s.userSkills.map(d => `${repo.folder}/${toolMap.skillsDir}/${d}/`));
282
+ console.log(` ${c.green}✅ ${s.userSkills.length} user skills preserved${c.reset}`);
283
+ }
284
+ }
285
+
286
+ // Shared files (GETTING-STARTED.md)
287
+ console.log(` 📖 Shared files`);
288
+ const sh = syncSharedFiles(projectPath, cwd, toolMap);
289
+ summary.totalUpdated += sh.updated;
290
+
291
+ console.log('');
292
+ }
293
+
294
+ // ── Summary ──
295
+ console.log(`${c.bold}${c.cyan}╔═══════════════════════════════════════════════════════╗
296
+ ║ ✅ Sync complete! ║
297
+ ╠═══════════════════════════════════════════════════════╣${c.reset}
298
+
299
+ ║ 📊 ${c.bold}${summary.totalUpdated} files updated${c.reset}, ${c.bold}${summary.totalNew} new files${c.reset}
300
+ ║ ${c.green}🔒 ${summary.userFiles.length} user files preserved (không bị xóa)${c.reset}`);
301
+
302
+ if (summary.userFiles.length > 0) {
303
+ console.log(`║`);
304
+ console.log(`║ ${c.dim}User files kept:${c.reset}`);
305
+ for (const f of summary.userFiles) {
306
+ console.log(`║ ${c.dim}· ${f}${c.reset}`);
307
+ }
308
+ }
309
+
310
+ console.log(`║
311
+ ║ ${c.dim}Lưu ý: Context file (AGENTS.md/CLAUDE.md/.cursorrules)${c.reset}
312
+ ║ ${c.dim}và rules RP05 (project conventions) KHÔNG bị ghi đè —${c.reset}
313
+ ║ ${c.dim}vì chứa nội dung riêng của từng dự án.${c.reset}
314
+
315
+ ${c.bold}${c.cyan}╚═══════════════════════════════════════════════════════╝${c.reset}
316
+ `);
317
+ }
@@ -114,7 +114,13 @@ function doUpgrade(targetVersion) {
114
114
  });
115
115
  console.log('');
116
116
  console.log(` ${c.green}+${c.reset} ${c.bold}Upgraded successfully -> v${targetVersion}${c.reset}`);
117
- console.log(` ${c.dim}Run ${c.cyan}evnict-kit doctor${c.dim} to verify.${c.reset}`);
117
+ console.log('');
118
+ console.log(` ${c.yellow}!${c.reset} ${c.bold}Quan trọng:${c.reset} Chạy lệnh sau để cập nhật templates vào các project:`);
119
+ console.log(` ${c.cyan} cd <workspace-folder>${c.reset}`);
120
+ console.log(` ${c.cyan} evnict-kit sync${c.reset}`);
121
+ console.log('');
122
+ console.log(` ${c.dim}Lệnh sync sẽ ghi đè workflows/skills/rules mới,${c.reset}`);
123
+ console.log(` ${c.dim}nhưng giữ nguyên files bạn tự thêm.${c.reset}`);
118
124
  console.log('');
119
125
  } catch (e) {
120
126
  console.log('');