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
@@ -0,0 +1,293 @@
1
+ /**
2
+ * Dependency monitoring command handler
3
+ *
4
+ * Extracted from setup.js to improve maintainability.
5
+ * Handles --deps, --dependency-monitoring commands.
6
+ */
7
+
8
+ const fs = require('fs')
9
+ const path = require('path')
10
+
11
+ const {
12
+ hasNpmProject,
13
+ generateBasicDependabotConfig,
14
+ writeBasicDependabotConfig,
15
+ } = require('../dependency-monitoring-basic')
16
+
17
+ const {
18
+ generatePremiumDependabotConfig,
19
+ writePremiumDependabotConfig,
20
+ } = require('../dependency-monitoring-premium')
21
+
22
+ const {
23
+ getLicenseInfo,
24
+ showUpgradeMessage,
25
+ checkUsageCaps,
26
+ incrementUsage,
27
+ } = require('../licensing')
28
+
29
+ /**
30
+ * Detect Python project
31
+ * @param {string} projectPath - Path to project
32
+ * @returns {boolean} True if Python project detected
33
+ */
34
+ function detectPythonProject(projectPath) {
35
+ const pythonFiles = [
36
+ 'pyproject.toml',
37
+ 'requirements.txt',
38
+ 'setup.py',
39
+ 'Pipfile',
40
+ ]
41
+ return pythonFiles.some(file => fs.existsSync(path.join(projectPath, file)))
42
+ }
43
+
44
+ /**
45
+ * Detect Rust project
46
+ * @param {string} projectPath - Path to project
47
+ * @returns {boolean} True if Rust project detected
48
+ */
49
+ function detectRustProject(projectPath) {
50
+ return fs.existsSync(path.join(projectPath, 'Cargo.toml'))
51
+ }
52
+
53
+ /**
54
+ * Detect Ruby project
55
+ * @param {string} projectPath - Path to project
56
+ * @returns {boolean} True if Ruby project detected
57
+ */
58
+ function detectRubyProject(projectPath) {
59
+ return fs.existsSync(path.join(projectPath, 'Gemfile'))
60
+ }
61
+
62
+ /**
63
+ * Handle dependency monitoring command (Free/Pro/Team/Enterprise)
64
+ */
65
+ async function handleDependencyMonitoring() {
66
+ const projectPath = process.cwd()
67
+ const license = getLicenseInfo()
68
+
69
+ // Detect all supported ecosystems (npm, Python, Ruby, Rust, etc.)
70
+ const hasNpm = hasNpmProject(projectPath)
71
+ const hasPython = detectPythonProject(projectPath)
72
+ const hasRust = detectRustProject(projectPath)
73
+ const hasRuby = detectRubyProject(projectPath)
74
+
75
+ if (!hasNpm && !hasPython && !hasRust && !hasRuby) {
76
+ console.error(
77
+ '❌ No supported dependency file found (package.json, pyproject.toml, requirements.txt, Gemfile, Cargo.toml).'
78
+ )
79
+ console.log("💡 Make sure you're in a directory with dependency files.")
80
+ process.exit(1)
81
+ }
82
+
83
+ if (hasNpm) console.log('📦 Detected: npm project')
84
+ if (hasPython) console.log('🐍 Detected: Python project')
85
+ if (hasRust) console.log('🦀 Detected: Rust project')
86
+ if (hasRuby) console.log('💎 Detected: Ruby project')
87
+ console.log(`📋 License tier: ${license.tier.toUpperCase()}`)
88
+
89
+ // Use sentinel value instead of null for consistent access patterns
90
+ const capCheck =
91
+ license.tier === 'FREE'
92
+ ? checkUsageCaps('dependency-pr')
93
+ : { allowed: true, usage: {}, caps: {} }
94
+
95
+ if (!capCheck.allowed) {
96
+ console.error(`❌ ${capCheck.reason}`)
97
+ console.error(
98
+ ' Upgrade to Pro, Team, or Enterprise for unlimited runs: https://vibebuildlab.com/qa-architect'
99
+ )
100
+ process.exit(1)
101
+ }
102
+
103
+ const dependabotPath = path.join(projectPath, '.github', 'dependabot.yml')
104
+
105
+ // Use premium or basic config based on license tier
106
+ const shouldUsePremium =
107
+ license.tier === 'PRO' ||
108
+ license.tier === 'TEAM' ||
109
+ license.tier === 'ENTERPRISE'
110
+
111
+ // Free tier only supports npm projects. Fail fast with a clear message.
112
+ if (!shouldUsePremium && !hasNpm && (hasPython || hasRust || hasRuby)) {
113
+ console.error(
114
+ '❌ Dependency monitoring for this project requires a Pro, Team, or Enterprise license.'
115
+ )
116
+ console.error(
117
+ ' Free tier supports npm projects only. Detected non-npm ecosystems.'
118
+ )
119
+ console.error(
120
+ ' Options: add npm/package.json, or upgrade and re-run: npx create-qa-architect@latest --deps after activation.'
121
+ )
122
+ process.exit(1)
123
+ }
124
+
125
+ if (shouldUsePremium) {
126
+ console.log(
127
+ '\n🚀 Setting up framework-aware dependency monitoring (Premium)...\n'
128
+ )
129
+
130
+ const configData = generatePremiumDependabotConfig({
131
+ projectPath,
132
+ schedule: 'weekly',
133
+ })
134
+
135
+ if (configData) {
136
+ const { ecosystems } = configData
137
+ const ecosystemNames = Object.keys(ecosystems)
138
+
139
+ if (ecosystemNames.length > 0) {
140
+ console.log('🔍 Detected ecosystems:')
141
+
142
+ let primaryEcosystem = null
143
+ ecosystemNames.forEach(ecoName => {
144
+ const eco = ecosystems[ecoName]
145
+ const frameworks = Object.keys(eco.detected || {})
146
+ const totalPackages = frameworks.reduce((sum, fw) => {
147
+ return sum + (eco.detected[fw]?.count || 0)
148
+ }, 0)
149
+
150
+ console.log(` • ${ecoName}: ${totalPackages} packages`)
151
+
152
+ if (eco.primary) {
153
+ primaryEcosystem = ecoName
154
+ }
155
+ })
156
+
157
+ if (primaryEcosystem) {
158
+ console.log(`\n🎯 Primary ecosystem: ${primaryEcosystem}`)
159
+ }
160
+ }
161
+
162
+ writePremiumDependabotConfig(configData, dependabotPath)
163
+ console.log('\n✅ Created .github/dependabot.yml with framework grouping')
164
+
165
+ console.log('\n🎉 Premium dependency monitoring setup complete!')
166
+ console.log('\n📋 What was added (Pro Tier):')
167
+ console.log(' • Framework-aware dependency grouping')
168
+ console.log(
169
+ ` • ${Object.keys(configData.config.updates[0].groups || {}).length} dependency groups created`
170
+ )
171
+ console.log(' • Intelligent update batching (reduces PRs by 60%+)')
172
+ console.log(' • GitHub Actions dependency monitoring')
173
+ }
174
+ } else {
175
+ console.log('\n🔍 Setting up basic dependency monitoring (Free Tier)...\n')
176
+
177
+ const dependabotConfig = generateBasicDependabotConfig({
178
+ projectPath,
179
+ schedule: 'weekly',
180
+ })
181
+
182
+ if (dependabotConfig) {
183
+ writeBasicDependabotConfig(dependabotConfig, dependabotPath)
184
+ console.log('✅ Created .github/dependabot.yml')
185
+ }
186
+
187
+ console.log('\n🎉 Basic dependency monitoring setup complete!')
188
+ console.log('\n📋 What was added (Free Tier):')
189
+ console.log(' • Basic Dependabot configuration for npm packages')
190
+ console.log(' • Weekly dependency updates on Monday 9am')
191
+ console.log(' • GitHub Actions dependency monitoring')
192
+
193
+ // Show upgrade message for premium features
194
+ console.log('\n🔒 Premium features now available:')
195
+ console.log(' ✅ Framework-aware package grouping (React, Vue, Angular)')
196
+ console.log(' • Coming soon: Multi-language support (Python, Rust, Go)')
197
+ console.log(' • Planned: Advanced security audit workflows')
198
+ console.log(' • Planned: Custom update schedules and notifications')
199
+
200
+ showUpgradeMessage('Framework-Aware Dependency Grouping')
201
+ }
202
+
203
+ if (license.tier === 'FREE') {
204
+ const increment = incrementUsage('dependency-pr')
205
+ const usage = increment.usage || capCheck.usage
206
+ const caps = capCheck.caps
207
+ if (usage && caps && caps.maxDependencyPRsPerMonth !== undefined) {
208
+ console.log(
209
+ `🧮 Usage: ${usage.dependencyPRs}/${caps.maxDependencyPRsPerMonth} dependency monitoring runs used this month`
210
+ )
211
+ }
212
+ }
213
+
214
+ // Auto-enable Dependabot on GitHub if token available
215
+ console.log('\n🔧 Attempting to enable Dependabot on GitHub...')
216
+ try {
217
+ const { setupDependabot } = require('../github-api')
218
+ const result = await setupDependabot(projectPath, { verbose: true })
219
+
220
+ if (result.success) {
221
+ console.log('✅ Dependabot alerts and security updates enabled!')
222
+ } else if (result.errors.length > 0) {
223
+ console.log('⚠️ Could not auto-enable Dependabot:')
224
+ result.errors.forEach(err => console.log(` • ${err}`))
225
+ console.log('\n💡 Manual steps needed:')
226
+ console.log(' • Go to GitHub repo → Settings → Code security')
227
+ console.log(
228
+ ' • Enable "Dependabot alerts" and "Dependabot security updates"'
229
+ )
230
+ }
231
+ } catch (error) {
232
+ const errorId = 'DEPENDABOT_AUTO_ENABLE_FAILED'
233
+
234
+ // Diagnose specific error types
235
+ let diagnosis = 'Unknown error'
236
+ let remedy = 'Check the error message for details'
237
+
238
+ if (
239
+ error.message.includes('401') ||
240
+ error.message.includes('authentication')
241
+ ) {
242
+ diagnosis = 'GitHub token is invalid or missing'
243
+ remedy = 'Set GITHUB_TOKEN environment variable with a valid token'
244
+ } else if (error.message.includes('404')) {
245
+ diagnosis = 'Repository not found or insufficient permissions'
246
+ remedy = 'Ensure token has repo:write access to this repository'
247
+ } else if (error.message.includes('rate limit')) {
248
+ diagnosis = 'GitHub API rate limit exceeded'
249
+ remedy = 'Wait 1 hour or use authenticated token for higher limits'
250
+ } else if (
251
+ error.code === 'ENOTFOUND' ||
252
+ error.message.includes('network')
253
+ ) {
254
+ diagnosis = 'Network connectivity issue'
255
+ remedy = 'Check internet connection and GitHub API status'
256
+ } else if (error.message.includes('timeout')) {
257
+ diagnosis = 'Request timed out'
258
+ remedy = 'Retry the operation or check network connectivity'
259
+ }
260
+
261
+ console.error(`\n❌ [${errorId}] Could not auto-enable Dependabot`)
262
+ console.error(` Diagnosis: ${diagnosis}`)
263
+ console.error(` Error: ${error.message}`)
264
+ console.error(`\n 🔧 Recommended fix: ${remedy}`)
265
+
266
+ if (process.env.DEBUG) {
267
+ console.error(`\n Debug info:`)
268
+ console.error(` • Error code: ${error.code || 'N/A'}`)
269
+ console.error(` • Stack: ${error.stack}`)
270
+ }
271
+
272
+ console.log('\n💡 Alternative: Enable manually:')
273
+ console.log(' 1. Go to GitHub repo → Settings → Code security')
274
+ console.log(' 2. Enable "Dependabot alerts"')
275
+ console.log(' 3. Enable "Dependabot security updates"')
276
+ console.log(
277
+ `\n • Report issue: https://github.com/vibebuildlab/qa-architect/issues/new?title=${errorId}`
278
+ )
279
+ }
280
+
281
+ console.log('\n💡 Next steps:')
282
+ console.log(' • Review and commit .github/dependabot.yml')
283
+ console.log(
284
+ ' • Dependabot will start monitoring weekly for dependency updates'
285
+ )
286
+ }
287
+
288
+ module.exports = {
289
+ handleDependencyMonitoring,
290
+ detectPythonProject,
291
+ detectRustProject,
292
+ detectRubyProject,
293
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Command handlers index
3
+ *
4
+ * Centralizes CLI command handlers for better maintainability.
5
+ * Each command has its own module with focused functionality.
6
+ */
7
+
8
+ const { handleValidationCommands } = require('./validate')
9
+ const {
10
+ handleDependencyMonitoring,
11
+ detectPythonProject,
12
+ detectRustProject,
13
+ detectRubyProject,
14
+ } = require('./deps')
15
+ const { handleAnalyzeCi } = require('./analyze-ci')
16
+
17
+ module.exports = {
18
+ // Validation commands
19
+ handleValidationCommands,
20
+
21
+ // Dependency monitoring commands
22
+ handleDependencyMonitoring,
23
+ detectPythonProject,
24
+ detectRustProject,
25
+ detectRubyProject,
26
+
27
+ // CI/CD optimization commands
28
+ handleAnalyzeCi,
29
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Validation command handlers
3
+ *
4
+ * Extracted from setup.js to improve maintainability.
5
+ * Handles --validate, --comprehensive, --security-config, --validate-docs commands.
6
+ */
7
+
8
+ const { ValidationRunner } = require('../validation')
9
+
10
+ /**
11
+ * Handle validation-only commands
12
+ *
13
+ * @param {Object} options - Validation options
14
+ * @param {boolean} options.isConfigSecurityMode - Run config security check only
15
+ * @param {boolean} options.isDocsValidationMode - Run docs validation only
16
+ * @param {boolean} options.isComprehensiveMode - Run comprehensive validation
17
+ * @param {boolean} options.isValidationMode - Run validation mode
18
+ * @param {boolean} options.disableNpmAudit - Skip npm audit
19
+ * @param {boolean} options.disableGitleaks - Skip gitleaks
20
+ * @param {boolean} options.disableActionlint - Skip actionlint
21
+ * @param {boolean} options.disableMarkdownlint - Skip markdownlint
22
+ * @param {boolean} options.disableEslintSecurity - Skip ESLint security
23
+ * @param {boolean} options.allowLatestGitleaks - Allow latest gitleaks version
24
+ */
25
+ async function handleValidationCommands(options) {
26
+ const {
27
+ isConfigSecurityMode,
28
+ isDocsValidationMode,
29
+ isComprehensiveMode,
30
+ isValidationMode,
31
+ disableNpmAudit,
32
+ disableGitleaks,
33
+ disableActionlint,
34
+ disableMarkdownlint,
35
+ disableEslintSecurity,
36
+ allowLatestGitleaks,
37
+ } = options
38
+
39
+ const validationOptions = {
40
+ disableNpmAudit,
41
+ disableGitleaks,
42
+ disableActionlint,
43
+ disableMarkdownlint,
44
+ disableEslintSecurity,
45
+ allowLatestGitleaks,
46
+ }
47
+ const validator = new ValidationRunner(validationOptions)
48
+
49
+ if (isConfigSecurityMode) {
50
+ try {
51
+ await validator.runConfigSecurity()
52
+ process.exit(0)
53
+ } catch (error) {
54
+ console.error(
55
+ `\n❌ Configuration security validation failed:\n${error.message}`
56
+ )
57
+ process.exit(1)
58
+ }
59
+ }
60
+
61
+ if (isDocsValidationMode) {
62
+ try {
63
+ await validator.runDocumentationValidation()
64
+ process.exit(0)
65
+ } catch (error) {
66
+ console.error(`\n❌ Documentation validation failed:\n${error.message}`)
67
+ process.exit(1)
68
+ }
69
+ }
70
+
71
+ if (isComprehensiveMode || isValidationMode) {
72
+ try {
73
+ // Use parallel validation for 3-5x speedup (runs checks concurrently)
74
+ await validator.runComprehensiveCheckParallel()
75
+ process.exit(0)
76
+ } catch (error) {
77
+ console.error(`\n❌ Comprehensive validation failed:\n${error.message}`)
78
+ process.exit(1)
79
+ }
80
+ }
81
+ }
82
+
83
+ module.exports = {
84
+ handleValidationCommands,
85
+ }
@@ -11,6 +11,33 @@ const addFormats = /** @type {(ajv: any) => void} */ (
11
11
  addFormatsImport.default || addFormatsImport
12
12
  )
13
13
 
14
+ /**
15
+ * Format a single AJV validation error into a human-readable message
16
+ */
17
+ function formatValidationError(error) {
18
+ const errorPath = error.instancePath || '(root)'
19
+ const message = error.message || 'validation failed'
20
+
21
+ const errorFormatters = {
22
+ required: () =>
23
+ `Missing required field: ${error.params?.missingProperty || 'unknown'}`,
24
+ enum: () =>
25
+ `Invalid value at ${errorPath}: must be one of ${error.params?.allowedValues?.join(', ') || 'unknown values'}`,
26
+ type: () =>
27
+ `Invalid type at ${errorPath}: expected ${error.params?.type || 'unknown'}`,
28
+ pattern: () => `Invalid format at ${errorPath}: ${message}`,
29
+ minimum: () =>
30
+ `Invalid value at ${errorPath}: must be >= ${error.params?.limit ?? 'unknown'}`,
31
+ additionalProperties: () =>
32
+ `Unknown property at ${errorPath}: ${error.params?.additionalProperty || 'unknown'}`,
33
+ }
34
+
35
+ const formatter = errorFormatters[error.keyword]
36
+ return formatter
37
+ ? formatter()
38
+ : `Validation error at ${errorPath}: ${message}`
39
+ }
40
+
14
41
  function validateQualityConfig(configPath) {
15
42
  const result = {
16
43
  valid: false,
@@ -61,51 +88,7 @@ function validateQualityConfig(configPath) {
61
88
 
62
89
  if (!valid) {
63
90
  if (validate.errors) {
64
- validate.errors.forEach(error => {
65
- const errorPath = error.instancePath || '(root)'
66
- const message = error.message || 'validation failed'
67
-
68
- if (error.keyword === 'required') {
69
- result.errors.push(
70
- 'Missing required field: ' +
71
- (error.params?.missingProperty || 'unknown')
72
- )
73
- } else if (error.keyword === 'enum') {
74
- result.errors.push(
75
- 'Invalid value at ' +
76
- errorPath +
77
- ': must be one of ' +
78
- (error.params?.allowedValues?.join(', ') || 'unknown values')
79
- )
80
- } else if (error.keyword === 'type') {
81
- result.errors.push(
82
- 'Invalid type at ' +
83
- errorPath +
84
- ': expected ' +
85
- (error.params?.type || 'unknown')
86
- )
87
- } else if (error.keyword === 'pattern') {
88
- result.errors.push('Invalid format at ' + errorPath + ': ' + message)
89
- } else if (error.keyword === 'minimum') {
90
- result.errors.push(
91
- 'Invalid value at ' +
92
- errorPath +
93
- ': must be >= ' +
94
- (error.params?.limit ?? 'unknown')
95
- )
96
- } else if (error.keyword === 'additionalProperties') {
97
- result.errors.push(
98
- 'Unknown property at ' +
99
- errorPath +
100
- ': ' +
101
- (error.params?.additionalProperty || 'unknown')
102
- )
103
- } else {
104
- result.errors.push(
105
- 'Validation error at ' + errorPath + ': ' + message
106
- )
107
- }
108
- })
91
+ result.errors = validate.errors.map(formatValidationError)
109
92
  }
110
93
  } else {
111
94
  result.valid = true
@@ -190,7 +190,7 @@ function loadErrorReports() {
190
190
  const data = fs.readFileSync(ERROR_REPORTS_FILE, 'utf8')
191
191
  return JSON.parse(data)
192
192
  }
193
- } catch {
193
+ } catch (_error) {
194
194
  // If corrupted, start fresh
195
195
  console.warn('⚠️ Error reports data corrupted, starting fresh')
196
196
  }
@@ -253,13 +253,25 @@ class ErrorReporter {
253
253
  const category = categorizeError(error)
254
254
  const reportId = generateReportId()
255
255
 
256
+ // DR20 fix: Limit stack trace exposure in production mode
257
+ const isProduction = process.env.NODE_ENV === 'production'
258
+ const fullStack = sanitizeStackTrace(error?.stack || '')
259
+
260
+ // In production: only include first 3 lines of stack (error + top 2 frames)
261
+ // In dev/test: include full sanitized stack for debugging
262
+ const stackToInclude =
263
+ isProduction && fullStack
264
+ ? fullStack.split('\n').slice(0, 3).join('\n')
265
+ : fullStack
266
+
256
267
  const report = {
257
268
  id: reportId,
258
269
  timestamp: new Date().toISOString(),
259
270
  category,
260
271
  errorType: error?.constructor?.name || 'Error',
261
272
  message: sanitizeMessage(error?.message || 'Unknown error'),
262
- sanitizedStack: sanitizeStackTrace(error?.stack || ''),
273
+ sanitizedStack: stackToInclude,
274
+ stackTruncated: isProduction && fullStack.split('\n').length > 3,
263
275
  operation: this.operation,
264
276
  context: {
265
277
  nodeVersion: process.version,