create-qa-architect 5.0.7 → 5.4.3

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 (45) hide show
  1. package/.github/workflows/auto-release.yml +49 -0
  2. package/.github/workflows/quality.yml +11 -11
  3. package/.github/workflows/shell-ci.yml.example +82 -0
  4. package/.github/workflows/shell-quality.yml.example +148 -0
  5. package/README.md +165 -12
  6. package/config/shell-ci.yml +82 -0
  7. package/config/shell-quality.yml +148 -0
  8. package/docs/ADOPTION-SUMMARY.md +41 -0
  9. package/docs/ARCHITECTURE-REVIEW.md +67 -0
  10. package/docs/ARCHITECTURE.md +29 -45
  11. package/docs/CI-COST-ANALYSIS.md +323 -0
  12. package/docs/CODE-REVIEW.md +100 -0
  13. package/docs/REQUIREMENTS.md +148 -0
  14. package/docs/SECURITY-AUDIT.md +68 -0
  15. package/docs/test-trace-matrix.md +28 -0
  16. package/eslint.config.cjs +2 -0
  17. package/lib/commands/analyze-ci.js +616 -0
  18. package/lib/commands/deps.js +293 -0
  19. package/lib/commands/index.js +29 -0
  20. package/lib/commands/validate.js +85 -0
  21. package/lib/config-validator.js +28 -45
  22. package/lib/error-reporter.js +14 -2
  23. package/lib/github-api.js +138 -13
  24. package/lib/license-signing.js +125 -0
  25. package/lib/license-validator.js +359 -71
  26. package/lib/licensing.js +434 -106
  27. package/lib/package-utils.js +9 -9
  28. package/lib/prelaunch-validator.js +828 -0
  29. package/lib/project-maturity.js +58 -6
  30. package/lib/quality-tools-generator.js +495 -0
  31. package/lib/result-types.js +112 -0
  32. package/lib/security-enhancements.js +1 -1
  33. package/lib/smart-strategy-generator.js +46 -10
  34. package/lib/telemetry.js +1 -1
  35. package/lib/template-loader.js +52 -19
  36. package/lib/ui-helpers.js +1 -1
  37. package/lib/validation/cache-manager.js +36 -6
  38. package/lib/validation/config-security.js +100 -33
  39. package/lib/validation/index.js +68 -97
  40. package/lib/validation/workflow-validation.js +28 -7
  41. package/package.json +4 -6
  42. package/scripts/check-test-coverage.sh +46 -0
  43. package/scripts/validate-claude-md.js +80 -0
  44. package/setup.js +923 -301
  45. package/create-saas-monetization.js +0 -1513
package/setup.js CHANGED
@@ -1,7 +1,71 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ /**
4
+ * DR26 fix: Architectural Refactoring Plan for setup.js (2201 lines)
5
+ *
6
+ * PRIORITY: This file should be split into focused modules to improve
7
+ * maintainability, testability, and reduce cognitive load.
8
+ *
9
+ * PROPOSED MODULE STRUCTURE (target: < 500 lines per module):
10
+ *
11
+ * 1. lib/commands/validate.js (~300 lines)
12
+ * - Validation command logic
13
+ * - Config validation workflows
14
+ * - Prelaunch validation integration
15
+ *
16
+ * 2. lib/commands/deps.js (~200 lines)
17
+ * - Dependency monitoring setup
18
+ * - Dependabot configuration
19
+ * - GitHub API integration calls
20
+ *
21
+ * 3. lib/commands/activate.js (~150 lines)
22
+ * - License activation flow
23
+ * - Interactive license prompts
24
+ * - License validation integration
25
+ *
26
+ * 4. lib/commands/setup-main.js (~800 lines)
27
+ * - Main setup orchestration
28
+ * - Template generation
29
+ * - File writing operations
30
+ * - Husky/lint-staged configuration
31
+ *
32
+ * 5. lib/setup-config.js (~200 lines)
33
+ * - Configuration merging logic
34
+ * - Package.json updates
35
+ * - Workflow injection helpers
36
+ *
37
+ * 6. setup.js (core CLI, ~200 lines)
38
+ * - Argument parsing
39
+ * - Command routing
40
+ * - Help/version display
41
+ * - Exit code handling
42
+ *
43
+ * MIGRATION CHECKLIST:
44
+ * - [ ] Extract validation command to lib/commands/validate.js
45
+ * - [ ] Extract deps command to lib/commands/deps.js
46
+ * - [ ] Extract license activation to lib/commands/activate.js
47
+ * - [ ] Extract main setup flow to lib/commands/setup-main.js
48
+ * - [ ] Extract config helpers to lib/setup-config.js
49
+ * - [ ] Update all tests to use new module structure
50
+ * - [ ] Verify all CLI commands still work (--help, --deps, --validate, etc.)
51
+ * - [ ] Update integration tests
52
+ * - [ ] Run full test suite and ensure 100% pass rate
53
+ *
54
+ * BENEFITS:
55
+ * - Easier to test individual commands
56
+ * - Reduced cognitive load when modifying specific features
57
+ * - Better code organization and discoverability
58
+ * - Easier to add new commands in the future
59
+ * - Faster development cycles (smaller files to navigate)
60
+ *
61
+ * BLOCKED BY: None (can be done incrementally)
62
+ * ESTIMATED EFFORT: 2-3 days with full test coverage
63
+ * RISK: Medium (need to ensure no regressions in CLI behavior)
64
+ */
65
+
3
66
  const fs = require('fs')
4
67
  const path = require('path')
68
+ const crypto = require('crypto')
5
69
  const { execSync } = require('child_process')
6
70
  const {
7
71
  mergeScripts,
@@ -49,29 +113,23 @@ const {
49
113
  } = require('./config/defaults')
50
114
 
51
115
  // Enhanced validation capabilities
52
- const { ValidationRunner } = require('./lib/validation')
53
116
  const { validateQualityConfig } = require('./lib/config-validator')
54
117
 
55
118
  // Interactive mode capabilities
56
119
  const { InteractivePrompt } = require('./lib/interactive/prompt')
57
120
  const { runInteractiveFlow } = require('./lib/interactive/questions')
58
121
 
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')
122
+ // Note: Dependency monitoring imports moved to ./lib/commands/deps.js
71
123
 
72
124
  // Custom template loading
73
125
  const { TemplateLoader } = require('./lib/template-loader')
74
126
 
127
+ // Command handlers (extracted for maintainability)
128
+ const {
129
+ handleValidationCommands,
130
+ handleDependencyMonitoring,
131
+ } = require('./lib/commands')
132
+
75
133
  // Licensing system
76
134
  const {
77
135
  getLicenseInfo,
@@ -91,6 +149,26 @@ const {
91
149
  getTestTierScripts,
92
150
  } = require('./lib/smart-strategy-generator')
93
151
 
152
+ // Quality Tools Generator (Lighthouse, size-limit, axe-core, commitlint, coverage)
153
+ const {
154
+ writeLighthouseConfig,
155
+ writeSizeLimitConfig,
156
+ writeCommitlintConfig,
157
+ writeCommitMsgHook,
158
+ writeAxeTestSetup,
159
+ getQualityToolsDependencies,
160
+ getQualityToolsScripts,
161
+ } = require('./lib/quality-tools-generator')
162
+
163
+ // Pre-Launch Validation (SEO, links, a11y, docs, env)
164
+ const {
165
+ writeValidationScripts,
166
+ writeEnvValidator,
167
+ writePa11yConfig,
168
+ getPrelaunchScripts,
169
+ getPrelaunchDependencies,
170
+ } = require('./lib/prelaunch-validator')
171
+
94
172
  // Telemetry (opt-in usage tracking)
95
173
  const { TelemetrySession, showTelemetryStatus } = require('./lib/telemetry')
96
174
 
@@ -103,7 +181,6 @@ const {
103
181
  // Critical setup enhancements (fixes production quality gaps)
104
182
  const {
105
183
  applyProductionQualityFixes,
106
- // generateEnhancedPreCommitHook,
107
184
  validateProjectSetup,
108
185
  } = require('./lib/setup-enhancements')
109
186
 
@@ -113,6 +190,31 @@ const STYLELINT_EXTENSION_GLOB = `*.{${STYLELINT_EXTENSIONS.join(',')}}`
113
190
  const STYLELINT_SCAN_EXCLUDES = new Set(EXCLUDE_DIRECTORIES.STYLELINT)
114
191
  const MAX_STYLELINT_SCAN_DEPTH = SCAN_LIMITS.STYLELINT_MAX_DEPTH
115
192
 
193
+ function normalizeRepoIdentifier(remoteUrl) {
194
+ if (!remoteUrl || typeof remoteUrl !== 'string') return null
195
+
196
+ const scpMatch = remoteUrl.match(/^[^@]+@([^:]+):(.+?)(\.git)?$/)
197
+ if (scpMatch) {
198
+ const host = scpMatch[1]
199
+ const repoPath = scpMatch[2].replace(/^\/+/, '').replace(/\.git$/, '')
200
+ return `${host}/${repoPath}`
201
+ }
202
+
203
+ try {
204
+ const parsed = new URL(remoteUrl)
205
+ const host = parsed.hostname
206
+ const repoPath = parsed.pathname.replace(/^\/+/, '').replace(/\.git$/, '')
207
+ if (!host || !repoPath) return null
208
+ return `${host}/${repoPath}`
209
+ } catch (_error) {
210
+ return null
211
+ }
212
+ }
213
+
214
+ function hashRepoIdentifier(value) {
215
+ return crypto.createHash('sha256').update(value).digest('hex')
216
+ }
217
+
116
218
  function injectCollaborationSteps(workflowContent, options = {}) {
117
219
  const { enableSlackAlerts = false, enablePrComments = false } = options
118
220
  let updated = workflowContent
@@ -151,7 +253,44 @@ function injectCollaborationSteps(workflowContent, options = {}) {
151
253
  const safeReadDir = dir => {
152
254
  try {
153
255
  return fs.readdirSync(dir, { withFileTypes: true })
154
- } catch {
256
+ } catch (error) {
257
+ // ENOENT is expected (dir doesn't exist) - return empty silently
258
+ if (error?.code === 'ENOENT') {
259
+ if (process.env.DEBUG) {
260
+ console.warn(
261
+ ` Debug: safeReadDir(${dir}) returned empty (directory not found)`
262
+ )
263
+ }
264
+ return []
265
+ }
266
+
267
+ // All other errors are unexpected and should be logged with context
268
+ console.error(`❌ Failed to read directory: ${dir}`)
269
+ console.error(` Error: ${error.message} (${error.code || 'unknown'})`)
270
+ console.error(` This may indicate a permission or filesystem issue`)
271
+
272
+ // Report to error tracking in production
273
+ if (process.env.NODE_ENV === 'production') {
274
+ try {
275
+ const errorReporter = new ErrorReporter()
276
+ errorReporter.captureException(error, {
277
+ context: 'safeReadDir',
278
+ directory: dir,
279
+ errorCode: error.code,
280
+ })
281
+ } catch (_error) {
282
+ // Don't fail if error reporting fails
283
+ }
284
+ }
285
+
286
+ // Re-throw for critical errors instead of silently returning []
287
+ if (['EACCES', 'EIO', 'ELOOP', 'EMFILE'].includes(error.code)) {
288
+ throw new Error(
289
+ `Cannot read directory ${dir}: ${error.message}. ` +
290
+ `This may indicate a serious filesystem or permission issue.`
291
+ )
292
+ }
293
+
155
294
  return []
156
295
  }
157
296
  }
@@ -332,7 +471,14 @@ function parseArguments(rawArgs) {
332
471
  const isCheckMaturityMode = sanitizedArgs.includes('--check-maturity')
333
472
  const isValidateConfigMode = sanitizedArgs.includes('--validate-config')
334
473
  const isActivateLicenseMode = sanitizedArgs.includes('--activate-license')
474
+ const isAnalyzeCiMode = sanitizedArgs.includes('--analyze-ci')
475
+ const isPrelaunchMode = sanitizedArgs.includes('--prelaunch')
335
476
  const isDryRun = sanitizedArgs.includes('--dry-run')
477
+ const isWorkflowMinimal = sanitizedArgs.includes('--workflow-minimal')
478
+ const isWorkflowStandard = sanitizedArgs.includes('--workflow-standard')
479
+ const isWorkflowComprehensive = sanitizedArgs.includes(
480
+ '--workflow-comprehensive'
481
+ )
336
482
  const ciProviderIndex = sanitizedArgs.findIndex(arg => arg === '--ci')
337
483
  const ciProvider =
338
484
  ciProviderIndex !== -1 && sanitizedArgs[ciProviderIndex + 1]
@@ -344,11 +490,39 @@ function parseArguments(rawArgs) {
344
490
  // Custom template directory - use raw args to preserve valid path characters (&, <, >, etc.)
345
491
  // Normalize path to prevent traversal attacks and make absolute
346
492
  const templateFlagIndex = sanitizedArgs.findIndex(arg => arg === '--template')
347
- const customTemplatePath =
493
+ let customTemplatePath =
348
494
  templateFlagIndex !== -1 && rawArgs[templateFlagIndex + 1]
349
495
  ? path.resolve(rawArgs[templateFlagIndex + 1])
350
496
  : null
351
497
 
498
+ // Validate custom template path early to prevent path traversal attacks
499
+ if (customTemplatePath) {
500
+ const inputPath = rawArgs[templateFlagIndex + 1]
501
+ // Check for suspicious patterns (path traversal attempts)
502
+ if (inputPath.includes('..') || inputPath.includes('~')) {
503
+ console.error(
504
+ `❌ Invalid template path: "${inputPath}". Path traversal patterns not allowed.`
505
+ )
506
+ console.error(' Use absolute paths only (e.g., /Users/you/templates)')
507
+ process.exit(1)
508
+ }
509
+
510
+ // Verify the resolved path exists and is a directory
511
+ try {
512
+ const stats = fs.statSync(customTemplatePath)
513
+ if (!stats.isDirectory()) {
514
+ console.error(
515
+ `❌ Template path is not a directory: ${customTemplatePath}`
516
+ )
517
+ process.exit(1)
518
+ }
519
+ } catch (error) {
520
+ console.error(`❌ Template path does not exist: ${customTemplatePath}`)
521
+ console.error(` Error: ${error.message}`)
522
+ process.exit(1)
523
+ }
524
+ }
525
+
352
526
  // Granular tool disable options
353
527
  const disableNpmAudit = sanitizedArgs.includes('--no-npm-audit')
354
528
  const disableGitleaks = sanitizedArgs.includes('--no-gitleaks')
@@ -372,6 +546,8 @@ function parseArguments(rawArgs) {
372
546
  isCheckMaturityMode,
373
547
  isValidateConfigMode,
374
548
  isActivateLicenseMode,
549
+ isAnalyzeCiMode,
550
+ isPrelaunchMode,
375
551
  isDryRun,
376
552
  ciProvider,
377
553
  enableSlackAlerts,
@@ -383,6 +559,9 @@ function parseArguments(rawArgs) {
383
559
  disableMarkdownlint,
384
560
  disableEslintSecurity,
385
561
  allowLatestGitleaks,
562
+ isWorkflowMinimal,
563
+ isWorkflowStandard,
564
+ isWorkflowComprehensive,
386
565
  }
387
566
  }
388
567
 
@@ -410,6 +589,8 @@ function parseArguments(rawArgs) {
410
589
  isCheckMaturityMode,
411
590
  isValidateConfigMode,
412
591
  isActivateLicenseMode,
592
+ isPrelaunchMode,
593
+ isAnalyzeCiMode,
413
594
  isDryRun,
414
595
  ciProvider,
415
596
  enableSlackAlerts,
@@ -421,6 +602,9 @@ function parseArguments(rawArgs) {
421
602
  disableMarkdownlint,
422
603
  disableEslintSecurity,
423
604
  allowLatestGitleaks,
605
+ isWorkflowMinimal,
606
+ isWorkflowStandard,
607
+ isWorkflowComprehensive,
424
608
  } = parsedConfig
425
609
 
426
610
  // Initialize telemetry session (opt-in only, fails silently)
@@ -483,6 +667,8 @@ function parseArguments(rawArgs) {
483
667
  isCheckMaturityMode,
484
668
  isValidateConfigMode,
485
669
  isActivateLicenseMode,
670
+ isPrelaunchMode,
671
+ isAnalyzeCiMode,
486
672
  isDryRun,
487
673
  ciProvider,
488
674
  enableSlackAlerts,
@@ -494,6 +680,9 @@ function parseArguments(rawArgs) {
494
680
  disableMarkdownlint,
495
681
  disableEslintSecurity,
496
682
  allowLatestGitleaks,
683
+ isWorkflowMinimal,
684
+ isWorkflowStandard,
685
+ isWorkflowComprehensive,
497
686
  } = parsedConfig)
498
687
 
499
688
  console.log('📋 Configuration after interactive selections applied\n')
@@ -528,6 +717,15 @@ SETUP OPTIONS:
528
717
  --template <path> Use custom templates from specified directory
529
718
  --dry-run Preview changes without modifying files
530
719
 
720
+ WORKFLOW TIERS (GitHub Actions optimization):
721
+ --workflow-minimal Minimal CI (default) - Single Node version, weekly security
722
+ ~5-10 min/commit, ~$0-5/mo for typical projects
723
+ --workflow-standard Standard CI - Matrix testing on main, weekly security
724
+ ~15-20 min/commit, ~$5-20/mo for typical projects
725
+ --workflow-comprehensive Comprehensive CI - Matrix on every push, security inline
726
+ ~50-100 min/commit, ~$100-350/mo for typical projects
727
+ --analyze-ci Analyze GitHub Actions usage and get optimization tips (Pro)
728
+
531
729
  VALIDATION OPTIONS:
532
730
  --validate Run comprehensive validation (same as --comprehensive)
533
731
  --comprehensive Run all validation checks
@@ -535,6 +733,7 @@ VALIDATION OPTIONS:
535
733
  --validate-docs Run documentation validation only
536
734
  --validate-config Validate .qualityrc.json configuration file
537
735
  --check-maturity Detect and display project maturity level
736
+ --prelaunch Add pre-launch validation suite (SEO, links, a11y, docs)
538
737
 
539
738
  LICENSE, TELEMETRY & ERROR REPORTING:
540
739
  --license-status Show current license tier and available features
@@ -576,6 +775,9 @@ EXAMPLES:
576
775
  npx create-qa-architect@latest --check-maturity
577
776
  → Detect project maturity level (minimal, bootstrap, development, production-ready)
578
777
 
778
+ npx create-qa-architect@latest --prelaunch
779
+ → Add pre-launch validation: SEO (sitemap, robots, meta), links, a11y, docs
780
+
579
781
  npx create-qa-architect@latest --validate-config
580
782
  → Validate .qualityrc.json configuration file against JSON Schema
581
783
 
@@ -591,6 +793,18 @@ EXAMPLES:
591
793
  npx create-qa-architect@latest --dry-run
592
794
  → Preview what files and configurations would be created/modified
593
795
 
796
+ npx create-qa-architect@latest --workflow-minimal
797
+ → Set up with minimal CI (default) - fastest, cheapest, ideal for solo devs
798
+
799
+ npx create-qa-architect@latest --workflow-standard
800
+ → Set up with standard CI - balanced quality/cost for small teams
801
+
802
+ npx create-qa-architect@latest --update --workflow-minimal
803
+ → Convert existing comprehensive workflow to minimal (reduce CI costs)
804
+
805
+ npx create-qa-architect@latest --analyze-ci
806
+ → Analyze your GitHub Actions usage and get cost optimization recommendations (Pro)
807
+
594
808
  PRIVACY & TELEMETRY:
595
809
  Telemetry and error reporting are OPT-IN only (disabled by default). To enable:
596
810
  export QAA_TELEMETRY=true # Usage tracking (local only)
@@ -648,258 +862,8 @@ HELP:
648
862
  process.exit(0)
649
863
  }
650
864
 
651
- // Handle validation-only commands
652
- async function handleValidationCommands() {
653
- const validationOptions = {
654
- disableNpmAudit,
655
- disableGitleaks,
656
- disableActionlint,
657
- disableMarkdownlint,
658
- disableEslintSecurity,
659
- allowLatestGitleaks,
660
- }
661
- const validator = new ValidationRunner(validationOptions)
662
-
663
- if (isConfigSecurityMode) {
664
- try {
665
- await validator.runConfigSecurity()
666
- process.exit(0)
667
- } catch (error) {
668
- console.error(
669
- `\n❌ Configuration security validation failed:\n${error.message}`
670
- )
671
- process.exit(1)
672
- }
673
- }
674
-
675
- if (isDocsValidationMode) {
676
- try {
677
- await validator.runDocumentationValidation()
678
- process.exit(0)
679
- } catch (error) {
680
- console.error(`\n❌ Documentation validation failed:\n${error.message}`)
681
- process.exit(1)
682
- }
683
- }
684
-
685
- if (isComprehensiveMode || isValidationMode) {
686
- try {
687
- // Use parallel validation for 3-5x speedup (runs checks concurrently)
688
- await validator.runComprehensiveCheckParallel()
689
- process.exit(0)
690
- } catch (error) {
691
- console.error(`\n❌ Comprehensive validation failed:\n${error.message}`)
692
- process.exit(1)
693
- }
694
- }
695
- }
696
-
697
- // Detect Python project
698
- function detectPythonProject(projectPath) {
699
- const pythonFiles = [
700
- 'pyproject.toml',
701
- 'requirements.txt',
702
- 'setup.py',
703
- 'Pipfile',
704
- ]
705
- return pythonFiles.some(file => fs.existsSync(path.join(projectPath, file)))
706
- }
707
-
708
- // Detect Rust project
709
- function detectRustProject(projectPath) {
710
- return fs.existsSync(path.join(projectPath, 'Cargo.toml'))
711
- }
712
-
713
- // Detect Ruby project
714
- function detectRubyProject(projectPath) {
715
- return fs.existsSync(path.join(projectPath, 'Gemfile'))
716
- }
717
-
718
- // Handle dependency monitoring (Free/Pro/Team/Enterprise)
719
- async function handleDependencyMonitoring() {
720
- const projectPath = process.cwd()
721
- const license = getLicenseInfo()
722
-
723
- // Detect all supported ecosystems (npm, Python, Ruby, Rust, etc.)
724
- const hasNpm = hasNpmProject(projectPath)
725
- const hasPython = detectPythonProject(projectPath)
726
- const hasRust = detectRustProject(projectPath)
727
- const hasRuby = detectRubyProject(projectPath)
728
-
729
- if (!hasNpm && !hasPython && !hasRust && !hasRuby) {
730
- console.error(
731
- '❌ No supported dependency file found (package.json, pyproject.toml, requirements.txt, Gemfile, Cargo.toml).'
732
- )
733
- console.log("💡 Make sure you're in a directory with dependency files.")
734
- process.exit(1)
735
- }
736
-
737
- if (hasNpm) console.log('📦 Detected: npm project')
738
- if (hasPython) console.log('🐍 Detected: Python project')
739
- if (hasRust) console.log('🦀 Detected: Rust project')
740
- if (hasRuby) console.log('💎 Detected: Ruby project')
741
- console.log(`📋 License tier: ${license.tier.toUpperCase()}`)
742
-
743
- // Enforce Free tier caps for dependency monitoring (counted as dependency PRs)
744
- if (license.tier === 'FREE') {
745
- const capCheck = checkUsageCaps('dependency-pr')
746
- if (!capCheck.allowed) {
747
- console.error(`❌ ${capCheck.reason}`)
748
- console.error(
749
- ' Upgrade to Pro, Team, or Enterprise for unlimited runs: https://vibebuildlab.com/qa-architect'
750
- )
751
- process.exit(1)
752
- }
753
-
754
- const increment = incrementUsage('dependency-pr')
755
- const usage = increment.usage || capCheck.usage
756
- const caps = capCheck.caps
757
- if (usage && caps && caps.maxDependencyPRsPerMonth !== undefined) {
758
- console.log(
759
- `🧮 Usage: ${usage.dependencyPRs}/${caps.maxDependencyPRsPerMonth} dependency monitoring runs used this month`
760
- )
761
- }
762
- }
763
-
764
- const dependabotPath = path.join(projectPath, '.github', 'dependabot.yml')
765
-
766
- // Use premium or basic config based on license tier
767
- // Free beta ended with v4.1.1 - Premium features now require Pro/Team/Enterprise tier
768
- // Pro/Team/Enterprise: Framework-aware dependency monitoring with grouping
769
- // Free: Basic npm-only dependency monitoring
770
- const shouldUsePremium =
771
- license.tier === 'PRO' ||
772
- license.tier === 'TEAM' ||
773
- license.tier === 'ENTERPRISE'
774
-
775
- // Free tier only supports npm projects. Fail fast with a clear message.
776
- if (!shouldUsePremium && !hasNpm && (hasPython || hasRust || hasRuby)) {
777
- console.error(
778
- '❌ Dependency monitoring for this project requires a Pro, Team, or Enterprise license.'
779
- )
780
- console.error(
781
- ' Free tier supports npm projects only. Detected non-npm ecosystems.'
782
- )
783
- console.error(
784
- ' Options: add npm/package.json, or upgrade and re-run: npx create-qa-architect@latest --deps after activation.'
785
- )
786
- process.exit(1)
787
- }
788
-
789
- if (shouldUsePremium) {
790
- console.log(
791
- '\n🚀 Setting up framework-aware dependency monitoring (Premium)...\n'
792
- )
793
-
794
- const configData = generatePremiumDependabotConfig({
795
- projectPath,
796
- schedule: 'weekly',
797
- })
798
-
799
- if (configData) {
800
- const { ecosystems } = configData
801
- const ecosystemNames = Object.keys(ecosystems)
802
-
803
- if (ecosystemNames.length > 0) {
804
- console.log('🔍 Detected ecosystems:')
805
-
806
- // Find primary ecosystem and total package count
807
- let primaryEcosystem = null
808
- ecosystemNames.forEach(ecoName => {
809
- const eco = ecosystems[ecoName]
810
- const frameworks = Object.keys(eco.detected || {})
811
- const totalPackages = frameworks.reduce((sum, fw) => {
812
- return sum + (eco.detected[fw]?.count || 0)
813
- }, 0)
814
-
815
- console.log(` • ${ecoName}: ${totalPackages} packages`)
816
-
817
- if (eco.primary) {
818
- primaryEcosystem = ecoName
819
- }
820
- })
821
-
822
- if (primaryEcosystem) {
823
- console.log(`\n🎯 Primary ecosystem: ${primaryEcosystem}`)
824
- }
825
- }
826
-
827
- writePremiumDependabotConfig(configData, dependabotPath)
828
- console.log(
829
- '\n✅ Created .github/dependabot.yml with framework grouping'
830
- )
831
-
832
- console.log('\n🎉 Premium dependency monitoring setup complete!')
833
- console.log('\n📋 What was added (Pro Tier):')
834
- console.log(' • Framework-aware dependency grouping')
835
- console.log(
836
- ` • ${Object.keys(configData.config.updates[0].groups || {}).length} dependency groups created`
837
- )
838
- console.log(' • Intelligent update batching (reduces PRs by 60%+)')
839
- console.log(' • GitHub Actions dependency monitoring')
840
- }
841
- } else {
842
- console.log(
843
- '\n🔍 Setting up basic dependency monitoring (Free Tier)...\n'
844
- )
845
-
846
- const dependabotConfig = generateBasicDependabotConfig({
847
- projectPath,
848
- schedule: 'weekly',
849
- })
850
-
851
- if (dependabotConfig) {
852
- writeBasicDependabotConfig(dependabotConfig, dependabotPath)
853
- console.log('✅ Created .github/dependabot.yml')
854
- }
855
-
856
- console.log('\n🎉 Basic dependency monitoring setup complete!')
857
- console.log('\n📋 What was added (Free Tier):')
858
- console.log(' • Basic Dependabot configuration for npm packages')
859
- console.log(' • Weekly dependency updates on Monday 9am')
860
- console.log(' • GitHub Actions dependency monitoring')
861
-
862
- // Show upgrade message for premium features
863
- console.log('\n🔒 Premium features now available:')
864
- console.log(
865
- ' ✅ Framework-aware package grouping (React, Vue, Angular)'
866
- )
867
- console.log(' • Coming soon: Multi-language support (Python, Rust, Go)')
868
- console.log(' • Planned: Advanced security audit workflows')
869
- console.log(' • Planned: Custom update schedules and notifications')
870
-
871
- showUpgradeMessage('Framework-Aware Dependency Grouping')
872
- }
873
-
874
- // Auto-enable Dependabot on GitHub if token available
875
- console.log('\n🔧 Attempting to enable Dependabot on GitHub...')
876
- try {
877
- const { setupDependabot } = require('./lib/github-api')
878
- const result = await setupDependabot(projectPath, { verbose: true })
879
-
880
- if (result.success) {
881
- console.log('✅ Dependabot alerts and security updates enabled!')
882
- } else if (result.errors.length > 0) {
883
- console.log('⚠️ Could not auto-enable Dependabot:')
884
- result.errors.forEach(err => console.log(` • ${err}`))
885
- console.log('\n💡 Manual steps needed:')
886
- console.log(' • Go to GitHub repo → Settings → Code security')
887
- console.log(
888
- ' • Enable "Dependabot alerts" and "Dependabot security updates"'
889
- )
890
- }
891
- } catch (error) {
892
- console.log('⚠️ Could not auto-enable Dependabot:', error.message)
893
- console.log('\n💡 Manual steps:')
894
- console.log(' • Enable Dependabot in GitHub repo settings')
895
- }
896
-
897
- console.log('\n💡 Next steps:')
898
- console.log(' • Review and commit .github/dependabot.yml')
899
- console.log(
900
- ' • Dependabot will start monitoring weekly for dependency updates'
901
- )
902
- }
865
+ // Note: handleValidationCommands, handleDependencyMonitoring, detectPythonProject,
866
+ // detectRustProject, detectRubyProject are now imported from ./lib/commands
903
867
 
904
868
  // Handle license status command
905
869
  if (isLicenseStatusMode) {
@@ -948,6 +912,20 @@ HELP:
948
912
  process.exit(0)
949
913
  }
950
914
 
915
+ // Handle CI cost analysis command
916
+ if (isAnalyzeCiMode) {
917
+ return (async () => {
918
+ try {
919
+ const { handleAnalyzeCi } = require('./lib/commands/analyze-ci')
920
+ await handleAnalyzeCi()
921
+ process.exit(0)
922
+ } catch (error) {
923
+ console.error('CI cost analysis error:', error.message)
924
+ process.exit(1)
925
+ }
926
+ })()
927
+ }
928
+
951
929
  // Handle validate config command
952
930
  if (isValidateConfigMode) {
953
931
  const { validateAndReport } = require('./lib/config-validator')
@@ -969,6 +947,83 @@ HELP:
969
947
  })()
970
948
  }
971
949
 
950
+ // Handle pre-launch validation setup command
951
+ if (isPrelaunchMode) {
952
+ return (async () => {
953
+ try {
954
+ const projectPath = process.cwd()
955
+ const PackageJson = checkNodeVersionAndLoadPackageJson()
956
+ const pkgJson = await PackageJson.load(projectPath)
957
+ const license = getLicenseInfo()
958
+ const isPro =
959
+ license.tier === 'PRO' ||
960
+ license.tier === 'TEAM' ||
961
+ license.tier === 'ENTERPRISE'
962
+
963
+ console.log('\n📋 Setting up pre-launch validation suite...\n')
964
+ console.log(` License tier: ${license.tier.toUpperCase()}`)
965
+
966
+ // Check feature availability
967
+ if (!hasFeature('prelaunchValidation')) {
968
+ console.error('❌ Pre-launch validation requires a valid license.')
969
+ showUpgradeMessage('Pre-launch validation')
970
+ process.exit(1)
971
+ }
972
+
973
+ // Write validation scripts
974
+ const scriptsWritten = writeValidationScripts(projectPath)
975
+ console.log(` ✅ Created ${scriptsWritten.length} validation scripts`)
976
+
977
+ // Write pa11y config
978
+ writePa11yConfig(projectPath)
979
+ console.log(' ✅ Created .pa11yci config')
980
+
981
+ // Write env validator for Pro+
982
+ if (isPro && hasFeature('envValidation')) {
983
+ writeEnvValidator(projectPath)
984
+ console.log(' ✅ Created env vars validator (Pro)')
985
+ }
986
+
987
+ // Add scripts to package.json
988
+ const prelaunchScripts = getPrelaunchScripts(isPro)
989
+ const prelaunchDeps = getPrelaunchDependencies(isPro)
990
+
991
+ // Merge scripts
992
+ const existingScripts = pkgJson.content.scripts || {}
993
+ pkgJson.update({
994
+ scripts: { ...existingScripts, ...prelaunchScripts },
995
+ })
996
+
997
+ // Merge devDependencies
998
+ const existingDevDeps = pkgJson.content.devDependencies || {}
999
+ pkgJson.update({
1000
+ devDependencies: { ...existingDevDeps, ...prelaunchDeps },
1001
+ })
1002
+
1003
+ await pkgJson.save()
1004
+
1005
+ console.log('\n✅ Pre-launch validation setup complete!\n')
1006
+ console.log('Available scripts:')
1007
+ console.log(' npm run validate:sitemap - Check sitemap.xml')
1008
+ console.log(' npm run validate:robots - Check robots.txt')
1009
+ console.log(' npm run validate:meta - Check meta tags')
1010
+ console.log(' npm run validate:links - Check for broken links')
1011
+ console.log(' npm run validate:a11y - Run accessibility audit')
1012
+ console.log(' npm run validate:docs - Check documentation')
1013
+ if (isPro) {
1014
+ console.log(' npm run validate:env - Audit env vars (Pro)')
1015
+ }
1016
+ console.log(' npm run validate:prelaunch - Run all checks')
1017
+ console.log('\n💡 Run: npm install && npm run validate:prelaunch')
1018
+
1019
+ process.exit(0)
1020
+ } catch (error) {
1021
+ console.error('Pre-launch validation setup error:', error.message)
1022
+ process.exit(1)
1023
+ }
1024
+ })()
1025
+ }
1026
+
972
1027
  // Run validation commands if requested
973
1028
  if (
974
1029
  isValidationMode ||
@@ -979,13 +1034,392 @@ HELP:
979
1034
  // Handle validation commands and exit
980
1035
  return (async () => {
981
1036
  try {
982
- await handleValidationCommands()
1037
+ await handleValidationCommands({
1038
+ isConfigSecurityMode,
1039
+ isDocsValidationMode,
1040
+ isComprehensiveMode,
1041
+ isValidationMode,
1042
+ disableNpmAudit,
1043
+ disableGitleaks,
1044
+ disableActionlint,
1045
+ disableMarkdownlint,
1046
+ disableEslintSecurity,
1047
+ allowLatestGitleaks,
1048
+ })
983
1049
  } catch (error) {
984
1050
  console.error('Validation error:', error.message)
985
1051
  process.exit(1)
986
1052
  }
987
1053
  })()
988
1054
  } else {
1055
+ /**
1056
+ * Setup quality tools based on license tier
1057
+ * - Lighthouse CI (Free: basic, Pro: with thresholds)
1058
+ * - Bundle size limits (Pro only)
1059
+ * - axe-core accessibility (Free)
1060
+ * - Conventional commits (Free)
1061
+ * - Coverage thresholds (Pro only)
1062
+ */
1063
+ async function setupQualityTools(_usesTypeScript, _packageJson) {
1064
+ const qualitySpinner = showProgress('Setting up quality tools...')
1065
+
1066
+ try {
1067
+ const projectPath = process.cwd()
1068
+ const PackageJson = checkNodeVersionAndLoadPackageJson()
1069
+ const pkgJson = await PackageJson.load(projectPath)
1070
+ const addedTools = []
1071
+
1072
+ // Determine which features are available
1073
+ const hasLighthouse = hasFeature('lighthouseCI')
1074
+ const hasLighthouseThresholds = hasFeature('lighthouseThresholds')
1075
+ const hasBundleSizeLimits = hasFeature('bundleSizeLimits')
1076
+ const hasAxeAccessibility = hasFeature('axeAccessibility')
1077
+ const hasConventionalCommits = hasFeature('conventionalCommits')
1078
+ const hasCoverageThresholds = hasFeature('coverageThresholds')
1079
+
1080
+ // 1. Lighthouse CI - available to all, thresholds for Pro+
1081
+ if (hasLighthouse) {
1082
+ try {
1083
+ const lighthousePath = path.join(projectPath, 'lighthouserc.js')
1084
+ if (!fs.existsSync(lighthousePath)) {
1085
+ writeLighthouseConfig(projectPath, {
1086
+ hasThresholds: hasLighthouseThresholds,
1087
+ })
1088
+ addedTools.push(
1089
+ hasLighthouseThresholds
1090
+ ? 'Lighthouse CI (with thresholds)'
1091
+ : 'Lighthouse CI (basic)'
1092
+ )
1093
+ }
1094
+ } catch (error) {
1095
+ console.warn('⚠️ Failed to configure Lighthouse CI:', error.message)
1096
+ if (process.env.DEBUG) {
1097
+ console.error(' Stack:', error.stack)
1098
+ }
1099
+ }
1100
+ }
1101
+
1102
+ // 2. Bundle size limits - Pro only
1103
+ if (hasBundleSizeLimits) {
1104
+ try {
1105
+ if (!pkgJson.content['size-limit']) {
1106
+ writeSizeLimitConfig(projectPath)
1107
+ addedTools.push('Bundle size limits (size-limit)')
1108
+ }
1109
+ } catch (error) {
1110
+ console.warn(
1111
+ '⚠️ Failed to configure bundle size limits:',
1112
+ error.message
1113
+ )
1114
+ if (process.env.DEBUG) {
1115
+ console.error(' Stack:', error.stack)
1116
+ }
1117
+ }
1118
+ }
1119
+
1120
+ // 3. axe-core accessibility testing - available to all
1121
+ if (hasAxeAccessibility) {
1122
+ try {
1123
+ const axeTestPath = path.join(
1124
+ projectPath,
1125
+ 'tests',
1126
+ 'accessibility.test.js'
1127
+ )
1128
+ if (!fs.existsSync(axeTestPath)) {
1129
+ writeAxeTestSetup(projectPath)
1130
+ addedTools.push('axe-core accessibility tests')
1131
+ }
1132
+ } catch (error) {
1133
+ console.warn(
1134
+ '⚠️ Failed to configure axe-core tests:',
1135
+ error.message
1136
+ )
1137
+ if (process.env.DEBUG) {
1138
+ console.error(' Stack:', error.stack)
1139
+ }
1140
+ }
1141
+ }
1142
+
1143
+ // 4. Conventional commits (commitlint) - available to all
1144
+ if (hasConventionalCommits) {
1145
+ try {
1146
+ const commitlintPath = path.join(
1147
+ projectPath,
1148
+ 'commitlint.config.js'
1149
+ )
1150
+ if (!fs.existsSync(commitlintPath)) {
1151
+ writeCommitlintConfig(projectPath)
1152
+ writeCommitMsgHook(projectPath)
1153
+ addedTools.push('Conventional commits (commitlint)')
1154
+ }
1155
+ } catch (error) {
1156
+ console.warn('⚠️ Failed to configure commitlint:', error.message)
1157
+ if (process.env.DEBUG) {
1158
+ console.error(' Stack:', error.stack)
1159
+ }
1160
+ }
1161
+ }
1162
+
1163
+ // 5. Coverage thresholds - Pro only (info message handled elsewhere)
1164
+ if (hasCoverageThresholds) {
1165
+ addedTools.push('Coverage thresholds (70% lines, 70% functions)')
1166
+ }
1167
+
1168
+ // Add dependencies for enabled features
1169
+ const deps = getQualityToolsDependencies({
1170
+ lighthouse: hasLighthouse,
1171
+ sizeLimit: hasBundleSizeLimits,
1172
+ commitlint: hasConventionalCommits,
1173
+ axeCore: hasAxeAccessibility,
1174
+ })
1175
+
1176
+ // Add scripts for enabled features
1177
+ const scripts = getQualityToolsScripts({
1178
+ lighthouse: hasLighthouse,
1179
+ sizeLimit: hasBundleSizeLimits,
1180
+ axeCore: hasAxeAccessibility,
1181
+ coverage: hasCoverageThresholds,
1182
+ })
1183
+
1184
+ // Merge dependencies and scripts
1185
+ pkgJson.content.devDependencies = mergeDevDependencies(
1186
+ pkgJson.content.devDependencies || {},
1187
+ deps
1188
+ )
1189
+ pkgJson.content.scripts = mergeScripts(
1190
+ pkgJson.content.scripts || {},
1191
+ scripts
1192
+ )
1193
+ await pkgJson.save()
1194
+
1195
+ if (addedTools.length > 0) {
1196
+ qualitySpinner.succeed(
1197
+ `Quality tools configured: ${addedTools.length} tools`
1198
+ )
1199
+ addedTools.forEach(tool => console.log(` ✅ ${tool}`))
1200
+
1201
+ // Show Pro upsell for missing features
1202
+ if (!hasBundleSizeLimits || !hasCoverageThresholds) {
1203
+ console.log('\n💎 Upgrade to Pro for additional quality tools:')
1204
+ if (!hasBundleSizeLimits) {
1205
+ console.log(' • Bundle size limits (size-limit)')
1206
+ }
1207
+ if (!hasCoverageThresholds) {
1208
+ console.log(' • Coverage threshold enforcement')
1209
+ }
1210
+ }
1211
+ } else {
1212
+ qualitySpinner.succeed('Quality tools already configured')
1213
+ }
1214
+ } catch (error) {
1215
+ qualitySpinner.fail('Quality tools setup failed')
1216
+ console.error(
1217
+ `❌ Unexpected error during quality tools setup: ${error.message}`
1218
+ )
1219
+ if (process.env.DEBUG) {
1220
+ console.error(' Stack:', error.stack)
1221
+ }
1222
+ console.error(
1223
+ ' Please report this issue at https://github.com/your-repo/issues'
1224
+ )
1225
+ throw error // Re-throw to prevent silent continuation
1226
+ }
1227
+ }
1228
+
1229
+ /**
1230
+ * Detect existing workflow mode from quality.yml
1231
+ * @param {string} projectPath - Path to the project
1232
+ * @returns {'minimal'|'standard'|'comprehensive'|null} Detected mode
1233
+ */
1234
+ function detectExistingWorkflowMode(projectPath) {
1235
+ const workflowPath = path.join(
1236
+ projectPath,
1237
+ '.github',
1238
+ 'workflows',
1239
+ 'quality.yml'
1240
+ )
1241
+
1242
+ if (!fs.existsSync(workflowPath)) {
1243
+ return null
1244
+ }
1245
+
1246
+ try {
1247
+ const content = fs.readFileSync(workflowPath, 'utf8')
1248
+
1249
+ // Check for version markers (new format)
1250
+ if (content.includes('# WORKFLOW_MODE: minimal')) {
1251
+ return 'minimal'
1252
+ }
1253
+ if (content.includes('# WORKFLOW_MODE: standard')) {
1254
+ return 'standard'
1255
+ }
1256
+ if (content.includes('# WORKFLOW_MODE: comprehensive')) {
1257
+ return 'comprehensive'
1258
+ }
1259
+
1260
+ // Legacy detection (no version marker)
1261
+ // Comprehensive: has security job + matrix testing on every push
1262
+ // Standard: has matrix testing but security is scheduled
1263
+ // Minimal: no matrix, single node version
1264
+ const hasSecurityJob = /jobs:\s*\n\s*security:/m.test(content)
1265
+ const hasMatrixInTests = /tests:[\s\S]*?strategy:[\s\S]*?matrix:/m.test(
1266
+ content
1267
+ )
1268
+ const hasScheduledSecurity =
1269
+ /on:\s*\n\s*schedule:[\s\S]*?- cron:/m.test(content)
1270
+
1271
+ if (hasSecurityJob && hasMatrixInTests && !hasScheduledSecurity) {
1272
+ return 'comprehensive'
1273
+ }
1274
+ if (hasMatrixInTests && hasScheduledSecurity) {
1275
+ return 'standard'
1276
+ }
1277
+ if (!hasMatrixInTests) {
1278
+ return 'minimal'
1279
+ }
1280
+
1281
+ // Default to comprehensive for unknown patterns
1282
+ return 'comprehensive'
1283
+ } catch (error) {
1284
+ console.warn(
1285
+ `⚠️ Could not detect existing workflow mode: ${error.message}`
1286
+ )
1287
+ return null
1288
+ }
1289
+ }
1290
+
1291
+ /**
1292
+ * Inject workflow mode-specific configuration into quality.yml
1293
+ * @param {string} workflowContent - Template content
1294
+ * @param {'minimal'|'standard'|'comprehensive'} mode - Selected mode
1295
+ * @returns {string} Modified workflow content
1296
+ */
1297
+ function injectWorkflowMode(workflowContent, mode) {
1298
+ let updated = workflowContent
1299
+
1300
+ // Add version marker at the top (after name section)
1301
+ const versionMarker = `# WORKFLOW_MODE: ${mode}`
1302
+ if (!updated.includes('# WORKFLOW_MODE:')) {
1303
+ // Insert after the comment block and before jobs
1304
+ updated = updated.replace(/(\n\njobs:)/, `\n${versionMarker}\n$1`)
1305
+ }
1306
+
1307
+ // Replace PATH_FILTERS_PLACEHOLDER
1308
+ if (updated.includes('# PATH_FILTERS_PLACEHOLDER')) {
1309
+ if (mode === 'minimal' || mode === 'standard') {
1310
+ const pathsIgnore = `paths-ignore:
1311
+ - '**.md'
1312
+ - 'docs/**'
1313
+ - 'LICENSE'
1314
+ - '.gitignore'
1315
+ - '.editorconfig'`
1316
+ updated = updated.replace('# PATH_FILTERS_PLACEHOLDER', pathsIgnore)
1317
+ } else {
1318
+ // comprehensive: Remove placeholder
1319
+ updated = updated.replace(/\s*# PATH_FILTERS_PLACEHOLDER\n?/, '')
1320
+ }
1321
+ }
1322
+
1323
+ // Replace SECURITY_SCHEDULE_PLACEHOLDER
1324
+ if (updated.includes('# SECURITY_SCHEDULE_PLACEHOLDER')) {
1325
+ if (mode === 'minimal' || mode === 'standard') {
1326
+ // Add weekly schedule for security scans
1327
+ const scheduleConfig = `schedule:
1328
+ - cron: '0 0 * * 0' # Weekly on Sunday (security scans)
1329
+ workflow_dispatch: # Manual trigger`
1330
+ updated = updated.replace(
1331
+ '# SECURITY_SCHEDULE_PLACEHOLDER',
1332
+ scheduleConfig
1333
+ )
1334
+ } else {
1335
+ // comprehensive: Remove placeholder
1336
+ updated = updated.replace(/\s*# SECURITY_SCHEDULE_PLACEHOLDER\n?/, '')
1337
+ }
1338
+ }
1339
+
1340
+ // Replace SECURITY_CONDITION_PLACEHOLDER
1341
+ if (updated.includes('# SECURITY_CONDITION_PLACEHOLDER')) {
1342
+ if (mode === 'minimal' || mode === 'standard') {
1343
+ // Only run security on schedule events (not on every push)
1344
+ // Replace both the placeholder comment AND the existing if line
1345
+ updated = updated.replace(
1346
+ /# SECURITY_CONDITION_PLACEHOLDER\n\s*if: needs\.detect-maturity\.outputs\.has-deps == 'true'/,
1347
+ `if: (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && needs.detect-maturity.outputs.has-deps == 'true'`
1348
+ )
1349
+ } else {
1350
+ // comprehensive: Remove placeholder, keep the existing if condition
1351
+ updated = updated.replace(
1352
+ /\s*# SECURITY_CONDITION_PLACEHOLDER\n?/,
1353
+ ''
1354
+ )
1355
+ }
1356
+ }
1357
+
1358
+ // Replace MATRIX_PLACEHOLDER
1359
+ if (updated.includes('# MATRIX_PLACEHOLDER')) {
1360
+ if (mode === 'minimal') {
1361
+ // Single Node version, no matrix
1362
+ // Replace both the placeholder comment AND the existing strategy block
1363
+ updated = updated.replace(
1364
+ /# MATRIX_PLACEHOLDER\n\s*strategy:\n\s*fail-fast: false/,
1365
+ `strategy:
1366
+ fail-fast: false
1367
+ matrix:
1368
+ node-version: [22]`
1369
+ )
1370
+ } else if (mode === 'standard') {
1371
+ // Matrix testing only on main branch
1372
+ // 1. Modify the job-level if condition to add branch check
1373
+ updated = updated.replace(
1374
+ /if: fromJSON\(needs\.detect-maturity\.outputs\.test-count\) > 0\n\s*# TESTS_CONDITION_PLACEHOLDER/,
1375
+ `if: github.ref == 'refs/heads/main' && fromJSON(needs.detect-maturity.outputs.test-count) > 0
1376
+ # TESTS_CONDITION_PLACEHOLDER`
1377
+ )
1378
+ // 2. Replace matrix placeholder and existing strategy block
1379
+ updated = updated.replace(
1380
+ /# MATRIX_PLACEHOLDER\n\s*strategy:\n\s*fail-fast: false/,
1381
+ `strategy:
1382
+ fail-fast: false
1383
+ matrix:
1384
+ node-version: [20, 22]`
1385
+ )
1386
+ } else {
1387
+ // comprehensive: Matrix on every push
1388
+ // Replace placeholder, merge with existing strategy
1389
+ updated = updated.replace(
1390
+ /# MATRIX_PLACEHOLDER\n\s*strategy:\n\s*fail-fast: false/,
1391
+ `strategy:
1392
+ fail-fast: false
1393
+ matrix:
1394
+ node-version: [20, 22]`
1395
+ )
1396
+ }
1397
+ }
1398
+
1399
+ // Replace TESTS_CONDITION_PLACEHOLDER
1400
+ if (updated.includes('# TESTS_CONDITION_PLACEHOLDER')) {
1401
+ let testsCondition = ''
1402
+
1403
+ if (mode === 'minimal') {
1404
+ // No matrix, always run
1405
+ testsCondition = '# Runs on every push (single Node version)'
1406
+ } else if (mode === 'standard') {
1407
+ // Matrix only on main
1408
+ testsCondition = '# Matrix testing only on main branch'
1409
+ } else {
1410
+ // comprehensive: Matrix on every push
1411
+ testsCondition = '# Matrix testing on every push'
1412
+ }
1413
+
1414
+ updated = updated.replace(
1415
+ '# TESTS_CONDITION_PLACEHOLDER',
1416
+ testsCondition
1417
+ )
1418
+ }
1419
+
1420
+ return updated
1421
+ }
1422
+
989
1423
  // Normal setup flow
990
1424
  async function runMainSetup() {
991
1425
  // Record telemetry start event (opt-in only, fails silently)
@@ -1000,7 +1434,7 @@ HELP:
1000
1434
  try {
1001
1435
  execSync('git status', { stdio: 'ignore' })
1002
1436
  gitSpinner.succeed('Git repository verified')
1003
- } catch {
1437
+ } catch (_error) {
1004
1438
  gitSpinner.fail('Not a git repository')
1005
1439
  console.error('❌ This must be run in a git repository')
1006
1440
  console.log('Run "git init" first, then try again.')
@@ -1010,6 +1444,8 @@ HELP:
1010
1444
  // Enforce FREE tier repo limit (1 private repo)
1011
1445
  // Must happen before any file modifications
1012
1446
  const license = getLicenseInfo()
1447
+ let pendingRepoRegistration = null
1448
+ let pendingRepoUsageSnapshot = null
1013
1449
  if (license.tier === 'FREE') {
1014
1450
  // Generate unique repo ID from git remote or directory name
1015
1451
  let repoId
@@ -1018,10 +1454,11 @@ HELP:
1018
1454
  encoding: 'utf8',
1019
1455
  stdio: ['pipe', 'pipe', 'ignore'],
1020
1456
  }).trim()
1021
- repoId = remoteUrl
1022
- } catch {
1457
+ const normalized = normalizeRepoIdentifier(remoteUrl)
1458
+ repoId = hashRepoIdentifier(normalized || remoteUrl)
1459
+ } catch (_error) {
1023
1460
  // No remote - use absolute path as fallback
1024
- repoId = process.cwd()
1461
+ repoId = hashRepoIdentifier(process.cwd())
1025
1462
  }
1026
1463
 
1027
1464
  const repoCheck = checkUsageCaps('repo')
@@ -1037,11 +1474,8 @@ HELP:
1037
1474
  process.exit(1)
1038
1475
  }
1039
1476
 
1040
- // Register this repo
1041
- incrementUsage('repo', 1, repoId)
1042
- console.log(
1043
- `✅ Registered repo (FREE tier: ${(repoCheck.usage?.repoCount || 0) + 1}/1 repos used)`
1044
- )
1477
+ pendingRepoRegistration = repoId
1478
+ pendingRepoUsageSnapshot = repoCheck.usage
1045
1479
  }
1046
1480
  }
1047
1481
 
@@ -1133,7 +1567,7 @@ HELP:
1133
1567
  const hasTypeScriptDependency = Boolean(
1134
1568
  (packageJson.devDependencies &&
1135
1569
  packageJson.devDependencies.typescript) ||
1136
- (packageJson.dependencies && packageJson.dependencies.typescript)
1570
+ (packageJson.dependencies && packageJson.dependencies.typescript)
1137
1571
  )
1138
1572
 
1139
1573
  const tsconfigCandidates = ['tsconfig.json', 'tsconfig.base.json']
@@ -1204,7 +1638,17 @@ HELP:
1204
1638
  }
1205
1639
 
1206
1640
  return false
1207
- } catch {
1641
+ } catch (error) {
1642
+ // Silent failure fix: Log unexpected errors in debug mode
1643
+ if (
1644
+ process.env.DEBUG &&
1645
+ error.code !== 'ENOENT' &&
1646
+ error.code !== 'EACCES'
1647
+ ) {
1648
+ console.warn(
1649
+ `⚠️ Could not scan ${dir} for Python files: ${error.message}`
1650
+ )
1651
+ }
1208
1652
  return false
1209
1653
  }
1210
1654
  }
@@ -1218,6 +1662,19 @@ HELP:
1218
1662
  )
1219
1663
  }
1220
1664
 
1665
+ // Shell project detection
1666
+ const { ProjectMaturityDetector } = require('./lib/project-maturity')
1667
+ const maturityDetector = new ProjectMaturityDetector({
1668
+ projectPath: process.cwd(),
1669
+ })
1670
+ const projectStats = maturityDetector.analyzeProject()
1671
+ const usesShell = projectStats.isShellProject
1672
+ if (usesShell) {
1673
+ console.log(
1674
+ '🐚 Detected shell script project; enabling shell quality automation'
1675
+ )
1676
+ }
1677
+
1221
1678
  const stylelintTargets = findStylelintTargets(process.cwd())
1222
1679
  const usingDefaultStylelintTarget =
1223
1680
  stylelintTargets.length === 1 &&
@@ -1370,29 +1827,44 @@ HELP:
1370
1827
 
1371
1828
  // Validate the generated config
1372
1829
  const validationResult = validateQualityConfig(qualityrcPath)
1830
+ if (!validationResult.valid) {
1831
+ console.error(
1832
+ '\n❌ CRITICAL: Generated .qualityrc.json failed validation'
1833
+ )
1834
+ console.error(
1835
+ ' This should never happen. Please report this bug.\n'
1836
+ )
1837
+
1838
+ console.error('Validation errors:')
1839
+ validationResult.errors.forEach((error, index) => {
1840
+ console.error(` ${index + 1}. ${error}`)
1841
+ })
1842
+
1843
+ console.error(`\n🐛 Report issue with this info:`)
1844
+ console.error(` • File: ${qualityrcPath}`)
1845
+ console.error(` • Detected maturity: ${detectedMaturity}`)
1846
+ console.error(` • Error count: ${validationResult.errors.length}`)
1847
+ console.error(
1848
+ ` • https://github.com/vibebuildlab/qa-architect/issues/new\n`
1849
+ )
1850
+
1851
+ // Don't continue - this is a bug in the tool itself
1852
+ throw new Error('Invalid quality config generated - cannot continue')
1853
+ }
1854
+ } else {
1855
+ // TD8 fix: Re-enabled validation (was disabled for debugging)
1856
+ const validationResult = validateQualityConfig(qualityrcPath)
1373
1857
  if (!validationResult.valid) {
1374
1858
  console.warn(
1375
- '⚠️ Warning: Generated config has validation issues (this should not happen):'
1859
+ '⚠️ Warning: Existing .qualityrc.json has validation issues:'
1376
1860
  )
1377
1861
  validationResult.errors.forEach(error => {
1378
1862
  console.warn(` - ${error}`)
1379
1863
  })
1864
+ console.warn(
1865
+ ' Setup will continue, but you may want to fix these issues.\n'
1866
+ )
1380
1867
  }
1381
- } else {
1382
- // Config exists, validate it
1383
- // Temporarily disabled - debugging
1384
- // const validationResult = validateQualityConfig(qualityrcPath)
1385
- // if (!validationResult.valid) {
1386
- // console.warn(
1387
- // '⚠️ Warning: Existing .qualityrc.json has validation issues:'
1388
- // )
1389
- // validationResult.errors.forEach(error => {
1390
- // console.warn(` - ${error}`)
1391
- // })
1392
- // console.warn(
1393
- // ' Setup will continue, but you may want to fix these issues.\n'
1394
- // )
1395
- // }
1396
1868
  }
1397
1869
 
1398
1870
  // Load and merge templates (custom + defaults)
@@ -1471,6 +1943,23 @@ HELP:
1471
1943
  }
1472
1944
 
1473
1945
  const workflowFile = path.join(githubWorkflowDir, 'quality.yml')
1946
+
1947
+ // Determine workflow mode
1948
+ let workflowMode = 'minimal' // Default to minimal
1949
+ if (isWorkflowMinimal) {
1950
+ workflowMode = 'minimal'
1951
+ } else if (isWorkflowStandard) {
1952
+ workflowMode = 'standard'
1953
+ } else if (isWorkflowComprehensive) {
1954
+ workflowMode = 'comprehensive'
1955
+ } else if (fs.existsSync(workflowFile)) {
1956
+ // Detect existing mode when updating
1957
+ const existingMode = detectExistingWorkflowMode(process.cwd())
1958
+ if (existingMode) {
1959
+ workflowMode = existingMode
1960
+ }
1961
+ }
1962
+
1474
1963
  if (!fs.existsSync(workflowFile)) {
1475
1964
  let templateWorkflow =
1476
1965
  templateLoader.getTemplate(
@@ -1482,13 +1971,59 @@ HELP:
1482
1971
  'utf8'
1483
1972
  )
1484
1973
 
1974
+ // Inject workflow mode configuration
1975
+ templateWorkflow = injectWorkflowMode(templateWorkflow, workflowMode)
1976
+
1977
+ // Inject collaboration steps
1485
1978
  templateWorkflow = injectCollaborationSteps(templateWorkflow, {
1486
1979
  enableSlackAlerts,
1487
1980
  enablePrComments,
1488
1981
  })
1489
1982
 
1490
1983
  fs.writeFileSync(workflowFile, templateWorkflow)
1491
- console.log('✅ Added GitHub Actions workflow')
1984
+ console.log(`✅ Added GitHub Actions workflow (${workflowMode} mode)`)
1985
+ } else if (isUpdateMode) {
1986
+ // Update existing workflow with new mode if explicitly specified
1987
+ if (
1988
+ isWorkflowMinimal ||
1989
+ isWorkflowStandard ||
1990
+ isWorkflowComprehensive
1991
+ ) {
1992
+ // Load fresh template and re-inject
1993
+ let templateWorkflow =
1994
+ templateLoader.getTemplate(
1995
+ templates,
1996
+ path.join('.github', 'workflows', 'quality.yml')
1997
+ ) ||
1998
+ fs.readFileSync(
1999
+ path.join(__dirname, '.github/workflows/quality.yml'),
2000
+ 'utf8'
2001
+ )
2002
+
2003
+ // Inject workflow mode configuration
2004
+ templateWorkflow = injectWorkflowMode(
2005
+ templateWorkflow,
2006
+ workflowMode
2007
+ )
2008
+
2009
+ // Inject collaboration steps (preserve from existing if present)
2010
+ const existingWorkflow = fs.readFileSync(workflowFile, 'utf8')
2011
+ const hasSlackAlerts =
2012
+ existingWorkflow.includes('SLACK_WEBHOOK_URL')
2013
+ const hasPrComments = existingWorkflow.includes(
2014
+ 'PR_COMMENT_PLACEHOLDER'
2015
+ )
2016
+
2017
+ templateWorkflow = injectCollaborationSteps(templateWorkflow, {
2018
+ enableSlackAlerts: hasSlackAlerts,
2019
+ enablePrComments: hasPrComments,
2020
+ })
2021
+
2022
+ fs.writeFileSync(workflowFile, templateWorkflow)
2023
+ console.log(
2024
+ `♻️ Updated GitHub Actions workflow to ${workflowMode} mode`
2025
+ )
2026
+ }
1492
2027
  }
1493
2028
  }
1494
2029
 
@@ -1699,7 +2234,7 @@ let tier = 'FREE'
1699
2234
  try {
1700
2235
  const data = JSON.parse(fs.readFileSync(licenseFile, 'utf8'))
1701
2236
  tier = (data && data.tier) || 'FREE'
1702
- } catch {
2237
+ } catch (error) {
1703
2238
  tier = 'FREE'
1704
2239
  }
1705
2240
 
@@ -1712,7 +2247,7 @@ try {
1712
2247
  if (data.month === currentMonth) {
1713
2248
  usage = { ...usage, ...data }
1714
2249
  }
1715
- } catch {
2250
+ } catch (error) {
1716
2251
  // First run or corrupt file – start fresh
1717
2252
  }
1718
2253
 
@@ -1900,6 +2435,86 @@ echo "✅ Pre-push validation passed!"
1900
2435
  }
1901
2436
  }
1902
2437
 
2438
+ pythonSpinner.succeed('Python quality tools configured')
2439
+ }
2440
+
2441
+ // Shell project setup
2442
+ if (usesShell) {
2443
+ // Copy Shell CI workflow (GitHub Actions only)
2444
+ if (ciProvider === 'github') {
2445
+ const shellCiWorkflowFile = path.join(
2446
+ githubWorkflowDir,
2447
+ 'shell-ci.yml'
2448
+ )
2449
+ if (!fs.existsSync(shellCiWorkflowFile)) {
2450
+ const templateShellCiWorkflow =
2451
+ templateLoader.getTemplate(
2452
+ templates,
2453
+ path.join('config', 'shell-ci.yml')
2454
+ ) ||
2455
+ fs.readFileSync(
2456
+ path.join(__dirname, 'config/shell-ci.yml'),
2457
+ 'utf8'
2458
+ )
2459
+ fs.writeFileSync(shellCiWorkflowFile, templateShellCiWorkflow)
2460
+ console.log('✅ Added Shell CI GitHub Actions workflow')
2461
+ }
2462
+
2463
+ // Copy Shell Quality workflow
2464
+ const shellQualityWorkflowFile = path.join(
2465
+ githubWorkflowDir,
2466
+ 'shell-quality.yml'
2467
+ )
2468
+ if (!fs.existsSync(shellQualityWorkflowFile)) {
2469
+ const templateShellQualityWorkflow =
2470
+ templateLoader.getTemplate(
2471
+ templates,
2472
+ path.join('config', 'shell-quality.yml')
2473
+ ) ||
2474
+ fs.readFileSync(
2475
+ path.join(__dirname, 'config/shell-quality.yml'),
2476
+ 'utf8'
2477
+ )
2478
+ fs.writeFileSync(
2479
+ shellQualityWorkflowFile,
2480
+ templateShellQualityWorkflow
2481
+ )
2482
+ console.log('✅ Added Shell Quality GitHub Actions workflow')
2483
+ }
2484
+ }
2485
+
2486
+ // Create a basic README if it doesn't exist
2487
+ const readmePath = path.join(process.cwd(), 'README.md')
2488
+ if (!fs.existsSync(readmePath)) {
2489
+ const projectName = path.basename(process.cwd())
2490
+ const basicReadme = `# ${projectName}
2491
+
2492
+ Shell script collection for ${projectName}.
2493
+
2494
+ ## Usage
2495
+
2496
+ \`\`\`bash
2497
+ # Make scripts executable
2498
+ chmod +x *.sh
2499
+
2500
+ # Run a script
2501
+ ./script-name.sh
2502
+ \`\`\`
2503
+
2504
+ ## Development
2505
+
2506
+ Quality checks are automated via GitHub Actions:
2507
+ - ShellCheck linting
2508
+ - Syntax validation
2509
+ - Permission checks
2510
+ - Best practices analysis
2511
+ `
2512
+ fs.writeFileSync(readmePath, basicReadme)
2513
+ console.log('✅ Created basic README.md')
2514
+ }
2515
+ }
2516
+
2517
+ if (usesPython) {
1903
2518
  // Create tests directory if it doesn't exist
1904
2519
  const testsDir = path.join(process.cwd(), 'tests')
1905
2520
  if (!fs.existsSync(testsDir)) {
@@ -1942,8 +2557,6 @@ echo "✅ Pre-push validation passed!"
1942
2557
  )
1943
2558
  }
1944
2559
  }
1945
-
1946
- pythonSpinner.succeed('Python quality tools configured')
1947
2560
  }
1948
2561
 
1949
2562
  // Smart Test Strategy (Pro/Team/Enterprise feature)
@@ -2007,6 +2620,9 @@ echo "✅ Pre-push validation passed!"
2007
2620
  showUpgradeMessage('Smart Test Strategy')
2008
2621
  }
2009
2622
 
2623
+ // Quality Tools Integration
2624
+ await setupQualityTools(usesTypeScript, packageJson)
2625
+
2010
2626
  // Generate placeholder test file with helpful documentation
2011
2627
  const testsDir = path.join(process.cwd(), 'tests')
2012
2628
  const testExtension = usesTypeScript ? 'ts' : 'js'
@@ -2106,6 +2722,12 @@ describe('Test framework validation', () => {
2106
2722
 
2107
2723
  console.log('\n🎉 Quality automation setup complete!')
2108
2724
 
2725
+ if (pendingRepoRegistration) {
2726
+ incrementUsage('repo', 1, pendingRepoRegistration)
2727
+ const repoCount = (pendingRepoUsageSnapshot?.repoCount || 0) + 1
2728
+ console.log(`✅ Registered repo (FREE tier: ${repoCount}/1 repos used)`)
2729
+ }
2730
+
2109
2731
  // Record telemetry completion event (opt-in only, fails silently)
2110
2732
  telemetry.recordComplete({
2111
2733
  usesPython,