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
@@ -0,0 +1,444 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Error reporting module for crash analytics (opt-in only)
5
+ *
6
+ * Privacy principles:
7
+ * - Completely opt-in (prompt on error or ENV var)
8
+ * - No personal information collected (paths/usernames sanitized)
9
+ * - Local storage primary (optional remote sync)
10
+ * - Easy to inspect and delete
11
+ * - User can add context/comments
12
+ *
13
+ * Data collected:
14
+ * - Error category (dependency, permission, config, validation, network)
15
+ * - Error type and sanitized message
16
+ * - Sanitized stack trace (paths removed)
17
+ * - Node version and OS platform
18
+ * - Operation attempted (setup, validate, deps)
19
+ * - Optional user comment/context
20
+ */
21
+
22
+ const fs = require('fs')
23
+ const path = require('path')
24
+ const os = require('os')
25
+ const crypto = require('crypto')
26
+ const { REPORTING_LIMITS } = require('../config/constants')
27
+
28
+ const ERROR_REPORTS_DIR =
29
+ process.env.QAA_ERROR_DIR || path.join(os.homedir(), '.create-qa-architect')
30
+ const ERROR_REPORTS_FILE = path.join(ERROR_REPORTS_DIR, 'error-reports.json')
31
+ const MAX_REPORTS = REPORTING_LIMITS.MAX_ERROR_REPORTS
32
+
33
+ /**
34
+ * Error categories for classification
35
+ */
36
+ const ErrorCategory = {
37
+ DEPENDENCY_ERROR: 'DEPENDENCY_ERROR', // npm install, missing packages
38
+ PERMISSION_ERROR: 'PERMISSION_ERROR', // EACCES, EPERM
39
+ CONFIGURATION_ERROR: 'CONFIGURATION_ERROR', // Invalid configs
40
+ VALIDATION_ERROR: 'VALIDATION_ERROR', // ESLint/Prettier failures
41
+ NETWORK_ERROR: 'NETWORK_ERROR', // npm registry, git
42
+ UNKNOWN_ERROR: 'UNKNOWN_ERROR', // Uncategorized
43
+ }
44
+
45
+ /**
46
+ * Check if error reporting is enabled
47
+ * Can be enabled via ENV var or interactive prompt
48
+ */
49
+ function isErrorReportingEnabled() {
50
+ const envEnabled =
51
+ process.env.QAA_ERROR_REPORTING === 'true' ||
52
+ process.env.QAA_ERROR_REPORTING === '1'
53
+
54
+ return envEnabled
55
+ }
56
+
57
+ /**
58
+ * Categorize error based on error message and code
59
+ */
60
+ function categorizeError(error) {
61
+ const message = error?.message?.toLowerCase() || ''
62
+ const code = error?.code || ''
63
+
64
+ // Permission errors
65
+ if (code === 'EACCES' || code === 'EPERM' || message.includes('permission')) {
66
+ return ErrorCategory.PERMISSION_ERROR
67
+ }
68
+
69
+ // Dependency errors
70
+ if (
71
+ message.includes('npm install') ||
72
+ message.includes('cannot find module') ||
73
+ message.includes('module not found') ||
74
+ code === 'MODULE_NOT_FOUND'
75
+ ) {
76
+ return ErrorCategory.DEPENDENCY_ERROR
77
+ }
78
+
79
+ // Network errors
80
+ if (
81
+ code === 'ENOTFOUND' ||
82
+ code === 'ETIMEDOUT' ||
83
+ code === 'ECONNREFUSED' ||
84
+ message.includes('network') ||
85
+ message.includes('registry')
86
+ ) {
87
+ return ErrorCategory.NETWORK_ERROR
88
+ }
89
+
90
+ // Configuration errors
91
+ if (
92
+ message.includes('package.json') ||
93
+ message.includes('invalid config') ||
94
+ message.includes('parse error') ||
95
+ message.includes('syntax error')
96
+ ) {
97
+ return ErrorCategory.CONFIGURATION_ERROR
98
+ }
99
+
100
+ // Validation errors
101
+ if (
102
+ message.includes('eslint') ||
103
+ message.includes('prettier') ||
104
+ message.includes('stylelint') ||
105
+ message.includes('validation failed')
106
+ ) {
107
+ return ErrorCategory.VALIDATION_ERROR
108
+ }
109
+
110
+ return ErrorCategory.UNKNOWN_ERROR
111
+ }
112
+
113
+ /**
114
+ * Sanitize file path to remove personal information
115
+ */
116
+ function sanitizePath(filePath) {
117
+ if (!filePath || typeof filePath !== 'string') return filePath
118
+
119
+ // Remove username from common paths
120
+ const homeDir = os.homedir()
121
+ const sanitized = filePath.replace(homeDir, '/Users/<redacted>')
122
+
123
+ // Remove common user-specific directories
124
+ return sanitized
125
+ .replace(/\/Users\/[^/]+/g, '/Users/<redacted>')
126
+ .replace(/\/home\/[^/]+/g, '/home/<redacted>')
127
+ .replace(/C:\\Users\\[^\\]+/g, 'C:\\Users\\<redacted>')
128
+ }
129
+
130
+ /**
131
+ * Sanitize error message to remove personal information
132
+ */
133
+ function sanitizeMessage(message) {
134
+ if (!message || typeof message !== 'string') return message
135
+
136
+ let sanitized = message
137
+
138
+ // Remove file paths
139
+ sanitized = sanitizePath(sanitized)
140
+
141
+ // Remove git URLs with tokens
142
+ sanitized = sanitized.replace(
143
+ /https:\/\/[^@]+@github\.com/g,
144
+ 'https://<token>@github.com'
145
+ )
146
+
147
+ // Remove email addresses
148
+ sanitized = sanitized.replace(
149
+ /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
150
+ '<email>'
151
+ )
152
+
153
+ return sanitized
154
+ }
155
+
156
+ /**
157
+ * Sanitize stack trace to remove personal information
158
+ */
159
+ function sanitizeStackTrace(stack) {
160
+ if (!stack || typeof stack !== 'string') return stack
161
+
162
+ return stack
163
+ .split('\n')
164
+ .map(line => sanitizePath(line))
165
+ .join('\n')
166
+ }
167
+
168
+ /**
169
+ * Generate unique error report ID
170
+ */
171
+ function generateReportId() {
172
+ return crypto.randomBytes(8).toString('hex')
173
+ }
174
+
175
+ /**
176
+ * Ensure error reports directory exists
177
+ */
178
+ function ensureErrorReportsDir() {
179
+ if (!fs.existsSync(ERROR_REPORTS_DIR)) {
180
+ fs.mkdirSync(ERROR_REPORTS_DIR, { recursive: true, mode: 0o700 })
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Load existing error reports
186
+ */
187
+ function loadErrorReports() {
188
+ try {
189
+ if (fs.existsSync(ERROR_REPORTS_FILE)) {
190
+ const data = fs.readFileSync(ERROR_REPORTS_FILE, 'utf8')
191
+ return JSON.parse(data)
192
+ }
193
+ } catch {
194
+ // If corrupted, start fresh
195
+ console.warn('⚠️ Error reports data corrupted, starting fresh')
196
+ }
197
+
198
+ return {
199
+ version: 1,
200
+ reports: [],
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Save error reports (with rotation)
206
+ */
207
+ function saveErrorReports(data) {
208
+ try {
209
+ ensureErrorReportsDir()
210
+
211
+ // Rotate: keep only last MAX_REPORTS
212
+ if (data.reports.length > MAX_REPORTS) {
213
+ data.reports = data.reports.slice(-MAX_REPORTS)
214
+ }
215
+
216
+ fs.writeFileSync(
217
+ ERROR_REPORTS_FILE,
218
+ JSON.stringify(data, null, 2),
219
+ { mode: 0o600 } // Owner read/write only
220
+ )
221
+ } catch (error) {
222
+ // Silently fail - error reporting should never break the tool
223
+ if (process.env.DEBUG) {
224
+ console.error('Error reporting save failed:', error.message)
225
+ }
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Error reporter for capturing and analyzing crashes
231
+ */
232
+ class ErrorReporter {
233
+ constructor(operation = 'unknown') {
234
+ this.operation = operation
235
+ this.enabled = isErrorReportingEnabled()
236
+ }
237
+
238
+ /**
239
+ * Capture and report an error
240
+ *
241
+ * @param {Error} error - The error to report
242
+ * @param {object} context - Additional context
243
+ * @param {string} userComment - Optional user comment
244
+ */
245
+ captureError(error, context = {}, userComment = null) {
246
+ if (!this.enabled && !context.forceCapture) {
247
+ return null
248
+ }
249
+
250
+ try {
251
+ const data = loadErrorReports()
252
+
253
+ const category = categorizeError(error)
254
+ const reportId = generateReportId()
255
+
256
+ const report = {
257
+ id: reportId,
258
+ timestamp: new Date().toISOString(),
259
+ category,
260
+ errorType: error?.constructor?.name || 'Error',
261
+ message: sanitizeMessage(error?.message || 'Unknown error'),
262
+ sanitizedStack: sanitizeStackTrace(error?.stack || ''),
263
+ operation: this.operation,
264
+ context: {
265
+ nodeVersion: process.version,
266
+ platform: os.platform(),
267
+ arch: os.arch(),
268
+ ...context,
269
+ },
270
+ userComment: userComment || null,
271
+ }
272
+
273
+ data.reports.push(report)
274
+ saveErrorReports(data)
275
+
276
+ return reportId
277
+ } catch (captureError) {
278
+ // Silently fail - error reporting should never break the tool
279
+ if (process.env.DEBUG) {
280
+ console.error('Error capture failed:', captureError.message)
281
+ }
282
+ return null
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Get friendly error message for user
288
+ */
289
+ getFriendlyMessage(error) {
290
+ const category = categorizeError(error)
291
+
292
+ const messages = {
293
+ [ErrorCategory.DEPENDENCY_ERROR]: {
294
+ title: '📦 Dependency Issue',
295
+ suggestion: 'Try running: npm install\nOr check your package.json file',
296
+ },
297
+ [ErrorCategory.PERMISSION_ERROR]: {
298
+ title: '🔒 Permission Denied',
299
+ suggestion:
300
+ 'Try running with appropriate permissions or check file ownership',
301
+ },
302
+ [ErrorCategory.CONFIGURATION_ERROR]: {
303
+ title: '⚙️ Configuration Error',
304
+ suggestion: 'Check your configuration files for syntax errors',
305
+ },
306
+ [ErrorCategory.VALIDATION_ERROR]: {
307
+ title: '✅ Validation Failed',
308
+ suggestion:
309
+ 'Review the validation errors above and fix them before continuing',
310
+ },
311
+ [ErrorCategory.NETWORK_ERROR]: {
312
+ title: '🌐 Network Issue',
313
+ suggestion: 'Check your internet connection and try again',
314
+ },
315
+ [ErrorCategory.UNKNOWN_ERROR]: {
316
+ title: '❌ Unexpected Error',
317
+ suggestion: 'Please report this issue with the error details below',
318
+ },
319
+ }
320
+
321
+ return messages[category] || messages[ErrorCategory.UNKNOWN_ERROR]
322
+ }
323
+
324
+ /**
325
+ * Show error report prompt to user
326
+ */
327
+ async promptErrorReport(error) {
328
+ const friendly = this.getFriendlyMessage(error)
329
+
330
+ console.error('\n' + '━'.repeat(60))
331
+ console.error(`${friendly.title}`)
332
+ console.error('━'.repeat(60))
333
+ console.error(`Error: ${error?.message || 'Unknown error'}`)
334
+ console.error(`\n💡 Suggestion: ${friendly.suggestion}`)
335
+ console.error('━'.repeat(60))
336
+
337
+ if (!this.enabled) {
338
+ console.log('\n📊 Help improve this tool by reporting errors')
339
+ console.log('Enable error reporting: export QAA_ERROR_REPORTING=true')
340
+ console.log(`Report will be saved locally at: ${ERROR_REPORTS_FILE}`)
341
+ }
342
+ }
343
+ }
344
+
345
+ /**
346
+ * Get error report statistics
347
+ */
348
+ function getErrorReportStats() {
349
+ const data = loadErrorReports()
350
+
351
+ const stats = {
352
+ totalReports: data.reports.length,
353
+ byCategory: {},
354
+ byPlatform: {},
355
+ byNodeVersion: {},
356
+ recentReports: data.reports.slice(-10),
357
+ }
358
+
359
+ data.reports.forEach(report => {
360
+ // Count by category
361
+ stats.byCategory[report.category] =
362
+ (stats.byCategory[report.category] || 0) + 1
363
+
364
+ // Count by platform
365
+ const platform = report.context?.platform || 'unknown'
366
+ stats.byPlatform[platform] = (stats.byPlatform[platform] || 0) + 1
367
+
368
+ // Count by Node version
369
+ const nodeVersion = report.context?.nodeVersion || 'unknown'
370
+ stats.byNodeVersion[nodeVersion] =
371
+ (stats.byNodeVersion[nodeVersion] || 0) + 1
372
+ })
373
+
374
+ return stats
375
+ }
376
+
377
+ /**
378
+ * Clear all error reports
379
+ */
380
+ function clearErrorReports() {
381
+ try {
382
+ if (fs.existsSync(ERROR_REPORTS_FILE)) {
383
+ fs.unlinkSync(ERROR_REPORTS_FILE)
384
+ return true
385
+ }
386
+ return false
387
+ } catch (error) {
388
+ console.error('Failed to clear error reports:', error.message)
389
+ return false
390
+ }
391
+ }
392
+
393
+ /**
394
+ * Show error reporting status
395
+ */
396
+ function showErrorReportingStatus() {
397
+ const enabled = isErrorReportingEnabled()
398
+
399
+ console.log('\n📊 Error Reporting Status')
400
+ console.log('─'.repeat(50))
401
+ console.log(
402
+ `Status: ${enabled ? '✅ Enabled' : '❌ Disabled (opt-in required)'}`
403
+ )
404
+
405
+ if (enabled) {
406
+ const stats = getErrorReportStats()
407
+ console.log(`Total error reports: ${stats.totalReports}`)
408
+ console.log(`Storage: ${ERROR_REPORTS_FILE}`)
409
+
410
+ if (stats.totalReports > 0) {
411
+ console.log('\nBy Category:')
412
+ Object.entries(stats.byCategory).forEach(([category, count]) => {
413
+ console.log(` ${category}: ${count}`)
414
+ })
415
+ }
416
+ } else {
417
+ console.log('\nTo enable error reporting (opt-in):')
418
+ console.log(' export QAA_ERROR_REPORTING=true')
419
+ console.log(' # or add to ~/.bashrc or ~/.zshrc')
420
+ console.log('\nWhy enable error reporting?')
421
+ console.log(' - Helps identify common issues and failure patterns')
422
+ console.log(' - All data stays local (no network calls)')
423
+ console.log(' - No personal information collected (paths sanitized)')
424
+ console.log(
425
+ ' - Easy to inspect: cat ~/.create-qa-architect/error-reports.json'
426
+ )
427
+ }
428
+
429
+ console.log('─'.repeat(50))
430
+ }
431
+
432
+ module.exports = {
433
+ ErrorReporter,
434
+ ErrorCategory,
435
+ isErrorReportingEnabled,
436
+ categorizeError,
437
+ sanitizePath,
438
+ sanitizeMessage,
439
+ sanitizeStackTrace,
440
+ getErrorReportStats,
441
+ clearErrorReports,
442
+ showErrorReportingStatus,
443
+ ERROR_REPORTS_FILE,
444
+ }
@@ -0,0 +1,128 @@
1
+ 'use strict'
2
+
3
+ const readline = require('readline')
4
+
5
+ /**
6
+ * Interactive prompt utility using Node.js readline
7
+ * Handles user input with TTY detection for CI safety
8
+ */
9
+ class InteractivePrompt {
10
+ constructor(options = {}) {
11
+ this.input = options.input || process.stdin
12
+ this.output = options.output || process.stdout
13
+ }
14
+
15
+ /**
16
+ * Check if running in a TTY environment
17
+ * @returns {boolean} True if TTY (interactive terminal)
18
+ */
19
+ isTTY() {
20
+ return Boolean(this.input.isTTY && this.output.isTTY)
21
+ }
22
+
23
+ /**
24
+ * Ask a single question and get user input
25
+ * @param {string} question - The question to ask
26
+ * @returns {Promise<string>} User's answer
27
+ */
28
+ async ask(question) {
29
+ if (!this.isTTY()) {
30
+ throw new Error('Interactive mode requires a TTY environment')
31
+ }
32
+
33
+ return new Promise((resolve, reject) => {
34
+ const rl = readline.createInterface({
35
+ input: this.input,
36
+ output: this.output,
37
+ })
38
+
39
+ rl.question(question, answer => {
40
+ rl.close()
41
+ resolve(answer.trim())
42
+ })
43
+
44
+ rl.on('SIGINT', () => {
45
+ rl.close()
46
+ reject(new Error('User cancelled interactive mode'))
47
+ })
48
+ })
49
+ }
50
+
51
+ /**
52
+ * Ask a yes/no question
53
+ * @param {string} question - The question to ask
54
+ * @param {boolean} defaultValue - Default value if user just presses Enter
55
+ * @returns {Promise<boolean>} True for yes, false for no
56
+ */
57
+ async confirm(question, defaultValue = false) {
58
+ const defaultHint = defaultValue ? ' [Y/n]' : ' [y/N]'
59
+ const answer = await this.ask(question + defaultHint + ' ')
60
+
61
+ if (answer === '') {
62
+ return defaultValue
63
+ }
64
+
65
+ const normalized = answer.toLowerCase()
66
+ return normalized === 'y' || normalized === 'yes'
67
+ }
68
+
69
+ /**
70
+ * Present a multiple choice question
71
+ * @param {string} question - The question to ask
72
+ * @param {Array<{value: string, label: string}>} choices - Available choices
73
+ * @returns {Promise<string>} Selected choice value
74
+ */
75
+ async select(question, choices) {
76
+ const choiceList = choices
77
+ .map((choice, index) => ` ${index + 1}) ${choice.label}`)
78
+ .join('\n')
79
+
80
+ const fullQuestion = `${question}\n${choiceList}\n\nEnter your choice (1-${choices.length}): `
81
+ const answer = await this.ask(fullQuestion)
82
+
83
+ const choiceIndex = parseInt(answer, 10) - 1
84
+ if (
85
+ isNaN(choiceIndex) ||
86
+ choiceIndex < 0 ||
87
+ choiceIndex >= choices.length
88
+ ) {
89
+ throw new Error(
90
+ `Invalid choice: ${answer}. Please enter a number between 1 and ${choices.length}`
91
+ )
92
+ }
93
+
94
+ return choices[choiceIndex].value
95
+ }
96
+
97
+ /**
98
+ * Present a multi-select question (checkboxes)
99
+ * @param {string} question - The question to ask
100
+ * @param {Array<{value: string, label: string}>} choices - Available choices
101
+ * @returns {Promise<Array<string>>} Array of selected choice values
102
+ */
103
+ async multiSelect(question, choices) {
104
+ const choiceList = choices
105
+ .map((choice, index) => ` ${index + 1}) ${choice.label}`)
106
+ .join('\n')
107
+
108
+ const fullQuestion =
109
+ `${question}\n${choiceList}\n\n` +
110
+ `Enter your choices (comma-separated, e.g., "1,3,5" or press Enter for none): `
111
+
112
+ const answer = await this.ask(fullQuestion)
113
+
114
+ if (answer === '') {
115
+ return []
116
+ }
117
+
118
+ const selections = answer
119
+ .split(',')
120
+ .map(s => s.trim())
121
+ .map(s => parseInt(s, 10) - 1)
122
+ .filter(index => !isNaN(index) && index >= 0 && index < choices.length)
123
+
124
+ return selections.map(index => choices[index].value)
125
+ }
126
+ }
127
+
128
+ module.exports = { InteractivePrompt }
@@ -0,0 +1,146 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * Question definitions and answer parsing for interactive mode
5
+ */
6
+
7
+ /**
8
+ * Generate question definitions for interactive flow
9
+ * @returns {Array<Object>} Array of question objects
10
+ */
11
+ function generateQuestions() {
12
+ return [
13
+ {
14
+ name: 'operationMode',
15
+ type: 'select',
16
+ message: 'What would you like to do?',
17
+ choices: [
18
+ {
19
+ value: 'setup',
20
+ label: 'Full quality automation setup (recommended)',
21
+ },
22
+ { value: 'update', label: 'Update existing configuration' },
23
+ { value: 'validate', label: 'Run validation checks only' },
24
+ ],
25
+ },
26
+ {
27
+ name: 'dependencyMonitoring',
28
+ type: 'confirm',
29
+ message: 'Include basic dependency monitoring (Free Tier)?',
30
+ default: true,
31
+ },
32
+ {
33
+ name: 'dryRun',
34
+ type: 'confirm',
35
+ message: 'Preview changes without modifying files (dry-run mode)?',
36
+ default: false,
37
+ },
38
+ {
39
+ name: 'toolExclusions',
40
+ type: 'multiSelect',
41
+ message: 'Disable any specific validation tools?',
42
+ choices: [
43
+ {
44
+ value: 'npm-audit',
45
+ label: 'npm audit (dependency vulnerability checks)',
46
+ },
47
+ { value: 'gitleaks', label: 'gitleaks (secret scanning)' },
48
+ {
49
+ value: 'actionlint',
50
+ label: 'actionlint (GitHub Actions workflow validation)',
51
+ },
52
+ {
53
+ value: 'markdownlint',
54
+ label: 'markdownlint (markdown formatting checks)',
55
+ },
56
+ { value: 'eslint-security', label: 'ESLint security rules' },
57
+ ],
58
+ },
59
+ ]
60
+ }
61
+
62
+ /**
63
+ * Parse user answers into CLI flags
64
+ * @param {Object} answers - User's answers to questions
65
+ * @returns {Array<string>} Array of CLI flags
66
+ */
67
+ function parseAnswers(answers) {
68
+ const flags = []
69
+
70
+ // Operation mode
71
+ if (answers.operationMode === 'update') {
72
+ flags.push('--update')
73
+ } else if (answers.operationMode === 'validate') {
74
+ flags.push('--comprehensive')
75
+ }
76
+ // 'setup' mode is default (no flag needed)
77
+
78
+ // Dependency monitoring
79
+ if (answers.dependencyMonitoring) {
80
+ flags.push('--deps')
81
+ }
82
+
83
+ // Dry-run mode
84
+ if (answers.dryRun) {
85
+ flags.push('--dry-run')
86
+ }
87
+
88
+ // Tool exclusions
89
+ if (Array.isArray(answers.toolExclusions)) {
90
+ answers.toolExclusions.forEach(tool => {
91
+ flags.push(`--no-${tool}`)
92
+ })
93
+ }
94
+
95
+ return flags
96
+ }
97
+
98
+ /**
99
+ * Run interactive prompt flow
100
+ * @param {InteractivePrompt} prompt - Prompt instance
101
+ * @returns {Promise<Array<string>>} Array of CLI flags based on answers
102
+ */
103
+ async function runInteractiveFlow(prompt) {
104
+ const questions = generateQuestions()
105
+ const answers = {}
106
+
107
+ console.log('\n🚀 Interactive Quality Automation Setup\n')
108
+
109
+ for (const question of questions) {
110
+ try {
111
+ if (question.type === 'confirm') {
112
+ answers[question.name] = await prompt.confirm(
113
+ question.message,
114
+ question.default
115
+ )
116
+ } else if (question.type === 'select') {
117
+ answers[question.name] = await prompt.select(
118
+ question.message,
119
+ question.choices
120
+ )
121
+ } else if (question.type === 'multiSelect') {
122
+ answers[question.name] = await prompt.multiSelect(
123
+ question.message,
124
+ question.choices
125
+ )
126
+ }
127
+ } catch (error) {
128
+ // Handle user cancellation or other errors
129
+ if (error.message.includes('cancelled')) {
130
+ console.log('\n\n❌ Interactive mode cancelled by user\n')
131
+ process.exit(0)
132
+ }
133
+ throw error
134
+ }
135
+ }
136
+
137
+ console.log('\n✅ Configuration complete!\n')
138
+
139
+ return parseAnswers(answers)
140
+ }
141
+
142
+ module.exports = {
143
+ generateQuestions,
144
+ parseAnswers,
145
+ runInteractiveFlow,
146
+ }