thachvd-kit 1.0.2 → 1.0.4
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 +1 -0
- package/bin/cli.js +533 -96
- package/package.json +5 -2
- package/scripts/init.py +313 -101
package/bin/cli.js
CHANGED
|
@@ -4,24 +4,26 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const prompts = require('prompts');
|
|
6
6
|
const pc = require('picocolors');
|
|
7
|
+
const packageJson = require('../package.json');
|
|
7
8
|
|
|
8
9
|
const sourceDir = path.resolve(__dirname, '..');
|
|
9
10
|
const targetDir = process.cwd();
|
|
10
11
|
|
|
11
12
|
// --- Skill Resolver ---
|
|
12
13
|
|
|
13
|
-
const LANG_SKILLS = {
|
|
14
|
-
javascript: ["nodejs-best-practices", "api-design"],
|
|
15
|
-
typescript: ["nodejs-best-practices", "api-design"],
|
|
16
|
-
python: ["python-patterns"],
|
|
17
|
-
go: ["golang-patterns", "golang-testing"],
|
|
18
|
-
php: ["
|
|
19
|
-
rust: ["rust-pro"],
|
|
20
|
-
java: ["api-design"],
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
14
|
+
const LANG_SKILLS = {
|
|
15
|
+
javascript: ["nodejs-best-practices", "api-design"],
|
|
16
|
+
typescript: ["nodejs-best-practices", "api-design"],
|
|
17
|
+
python: ["python-patterns"],
|
|
18
|
+
go: ["golang-patterns", "golang-testing"],
|
|
19
|
+
php: ["api-design"],
|
|
20
|
+
rust: ["rust-pro"],
|
|
21
|
+
java: ["api-design"],
|
|
22
|
+
ruby: ["api-design"],
|
|
23
|
+
swift: ["mobile-design"],
|
|
24
|
+
kotlin: ["mobile-design"],
|
|
25
|
+
csharp: ["api-design"],
|
|
26
|
+
};
|
|
25
27
|
|
|
26
28
|
const FRAMEWORK_SKILLS = {
|
|
27
29
|
vue: ["frontend-design", "tailwind-patterns"],
|
|
@@ -29,14 +31,25 @@ const FRAMEWORK_SKILLS = {
|
|
|
29
31
|
next: ["nextjs-react-expert", "frontend-design"],
|
|
30
32
|
nuxt: ["frontend-design"],
|
|
31
33
|
svelte: ["frontend-design"],
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
angular: ["frontend-design"],
|
|
35
|
+
laravel: ["laravel-patterns", "laravel-security", "laravel-tdd"],
|
|
36
|
+
cakephp: ["api-design"],
|
|
37
|
+
symfony: ["api-design"],
|
|
38
|
+
codeigniter: ["api-design"],
|
|
39
|
+
express: ["nodejs-best-practices", "api-design"],
|
|
40
|
+
fastapi: ["python-patterns", "api-design"],
|
|
41
|
+
django: ["python-patterns", "api-design"],
|
|
42
|
+
flask: ["python-patterns", "api-design"],
|
|
43
|
+
gin: ["golang-patterns", "golang-testing"],
|
|
44
|
+
fiber: ["golang-patterns", "golang-testing"],
|
|
45
|
+
echo: ["golang-patterns", "golang-testing"],
|
|
46
|
+
nestjs: ["nodejs-best-practices", "api-design"],
|
|
47
|
+
rails: ["api-design"],
|
|
48
|
+
spring: ["api-design"],
|
|
49
|
+
aspnet: ["api-design"],
|
|
39
50
|
tauri: ["rust-pro", "desktop-design"],
|
|
51
|
+
axum: ["rust-pro", "api-design"],
|
|
52
|
+
actix: ["rust-pro", "api-design"],
|
|
40
53
|
flutter: ["mobile-design"],
|
|
41
54
|
"react-native": ["mobile-design"],
|
|
42
55
|
};
|
|
@@ -79,21 +92,27 @@ function resolveSkills(data) {
|
|
|
79
92
|
|
|
80
93
|
const FRAMEWORK_CHOICES = [
|
|
81
94
|
'vue', 'react', 'next', 'nuxt', 'svelte', 'angular',
|
|
82
|
-
'laravel', '
|
|
83
|
-
'
|
|
95
|
+
'laravel', 'cakephp', 'symfony', 'codeigniter',
|
|
96
|
+
'express', 'fastapi', 'django', 'flask', 'gin', 'fiber', 'echo', 'nestjs',
|
|
97
|
+
'rails', 'spring', 'aspnet',
|
|
98
|
+
'tauri', 'axum', 'actix', 'flutter', 'react-native'
|
|
84
99
|
];
|
|
85
100
|
|
|
86
|
-
const LANGUAGE_CHOICES = ['typescript', 'javascript', 'python', 'go', 'php', 'rust', 'java', 'swift', 'kotlin', 'csharp', 'cpp'];
|
|
101
|
+
const LANGUAGE_CHOICES = ['typescript', 'javascript', 'python', 'go', 'php', 'rust', 'java', 'ruby', 'swift', 'kotlin', 'csharp', 'cpp'];
|
|
87
102
|
const PROJECT_TYPE_CHOICES = ['web-app', 'api', 'mobile', 'desktop', 'cli', 'fullstack'];
|
|
88
103
|
const DATABASE_CHOICES = ['postgres', 'mysql', 'sqlite', 'mongodb', 'redis', 'dynamodb', 'none'];
|
|
89
104
|
const PACKAGE_MANAGER_CHOICES = ['npm', 'pnpm', 'yarn', 'bun', 'pip', 'uv', 'go mod', 'cargo', 'composer', 'auto-detect'];
|
|
90
105
|
const TEST_FRAMEWORK_CHOICES = ['jest', 'vitest', 'pytest', 'go test', 'phpunit', 'pest', 'rspec', 'junit', 'auto-detect'];
|
|
91
106
|
const CLOUD_CHOICES = ['aws', 'gcp', 'azure', 'vercel', 'fly', 'railway', 'none'];
|
|
107
|
+
const FRONTEND_FRAMEWORKS = ['vue', 'react', 'next', 'nuxt', 'svelte', 'angular'];
|
|
108
|
+
const BACKEND_FRAMEWORKS = ['laravel', 'cakephp', 'symfony', 'codeigniter', 'express', 'fastapi', 'django', 'flask', 'gin', 'fiber', 'echo', 'nestjs', 'rails', 'spring', 'aspnet', 'axum', 'actix'];
|
|
109
|
+
const MOBILE_DESKTOP_FRAMEWORKS = ['tauri', 'flutter', 'react-native'];
|
|
92
110
|
|
|
93
111
|
function printHelp() {
|
|
94
112
|
console.log(`
|
|
95
113
|
${pc.bold('Usage:')}
|
|
96
114
|
thachvd-kit init [--scan] [--yes]
|
|
115
|
+
thachvd-kit --version
|
|
97
116
|
thachvd-kit --help
|
|
98
117
|
|
|
99
118
|
${pc.bold('Commands:')}
|
|
@@ -102,10 +121,20 @@ ${pc.bold('Commands:')}
|
|
|
102
121
|
${pc.bold('Options:')}
|
|
103
122
|
--scan Scan the current project to prefill PROJECT_CONTEXT.md
|
|
104
123
|
--yes Use detected/default values without prompting
|
|
124
|
+
--version Show the installed CLI version
|
|
105
125
|
--help Show this help message
|
|
106
126
|
`);
|
|
107
127
|
}
|
|
108
128
|
|
|
129
|
+
async function askQuestions(questions) {
|
|
130
|
+
return prompts(questions, {
|
|
131
|
+
onCancel: () => {
|
|
132
|
+
console.log(pc.yellow('\nCancelled.'));
|
|
133
|
+
process.exit(130);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
109
138
|
function fileExists(...parts) {
|
|
110
139
|
return fs.existsSync(path.join(targetDir, ...parts));
|
|
111
140
|
}
|
|
@@ -148,74 +177,448 @@ function uniq(values) {
|
|
|
148
177
|
return Array.from(new Set(values.filter(Boolean)));
|
|
149
178
|
}
|
|
150
179
|
|
|
180
|
+
function evidenceIf(condition, label, weight = 1) {
|
|
181
|
+
return condition ? { label, weight } : null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function buildDetection(id, evidence, meta = {}) {
|
|
185
|
+
const hits = evidence.filter(Boolean);
|
|
186
|
+
const score = hits.reduce((sum, hit) => sum + hit.weight, 0);
|
|
187
|
+
return {
|
|
188
|
+
id,
|
|
189
|
+
score,
|
|
190
|
+
evidence: hits.map(hit => hit.label),
|
|
191
|
+
...meta
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function confidenceLabel(score) {
|
|
196
|
+
if (score >= 6) return 'high';
|
|
197
|
+
if (score >= 3) return 'medium';
|
|
198
|
+
return 'low';
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function hasDependency(depNames, ...names) {
|
|
202
|
+
return names.some(name => depNames.has(name));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function hasComposerDependency(composerDepNames, ...names) {
|
|
206
|
+
return names.some(name => composerDepNames.has(name));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function hasRootFile(rootEntries, ...filenames) {
|
|
210
|
+
return filenames.some(name => rootEntries.has(name));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function textMatches(text, pattern) {
|
|
214
|
+
return Boolean(text && pattern.test(text));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const LANGUAGE_DETECTORS = [
|
|
218
|
+
{
|
|
219
|
+
id: 'typescript',
|
|
220
|
+
detect: ctx => buildDetection('typescript', [
|
|
221
|
+
evidenceIf(ctx.fileExists('tsconfig.json'), 'found tsconfig.json', 4),
|
|
222
|
+
evidenceIf(hasDependency(ctx.depNames, 'typescript', 'ts-node', 'tsx'), 'package.json -> typescript toolchain', 2)
|
|
223
|
+
])
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
id: 'javascript',
|
|
227
|
+
detect: ctx => buildDetection('javascript', [
|
|
228
|
+
evidenceIf(ctx.fileExists('package.json'), 'found package.json', 2),
|
|
229
|
+
evidenceIf(ctx.depNames.size > 0, 'package.json -> JavaScript dependency graph', 1)
|
|
230
|
+
])
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
id: 'python',
|
|
234
|
+
detect: ctx => buildDetection('python', [
|
|
235
|
+
evidenceIf(ctx.fileExists('requirements.txt'), 'found requirements.txt', 3),
|
|
236
|
+
evidenceIf(ctx.fileExists('pyproject.toml'), 'found pyproject.toml', 2),
|
|
237
|
+
evidenceIf(textMatches(ctx.pyprojectText, /\[tool\.(poetry|uv|hatch|pdm)\]/i), 'pyproject.toml -> Python tool section', 2)
|
|
238
|
+
])
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
id: 'go',
|
|
242
|
+
detect: ctx => buildDetection('go', [
|
|
243
|
+
evidenceIf(ctx.fileExists('go.mod'), 'found go.mod', 4)
|
|
244
|
+
])
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
id: 'php',
|
|
248
|
+
detect: ctx => buildDetection('php', [
|
|
249
|
+
evidenceIf(ctx.fileExists('composer.json'), 'found composer.json', 4),
|
|
250
|
+
evidenceIf(ctx.composerDepNames.size > 0, 'composer.json -> PHP dependency graph', 1)
|
|
251
|
+
])
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
id: 'rust',
|
|
255
|
+
detect: ctx => buildDetection('rust', [
|
|
256
|
+
evidenceIf(ctx.fileExists('Cargo.toml'), 'found Cargo.toml', 4),
|
|
257
|
+
evidenceIf(ctx.fileExists('src-tauri', 'Cargo.toml'), 'found src-tauri/Cargo.toml', 5)
|
|
258
|
+
])
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
id: 'java',
|
|
262
|
+
detect: ctx => buildDetection('java', [
|
|
263
|
+
evidenceIf(ctx.fileExists('pom.xml'), 'found pom.xml', 4),
|
|
264
|
+
evidenceIf(ctx.fileExists('build.gradle') || ctx.fileExists('build.gradle.kts'), 'found Gradle build file', 3)
|
|
265
|
+
])
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
id: 'ruby',
|
|
269
|
+
detect: ctx => buildDetection('ruby', [
|
|
270
|
+
evidenceIf(ctx.fileExists('Gemfile'), 'found Gemfile', 4),
|
|
271
|
+
evidenceIf(ctx.fileExists('config.ru'), 'found config.ru', 1)
|
|
272
|
+
])
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
id: 'csharp',
|
|
276
|
+
detect: ctx => buildDetection('csharp', [
|
|
277
|
+
evidenceIf(ctx.rootEntries.some(name => name.endsWith('.sln') || name.endsWith('.csproj')), 'found .sln/.csproj', 4),
|
|
278
|
+
evidenceIf(ctx.fileExists('Program.cs'), 'found Program.cs', 1)
|
|
279
|
+
])
|
|
280
|
+
}
|
|
281
|
+
];
|
|
282
|
+
|
|
283
|
+
const FRAMEWORK_DETECTORS = [
|
|
284
|
+
{
|
|
285
|
+
id: 'next',
|
|
286
|
+
kind: 'frontend',
|
|
287
|
+
projectType: 'web-app',
|
|
288
|
+
detect: ctx => buildDetection('next', [
|
|
289
|
+
evidenceIf(hasDependency(ctx.depNames, 'next'), 'package.json -> next', 4),
|
|
290
|
+
evidenceIf(ctx.fileExists('next.config.js') || ctx.fileExists('next.config.mjs') || ctx.fileExists('next.config.ts'), 'found next.config.*', 3),
|
|
291
|
+
evidenceIf(ctx.fileExists('app') || ctx.fileExists('pages'), 'found app/ or pages/', 1)
|
|
292
|
+
])
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
id: 'react',
|
|
296
|
+
kind: 'frontend',
|
|
297
|
+
projectType: 'web-app',
|
|
298
|
+
detect: ctx => buildDetection('react', [
|
|
299
|
+
evidenceIf(hasDependency(ctx.depNames, 'react'), 'package.json -> react', 4),
|
|
300
|
+
evidenceIf(hasDependency(ctx.depNames, 'react-dom'), 'package.json -> react-dom', 2)
|
|
301
|
+
])
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
id: 'vue',
|
|
305
|
+
kind: 'frontend',
|
|
306
|
+
projectType: 'web-app',
|
|
307
|
+
detect: ctx => buildDetection('vue', [
|
|
308
|
+
evidenceIf(hasDependency(ctx.depNames, 'vue'), 'package.json -> vue', 4)
|
|
309
|
+
])
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
id: 'nuxt',
|
|
313
|
+
kind: 'frontend',
|
|
314
|
+
projectType: 'web-app',
|
|
315
|
+
detect: ctx => buildDetection('nuxt', [
|
|
316
|
+
evidenceIf(hasDependency(ctx.depNames, 'nuxt'), 'package.json -> nuxt', 4),
|
|
317
|
+
evidenceIf(ctx.fileExists('nuxt.config.ts') || ctx.fileExists('nuxt.config.js'), 'found nuxt.config.*', 3)
|
|
318
|
+
])
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
id: 'svelte',
|
|
322
|
+
kind: 'frontend',
|
|
323
|
+
projectType: 'web-app',
|
|
324
|
+
detect: ctx => buildDetection('svelte', [
|
|
325
|
+
evidenceIf(hasDependency(ctx.depNames, 'svelte', '@sveltejs/kit'), 'package.json -> svelte', 4)
|
|
326
|
+
])
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
id: 'angular',
|
|
330
|
+
kind: 'frontend',
|
|
331
|
+
projectType: 'web-app',
|
|
332
|
+
detect: ctx => buildDetection('angular', [
|
|
333
|
+
evidenceIf(hasDependency(ctx.depNames, '@angular/core'), 'package.json -> @angular/core', 4),
|
|
334
|
+
evidenceIf(ctx.fileExists('angular.json'), 'found angular.json', 3)
|
|
335
|
+
])
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
id: 'express',
|
|
339
|
+
kind: 'backend',
|
|
340
|
+
language: 'javascript',
|
|
341
|
+
projectType: 'api',
|
|
342
|
+
detect: ctx => buildDetection('express', [
|
|
343
|
+
evidenceIf(hasDependency(ctx.depNames, 'express'), 'package.json -> express', 4)
|
|
344
|
+
])
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
id: 'nestjs',
|
|
348
|
+
kind: 'backend',
|
|
349
|
+
language: 'javascript',
|
|
350
|
+
projectType: 'api',
|
|
351
|
+
detect: ctx => buildDetection('nestjs', [
|
|
352
|
+
evidenceIf(hasDependency(ctx.depNames, '@nestjs/core'), 'package.json -> @nestjs/core', 4)
|
|
353
|
+
])
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
id: 'fastapi',
|
|
357
|
+
kind: 'backend',
|
|
358
|
+
language: 'python',
|
|
359
|
+
projectType: 'api',
|
|
360
|
+
detect: ctx => buildDetection('fastapi', [
|
|
361
|
+
evidenceIf(textMatches(ctx.requirementsText, /\bfastapi\b/i) || textMatches(ctx.pyprojectText, /\bfastapi\b/i), 'Python deps -> fastapi', 4)
|
|
362
|
+
])
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
id: 'django',
|
|
366
|
+
kind: 'backend',
|
|
367
|
+
language: 'python',
|
|
368
|
+
projectType: 'api',
|
|
369
|
+
detect: ctx => buildDetection('django', [
|
|
370
|
+
evidenceIf(textMatches(ctx.requirementsText, /\bdjango\b/i) || textMatches(ctx.pyprojectText, /\bdjango\b/i), 'Python deps -> django', 4),
|
|
371
|
+
evidenceIf(ctx.fileExists('manage.py'), 'found manage.py', 2)
|
|
372
|
+
])
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
id: 'flask',
|
|
376
|
+
kind: 'backend',
|
|
377
|
+
language: 'python',
|
|
378
|
+
projectType: 'api',
|
|
379
|
+
detect: ctx => buildDetection('flask', [
|
|
380
|
+
evidenceIf(textMatches(ctx.requirementsText, /\bflask\b/i) || textMatches(ctx.pyprojectText, /\bflask\b/i), 'Python deps -> flask', 4)
|
|
381
|
+
])
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
id: 'laravel',
|
|
385
|
+
kind: 'backend',
|
|
386
|
+
language: 'php',
|
|
387
|
+
projectType: 'api',
|
|
388
|
+
detect: ctx => buildDetection('laravel', [
|
|
389
|
+
evidenceIf(hasComposerDependency(ctx.composerDepNames, 'laravel/framework'), 'composer.json -> laravel/framework', 4),
|
|
390
|
+
evidenceIf(ctx.fileExists('artisan'), 'found artisan', 3)
|
|
391
|
+
])
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
id: 'cakephp',
|
|
395
|
+
kind: 'backend',
|
|
396
|
+
language: 'php',
|
|
397
|
+
projectType: 'api',
|
|
398
|
+
detect: ctx => buildDetection('cakephp', [
|
|
399
|
+
evidenceIf(hasComposerDependency(ctx.composerDepNames, 'cakephp/cakephp'), 'composer.json -> cakephp/cakephp', 4),
|
|
400
|
+
evidenceIf(ctx.fileExists('bin', 'cake'), 'found bin/cake', 3),
|
|
401
|
+
evidenceIf(ctx.fileExists('config', 'app.php'), 'found config/app.php', 1)
|
|
402
|
+
])
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
id: 'symfony',
|
|
406
|
+
kind: 'backend',
|
|
407
|
+
language: 'php',
|
|
408
|
+
projectType: 'api',
|
|
409
|
+
detect: ctx => buildDetection('symfony', [
|
|
410
|
+
evidenceIf(hasComposerDependency(ctx.composerDepNames, 'symfony/framework-bundle'), 'composer.json -> symfony/framework-bundle', 4),
|
|
411
|
+
evidenceIf(ctx.fileExists('bin', 'console'), 'found bin/console', 2),
|
|
412
|
+
evidenceIf(ctx.fileExists('config', 'bundles.php'), 'found config/bundles.php', 1)
|
|
413
|
+
])
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
id: 'codeigniter',
|
|
417
|
+
kind: 'backend',
|
|
418
|
+
language: 'php',
|
|
419
|
+
projectType: 'api',
|
|
420
|
+
detect: ctx => buildDetection('codeigniter', [
|
|
421
|
+
evidenceIf(hasComposerDependency(ctx.composerDepNames, 'codeigniter4/framework'), 'composer.json -> codeigniter4/framework', 4),
|
|
422
|
+
evidenceIf(ctx.fileExists('spark'), 'found spark', 2),
|
|
423
|
+
evidenceIf(ctx.fileExists('app', 'Config', 'App.php'), 'found app/Config/App.php', 1)
|
|
424
|
+
])
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
id: 'gin',
|
|
428
|
+
kind: 'backend',
|
|
429
|
+
language: 'go',
|
|
430
|
+
projectType: 'api',
|
|
431
|
+
detect: ctx => buildDetection('gin', [
|
|
432
|
+
evidenceIf(textMatches(ctx.goModText, /\bgithub\.com\/gin-gonic\/gin\b/i), 'go.mod -> github.com/gin-gonic/gin', 4)
|
|
433
|
+
])
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
id: 'fiber',
|
|
437
|
+
kind: 'backend',
|
|
438
|
+
language: 'go',
|
|
439
|
+
projectType: 'api',
|
|
440
|
+
detect: ctx => buildDetection('fiber', [
|
|
441
|
+
evidenceIf(textMatches(ctx.goModText, /\bgithub\.com\/gofiber\/fiber\b/i), 'go.mod -> github.com/gofiber/fiber', 4)
|
|
442
|
+
])
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
id: 'echo',
|
|
446
|
+
kind: 'backend',
|
|
447
|
+
language: 'go',
|
|
448
|
+
projectType: 'api',
|
|
449
|
+
detect: ctx => buildDetection('echo', [
|
|
450
|
+
evidenceIf(textMatches(ctx.goModText, /\bgithub\.com\/labstack\/echo\b/i), 'go.mod -> github.com/labstack/echo', 4)
|
|
451
|
+
])
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
id: 'rails',
|
|
455
|
+
kind: 'backend',
|
|
456
|
+
language: 'ruby',
|
|
457
|
+
projectType: 'api',
|
|
458
|
+
detect: ctx => buildDetection('rails', [
|
|
459
|
+
evidenceIf(textMatches(ctx.gemfileText, /^\s*gem\s+["']rails["']/im), 'Gemfile -> rails', 4),
|
|
460
|
+
evidenceIf(ctx.fileExists('bin', 'rails'), 'found bin/rails', 2)
|
|
461
|
+
])
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
id: 'spring',
|
|
465
|
+
kind: 'backend',
|
|
466
|
+
language: 'java',
|
|
467
|
+
projectType: 'api',
|
|
468
|
+
detect: ctx => buildDetection('spring', [
|
|
469
|
+
evidenceIf(textMatches(ctx.pomText, /spring-boot/i) || textMatches(ctx.gradleText, /spring-boot/i), 'Java build -> spring-boot', 4)
|
|
470
|
+
])
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
id: 'aspnet',
|
|
474
|
+
kind: 'backend',
|
|
475
|
+
language: 'csharp',
|
|
476
|
+
projectType: 'api',
|
|
477
|
+
detect: ctx => buildDetection('aspnet', [
|
|
478
|
+
evidenceIf(textMatches(ctx.csprojText, /Microsoft\.NET\.Sdk\.Web/i), '.csproj -> Microsoft.NET.Sdk.Web', 4),
|
|
479
|
+
evidenceIf(textMatches(ctx.programCsText, /WebApplication\.CreateBuilder/i), 'Program.cs -> WebApplication.CreateBuilder', 2)
|
|
480
|
+
])
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
id: 'tauri',
|
|
484
|
+
kind: 'desktop',
|
|
485
|
+
language: 'rust',
|
|
486
|
+
projectType: 'desktop',
|
|
487
|
+
detect: ctx => buildDetection('tauri', [
|
|
488
|
+
evidenceIf(hasDependency(ctx.depNames, '@tauri-apps/api'), 'package.json -> @tauri-apps/api', 4),
|
|
489
|
+
evidenceIf(ctx.fileExists('src-tauri', 'tauri.conf.json') || ctx.fileExists('src-tauri', 'tauri.conf.json5'), 'found src-tauri/tauri.conf.*', 4),
|
|
490
|
+
evidenceIf(ctx.fileExists('src-tauri', 'Cargo.toml'), 'found src-tauri/Cargo.toml', 2)
|
|
491
|
+
])
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
id: 'axum',
|
|
495
|
+
kind: 'backend',
|
|
496
|
+
language: 'rust',
|
|
497
|
+
projectType: 'api',
|
|
498
|
+
detect: ctx => buildDetection('axum', [
|
|
499
|
+
evidenceIf(textMatches(ctx.cargoText, /\baxum\b/i), 'Cargo.toml -> axum', 4)
|
|
500
|
+
])
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
id: 'actix',
|
|
504
|
+
kind: 'backend',
|
|
505
|
+
language: 'rust',
|
|
506
|
+
projectType: 'api',
|
|
507
|
+
detect: ctx => buildDetection('actix', [
|
|
508
|
+
evidenceIf(textMatches(ctx.cargoText, /\bactix-web\b/i), 'Cargo.toml -> actix-web', 4)
|
|
509
|
+
])
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
id: 'flutter',
|
|
513
|
+
kind: 'mobile',
|
|
514
|
+
projectType: 'mobile',
|
|
515
|
+
detect: ctx => buildDetection('flutter', [
|
|
516
|
+
evidenceIf(textMatches(ctx.pubspecText, /^\s*flutter\s*:/im), 'pubspec.yaml -> flutter', 4)
|
|
517
|
+
])
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
id: 'react-native',
|
|
521
|
+
kind: 'mobile',
|
|
522
|
+
projectType: 'mobile',
|
|
523
|
+
detect: ctx => buildDetection('react-native', [
|
|
524
|
+
evidenceIf(hasDependency(ctx.depNames, 'react-native', 'expo'), 'package.json -> react-native/expo', 4)
|
|
525
|
+
])
|
|
526
|
+
}
|
|
527
|
+
];
|
|
528
|
+
|
|
151
529
|
function scanProject() {
|
|
152
530
|
const pkg = readJsonIfExists('package.json') || {};
|
|
531
|
+
const composer = readJsonIfExists('composer.json') || {};
|
|
532
|
+
const rootEntries = fs.existsSync(targetDir) ? fs.readdirSync(targetDir) : [];
|
|
533
|
+
const rootCsproj = rootEntries.find(name => name.endsWith('.csproj')) || '';
|
|
153
534
|
const deps = {
|
|
154
535
|
...(pkg.dependencies || {}),
|
|
155
536
|
...(pkg.devDependencies || {}),
|
|
156
537
|
...(pkg.peerDependencies || {})
|
|
157
538
|
};
|
|
158
539
|
const depNames = new Set(Object.keys(deps));
|
|
159
|
-
const
|
|
540
|
+
const composerDeps = {
|
|
541
|
+
...(composer.require || {}),
|
|
542
|
+
...(composer['require-dev'] || {})
|
|
543
|
+
};
|
|
544
|
+
const composerDepNames = new Set(Object.keys(composerDeps));
|
|
545
|
+
const ctx = {
|
|
546
|
+
pkg,
|
|
547
|
+
composer,
|
|
548
|
+
depNames,
|
|
549
|
+
composerDepNames,
|
|
550
|
+
rootEntries,
|
|
551
|
+
fileExists,
|
|
552
|
+
requirementsText: readTextIfExists('requirements.txt'),
|
|
553
|
+
pyprojectText: readTextIfExists('pyproject.toml'),
|
|
554
|
+
goModText: readTextIfExists('go.mod'),
|
|
555
|
+
pubspecText: readTextIfExists('pubspec.yaml'),
|
|
556
|
+
gemfileText: readTextIfExists('Gemfile'),
|
|
557
|
+
pomText: readTextIfExists('pom.xml'),
|
|
558
|
+
gradleText: `${readTextIfExists('build.gradle')}\n${readTextIfExists('build.gradle.kts')}`,
|
|
559
|
+
cargoText: `${readTextIfExists('Cargo.toml')}\n${readTextIfExists('src-tauri', 'Cargo.toml')}`,
|
|
560
|
+
csprojText: rootCsproj ? readTextIfExists(rootCsproj) : '',
|
|
561
|
+
programCsText: readTextIfExists('Program.cs')
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
const languageDetections = LANGUAGE_DETECTORS
|
|
565
|
+
.map(detector => detector.detect(ctx))
|
|
566
|
+
.filter(result => result.score >= 2)
|
|
567
|
+
.sort((a, b) => b.score - a.score);
|
|
568
|
+
|
|
569
|
+
const frameworkDetails = FRAMEWORK_DETECTORS
|
|
570
|
+
.map(detector => {
|
|
571
|
+
const result = detector.detect(ctx);
|
|
572
|
+
if (result.score < 3) return null;
|
|
573
|
+
return {
|
|
574
|
+
...result,
|
|
575
|
+
kind: detector.kind,
|
|
576
|
+
language: detector.language,
|
|
577
|
+
projectType: detector.projectType,
|
|
578
|
+
confidence: confidenceLabel(result.score)
|
|
579
|
+
};
|
|
580
|
+
})
|
|
581
|
+
.filter(Boolean)
|
|
582
|
+
.sort((a, b) => b.score - a.score);
|
|
583
|
+
|
|
584
|
+
const frameworkLanguageScores = frameworkDetails.reduce((acc, item) => {
|
|
585
|
+
if (!item.language) return acc;
|
|
586
|
+
acc[item.language] = (acc[item.language] || 0) + item.score;
|
|
587
|
+
return acc;
|
|
588
|
+
}, {});
|
|
589
|
+
const primaryLanguage = Object.keys(frameworkLanguageScores).sort((a, b) => frameworkLanguageScores[b] - frameworkLanguageScores[a])[0]
|
|
590
|
+
|| languageDetections[0]?.id
|
|
591
|
+
|| 'typescript';
|
|
592
|
+
const frameworks = uniq(frameworkDetails.map(item => item.id)).filter(name => FRAMEWORK_CHOICES.includes(name));
|
|
160
593
|
const infrastructure = [];
|
|
161
|
-
let primaryLanguage = '';
|
|
162
594
|
let projectType = '';
|
|
163
595
|
let database = 'none';
|
|
164
596
|
let testFramework = 'auto-detect';
|
|
165
597
|
let cloud = 'none';
|
|
166
598
|
let ciCd = 'none';
|
|
167
599
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
600
|
+
if (frameworkDetails.some(item => item.projectType === 'desktop')) projectType = 'desktop';
|
|
601
|
+
else if (frameworkDetails.some(item => item.projectType === 'mobile')) projectType = 'mobile';
|
|
602
|
+
else if (frameworkDetails.some(item => item.kind === 'frontend') && frameworkDetails.some(item => item.kind === 'backend')) projectType = 'fullstack';
|
|
603
|
+
else if (frameworkDetails.some(item => item.kind === 'backend')) projectType = 'api';
|
|
604
|
+
else if (frameworkDetails.some(item => item.kind === 'frontend')) projectType = 'web-app';
|
|
605
|
+
else if (fileExists('package.json') && pkg.bin) projectType = 'cli';
|
|
606
|
+
else if (['php', 'python', 'go', 'java', 'ruby', 'csharp', 'rust'].includes(primaryLanguage)) projectType = 'api';
|
|
171
607
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
addFramework('express', depNames.has('express'));
|
|
179
|
-
addFramework('nestjs', depNames.has('@nestjs/core'));
|
|
180
|
-
addFramework('fastapi', fileExists('requirements.txt') && /fastapi/i.test(readTextIfExists('requirements.txt')));
|
|
181
|
-
addFramework('django', fileExists('requirements.txt') && /django/i.test(readTextIfExists('requirements.txt')));
|
|
182
|
-
addFramework('tauri', depNames.has('@tauri-apps/api') || fileExists('src-tauri', 'tauri.conf.json') || fileExists('src-tauri', 'tauri.conf.json5'));
|
|
183
|
-
addFramework('flutter', fileExists('pubspec.yaml') && /flutter:/i.test(readTextIfExists('pubspec.yaml')));
|
|
184
|
-
addFramework('react-native', depNames.has('react-native') || depNames.has('expo'));
|
|
185
|
-
addFramework('laravel', fileExists('artisan') || depNames.has('laravel'));
|
|
186
|
-
addFramework('gin', fileExists('go.mod') && /\bgithub\.com\/gin-gonic\/gin\b/i.test(readTextIfExists('go.mod')));
|
|
187
|
-
addFramework('fiber', fileExists('go.mod') && /\bgithub\.com\/gofiber\/fiber\b/i.test(readTextIfExists('go.mod')));
|
|
188
|
-
|
|
189
|
-
if (fileExists('tsconfig.json') || depNames.has('typescript')) primaryLanguage = 'typescript';
|
|
190
|
-
else if (fileExists('Cargo.toml') || fileExists('src-tauri', 'Cargo.toml')) primaryLanguage = 'rust';
|
|
191
|
-
else if (fileExists('go.mod')) primaryLanguage = 'go';
|
|
192
|
-
else if (fileExists('requirements.txt') || fileExists('pyproject.toml')) primaryLanguage = 'python';
|
|
193
|
-
else if (fileExists('composer.json')) primaryLanguage = 'php';
|
|
194
|
-
else if (depNames.size > 0 || fileExists('package.json')) primaryLanguage = 'javascript';
|
|
195
|
-
|
|
196
|
-
if (frameworks.includes('tauri')) projectType = 'desktop';
|
|
197
|
-
else if (frameworks.includes('flutter') || frameworks.includes('react-native')) projectType = 'mobile';
|
|
198
|
-
else if (frameworks.some(f => ['express', 'fastapi', 'django', 'laravel', 'gin', 'fiber', 'nestjs'].includes(f))) {
|
|
199
|
-
projectType = frameworks.some(f => ['react', 'next', 'vue', 'nuxt', 'svelte', 'angular'].includes(f)) ? 'fullstack' : 'api';
|
|
200
|
-
} else if (frameworks.some(f => ['react', 'next', 'vue', 'nuxt', 'svelte', 'angular'].includes(f))) {
|
|
201
|
-
projectType = 'web-app';
|
|
202
|
-
} else if (fileExists('package.json') && pkg.bin) {
|
|
203
|
-
projectType = 'cli';
|
|
204
|
-
}
|
|
608
|
+
if (hasDependency(depNames, 'pg', 'postgres', '@prisma/client') || /postgres/i.test(JSON.stringify(pkg))) database = 'postgres';
|
|
609
|
+
else if (hasDependency(depNames, 'mysql', 'mysql2')) database = 'mysql';
|
|
610
|
+
else if (hasDependency(depNames, 'sqlite3', 'better-sqlite3', 'rusqlite')) database = 'sqlite';
|
|
611
|
+
else if (hasDependency(depNames, 'mongodb', 'mongoose')) database = 'mongodb';
|
|
612
|
+
else if (hasDependency(depNames, 'redis', 'ioredis')) database = 'redis';
|
|
613
|
+
else if (hasDependency(depNames, '@aws-sdk/client-dynamodb')) database = 'dynamodb';
|
|
205
614
|
|
|
206
|
-
if (
|
|
207
|
-
else if (depNames
|
|
208
|
-
else if (depNames
|
|
209
|
-
else if (
|
|
210
|
-
else if (
|
|
211
|
-
else if (depNames.has('@aws-sdk/client-dynamodb')) database = 'dynamodb';
|
|
212
|
-
|
|
213
|
-
if (depNames.has('vitest')) testFramework = 'vitest';
|
|
214
|
-
else if (depNames.has('jest')) testFramework = 'jest';
|
|
215
|
-
else if (depNames.has('pytest') || /pytest/i.test(readTextIfExists('requirements.txt'))) testFramework = 'pytest';
|
|
216
|
-
else if (depNames.has('phpunit')) testFramework = 'phpunit';
|
|
217
|
-
else if (depNames.has('pest')) testFramework = 'pest';
|
|
615
|
+
if (hasDependency(depNames, 'vitest')) testFramework = 'vitest';
|
|
616
|
+
else if (hasDependency(depNames, 'jest')) testFramework = 'jest';
|
|
617
|
+
else if (hasDependency(depNames, 'pytest') || textMatches(ctx.requirementsText, /\bpytest\b/i) || textMatches(ctx.pyprojectText, /\bpytest\b/i)) testFramework = 'pytest';
|
|
618
|
+
else if (hasComposerDependency(composerDepNames, 'phpunit/phpunit')) testFramework = 'phpunit';
|
|
619
|
+
else if (hasComposerDependency(composerDepNames, 'pestphp/pest')) testFramework = 'pest';
|
|
218
620
|
else if (fileExists('go.mod')) testFramework = 'go test';
|
|
621
|
+
else if (textMatches(ctx.gemfileText, /^\s*gem\s+["']rspec["']/im)) testFramework = 'rspec';
|
|
219
622
|
|
|
220
623
|
if (fileExists('Dockerfile') || fileExists('docker-compose.yml') || fileExists('docker-compose.yaml')) {
|
|
221
624
|
infrastructure.push('docker');
|
|
@@ -237,12 +640,19 @@ function scanProject() {
|
|
|
237
640
|
else if (fileExists('.circleci', 'config.yml')) ciCd = 'circleci';
|
|
238
641
|
else if (fileExists('Jenkinsfile')) ciCd = 'jenkins';
|
|
239
642
|
|
|
643
|
+
const scanEvidence = [
|
|
644
|
+
...(languageDetections[0] ? [`language:${languageDetections[0].id} => ${languageDetections[0].evidence.join('; ')}`] : []),
|
|
645
|
+
...frameworkDetails.map(item => `${item.id} [${item.confidence}] => ${item.evidence.join('; ')}`)
|
|
646
|
+
];
|
|
647
|
+
|
|
240
648
|
return {
|
|
241
649
|
project_name: path.basename(targetDir),
|
|
242
|
-
description: pkg.description || 'A software project',
|
|
650
|
+
description: pkg.description || composer.description || 'A software project',
|
|
243
651
|
project_type: PROJECT_TYPE_CHOICES.includes(projectType) ? projectType : 'web-app',
|
|
244
652
|
primary_language: LANGUAGE_CHOICES.includes(primaryLanguage) ? primaryLanguage : 'typescript',
|
|
245
|
-
frameworks
|
|
653
|
+
frameworks,
|
|
654
|
+
framework_details: frameworkDetails,
|
|
655
|
+
scan_evidence: scanEvidence,
|
|
246
656
|
database: DATABASE_CHOICES.includes(database) ? database : 'none',
|
|
247
657
|
package_manager: PACKAGE_MANAGER_CHOICES.includes(detectPackageManager()) ? detectPackageManager() : 'auto-detect',
|
|
248
658
|
uses_docker: infrastructure.includes('docker'),
|
|
@@ -265,7 +675,7 @@ function resolveAgentTable(data) {
|
|
|
265
675
|
const lang = (data.primary_language || "").toLowerCase();
|
|
266
676
|
|
|
267
677
|
// Frontend
|
|
268
|
-
const frontendFws = frameworks.filter(f =>
|
|
678
|
+
const frontendFws = frameworks.filter(f => FRONTEND_FRAMEWORKS.includes(f));
|
|
269
679
|
if (frontendFws.length > 0) {
|
|
270
680
|
let feSkills = [];
|
|
271
681
|
frontendFws.forEach(fw => {
|
|
@@ -276,14 +686,14 @@ function resolveAgentTable(data) {
|
|
|
276
686
|
|
|
277
687
|
// Backend
|
|
278
688
|
let beSkills = [...(LANG_SKILLS[lang] || ["api-design"])];
|
|
279
|
-
const backendFws = frameworks.filter(f =>
|
|
689
|
+
const backendFws = frameworks.filter(f => BACKEND_FRAMEWORKS.includes(f));
|
|
280
690
|
backendFws.forEach(fw => {
|
|
281
691
|
beSkills.push(...(FRAMEWORK_SKILLS[fw] || []));
|
|
282
692
|
});
|
|
283
693
|
rows.push(["Backend API", "backend-specialist", Array.from(new Set(beSkills)).slice(0, 3).join(", ")]);
|
|
284
694
|
|
|
285
695
|
// Mobile/Desktop
|
|
286
|
-
const mobileFws = frameworks.filter(f =>
|
|
696
|
+
const mobileFws = frameworks.filter(f => MOBILE_DESKTOP_FRAMEWORKS.includes(f));
|
|
287
697
|
if (mobileFws.length > 0) {
|
|
288
698
|
let mobileSkills = [];
|
|
289
699
|
mobileFws.forEach(fw => {
|
|
@@ -306,12 +716,25 @@ function resolveAgentTable(data) {
|
|
|
306
716
|
|
|
307
717
|
// --- File Generators ---
|
|
308
718
|
|
|
309
|
-
function generateProjectContext(data) {
|
|
310
|
-
const agentTable = resolveAgentTable(data);
|
|
311
|
-
const autoSkills = resolveSkills(data);
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
719
|
+
function generateProjectContext(data) {
|
|
720
|
+
const agentTable = resolveAgentTable(data);
|
|
721
|
+
const autoSkills = resolveSkills(data);
|
|
722
|
+
const scanEvidenceBlock = (data.scan_evidence || []).length > 0
|
|
723
|
+
? `
|
|
724
|
+
---
|
|
725
|
+
|
|
726
|
+
## Scan Evidence
|
|
727
|
+
|
|
728
|
+
These detections were inferred from the current codebase during \`init --scan\`:
|
|
729
|
+
|
|
730
|
+
\`\`\`text
|
|
731
|
+
${(data.scan_evidence || []).map(line => '- ' + line).join('\n')}
|
|
732
|
+
\`\`\`
|
|
733
|
+
`
|
|
734
|
+
: '';
|
|
735
|
+
|
|
736
|
+
const agentRows = agentTable.map(([task, agent, skills]) => `| ${task} | \`${agent}\` | ${skills} |`).join('\n');
|
|
737
|
+
const autoSkillsList = autoSkills.map(s => `- ${s}`).join('\n');
|
|
315
738
|
|
|
316
739
|
return `# PROJECT_CONTEXT.md
|
|
317
740
|
# Generated by thachvd-kit init wizard on ${new Date().toISOString().split('T')[0]}
|
|
@@ -335,11 +758,13 @@ stack:
|
|
|
335
758
|
infrastructure: [${(data.infrastructure || []).join(', ')}]
|
|
336
759
|
package_manager: ${data.package_manager || 'auto-detect'}
|
|
337
760
|
test_framework: ${data.test_framework || 'auto-detect'}
|
|
338
|
-
\`\`\`
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
761
|
+
\`\`\`
|
|
762
|
+
|
|
763
|
+
${scanEvidenceBlock}
|
|
764
|
+
|
|
765
|
+
---
|
|
766
|
+
|
|
767
|
+
## Agent Routing
|
|
343
768
|
|
|
344
769
|
| Task Type | Agent | Primary Skills |
|
|
345
770
|
|-----------|-------|----------------|
|
|
@@ -423,6 +848,11 @@ async function main() {
|
|
|
423
848
|
const command = rawArgs[0] && !rawArgs[0].startsWith('-') ? rawArgs[0] : 'init';
|
|
424
849
|
const args = command === 'init' ? rawArgs.slice(rawArgs[0] === 'init' ? 1 : 0) : rawArgs.slice(1);
|
|
425
850
|
|
|
851
|
+
if (rawArgs.includes('--version') || rawArgs.includes('-v')) {
|
|
852
|
+
console.log(packageJson.version);
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
|
|
426
856
|
if (rawArgs.includes('--help') || rawArgs.includes('-h')) {
|
|
427
857
|
printHelp();
|
|
428
858
|
return;
|
|
@@ -444,7 +874,7 @@ async function main() {
|
|
|
444
874
|
|
|
445
875
|
let projectSetup = 'new';
|
|
446
876
|
if (!useScan && !assumeYes) {
|
|
447
|
-
const modeAnswer = await
|
|
877
|
+
const modeAnswer = await askQuestions([
|
|
448
878
|
{
|
|
449
879
|
type: 'select',
|
|
450
880
|
name: 'project_setup',
|
|
@@ -476,10 +906,10 @@ async function main() {
|
|
|
476
906
|
}
|
|
477
907
|
|
|
478
908
|
if (fs.existsSync(path.join(targetDir, 'PROJECT_CONTEXT.md')) && !assumeYes) {
|
|
479
|
-
const res = await
|
|
909
|
+
const res = await askQuestions({
|
|
480
910
|
type: 'confirm',
|
|
481
911
|
name: 'overwrite',
|
|
482
|
-
message: 'PROJECT_CONTEXT.md already exists in this folder. Overwrite?',
|
|
912
|
+
message: 'PROJECT_CONTEXT.md already exists in this folder. Overwrite?',
|
|
483
913
|
initial: false
|
|
484
914
|
});
|
|
485
915
|
if (!res.overwrite) {
|
|
@@ -493,9 +923,14 @@ async function main() {
|
|
|
493
923
|
if (useScan) {
|
|
494
924
|
console.log(`${pc.bold(' Scan result:')} ${scanned.primary_language} | ${scanned.frameworks.join(', ') || 'no framework'}`);
|
|
495
925
|
console.log(` ${pc.bold('Detected infra:')} ${scanned.infrastructure.join(', ') || 'none'}\n`);
|
|
926
|
+
if ((scanned.scan_evidence || []).length > 0) {
|
|
927
|
+
console.log(` ${pc.bold('Evidence:')}`);
|
|
928
|
+
scanned.scan_evidence.forEach(line => console.log(` · ${line}`));
|
|
929
|
+
console.log('');
|
|
930
|
+
}
|
|
496
931
|
}
|
|
497
932
|
|
|
498
|
-
const { project_name, description, project_type } = await
|
|
933
|
+
const { project_name, description, project_type } = await askQuestions([
|
|
499
934
|
{ type: assumeYes ? null : 'text', name: 'project_name', message: 'Project name:', initial: scanned?.project_name || path.basename(targetDir) },
|
|
500
935
|
{ type: assumeYes ? null : 'text', name: 'description', message: 'Short description:', initial: scanned?.description || 'A software project' },
|
|
501
936
|
{
|
|
@@ -505,7 +940,7 @@ async function main() {
|
|
|
505
940
|
}
|
|
506
941
|
]);
|
|
507
942
|
|
|
508
|
-
const { primary_language } = await
|
|
943
|
+
const { primary_language } = await askQuestions({
|
|
509
944
|
type: assumeYes ? null : 'select',
|
|
510
945
|
name: 'primary_language',
|
|
511
946
|
message: 'Primary language:',
|
|
@@ -513,7 +948,7 @@ async function main() {
|
|
|
513
948
|
initial: Math.max(LANGUAGE_CHOICES.indexOf(scanned?.primary_language || 'typescript'), 0)
|
|
514
949
|
});
|
|
515
950
|
|
|
516
|
-
const { frameworks } = await
|
|
951
|
+
const { frameworks } = await askQuestions({
|
|
517
952
|
type: assumeYes ? null : 'multiselect',
|
|
518
953
|
name: 'frameworks',
|
|
519
954
|
message: 'Frameworks (Space to select, Enter to confirm):',
|
|
@@ -521,7 +956,7 @@ async function main() {
|
|
|
521
956
|
min: 0
|
|
522
957
|
});
|
|
523
958
|
|
|
524
|
-
const dbAndInfra = await
|
|
959
|
+
const dbAndInfra = await askQuestions([
|
|
525
960
|
{
|
|
526
961
|
type: assumeYes ? null : 'select', name: 'database', message: 'Database / ORM:',
|
|
527
962
|
choices: DATABASE_CHOICES.map(d => ({ title: d, value: d })),
|
|
@@ -535,7 +970,7 @@ async function main() {
|
|
|
535
970
|
}
|
|
536
971
|
]);
|
|
537
972
|
|
|
538
|
-
const { package_manager, test_framework, max_file_lines } = await
|
|
973
|
+
const { package_manager, test_framework, max_file_lines } = await askQuestions([
|
|
539
974
|
{
|
|
540
975
|
type: assumeYes ? null : 'select', name: 'package_manager', message: 'Package Manager:',
|
|
541
976
|
choices: PACKAGE_MANAGER_CHOICES.map(p => ({ title: p, value: p })),
|
|
@@ -561,6 +996,8 @@ async function main() {
|
|
|
561
996
|
data.package_manager = data.package_manager || scanned?.package_manager || 'auto-detect';
|
|
562
997
|
data.test_framework = data.test_framework || scanned?.test_framework || 'auto-detect';
|
|
563
998
|
data.max_file_lines = data.max_file_lines || scanned?.max_file_lines || '300';
|
|
999
|
+
data.framework_details = scanned?.framework_details || [];
|
|
1000
|
+
data.scan_evidence = scanned?.scan_evidence || [];
|
|
564
1001
|
data.infrastructure = [];
|
|
565
1002
|
if (data.uses_docker) data.infrastructure.push('docker');
|
|
566
1003
|
if (data.cloud !== 'none') data.infrastructure.push(data.cloud);
|