thachvd-kit 1.0.0 → 1.0.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/LICENSE +21 -0
- package/README.md +30 -19
- package/agents/frontend-specialist.md +2 -2
- package/agents/mobile-developer.md +4 -4
- package/bin/cli.js +340 -118
- package/package.json +43 -19
- package/rules/GEMINI.md +5 -3
- package/scripts/init.py +317 -122
- package/skills/desktop-design/SKILL.md +25 -0
- package/skills/react-frontend/SKILL.md +25 -0
- package/.shared/ui-ux-pro-max/data/charts.csv +0 -26
- package/.shared/ui-ux-pro-max/data/colors.csv +0 -97
- package/.shared/ui-ux-pro-max/data/icons.csv +0 -101
- package/.shared/ui-ux-pro-max/data/landing.csv +0 -31
- package/.shared/ui-ux-pro-max/data/products.csv +0 -97
- package/.shared/ui-ux-pro-max/data/prompts.csv +0 -24
- package/.shared/ui-ux-pro-max/data/react-performance.csv +0 -45
- package/.shared/ui-ux-pro-max/data/stacks/flutter.csv +0 -53
- package/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +0 -56
- package/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +0 -53
- package/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +0 -53
- package/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +0 -51
- package/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +0 -59
- package/.shared/ui-ux-pro-max/data/stacks/react-native.csv +0 -52
- package/.shared/ui-ux-pro-max/data/stacks/react.csv +0 -54
- package/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +0 -61
- package/.shared/ui-ux-pro-max/data/stacks/svelte.csv +0 -54
- package/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +0 -51
- package/.shared/ui-ux-pro-max/data/stacks/vue.csv +0 -50
- package/.shared/ui-ux-pro-max/data/styles.csv +0 -59
- package/.shared/ui-ux-pro-max/data/typography.csv +0 -58
- package/.shared/ui-ux-pro-max/data/ui-reasoning.csv +0 -101
- package/.shared/ui-ux-pro-max/data/ux-guidelines.csv +0 -100
- package/.shared/ui-ux-pro-max/data/web-interface.csv +0 -31
- package/.shared/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc +0 -0
- package/.shared/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-313.pyc +0 -0
- package/.shared/ui-ux-pro-max/scripts/core.py +0 -258
- package/.shared/ui-ux-pro-max/scripts/design_system.py +0 -1067
- package/.shared/ui-ux-pro-max/scripts/search.py +0 -106
- package/ARCHITECTURE.md +0 -288
- package/CLAUDE.md +0 -90
- package/mcp_config.json +0 -24
package/bin/cli.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const prompts = require('prompts');
|
|
6
|
-
const pc = require('picocolors');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const prompts = require('prompts');
|
|
6
|
+
const pc = require('picocolors');
|
|
7
7
|
|
|
8
8
|
const sourceDir = path.resolve(__dirname, '..');
|
|
9
9
|
const targetDir = process.cwd();
|
|
@@ -23,12 +23,12 @@ const LANG_SKILLS = {
|
|
|
23
23
|
csharp: ["api-design"],
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
const FRAMEWORK_SKILLS = {
|
|
27
|
-
vue: ["frontend-design", "tailwind-patterns"],
|
|
28
|
-
react: ["
|
|
29
|
-
next: ["nextjs-react-expert", "frontend-design"],
|
|
30
|
-
nuxt: ["frontend-design"],
|
|
31
|
-
svelte: ["frontend-design"],
|
|
26
|
+
const FRAMEWORK_SKILLS = {
|
|
27
|
+
vue: ["frontend-design", "tailwind-patterns"],
|
|
28
|
+
react: ["react-frontend", "frontend-design"],
|
|
29
|
+
next: ["nextjs-react-expert", "frontend-design"],
|
|
30
|
+
nuxt: ["frontend-design"],
|
|
31
|
+
svelte: ["frontend-design"],
|
|
32
32
|
laravel: ["laravel-patterns", "laravel-security", "laravel-tdd"],
|
|
33
33
|
express: ["nodejs-best-practices", "api-design"],
|
|
34
34
|
fastapi: ["python-patterns", "api-design"],
|
|
@@ -36,12 +36,12 @@ const FRAMEWORK_SKILLS = {
|
|
|
36
36
|
gin: ["golang-patterns", "golang-testing"],
|
|
37
37
|
fiber: ["golang-patterns", "golang-testing"],
|
|
38
38
|
nestjs: ["nodejs-best-practices", "api-design"],
|
|
39
|
-
tauri: ["rust-pro", "
|
|
40
|
-
flutter: ["mobile-design"],
|
|
41
|
-
"react-native": ["mobile-design"],
|
|
42
|
-
};
|
|
39
|
+
tauri: ["rust-pro", "desktop-design"],
|
|
40
|
+
flutter: ["mobile-design"],
|
|
41
|
+
"react-native": ["mobile-design"],
|
|
42
|
+
};
|
|
43
43
|
|
|
44
|
-
const INFRA_SKILLS = {
|
|
44
|
+
const INFRA_SKILLS = {
|
|
45
45
|
docker: ["docker-patterns"],
|
|
46
46
|
aws: ["deployment-procedures", "docker-patterns"],
|
|
47
47
|
gcp: ["deployment-procedures", "docker-patterns"],
|
|
@@ -74,8 +74,174 @@ function resolveSkills(data) {
|
|
|
74
74
|
skills.add("clean-code");
|
|
75
75
|
skills.add("systematic-debugging");
|
|
76
76
|
|
|
77
|
-
return Array.from(skills).sort();
|
|
78
|
-
}
|
|
77
|
+
return Array.from(skills).sort();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const FRAMEWORK_CHOICES = [
|
|
81
|
+
'vue', 'react', 'next', 'nuxt', 'svelte', 'angular',
|
|
82
|
+
'laravel', 'express', 'fastapi', 'django', 'gin', 'fiber', 'nestjs',
|
|
83
|
+
'tauri', 'flutter', 'react-native'
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
const LANGUAGE_CHOICES = ['typescript', 'javascript', 'python', 'go', 'php', 'rust', 'java', 'swift', 'kotlin', 'csharp', 'cpp'];
|
|
87
|
+
const PROJECT_TYPE_CHOICES = ['web-app', 'api', 'mobile', 'desktop', 'cli', 'fullstack'];
|
|
88
|
+
const DATABASE_CHOICES = ['postgres', 'mysql', 'sqlite', 'mongodb', 'redis', 'dynamodb', 'none'];
|
|
89
|
+
const PACKAGE_MANAGER_CHOICES = ['npm', 'pnpm', 'yarn', 'bun', 'pip', 'uv', 'go mod', 'cargo', 'composer', 'auto-detect'];
|
|
90
|
+
const TEST_FRAMEWORK_CHOICES = ['jest', 'vitest', 'pytest', 'go test', 'phpunit', 'pest', 'rspec', 'junit', 'auto-detect'];
|
|
91
|
+
const CLOUD_CHOICES = ['aws', 'gcp', 'azure', 'vercel', 'fly', 'railway', 'none'];
|
|
92
|
+
|
|
93
|
+
function fileExists(...parts) {
|
|
94
|
+
return fs.existsSync(path.join(targetDir, ...parts));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function readJsonIfExists(...parts) {
|
|
98
|
+
const fullPath = path.join(targetDir, ...parts);
|
|
99
|
+
if (!fs.existsSync(fullPath)) return null;
|
|
100
|
+
try {
|
|
101
|
+
return JSON.parse(fs.readFileSync(fullPath, 'utf8'));
|
|
102
|
+
} catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function readTextIfExists(...parts) {
|
|
108
|
+
const fullPath = path.join(targetDir, ...parts);
|
|
109
|
+
if (!fs.existsSync(fullPath)) return '';
|
|
110
|
+
try {
|
|
111
|
+
return fs.readFileSync(fullPath, 'utf8');
|
|
112
|
+
} catch {
|
|
113
|
+
return '';
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function detectPackageManager() {
|
|
118
|
+
if (fileExists('pnpm-lock.yaml')) return 'pnpm';
|
|
119
|
+
if (fileExists('yarn.lock')) return 'yarn';
|
|
120
|
+
if (fileExists('bun.lockb') || fileExists('bun.lock')) return 'bun';
|
|
121
|
+
if (fileExists('package-lock.json')) return 'npm';
|
|
122
|
+
if (fileExists('uv.lock')) return 'uv';
|
|
123
|
+
if (fileExists('poetry.lock')) return 'pip';
|
|
124
|
+
if (fileExists('requirements.txt') || fileExists('pyproject.toml')) return 'pip';
|
|
125
|
+
if (fileExists('go.mod')) return 'go mod';
|
|
126
|
+
if (fileExists('Cargo.toml')) return 'cargo';
|
|
127
|
+
if (fileExists('composer.json')) return 'composer';
|
|
128
|
+
return 'auto-detect';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function uniq(values) {
|
|
132
|
+
return Array.from(new Set(values.filter(Boolean)));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function scanProject() {
|
|
136
|
+
const pkg = readJsonIfExists('package.json') || {};
|
|
137
|
+
const deps = {
|
|
138
|
+
...(pkg.dependencies || {}),
|
|
139
|
+
...(pkg.devDependencies || {}),
|
|
140
|
+
...(pkg.peerDependencies || {})
|
|
141
|
+
};
|
|
142
|
+
const depNames = new Set(Object.keys(deps));
|
|
143
|
+
const frameworks = [];
|
|
144
|
+
const infrastructure = [];
|
|
145
|
+
let primaryLanguage = '';
|
|
146
|
+
let projectType = '';
|
|
147
|
+
let database = 'none';
|
|
148
|
+
let testFramework = 'auto-detect';
|
|
149
|
+
let cloud = 'none';
|
|
150
|
+
let ciCd = 'none';
|
|
151
|
+
|
|
152
|
+
const addFramework = (name, condition) => {
|
|
153
|
+
if (condition) frameworks.push(name);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
addFramework('next', depNames.has('next'));
|
|
157
|
+
addFramework('react', depNames.has('react'));
|
|
158
|
+
addFramework('vue', depNames.has('vue'));
|
|
159
|
+
addFramework('nuxt', depNames.has('nuxt'));
|
|
160
|
+
addFramework('svelte', depNames.has('svelte') || depNames.has('@sveltejs/kit'));
|
|
161
|
+
addFramework('angular', depNames.has('@angular/core'));
|
|
162
|
+
addFramework('express', depNames.has('express'));
|
|
163
|
+
addFramework('nestjs', depNames.has('@nestjs/core'));
|
|
164
|
+
addFramework('fastapi', fileExists('requirements.txt') && /fastapi/i.test(readTextIfExists('requirements.txt')));
|
|
165
|
+
addFramework('django', fileExists('requirements.txt') && /django/i.test(readTextIfExists('requirements.txt')));
|
|
166
|
+
addFramework('tauri', depNames.has('@tauri-apps/api') || fileExists('src-tauri', 'tauri.conf.json') || fileExists('src-tauri', 'tauri.conf.json5'));
|
|
167
|
+
addFramework('flutter', fileExists('pubspec.yaml') && /flutter:/i.test(readTextIfExists('pubspec.yaml')));
|
|
168
|
+
addFramework('react-native', depNames.has('react-native') || depNames.has('expo'));
|
|
169
|
+
addFramework('laravel', fileExists('artisan') || depNames.has('laravel'));
|
|
170
|
+
addFramework('gin', fileExists('go.mod') && /\bgithub\.com\/gin-gonic\/gin\b/i.test(readTextIfExists('go.mod')));
|
|
171
|
+
addFramework('fiber', fileExists('go.mod') && /\bgithub\.com\/gofiber\/fiber\b/i.test(readTextIfExists('go.mod')));
|
|
172
|
+
|
|
173
|
+
if (fileExists('tsconfig.json') || depNames.has('typescript')) primaryLanguage = 'typescript';
|
|
174
|
+
else if (fileExists('Cargo.toml') || fileExists('src-tauri', 'Cargo.toml')) primaryLanguage = 'rust';
|
|
175
|
+
else if (fileExists('go.mod')) primaryLanguage = 'go';
|
|
176
|
+
else if (fileExists('requirements.txt') || fileExists('pyproject.toml')) primaryLanguage = 'python';
|
|
177
|
+
else if (fileExists('composer.json')) primaryLanguage = 'php';
|
|
178
|
+
else if (depNames.size > 0 || fileExists('package.json')) primaryLanguage = 'javascript';
|
|
179
|
+
|
|
180
|
+
if (frameworks.includes('tauri')) projectType = 'desktop';
|
|
181
|
+
else if (frameworks.includes('flutter') || frameworks.includes('react-native')) projectType = 'mobile';
|
|
182
|
+
else if (frameworks.some(f => ['express', 'fastapi', 'django', 'laravel', 'gin', 'fiber', 'nestjs'].includes(f))) {
|
|
183
|
+
projectType = frameworks.some(f => ['react', 'next', 'vue', 'nuxt', 'svelte', 'angular'].includes(f)) ? 'fullstack' : 'api';
|
|
184
|
+
} else if (frameworks.some(f => ['react', 'next', 'vue', 'nuxt', 'svelte', 'angular'].includes(f))) {
|
|
185
|
+
projectType = 'web-app';
|
|
186
|
+
} else if (fileExists('package.json') && pkg.bin) {
|
|
187
|
+
projectType = 'cli';
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (depNames.has('pg') || depNames.has('postgres') || depNames.has('@prisma/client') || /postgres/i.test(JSON.stringify(pkg))) database = 'postgres';
|
|
191
|
+
else if (depNames.has('mysql') || depNames.has('mysql2')) database = 'mysql';
|
|
192
|
+
else if (depNames.has('sqlite3') || depNames.has('better-sqlite3') || depNames.has('rusqlite')) database = 'sqlite';
|
|
193
|
+
else if (depNames.has('mongodb') || depNames.has('mongoose')) database = 'mongodb';
|
|
194
|
+
else if (depNames.has('redis') || depNames.has('ioredis')) database = 'redis';
|
|
195
|
+
else if (depNames.has('@aws-sdk/client-dynamodb')) database = 'dynamodb';
|
|
196
|
+
|
|
197
|
+
if (depNames.has('vitest')) testFramework = 'vitest';
|
|
198
|
+
else if (depNames.has('jest')) testFramework = 'jest';
|
|
199
|
+
else if (depNames.has('pytest') || /pytest/i.test(readTextIfExists('requirements.txt'))) testFramework = 'pytest';
|
|
200
|
+
else if (depNames.has('phpunit')) testFramework = 'phpunit';
|
|
201
|
+
else if (depNames.has('pest')) testFramework = 'pest';
|
|
202
|
+
else if (fileExists('go.mod')) testFramework = 'go test';
|
|
203
|
+
|
|
204
|
+
if (fileExists('Dockerfile') || fileExists('docker-compose.yml') || fileExists('docker-compose.yaml')) {
|
|
205
|
+
infrastructure.push('docker');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (fileExists('vercel.json')) {
|
|
209
|
+
cloud = 'vercel';
|
|
210
|
+
infrastructure.push('vercel');
|
|
211
|
+
} else if (fileExists('fly.toml')) {
|
|
212
|
+
cloud = 'fly';
|
|
213
|
+
infrastructure.push('fly');
|
|
214
|
+
} else if (fileExists('railway.json') || fileExists('railway.toml')) {
|
|
215
|
+
cloud = 'railway';
|
|
216
|
+
infrastructure.push('railway');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (fileExists('.github', 'workflows')) ciCd = 'github-actions';
|
|
220
|
+
else if (fileExists('.gitlab-ci.yml')) ciCd = 'gitlab-ci';
|
|
221
|
+
else if (fileExists('.circleci', 'config.yml')) ciCd = 'circleci';
|
|
222
|
+
else if (fileExists('Jenkinsfile')) ciCd = 'jenkins';
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
project_name: path.basename(targetDir),
|
|
226
|
+
description: pkg.description || 'A software project',
|
|
227
|
+
project_type: PROJECT_TYPE_CHOICES.includes(projectType) ? projectType : 'web-app',
|
|
228
|
+
primary_language: LANGUAGE_CHOICES.includes(primaryLanguage) ? primaryLanguage : 'typescript',
|
|
229
|
+
frameworks: uniq(frameworks),
|
|
230
|
+
database: DATABASE_CHOICES.includes(database) ? database : 'none',
|
|
231
|
+
package_manager: PACKAGE_MANAGER_CHOICES.includes(detectPackageManager()) ? detectPackageManager() : 'auto-detect',
|
|
232
|
+
uses_docker: infrastructure.includes('docker'),
|
|
233
|
+
cloud,
|
|
234
|
+
infrastructure: uniq(infrastructure),
|
|
235
|
+
ci_cd: ciCd,
|
|
236
|
+
test_framework: TEST_FRAMEWORK_CHOICES.includes(testFramework) ? testFramework : 'auto-detect',
|
|
237
|
+
max_file_lines: '300',
|
|
238
|
+
code_style: 'project-standard',
|
|
239
|
+
architecture_notes: '<!-- Add notes -->',
|
|
240
|
+
current_phase: 'Initial setup',
|
|
241
|
+
next_phase: '—',
|
|
242
|
+
known_issues: '<!-- None yet -->'
|
|
243
|
+
};
|
|
244
|
+
}
|
|
79
245
|
|
|
80
246
|
function resolveAgentTable(data) {
|
|
81
247
|
const rows = [];
|
|
@@ -202,20 +368,21 @@ ${autoSkillsList}
|
|
|
202
368
|
`;
|
|
203
369
|
}
|
|
204
370
|
|
|
205
|
-
function generateClaudeMd(data) {
|
|
206
|
-
return `# CLAUDE.md — ${data.project_name}
|
|
207
|
-
# Generated by thachvd-kit. See PROJECT_CONTEXT.md for full stack info.
|
|
208
|
-
# Cross-platform: Claude Code · Codex · OpenCode
|
|
371
|
+
function generateClaudeMd(data) {
|
|
372
|
+
return `# CLAUDE.md — ${data.project_name}
|
|
373
|
+
# Generated by thachvd-kit. See PROJECT_CONTEXT.md for full stack info.
|
|
374
|
+
# Cross-platform: Claude Code · Codex · OpenCode
|
|
209
375
|
|
|
210
376
|
## Project
|
|
211
377
|
${data.description}
|
|
212
378
|
Stack: ${data.primary_language} | ${(data.frameworks || []).join(', ')}
|
|
213
379
|
|
|
214
|
-
## Rules
|
|
215
|
-
- Read PROJECT_CONTEXT.md first for stack details and agent routing.
|
|
216
|
-
-
|
|
217
|
-
-
|
|
218
|
-
-
|
|
380
|
+
## Rules
|
|
381
|
+
- Read PROJECT_CONTEXT.md first for stack details and agent routing.
|
|
382
|
+
- If PROJECT_CONTEXT.md is missing or stale, create/update it before major work.
|
|
383
|
+
- Follow skills in .agent/skills/ (or .claude/skills/).
|
|
384
|
+
- Tests are mandatory. Verify before claiming done.
|
|
385
|
+
- Max ${data.max_file_lines || '300'} lines per file.
|
|
219
386
|
|
|
220
387
|
## Key Skills
|
|
221
388
|
- Plan: \`plan-writing\` → \`executing-plans\`
|
|
@@ -235,90 +402,138 @@ See PROJECT_CONTEXT.md for full agent routing table.
|
|
|
235
402
|
|
|
236
403
|
// --- Main CLI ---
|
|
237
404
|
|
|
238
|
-
async function main() {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
405
|
+
async function main() {
|
|
406
|
+
const args = process.argv.slice(2);
|
|
407
|
+
let useScan = args.includes('--scan');
|
|
408
|
+
const assumeYes = args.includes('--yes') || args.includes('-y');
|
|
409
|
+
|
|
410
|
+
console.log(pc.bold(pc.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')));
|
|
411
|
+
console.log(' ' + pc.bold('thachvd-kit') + ' — Project Init Wizard');
|
|
412
|
+
console.log(` Generates .agent/ folder & smart PROJECT_CONTEXT.md${useScan ? ' (scan-assisted)' : ''}`);
|
|
413
|
+
console.log(pc.bold(pc.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')) + '\n');
|
|
414
|
+
|
|
415
|
+
let projectSetup = 'new';
|
|
416
|
+
if (!useScan && !assumeYes) {
|
|
417
|
+
const modeAnswer = await prompts([
|
|
418
|
+
{
|
|
419
|
+
type: 'select',
|
|
420
|
+
name: 'project_setup',
|
|
421
|
+
message: 'Project already exists or are you starting fresh?',
|
|
422
|
+
choices: [
|
|
423
|
+
{ title: 'Existing project', value: 'existing' },
|
|
424
|
+
{ title: 'New project', value: 'new' }
|
|
425
|
+
]
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
type: prev => prev === 'existing' ? 'select' : null,
|
|
429
|
+
name: 'existing_mode',
|
|
430
|
+
message: 'How should thachvd-kit build PROJECT_CONTEXT.md?',
|
|
431
|
+
choices: [
|
|
432
|
+
{ title: 'Scan current project files', value: 'scan' },
|
|
433
|
+
{ title: 'Enter context manually', value: 'manual' }
|
|
434
|
+
],
|
|
435
|
+
initial: 0
|
|
436
|
+
}
|
|
437
|
+
]);
|
|
438
|
+
projectSetup = modeAnswer.project_setup || 'new';
|
|
439
|
+
useScan = modeAnswer.existing_mode === 'scan';
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (useScan) {
|
|
443
|
+
console.log(pc.dim(' Scan mode: detecting stack from current project files...\n'));
|
|
444
|
+
} else if (projectSetup === 'new') {
|
|
445
|
+
console.log(pc.dim(' Manual mode: asking for project context explicitly.\n'));
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (fs.existsSync(path.join(targetDir, 'PROJECT_CONTEXT.md')) && !assumeYes) {
|
|
449
|
+
const res = await prompts({
|
|
450
|
+
type: 'confirm',
|
|
451
|
+
name: 'overwrite',
|
|
248
452
|
message: 'PROJECT_CONTEXT.md already exists in this folder. Overwrite?',
|
|
249
453
|
initial: false
|
|
250
454
|
});
|
|
251
455
|
if (!res.overwrite) {
|
|
252
456
|
console.log(pc.yellow('\nAborted. Existing project context unchanged.'));
|
|
253
457
|
process.exit(0);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
{
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
},
|
|
297
|
-
{
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
302
|
-
]);
|
|
303
|
-
|
|
304
|
-
const { package_manager, test_framework, max_file_lines } = await prompts([
|
|
305
|
-
{
|
|
306
|
-
type: 'select', name: 'package_manager', message: 'Package Manager:',
|
|
307
|
-
choices:
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
{ type: 'text', name: '
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const scanned = useScan ? scanProject() : null;
|
|
462
|
+
|
|
463
|
+
if (useScan) {
|
|
464
|
+
console.log(`${pc.bold(' Scan result:')} ${scanned.primary_language} | ${scanned.frameworks.join(', ') || 'no framework'}`);
|
|
465
|
+
console.log(` ${pc.bold('Detected infra:')} ${scanned.infrastructure.join(', ') || 'none'}\n`);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const { project_name, description, project_type } = await prompts([
|
|
469
|
+
{ type: assumeYes ? null : 'text', name: 'project_name', message: 'Project name:', initial: scanned?.project_name || path.basename(targetDir) },
|
|
470
|
+
{ type: assumeYes ? null : 'text', name: 'description', message: 'Short description:', initial: scanned?.description || 'A software project' },
|
|
471
|
+
{
|
|
472
|
+
type: assumeYes ? null : 'select', name: 'project_type', message: 'Project type:',
|
|
473
|
+
choices: PROJECT_TYPE_CHOICES.map(value => ({ title: value, value })),
|
|
474
|
+
initial: Math.max(PROJECT_TYPE_CHOICES.indexOf(scanned?.project_type || 'web-app'), 0)
|
|
475
|
+
}
|
|
476
|
+
]);
|
|
477
|
+
|
|
478
|
+
const { primary_language } = await prompts({
|
|
479
|
+
type: assumeYes ? null : 'select',
|
|
480
|
+
name: 'primary_language',
|
|
481
|
+
message: 'Primary language:',
|
|
482
|
+
choices: LANGUAGE_CHOICES.map(l => ({ title: l, value: l })),
|
|
483
|
+
initial: Math.max(LANGUAGE_CHOICES.indexOf(scanned?.primary_language || 'typescript'), 0)
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
const { frameworks } = await prompts({
|
|
487
|
+
type: assumeYes ? null : 'multiselect',
|
|
488
|
+
name: 'frameworks',
|
|
489
|
+
message: 'Frameworks (Space to select, Enter to confirm):',
|
|
490
|
+
choices: FRAMEWORK_CHOICES.map(f => ({ title: f, value: f, selected: (scanned?.frameworks || []).includes(f) })),
|
|
491
|
+
min: 0
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
const dbAndInfra = await prompts([
|
|
495
|
+
{
|
|
496
|
+
type: assumeYes ? null : 'select', name: 'database', message: 'Database / ORM:',
|
|
497
|
+
choices: DATABASE_CHOICES.map(d => ({ title: d, value: d })),
|
|
498
|
+
initial: Math.max(DATABASE_CHOICES.indexOf(scanned?.database || 'none'), 0)
|
|
499
|
+
},
|
|
500
|
+
{ type: assumeYes ? null : 'confirm', name: 'uses_docker', message: 'Uses Docker / Docker Compose?', initial: scanned?.uses_docker ?? true },
|
|
501
|
+
{
|
|
502
|
+
type: assumeYes ? null : 'select', name: 'cloud', message: 'Cloud Provider:',
|
|
503
|
+
choices: CLOUD_CHOICES.map(c => ({ title: c, value: c })),
|
|
504
|
+
initial: Math.max(CLOUD_CHOICES.indexOf(scanned?.cloud || 'none'), 0)
|
|
505
|
+
}
|
|
506
|
+
]);
|
|
507
|
+
|
|
508
|
+
const { package_manager, test_framework, max_file_lines } = await prompts([
|
|
509
|
+
{
|
|
510
|
+
type: assumeYes ? null : 'select', name: 'package_manager', message: 'Package Manager:',
|
|
511
|
+
choices: PACKAGE_MANAGER_CHOICES.map(p => ({ title: p, value: p })),
|
|
512
|
+
initial: Math.max(PACKAGE_MANAGER_CHOICES.indexOf(scanned?.package_manager || 'auto-detect'), 0)
|
|
513
|
+
},
|
|
514
|
+
{ type: assumeYes ? null : 'text', name: 'test_framework', message: 'Test Framework (jest, phpunit, etc):', initial: scanned?.test_framework || 'auto-detect' },
|
|
515
|
+
{ type: assumeYes ? null : 'text', name: 'max_file_lines', message: 'Max lines per file:', initial: scanned?.max_file_lines || '300' }
|
|
516
|
+
]);
|
|
517
|
+
|
|
518
|
+
// Combine data
|
|
519
|
+
const data = {
|
|
520
|
+
project_name: project_name || scanned?.project_name || path.basename(targetDir),
|
|
521
|
+
description: description || scanned?.description || 'A software project',
|
|
522
|
+
project_type: project_type || scanned?.project_type || 'web-app',
|
|
523
|
+
primary_language: primary_language || scanned?.primary_language || 'typescript',
|
|
524
|
+
frameworks: frameworks || scanned?.frameworks || [],
|
|
525
|
+
...dbAndInfra, package_manager, test_framework, max_file_lines
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
data.database = data.database || scanned?.database || 'none';
|
|
529
|
+
data.uses_docker = typeof data.uses_docker === 'boolean' ? data.uses_docker : (scanned?.uses_docker ?? true);
|
|
530
|
+
data.cloud = data.cloud || scanned?.cloud || 'none';
|
|
531
|
+
data.package_manager = data.package_manager || scanned?.package_manager || 'auto-detect';
|
|
532
|
+
data.test_framework = data.test_framework || scanned?.test_framework || 'auto-detect';
|
|
533
|
+
data.max_file_lines = data.max_file_lines || scanned?.max_file_lines || '300';
|
|
534
|
+
data.infrastructure = [];
|
|
535
|
+
if (data.uses_docker) data.infrastructure.push('docker');
|
|
536
|
+
if (data.cloud !== 'none') data.infrastructure.push(data.cloud);
|
|
322
537
|
|
|
323
538
|
// --- Copy .agent folder ---
|
|
324
539
|
const agentDir = path.join(targetDir, '.agent');
|
|
@@ -343,11 +558,13 @@ async function main() {
|
|
|
343
558
|
}
|
|
344
559
|
|
|
345
560
|
// Write contexts
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
561
|
+
const projectContextPath = path.join(targetDir, 'PROJECT_CONTEXT.md');
|
|
562
|
+
fs.writeFileSync(projectContextPath, generateProjectContext(data), 'utf8');
|
|
563
|
+
console.log(` ${pc.green('✓')} Generated ${pc.bold('PROJECT_CONTEXT.md')}`);
|
|
564
|
+
|
|
565
|
+
const claudeMdPath = path.join(targetDir, 'CLAUDE.md');
|
|
566
|
+
fs.writeFileSync(claudeMdPath, generateClaudeMd(data), 'utf8');
|
|
567
|
+
console.log(` ${pc.green('✓')} Generated ${pc.bold('CLAUDE.md')}`);
|
|
351
568
|
|
|
352
569
|
// Summary
|
|
353
570
|
const autoSkills = resolveSkills(data);
|
|
@@ -357,15 +574,20 @@ async function main() {
|
|
|
357
574
|
${pc.bold('Stack:')} ${data.primary_language} | ${(data.frameworks || []).join(', ') || 'no framework'}
|
|
358
575
|
${pc.bold('Infra:')} ${(data.infrastructure || []).join(', ') || 'none'}
|
|
359
576
|
|
|
360
|
-
${pc.bold('Auto-resolved skills:')}
|
|
361
|
-
${autoSkills.map(s => ' · ' + s).join('\n')}
|
|
362
|
-
|
|
363
|
-
${pc.bold('
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
577
|
+
${pc.bold('Auto-resolved skills:')}
|
|
578
|
+
${autoSkills.map(s => ' · ' + s).join('\n')}
|
|
579
|
+
|
|
580
|
+
${pc.bold('Generated files:')}
|
|
581
|
+
· ${pc.bold(projectContextPath)}
|
|
582
|
+
· ${pc.bold(claudeMdPath)}
|
|
583
|
+
|
|
584
|
+
${pc.bold('Next steps:')}
|
|
585
|
+
1. Your kit is installed in ${pc.bold('./.agent')}
|
|
586
|
+
2. Review ${pc.bold('PROJECT_CONTEXT.md')} first and add architecture notes
|
|
587
|
+
3. Instruct the agent to read/update ${pc.bold('PROJECT_CONTEXT.md')} before major tasks
|
|
588
|
+
4. Open project in Antigravity or Claude Code — the agent will read context on start
|
|
589
|
+
`);
|
|
590
|
+
}
|
|
369
591
|
|
|
370
592
|
main().catch(err => {
|
|
371
593
|
console.error(pc.red('\nError during init:'), err);
|
package/package.json
CHANGED
|
@@ -1,25 +1,49 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "thachvd-kit",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Universal Agent Kit for Antigravity, Claude Code, and OpenCode",
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"bin": {
|
|
7
|
-
"thachvd-kit": "./bin/cli.js"
|
|
8
|
-
},
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "thachvd-kit",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Universal Agent Kit for Antigravity, Claude Code, and OpenCode",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"thachvd-kit": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"agents",
|
|
11
|
+
"bin",
|
|
12
|
+
"kit",
|
|
13
|
+
"rules",
|
|
14
|
+
"scripts",
|
|
15
|
+
"skills",
|
|
16
|
+
"workflows",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE",
|
|
19
|
+
"PROJECT_CONTEXT.template.md"
|
|
20
|
+
],
|
|
21
|
+
"preferGlobal": true,
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=16.7"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
27
|
+
},
|
|
12
28
|
"keywords": [
|
|
13
29
|
"ai",
|
|
14
30
|
"agent",
|
|
15
31
|
"claude",
|
|
16
32
|
"antigravity",
|
|
17
33
|
"codex"
|
|
18
|
-
],
|
|
19
|
-
"author": "thachvd",
|
|
20
|
-
"license": "MIT",
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
}
|
|
25
|
-
|
|
34
|
+
],
|
|
35
|
+
"author": "thachvd",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git@github_home:holdon1996/thachvd-kit.git"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/holdon1996/thachvd-kit",
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/holdon1996/thachvd-kit/issues"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"picocolors": "^1.0.0",
|
|
47
|
+
"prompts": "^2.4.2"
|
|
48
|
+
}
|
|
49
|
+
}
|
package/rules/GEMINI.md
CHANGED
|
@@ -53,7 +53,7 @@ trigger: always_on
|
|
|
53
53
|
| Web frontend (any framework) | `frontend-specialist` | `frontend-design` + framework skill from PROJECT_CONTEXT |
|
|
54
54
|
| Backend API (any language) | `backend-specialist` | language skill + `api-design` |
|
|
55
55
|
| Database / Schema | `database-architect` | `database-design` |
|
|
56
|
-
| Mobile / Desktop (Tauri, RN, Flutter) | `mobile-developer` |
|
|
56
|
+
| Mobile / Desktop (Tauri, RN, Flutter) | `mobile-developer` | platform skill from PROJECT_CONTEXT |
|
|
57
57
|
| DevOps / Cloud / CI | `devops-engineer` | `docker-patterns`, `deployment-procedures` |
|
|
58
58
|
| Security audit | `security-auditor` | `vulnerability-scanner` |
|
|
59
59
|
| Debug / RCA | `debugger` | `systematic-debugging` |
|
|
@@ -65,12 +65,14 @@ trigger: always_on
|
|
|
65
65
|
| Framework/Language | Load Skill |
|
|
66
66
|
|-------------------|-----------|
|
|
67
67
|
| Vue | `frontend-design` (+ Vue-specific if available) |
|
|
68
|
-
| React
|
|
68
|
+
| React | `react-frontend` |
|
|
69
|
+
| Next.js | `nextjs-react-expert` |
|
|
69
70
|
| Laravel | `laravel-patterns` + `laravel-security` |
|
|
70
71
|
| Node.js | `nodejs-best-practices` |
|
|
71
72
|
| Python | `python-patterns` |
|
|
72
73
|
| Go | `golang-patterns` |
|
|
73
|
-
| Rust
|
|
74
|
+
| Rust | `rust-pro` |
|
|
75
|
+
| Tauri | `desktop-design` + `rust-pro` |
|
|
74
76
|
| Docker | `docker-patterns` |
|
|
75
77
|
|
|
76
78
|
> If stack not in mapping → use agent's general skill set + `clean-code`.
|