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.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -19
  3. package/agents/frontend-specialist.md +2 -2
  4. package/agents/mobile-developer.md +4 -4
  5. package/bin/cli.js +340 -118
  6. package/package.json +43 -19
  7. package/rules/GEMINI.md +5 -3
  8. package/scripts/init.py +317 -122
  9. package/skills/desktop-design/SKILL.md +25 -0
  10. package/skills/react-frontend/SKILL.md +25 -0
  11. package/.shared/ui-ux-pro-max/data/charts.csv +0 -26
  12. package/.shared/ui-ux-pro-max/data/colors.csv +0 -97
  13. package/.shared/ui-ux-pro-max/data/icons.csv +0 -101
  14. package/.shared/ui-ux-pro-max/data/landing.csv +0 -31
  15. package/.shared/ui-ux-pro-max/data/products.csv +0 -97
  16. package/.shared/ui-ux-pro-max/data/prompts.csv +0 -24
  17. package/.shared/ui-ux-pro-max/data/react-performance.csv +0 -45
  18. package/.shared/ui-ux-pro-max/data/stacks/flutter.csv +0 -53
  19. package/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +0 -56
  20. package/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +0 -53
  21. package/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +0 -53
  22. package/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +0 -51
  23. package/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +0 -59
  24. package/.shared/ui-ux-pro-max/data/stacks/react-native.csv +0 -52
  25. package/.shared/ui-ux-pro-max/data/stacks/react.csv +0 -54
  26. package/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +0 -61
  27. package/.shared/ui-ux-pro-max/data/stacks/svelte.csv +0 -54
  28. package/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +0 -51
  29. package/.shared/ui-ux-pro-max/data/stacks/vue.csv +0 -50
  30. package/.shared/ui-ux-pro-max/data/styles.csv +0 -59
  31. package/.shared/ui-ux-pro-max/data/typography.csv +0 -58
  32. package/.shared/ui-ux-pro-max/data/ui-reasoning.csv +0 -101
  33. package/.shared/ui-ux-pro-max/data/ux-guidelines.csv +0 -100
  34. package/.shared/ui-ux-pro-max/data/web-interface.csv +0 -31
  35. package/.shared/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc +0 -0
  36. package/.shared/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-313.pyc +0 -0
  37. package/.shared/ui-ux-pro-max/scripts/core.py +0 -258
  38. package/.shared/ui-ux-pro-max/scripts/design_system.py +0 -1067
  39. package/.shared/ui-ux-pro-max/scripts/search.py +0 -106
  40. package/ARCHITECTURE.md +0 -288
  41. package/CLAUDE.md +0 -90
  42. 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: ["nextjs-react-expert", "frontend-design"],
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", "mobile-design"],
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
- - Follow skills in .agent/skills/ (or .claude/skills/).
217
- - Tests are mandatory. Verify before claiming done.
218
- - Max ${data.max_file_lines || '300'} lines per file.
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
- console.log(pc.bold(pc.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')));
240
- console.log(' ' + pc.bold('thachvd-kit') + ' — Project Init Wizard');
241
- console.log(' Generates .agent/ folder & smart PROJECT_CONTEXT.md');
242
- console.log(pc.bold(pc.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')) + '\n');
243
-
244
- if (fs.existsSync(path.join(targetDir, 'PROJECT_CONTEXT.md'))) {
245
- const res = await prompts({
246
- type: 'confirm',
247
- name: 'overwrite',
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 { project_name, description, project_type } = await prompts([
258
- { type: 'text', name: 'project_name', message: 'Project name:', initial: path.basename(targetDir) },
259
- { type: 'text', name: 'description', message: 'Short description:', initial: 'A software project' },
260
- {
261
- type: 'select', name: 'project_type', message: 'Project type:',
262
- choices: [
263
- { title: 'Web App', value: 'web-app' },
264
- { title: 'API', value: 'api' },
265
- { title: 'Mobile', value: 'mobile' },
266
- { title: 'Desktop', value: 'desktop' },
267
- { title: 'CLI', value: 'cli' },
268
- { title: 'Fullstack', value: 'fullstack' },
269
- ]
270
- }
271
- ]);
272
-
273
- const { primary_language } = await prompts({
274
- type: 'select',
275
- name: 'primary_language',
276
- message: 'Primary language:',
277
- choices: ['typescript', 'javascript', 'python', 'go', 'php', 'rust', 'java', 'swift', 'kotlin', 'csharp', 'cpp'].map(l => ({ title: l, value: l }))
278
- });
279
-
280
- const { frameworks } = await prompts({
281
- type: 'multiselect',
282
- name: 'frameworks',
283
- message: 'Frameworks (Space to select, Enter to confirm):',
284
- choices: [
285
- 'vue', 'react', 'next', 'nuxt', 'svelte', 'angular',
286
- 'laravel', 'express', 'fastapi', 'django', 'gin', 'fiber', 'nestjs',
287
- 'tauri', 'flutter', 'react-native'
288
- ].map(f => ({ title: f, value: f })),
289
- min: 0
290
- });
291
-
292
- const dbAndInfra = await prompts([
293
- {
294
- type: 'select', name: 'database', message: 'Database / ORM:',
295
- choices: ['postgres', 'mysql', 'sqlite', 'mongodb', 'redis', 'dynamodb', 'none'].map(d => ({ title: d, value: d }))
296
- },
297
- { type: 'confirm', name: 'uses_docker', message: 'Uses Docker / Docker Compose?', initial: true },
298
- {
299
- type: 'select', name: 'cloud', message: 'Cloud Provider:',
300
- choices: ['aws', 'gcp', 'azure', 'vercel', 'fly', 'railway', 'none'].map(c => ({ title: c, value: c }))
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: ['npm', 'pnpm', 'yarn', 'bun', 'pip', 'uv', 'go mod', 'cargo', 'composer', 'auto-detect'].map(p => ({ title: p, value: p }))
308
- },
309
- { type: 'text', name: 'test_framework', message: 'Test Framework (jest, phpunit, etc):', initial: 'auto-detect' },
310
- { type: 'text', name: 'max_file_lines', message: 'Max lines per file:', initial: '300' }
311
- ]);
312
-
313
- // Combine data
314
- const data = {
315
- project_name, description, project_type, primary_language, frameworks,
316
- ...dbAndInfra, package_manager, test_framework, max_file_lines
317
- };
318
-
319
- data.infrastructure = [];
320
- if (data.uses_docker) data.infrastructure.push('docker');
321
- if (data.cloud !== 'none') data.infrastructure.push(data.cloud);
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
- fs.writeFileSync(path.join(targetDir, 'PROJECT_CONTEXT.md'), generateProjectContext(data), 'utf8');
347
- console.log(` ${pc.green('✓')} Generated ${pc.bold('PROJECT_CONTEXT.md')}`);
348
-
349
- fs.writeFileSync(path.join(targetDir, 'CLAUDE.md'), generateClaudeMd(data), 'utf8');
350
- console.log(` ${pc.green('✓')} Generated ${pc.bold('CLAUDE.md')}`);
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('Next steps:')}
364
- 1. Your kit is installed in ${pc.bold('./.agent')}
365
- 2. Review ${pc.bold('PROJECT_CONTEXT.md')} and add architecture notes
366
- 3. Open project in Antigravity or Claude Code — the agent will read context on start!
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.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
- "scripts": {
10
- "test": "echo \"Error: no test specified\" && exit 1"
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
- "dependencies": {
22
- "picocolors": "^1.0.0",
23
- "prompts": "^2.4.2"
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` | `mobile-design` |
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 / Next.js | `nextjs-react-expert` |
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 / Tauri | `rust-pro` |
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`.