create-qa-architect 5.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/.editorconfig +12 -0
  2. package/.github/CLAUDE_MD_AUTOMATION.md +248 -0
  3. package/.github/PROGRESSIVE_QUALITY_IMPLEMENTATION.md +408 -0
  4. package/.github/PROGRESSIVE_QUALITY_PROPOSAL.md +443 -0
  5. package/.github/RELEASE_CHECKLIST.md +100 -0
  6. package/.github/dependabot.yml +50 -0
  7. package/.github/git-sync.sh +48 -0
  8. package/.github/workflows/claude-md-validation.yml +82 -0
  9. package/.github/workflows/nightly-gitleaks-verification.yml +176 -0
  10. package/.github/workflows/pnpm-ci.yml.example +53 -0
  11. package/.github/workflows/python-ci.yml.example +69 -0
  12. package/.github/workflows/quality-legacy.yml.backup +165 -0
  13. package/.github/workflows/quality-progressive.yml.example +291 -0
  14. package/.github/workflows/quality.yml +436 -0
  15. package/.github/workflows/release.yml +53 -0
  16. package/.nvmrc +1 -0
  17. package/.prettierignore +14 -0
  18. package/.prettierrc +9 -0
  19. package/.stylelintrc.json +5 -0
  20. package/README.md +212 -0
  21. package/config/.lighthouserc.js +45 -0
  22. package/config/.pre-commit-config.yaml +66 -0
  23. package/config/constants.js +128 -0
  24. package/config/defaults.js +124 -0
  25. package/config/pyproject.toml +124 -0
  26. package/config/quality-config.schema.json +97 -0
  27. package/config/quality-python.yml +89 -0
  28. package/config/requirements-dev.txt +15 -0
  29. package/create-saas-monetization.js +1465 -0
  30. package/eslint.config.cjs +117 -0
  31. package/eslint.config.ts.cjs +99 -0
  32. package/legal/README.md +106 -0
  33. package/legal/copyright.md +76 -0
  34. package/legal/disclaimer.md +146 -0
  35. package/legal/privacy-policy.html +324 -0
  36. package/legal/privacy-policy.md +196 -0
  37. package/legal/terms-of-service.md +224 -0
  38. package/lib/billing-dashboard.html +645 -0
  39. package/lib/config-validator.js +163 -0
  40. package/lib/dependency-monitoring-basic.js +185 -0
  41. package/lib/dependency-monitoring-premium.js +1490 -0
  42. package/lib/error-reporter.js +444 -0
  43. package/lib/interactive/prompt.js +128 -0
  44. package/lib/interactive/questions.js +146 -0
  45. package/lib/license-validator.js +403 -0
  46. package/lib/licensing.js +989 -0
  47. package/lib/package-utils.js +187 -0
  48. package/lib/project-maturity.js +516 -0
  49. package/lib/security-enhancements.js +340 -0
  50. package/lib/setup-enhancements.js +317 -0
  51. package/lib/smart-strategy-generator.js +344 -0
  52. package/lib/telemetry.js +323 -0
  53. package/lib/template-loader.js +252 -0
  54. package/lib/typescript-config-generator.js +210 -0
  55. package/lib/ui-helpers.js +74 -0
  56. package/lib/validation/base-validator.js +174 -0
  57. package/lib/validation/cache-manager.js +158 -0
  58. package/lib/validation/config-security.js +741 -0
  59. package/lib/validation/documentation.js +326 -0
  60. package/lib/validation/index.js +186 -0
  61. package/lib/validation/validation-factory.js +153 -0
  62. package/lib/validation/workflow-validation.js +172 -0
  63. package/lib/yaml-utils.js +120 -0
  64. package/marketing/beta-user-email-campaign.md +372 -0
  65. package/marketing/landing-page.html +721 -0
  66. package/package.json +165 -0
  67. package/setup.js +2076 -0
package/setup.js ADDED
@@ -0,0 +1,2076 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs')
4
+ const path = require('path')
5
+ const { execSync } = require('child_process')
6
+ const {
7
+ mergeScripts,
8
+ mergeDevDependencies,
9
+ mergeLintStaged,
10
+ } = require('./lib/package-utils')
11
+ const { showProgress } = require('./lib/ui-helpers')
12
+ const {
13
+ NODE_VERSION,
14
+ SCAN_LIMITS,
15
+ EXCLUDE_DIRECTORIES,
16
+ } = require('./config/constants')
17
+
18
+ /**
19
+ * Check Node version and lazily load @npmcli/package-json
20
+ * This prevents import errors on older Node versions for basic commands like --help
21
+ */
22
+ function checkNodeVersionAndLoadPackageJson() {
23
+ const nodeVersion = process.version
24
+ const majorVersion = parseInt(nodeVersion.split('.')[0].slice(1))
25
+
26
+ if (majorVersion < NODE_VERSION.MIN_MAJOR) {
27
+ console.error(
28
+ `❌ Node.js ${nodeVersion} is not supported. This tool requires Node.js ${NODE_VERSION.MIN_MAJOR} or higher.`
29
+ )
30
+ console.log('Please upgrade Node.js and try again.')
31
+ console.log('Visit https://nodejs.org/ to download the latest version.')
32
+ process.exit(1)
33
+ }
34
+
35
+ try {
36
+ return require('@npmcli/package-json')
37
+ } catch (error) {
38
+ console.error(`❌ Failed to load package.json utilities: ${error.message}`)
39
+ console.log('This tool requires Node.js 20+ with modern module support.')
40
+ process.exit(1)
41
+ }
42
+ }
43
+
44
+ const {
45
+ STYLELINT_EXTENSIONS,
46
+ getDefaultDevDependencies,
47
+ getDefaultLintStaged,
48
+ getDefaultScripts,
49
+ } = require('./config/defaults')
50
+
51
+ // Enhanced validation capabilities
52
+ const { ValidationRunner } = require('./lib/validation')
53
+ const { validateQualityConfig } = require('./lib/config-validator')
54
+
55
+ // Interactive mode capabilities
56
+ const { InteractivePrompt } = require('./lib/interactive/prompt')
57
+ const { runInteractiveFlow } = require('./lib/interactive/questions')
58
+
59
+ // Basic dependency monitoring (Free Tier)
60
+ const {
61
+ hasNpmProject,
62
+ generateBasicDependabotConfig,
63
+ writeBasicDependabotConfig,
64
+ } = require('./lib/dependency-monitoring-basic')
65
+
66
+ // Premium dependency monitoring (Pro/Team/Enterprise Tiers)
67
+ const {
68
+ generatePremiumDependabotConfig,
69
+ writePremiumDependabotConfig,
70
+ } = require('./lib/dependency-monitoring-premium')
71
+
72
+ // Custom template loading
73
+ const { TemplateLoader } = require('./lib/template-loader')
74
+
75
+ // Licensing system
76
+ const {
77
+ getLicenseInfo,
78
+ hasFeature,
79
+ showUpgradeMessage,
80
+ showLicenseStatus,
81
+ checkUsageCaps,
82
+ incrementUsage,
83
+ } = require('./lib/licensing')
84
+
85
+ // Smart Test Strategy Generator (Pro/Team/Enterprise feature)
86
+ const {
87
+ detectProjectType,
88
+ generateSmartStrategy,
89
+ writeSmartStrategy,
90
+ generateSmartPrePushHook,
91
+ getTestTierScripts,
92
+ } = require('./lib/smart-strategy-generator')
93
+
94
+ // Telemetry (opt-in usage tracking)
95
+ const { TelemetrySession, showTelemetryStatus } = require('./lib/telemetry')
96
+
97
+ // Error reporting (opt-in crash analytics)
98
+ const {
99
+ ErrorReporter,
100
+ showErrorReportingStatus,
101
+ } = require('./lib/error-reporter')
102
+
103
+ // Critical setup enhancements (fixes production quality gaps)
104
+ const {
105
+ applyProductionQualityFixes,
106
+ // generateEnhancedPreCommitHook,
107
+ validateProjectSetup,
108
+ } = require('./lib/setup-enhancements')
109
+
110
+ const STYLELINT_EXTENSION_SET = new Set(STYLELINT_EXTENSIONS)
111
+ const STYLELINT_DEFAULT_TARGET = `**/*.{${STYLELINT_EXTENSIONS.join(',')}}`
112
+ const STYLELINT_EXTENSION_GLOB = `*.{${STYLELINT_EXTENSIONS.join(',')}}`
113
+ const STYLELINT_SCAN_EXCLUDES = new Set(EXCLUDE_DIRECTORIES.STYLELINT)
114
+ const MAX_STYLELINT_SCAN_DEPTH = SCAN_LIMITS.STYLELINT_MAX_DEPTH
115
+
116
+ /**
117
+ * Safely reads directory contents without throwing on permission errors
118
+ *
119
+ * Wraps fs.readdirSync with error handling to prevent crashes when
120
+ * encountering permission denied errors or non-existent directories.
121
+ *
122
+ * @param {string} dir - Directory path to read
123
+ * @returns {fs.Dirent[]} Array of directory entries, empty array on error
124
+ *
125
+ * @example
126
+ * const entries = safeReadDir('./src')
127
+ * // Returns: [Dirent { name: 'index.js', ... }, ...]
128
+ * // On error: []
129
+ */
130
+ const safeReadDir = dir => {
131
+ try {
132
+ return fs.readdirSync(dir, { withFileTypes: true })
133
+ } catch {
134
+ return []
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Checks if a filename has a Stylelint-supported extension
140
+ *
141
+ * Validates whether a file should be linted by Stylelint based on
142
+ * its extension. Supports: css, scss, sass, less, pcss
143
+ *
144
+ * @param {string} fileName - Name of the file to check (e.g., 'styles.css')
145
+ * @returns {boolean} True if file has a supported CSS extension
146
+ *
147
+ * @example
148
+ * isStylelintFile('styles.css') // true
149
+ * isStylelintFile('index.js') // false
150
+ * isStylelintFile('theme.scss') // true
151
+ */
152
+ const isStylelintFile = fileName => {
153
+ const ext = path.extname(fileName).slice(1).toLowerCase()
154
+ return STYLELINT_EXTENSION_SET.has(ext)
155
+ }
156
+
157
+ const directoryContainsStylelintFiles = (dir, depth = 0) => {
158
+ if (depth > MAX_STYLELINT_SCAN_DEPTH) {
159
+ return false
160
+ }
161
+
162
+ const entries = safeReadDir(dir)
163
+ for (const entry of entries) {
164
+ if (entry.isSymbolicLink()) {
165
+ continue
166
+ }
167
+
168
+ const entryPath = path.join(dir, entry.name)
169
+
170
+ if (entry.isFile() && isStylelintFile(entry.name)) {
171
+ return true
172
+ }
173
+
174
+ if (entry.isDirectory()) {
175
+ if (STYLELINT_SCAN_EXCLUDES.has(entry.name)) {
176
+ continue
177
+ }
178
+ if (directoryContainsStylelintFiles(entryPath, depth + 1)) {
179
+ return true
180
+ }
181
+ }
182
+ }
183
+
184
+ return false
185
+ }
186
+
187
+ /**
188
+ * Intelligently discovers Stylelint target directories in a project
189
+ *
190
+ * Scans the root directory to find which subdirectories contain CSS/SCSS files
191
+ * and generates optimized glob patterns for Stylelint. Avoids scanning excluded
192
+ * directories like node_modules and skips symbolic links for safety.
193
+ *
194
+ * Algorithm:
195
+ * 1. Scan root directory for CSS files and relevant subdirectories
196
+ * 2. Skip excluded dirs (node_modules, .git, etc.) and symlinks
197
+ * 3. Recursively check subdirs up to MAX_STYLELINT_SCAN_DEPTH
198
+ * 4. Generate specific globs for dirs with CSS files
199
+ * 5. Fall back to default glob if no CSS files found
200
+ *
201
+ * @param {string} rootDir - Root directory to scan
202
+ * @returns {string[]} Array of glob patterns for Stylelint targets
203
+ *
204
+ * @example
205
+ * findStylelintTargets('/project')
206
+ * // Project has CSS in root and src/:
207
+ * // ['**\/*.{css,scss,sass,less,pcss}', 'src/**\/*.{css,scss,sass,less,pcss}']
208
+ *
209
+ * @example
210
+ * findStylelintTargets('/empty-project')
211
+ * // No CSS files found:
212
+ * // ['**\/*.{css,scss,sass,less,pcss}'] (default fallback)
213
+ */
214
+ const findStylelintTargets = rootDir => {
215
+ const entries = safeReadDir(rootDir)
216
+ const targets = new Set()
217
+ let hasRootCss = false
218
+
219
+ for (const entry of entries) {
220
+ if (entry.isSymbolicLink()) {
221
+ continue
222
+ }
223
+
224
+ const entryPath = path.join(rootDir, entry.name)
225
+
226
+ if (entry.isFile()) {
227
+ if (isStylelintFile(entry.name)) {
228
+ hasRootCss = true
229
+ }
230
+ continue
231
+ }
232
+
233
+ if (!entry.isDirectory()) {
234
+ continue
235
+ }
236
+
237
+ if (STYLELINT_SCAN_EXCLUDES.has(entry.name)) {
238
+ continue
239
+ }
240
+
241
+ if (directoryContainsStylelintFiles(entryPath)) {
242
+ targets.add(entry.name)
243
+ }
244
+ }
245
+
246
+ const resolvedTargets = []
247
+
248
+ if (hasRootCss) {
249
+ resolvedTargets.push(STYLELINT_EXTENSION_GLOB)
250
+ }
251
+
252
+ Array.from(targets)
253
+ .sort()
254
+ .forEach(dir => {
255
+ resolvedTargets.push(`${dir}/**/${STYLELINT_EXTENSION_GLOB}`)
256
+ })
257
+
258
+ if (!resolvedTargets.length) {
259
+ return [STYLELINT_DEFAULT_TARGET]
260
+ }
261
+
262
+ return resolvedTargets
263
+ }
264
+
265
+ const patternIncludesStylelintExtension = pattern => {
266
+ const lower = pattern.toLowerCase()
267
+ return STYLELINT_EXTENSIONS.some(ext => lower.includes(`.${ext}`))
268
+ }
269
+
270
+ // Input validation and sanitization functions from WFHroulette patterns
271
+ const validateAndSanitizeInput = input => {
272
+ if (typeof input !== 'string') {
273
+ throw new Error('Input must be a string')
274
+ }
275
+ // Normalize and trim input
276
+ const normalized = input.trim()
277
+ if (normalized.length === 0) {
278
+ return null
279
+ }
280
+ // Basic sanitization - remove potentially dangerous characters
281
+ const sanitized = normalized.replace(/[<>'"&]/g, '')
282
+ return sanitized
283
+ }
284
+
285
+ /**
286
+ * Parse CLI arguments and return configuration object
287
+ * @param {string[]} rawArgs - Raw command line arguments
288
+ * @returns {Object} Parsed configuration
289
+ */
290
+ function parseArguments(rawArgs) {
291
+ const sanitizedArgs = rawArgs
292
+ .map(arg => validateAndSanitizeInput(arg))
293
+ .filter(Boolean)
294
+
295
+ // Interactive mode detection - to be handled at execution time
296
+ const isInteractiveRequested = sanitizedArgs.includes('--interactive')
297
+
298
+ const isUpdateMode = sanitizedArgs.includes('--update')
299
+ const isValidationMode = sanitizedArgs.includes('--validate')
300
+ const isConfigSecurityMode = sanitizedArgs.includes('--security-config')
301
+ const isDocsValidationMode = sanitizedArgs.includes('--validate-docs')
302
+ const isComprehensiveMode = sanitizedArgs.includes('--comprehensive')
303
+ const isDependencyMonitoringMode =
304
+ sanitizedArgs.includes('--deps') ||
305
+ sanitizedArgs.includes('--dependency-monitoring')
306
+ const isLicenseStatusMode = sanitizedArgs.includes('--license-status')
307
+ const isTelemetryStatusMode = sanitizedArgs.includes('--telemetry-status')
308
+ const isErrorReportingStatusMode = sanitizedArgs.includes(
309
+ '--error-reporting-status'
310
+ )
311
+ const isCheckMaturityMode = sanitizedArgs.includes('--check-maturity')
312
+ const isValidateConfigMode = sanitizedArgs.includes('--validate-config')
313
+ const isActivateLicenseMode = sanitizedArgs.includes('--activate-license')
314
+ const isDryRun = sanitizedArgs.includes('--dry-run')
315
+
316
+ // Custom template directory - use raw args to preserve valid path characters (&, <, >, etc.)
317
+ // Normalize path to prevent traversal attacks and make absolute
318
+ const templateFlagIndex = sanitizedArgs.findIndex(arg => arg === '--template')
319
+ const customTemplatePath =
320
+ templateFlagIndex !== -1 && rawArgs[templateFlagIndex + 1]
321
+ ? path.resolve(rawArgs[templateFlagIndex + 1])
322
+ : null
323
+
324
+ // Granular tool disable options
325
+ const disableNpmAudit = sanitizedArgs.includes('--no-npm-audit')
326
+ const disableGitleaks = sanitizedArgs.includes('--no-gitleaks')
327
+ const disableActionlint = sanitizedArgs.includes('--no-actionlint')
328
+ const disableMarkdownlint = sanitizedArgs.includes('--no-markdownlint')
329
+ const disableEslintSecurity = sanitizedArgs.includes('--no-eslint-security')
330
+
331
+ return {
332
+ sanitizedArgs,
333
+ isInteractiveRequested,
334
+ isUpdateMode,
335
+ isValidationMode,
336
+ isConfigSecurityMode,
337
+ isDocsValidationMode,
338
+ isComprehensiveMode,
339
+ isDependencyMonitoringMode,
340
+ isLicenseStatusMode,
341
+ isTelemetryStatusMode,
342
+ isErrorReportingStatusMode,
343
+ isCheckMaturityMode,
344
+ isValidateConfigMode,
345
+ isActivateLicenseMode,
346
+ isDryRun,
347
+ customTemplatePath,
348
+ disableNpmAudit,
349
+ disableGitleaks,
350
+ disableActionlint,
351
+ disableMarkdownlint,
352
+ disableEslintSecurity,
353
+ }
354
+ }
355
+
356
+ /**
357
+ * Main entry point - wraps everything in async context for interactive mode
358
+ */
359
+ ;(async function main() {
360
+ // Initial argument parsing
361
+ const args = process.argv.slice(2)
362
+ let parsedConfig = parseArguments(args)
363
+
364
+ // Destructure for backward compatibility
365
+ let {
366
+ sanitizedArgs,
367
+ isInteractiveRequested,
368
+ isUpdateMode,
369
+ isValidationMode,
370
+ isConfigSecurityMode,
371
+ isDocsValidationMode,
372
+ isComprehensiveMode,
373
+ isDependencyMonitoringMode,
374
+ isLicenseStatusMode,
375
+ isTelemetryStatusMode,
376
+ isErrorReportingStatusMode,
377
+ isCheckMaturityMode,
378
+ isValidateConfigMode,
379
+ isActivateLicenseMode,
380
+ isDryRun,
381
+ customTemplatePath,
382
+ disableNpmAudit,
383
+ disableGitleaks,
384
+ disableActionlint,
385
+ disableMarkdownlint,
386
+ disableEslintSecurity,
387
+ } = parsedConfig
388
+
389
+ // Initialize telemetry session (opt-in only, fails silently)
390
+ const telemetry = new TelemetrySession()
391
+
392
+ // Handle interactive mode FIRST (before any routing)
393
+ // This must happen before help/dry-run/routing to ensure interactive selections drive behavior
394
+ if (isInteractiveRequested) {
395
+ const prompt = new InteractivePrompt()
396
+
397
+ // Check TTY availability
398
+ if (!prompt.isTTY()) {
399
+ console.error(
400
+ '❌ Interactive mode requires a TTY environment (interactive terminal).'
401
+ )
402
+ console.error(
403
+ ' For non-interactive use, please specify flags directly.'
404
+ )
405
+ console.error(' Run with --help to see available options.\n')
406
+ process.exit(1)
407
+ }
408
+
409
+ // Run interactive flow
410
+ let interactiveFlags
411
+ try {
412
+ interactiveFlags = await runInteractiveFlow(prompt)
413
+ console.log(
414
+ `\n🚀 Running setup with options: ${interactiveFlags.join(' ')}\n`
415
+ )
416
+ } catch (error) {
417
+ if (error.message.includes('cancelled')) {
418
+ console.log('\n❌ Interactive mode cancelled\n')
419
+ process.exit(0)
420
+ }
421
+ console.error(`❌ Interactive mode error: ${error.message}\n`)
422
+ process.exit(1)
423
+ }
424
+
425
+ // Merge interactive flags with original command-line args
426
+ // Remove --interactive flag, keep all other original flags (like --template)
427
+ const originalFlags = args.filter(arg => arg !== '--interactive')
428
+ const mergedFlags = [...originalFlags, ...interactiveFlags]
429
+
430
+ // Re-parse with merged flags
431
+ parsedConfig = parseArguments(mergedFlags)
432
+
433
+ // Update all configuration variables
434
+ ;({
435
+ sanitizedArgs,
436
+ isInteractiveRequested, // Will be false after re-parse since we filtered it out
437
+ isUpdateMode,
438
+ isValidationMode,
439
+ isConfigSecurityMode,
440
+ isDocsValidationMode,
441
+ isComprehensiveMode,
442
+ isDependencyMonitoringMode,
443
+ isLicenseStatusMode,
444
+ isTelemetryStatusMode,
445
+ isErrorReportingStatusMode,
446
+ isCheckMaturityMode,
447
+ isDryRun,
448
+ customTemplatePath,
449
+ disableNpmAudit,
450
+ disableGitleaks,
451
+ disableActionlint,
452
+ disableMarkdownlint,
453
+ disableEslintSecurity,
454
+ } = parsedConfig)
455
+
456
+ console.log('📋 Configuration after interactive selections applied\n')
457
+ }
458
+
459
+ // Show telemetry status if requested
460
+ if (isTelemetryStatusMode) {
461
+ showTelemetryStatus()
462
+ process.exit(0)
463
+ }
464
+
465
+ // Show error reporting status if requested
466
+ if (isErrorReportingStatusMode) {
467
+ showErrorReportingStatus()
468
+ process.exit(0)
469
+ }
470
+
471
+ // Show help if requested
472
+ if (sanitizedArgs.includes('--help') || sanitizedArgs.includes('-h')) {
473
+ console.log(`
474
+ 🚀 Create Quality Automation Setup
475
+
476
+ Usage: npx create-qa-architect@latest [options]
477
+
478
+ SETUP OPTIONS:
479
+ (no args) Run complete quality automation setup
480
+ --interactive Interactive mode with guided configuration prompts
481
+ --update Update existing configuration
482
+ --deps Add basic dependency monitoring (Free Tier)
483
+ --dependency-monitoring Same as --deps
484
+ --template <path> Use custom templates from specified directory
485
+ --dry-run Preview changes without modifying files
486
+
487
+ VALIDATION OPTIONS:
488
+ --validate Run comprehensive validation (same as --comprehensive)
489
+ --comprehensive Run all validation checks
490
+ --security-config Run configuration security checks only
491
+ --validate-docs Run documentation validation only
492
+ --validate-config Validate .qualityrc.json configuration file
493
+ --check-maturity Detect and display project maturity level
494
+
495
+ LICENSE, TELEMETRY & ERROR REPORTING:
496
+ --license-status Show current license tier and available features
497
+ --activate-license Activate Pro/Team/Enterprise license key from Stripe purchase
498
+ --telemetry-status Show telemetry status and opt-in instructions
499
+ --error-reporting-status Show error reporting status and privacy information
500
+
501
+ GRANULAR TOOL CONTROL:
502
+ --no-npm-audit Disable npm audit dependency vulnerability checks
503
+ --no-gitleaks Disable gitleaks secret scanning
504
+ --allow-latest-gitleaks Allow unpinned latest gitleaks (NOT RECOMMENDED - supply chain risk)
505
+ --no-actionlint Disable actionlint GitHub Actions workflow validation
506
+ --no-markdownlint Disable markdownlint markdown formatting checks
507
+ --no-eslint-security Disable ESLint security rule checking
508
+
509
+ EXAMPLES:
510
+ npx create-qa-architect@latest
511
+ → Set up quality automation with all tools
512
+
513
+ npx create-qa-architect@latest --deps
514
+ → Add basic dependency monitoring (Dependabot config + weekly updates + GitHub Actions)
515
+
516
+ npx create-qa-architect@latest --license-status
517
+ → Show current license tier and upgrade options
518
+
519
+ npx create-qa-architect@latest --activate-license
520
+ → Activate Pro/Team/Enterprise license after Stripe purchase
521
+
522
+ npx create-qa-architect@latest --telemetry-status
523
+ → Show telemetry status and privacy information
524
+
525
+ npx create-qa-architect@latest --error-reporting-status
526
+ → Show error reporting status and crash analytics information
527
+
528
+ npx create-qa-architect@latest --check-maturity
529
+ → Detect project maturity level (minimal, bootstrap, development, production-ready)
530
+
531
+ npx create-qa-architect@latest --validate-config
532
+ → Validate .qualityrc.json configuration file against JSON Schema
533
+
534
+ npx create-qa-architect@latest --comprehensive --no-gitleaks
535
+ → Run validation but skip gitleaks secret scanning
536
+
537
+ npx create-qa-architect@latest --security-config --allow-latest-gitleaks
538
+ → Run security checks with unpinned gitleaks (NOT RECOMMENDED - supply chain risk)
539
+
540
+ npx create-qa-architect@latest --security-config --no-npm-audit
541
+ → Run security checks but skip npm audit
542
+
543
+ npx create-qa-architect@latest --dry-run
544
+ → Preview what files and configurations would be created/modified
545
+
546
+ PRIVACY & TELEMETRY:
547
+ Telemetry and error reporting are OPT-IN only (disabled by default). To enable:
548
+ export QAA_TELEMETRY=true # Usage tracking (local only)
549
+ export QAA_ERROR_REPORTING=true # Crash analytics (local only)
550
+ All data stays local (~/.create-qa-architect/)
551
+ No personal information collected. Run --telemetry-status or
552
+ --error-reporting-status for details.
553
+
554
+ HELP:
555
+ --help, -h Show this help message
556
+ `)
557
+ process.exit(0)
558
+ }
559
+
560
+ console.log(
561
+ `🚀 ${isDryRun ? '[DRY RUN] Previewing' : isUpdateMode ? 'Updating' : isDependencyMonitoringMode ? 'Adding dependency monitoring to' : 'Setting up'} Quality Automation...\n`
562
+ )
563
+
564
+ // Handle dry-run mode - preview what would be changed
565
+ if (isDryRun) {
566
+ console.log('📋 DRY RUN MODE - No files will be modified\n')
567
+ console.log('The following files would be created/modified:\n')
568
+ console.log('Configuration Files:')
569
+ console.log(' • .prettierrc - Prettier formatting configuration')
570
+ console.log(' • .prettierignore - Files to exclude from formatting')
571
+ console.log(' • eslint.config.cjs - ESLint linting configuration')
572
+ console.log(' • .stylelintrc.json - Stylelint CSS linting configuration')
573
+ console.log(
574
+ ' • .editorconfig - Editor configuration for consistent formatting'
575
+ )
576
+ console.log(' • .nvmrc - Node version specification')
577
+ console.log(' • .npmrc - npm configuration with engine-strict')
578
+ console.log('')
579
+ console.log('Git Hooks (Husky):')
580
+ console.log(' • .husky/pre-commit - Pre-commit hook for lint-staged')
581
+ console.log(
582
+ ' • .husky/pre-push - Pre-push validation (lint, format, tests)'
583
+ )
584
+ console.log('')
585
+ console.log('GitHub Actions:')
586
+ console.log(' • .github/workflows/quality.yml - Quality checks workflow')
587
+ console.log('')
588
+ console.log('Package.json Modifications:')
589
+ console.log(
590
+ ' • Add devDependencies: eslint, prettier, stylelint, husky, lint-staged'
591
+ )
592
+ console.log(' • Add scripts: format, lint, prepare')
593
+ console.log(' • Add lint-staged configuration')
594
+ console.log(' • Add engines requirement (Node >=20)')
595
+ console.log('')
596
+ console.log('✅ Dry run complete - no files were modified')
597
+ console.log('')
598
+ console.log('To apply these changes, run without --dry-run flag:')
599
+ console.log(' npx create-qa-architect@latest')
600
+ process.exit(0)
601
+ }
602
+
603
+ // Handle validation-only commands
604
+ async function handleValidationCommands() {
605
+ const validationOptions = {
606
+ disableNpmAudit,
607
+ disableGitleaks,
608
+ disableActionlint,
609
+ disableMarkdownlint,
610
+ disableEslintSecurity,
611
+ }
612
+ const validator = new ValidationRunner(validationOptions)
613
+
614
+ if (isConfigSecurityMode) {
615
+ try {
616
+ await validator.runConfigSecurity()
617
+ process.exit(0)
618
+ } catch (error) {
619
+ console.error(
620
+ `\n❌ Configuration security validation failed:\n${error.message}`
621
+ )
622
+ process.exit(1)
623
+ }
624
+ }
625
+
626
+ if (isDocsValidationMode) {
627
+ try {
628
+ await validator.runDocumentationValidation()
629
+ process.exit(0)
630
+ } catch (error) {
631
+ console.error(`\n❌ Documentation validation failed:\n${error.message}`)
632
+ process.exit(1)
633
+ }
634
+ }
635
+
636
+ if (isComprehensiveMode || isValidationMode) {
637
+ try {
638
+ // Use parallel validation for 3-5x speedup (runs checks concurrently)
639
+ await validator.runComprehensiveCheckParallel()
640
+ process.exit(0)
641
+ } catch (error) {
642
+ console.error(`\n❌ Comprehensive validation failed:\n${error.message}`)
643
+ process.exit(1)
644
+ }
645
+ }
646
+ }
647
+
648
+ // Detect Python project
649
+ function detectPythonProject(projectPath) {
650
+ const pythonFiles = [
651
+ 'pyproject.toml',
652
+ 'requirements.txt',
653
+ 'setup.py',
654
+ 'Pipfile',
655
+ ]
656
+ return pythonFiles.some(file => fs.existsSync(path.join(projectPath, file)))
657
+ }
658
+
659
+ // Detect Rust project
660
+ function detectRustProject(projectPath) {
661
+ return fs.existsSync(path.join(projectPath, 'Cargo.toml'))
662
+ }
663
+
664
+ // Detect Ruby project
665
+ function detectRubyProject(projectPath) {
666
+ return fs.existsSync(path.join(projectPath, 'Gemfile'))
667
+ }
668
+
669
+ // Handle dependency monitoring (Free/Pro/Team/Enterprise)
670
+ async function handleDependencyMonitoring() {
671
+ const projectPath = process.cwd()
672
+ const license = getLicenseInfo()
673
+
674
+ // Detect all supported ecosystems (npm, Python, Ruby, Rust, etc.)
675
+ const hasNpm = hasNpmProject(projectPath)
676
+ const hasPython = detectPythonProject(projectPath)
677
+ const hasRust = detectRustProject(projectPath)
678
+ const hasRuby = detectRubyProject(projectPath)
679
+
680
+ if (!hasNpm && !hasPython && !hasRust && !hasRuby) {
681
+ console.error(
682
+ '❌ No supported dependency file found (package.json, pyproject.toml, requirements.txt, Gemfile, Cargo.toml).'
683
+ )
684
+ console.log("💡 Make sure you're in a directory with dependency files.")
685
+ process.exit(1)
686
+ }
687
+
688
+ if (hasNpm) console.log('📦 Detected: npm project')
689
+ if (hasPython) console.log('🐍 Detected: Python project')
690
+ if (hasRust) console.log('🦀 Detected: Rust project')
691
+ if (hasRuby) console.log('💎 Detected: Ruby project')
692
+ console.log(`📋 License tier: ${license.tier.toUpperCase()}`)
693
+
694
+ // Enforce Free tier caps for dependency monitoring (counted as dependency PRs)
695
+ if (license.tier === 'FREE') {
696
+ const capCheck = checkUsageCaps('dependency-pr')
697
+ if (!capCheck.allowed) {
698
+ console.error(`❌ ${capCheck.reason}`)
699
+ console.error(
700
+ ' Upgrade to Pro, Team, or Enterprise for unlimited runs: https://vibebuildlab.com/cqa'
701
+ )
702
+ process.exit(1)
703
+ }
704
+
705
+ const increment = incrementUsage('dependency-pr')
706
+ const usage = increment.usage || capCheck.usage
707
+ const caps = capCheck.caps
708
+ if (usage && caps && caps.maxDependencyPRsPerMonth !== undefined) {
709
+ console.log(
710
+ `🧮 Usage: ${usage.dependencyPRs}/${caps.maxDependencyPRsPerMonth} dependency monitoring runs used this month`
711
+ )
712
+ }
713
+ }
714
+
715
+ const dependabotPath = path.join(projectPath, '.github', 'dependabot.yml')
716
+
717
+ // Use premium or basic config based on license tier
718
+ // Free beta ended with v4.1.1 - Premium features now require Pro/Team/Enterprise tier
719
+ // Pro/Team/Enterprise: Framework-aware dependency monitoring with grouping
720
+ // Free: Basic npm-only dependency monitoring
721
+ const shouldUsePremium =
722
+ license.tier === 'PRO' ||
723
+ license.tier === 'TEAM' ||
724
+ license.tier === 'ENTERPRISE'
725
+
726
+ // Free tier only supports npm projects. Fail fast with a clear message.
727
+ if (!shouldUsePremium && !hasNpm && (hasPython || hasRust || hasRuby)) {
728
+ console.error(
729
+ '❌ Dependency monitoring for this project requires a Pro, Team, or Enterprise license.'
730
+ )
731
+ console.error(
732
+ ' Free tier supports npm projects only. Detected non-npm ecosystems.'
733
+ )
734
+ console.error(
735
+ ' Options: add npm/package.json, or upgrade and re-run: npx create-qa-architect@latest --deps after activation.'
736
+ )
737
+ process.exit(1)
738
+ }
739
+
740
+ if (shouldUsePremium) {
741
+ console.log(
742
+ '\n🚀 Setting up framework-aware dependency monitoring (Premium)...\n'
743
+ )
744
+
745
+ const configData = generatePremiumDependabotConfig({
746
+ projectPath,
747
+ schedule: 'weekly',
748
+ })
749
+
750
+ if (configData) {
751
+ const { ecosystems } = configData
752
+ const ecosystemNames = Object.keys(ecosystems)
753
+
754
+ if (ecosystemNames.length > 0) {
755
+ console.log('🔍 Detected ecosystems:')
756
+
757
+ // Find primary ecosystem and total package count
758
+ let primaryEcosystem = null
759
+ ecosystemNames.forEach(ecoName => {
760
+ const eco = ecosystems[ecoName]
761
+ const frameworks = Object.keys(eco.detected || {})
762
+ const totalPackages = frameworks.reduce((sum, fw) => {
763
+ return sum + (eco.detected[fw]?.count || 0)
764
+ }, 0)
765
+
766
+ console.log(` • ${ecoName}: ${totalPackages} packages`)
767
+
768
+ if (eco.primary) {
769
+ primaryEcosystem = ecoName
770
+ }
771
+ })
772
+
773
+ if (primaryEcosystem) {
774
+ console.log(`\n🎯 Primary ecosystem: ${primaryEcosystem}`)
775
+ }
776
+ }
777
+
778
+ writePremiumDependabotConfig(configData, dependabotPath)
779
+ console.log(
780
+ '\n✅ Created .github/dependabot.yml with framework grouping'
781
+ )
782
+
783
+ console.log('\n🎉 Premium dependency monitoring setup complete!')
784
+ console.log('\n📋 What was added (Pro Tier):')
785
+ console.log(' • Framework-aware dependency grouping')
786
+ console.log(
787
+ ` • ${Object.keys(configData.config.updates[0].groups || {}).length} dependency groups created`
788
+ )
789
+ console.log(' • Intelligent update batching (reduces PRs by 60%+)')
790
+ console.log(' • GitHub Actions dependency monitoring')
791
+ }
792
+ } else {
793
+ console.log(
794
+ '\n🔍 Setting up basic dependency monitoring (Free Tier)...\n'
795
+ )
796
+
797
+ const dependabotConfig = generateBasicDependabotConfig({
798
+ projectPath,
799
+ schedule: 'weekly',
800
+ })
801
+
802
+ if (dependabotConfig) {
803
+ writeBasicDependabotConfig(dependabotConfig, dependabotPath)
804
+ console.log('✅ Created .github/dependabot.yml')
805
+ }
806
+
807
+ console.log('\n🎉 Basic dependency monitoring setup complete!')
808
+ console.log('\n📋 What was added (Free Tier):')
809
+ console.log(' • Basic Dependabot configuration for npm packages')
810
+ console.log(' • Weekly dependency updates on Monday 9am')
811
+ console.log(' • GitHub Actions dependency monitoring')
812
+
813
+ // Show upgrade message for premium features
814
+ console.log('\n🔒 Premium features now available:')
815
+ console.log(
816
+ ' ✅ Framework-aware package grouping (React, Vue, Angular)'
817
+ )
818
+ console.log(' • Coming soon: Multi-language support (Python, Rust, Go)')
819
+ console.log(' • Planned: Advanced security audit workflows')
820
+ console.log(' • Planned: Custom update schedules and notifications')
821
+
822
+ showUpgradeMessage('Framework-Aware Dependency Grouping')
823
+ }
824
+
825
+ console.log('\n💡 Next steps:')
826
+ console.log(' • Review and commit .github/dependabot.yml')
827
+ console.log(' • Enable Dependabot alerts in GitHub repository settings')
828
+ console.log(
829
+ ' • Dependabot will start monitoring weekly for dependency updates'
830
+ )
831
+ }
832
+
833
+ // Handle license status command
834
+ if (isLicenseStatusMode) {
835
+ showLicenseStatus()
836
+ process.exit(0)
837
+ }
838
+
839
+ // Handle license activation command
840
+ if (isActivateLicenseMode) {
841
+ const { promptLicenseActivation } = require('./lib/licensing')
842
+
843
+ console.log('🔑 Create Quality Automation - License Activation')
844
+ console.log('════════════════════════════════════════════════')
845
+
846
+ try {
847
+ const result = await promptLicenseActivation()
848
+
849
+ if (result.success) {
850
+ console.log('\n🎉 Success! Premium features are now available.')
851
+ console.log('\nNext steps:')
852
+ console.log('• Run: npx create-qa-architect@latest --deps')
853
+ console.log('• Enable framework-aware dependency grouping')
854
+ console.log('• Enjoy 60%+ reduction in dependency PRs!')
855
+ } else {
856
+ console.log('\n❌ License activation failed.')
857
+ console.log('• Check your license key format (QAA-XXXX-XXXX-XXXX-XXXX)')
858
+ console.log('• Verify your email address')
859
+ console.log('• Contact support: hello@aibuilderlab.com')
860
+ }
861
+ } catch (error) {
862
+ console.error('\n❌ License activation error:', error.message)
863
+ console.log('Contact support for assistance: hello@aibuilderlab.com')
864
+ }
865
+
866
+ process.exit(0)
867
+ }
868
+
869
+ // Handle check maturity command
870
+ if (isCheckMaturityMode) {
871
+ const { ProjectMaturityDetector } = require('./lib/project-maturity')
872
+ const detector = new ProjectMaturityDetector({
873
+ projectPath: process.cwd(),
874
+ verbose: true,
875
+ })
876
+ detector.printReport()
877
+ process.exit(0)
878
+ }
879
+
880
+ // Handle validate config command
881
+ if (isValidateConfigMode) {
882
+ const { validateAndReport } = require('./lib/config-validator')
883
+ const configPath = path.join(process.cwd(), '.qualityrc.json')
884
+ const isValid = validateAndReport(configPath)
885
+ process.exit(isValid ? 0 : 1)
886
+ }
887
+
888
+ // Handle dependency monitoring command
889
+ if (isDependencyMonitoringMode) {
890
+ return (async () => {
891
+ try {
892
+ await handleDependencyMonitoring()
893
+ process.exit(0)
894
+ } catch (error) {
895
+ console.error('Dependency monitoring setup error:', error.message)
896
+ process.exit(1)
897
+ }
898
+ })()
899
+ }
900
+
901
+ // Run validation commands if requested
902
+ if (
903
+ isValidationMode ||
904
+ isConfigSecurityMode ||
905
+ isDocsValidationMode ||
906
+ isComprehensiveMode
907
+ ) {
908
+ // Handle validation commands and exit
909
+ return (async () => {
910
+ try {
911
+ await handleValidationCommands()
912
+ } catch (error) {
913
+ console.error('Validation error:', error.message)
914
+ process.exit(1)
915
+ }
916
+ })()
917
+ } else {
918
+ // Normal setup flow
919
+ async function runMainSetup() {
920
+ // Record telemetry start event (opt-in only, fails silently)
921
+ telemetry.recordStart({
922
+ mode: isDryRun ? 'dry-run' : isUpdateMode ? 'update' : 'setup',
923
+ hasCustomTemplate: !!customTemplatePath,
924
+ isInteractive: false, // Already handled at this point
925
+ })
926
+
927
+ // Check if we're in a git repository
928
+ const gitSpinner = showProgress('Checking git repository...')
929
+ try {
930
+ execSync('git status', { stdio: 'ignore' })
931
+ gitSpinner.succeed('Git repository verified')
932
+ } catch {
933
+ gitSpinner.fail('Not a git repository')
934
+ console.error('❌ This must be run in a git repository')
935
+ console.log('Run "git init" first, then try again.')
936
+ process.exit(1)
937
+ }
938
+
939
+ // Enforce FREE tier repo limit (1 private repo)
940
+ // Must happen before any file modifications
941
+ const license = getLicenseInfo()
942
+ if (license.tier === 'FREE') {
943
+ // Generate unique repo ID from git remote or directory name
944
+ let repoId
945
+ try {
946
+ const remoteUrl = execSync('git remote get-url origin', {
947
+ encoding: 'utf8',
948
+ stdio: ['pipe', 'pipe', 'ignore'],
949
+ }).trim()
950
+ repoId = remoteUrl
951
+ } catch {
952
+ // No remote - use absolute path as fallback
953
+ repoId = process.cwd()
954
+ }
955
+
956
+ const repoCheck = checkUsageCaps('repo')
957
+ const currentRepos = repoCheck.usage?.repos || []
958
+
959
+ // Only enforce if this is a NEW repo (not already tracked)
960
+ if (!currentRepos.includes(repoId)) {
961
+ if (!repoCheck.allowed) {
962
+ console.error(`\n❌ ${repoCheck.reason}`)
963
+ console.error(
964
+ ' Upgrade to Pro for unlimited repos: https://vibebuildlab.com/cqa'
965
+ )
966
+ process.exit(1)
967
+ }
968
+
969
+ // Register this repo
970
+ incrementUsage('repo', 1, repoId)
971
+ console.log(
972
+ `✅ Registered repo (FREE tier: ${currentRepos.length + 1}/1 repos used)`
973
+ )
974
+ }
975
+ }
976
+
977
+ // Validate custom template path BEFORE any mutations
978
+ if (customTemplatePath) {
979
+ if (!fs.existsSync(customTemplatePath)) {
980
+ console.error(
981
+ `❌ Custom template path does not exist: ${customTemplatePath}`
982
+ )
983
+ console.error(
984
+ '\nWhen using --template, the path must exist and be a valid directory.'
985
+ )
986
+ console.error('Please check the path and try again.\n')
987
+ process.exit(1)
988
+ }
989
+
990
+ const stats = fs.statSync(customTemplatePath)
991
+ if (!stats.isDirectory()) {
992
+ console.error(
993
+ `❌ Custom template path is not a directory: ${customTemplatePath}`
994
+ )
995
+ console.error(
996
+ '\nThe --template path must be a directory containing template files.'
997
+ )
998
+ console.error('Please provide a valid directory path.\n')
999
+ process.exit(1)
1000
+ }
1001
+
1002
+ console.log(`✅ Custom template path validated: ${customTemplatePath}`)
1003
+ }
1004
+
1005
+ // Check if package.json exists with validation
1006
+ const packageJsonPath = path.join(process.cwd(), 'package.json')
1007
+ let packageJson = {}
1008
+
1009
+ if (fs.existsSync(packageJsonPath)) {
1010
+ try {
1011
+ const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8')
1012
+ // Validate JSON content before parsing
1013
+ if (packageJsonContent.trim().length === 0) {
1014
+ console.error('❌ package.json is empty')
1015
+ console.log(
1016
+ 'Please add valid JSON content to package.json and try again.'
1017
+ )
1018
+ process.exit(1)
1019
+ }
1020
+
1021
+ packageJson = JSON.parse(packageJsonContent)
1022
+
1023
+ // Validate package.json structure
1024
+ if (typeof packageJson !== 'object' || packageJson === null) {
1025
+ console.error('❌ package.json must contain a valid JSON object')
1026
+ console.log('Please fix the package.json structure and try again.')
1027
+ process.exit(1)
1028
+ }
1029
+
1030
+ // Sanitize package name if present
1031
+ if (packageJson.name && typeof packageJson.name === 'string') {
1032
+ packageJson.name =
1033
+ validateAndSanitizeInput(packageJson.name) || 'my-project'
1034
+ }
1035
+
1036
+ console.log('✅ Found existing package.json')
1037
+ } catch (error) {
1038
+ console.error(`❌ Error parsing package.json: ${error.message}`)
1039
+ console.log(
1040
+ 'Please fix the JSON syntax in package.json and try again.'
1041
+ )
1042
+ console.log(
1043
+ 'Common issues: trailing commas, missing quotes, unclosed brackets'
1044
+ )
1045
+ process.exit(1)
1046
+ }
1047
+ } else {
1048
+ console.log('📦 Creating new package.json')
1049
+ const projectName =
1050
+ validateAndSanitizeInput(path.basename(process.cwd())) || 'my-project'
1051
+ packageJson = {
1052
+ name: projectName,
1053
+ version: '1.0.0',
1054
+ description: '',
1055
+ main: 'index.js',
1056
+ scripts: {},
1057
+ }
1058
+ }
1059
+
1060
+ const hasTypeScriptDependency = Boolean(
1061
+ (packageJson.devDependencies &&
1062
+ packageJson.devDependencies.typescript) ||
1063
+ (packageJson.dependencies && packageJson.dependencies.typescript)
1064
+ )
1065
+
1066
+ const tsconfigCandidates = ['tsconfig.json', 'tsconfig.base.json']
1067
+ const hasTypeScriptConfig = tsconfigCandidates.some(file =>
1068
+ fs.existsSync(path.join(process.cwd(), file))
1069
+ )
1070
+
1071
+ const usesTypeScript = Boolean(
1072
+ hasTypeScriptDependency || hasTypeScriptConfig
1073
+ )
1074
+ if (usesTypeScript) {
1075
+ console.log(
1076
+ '🔍 Detected TypeScript configuration; enabling TypeScript lint defaults'
1077
+ )
1078
+ }
1079
+
1080
+ // Python detection (including in workspace packages for monorepos)
1081
+ const pythonCandidates = [
1082
+ 'pyproject.toml',
1083
+ 'setup.py',
1084
+ 'requirements.txt',
1085
+ 'poetry.lock',
1086
+ ]
1087
+ const hasPythonConfig = pythonCandidates.some(file =>
1088
+ fs.existsSync(path.join(process.cwd(), file))
1089
+ )
1090
+
1091
+ /**
1092
+ * Recursively check for Python files in directory and subdirectories
1093
+ * Limited to 2 levels deep to avoid performance issues in large monorepos
1094
+ */
1095
+ function hasPythonFilesRecursive(dir, depth = 0, maxDepth = 2) {
1096
+ if (depth > maxDepth) return false
1097
+
1098
+ try {
1099
+ const entries = safeReadDir(dir)
1100
+
1101
+ // Count .py files in current directory (excluding __pycache__)
1102
+ const pyFiles = entries.filter(
1103
+ dirent =>
1104
+ dirent.isFile() &&
1105
+ dirent.name.endsWith('.py') &&
1106
+ dirent.name !== '__pycache__'
1107
+ )
1108
+
1109
+ // Strong indicators: multiple .py files OR main/app/run patterns
1110
+ const hasMultiplePyFiles = pyFiles.length >= 2
1111
+ const hasMainPattern = pyFiles.some(
1112
+ f =>
1113
+ f.name === 'main.py' ||
1114
+ f.name === 'app.py' ||
1115
+ f.name === 'run.py' ||
1116
+ f.name === '__main__.py'
1117
+ )
1118
+
1119
+ // Require stronger evidence than a single random .py file
1120
+ if (hasMultiplePyFiles || hasMainPattern) return true
1121
+
1122
+ // Check subdirectories (skip node_modules, .git, etc.)
1123
+ const skipDirs = ['node_modules', '.git', 'dist', 'build', 'coverage']
1124
+ for (const dirent of entries) {
1125
+ if (dirent.isDirectory() && !skipDirs.includes(dirent.name)) {
1126
+ const subDir = path.join(dir, dirent.name)
1127
+ if (hasPythonFilesRecursive(subDir, depth + 1, maxDepth)) {
1128
+ return true
1129
+ }
1130
+ }
1131
+ }
1132
+
1133
+ return false
1134
+ } catch {
1135
+ return false
1136
+ }
1137
+ }
1138
+
1139
+ const hasPythonFiles = hasPythonFilesRecursive(process.cwd())
1140
+
1141
+ const usesPython = Boolean(hasPythonConfig || hasPythonFiles)
1142
+ if (usesPython) {
1143
+ console.log(
1144
+ '🐍 Detected Python project; enabling Python quality automation'
1145
+ )
1146
+ }
1147
+
1148
+ const stylelintTargets = findStylelintTargets(process.cwd())
1149
+ const usingDefaultStylelintTarget =
1150
+ stylelintTargets.length === 1 &&
1151
+ stylelintTargets[0] === STYLELINT_DEFAULT_TARGET
1152
+ if (!usingDefaultStylelintTarget) {
1153
+ console.log(
1154
+ `🔍 Detected stylelint targets: ${stylelintTargets.join(', ')}`
1155
+ )
1156
+ }
1157
+
1158
+ // Add quality automation scripts (conservative: do not overwrite existing)
1159
+ console.log('📝 Adding quality automation scripts...')
1160
+ const defaultScripts = getDefaultScripts({
1161
+ typescript: usesTypeScript,
1162
+ stylelintTargets,
1163
+ })
1164
+
1165
+ // Import enhanced scripts to fix production quality gaps
1166
+ const {
1167
+ getEnhancedTypeScriptScripts,
1168
+ } = require('./lib/typescript-config-generator')
1169
+ const enhancedScripts = getEnhancedTypeScriptScripts()
1170
+
1171
+ // Merge both default and enhanced scripts
1172
+ packageJson.scripts = mergeScripts(packageJson.scripts || {}, {
1173
+ ...defaultScripts,
1174
+ ...enhancedScripts,
1175
+ })
1176
+
1177
+ // Add devDependencies
1178
+ console.log('📦 Adding devDependencies...')
1179
+ const defaultDevDependencies = getDefaultDevDependencies({
1180
+ typescript: usesTypeScript,
1181
+ })
1182
+ packageJson.devDependencies = mergeDevDependencies(
1183
+ packageJson.devDependencies || {},
1184
+ defaultDevDependencies
1185
+ )
1186
+
1187
+ // Add lint-staged configuration
1188
+ console.log('⚙️ Adding lint-staged configuration...')
1189
+ const defaultLintStaged = getDefaultLintStaged({
1190
+ typescript: usesTypeScript,
1191
+ stylelintTargets,
1192
+ python: usesPython,
1193
+ })
1194
+
1195
+ // Import enhanced lint-staged to fix production quality gaps
1196
+ const {
1197
+ getEnhancedLintStaged,
1198
+ } = require('./lib/typescript-config-generator')
1199
+ const enhancedLintStaged = getEnhancedLintStaged(
1200
+ usesPython,
1201
+ usesTypeScript
1202
+ )
1203
+
1204
+ // Merge enhanced configuration with defaults
1205
+ const finalLintStaged = { ...defaultLintStaged, ...enhancedLintStaged }
1206
+
1207
+ const hasExistingCssPatterns = Object.keys(
1208
+ packageJson['lint-staged'] || {}
1209
+ ).some(patternIncludesStylelintExtension)
1210
+
1211
+ if (hasExistingCssPatterns) {
1212
+ console.log(
1213
+ 'ℹ️ Detected existing lint-staged CSS globs; preserving current CSS targets'
1214
+ )
1215
+ }
1216
+
1217
+ packageJson['lint-staged'] = mergeLintStaged(
1218
+ packageJson['lint-staged'] || {},
1219
+ finalLintStaged,
1220
+ { stylelintTargets },
1221
+ patternIncludesStylelintExtension
1222
+ )
1223
+
1224
+ // Write updated package.json using @npmcli/package-json
1225
+ try {
1226
+ const PackageJson = checkNodeVersionAndLoadPackageJson()
1227
+ let pkgJson
1228
+ if (fs.existsSync(packageJsonPath)) {
1229
+ // Load existing package.json
1230
+ pkgJson = await PackageJson.load(process.cwd())
1231
+ // Update with our changes
1232
+ Object.assign(pkgJson.content, packageJson)
1233
+ } else {
1234
+ // Create new package.json
1235
+ pkgJson = await PackageJson.create(process.cwd())
1236
+ Object.assign(pkgJson.content, packageJson)
1237
+ }
1238
+
1239
+ await pkgJson.save()
1240
+ console.log('✅ Updated package.json')
1241
+ } catch (error) {
1242
+ console.error(`❌ Error writing package.json: ${error.message}`)
1243
+ process.exit(1)
1244
+ }
1245
+
1246
+ // Ensure Node toolchain pinning in target project
1247
+ const nvmrcPath = path.join(process.cwd(), '.nvmrc')
1248
+ if (!fs.existsSync(nvmrcPath)) {
1249
+ fs.writeFileSync(nvmrcPath, '20\n')
1250
+ console.log('✅ Added .nvmrc (Node 20)')
1251
+ }
1252
+
1253
+ const npmrcPath = path.join(process.cwd(), '.npmrc')
1254
+ if (!fs.existsSync(npmrcPath)) {
1255
+ fs.writeFileSync(npmrcPath, 'engine-strict = true\n')
1256
+ console.log('✅ Added .npmrc (engine-strict)')
1257
+ }
1258
+
1259
+ // Generate .qualityrc.json with detected maturity level
1260
+ const qualityrcPath = path.join(process.cwd(), '.qualityrc.json')
1261
+ if (!fs.existsSync(qualityrcPath)) {
1262
+ const { ProjectMaturityDetector } = require('./lib/project-maturity')
1263
+ const detector = new ProjectMaturityDetector({
1264
+ projectPath: process.cwd(),
1265
+ })
1266
+ const detectedMaturity = detector.detect()
1267
+ const stats = detector.analyzeProject()
1268
+
1269
+ const qualityConfig = {
1270
+ version: '1.0.0',
1271
+ maturity: 'auto',
1272
+ detected: {
1273
+ level: detectedMaturity,
1274
+ sourceFiles: stats.totalSourceFiles,
1275
+ testFiles: stats.testFiles,
1276
+ hasDocumentation: stats.hasDocumentation,
1277
+ hasDependencies: stats.hasDependencies,
1278
+ detectedAt: new Date().toISOString(),
1279
+ },
1280
+ checks: {
1281
+ prettier: { enabled: true, required: true },
1282
+ eslint: { enabled: 'auto', required: false },
1283
+ stylelint: { enabled: 'auto', required: false },
1284
+ tests: { enabled: 'auto', required: false },
1285
+ coverage: { enabled: false, required: false, threshold: 80 },
1286
+ 'security-audit': { enabled: 'auto', required: false },
1287
+ documentation: { enabled: false, required: false },
1288
+ lighthouse: { enabled: false, required: false },
1289
+ },
1290
+ }
1291
+
1292
+ fs.writeFileSync(
1293
+ qualityrcPath,
1294
+ JSON.stringify(qualityConfig, null, 2) + '\n'
1295
+ )
1296
+ console.log(`✅ Added .qualityrc.json (detected: ${detectedMaturity})`)
1297
+
1298
+ // Validate the generated config
1299
+ const validationResult = validateQualityConfig(qualityrcPath)
1300
+ if (!validationResult.valid) {
1301
+ console.warn(
1302
+ '⚠️ Warning: Generated config has validation issues (this should not happen):'
1303
+ )
1304
+ validationResult.errors.forEach(error => {
1305
+ console.warn(` - ${error}`)
1306
+ })
1307
+ }
1308
+ } else {
1309
+ // Config exists, validate it
1310
+ // Temporarily disabled - debugging
1311
+ // const validationResult = validateQualityConfig(qualityrcPath)
1312
+ // if (!validationResult.valid) {
1313
+ // console.warn(
1314
+ // '⚠️ Warning: Existing .qualityrc.json has validation issues:'
1315
+ // )
1316
+ // validationResult.errors.forEach(error => {
1317
+ // console.warn(` - ${error}`)
1318
+ // })
1319
+ // console.warn(
1320
+ // ' Setup will continue, but you may want to fix these issues.\n'
1321
+ // )
1322
+ // }
1323
+ }
1324
+
1325
+ // Load and merge templates (custom + defaults)
1326
+ // Enable strict mode when custom template path is explicitly provided
1327
+ const templateSpinner = showProgress('Loading templates...')
1328
+ const templateLoader = new TemplateLoader({
1329
+ verbose: true,
1330
+ strict: !!customTemplatePath,
1331
+ })
1332
+
1333
+ let templates
1334
+ try {
1335
+ templates = await templateLoader.mergeTemplates(
1336
+ customTemplatePath,
1337
+ __dirname
1338
+ )
1339
+ if (customTemplatePath) {
1340
+ templateSpinner.succeed('Custom templates loaded successfully')
1341
+ } else {
1342
+ templateSpinner.succeed('Default templates loaded')
1343
+ }
1344
+ } catch (error) {
1345
+ templateSpinner.fail('Template loading failed')
1346
+ console.error(`❌ Template loading failed: ${error.message}`)
1347
+ console.error(
1348
+ '\nWhen using --template, the path must exist and be a valid directory.'
1349
+ )
1350
+ console.error('Please check the path and try again.\n')
1351
+ process.exit(1)
1352
+ }
1353
+
1354
+ // Create .github/workflows directory if it doesn't exist
1355
+ const configSpinner = showProgress('Copying configuration files...')
1356
+ const workflowDir = path.join(process.cwd(), '.github', 'workflows')
1357
+ if (!fs.existsSync(workflowDir)) {
1358
+ fs.mkdirSync(workflowDir, { recursive: true })
1359
+ console.log('📁 Created .github/workflows directory')
1360
+ }
1361
+
1362
+ // Copy workflow file if it doesn't exist
1363
+ const workflowFile = path.join(workflowDir, 'quality.yml')
1364
+ if (!fs.existsSync(workflowFile)) {
1365
+ const templateWorkflow =
1366
+ templateLoader.getTemplate(
1367
+ templates,
1368
+ path.join('.github', 'workflows', 'quality.yml')
1369
+ ) ||
1370
+ fs.readFileSync(
1371
+ path.join(__dirname, '.github/workflows/quality.yml'),
1372
+ 'utf8'
1373
+ )
1374
+ fs.writeFileSync(workflowFile, templateWorkflow)
1375
+ console.log('✅ Added GitHub Actions workflow')
1376
+ }
1377
+
1378
+ // Copy Prettier config if it doesn't exist
1379
+ const prettierrcPath = path.join(process.cwd(), '.prettierrc')
1380
+ if (!fs.existsSync(prettierrcPath)) {
1381
+ const templatePrettierrc =
1382
+ templateLoader.getTemplate(templates, '.prettierrc') ||
1383
+ fs.readFileSync(path.join(__dirname, '.prettierrc'), 'utf8')
1384
+ fs.writeFileSync(prettierrcPath, templatePrettierrc)
1385
+ console.log('✅ Added Prettier configuration')
1386
+ }
1387
+
1388
+ // Copy ESLint config if it doesn't exist
1389
+ const eslintConfigPath = path.join(process.cwd(), 'eslint.config.cjs')
1390
+ const eslintTemplateFile = usesTypeScript
1391
+ ? 'eslint.config.ts.cjs'
1392
+ : 'eslint.config.cjs'
1393
+ const templateEslint =
1394
+ templateLoader.getTemplate(templates, eslintTemplateFile) ||
1395
+ fs.readFileSync(path.join(__dirname, eslintTemplateFile), 'utf8')
1396
+
1397
+ if (!fs.existsSync(eslintConfigPath)) {
1398
+ fs.writeFileSync(eslintConfigPath, templateEslint)
1399
+ console.log(
1400
+ `✅ Added ESLint configuration${usesTypeScript ? ' (TypeScript-aware)' : ''}`
1401
+ )
1402
+ } else if (usesTypeScript) {
1403
+ const existingConfig = fs.readFileSync(eslintConfigPath, 'utf8')
1404
+ if (!existingConfig.includes('@typescript-eslint')) {
1405
+ fs.writeFileSync(eslintConfigPath, templateEslint)
1406
+ console.log('♻️ Updated ESLint configuration with TypeScript support')
1407
+ }
1408
+ }
1409
+
1410
+ const legacyEslintrcPath = path.join(process.cwd(), '.eslintrc.json')
1411
+ if (fs.existsSync(legacyEslintrcPath)) {
1412
+ console.log(
1413
+ 'ℹ️ Detected legacy .eslintrc.json; ESLint 9 prefers eslint.config.cjs. Consider removing the legacy file after verifying the new config.'
1414
+ )
1415
+ }
1416
+
1417
+ // Copy Stylelint config if it doesn't exist
1418
+ const stylelintrcPath = path.join(process.cwd(), '.stylelintrc.json')
1419
+ if (!fs.existsSync(stylelintrcPath)) {
1420
+ const templateStylelint =
1421
+ templateLoader.getTemplate(templates, '.stylelintrc.json') ||
1422
+ fs.readFileSync(path.join(__dirname, '.stylelintrc.json'), 'utf8')
1423
+ fs.writeFileSync(stylelintrcPath, templateStylelint)
1424
+ console.log('✅ Added Stylelint configuration')
1425
+ }
1426
+
1427
+ // Copy .prettierignore if it doesn't exist
1428
+ const prettierignorePath = path.join(process.cwd(), '.prettierignore')
1429
+ if (!fs.existsSync(prettierignorePath)) {
1430
+ const templatePrettierignore =
1431
+ templateLoader.getTemplate(templates, '.prettierignore') ||
1432
+ fs.readFileSync(path.join(__dirname, '.prettierignore'), 'utf8')
1433
+ fs.writeFileSync(prettierignorePath, templatePrettierignore)
1434
+ console.log('✅ Added Prettier ignore file')
1435
+ }
1436
+
1437
+ // Copy Lighthouse CI config if it doesn't exist
1438
+ const lighthousercPath = path.join(process.cwd(), '.lighthouserc.js')
1439
+ if (!fs.existsSync(lighthousercPath)) {
1440
+ const templateLighthouserc =
1441
+ templateLoader.getTemplate(
1442
+ templates,
1443
+ path.join('config', '.lighthouserc.js')
1444
+ ) ||
1445
+ fs.readFileSync(
1446
+ path.join(__dirname, 'config', '.lighthouserc.js'),
1447
+ 'utf8'
1448
+ )
1449
+ fs.writeFileSync(lighthousercPath, templateLighthouserc)
1450
+ console.log('✅ Added Lighthouse CI configuration')
1451
+ }
1452
+
1453
+ // Copy ESLint ignore if it doesn't exist
1454
+ const eslintignorePath = path.join(process.cwd(), '.eslintignore')
1455
+ const eslintignoreTemplatePath = path.join(__dirname, '.eslintignore')
1456
+ if (
1457
+ !fs.existsSync(eslintignorePath) &&
1458
+ (templateLoader.hasTemplate(templates, '.eslintignore') ||
1459
+ fs.existsSync(eslintignoreTemplatePath))
1460
+ ) {
1461
+ const templateEslintIgnore =
1462
+ templateLoader.getTemplate(templates, '.eslintignore') ||
1463
+ fs.readFileSync(eslintignoreTemplatePath, 'utf8')
1464
+ fs.writeFileSync(eslintignorePath, templateEslintIgnore)
1465
+ console.log('✅ Added ESLint ignore file')
1466
+ }
1467
+
1468
+ // Copy .editorconfig if it doesn't exist
1469
+ const editorconfigPath = path.join(process.cwd(), '.editorconfig')
1470
+ if (!fs.existsSync(editorconfigPath)) {
1471
+ const templateEditorconfig =
1472
+ templateLoader.getTemplate(templates, '.editorconfig') ||
1473
+ fs.readFileSync(path.join(__dirname, '.editorconfig'), 'utf8')
1474
+ fs.writeFileSync(editorconfigPath, templateEditorconfig)
1475
+ console.log('✅ Added .editorconfig')
1476
+ }
1477
+
1478
+ configSpinner.succeed('Configuration files copied')
1479
+
1480
+ // Ensure .gitignore exists with essential entries
1481
+ const gitignorePath = path.join(process.cwd(), '.gitignore')
1482
+ if (!fs.existsSync(gitignorePath)) {
1483
+ const essentialGitignore = `# Dependencies
1484
+ node_modules/
1485
+ .pnpm-store/
1486
+
1487
+ # Environment variables
1488
+ .env*
1489
+
1490
+ # Logs
1491
+ *.log
1492
+ npm-debug.log*
1493
+ yarn-debug.log*
1494
+ yarn-error.log*
1495
+ pnpm-debug.log*
1496
+
1497
+ # Build outputs
1498
+ dist/
1499
+ build/
1500
+ .next/
1501
+ .nuxt/
1502
+ .output/
1503
+ .vercel/
1504
+
1505
+ # OS
1506
+ .DS_Store
1507
+ Thumbs.db
1508
+
1509
+ # IDE
1510
+ .vscode/
1511
+ .idea/
1512
+ *.swp
1513
+ *.swo
1514
+
1515
+ # Coverage
1516
+ coverage/
1517
+ .nyc_output/
1518
+
1519
+ # Cache
1520
+ .cache/
1521
+ .parcel-cache/
1522
+ .turbo/
1523
+ `
1524
+ fs.writeFileSync(gitignorePath, essentialGitignore)
1525
+ console.log('✅ Added .gitignore with essential entries')
1526
+ }
1527
+
1528
+ // Ensure Husky pre-commit hook runs lint-staged
1529
+ const huskySpinner = showProgress('Setting up Husky git hooks...')
1530
+ try {
1531
+ const huskyDir = path.join(process.cwd(), '.husky')
1532
+ if (!fs.existsSync(huskyDir)) {
1533
+ fs.mkdirSync(huskyDir, { recursive: true })
1534
+ }
1535
+ const preCommitPath = path.join(huskyDir, 'pre-commit')
1536
+ if (!fs.existsSync(preCommitPath)) {
1537
+ const hook =
1538
+ '#!/bin/sh\n. "$(dirname "$0")/_/husky.sh"\n\n# Run lint-staged on staged files\nnpx --no -- lint-staged\n'
1539
+ fs.writeFileSync(preCommitPath, hook)
1540
+ fs.chmodSync(preCommitPath, 0o755)
1541
+ console.log('✅ Added Husky pre-commit hook (lint-staged)')
1542
+ }
1543
+ } catch (e) {
1544
+ huskySpinner.warn('Could not create Husky pre-commit hook')
1545
+ console.warn('⚠️ Could not create Husky pre-commit hook:', e.message)
1546
+ }
1547
+
1548
+ // Ensure Husky pre-push hook runs validation checks
1549
+ try {
1550
+ const huskyDir = path.join(process.cwd(), '.husky')
1551
+ if (!fs.existsSync(huskyDir)) {
1552
+ fs.mkdirSync(huskyDir, { recursive: true })
1553
+ }
1554
+ const prePushPath = path.join(huskyDir, 'pre-push')
1555
+ if (!fs.existsSync(prePushPath)) {
1556
+ const hook = `#!/bin/sh
1557
+ . "$(dirname "$0")/_/husky.sh"
1558
+
1559
+ echo "🔍 Running pre-push validation..."
1560
+
1561
+ # Enforce Free tier pre-push cap (50/month)
1562
+ node - <<'EOF'
1563
+ const fs = require('fs')
1564
+ const path = require('path')
1565
+ const os = require('os')
1566
+
1567
+ const licenseDir =
1568
+ process.env.QAA_LICENSE_DIR || path.join(os.homedir(), '.create-qa-architect')
1569
+ const licenseFile = path.join(licenseDir, 'license.json')
1570
+ const usageFile = path.join(licenseDir, 'usage.json')
1571
+ const now = new Date()
1572
+ const currentMonth = now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0')
1573
+
1574
+ let usage = {
1575
+ month: currentMonth,
1576
+ prePushRuns: 0,
1577
+ dependencyPRs: 0,
1578
+ repos: [],
1579
+ }
1580
+
1581
+ let tier = 'FREE'
1582
+ try {
1583
+ const data = JSON.parse(fs.readFileSync(licenseFile, 'utf8'))
1584
+ tier = (data && data.tier) || 'FREE'
1585
+ } catch (_error) {
1586
+ tier = 'FREE'
1587
+ }
1588
+
1589
+ if (tier !== 'FREE') {
1590
+ process.exit(0)
1591
+ }
1592
+
1593
+ try {
1594
+ const data = JSON.parse(fs.readFileSync(usageFile, 'utf8'))
1595
+ if (data.month === currentMonth) {
1596
+ usage = { ...usage, ...data }
1597
+ }
1598
+ } catch (_error) {
1599
+ // First run or corrupt file – start fresh
1600
+ }
1601
+
1602
+ const CAP = 50
1603
+ if (usage.prePushRuns >= CAP) {
1604
+ console.error('❌ Free tier limit reached: ' + usage.prePushRuns + '/' + CAP + ' pre-push runs this month')
1605
+ console.error(' Upgrade to Pro, Team, or Enterprise: https://vibebuildlab.com/cqa')
1606
+ process.exit(1)
1607
+ }
1608
+
1609
+ usage.prePushRuns += 1
1610
+ fs.mkdirSync(licenseDir, { recursive: true })
1611
+ fs.writeFileSync(usageFile, JSON.stringify(usage, null, 2))
1612
+ console.log('🧮 Usage: ' + usage.prePushRuns + '/' + CAP + ' pre-push runs used this month')
1613
+ EOF
1614
+
1615
+ # Validate command patterns (fast - catches deprecated patterns)
1616
+ if node -e "const pkg=require('./package.json');process.exit(pkg.scripts['test:patterns']?0:1)" 2>/dev/null; then
1617
+ echo "🔍 Validating command patterns..."
1618
+ npm run test:patterns || {
1619
+ echo "❌ Pattern validation failed! Deprecated patterns detected."
1620
+ exit 1
1621
+ }
1622
+ fi
1623
+
1624
+ # Run lint (catches errors before CI)
1625
+ echo "📝 Linting..."
1626
+ npm run lint || {
1627
+ echo "❌ Lint failed! Fix errors before pushing."
1628
+ exit 1
1629
+ }
1630
+
1631
+ # Run format check (ensures code style consistency)
1632
+ echo "✨ Checking formatting..."
1633
+ npm run format:check || {
1634
+ echo "❌ Format check failed! Run 'npm run format' to fix."
1635
+ exit 1
1636
+ }
1637
+
1638
+ # Test command execution (CRITICAL - prevents command generation bugs)
1639
+ if node -e "const pkg=require('./package.json');process.exit(pkg.scripts['test:commands']?0:1)" 2>/dev/null; then
1640
+ echo "🧪 Testing command execution..."
1641
+ npm run test:commands || {
1642
+ echo "❌ Command execution tests failed! Generated commands are broken."
1643
+ exit 1
1644
+ }
1645
+ fi
1646
+
1647
+ # Run tests if they exist
1648
+ if node -e "const pkg=require('./package.json');process.exit(pkg.scripts.test?0:1)" 2>/dev/null; then
1649
+ echo "🧪 Running unit tests..."
1650
+ npm test || {
1651
+ echo "❌ Tests failed! Fix failing tests before pushing."
1652
+ exit 1
1653
+ }
1654
+ fi
1655
+
1656
+ echo "✅ Pre-push validation passed!"
1657
+ `
1658
+ fs.writeFileSync(prePushPath, hook)
1659
+ fs.chmodSync(prePushPath, 0o755)
1660
+ console.log('✅ Added Husky pre-push hook (validation)')
1661
+ }
1662
+ huskySpinner.succeed('Husky git hooks configured')
1663
+ } catch (e) {
1664
+ huskySpinner.warn('Could not create Husky pre-push hook')
1665
+ console.warn('⚠️ Could not create Husky pre-push hook:', e.message)
1666
+ }
1667
+
1668
+ // Ensure engines/volta pins in target package.json (enforce minimums)
1669
+ try {
1670
+ if (fs.existsSync(packageJsonPath)) {
1671
+ const PackageJson = checkNodeVersionAndLoadPackageJson()
1672
+ const pkgJson = await PackageJson.load(process.cwd())
1673
+
1674
+ // Preserve existing engines but enforce Node >=20 minimum
1675
+ const existingEngines = pkgJson.content.engines || {}
1676
+ pkgJson.content.engines = {
1677
+ ...existingEngines,
1678
+ node: '>=20', // Always enforce our minimum
1679
+ }
1680
+
1681
+ // Preserve existing volta but set our pinned versions
1682
+ const existingVolta = pkgJson.content.volta || {}
1683
+ pkgJson.content.volta = {
1684
+ ...existingVolta,
1685
+ node: '20.11.1',
1686
+ npm: '10.2.4',
1687
+ }
1688
+
1689
+ await pkgJson.save()
1690
+ console.log(
1691
+ '✅ Ensured engines and Volta pins in package.json (Node >=20 enforced)'
1692
+ )
1693
+ }
1694
+ } catch (e) {
1695
+ console.warn(
1696
+ '⚠️ Could not update engines/volta in package.json:',
1697
+ e.message
1698
+ )
1699
+ }
1700
+
1701
+ // Python quality automation setup
1702
+ if (usesPython) {
1703
+ console.log('\n🐍 Setting up Python quality automation...')
1704
+
1705
+ const pythonSpinner = showProgress(
1706
+ 'Configuring Python quality tools...'
1707
+ )
1708
+
1709
+ // Copy pyproject.toml if it doesn't exist
1710
+ const pyprojectPath = path.join(process.cwd(), 'pyproject.toml')
1711
+ if (!fs.existsSync(pyprojectPath)) {
1712
+ const templatePyproject =
1713
+ templateLoader.getTemplate(
1714
+ templates,
1715
+ path.join('config', 'pyproject.toml')
1716
+ ) ||
1717
+ fs.readFileSync(
1718
+ path.join(__dirname, 'config/pyproject.toml'),
1719
+ 'utf8'
1720
+ )
1721
+ fs.writeFileSync(pyprojectPath, templatePyproject)
1722
+ console.log(
1723
+ '✅ Added pyproject.toml with Black, Ruff, isort, mypy config'
1724
+ )
1725
+ }
1726
+
1727
+ // Copy pre-commit config
1728
+ const preCommitPath = path.join(
1729
+ process.cwd(),
1730
+ '.pre-commit-config.yaml'
1731
+ )
1732
+ if (!fs.existsSync(preCommitPath)) {
1733
+ const templatePreCommit =
1734
+ templateLoader.getTemplate(
1735
+ templates,
1736
+ path.join('config', '.pre-commit-config.yaml')
1737
+ ) ||
1738
+ fs.readFileSync(
1739
+ path.join(__dirname, 'config/.pre-commit-config.yaml'),
1740
+ 'utf8'
1741
+ )
1742
+ fs.writeFileSync(preCommitPath, templatePreCommit)
1743
+ console.log('✅ Added .pre-commit-config.yaml')
1744
+ }
1745
+
1746
+ // Copy requirements-dev.txt
1747
+ const requirementsDevPath = path.join(
1748
+ process.cwd(),
1749
+ 'requirements-dev.txt'
1750
+ )
1751
+ if (!fs.existsSync(requirementsDevPath)) {
1752
+ const templateRequirements =
1753
+ templateLoader.getTemplate(
1754
+ templates,
1755
+ path.join('config', 'requirements-dev.txt')
1756
+ ) ||
1757
+ fs.readFileSync(
1758
+ path.join(__dirname, 'config/requirements-dev.txt'),
1759
+ 'utf8'
1760
+ )
1761
+ fs.writeFileSync(requirementsDevPath, templateRequirements)
1762
+ console.log('✅ Added requirements-dev.txt')
1763
+ }
1764
+
1765
+ // Copy Python workflow
1766
+ const pythonWorkflowFile = path.join(workflowDir, 'quality-python.yml')
1767
+ if (!fs.existsSync(pythonWorkflowFile)) {
1768
+ const templatePythonWorkflow =
1769
+ templateLoader.getTemplate(
1770
+ templates,
1771
+ path.join('config', 'quality-python.yml')
1772
+ ) ||
1773
+ fs.readFileSync(
1774
+ path.join(__dirname, 'config/quality-python.yml'),
1775
+ 'utf8'
1776
+ )
1777
+ fs.writeFileSync(pythonWorkflowFile, templatePythonWorkflow)
1778
+ console.log('✅ Added Python GitHub Actions workflow')
1779
+ }
1780
+
1781
+ // Create tests directory if it doesn't exist
1782
+ const testsDir = path.join(process.cwd(), 'tests')
1783
+ if (!fs.existsSync(testsDir)) {
1784
+ fs.mkdirSync(testsDir)
1785
+ fs.writeFileSync(path.join(testsDir, '__init__.py'), '')
1786
+ console.log('✅ Created tests directory')
1787
+ }
1788
+
1789
+ // Add Python helper scripts to package.json if it exists and is a JS/TS project too
1790
+ if (fs.existsSync(packageJsonPath)) {
1791
+ try {
1792
+ const PackageJson = checkNodeVersionAndLoadPackageJson()
1793
+ const pkgJson = await PackageJson.load(process.cwd())
1794
+
1795
+ const pythonScripts = {
1796
+ 'python:format': 'black .',
1797
+ 'python:format:check': 'black --check .',
1798
+ 'python:lint': 'ruff check .',
1799
+ 'python:lint:fix': 'ruff check --fix .',
1800
+ 'python:type-check': 'mypy .',
1801
+ 'python:quality':
1802
+ 'black --check . && ruff check . && isort --check-only . && mypy .',
1803
+ 'python:test': 'pytest',
1804
+ }
1805
+
1806
+ if (!pkgJson.content.scripts) {
1807
+ pkgJson.content.scripts = {}
1808
+ }
1809
+ // Use mergeScripts to preserve existing scripts
1810
+ pkgJson.content.scripts = mergeScripts(
1811
+ pkgJson.content.scripts,
1812
+ pythonScripts
1813
+ )
1814
+ await pkgJson.save()
1815
+ console.log('✅ Added Python helper scripts to package.json')
1816
+ } catch (e) {
1817
+ console.warn(
1818
+ '⚠️ Could not add Python scripts to package.json:',
1819
+ e.message
1820
+ )
1821
+ }
1822
+ }
1823
+
1824
+ pythonSpinner.succeed('Python quality tools configured')
1825
+ }
1826
+
1827
+ // Smart Test Strategy (Pro/Team/Enterprise feature)
1828
+ const smartStrategyEnabled = hasFeature('smartTestStrategy')
1829
+ if (smartStrategyEnabled) {
1830
+ const smartSpinner = showProgress('Setting up Smart Test Strategy...')
1831
+
1832
+ try {
1833
+ // Detect project type and generate customized strategy
1834
+ const projectType = detectProjectType(process.cwd())
1835
+ const { script, projectTypeName } = generateSmartStrategy({
1836
+ projectPath: process.cwd(),
1837
+ projectName: packageJson.name || path.basename(process.cwd()),
1838
+ projectType,
1839
+ })
1840
+
1841
+ // Write smart strategy script
1842
+ writeSmartStrategy(process.cwd(), script)
1843
+ console.log(`✅ Added Smart Test Strategy (${projectTypeName})`)
1844
+
1845
+ // Update pre-push hook to use smart strategy
1846
+ const huskyDir = path.join(process.cwd(), '.husky')
1847
+ const prePushPath = path.join(huskyDir, 'pre-push')
1848
+ const smartPrePush = generateSmartPrePushHook()
1849
+ fs.writeFileSync(prePushPath, smartPrePush)
1850
+ fs.chmodSync(prePushPath, 0o755)
1851
+ console.log('✅ Updated pre-push hook to use smart strategy')
1852
+
1853
+ // Add test tier scripts to package.json
1854
+ const testTierScripts = getTestTierScripts(projectType)
1855
+ const PackageJson = checkNodeVersionAndLoadPackageJson()
1856
+ const pkgJson = await PackageJson.load(process.cwd())
1857
+ pkgJson.content.scripts = mergeScripts(
1858
+ pkgJson.content.scripts || {},
1859
+ testTierScripts
1860
+ )
1861
+ await pkgJson.save()
1862
+ console.log(
1863
+ '✅ Added test tier scripts (test:fast, test:medium, test:comprehensive)'
1864
+ )
1865
+
1866
+ smartSpinner.succeed('Smart Test Strategy configured')
1867
+
1868
+ console.log('\n💎 Smart Test Strategy Benefits:')
1869
+ console.log(' • 70% faster pre-push validation on average')
1870
+ console.log(' • Risk-based test selection')
1871
+ console.log(' • Adapts to branch, time of day, and change size')
1872
+ console.log(
1873
+ ' • Override with SKIP_SMART=1, FORCE_COMPREHENSIVE=1, or FORCE_MINIMAL=1'
1874
+ )
1875
+ } catch (error) {
1876
+ smartSpinner.warn('Could not set up Smart Test Strategy')
1877
+ console.warn('⚠️ Smart Test Strategy setup error:', error.message)
1878
+ }
1879
+ } else {
1880
+ // Show upgrade message for Free tier users
1881
+ console.log('\n💡 Smart Test Strategy is available with Pro tier:')
1882
+ console.log(' • 70% faster pre-push validation')
1883
+ console.log(' • Intelligent risk-based test selection')
1884
+ console.log(' • Saves 10-20 hours/month per developer')
1885
+ showUpgradeMessage('Smart Test Strategy')
1886
+ }
1887
+
1888
+ // Generate placeholder test file with helpful documentation
1889
+ const testsDir = path.join(process.cwd(), 'tests')
1890
+ const testExtension = usesTypeScript ? 'ts' : 'js'
1891
+ const placeholderTestPath = path.join(
1892
+ testsDir,
1893
+ `placeholder.test.${testExtension}`
1894
+ )
1895
+
1896
+ if (!fs.existsSync(testsDir)) {
1897
+ fs.mkdirSync(testsDir, { recursive: true })
1898
+ }
1899
+
1900
+ if (!fs.existsSync(placeholderTestPath)) {
1901
+ const placeholderContent = `import { describe, it, expect } from 'vitest'
1902
+
1903
+ /**
1904
+ * PLACEHOLDER TEST FILE
1905
+ *
1906
+ * This file ensures your test suite passes even when you're just getting started.
1907
+ * Replace these placeholders with real tests as you build your application.
1908
+ *
1909
+ * Progressive Testing Strategy:
1910
+ * 1. Start: Use describe.skip() placeholders (tests pass but are marked as skipped)
1911
+ * 2. Planning: Convert to it.todo() when you know what to test
1912
+ * 3. Implementation: Write actual test implementations
1913
+ * 4. Tighten: Remove --passWithNoTests flag once you have real tests
1914
+ *
1915
+ * To tighten enforcement, update package.json:
1916
+ * - Change: "test": "vitest run --passWithNoTests"
1917
+ * - To: "test": "vitest run" (fails if no tests exist)
1918
+ */
1919
+
1920
+ describe.skip('Example test suite (placeholder)', () => {
1921
+ /**
1922
+ * These tests are skipped by default to prevent false positives.
1923
+ * Remove .skip and implement these tests when you're ready.
1924
+ */
1925
+
1926
+ it.todo('should test core functionality')
1927
+
1928
+ it.todo('should handle edge cases')
1929
+
1930
+ it.todo('should validate error conditions')
1931
+ })
1932
+
1933
+ // Example of a passing test (demonstrates test framework is working)
1934
+ describe('Test framework validation', () => {
1935
+ it('should confirm Vitest is properly configured', () => {
1936
+ expect(true).toBe(true)
1937
+ })
1938
+ })
1939
+
1940
+ /**
1941
+ * Next Steps:
1942
+ * 1. Create feature-specific test files (e.g., user.test.${testExtension}, api.test.${testExtension})
1943
+ * 2. Move these it.todo() placeholders to appropriate test files
1944
+ * 3. Implement actual test logic
1945
+ * 4. Delete this placeholder.test.${testExtension} file when you have real tests
1946
+ *
1947
+ * Resources:
1948
+ * - Vitest Docs: https://vitest.dev/guide/
1949
+ * - Testing Best Practices: https://github.com/goldbergyoni/javascript-testing-best-practices
1950
+ */
1951
+ `
1952
+ fs.writeFileSync(placeholderTestPath, placeholderContent)
1953
+ console.log(
1954
+ `✅ Added placeholder test file (tests/placeholder.test.${testExtension})`
1955
+ )
1956
+ console.log(
1957
+ ' 💡 Replace with real tests as you build your application'
1958
+ )
1959
+ }
1960
+
1961
+ // Apply critical production quality fixes
1962
+ console.log('\n🔧 Applying production quality enhancements...')
1963
+ const qualityEnhancements = applyProductionQualityFixes('.', {
1964
+ hasTypeScript: usesTypeScript,
1965
+ hasPython: usesPython,
1966
+ skipTypeScriptTests: false,
1967
+ })
1968
+
1969
+ // Display applied fixes
1970
+ qualityEnhancements.fixes.forEach(fix => console.log(fix))
1971
+
1972
+ // Validate setup for common gaps
1973
+ const { warnings, errors } = validateProjectSetup('.')
1974
+
1975
+ if (errors.length > 0) {
1976
+ console.log('\n🚨 CRITICAL ISSUES DETECTED:')
1977
+ errors.forEach(error => console.log(error))
1978
+ }
1979
+
1980
+ if (warnings.length > 0) {
1981
+ console.log('\n⚠️ Setup Warnings:')
1982
+ warnings.forEach(warning => console.log(warning))
1983
+ }
1984
+
1985
+ console.log('\n🎉 Quality automation setup complete!')
1986
+
1987
+ // Record telemetry completion event (opt-in only, fails silently)
1988
+ telemetry.recordComplete({
1989
+ usesPython,
1990
+ usesTypeScript,
1991
+ hasStylelintFiles: stylelintTargets.length > 0,
1992
+ mode: isDryRun ? 'dry-run' : isUpdateMode ? 'update' : 'setup',
1993
+ })
1994
+
1995
+ // Dynamic next steps based on detected languages
1996
+ console.log('\n📋 Next steps:')
1997
+
1998
+ if (usesPython && fs.existsSync(packageJsonPath)) {
1999
+ console.log('JavaScript/TypeScript setup:')
2000
+ console.log('1. Run: npm install')
2001
+ console.log('2. Run: npm run prepare')
2002
+ console.log('\nPython setup:')
2003
+ console.log('3. Run: python3 -m pip install -r requirements-dev.txt')
2004
+ console.log('4. Run: pre-commit install')
2005
+ console.log('\n5. Commit your changes to activate both workflows')
2006
+ } else if (usesPython) {
2007
+ console.log('Python setup:')
2008
+ console.log('1. Run: python3 -m pip install -r requirements-dev.txt')
2009
+ console.log('2. Run: pre-commit install')
2010
+ console.log('3. Commit your changes to activate the workflow')
2011
+ } else {
2012
+ console.log('1. Run: npm install')
2013
+ console.log('2. Run: npm run prepare')
2014
+ console.log('3. Commit your changes to activate the workflow')
2015
+ }
2016
+ console.log('\n✨ Your project now has:')
2017
+ console.log(' • Prettier code formatting')
2018
+ console.log(' • Pre-commit hooks via Husky (lint-staged)')
2019
+ console.log(' • Pre-push validation (lint, format, tests)')
2020
+ console.log(' • GitHub Actions quality checks')
2021
+ console.log(' • Lint-staged for efficient processing')
2022
+ } // End of runMainSetup function
2023
+
2024
+ // Run main setup (interactive handling already done at top if requested)
2025
+ await runMainSetup()
2026
+ } // End of normal setup flow
2027
+
2028
+ // Close the main async function and handle errors
2029
+ })().catch(error => {
2030
+ try {
2031
+ // Always show stack trace for debugging
2032
+ if (error?.stack) {
2033
+ console.error('\n🐛 Error stack trace:')
2034
+ console.error(error.stack)
2035
+ }
2036
+
2037
+ // Record telemetry failure event (opt-in only, fails silently)
2038
+ const telemetry = new TelemetrySession()
2039
+ telemetry.recordFailure(error, {
2040
+ errorLocation: error?.stack ? error.stack.split('\n')[1] : 'unknown',
2041
+ })
2042
+
2043
+ // Capture and report error (opt-in only, fails silently)
2044
+ const errorReporter = new ErrorReporter('setup')
2045
+ const reportId = errorReporter.captureError(error, {
2046
+ operation: 'setup',
2047
+ errorLocation: error?.stack ? error.stack.split('\n')[1] : 'unknown',
2048
+ })
2049
+
2050
+ // Show friendly error message with category
2051
+ errorReporter.promptErrorReport(error)
2052
+
2053
+ // If report was captured, show location
2054
+ if (reportId) {
2055
+ console.log(`\n📊 Error report saved: ${reportId}`)
2056
+ console.log(`View at: ~/.create-qa-architect/error-reports.json`)
2057
+ }
2058
+ } catch (reportingError) {
2059
+ // Error in error reporting - fallback to basic error display
2060
+ console.error('\n❌ Setup failed with error:')
2061
+ console.error(error?.message || error || 'Unknown error')
2062
+ if (error?.stack) {
2063
+ console.error('\nStack trace:')
2064
+ console.error(error.stack)
2065
+ }
2066
+ // Show error reporting failure for debugging
2067
+ if (process.env.DEBUG) {
2068
+ console.error('\n⚠️ Error reporting also failed:')
2069
+ console.error(
2070
+ reportingError?.stack || reportingError?.message || reportingError
2071
+ )
2072
+ }
2073
+ }
2074
+
2075
+ process.exit(1)
2076
+ })