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.
- package/README.md +10 -2
- package/bin/cli.js +7 -1
- package/package.json +1 -1
- package/scripts/patch-workflows.js +142 -0
- package/scripts/postinstall.js +1 -1
- package/src/commands/init.js +7 -7
- package/src/commands/sync.js +317 -0
- package/src/commands/upgrade.js +7 -1
- package/src/utils/config.js +2 -2
- package/templates/GETTING-STARTED.md +23 -2
- package/templates/skills/evnict-kit-create-component/SKILL.md +23 -0
- package/templates/skills/evnict-kit-create-page/SKILL.md +23 -0
- package/templates/skills/evnict-kit-frontend-design/SKILL.md +161 -0
- package/templates/workflows/antigravity/evnict-kit-archive-wiki.md +20 -0
- package/templates/workflows/antigravity/evnict-kit-attt.md +20 -0
- package/templates/workflows/antigravity/evnict-kit-bug-fix.md +23 -0
- package/templates/workflows/antigravity/evnict-kit-feature-large.md +24 -0
- package/templates/workflows/antigravity/evnict-kit-feature-small.md +23 -0
- package/templates/workflows/antigravity/evnict-kit-handoff.md +23 -0
- package/templates/workflows/antigravity/evnict-kit-implement.md +23 -0
- package/templates/workflows/antigravity/evnict-kit-init-check.md +20 -0
- package/templates/workflows/antigravity/evnict-kit-init-context.md +20 -0
- package/templates/workflows/antigravity/evnict-kit-init-rules.md +20 -0
- package/templates/workflows/antigravity/evnict-kit-init-wiki.md +20 -0
- package/templates/workflows/antigravity/evnict-kit-plan.md +24 -0
- package/templates/workflows/antigravity/evnict-kit-review.md +20 -0
- package/templates/workflows/antigravity/evnict-kit-spec-archive.md +20 -0
- package/templates/workflows/antigravity/evnict-kit-wiki-archive-feature.md +20 -0
- package/templates/workflows/antigravity/evnict-kit-wiki-query.md +20 -0
- package/templates/workflows/antigravity/evnict-kit-wiki-scan-project.md +20 -0
- package/templates/workflows/claude/evnict-kit-archive-wiki.md +20 -0
- package/templates/workflows/claude/evnict-kit-attt.md +20 -0
- package/templates/workflows/claude/evnict-kit-bug-fix.md +23 -0
- package/templates/workflows/claude/evnict-kit-feature-large.md +24 -0
- package/templates/workflows/claude/evnict-kit-feature-small.md +23 -0
- package/templates/workflows/claude/evnict-kit-handoff.md +23 -0
- package/templates/workflows/claude/evnict-kit-implement.md +23 -0
- package/templates/workflows/claude/evnict-kit-init-check.md +20 -0
- package/templates/workflows/claude/evnict-kit-init-context.md +20 -0
- package/templates/workflows/claude/evnict-kit-init-rules.md +20 -0
- package/templates/workflows/claude/evnict-kit-init-wiki.md +20 -0
- package/templates/workflows/claude/evnict-kit-plan.md +24 -0
- package/templates/workflows/claude/evnict-kit-review.md +20 -0
- package/templates/workflows/claude/evnict-kit-spec-archive.md +20 -0
- package/templates/workflows/claude/evnict-kit-wiki-archive-feature.md +20 -0
- package/templates/workflows/claude/evnict-kit-wiki-query.md +20 -0
- 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 |
|
|
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.
|
|
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.
|
|
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
|
@@ -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.`);
|
package/scripts/postinstall.js
CHANGED
package/src/commands/init.js
CHANGED
|
@@ -54,7 +54,7 @@ async function initInteractive(options) {
|
|
|
54
54
|
|
|
55
55
|
console.log(`
|
|
56
56
|
╔═══════════════════════════════════════════╗
|
|
57
|
-
║ 🚀 EVNICT-KIT v0.2.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
+
}
|
package/src/commands/upgrade.js
CHANGED
|
@@ -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(
|
|
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('');
|
package/src/utils/config.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
+
> 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.
|
|
215
|
+
- **evnict-kit:** v0.2.3
|
|
195
216
|
- **Ngày tạo:** {{DATE}}
|
|
196
217
|
- **Project:** {{PROJECT_NAME}}
|