claude-cli-advanced-starter-pack 1.0.0

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 (67) hide show
  1. package/LICENSE +21 -0
  2. package/OVERVIEW.md +597 -0
  3. package/README.md +439 -0
  4. package/bin/gtask.js +282 -0
  5. package/bin/postinstall.js +53 -0
  6. package/package.json +69 -0
  7. package/src/agents/phase-dev-templates.js +1011 -0
  8. package/src/agents/templates.js +668 -0
  9. package/src/analysis/checklist-parser.js +414 -0
  10. package/src/analysis/codebase.js +481 -0
  11. package/src/cli/menu.js +958 -0
  12. package/src/commands/claude-audit.js +1482 -0
  13. package/src/commands/claude-settings.js +2243 -0
  14. package/src/commands/create-agent.js +681 -0
  15. package/src/commands/create-command.js +337 -0
  16. package/src/commands/create-hook.js +262 -0
  17. package/src/commands/create-phase-dev/codebase-analyzer.js +813 -0
  18. package/src/commands/create-phase-dev/documentation-generator.js +352 -0
  19. package/src/commands/create-phase-dev/post-completion.js +404 -0
  20. package/src/commands/create-phase-dev/scale-calculator.js +344 -0
  21. package/src/commands/create-phase-dev/wizard.js +492 -0
  22. package/src/commands/create-phase-dev.js +481 -0
  23. package/src/commands/create-skill.js +313 -0
  24. package/src/commands/create.js +446 -0
  25. package/src/commands/decompose.js +392 -0
  26. package/src/commands/detect-tech-stack.js +768 -0
  27. package/src/commands/explore-mcp/claude-md-updater.js +252 -0
  28. package/src/commands/explore-mcp/mcp-installer.js +346 -0
  29. package/src/commands/explore-mcp/mcp-registry.js +438 -0
  30. package/src/commands/explore-mcp.js +638 -0
  31. package/src/commands/gtask-init.js +641 -0
  32. package/src/commands/help.js +128 -0
  33. package/src/commands/init.js +1890 -0
  34. package/src/commands/install.js +250 -0
  35. package/src/commands/list.js +116 -0
  36. package/src/commands/roadmap.js +750 -0
  37. package/src/commands/setup-wizard.js +482 -0
  38. package/src/commands/setup.js +351 -0
  39. package/src/commands/sync.js +534 -0
  40. package/src/commands/test-run.js +456 -0
  41. package/src/commands/test-setup.js +456 -0
  42. package/src/commands/validate.js +67 -0
  43. package/src/config/tech-stack.defaults.json +182 -0
  44. package/src/config/tech-stack.schema.json +502 -0
  45. package/src/github/client.js +359 -0
  46. package/src/index.js +84 -0
  47. package/src/templates/claude-command.js +244 -0
  48. package/src/templates/issue-body.js +284 -0
  49. package/src/testing/config.js +411 -0
  50. package/src/utils/template-engine.js +398 -0
  51. package/src/utils/validate-templates.js +223 -0
  52. package/src/utils.js +396 -0
  53. package/templates/commands/ccasp-setup.template.md +113 -0
  54. package/templates/commands/context-audit.template.md +97 -0
  55. package/templates/commands/create-task-list.template.md +382 -0
  56. package/templates/commands/deploy-full.template.md +261 -0
  57. package/templates/commands/github-task-start.template.md +99 -0
  58. package/templates/commands/github-update.template.md +69 -0
  59. package/templates/commands/happy-start.template.md +117 -0
  60. package/templates/commands/phase-track.template.md +142 -0
  61. package/templates/commands/tunnel-start.template.md +127 -0
  62. package/templates/commands/tunnel-stop.template.md +106 -0
  63. package/templates/hooks/context-guardian.template.js +173 -0
  64. package/templates/hooks/deployment-orchestrator.template.js +219 -0
  65. package/templates/hooks/github-progress-hook.template.js +197 -0
  66. package/templates/hooks/happy-checkpoint-manager.template.js +222 -0
  67. package/templates/hooks/phase-dev-enforcer.template.js +183 -0
@@ -0,0 +1,768 @@
1
+ /**
2
+ * Tech Stack Detection
3
+ *
4
+ * Auto-detects the project's technology stack by scanning:
5
+ * - Package files (package.json, requirements.txt, Cargo.toml, etc.)
6
+ * - Config files (vite.config.ts, next.config.js, etc.)
7
+ * - Source directories and file patterns
8
+ * - Git remote URLs
9
+ * - Existing .claude configurations
10
+ */
11
+
12
+ import chalk from 'chalk';
13
+ import ora from 'ora';
14
+ import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
15
+ import { join, basename } from 'path';
16
+ import { execSync } from 'child_process';
17
+
18
+ /**
19
+ * Detection patterns for various technologies
20
+ */
21
+ const DETECTION_PATTERNS = {
22
+ // Frontend frameworks
23
+ frontend: {
24
+ react: {
25
+ packages: ['react', 'react-dom'],
26
+ files: ['src/App.tsx', 'src/App.jsx', 'src/index.tsx'],
27
+ configFiles: ['vite.config.ts', 'vite.config.js', 'craco.config.js'],
28
+ },
29
+ vue: {
30
+ packages: ['vue'],
31
+ files: ['src/App.vue', 'src/main.ts'],
32
+ configFiles: ['vue.config.js', 'vite.config.ts'],
33
+ },
34
+ angular: {
35
+ packages: ['@angular/core'],
36
+ files: ['src/app/app.component.ts'],
37
+ configFiles: ['angular.json'],
38
+ },
39
+ svelte: {
40
+ packages: ['svelte'],
41
+ files: ['src/App.svelte'],
42
+ configFiles: ['svelte.config.js'],
43
+ },
44
+ nextjs: {
45
+ packages: ['next'],
46
+ configFiles: ['next.config.js', 'next.config.mjs', 'next.config.ts'],
47
+ },
48
+ nuxt: {
49
+ packages: ['nuxt'],
50
+ configFiles: ['nuxt.config.ts', 'nuxt.config.js'],
51
+ },
52
+ astro: {
53
+ packages: ['astro'],
54
+ configFiles: ['astro.config.mjs'],
55
+ },
56
+ },
57
+
58
+ // Build tools
59
+ buildTool: {
60
+ vite: {
61
+ packages: ['vite'],
62
+ configFiles: ['vite.config.ts', 'vite.config.js'],
63
+ },
64
+ webpack: {
65
+ packages: ['webpack'],
66
+ configFiles: ['webpack.config.js', 'webpack.config.ts'],
67
+ },
68
+ esbuild: {
69
+ packages: ['esbuild'],
70
+ },
71
+ parcel: {
72
+ packages: ['parcel'],
73
+ },
74
+ turbopack: {
75
+ packages: ['turbo'],
76
+ configFiles: ['turbo.json'],
77
+ },
78
+ },
79
+
80
+ // State managers
81
+ stateManager: {
82
+ zustand: { packages: ['zustand'] },
83
+ redux: { packages: ['@reduxjs/toolkit', 'redux'] },
84
+ mobx: { packages: ['mobx'] },
85
+ jotai: { packages: ['jotai'] },
86
+ recoil: { packages: ['recoil'] },
87
+ pinia: { packages: ['pinia'] },
88
+ vuex: { packages: ['vuex'] },
89
+ },
90
+
91
+ // Backend languages/frameworks
92
+ backend: {
93
+ fastapi: {
94
+ pythonPackages: ['fastapi'],
95
+ files: ['main.py', 'run_api.py', 'app/main.py'],
96
+ },
97
+ express: {
98
+ packages: ['express'],
99
+ files: ['server.js', 'app.js', 'index.js'],
100
+ },
101
+ nestjs: {
102
+ packages: ['@nestjs/core'],
103
+ files: ['src/main.ts'],
104
+ },
105
+ django: {
106
+ pythonPackages: ['django'],
107
+ files: ['manage.py'],
108
+ },
109
+ flask: {
110
+ pythonPackages: ['flask'],
111
+ files: ['app.py', 'wsgi.py'],
112
+ },
113
+ rails: {
114
+ gemPackages: ['rails'],
115
+ files: ['Gemfile', 'config/routes.rb'],
116
+ },
117
+ gin: {
118
+ goPackages: ['github.com/gin-gonic/gin'],
119
+ },
120
+ },
121
+
122
+ // Databases
123
+ database: {
124
+ postgresql: {
125
+ envPatterns: ['DATABASE_URL.*postgres', 'POSTGRES_'],
126
+ packages: ['pg', 'psycopg2', 'asyncpg'],
127
+ },
128
+ mysql: {
129
+ envPatterns: ['DATABASE_URL.*mysql', 'MYSQL_'],
130
+ packages: ['mysql2', 'mysqlclient'],
131
+ },
132
+ mongodb: {
133
+ packages: ['mongodb', 'mongoose', 'pymongo'],
134
+ },
135
+ sqlite: {
136
+ files: ['*.db', '*.sqlite', '*.sqlite3'],
137
+ },
138
+ },
139
+
140
+ // ORMs
141
+ orm: {
142
+ prisma: {
143
+ packages: ['prisma', '@prisma/client'],
144
+ configFiles: ['prisma/schema.prisma'],
145
+ },
146
+ drizzle: {
147
+ packages: ['drizzle-orm'],
148
+ },
149
+ typeorm: {
150
+ packages: ['typeorm'],
151
+ },
152
+ sqlalchemy: {
153
+ pythonPackages: ['sqlalchemy'],
154
+ },
155
+ sequelize: {
156
+ packages: ['sequelize'],
157
+ },
158
+ },
159
+
160
+ // Testing frameworks
161
+ e2eFramework: {
162
+ playwright: {
163
+ packages: ['@playwright/test', 'playwright'],
164
+ configFiles: ['playwright.config.ts', 'playwright.config.js'],
165
+ },
166
+ cypress: {
167
+ packages: ['cypress'],
168
+ configFiles: ['cypress.config.js', 'cypress.config.ts'],
169
+ },
170
+ puppeteer: {
171
+ packages: ['puppeteer'],
172
+ },
173
+ },
174
+
175
+ unitFramework: {
176
+ vitest: {
177
+ packages: ['vitest'],
178
+ configFiles: ['vitest.config.ts'],
179
+ },
180
+ jest: {
181
+ packages: ['jest'],
182
+ configFiles: ['jest.config.js', 'jest.config.ts'],
183
+ },
184
+ mocha: {
185
+ packages: ['mocha'],
186
+ },
187
+ pytest: {
188
+ pythonPackages: ['pytest'],
189
+ configFiles: ['pytest.ini', 'pyproject.toml'],
190
+ },
191
+ },
192
+
193
+ // Deployment platforms (detected from config files)
194
+ deployment: {
195
+ vercel: {
196
+ configFiles: ['vercel.json'],
197
+ },
198
+ netlify: {
199
+ configFiles: ['netlify.toml'],
200
+ },
201
+ cloudflare: {
202
+ configFiles: ['wrangler.toml', 'wrangler.json'],
203
+ },
204
+ railway: {
205
+ configFiles: ['railway.json', 'railway.toml'],
206
+ },
207
+ docker: {
208
+ configFiles: ['Dockerfile', 'docker-compose.yml', 'docker-compose.yaml'],
209
+ },
210
+ },
211
+
212
+ // Tunnel services
213
+ tunnel: {
214
+ ngrok: {
215
+ configFiles: ['ngrok.yml', 'ngrok.yaml'],
216
+ processPatterns: ['ngrok'],
217
+ },
218
+ cloudflare: {
219
+ configFiles: ['.cloudflared/'],
220
+ },
221
+ },
222
+ };
223
+
224
+ /**
225
+ * Read and parse package.json
226
+ */
227
+ function readPackageJson(projectRoot) {
228
+ const pkgPath = join(projectRoot, 'package.json');
229
+ if (!existsSync(pkgPath)) return null;
230
+
231
+ try {
232
+ return JSON.parse(readFileSync(pkgPath, 'utf8'));
233
+ } catch {
234
+ return null;
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Read Python requirements
240
+ */
241
+ function readPythonRequirements(projectRoot) {
242
+ const files = ['requirements.txt', 'pyproject.toml', 'Pipfile'];
243
+ const packages = [];
244
+
245
+ for (const file of files) {
246
+ const filePath = join(projectRoot, file);
247
+ if (existsSync(filePath)) {
248
+ const content = readFileSync(filePath, 'utf8');
249
+ // Extract package names (simplified)
250
+ const matches = content.match(/^[a-zA-Z][a-zA-Z0-9_-]*/gm);
251
+ if (matches) packages.push(...matches);
252
+ }
253
+ }
254
+
255
+ return packages;
256
+ }
257
+
258
+ /**
259
+ * Check if any files match patterns
260
+ */
261
+ function fileExists(projectRoot, patterns) {
262
+ if (!patterns) return false;
263
+
264
+ for (const pattern of patterns) {
265
+ const filePath = join(projectRoot, pattern);
266
+ if (existsSync(filePath)) return true;
267
+ }
268
+ return false;
269
+ }
270
+
271
+ /**
272
+ * Check if packages are installed
273
+ */
274
+ function hasPackages(pkgJson, packages) {
275
+ if (!pkgJson || !packages) return false;
276
+
277
+ const allDeps = {
278
+ ...(pkgJson.dependencies || {}),
279
+ ...(pkgJson.devDependencies || {}),
280
+ };
281
+
282
+ return packages.some((pkg) => allDeps[pkg]);
283
+ }
284
+
285
+ /**
286
+ * Detect port from various config files
287
+ */
288
+ function detectPort(projectRoot, type) {
289
+ // Check vite.config.ts/js
290
+ const viteConfigs = ['vite.config.ts', 'vite.config.js'];
291
+ for (const config of viteConfigs) {
292
+ const configPath = join(projectRoot, config);
293
+ if (existsSync(configPath)) {
294
+ const content = readFileSync(configPath, 'utf8');
295
+ const portMatch = content.match(/port:\s*(\d+)/);
296
+ if (portMatch) return parseInt(portMatch[1]);
297
+ }
298
+ }
299
+
300
+ // Check package.json scripts
301
+ const pkgJson = readPackageJson(projectRoot);
302
+ if (pkgJson?.scripts?.dev) {
303
+ const portMatch = pkgJson.scripts.dev.match(/--port[=\s]+(\d+)/);
304
+ if (portMatch) return parseInt(portMatch[1]);
305
+ }
306
+
307
+ // Default ports
308
+ return type === 'frontend' ? 5173 : 8000;
309
+ }
310
+
311
+ /**
312
+ * Detect Git remote info
313
+ */
314
+ function detectGitInfo(projectRoot) {
315
+ try {
316
+ const remoteUrl = execSync('git config --get remote.origin.url', {
317
+ cwd: projectRoot,
318
+ encoding: 'utf8',
319
+ }).trim();
320
+
321
+ // Parse GitHub URL
322
+ const githubMatch = remoteUrl.match(
323
+ /github\.com[:/]([^/]+)\/([^/.]+)/
324
+ );
325
+ if (githubMatch) {
326
+ return {
327
+ provider: 'github',
328
+ owner: githubMatch[1],
329
+ repo: githubMatch[2].replace('.git', ''),
330
+ };
331
+ }
332
+
333
+ // Parse GitLab URL
334
+ const gitlabMatch = remoteUrl.match(
335
+ /gitlab\.com[:/]([^/]+)\/([^/.]+)/
336
+ );
337
+ if (gitlabMatch) {
338
+ return {
339
+ provider: 'gitlab',
340
+ owner: gitlabMatch[1],
341
+ repo: gitlabMatch[2].replace('.git', ''),
342
+ };
343
+ }
344
+
345
+ return null;
346
+ } catch {
347
+ return null;
348
+ }
349
+ }
350
+
351
+ /**
352
+ * Detect default branch
353
+ */
354
+ function detectDefaultBranch(projectRoot) {
355
+ try {
356
+ // Try to get from git
357
+ const branch = execSync('git symbolic-ref refs/remotes/origin/HEAD', {
358
+ cwd: projectRoot,
359
+ encoding: 'utf8',
360
+ stdio: ['pipe', 'pipe', 'ignore'],
361
+ }).trim();
362
+ return branch.replace('refs/remotes/origin/', '');
363
+ } catch {
364
+ // Check if main or master exists
365
+ try {
366
+ execSync('git rev-parse --verify main', {
367
+ cwd: projectRoot,
368
+ stdio: ['pipe', 'pipe', 'ignore'],
369
+ });
370
+ return 'main';
371
+ } catch {
372
+ return 'master';
373
+ }
374
+ }
375
+ }
376
+
377
+ /**
378
+ * Detect selectors from existing test files
379
+ */
380
+ function detectSelectors(projectRoot) {
381
+ const testDirs = ['tests', 'test', 'e2e', '__tests__', 'playwright'];
382
+ const defaultSelectors = {
383
+ strategy: 'data-testid',
384
+ username: '[data-testid="username-input"]',
385
+ password: '[data-testid="password-input"]',
386
+ loginButton: '[data-testid="login-submit"]',
387
+ loginSuccess: '[data-testid="dashboard"]',
388
+ };
389
+
390
+ // Scan for existing test files
391
+ for (const dir of testDirs) {
392
+ const testDir = join(projectRoot, dir);
393
+ if (!existsSync(testDir)) continue;
394
+
395
+ try {
396
+ const files = readdirSync(testDir, { recursive: true });
397
+ for (const file of files) {
398
+ if (!file.toString().match(/\.(ts|js|spec|test)/)) continue;
399
+
400
+ const filePath = join(testDir, file.toString());
401
+ if (!statSync(filePath).isFile()) continue;
402
+
403
+ const content = readFileSync(filePath, 'utf8');
404
+
405
+ // Look for data-testid patterns
406
+ const testIdMatch = content.match(
407
+ /\[data-testid=["']([^"']+)["']\]/
408
+ );
409
+ if (testIdMatch) {
410
+ defaultSelectors.strategy = 'data-testid';
411
+ // Try to find specific patterns
412
+ const usernameMatch = content.match(
413
+ /\[data-testid=["']([^"']*(?:user|login|email)[^"']*)["']\]/i
414
+ );
415
+ const passwordMatch = content.match(
416
+ /\[data-testid=["']([^"']*(?:pass|pwd)[^"']*)["']\]/i
417
+ );
418
+ const submitMatch = content.match(
419
+ /\[data-testid=["']([^"']*(?:submit|login|signin)[^"']*)["']\]/i
420
+ );
421
+
422
+ if (usernameMatch) defaultSelectors.username = `[data-testid="${usernameMatch[1]}"]`;
423
+ if (passwordMatch) defaultSelectors.password = `[data-testid="${passwordMatch[1]}"]`;
424
+ if (submitMatch) defaultSelectors.loginButton = `[data-testid="${submitMatch[1]}"]`;
425
+
426
+ return defaultSelectors;
427
+ }
428
+
429
+ // Look for name attribute patterns
430
+ const nameMatch = content.match(/name=["'](\w+)["']/);
431
+ if (nameMatch) {
432
+ defaultSelectors.strategy = 'name';
433
+ }
434
+ }
435
+ } catch {
436
+ // Continue if directory scan fails
437
+ }
438
+ }
439
+
440
+ return defaultSelectors;
441
+ }
442
+
443
+ /**
444
+ * Detect existing ngrok or tunnel configuration
445
+ */
446
+ function detectTunnelConfig(projectRoot) {
447
+ // Check for ngrok.yml
448
+ const ngrokConfig = join(projectRoot, 'ngrok.yml');
449
+ if (existsSync(ngrokConfig)) {
450
+ const content = readFileSync(ngrokConfig, 'utf8');
451
+ const subdomainMatch = content.match(/subdomain:\s*(\S+)/);
452
+ return {
453
+ service: 'ngrok',
454
+ subdomain: subdomainMatch?.[1] || null,
455
+ startCommand: 'ngrok http {{FRONTEND_PORT}}',
456
+ adminPort: 4040,
457
+ };
458
+ }
459
+
460
+ // Check for localtunnel in package.json
461
+ const pkgJson = readPackageJson(projectRoot);
462
+ if (pkgJson?.devDependencies?.localtunnel) {
463
+ return {
464
+ service: 'localtunnel',
465
+ startCommand: 'lt --port {{FRONTEND_PORT}}',
466
+ };
467
+ }
468
+
469
+ return {
470
+ service: 'none',
471
+ url: null,
472
+ subdomain: null,
473
+ };
474
+ }
475
+
476
+ /**
477
+ * Main detection function
478
+ */
479
+ export async function detectTechStack(projectRoot, options = {}) {
480
+ const spinner = options.silent ? null : ora('Detecting tech stack...').start();
481
+ const result = {
482
+ version: '1.0.0',
483
+ project: {
484
+ name: basename(projectRoot),
485
+ rootPath: '.',
486
+ },
487
+ frontend: {},
488
+ backend: {},
489
+ database: {},
490
+ deployment: { frontend: {}, backend: {} },
491
+ devEnvironment: {},
492
+ testing: { e2e: {}, unit: {}, selectors: {}, credentials: {} },
493
+ versionControl: {},
494
+ urls: { local: {}, tunnel: {}, production: {} },
495
+ _detected: [], // Track what was auto-detected
496
+ };
497
+
498
+ // Read package.json
499
+ const pkgJson = readPackageJson(projectRoot);
500
+ const pythonPackages = readPythonRequirements(projectRoot);
501
+
502
+ // --- FRONTEND DETECTION ---
503
+ spinner?.text = 'Detecting frontend framework...';
504
+
505
+ for (const [framework, patterns] of Object.entries(DETECTION_PATTERNS.frontend)) {
506
+ if (
507
+ hasPackages(pkgJson, patterns.packages) ||
508
+ fileExists(projectRoot, patterns.files) ||
509
+ fileExists(projectRoot, patterns.configFiles)
510
+ ) {
511
+ result.frontend.framework = framework;
512
+ result._detected.push(`frontend.framework: ${framework}`);
513
+ break;
514
+ }
515
+ }
516
+
517
+ // Detect build tool
518
+ for (const [tool, patterns] of Object.entries(DETECTION_PATTERNS.buildTool)) {
519
+ if (
520
+ hasPackages(pkgJson, patterns.packages) ||
521
+ fileExists(projectRoot, patterns.configFiles)
522
+ ) {
523
+ result.frontend.buildTool = tool;
524
+ result._detected.push(`frontend.buildTool: ${tool}`);
525
+ break;
526
+ }
527
+ }
528
+
529
+ // Detect state manager
530
+ for (const [manager, patterns] of Object.entries(DETECTION_PATTERNS.stateManager)) {
531
+ if (hasPackages(pkgJson, patterns.packages)) {
532
+ result.frontend.stateManager = manager;
533
+ result._detected.push(`frontend.stateManager: ${manager}`);
534
+ break;
535
+ }
536
+ }
537
+
538
+ // Detect frontend port
539
+ result.frontend.port = detectPort(projectRoot, 'frontend');
540
+ result._detected.push(`frontend.port: ${result.frontend.port}`);
541
+
542
+ // Detect styling
543
+ if (hasPackages(pkgJson, ['tailwindcss'])) {
544
+ result.frontend.styling = 'tailwind';
545
+ } else if (hasPackages(pkgJson, ['styled-components'])) {
546
+ result.frontend.styling = 'styled-components';
547
+ } else if (hasPackages(pkgJson, ['@emotion/react'])) {
548
+ result.frontend.styling = 'emotion';
549
+ }
550
+
551
+ // --- BACKEND DETECTION ---
552
+ spinner?.text = 'Detecting backend framework...';
553
+
554
+ for (const [framework, patterns] of Object.entries(DETECTION_PATTERNS.backend)) {
555
+ const hasPkg = hasPackages(pkgJson, patterns.packages);
556
+ const hasPyPkg = patterns.pythonPackages?.some((p) =>
557
+ pythonPackages.includes(p)
558
+ );
559
+ const hasFiles = fileExists(projectRoot, patterns.files);
560
+
561
+ if (hasPkg || hasPyPkg || hasFiles) {
562
+ result.backend.framework = framework;
563
+ result._detected.push(`backend.framework: ${framework}`);
564
+
565
+ // Set language based on framework
566
+ if (['fastapi', 'django', 'flask'].includes(framework)) {
567
+ result.backend.language = 'python';
568
+ } else if (['express', 'nestjs'].includes(framework)) {
569
+ result.backend.language = 'node';
570
+ } else if (framework === 'rails') {
571
+ result.backend.language = 'ruby';
572
+ } else if (framework === 'gin') {
573
+ result.backend.language = 'go';
574
+ }
575
+ break;
576
+ }
577
+ }
578
+
579
+ result.backend.port = detectPort(projectRoot, 'backend');
580
+ result.backend.healthEndpoint = '/api/health';
581
+
582
+ // --- DATABASE DETECTION ---
583
+ spinner?.text = 'Detecting database...';
584
+
585
+ for (const [db, patterns] of Object.entries(DETECTION_PATTERNS.database)) {
586
+ if (hasPackages(pkgJson, patterns.packages)) {
587
+ result.database.primary = db;
588
+ result._detected.push(`database.primary: ${db}`);
589
+ break;
590
+ }
591
+ }
592
+
593
+ // Detect ORM
594
+ for (const [orm, patterns] of Object.entries(DETECTION_PATTERNS.orm)) {
595
+ if (
596
+ hasPackages(pkgJson, patterns.packages) ||
597
+ fileExists(projectRoot, patterns.configFiles)
598
+ ) {
599
+ result.database.orm = orm;
600
+ result._detected.push(`database.orm: ${orm}`);
601
+ break;
602
+ }
603
+ }
604
+
605
+ // --- TESTING DETECTION ---
606
+ spinner?.text = 'Detecting testing frameworks...';
607
+
608
+ for (const [framework, patterns] of Object.entries(DETECTION_PATTERNS.e2eFramework)) {
609
+ if (
610
+ hasPackages(pkgJson, patterns.packages) ||
611
+ fileExists(projectRoot, patterns.configFiles)
612
+ ) {
613
+ result.testing.e2e.framework = framework;
614
+ result._detected.push(`testing.e2e.framework: ${framework}`);
615
+
616
+ // Set config file
617
+ if (patterns.configFiles) {
618
+ for (const cfg of patterns.configFiles) {
619
+ if (existsSync(join(projectRoot, cfg))) {
620
+ result.testing.e2e.configFile = cfg;
621
+ break;
622
+ }
623
+ }
624
+ }
625
+ break;
626
+ }
627
+ }
628
+
629
+ for (const [framework, patterns] of Object.entries(DETECTION_PATTERNS.unitFramework)) {
630
+ if (
631
+ hasPackages(pkgJson, patterns.packages) ||
632
+ fileExists(projectRoot, patterns.configFiles)
633
+ ) {
634
+ result.testing.unit.framework = framework;
635
+ result._detected.push(`testing.unit.framework: ${framework}`);
636
+ break;
637
+ }
638
+ }
639
+
640
+ // Detect selectors
641
+ result.testing.selectors = detectSelectors(projectRoot);
642
+
643
+ // --- DEPLOYMENT DETECTION ---
644
+ spinner?.text = 'Detecting deployment platforms...';
645
+
646
+ for (const [platform, patterns] of Object.entries(DETECTION_PATTERNS.deployment)) {
647
+ if (fileExists(projectRoot, patterns.configFiles)) {
648
+ // Determine if frontend or backend
649
+ if (['vercel', 'netlify', 'cloudflare'].includes(platform)) {
650
+ result.deployment.frontend.platform = platform;
651
+ result._detected.push(`deployment.frontend.platform: ${platform}`);
652
+ } else if (platform === 'railway') {
653
+ result.deployment.backend.platform = platform;
654
+ result._detected.push(`deployment.backend.platform: ${platform}`);
655
+ }
656
+ }
657
+ }
658
+
659
+ // --- DEV ENVIRONMENT ---
660
+ spinner?.text = 'Detecting dev environment...';
661
+
662
+ // Detect package manager
663
+ if (existsSync(join(projectRoot, 'pnpm-lock.yaml'))) {
664
+ result.devEnvironment.packageManager = 'pnpm';
665
+ } else if (existsSync(join(projectRoot, 'yarn.lock'))) {
666
+ result.devEnvironment.packageManager = 'yarn';
667
+ } else if (existsSync(join(projectRoot, 'bun.lockb'))) {
668
+ result.devEnvironment.packageManager = 'bun';
669
+ } else if (existsSync(join(projectRoot, 'package-lock.json'))) {
670
+ result.devEnvironment.packageManager = 'npm';
671
+ } else if (existsSync(join(projectRoot, 'requirements.txt'))) {
672
+ result.devEnvironment.packageManager = 'pip';
673
+ } else if (existsSync(join(projectRoot, 'poetry.lock'))) {
674
+ result.devEnvironment.packageManager = 'poetry';
675
+ }
676
+
677
+ // Detect tunnel
678
+ result.devEnvironment.tunnel = detectTunnelConfig(projectRoot);
679
+
680
+ // Detect container
681
+ if (fileExists(projectRoot, ['Dockerfile', 'docker-compose.yml'])) {
682
+ result.devEnvironment.container = 'docker';
683
+ }
684
+
685
+ // --- VERSION CONTROL ---
686
+ spinner?.text = 'Detecting version control...';
687
+
688
+ const gitInfo = detectGitInfo(projectRoot);
689
+ if (gitInfo) {
690
+ result.versionControl = {
691
+ ...gitInfo,
692
+ defaultBranch: detectDefaultBranch(projectRoot),
693
+ projectBoard: { type: 'none' },
694
+ };
695
+ result._detected.push(`versionControl: ${gitInfo.provider}/${gitInfo.owner}/${gitInfo.repo}`);
696
+ }
697
+
698
+ // --- BUILD URLs ---
699
+ result.urls.local = {
700
+ frontend: `http://localhost:${result.frontend.port || 5173}`,
701
+ backend: `http://localhost:${result.backend.port || 8000}`,
702
+ api: `http://localhost:${result.backend.port || 8000}/api`,
703
+ };
704
+
705
+ spinner?.succeed('Tech stack detection complete');
706
+
707
+ return result;
708
+ }
709
+
710
+ /**
711
+ * Run detection and output results
712
+ */
713
+ export async function runDetection(options = {}) {
714
+ const projectRoot = options.projectRoot || process.cwd();
715
+
716
+ console.log(chalk.cyan('\n📦 Tech Stack Detection\n'));
717
+ console.log(chalk.dim(`Project: ${projectRoot}\n`));
718
+
719
+ const result = await detectTechStack(projectRoot, options);
720
+
721
+ // Display results
722
+ console.log(chalk.green('\n✓ Detected Technologies:\n'));
723
+
724
+ if (result.frontend.framework) {
725
+ console.log(chalk.white(` Frontend: ${chalk.cyan(result.frontend.framework)}`));
726
+ if (result.frontend.buildTool) {
727
+ console.log(chalk.dim(` Build: ${result.frontend.buildTool}`));
728
+ }
729
+ if (result.frontend.stateManager) {
730
+ console.log(chalk.dim(` State: ${result.frontend.stateManager}`));
731
+ }
732
+ console.log(chalk.dim(` Port: ${result.frontend.port}`));
733
+ }
734
+
735
+ if (result.backend.framework) {
736
+ console.log(chalk.white(` Backend: ${chalk.cyan(result.backend.framework)} (${result.backend.language})`));
737
+ console.log(chalk.dim(` Port: ${result.backend.port}`));
738
+ }
739
+
740
+ if (result.database.primary) {
741
+ console.log(chalk.white(` Database: ${chalk.cyan(result.database.primary)}`));
742
+ if (result.database.orm) {
743
+ console.log(chalk.dim(` ORM: ${result.database.orm}`));
744
+ }
745
+ }
746
+
747
+ if (result.testing.e2e.framework) {
748
+ console.log(chalk.white(` E2E Tests: ${chalk.cyan(result.testing.e2e.framework)}`));
749
+ }
750
+
751
+ if (result.testing.unit.framework) {
752
+ console.log(chalk.white(` Unit Tests: ${chalk.cyan(result.testing.unit.framework)}`));
753
+ }
754
+
755
+ if (result.versionControl.provider) {
756
+ console.log(chalk.white(` Git: ${chalk.cyan(`${result.versionControl.owner}/${result.versionControl.repo}`)}`));
757
+ }
758
+
759
+ if (result.devEnvironment.packageManager) {
760
+ console.log(chalk.white(` Package Manager: ${chalk.cyan(result.devEnvironment.packageManager)}`));
761
+ }
762
+
763
+ console.log('');
764
+
765
+ return result;
766
+ }
767
+
768
+ export default { detectTechStack, runDetection };