evnict-kit 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/bin/cli.js +38 -0
- package/package.json +48 -0
- package/src/commands/add.js +129 -0
- package/src/commands/init-check.js +19 -0
- package/src/commands/init-context.js +37 -0
- package/src/commands/init-rules.js +42 -0
- package/src/commands/init-workflow.js +36 -0
- package/src/commands/init.js +722 -0
- package/src/utils/config.js +167 -0
- package/src/utils/file.js +53 -0
- package/templates/GETTING-STARTED.md +196 -0
- package/templates/content/context/AGENTS.md.template +462 -0
- package/templates/content/rules/01-evnict-kit-general-rules.md +303 -0
- package/templates/content/rules/02-evnict-kit-security-rules.md +423 -0
- package/templates/content/rules/03-evnict-kit-backend-conventions.md +383 -0
- package/templates/content/rules/04-evnict-kit-frontend-conventions.md +402 -0
- package/templates/content/rules/05-evnict-kit-project-conventions.md +228 -0
- package/templates/content/skills/evnict-kit-brainstorm/SKILL.md +140 -0
- package/templates/content/skills/evnict-kit-bug-fix/SKILL.md +108 -0
- package/templates/content/skills/evnict-kit-checkpoint/SKILL.md +156 -0
- package/templates/content/skills/evnict-kit-code-review/SKILL.md +158 -0
- package/templates/content/skills/evnict-kit-coordinate/SKILL.md +274 -0
- package/templates/content/skills/evnict-kit-create-api/SKILL.md +281 -0
- package/templates/content/skills/evnict-kit-create-component/SKILL.md +263 -0
- package/templates/content/skills/evnict-kit-create-page/SKILL.md +247 -0
- package/templates/content/skills/evnict-kit-database-migration/SKILL.md +164 -0
- package/templates/content/skills/evnict-kit-doc-postmortem/SKILL.md +93 -0
- package/templates/content/skills/evnict-kit-finish-branch/SKILL.md +87 -0
- package/templates/content/skills/evnict-kit-fix-attt/SKILL.md +129 -0
- package/templates/content/skills/evnict-kit-fix-business-logic/SKILL.md +89 -0
- package/templates/content/skills/evnict-kit-git-worktrees/SKILL.md +104 -0
- package/templates/content/skills/evnict-kit-merge-checklist/SKILL.md +108 -0
- package/templates/content/skills/evnict-kit-onboard/SKILL.md +143 -0
- package/templates/content/skills/evnict-kit-prompt-standard/SKILL.md +103 -0
- package/templates/content/skills/evnict-kit-receiving-review/SKILL.md +89 -0
- package/templates/content/skills/evnict-kit-security-audit/SKILL.md +190 -0
- package/templates/content/skills/evnict-kit-spec/SKILL.md +237 -0
- package/templates/content/skills/evnict-kit-tdd/SKILL.md +413 -0
- package/templates/content/skills/evnict-kit-wiki/SKILL.md +412 -0
- package/templates/content/workflows/evnict-kit-archive-wiki.md +100 -0
- package/templates/content/workflows/evnict-kit-attt.md +100 -0
- package/templates/content/workflows/evnict-kit-bug-fix.md +107 -0
- package/templates/content/workflows/evnict-kit-feature-large.md +393 -0
- package/templates/content/workflows/evnict-kit-feature-small.md +86 -0
- package/templates/content/workflows/evnict-kit-handoff.md +243 -0
- package/templates/content/workflows/evnict-kit-implement.md +247 -0
- package/templates/content/workflows/evnict-kit-init-check.md +76 -0
- package/templates/content/workflows/evnict-kit-init-context.md +58 -0
- package/templates/content/workflows/evnict-kit-init-rules.md +114 -0
- package/templates/content/workflows/evnict-kit-init-wiki.md +80 -0
- package/templates/content/workflows/evnict-kit-plan.md +308 -0
- package/templates/content/workflows/evnict-kit-review.md +53 -0
- package/templates/content/workflows/evnict-kit-spec-archive.md +53 -0
- package/templates/content/workflows/evnict-kit-wiki-archive-feature.md +164 -0
- package/templates/content/workflows/evnict-kit-wiki-query.md +91 -0
- package/templates/content/workflows/evnict-kit-wiki-scan-project.md +272 -0
- package/templates/context/AGENT.md.template +9 -0
- package/templates/context/AGENTS.md.template +462 -0
- package/templates/context/CLAUDE.md.template +301 -0
- package/templates/context/copilot-instructions.md.template +60 -0
- package/templates/context/cursorrules.template +114 -0
- package/templates/instruct/Instruct-Agent-AI.be.md +96 -0
- package/templates/instruct/Instruct-Agent-AI.fe.md +79 -0
- package/templates/rules/antigravity/01-evnict-kit-general-rules.md +303 -0
- package/templates/rules/antigravity/02-evnict-kit-security-rules.md +423 -0
- package/templates/rules/antigravity/03-evnict-kit-backend-conventions.md +383 -0
- package/templates/rules/antigravity/04-evnict-kit-frontend-conventions.md +402 -0
- package/templates/rules/antigravity/05-evnict-kit-project-conventions.md +228 -0
- package/templates/rules/claude/README.md +8 -0
- package/templates/rules/cursor/01-evnict-kit-general-rules.mdc +46 -0
- package/templates/rules/cursor/02-evnict-kit-security-rules.mdc +46 -0
- package/templates/rules/cursor/03-evnict-kit-backend-conventions.mdc +50 -0
- package/templates/rules/cursor/04-evnict-kit-frontend-conventions.mdc +43 -0
- package/templates/rules/cursor/05-evnict-kit-project-conventions.mdc +63 -0
- package/templates/rules/cursor/README.md +7 -0
- package/templates/skills/evnict-kit-brainstorm/SKILL.md +140 -0
- package/templates/skills/evnict-kit-bug-fix/SKILL.md +108 -0
- package/templates/skills/evnict-kit-checkpoint/SKILL.md +156 -0
- package/templates/skills/evnict-kit-code-review/SKILL.md +158 -0
- package/templates/skills/evnict-kit-coordinate/SKILL.md +274 -0
- package/templates/skills/evnict-kit-create-api/SKILL.md +281 -0
- package/templates/skills/evnict-kit-create-component/SKILL.md +263 -0
- package/templates/skills/evnict-kit-create-page/SKILL.md +247 -0
- package/templates/skills/evnict-kit-database-migration/SKILL.md +164 -0
- package/templates/skills/evnict-kit-doc-postmortem/SKILL.md +93 -0
- package/templates/skills/evnict-kit-finish-branch/SKILL.md +87 -0
- package/templates/skills/evnict-kit-fix-attt/SKILL.md +129 -0
- package/templates/skills/evnict-kit-fix-business-logic/SKILL.md +89 -0
- package/templates/skills/evnict-kit-git-worktrees/SKILL.md +104 -0
- package/templates/skills/evnict-kit-merge-checklist/SKILL.md +108 -0
- package/templates/skills/evnict-kit-onboard/SKILL.md +143 -0
- package/templates/skills/evnict-kit-prompt-standard/SKILL.md +103 -0
- package/templates/skills/evnict-kit-receiving-review/SKILL.md +89 -0
- package/templates/skills/evnict-kit-security-audit/SKILL.md +190 -0
- package/templates/skills/evnict-kit-spec/SKILL.md +237 -0
- package/templates/skills/evnict-kit-tdd/SKILL.md +413 -0
- package/templates/skills/evnict-kit-wiki/SKILL.md +412 -0
- package/templates/wiki/README.md +35 -0
- package/templates/wiki/config.example.yaml +17 -0
- package/templates/wiki/package.json +17 -0
- package/templates/wiki/raw/notes/.gitkeep +1 -0
- package/templates/wiki/scripts/ingest.js +66 -0
- package/templates/workflows/antigravity/evnict-kit-archive-wiki.md +100 -0
- package/templates/workflows/antigravity/evnict-kit-attt.md +100 -0
- package/templates/workflows/antigravity/evnict-kit-bug-fix.md +107 -0
- package/templates/workflows/antigravity/evnict-kit-feature-large.md +393 -0
- package/templates/workflows/antigravity/evnict-kit-feature-small.md +86 -0
- package/templates/workflows/antigravity/evnict-kit-handoff.md +243 -0
- package/templates/workflows/antigravity/evnict-kit-implement.md +247 -0
- package/templates/workflows/antigravity/evnict-kit-init-check.md +76 -0
- package/templates/workflows/antigravity/evnict-kit-init-context.md +58 -0
- package/templates/workflows/antigravity/evnict-kit-init-rules.md +114 -0
- package/templates/workflows/antigravity/evnict-kit-init-wiki.md +80 -0
- package/templates/workflows/antigravity/evnict-kit-plan.md +308 -0
- package/templates/workflows/antigravity/evnict-kit-review.md +53 -0
- package/templates/workflows/antigravity/evnict-kit-spec-archive.md +53 -0
- package/templates/workflows/antigravity/evnict-kit-wiki-archive-feature.md +164 -0
- package/templates/workflows/antigravity/evnict-kit-wiki-query.md +91 -0
- package/templates/workflows/antigravity/evnict-kit-wiki-scan-project.md +272 -0
- package/templates/workflows/claude/README.md +6 -0
- package/templates/workflows/claude/evnict-kit-archive-wiki.md +98 -0
- package/templates/workflows/claude/evnict-kit-attt.md +98 -0
- package/templates/workflows/claude/evnict-kit-bug-fix.md +105 -0
- package/templates/workflows/claude/evnict-kit-feature-large.md +391 -0
- package/templates/workflows/claude/evnict-kit-feature-small.md +84 -0
- package/templates/workflows/claude/evnict-kit-handoff.md +240 -0
- package/templates/workflows/claude/evnict-kit-implement.md +245 -0
- package/templates/workflows/claude/evnict-kit-init-check.md +74 -0
- package/templates/workflows/claude/evnict-kit-init-context.md +56 -0
- package/templates/workflows/claude/evnict-kit-init-rules.md +112 -0
- package/templates/workflows/claude/evnict-kit-init-wiki.md +78 -0
- package/templates/workflows/claude/evnict-kit-plan.md +305 -0
- package/templates/workflows/claude/evnict-kit-review.md +51 -0
- package/templates/workflows/claude/evnict-kit-spec-archive.md +51 -0
- package/templates/workflows/claude/evnict-kit-wiki-archive-feature.md +162 -0
- package/templates/workflows/claude/evnict-kit-wiki-query.md +89 -0
- package/templates/workflows/claude/evnict-kit-wiki-scan-project.md +270 -0
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
import { existsSync, writeFileSync, readFileSync, readdirSync, mkdirSync, lstatSync, symlinkSync } from 'node:fs';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
import readline from 'node:readline';
|
|
4
|
+
import { ensureDir, writeFile, copyTemplateDir, TEMPLATES_DIR } from '../utils/file.js';
|
|
5
|
+
import { TECH_LABELS, TECH_TYPE_HINTS, getToolMap, SUPPORTED_TOOLS } from '../utils/config.js';
|
|
6
|
+
|
|
7
|
+
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
8
|
+
// Entry point β route to interactive or non-interactive
|
|
9
|
+
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
10
|
+
|
|
11
|
+
export async function initCommand(options) {
|
|
12
|
+
// NαΊΏu cΓ³ --name hoαΊ·c --no-interactive β backward compat, chαΊ‘y nhΖ° v0.1.3
|
|
13
|
+
if (options.name || options.interactive === false) {
|
|
14
|
+
if (!options.name) {
|
|
15
|
+
console.error('β --no-interactive yΓͺu cαΊ§u --name. VD: evnict-kit init --name=ncpt --no-interactive');
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
return initNonInteractive(options);
|
|
19
|
+
}
|
|
20
|
+
// KhΓ΄ng cΓ³ --name β interactive wizard
|
|
21
|
+
return initInteractive(options);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
25
|
+
// NON-INTERACTIVE MODE (backward compat v0.1.3)
|
|
26
|
+
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
27
|
+
|
|
28
|
+
async function initNonInteractive(options) {
|
|
29
|
+
const cwd = process.cwd();
|
|
30
|
+
const name = options.name;
|
|
31
|
+
const be = options.be || `${name}-be`;
|
|
32
|
+
const fe = options.fe || `${name}-fe`;
|
|
33
|
+
const tool = options.tool || 'antigravity';
|
|
34
|
+
const techBe = options.techBe || 'springboot';
|
|
35
|
+
const techFe = options.techFe || 'angular';
|
|
36
|
+
const db = options.db || 'oracle';
|
|
37
|
+
const wikiEnabled = options.wiki !== false;
|
|
38
|
+
|
|
39
|
+
const repoConfigs = [
|
|
40
|
+
{ folder: be, type: 'backend', tech: techBe },
|
|
41
|
+
{ folder: fe, type: 'frontend', tech: techFe },
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
await deployWorkspace({ name, tool, repoConfigs, db, wikiEnabled, cwd });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
48
|
+
// INTERACTIVE MODE (v0.1.4 wizard)
|
|
49
|
+
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
50
|
+
|
|
51
|
+
async function initInteractive(options) {
|
|
52
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
53
|
+
const ask = (q) => new Promise(r => rl.question(q, r));
|
|
54
|
+
|
|
55
|
+
console.log(`
|
|
56
|
+
βββββββββββββββββββββββββββββββββββββββββββββ
|
|
57
|
+
β π EVNICT-KIT v0.2.1: Init Setup β
|
|
58
|
+
βββββββββββββββββββββββββββββββββββββββββββββ
|
|
59
|
+
`);
|
|
60
|
+
|
|
61
|
+
// Step 1: TΓͺn dα»± Γ‘n
|
|
62
|
+
const name = (await ask('? TΓͺn dα»± Γ‘n: ')).trim();
|
|
63
|
+
if (!name) {
|
|
64
|
+
console.error('β TΓͺn dα»± Γ‘n khΓ΄ng Δược Δα» trα»ng.');
|
|
65
|
+
rl.close();
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Step 2: Chα»n AI Tool
|
|
70
|
+
const toolChoices = ['antigravity', 'claude', 'cursor', 'codex', 'copilot'];
|
|
71
|
+
console.log('\n? Chα»n AI Tool:');
|
|
72
|
+
toolChoices.forEach((t, i) => {
|
|
73
|
+
const marker = i === 0 ? ' (default)' : '';
|
|
74
|
+
const supported = SUPPORTED_TOOLS.includes(t) ? '' : ' [chΖ°a cΓ³ template]';
|
|
75
|
+
console.log(` ${i + 1}. ${t}${marker}${supported}`);
|
|
76
|
+
});
|
|
77
|
+
const toolInput = (await ask(` Chα»n (1-${toolChoices.length}) [1]: `)).trim();
|
|
78
|
+
const toolIdx = toolInput ? parseInt(toolInput, 10) - 1 : 0;
|
|
79
|
+
const tool = toolChoices[toolIdx] || 'antigravity';
|
|
80
|
+
|
|
81
|
+
if (!SUPPORTED_TOOLS.includes(tool)) {
|
|
82
|
+
console.log(`\nβ οΈ ${tool} templates chΖ°a sαΊ΅n sΓ ng. DΓΉng 1 trong 5 tool: ${SUPPORTED_TOOLS.join(', ')}.`);
|
|
83
|
+
rl.close();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Step 3: Scan workspace
|
|
88
|
+
const cwd = process.cwd();
|
|
89
|
+
console.log('\nScanning workspace...');
|
|
90
|
+
const folders = scanWorkspaceFolders(cwd);
|
|
91
|
+
|
|
92
|
+
if (folders.length === 0) {
|
|
93
|
+
console.log(' KhΓ΄ng tΓ¬m thαΊ₯y project folder nΓ o trong workspace.');
|
|
94
|
+
console.log(' TαΊ‘o folder cho BE/FE trΖ°α»c, rα»i chαΊ‘y lαΊ‘i evnict-kit init.\n');
|
|
95
|
+
rl.close();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.log(`TΓ¬m thαΊ₯y ${folders.length} folders:`);
|
|
100
|
+
folders.forEach((f, i) => {
|
|
101
|
+
const techLabel = f.detectedTech ? ` (detected: ${TECH_LABELS[f.detectedTech] || f.detectedTech})` : '';
|
|
102
|
+
console.log(` ${i + 1}. ${f.folder}/${techLabel}`);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Step 4: Chα»n repos (multi-select)
|
|
106
|
+
const repoInput = (await ask(`\n? Chα»n repos (nhαΊp sα», phΓ’n cΓ‘ch dαΊ₯u phαΊ©y, hoαΊ·c 'all') [all]: `)).trim();
|
|
107
|
+
let selectedFolders;
|
|
108
|
+
if (!repoInput || repoInput.toLowerCase() === 'all') {
|
|
109
|
+
selectedFolders = [...folders];
|
|
110
|
+
} else {
|
|
111
|
+
const indices = repoInput.split(',').map(s => parseInt(s.trim(), 10) - 1);
|
|
112
|
+
selectedFolders = indices
|
|
113
|
+
.filter(i => i >= 0 && i < folders.length)
|
|
114
|
+
.map(i => folders[i]);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (selectedFolders.length === 0) {
|
|
118
|
+
console.log('β KhΓ΄ng cΓ³ repo nΓ o Δược chα»n.');
|
|
119
|
+
rl.close();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Step 5: GΓ‘n type cho mα»i repo (auto-detect + confirm)
|
|
124
|
+
console.log('');
|
|
125
|
+
const repoConfigs = [];
|
|
126
|
+
for (const f of selectedFolders) {
|
|
127
|
+
const suggestedType = f.detectedTech ? (TECH_TYPE_HINTS[f.detectedTech] || '') : '';
|
|
128
|
+
const defaultLabel = suggestedType ? ` [${suggestedType}]` : '';
|
|
129
|
+
const techInfo = f.detectedTech ? ` (${TECH_LABELS[f.detectedTech] || f.detectedTech})` : '';
|
|
130
|
+
const typeInput = (await ask(`? LoαΊ‘i cho ${f.folder}${techInfo}${defaultLabel}: `)).trim();
|
|
131
|
+
const type = typeInput || suggestedType || 'backend';
|
|
132
|
+
const tech = f.detectedTech || (type === 'backend' ? 'springboot' : 'angular');
|
|
133
|
+
repoConfigs.push({ folder: f.folder, type, tech });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Step 6: Database
|
|
137
|
+
const dbInput = (await ask('\n? Database (oracle/sqlserver) [oracle]: ')).trim();
|
|
138
|
+
const db = dbInput || 'oracle';
|
|
139
|
+
|
|
140
|
+
// Step 7: Wiki
|
|
141
|
+
const wikiInput = (await ask('? ThΓͺm LLM Wiki? (Y/n): ')).trim();
|
|
142
|
+
const wikiEnabled = wikiInput.toLowerCase() !== 'n';
|
|
143
|
+
|
|
144
|
+
rl.close();
|
|
145
|
+
|
|
146
|
+
console.log('\nInitializing...\n');
|
|
147
|
+
await deployWorkspace({ name, tool, repoConfigs, db, wikiEnabled, cwd });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
151
|
+
// Scan workspace folders + detect tech
|
|
152
|
+
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
153
|
+
|
|
154
|
+
function scanWorkspaceFolders(cwd) {
|
|
155
|
+
const entries = readdirSync(cwd, { withFileTypes: true });
|
|
156
|
+
const results = [];
|
|
157
|
+
|
|
158
|
+
for (const entry of entries) {
|
|
159
|
+
if (!entry.isDirectory()) continue;
|
|
160
|
+
// Skip hidden dirs and well-known non-project dirs
|
|
161
|
+
if (entry.name.startsWith('.')) continue;
|
|
162
|
+
if (['node_modules', 'docs', 'database', 'dist', 'build', 'target'].includes(entry.name)) continue;
|
|
163
|
+
if (entry.name.endsWith('-wiki')) continue;
|
|
164
|
+
|
|
165
|
+
const folderPath = join(cwd, entry.name);
|
|
166
|
+
const detectedTech = detectTech(folderPath);
|
|
167
|
+
results.push({ folder: entry.name, detectedTech });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return results;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function detectTech(folderPath) {
|
|
174
|
+
// Java Spring Boot
|
|
175
|
+
if (existsSync(join(folderPath, 'pom.xml'))) {
|
|
176
|
+
try {
|
|
177
|
+
const pom = readFileSync(join(folderPath, 'pom.xml'), 'utf8');
|
|
178
|
+
if (pom.includes('spring-boot-starter')) return 'springboot';
|
|
179
|
+
} catch { /* ignore */ }
|
|
180
|
+
return 'springboot'; // Java project, assume Spring Boot
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Java/Kotlin Gradle
|
|
184
|
+
if (existsSync(join(folderPath, 'build.gradle')) || existsSync(join(folderPath, 'build.gradle.kts'))) {
|
|
185
|
+
return 'springboot';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Angular
|
|
189
|
+
if (existsSync(join(folderPath, 'angular.json'))) {
|
|
190
|
+
return 'angular';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ASP.NET Core
|
|
194
|
+
try {
|
|
195
|
+
const files = readdirSync(folderPath);
|
|
196
|
+
if (files.some(f => f.endsWith('.csproj') || f.endsWith('.sln'))) {
|
|
197
|
+
return 'aspnet';
|
|
198
|
+
}
|
|
199
|
+
} catch { /* ignore */ }
|
|
200
|
+
|
|
201
|
+
// Node.js / React / React Native / Next.js
|
|
202
|
+
const pkgPath = join(folderPath, 'package.json');
|
|
203
|
+
if (existsSync(pkgPath)) {
|
|
204
|
+
try {
|
|
205
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
206
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
207
|
+
if (allDeps['react-native']) return 'react-mobile';
|
|
208
|
+
if (allDeps['next']) return 'nextjs';
|
|
209
|
+
if (allDeps['react']) return 'react-web';
|
|
210
|
+
return 'nodejs';
|
|
211
|
+
} catch { /* ignore */ }
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return null; // Unknown
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
218
|
+
// Deploy workspace β shared logic for interactive + non-interactive
|
|
219
|
+
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
220
|
+
|
|
221
|
+
async function deployWorkspace({ name, tool, repoConfigs, db, wikiEnabled, cwd }) {
|
|
222
|
+
const toolMap = getToolMap(tool);
|
|
223
|
+
|
|
224
|
+
console.log(`
|
|
225
|
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
226
|
+
β π EVNICT-KIT v0.2.1: Init Workspace β
|
|
227
|
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
228
|
+
|
|
229
|
+
Project: ${name}
|
|
230
|
+
Repos: ${repoConfigs.map(r => `${r.folder} (${r.type}/${TECH_LABELS[r.tech] || r.tech})`).join(', ')}
|
|
231
|
+
Database: ${TECH_LABELS[db] || db}
|
|
232
|
+
AI Tool: ${tool} β ${toolMap.agentDir}/ + ${toolMap.contextFile}
|
|
233
|
+
Wiki: ${wikiEnabled ? 'enabled' : 'disabled'}
|
|
234
|
+
`);
|
|
235
|
+
|
|
236
|
+
const totalSteps = 3 + repoConfigs.length + (wikiEnabled ? 1 : 0) + 1;
|
|
237
|
+
let step = 0;
|
|
238
|
+
|
|
239
|
+
// ββ 1. Workspace-level .evnict/ ββ
|
|
240
|
+
step++;
|
|
241
|
+
console.log(`ββ [${step}/${totalSteps}] Workspace .evnict/ ββ`);
|
|
242
|
+
const evnictDir = join(cwd, '.evnict');
|
|
243
|
+
for (const d of [evnictDir, join(evnictDir,'handoff'), join(evnictDir,'handoff/contracts'), join(evnictDir,'specs')]) {
|
|
244
|
+
ensureDir(d, cwd);
|
|
245
|
+
}
|
|
246
|
+
writeFile(join(evnictDir, 'config.yaml'), genConfig(name, repoConfigs, db, tool, wikiEnabled), {cwd});
|
|
247
|
+
writeFile(join(evnictDir, 'handoff/current-feature.md'), `# Current Feature\nstatus: idle\n`, {cwd,silent:true});
|
|
248
|
+
writeFile(join(evnictDir, 'handoff/be-status.md'), `# BE Status\nstatus: idle\n`, {cwd,silent:true});
|
|
249
|
+
writeFile(join(evnictDir, 'handoff/fe-status.md'), `# FE Status\nstatus: idle\n`, {cwd,silent:true});
|
|
250
|
+
|
|
251
|
+
// v0.2.1: TαΊ‘o handoff.md template
|
|
252
|
+
const handoffTemplate = `# Agent Handoff Log
|
|
253
|
+
> File nΓ y dΓΉng Δα» trao Δα»i giα»―a BE Agent vΓ FE Agent.
|
|
254
|
+
> Mα»i issue ghi theo format bΓͺn dΖ°α»i.
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Template
|
|
259
|
+
### [YYYY-MM-DD] {FEβBE | BEβFE}: TiΓͺu Δα» ngαΊ―n
|
|
260
|
+
- **TrαΊ‘ng thΓ‘i:** π΄ Chα» xα» lΓ½ | π‘ Δang xα» lΓ½ | π’ ΔΓ£ xα» lΓ½
|
|
261
|
+
- **MΓ΄ tαΊ£:** ...
|
|
262
|
+
- **API liΓͺn quan:** METHOD /endpoint
|
|
263
|
+
- **Request/Response mαΊ«u:** ...
|
|
264
|
+
- **Mong muα»n:** ...
|
|
265
|
+
- **File liΓͺn quan:** ...
|
|
266
|
+
- **KαΊΏt quαΊ£ xα» lΓ½:** (bΓͺn nhαΊn Δiα»n sau khi fix)
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Entries
|
|
271
|
+
|
|
272
|
+
`;
|
|
273
|
+
writeFile(join(evnictDir, 'handoff/handoff.md'), handoffTemplate, {cwd,silent:true});
|
|
274
|
+
|
|
275
|
+
// ββ 2. Docs + Database folders ββ
|
|
276
|
+
step++;
|
|
277
|
+
console.log(`\nββ [${step}/${totalSteps}] Docs & Database ββ`);
|
|
278
|
+
for (const d of ['docs','docs/specs','docs/attt','docs/postmortem','database','database/migrations']) {
|
|
279
|
+
ensureDir(join(cwd,d), cwd);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ββ Wiki ββ
|
|
283
|
+
if (wikiEnabled) {
|
|
284
|
+
step++;
|
|
285
|
+
console.log(`\nββ [${step}/${totalSteps}] Wiki ββ`);
|
|
286
|
+
|
|
287
|
+
// v0.1.5: Detect wiki repo cΓ³ sαΊ΅n trong workspace
|
|
288
|
+
const existingWiki = readdirSync(cwd, { withFileTypes: true })
|
|
289
|
+
.filter(e => e.isDirectory())
|
|
290
|
+
.map(e => e.name)
|
|
291
|
+
.filter(f => f.endsWith('-wiki') || f === 'llm-wiki')
|
|
292
|
+
.filter(f => existsSync(join(cwd, f, 'config.yaml')) || existsSync(join(cwd, f, 'config.example.yaml')));
|
|
293
|
+
|
|
294
|
+
if (existingWiki.length > 0 && !existingWiki.includes(`${name}-wiki`)) {
|
|
295
|
+
console.log(` π PhΓ‘t hiα»n wiki repo cΓ³ sαΊ΅n: ${existingWiki.join(', ')}`);
|
|
296
|
+
console.log(` π‘ SαΊ½ dΓΉng wiki ΔαΊ§u tiΓͺn tΓ¬m Δược: ${existingWiki[0]}`);
|
|
297
|
+
console.log(` π‘ NαΊΏu muα»n dΓΉng wiki khΓ‘c, rename thΓ nh ${name}-wiki hoαΊ·c sα»a .evnict/config.yaml`);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const wikiPath = join(cwd, `${name}-wiki`);
|
|
301
|
+
if (!existsSync(wikiPath)) {
|
|
302
|
+
console.log(' π Copying wiki template...');
|
|
303
|
+
const wikiSrc = join(TEMPLATES_DIR, 'wiki');
|
|
304
|
+
if (existsSync(wikiSrc)) {
|
|
305
|
+
ensureDir(wikiPath, cwd);
|
|
306
|
+
const count = copyTemplateDir(wikiSrc, wikiPath, cwd);
|
|
307
|
+
const configPath = join(wikiPath, 'config.example.yaml');
|
|
308
|
+
if (existsSync(configPath)) {
|
|
309
|
+
let content = readFileSync(configPath, 'utf8');
|
|
310
|
+
content = content.replaceAll('{{PROJECT_NAME}}', name);
|
|
311
|
+
writeFileSync(join(wikiPath, 'config.yaml'), content, 'utf8');
|
|
312
|
+
console.log(` β
config.yaml created`);
|
|
313
|
+
}
|
|
314
|
+
console.log(` β
Wiki template deployed (${count} files)`);
|
|
315
|
+
console.log(` π‘ Run: cd ${name}-wiki && npm install`);
|
|
316
|
+
} else {
|
|
317
|
+
console.log(` β οΈ Wiki template not found β fallback to manual setup`);
|
|
318
|
+
ensureDir(wikiPath, cwd);
|
|
319
|
+
ensureDir(join(wikiPath, 'raw/notes'), cwd);
|
|
320
|
+
}
|
|
321
|
+
} else {
|
|
322
|
+
console.log(` βοΈ Wiki repo ΔΓ£ tα»n tαΊ‘i: ${name}-wiki/`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// ββ 3..N. Deploy vΓ o tα»«ng project ββ
|
|
327
|
+
for (const repo of repoConfigs) {
|
|
328
|
+
step++;
|
|
329
|
+
console.log(`\nββ [${step}/${totalSteps}] ${repo.type.toUpperCase()} project: ${repo.folder} ββ`);
|
|
330
|
+
const projectPath = join(cwd, repo.folder);
|
|
331
|
+
if (existsSync(projectPath)) {
|
|
332
|
+
deployToProject(projectPath, cwd, {
|
|
333
|
+
tool, toolMap, name, type: repo.type, tech: repo.tech, db,
|
|
334
|
+
allRepos: repoConfigs,
|
|
335
|
+
});
|
|
336
|
+
// Symlinks
|
|
337
|
+
createSymlinks(projectPath, cwd, name);
|
|
338
|
+
// Update .gitignore
|
|
339
|
+
updateGitignore(projectPath, name);
|
|
340
|
+
} else {
|
|
341
|
+
console.log(` β οΈ Folder "${repo.folder}" chΖ°a tα»n tαΊ‘i β skip`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
// ββ Workspace file ββ
|
|
347
|
+
step++;
|
|
348
|
+
console.log(`\nββ [${step}/${totalSteps}] Workspace file ββ`);
|
|
349
|
+
const wsContent = {
|
|
350
|
+
folders: repoConfigs.map(r => ({
|
|
351
|
+
name: `${r.type === 'backend' ? 'π§' : 'π¨'} ${r.folder}`,
|
|
352
|
+
path: r.folder,
|
|
353
|
+
})),
|
|
354
|
+
settings: { 'evnict-kit.project': name, 'evnict-kit.tool': tool }
|
|
355
|
+
};
|
|
356
|
+
wsContent.folders.push({ name: 'π docs', path: 'docs' });
|
|
357
|
+
wsContent.folders.push({ name: 'ποΈ database', path: 'database' });
|
|
358
|
+
if (wikiEnabled) wsContent.folders.push({ name: 'π wiki', path: `${name}-wiki` });
|
|
359
|
+
writeFile(join(cwd, `${name}.code-workspace`), JSON.stringify(wsContent, null, 2), {cwd});
|
|
360
|
+
|
|
361
|
+
// ββ Summary ββ
|
|
362
|
+
const repoList = repoConfigs.map(r => `β ${r.type === 'backend' ? 'π§' : 'π¨'} ${r.folder} (${TECH_LABELS[r.tech] || r.tech})`).join('\n');
|
|
363
|
+
|
|
364
|
+
console.log(`
|
|
365
|
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
366
|
+
β β
Workspace "${name}" v0.2.1 initialized! β
|
|
367
|
+
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£
|
|
368
|
+
β β
|
|
369
|
+
β Projects: β
|
|
370
|
+
${repoList}
|
|
371
|
+
β β
|
|
372
|
+
β CαΊ₯u trΓΊc mα»i project: β
|
|
373
|
+
β ${toolMap.agentDir}/ β
|
|
374
|
+
β βββ rules/ β rule files (flat) β
|
|
375
|
+
β βββ skills/ β skills β
|
|
376
|
+
β βββ workflows/ β workflows + commands (flat) β
|
|
377
|
+
β π Symlinks: ${name}-wiki, docs, database, .evnict β
|
|
378
|
+
β β
|
|
379
|
+
β π Next steps: β
|
|
380
|
+
β 1. Mα» AI Agent trong project β
|
|
381
|
+
β 2. /evnict-kit:init-rules β Δiα»n rules RP01-RP07 β
|
|
382
|
+
β 3. /evnict-kit:init-context β sinh context file β
|
|
383
|
+
β 4. /evnict-kit:init-check β verify setup β
|
|
384
|
+
β β
|
|
385
|
+
β π‘ ThΓͺm project sau: evnict-kit add <folder> β
|
|
386
|
+
β β
|
|
387
|
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
388
|
+
`);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
392
|
+
// Create symbolic links
|
|
393
|
+
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
394
|
+
|
|
395
|
+
function createSymlinks(projectPath, cwd, projectName) {
|
|
396
|
+
const linkType = process.platform === 'win32' ? 'junction' : 'dir';
|
|
397
|
+
|
|
398
|
+
const links = [
|
|
399
|
+
{ target: join(cwd, `${projectName}-wiki`), link: join(projectPath, `${projectName}-wiki`), label: `${projectName}-wiki` },
|
|
400
|
+
{ target: join(cwd, 'docs'), link: join(projectPath, 'docs'), label: 'docs' },
|
|
401
|
+
{ target: join(cwd, 'database'), link: join(projectPath, 'database'), label: 'database' },
|
|
402
|
+
{ target: join(cwd, '.evnict'), link: join(projectPath, '.evnict'), label: '.evnict' },
|
|
403
|
+
];
|
|
404
|
+
|
|
405
|
+
for (const { target, link, label } of links) {
|
|
406
|
+
if (!existsSync(target)) {
|
|
407
|
+
// Target doesn't exist at workspace level β skip quietly
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (existsSync(link)) {
|
|
412
|
+
try {
|
|
413
|
+
const stat = lstatSync(link);
|
|
414
|
+
if (stat.isSymbolicLink()) {
|
|
415
|
+
console.log(` βοΈ Symlink ${label} ΔΓ£ tα»n tαΊ‘i β skip`);
|
|
416
|
+
} else {
|
|
417
|
+
// Real directory/file β warn, don't overwrite
|
|
418
|
+
console.log(` β οΈ ${label} ΔΓ£ tα»n tαΊ‘i (khΓ΄ng phαΊ£i symlink) β skip`);
|
|
419
|
+
console.log(` NαΊΏu muα»n dΓΉng ${label} chung tα»« workspace:`);
|
|
420
|
+
console.log(` 1. Di chuyα»n nα»i dung ${label}/ hiα»n tαΊ‘i vΓ o workspace`);
|
|
421
|
+
console.log(` 2. XΓ³a folder ${label}/ trong project`);
|
|
422
|
+
console.log(` 3. ChαΊ‘y lαΊ‘i evnict-kit init hoαΊ·c evnict-kit add`);
|
|
423
|
+
}
|
|
424
|
+
} catch {
|
|
425
|
+
console.log(` β οΈ ${label} tα»n tαΊ‘i nhΖ°ng khΓ΄ng Δα»c Δược stat β skip`);
|
|
426
|
+
}
|
|
427
|
+
} else {
|
|
428
|
+
try {
|
|
429
|
+
// For junctions on Windows, target must be absolute
|
|
430
|
+
const absTarget = resolve(target);
|
|
431
|
+
symlinkSync(absTarget, link, linkType);
|
|
432
|
+
console.log(` π Symlink: ${label} β workspace ${label}`);
|
|
433
|
+
} catch (err) {
|
|
434
|
+
console.log(` β KhΓ΄ng tαΊ‘o Δược symlink ${label}: ${err.message}`);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
441
|
+
// Update .gitignore with symlink entries
|
|
442
|
+
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
443
|
+
|
|
444
|
+
function updateGitignore(projectPath, projectName) {
|
|
445
|
+
const gitignorePath = join(projectPath, '.gitignore');
|
|
446
|
+
const symlinkEntries = [
|
|
447
|
+
`# Symlinks tα»« evnict-kit`,
|
|
448
|
+
`${projectName}-wiki`,
|
|
449
|
+
`docs`,
|
|
450
|
+
`database`,
|
|
451
|
+
`.evnict`,
|
|
452
|
+
];
|
|
453
|
+
|
|
454
|
+
let content = '';
|
|
455
|
+
if (existsSync(gitignorePath)) {
|
|
456
|
+
content = readFileSync(gitignorePath, 'utf8');
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Check if already has evnict-kit symlink section
|
|
460
|
+
if (content.includes('# Symlinks tα»« evnict-kit')) {
|
|
461
|
+
return; // Already configured
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const separator = content.endsWith('\n') || content === '' ? '' : '\n';
|
|
465
|
+
const addition = `${separator}\n${symlinkEntries.join('\n')}\n`;
|
|
466
|
+
writeFileSync(gitignorePath, content + addition, 'utf8');
|
|
467
|
+
console.log(` β
.gitignore updated (symlink entries)`);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
471
|
+
// Deploy to individual project β v0.2.1 Multi-Tool Adapter Pattern
|
|
472
|
+
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
473
|
+
|
|
474
|
+
function deployToProject(projectPath, cwd, opts) {
|
|
475
|
+
const { tool, toolMap } = opts;
|
|
476
|
+
|
|
477
|
+
switch (toolMap.deployMode) {
|
|
478
|
+
case 'flat':
|
|
479
|
+
// Antigravity + Codex: deploy rules/, skills/, workflows/ riΓͺng
|
|
480
|
+
if (tool === 'codex') {
|
|
481
|
+
// Codex reuse antigravity templates
|
|
482
|
+
deployFlat(projectPath, cwd, { ...opts, tool: 'antigravity' });
|
|
483
|
+
} else {
|
|
484
|
+
deployFlat(projectPath, cwd, opts);
|
|
485
|
+
}
|
|
486
|
+
break;
|
|
487
|
+
|
|
488
|
+
case 'mega-file':
|
|
489
|
+
// Claude Code: sinh 1 CLAUDE.md lα»n + .claude/commands/
|
|
490
|
+
deployClaude(projectPath, cwd, opts);
|
|
491
|
+
break;
|
|
492
|
+
|
|
493
|
+
case 'rules-only':
|
|
494
|
+
// Cursor: deploy .cursor/rules/*.mdc + .cursorrules
|
|
495
|
+
deployCursor(projectPath, cwd, opts);
|
|
496
|
+
break;
|
|
497
|
+
|
|
498
|
+
case 'single-file':
|
|
499
|
+
// Copilot: sinh 1 file .github/copilot-instructions.md
|
|
500
|
+
deployCopilot(projectPath, cwd, opts);
|
|
501
|
+
break;
|
|
502
|
+
|
|
503
|
+
default:
|
|
504
|
+
console.log(` β οΈ Unknown deployMode: ${toolMap.deployMode} β fallback to flat`);
|
|
505
|
+
deployFlat(projectPath, cwd, opts);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// βββ Shared: Context file + Instruct + Getting Started βββ
|
|
509
|
+
deployShared(projectPath, cwd, opts);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
513
|
+
// deployFlat β Antigravity / Codex (original logic, unchanged output)
|
|
514
|
+
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
515
|
+
|
|
516
|
+
function deployFlat(projectPath, cwd, opts) {
|
|
517
|
+
const { tool, toolMap } = opts;
|
|
518
|
+
|
|
519
|
+
// βββ Rules β FLAT deployment βββ
|
|
520
|
+
console.log(` π Rules β ${toolMap.rulesDir}/`);
|
|
521
|
+
const rulesSrc = join(TEMPLATES_DIR, 'rules', tool);
|
|
522
|
+
const rulesDest = join(projectPath, toolMap.rulesDir);
|
|
523
|
+
if (existsSync(rulesSrc)) {
|
|
524
|
+
ensureDir(rulesDest, cwd);
|
|
525
|
+
const ruleFiles = readdirSync(rulesSrc).filter(f => f.endsWith('.md'));
|
|
526
|
+
for (const file of ruleFiles) {
|
|
527
|
+
const content = readFileSync(join(rulesSrc, file), 'utf8');
|
|
528
|
+
writeFile(join(rulesDest, file), content, {cwd});
|
|
529
|
+
}
|
|
530
|
+
console.log(` ${ruleFiles.length} rule files deployed`);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// βββ Skills β subfolder/SKILL.md (unchanged structure) βββ
|
|
534
|
+
console.log(` π― Skills β ${toolMap.skillsDir}/`);
|
|
535
|
+
const skillsSrc = join(TEMPLATES_DIR, 'skills');
|
|
536
|
+
if (existsSync(skillsSrc)) {
|
|
537
|
+
const skillDirs = readdirSync(skillsSrc, {withFileTypes:true})
|
|
538
|
+
.filter(d => d.isDirectory());
|
|
539
|
+
for (const d of skillDirs) {
|
|
540
|
+
const dest = join(projectPath, toolMap.skillsDir, d.name);
|
|
541
|
+
ensureDir(dest, cwd);
|
|
542
|
+
copyTemplateDir(join(skillsSrc, d.name), dest, cwd);
|
|
543
|
+
}
|
|
544
|
+
console.log(` ${skillDirs.length} skills deployed`);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// βββ Workflows β FLAT deployment (includes commands merged) βββ
|
|
548
|
+
console.log(` π Workflows β ${toolMap.workflowsDir}/`);
|
|
549
|
+
const workflowsSrc = join(TEMPLATES_DIR, 'workflows', tool);
|
|
550
|
+
const workflowsDest = join(projectPath, toolMap.workflowsDir);
|
|
551
|
+
if (existsSync(workflowsSrc)) {
|
|
552
|
+
ensureDir(workflowsDest, cwd);
|
|
553
|
+
const wfFiles = readdirSync(workflowsSrc).filter(f => f.endsWith('.md'));
|
|
554
|
+
for (const file of wfFiles) {
|
|
555
|
+
const content = readFileSync(join(workflowsSrc, file), 'utf8');
|
|
556
|
+
writeFile(join(workflowsDest, file), content, {cwd});
|
|
557
|
+
}
|
|
558
|
+
console.log(` ${wfFiles.length} workflow files deployed`);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// βββ Context directory βββ
|
|
562
|
+
if (toolMap.contextDir) {
|
|
563
|
+
ensureDir(join(projectPath, toolMap.contextDir), cwd);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
568
|
+
// deployClaude β Claude Code: CLAUDE.md + .claude/commands/
|
|
569
|
+
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
570
|
+
|
|
571
|
+
function deployClaude(projectPath, cwd, opts) {
|
|
572
|
+
const { toolMap } = opts;
|
|
573
|
+
|
|
574
|
+
// βββ Commands β workflows adapted for Claude βββ
|
|
575
|
+
console.log(` π Commands β ${toolMap.workflowsDir}/`);
|
|
576
|
+
const cmdSrc = join(TEMPLATES_DIR, 'workflows', 'claude');
|
|
577
|
+
const cmdDest = join(projectPath, toolMap.workflowsDir);
|
|
578
|
+
if (existsSync(cmdSrc)) {
|
|
579
|
+
ensureDir(cmdDest, cwd);
|
|
580
|
+
const cmdFiles = readdirSync(cmdSrc).filter(f => f.endsWith('.md'));
|
|
581
|
+
for (const file of cmdFiles) {
|
|
582
|
+
const content = readFileSync(join(cmdSrc, file), 'utf8');
|
|
583
|
+
writeFile(join(cmdDest, file), content, {cwd});
|
|
584
|
+
}
|
|
585
|
+
console.log(` ${cmdFiles.length} commands deployed`);
|
|
586
|
+
} else {
|
|
587
|
+
console.log(` β οΈ Claude commands not found β run conversion script first`);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// No separate rules, skills, contextDir for Claude (all in CLAUDE.md)
|
|
591
|
+
console.log(` π Rules β gα»p trong CLAUDE.md`);
|
|
592
|
+
console.log(` π― Skills β gα»p trong .claude/commands/`);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
596
|
+
// deployCursor β Cursor: .cursorrules + .cursor/rules/*.mdc
|
|
597
|
+
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
598
|
+
|
|
599
|
+
function deployCursor(projectPath, cwd, opts) {
|
|
600
|
+
const { toolMap } = opts;
|
|
601
|
+
|
|
602
|
+
// βββ Rules β .mdc files βββ
|
|
603
|
+
console.log(` π Rules β ${toolMap.rulesDir}/ (.mdc)`);
|
|
604
|
+
const rulesSrc = join(TEMPLATES_DIR, 'rules', 'cursor');
|
|
605
|
+
const rulesDest = join(projectPath, toolMap.rulesDir);
|
|
606
|
+
if (existsSync(rulesSrc)) {
|
|
607
|
+
ensureDir(rulesDest, cwd);
|
|
608
|
+
const ruleFiles = readdirSync(rulesSrc).filter(f => f.endsWith('.mdc'));
|
|
609
|
+
for (const file of ruleFiles) {
|
|
610
|
+
const content = readFileSync(join(rulesSrc, file), 'utf8');
|
|
611
|
+
writeFile(join(rulesDest, file), content, {cwd});
|
|
612
|
+
}
|
|
613
|
+
console.log(` ${ruleFiles.length} rule files deployed (.mdc)`);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// No skills, workflows, contextDir for Cursor
|
|
617
|
+
console.log(` π― Skills β β Cursor khΓ΄ng hα» trợ native skills`);
|
|
618
|
+
console.log(` π Workflows β β Cursor khΓ΄ng hα» trợ native workflows`);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
622
|
+
// deployCopilot β GitHub Copilot: single file
|
|
623
|
+
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
624
|
+
|
|
625
|
+
function deployCopilot(projectPath, cwd, opts) {
|
|
626
|
+
// Copilot chα» cαΊ§n context file (copilot-instructions.md)
|
|
627
|
+
// Context file sαΊ½ Δược deploy trong deployShared()
|
|
628
|
+
console.log(` π Rules β gα»p trong copilot-instructions.md`);
|
|
629
|
+
console.log(` π― Skills β β Copilot khΓ΄ng hα» trợ`);
|
|
630
|
+
console.log(` π Workflows β β Copilot khΓ΄ng hα» trợ`);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
634
|
+
// deployShared β Context file, Instruct, Getting Started (all tools)
|
|
635
|
+
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
636
|
+
|
|
637
|
+
function deployShared(projectPath, cwd, opts) {
|
|
638
|
+
const { tool, toolMap, name, type, tech, db, allRepos } = opts;
|
|
639
|
+
|
|
640
|
+
// βββ Context file (AGENTS.md / CLAUDE.md / .cursorrules / copilot-instructions.md) βββ
|
|
641
|
+
const ctxTemplateMap = {
|
|
642
|
+
antigravity: 'AGENTS.md.template',
|
|
643
|
+
claude: 'CLAUDE.md.template',
|
|
644
|
+
cursor: 'cursorrules.template',
|
|
645
|
+
copilot: 'copilot-instructions.md.template',
|
|
646
|
+
codex: 'AGENTS.md.template',
|
|
647
|
+
};
|
|
648
|
+
const ctxSrc = join(TEMPLATES_DIR, 'context', ctxTemplateMap[tool] || 'AGENTS.md.template');
|
|
649
|
+
if (existsSync(ctxSrc)) {
|
|
650
|
+
let content = readFileSync(ctxSrc, 'utf8');
|
|
651
|
+
content = content.replaceAll('{{PROJECT_NAME}}', name);
|
|
652
|
+
content = content.replaceAll('{{TECH_STACK}}', TECH_LABELS[tech] || tech);
|
|
653
|
+
content = content.replaceAll('{{DATABASE}}', TECH_LABELS[db] || db);
|
|
654
|
+
content = content.replaceAll('{{DATE}}', new Date().toISOString().split('T')[0]);
|
|
655
|
+
|
|
656
|
+
// Multi-repo aware: find be/fe folders from allRepos
|
|
657
|
+
const beRepo = allRepos?.find(r => r.type === 'backend');
|
|
658
|
+
const feRepo = allRepos?.find(r => r.type === 'frontend');
|
|
659
|
+
content = content.replaceAll('{{BE_FOLDER}}', type === 'backend' ? '.' : (beRepo ? '../' + beRepo.folder : '.'));
|
|
660
|
+
content = content.replaceAll('{{FE_FOLDER}}', type === 'frontend' ? '.' : (feRepo ? '../' + feRepo.folder : '.'));
|
|
661
|
+
|
|
662
|
+
const ctxDest = join(projectPath, toolMap.contextFile);
|
|
663
|
+
if (toolMap.contextFile.includes('/')) {
|
|
664
|
+
mkdirSync(join(projectPath, toolMap.contextFile.split('/').slice(0,-1).join('/')), {recursive:true});
|
|
665
|
+
}
|
|
666
|
+
writeFile(ctxDest, content, {cwd});
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// βββ Instruct-Agent-AI.md βββ
|
|
670
|
+
const instructTemplate = type === 'backend' ? 'Instruct-Agent-AI.be.md' : 'Instruct-Agent-AI.fe.md';
|
|
671
|
+
const instructSrc = join(TEMPLATES_DIR, 'instruct', instructTemplate);
|
|
672
|
+
if (existsSync(instructSrc)) {
|
|
673
|
+
let content = readFileSync(instructSrc, 'utf8');
|
|
674
|
+
content = content.replaceAll('{{PROJECT_NAME}}', name);
|
|
675
|
+
content = content.replaceAll('{{TECH_STACK}}', TECH_LABELS[tech] || tech);
|
|
676
|
+
content = content.replaceAll('{{DATABASE}}', TECH_LABELS[db] || db);
|
|
677
|
+
content = content.replaceAll('{{AGENT_DIR}}', toolMap.agentDir);
|
|
678
|
+
writeFile(join(projectPath, 'Instruct-Agent-AI.md'), content, {cwd});
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// βββ GETTING-STARTED.md (v0.2.1) βββ
|
|
682
|
+
const guideTemplate = join(TEMPLATES_DIR, 'GETTING-STARTED.md');
|
|
683
|
+
if (existsSync(guideTemplate)) {
|
|
684
|
+
let content = readFileSync(guideTemplate, 'utf8');
|
|
685
|
+
content = content.replaceAll('{{PROJECT_NAME}}', name);
|
|
686
|
+
content = content.replaceAll('{{DATE}}', new Date().toISOString().split('T')[0]);
|
|
687
|
+
writeFile(join(projectPath, 'GETTING-STARTED.md'), content, {cwd});
|
|
688
|
+
console.log(` π GETTING-STARTED.md deployed`);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
694
|
+
// Generate v0.1.4 config.yaml (multi-repo array format)
|
|
695
|
+
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
696
|
+
|
|
697
|
+
function genConfig(name, repoConfigs, db, tool, wiki) {
|
|
698
|
+
const reposYaml = repoConfigs.map(r =>
|
|
699
|
+
` - { folder: "${r.folder}", type: "${r.type}", tech: "${r.tech}" }`
|
|
700
|
+
).join('\n');
|
|
701
|
+
|
|
702
|
+
return `# EVNICT-KIT v0.2.1 Config
|
|
703
|
+
project:
|
|
704
|
+
name: "${name}"
|
|
705
|
+
repos:
|
|
706
|
+
${reposYaml}
|
|
707
|
+
database:
|
|
708
|
+
type: "${db}"
|
|
709
|
+
folder: "database"
|
|
710
|
+
wiki:
|
|
711
|
+
enabled: ${wiki}
|
|
712
|
+
folder: "${name}-wiki"
|
|
713
|
+
raw_notes_path: "raw/notes"
|
|
714
|
+
auto_ingest: true
|
|
715
|
+
ai_tool: "${tool}"
|
|
716
|
+
coordination:
|
|
717
|
+
handoff_dir: ".evnict/handoff"
|
|
718
|
+
`;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// Export for use by add.js
|
|
722
|
+
export { deployToProject, createSymlinks, updateGitignore, scanWorkspaceFolders, detectTech };
|