evnict-kit 0.2.2 → 0.2.3

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 (47) hide show
  1. package/README.md +10 -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 +7 -7
  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/skills/evnict-kit-create-component/SKILL.md +23 -0
  12. package/templates/skills/evnict-kit-create-page/SKILL.md +23 -0
  13. package/templates/skills/evnict-kit-frontend-design/SKILL.md +161 -0
  14. package/templates/workflows/antigravity/evnict-kit-archive-wiki.md +20 -0
  15. package/templates/workflows/antigravity/evnict-kit-attt.md +20 -0
  16. package/templates/workflows/antigravity/evnict-kit-bug-fix.md +23 -0
  17. package/templates/workflows/antigravity/evnict-kit-feature-large.md +24 -0
  18. package/templates/workflows/antigravity/evnict-kit-feature-small.md +23 -0
  19. package/templates/workflows/antigravity/evnict-kit-handoff.md +23 -0
  20. package/templates/workflows/antigravity/evnict-kit-implement.md +23 -0
  21. package/templates/workflows/antigravity/evnict-kit-init-check.md +20 -0
  22. package/templates/workflows/antigravity/evnict-kit-init-context.md +20 -0
  23. package/templates/workflows/antigravity/evnict-kit-init-rules.md +20 -0
  24. package/templates/workflows/antigravity/evnict-kit-init-wiki.md +20 -0
  25. package/templates/workflows/antigravity/evnict-kit-plan.md +24 -0
  26. package/templates/workflows/antigravity/evnict-kit-review.md +20 -0
  27. package/templates/workflows/antigravity/evnict-kit-spec-archive.md +20 -0
  28. package/templates/workflows/antigravity/evnict-kit-wiki-archive-feature.md +20 -0
  29. package/templates/workflows/antigravity/evnict-kit-wiki-query.md +20 -0
  30. package/templates/workflows/antigravity/evnict-kit-wiki-scan-project.md +20 -0
  31. package/templates/workflows/claude/evnict-kit-archive-wiki.md +20 -0
  32. package/templates/workflows/claude/evnict-kit-attt.md +20 -0
  33. package/templates/workflows/claude/evnict-kit-bug-fix.md +23 -0
  34. package/templates/workflows/claude/evnict-kit-feature-large.md +24 -0
  35. package/templates/workflows/claude/evnict-kit-feature-small.md +23 -0
  36. package/templates/workflows/claude/evnict-kit-handoff.md +23 -0
  37. package/templates/workflows/claude/evnict-kit-implement.md +23 -0
  38. package/templates/workflows/claude/evnict-kit-init-check.md +20 -0
  39. package/templates/workflows/claude/evnict-kit-init-context.md +20 -0
  40. package/templates/workflows/claude/evnict-kit-init-rules.md +20 -0
  41. package/templates/workflows/claude/evnict-kit-init-wiki.md +20 -0
  42. package/templates/workflows/claude/evnict-kit-plan.md +24 -0
  43. package/templates/workflows/claude/evnict-kit-review.md +20 -0
  44. package/templates/workflows/claude/evnict-kit-spec-archive.md +20 -0
  45. package/templates/workflows/claude/evnict-kit-wiki-archive-feature.md +20 -0
  46. package/templates/workflows/claude/evnict-kit-wiki-query.md +20 -0
  47. package/templates/workflows/claude/evnict-kit-wiki-scan-project.md +20 -0
package/README.md CHANGED
@@ -158,6 +158,7 @@ evnict-kit init-rules --tool=cursor
158
158
  | `evnict-kit doctor` | 🩺 Kiểm tra môi trường, phiên bản, cập nhật |
159
159
  | `evnict-kit info` | 📊 Thống kê chi tiết toolkit |
160
160
  | `evnict-kit upgrade` | 🔄 Kiểm tra & cập nhật phiên bản mới nhất |
161
+ | `evnict-kit sync` | 🔄 Re-deploy templates vào các project — ghi đè files evnict-kit, giữ nguyên files user |
161
162
 
162
163
  ### Lệnh Agent (gõ trong AI Agent chat)
163
164
 
@@ -281,7 +282,7 @@ FE: Đọc handoff.md → implement → update 🟢 Đã xử lý
281
282
  | Metric | Count |
282
283
  |--------|-------|
283
284
  | Rule Sets | 5 |
284
- | Skills | 22 |
285
+ | Skills | 23 |
285
286
  | Workflows | 17 |
286
287
  | Supported AI Tools | 5 |
287
288
  | Tech Stacks (BE) | SpringBoot · ASP.NET · Java EE |
@@ -305,7 +306,14 @@ FE: Đọc handoff.md → implement → update 🟢 Đã xử lý
305
306
 
306
307
  ## 📜 Changelog
307
308
 
308
- ### v0.2.2 (Current)
309
+ ### v0.2.3 (Current)
310
+ - 🎨 **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
311
+ - 🧠 **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)
312
+ - 🎯 **FE Design Hint**: 6 workflows có code FE được bổ sung nhắc tham chiếu skill frontend-design
313
+ - 🔄 **Sync Command**: Lệnh `evnict-kit sync` — re-deploy templates khi upgrade version, không xóa files user tự thêm
314
+ - 📦 Hỗ trợ cả Antigravity + Claude workflows
315
+
316
+ ### v0.2.2
309
317
  - 🧹 Removed obsolete standalone commands (`init-rules`, `init-context`, `init-workflow`, `init-check`)
310
318
  - 🩺 `doctor` command: health check, version check, workspace status
311
319
  - 📊 `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.3",
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.3: 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.3: 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.
@@ -363,7 +363,7 @@ async function deployWorkspace({ name, tool, repoConfigs, db, wikiEnabled, cwd }
363
363
 
364
364
  console.log(`
365
365
  ╔═══════════════════════════════════════════════════════╗
366
- ║ ✅ Workspace "${name}" v0.2.2 initialized! ║
366
+ ║ ✅ Workspace "${name}" v0.2.3 initialized! ║
367
367
  ╠═══════════════════════════════════════════════════════╣
368
368
  ║ ║
369
369
  ║ Projects: ║
@@ -468,7 +468,7 @@ function updateGitignore(projectPath, projectName) {
468
468
  }
469
469
 
470
470
  // ════════════════════════════════════════════════════════════════════
471
- // Deploy to individual project — v0.2.2 Multi-Tool Adapter Pattern
471
+ // Deploy to individual project — v0.2.3 Multi-Tool Adapter Pattern
472
472
  // ════════════════════════════════════════════════════════════════════
473
473
 
474
474
  function deployToProject(projectPath, cwd, opts) {
@@ -678,7 +678,7 @@ function deployShared(projectPath, cwd, opts) {
678
678
  writeFile(join(projectPath, 'Instruct-Agent-AI.md'), content, {cwd});
679
679
  }
680
680
 
681
- // ═══ GETTING-STARTED.md (v0.2.2) ═══
681
+ // ═══ GETTING-STARTED.md (v0.2.3) ═══
682
682
  const guideTemplate = join(TEMPLATES_DIR, 'GETTING-STARTED.md');
683
683
  if (existsSync(guideTemplate)) {
684
684
  let content = readFileSync(guideTemplate, 'utf8');
@@ -699,7 +699,7 @@ function genConfig(name, repoConfigs, db, tool, wiki) {
699
699
  ` - { folder: "${r.folder}", type: "${r.type}", tech: "${r.tech}" }`
700
700
  ).join('\n');
701
701
 
702
- return `# EVNICT-KIT v0.2.2 Config
702
+ return `# EVNICT-KIT v0.2.3 Config
703
703
  project:
704
704
  name: "${name}"
705
705
  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('');
@@ -97,7 +97,7 @@ export const TECH_TYPE_HINTS = {
97
97
 
98
98
  /**
99
99
  * Tool-specific directory and file mapping
100
- * Mỗi AI tool có cách tổ chức riêng — v0.2.2 Multi-Tool Support
100
+ * Mỗi AI tool có cách tổ chức riêng — v0.2.3 Multi-Tool Support
101
101
  */
102
102
  export const TOOL_MAP = {
103
103
  antigravity: {
@@ -153,7 +153,7 @@ export const TOOL_MAP = {
153
153
  };
154
154
 
155
155
  /**
156
- * Tools that have templates ready — v0.2.2: ALL 5 tools supported
156
+ * Tools that have templates ready — v0.2.3: ALL 5 tools supported
157
157
  */
158
158
  export const SUPPORTED_TOOLS = ['antigravity', 'claude', 'cursor', 'copilot', 'codex'];
159
159
 
@@ -1,5 +1,5 @@
1
1
  # EVNICT-KIT — Hướng Dẫn Sử Dụng
2
- > File này được tạo bởi evnict-kit v0.2.2
2
+ > File này được tạo bởi evnict-kit v0.2.3
3
3
  > Đọc file này để biết dùng workflow nào cho công việc nào
4
4
 
5
5
  ---
@@ -190,7 +190,28 @@ evnict-kit init → init-rules → init-context → init-check → init-wiki →
190
190
 
191
191
  ---
192
192
 
193
+ ## 🔄 Cập nhật evnict-kit
194
+
195
+ Khi có phiên bản mới:
196
+ ```bash
197
+ # Bước 1: Cập nhật CLI
198
+ evnict-kit upgrade # hoặc: npm install -g evnict-kit@latest
199
+
200
+ # Bước 2: Cập nhật templates vào project
201
+ cd <workspace-folder>
202
+ evnict-kit sync # Ghi đè workflows/skills/rules mới
203
+
204
+ # Bước 3 (tùy chọn): Kiểm tra
205
+ evnict-kit doctor # Verify version + workspace status
206
+ ```
207
+
208
+ > **Lưu ý:** Lệnh `sync` chỉ ghi đè files evnict-kit (prefix `evnict-kit-`).
209
+ > Files bạn tự thêm vào workflows/skills/rules sẽ **KHÔNG bị xóa**.
210
+ > Context file (AGENTS.md) và project conventions cũng **KHÔNG bị ghi đè**.
211
+
212
+ ---
213
+
193
214
  ## 📌 Phiên bản
194
- - **evnict-kit:** v0.2.2
215
+ - **evnict-kit:** v0.2.3
195
216
  - **Ngày tạo:** {{DATE}}
196
217
  - **Project:** {{PROJECT_NAME}}