create-qa-architect 5.0.0 → 5.0.1

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.
@@ -177,6 +177,219 @@ function getAuditCommand(packageManager) {
177
177
  return commands[packageManager] || 'npm audit'
178
178
  }
179
179
 
180
+ /**
181
+ * Detect monorepo type and configuration
182
+ * @param {string} projectPath - Path to the project directory
183
+ * @returns {Object} Monorepo info: { type, isMonorepo, packages, tool }
184
+ */
185
+ function detectMonorepoType(projectPath = process.cwd()) {
186
+ const fs = require('fs')
187
+ const path = require('path')
188
+
189
+ const result = {
190
+ isMonorepo: false,
191
+ type: null, // 'workspaces' | 'lerna' | 'nx' | 'turborepo' | 'rush'
192
+ tool: null, // Specific tool name
193
+ packages: [], // List of workspace package paths
194
+ packageManager: detectPackageManager(projectPath),
195
+ }
196
+
197
+ // Check for Nx (nx.json)
198
+ const nxJsonPath = path.join(projectPath, 'nx.json')
199
+ if (fs.existsSync(nxJsonPath)) {
200
+ result.isMonorepo = true
201
+ result.type = 'nx'
202
+ result.tool = 'nx'
203
+ try {
204
+ const nxJson = JSON.parse(fs.readFileSync(nxJsonPath, 'utf8'))
205
+ result.config = nxJson
206
+ } catch {
207
+ // Ignore parse errors
208
+ }
209
+ }
210
+
211
+ // Check for Turborepo (turbo.json)
212
+ const turboJsonPath = path.join(projectPath, 'turbo.json')
213
+ if (fs.existsSync(turboJsonPath)) {
214
+ result.isMonorepo = true
215
+ result.type = 'turborepo'
216
+ result.tool = 'turborepo'
217
+ try {
218
+ const turboJson = JSON.parse(fs.readFileSync(turboJsonPath, 'utf8'))
219
+ result.config = turboJson
220
+ } catch {
221
+ // Ignore parse errors
222
+ }
223
+ }
224
+
225
+ // Check for Rush (rush.json)
226
+ const rushJsonPath = path.join(projectPath, 'rush.json')
227
+ if (fs.existsSync(rushJsonPath)) {
228
+ result.isMonorepo = true
229
+ result.type = 'rush'
230
+ result.tool = 'rush'
231
+ }
232
+
233
+ // Check for Lerna (lerna.json)
234
+ const lernaJsonPath = path.join(projectPath, 'lerna.json')
235
+ if (fs.existsSync(lernaJsonPath)) {
236
+ result.isMonorepo = true
237
+ result.type = 'lerna'
238
+ result.tool = 'lerna'
239
+ try {
240
+ const lernaJson = JSON.parse(fs.readFileSync(lernaJsonPath, 'utf8'))
241
+ result.packages = lernaJson.packages || ['packages/*']
242
+ } catch {
243
+ result.packages = ['packages/*']
244
+ }
245
+ }
246
+
247
+ // Check for pnpm workspaces (pnpm-workspace.yaml)
248
+ const pnpmWorkspacePath = path.join(projectPath, 'pnpm-workspace.yaml')
249
+ if (fs.existsSync(pnpmWorkspacePath)) {
250
+ result.isMonorepo = true
251
+ result.type = result.type || 'workspaces'
252
+ result.tool = result.tool || 'pnpm'
253
+ try {
254
+ const yaml = fs.readFileSync(pnpmWorkspacePath, 'utf8')
255
+ // Simple line-by-line YAML parsing (safer than regex)
256
+ const lines = yaml.split('\n')
257
+ let inPackages = false
258
+ const packages = []
259
+ for (const line of lines) {
260
+ if (line.trim() === 'packages:') {
261
+ inPackages = true
262
+ continue
263
+ }
264
+ if (inPackages) {
265
+ // Check if line is a list item (starts with -)
266
+ const match = line.match(/^\s*-\s*['"]?([^'"]+)['"]?\s*$/)
267
+ if (match) {
268
+ packages.push(match[1])
269
+ } else if (
270
+ line.trim() &&
271
+ !line.startsWith(' ') &&
272
+ !line.startsWith('\t')
273
+ ) {
274
+ // New top-level key, stop parsing packages
275
+ break
276
+ }
277
+ }
278
+ }
279
+ if (packages.length > 0) {
280
+ result.packages = packages
281
+ }
282
+ } catch {
283
+ // Ignore parse errors
284
+ }
285
+ }
286
+
287
+ // Check for npm/yarn workspaces in package.json
288
+ const packageJsonPath = path.join(projectPath, 'package.json')
289
+ if (fs.existsSync(packageJsonPath)) {
290
+ try {
291
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
292
+ if (packageJson.workspaces) {
293
+ result.isMonorepo = true
294
+ result.type = result.type || 'workspaces'
295
+ // workspaces can be array or object with packages key
296
+ const workspaces = Array.isArray(packageJson.workspaces)
297
+ ? packageJson.workspaces
298
+ : packageJson.workspaces.packages || []
299
+ result.packages = result.packages.length ? result.packages : workspaces
300
+ if (!result.tool) {
301
+ result.tool =
302
+ result.packageManager === 'yarn' ? 'yarn' : result.packageManager
303
+ }
304
+ }
305
+ } catch {
306
+ // Ignore parse errors
307
+ }
308
+ }
309
+
310
+ // Resolve workspace package paths to actual directories
311
+ if (result.isMonorepo && result.packages.length > 0) {
312
+ result.resolvedPackages = resolveWorkspacePackages(
313
+ projectPath,
314
+ result.packages
315
+ )
316
+ }
317
+
318
+ return result
319
+ }
320
+
321
+ /**
322
+ * Resolve workspace glob patterns to actual package directories
323
+ * @param {string} projectPath - Root project path
324
+ * @param {Array<string>} patterns - Workspace patterns (e.g., ['packages/*', 'apps/*'])
325
+ * @returns {Array<Object>} Resolved packages with name and path
326
+ */
327
+ function resolveWorkspacePackages(projectPath, patterns) {
328
+ const fs = require('fs')
329
+ const path = require('path')
330
+ const packages = []
331
+
332
+ for (const pattern of patterns) {
333
+ // Handle simple glob patterns like 'packages/*'
334
+ if (pattern.endsWith('/*')) {
335
+ const baseDir = path.join(projectPath, pattern.slice(0, -2))
336
+ if (fs.existsSync(baseDir)) {
337
+ try {
338
+ const entries = fs.readdirSync(baseDir, { withFileTypes: true })
339
+ for (const entry of entries) {
340
+ if (entry.isDirectory()) {
341
+ const pkgPath = path.join(baseDir, entry.name)
342
+ const pkgJsonPath = path.join(pkgPath, 'package.json')
343
+ if (fs.existsSync(pkgJsonPath)) {
344
+ try {
345
+ const pkgJson = JSON.parse(
346
+ fs.readFileSync(pkgJsonPath, 'utf8')
347
+ )
348
+ packages.push({
349
+ name: pkgJson.name || entry.name,
350
+ path: pkgPath,
351
+ relativePath: path.relative(projectPath, pkgPath),
352
+ })
353
+ } catch {
354
+ packages.push({
355
+ name: entry.name,
356
+ path: pkgPath,
357
+ relativePath: path.relative(projectPath, pkgPath),
358
+ })
359
+ }
360
+ }
361
+ }
362
+ }
363
+ } catch {
364
+ // Ignore read errors
365
+ }
366
+ }
367
+ } else if (!pattern.includes('*')) {
368
+ // Direct path without glob
369
+ const pkgPath = path.join(projectPath, pattern)
370
+ const pkgJsonPath = path.join(pkgPath, 'package.json')
371
+ if (fs.existsSync(pkgJsonPath)) {
372
+ try {
373
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'))
374
+ packages.push({
375
+ name: pkgJson.name || path.basename(pkgPath),
376
+ path: pkgPath,
377
+ relativePath: pattern,
378
+ })
379
+ } catch {
380
+ packages.push({
381
+ name: path.basename(pkgPath),
382
+ path: pkgPath,
383
+ relativePath: pattern,
384
+ })
385
+ }
386
+ }
387
+ }
388
+ }
389
+
390
+ return packages
391
+ }
392
+
180
393
  module.exports = {
181
394
  mergeScripts,
182
395
  mergeDevDependencies,
@@ -184,4 +397,6 @@ module.exports = {
184
397
  detectPackageManager,
185
398
  getInstallCommand,
186
399
  getAuditCommand,
400
+ detectMonorepoType,
401
+ resolveWorkspacePackages,
187
402
  }
@@ -80,6 +80,10 @@ function applyProductionQualityFixes(projectPath = '.', options = {}) {
80
80
  copyIntegrationTestTemplates(projectPath, projectType)
81
81
  fixes.push(`✅ Added ${projectType} integration test templates`)
82
82
 
83
+ // Fix 6b: Add starter unit and e2e smoke test stubs
84
+ copyTestStubs(projectPath)
85
+ fixes.push('✅ Added unit and e2e smoke test stubs')
86
+
83
87
  // Fix 7: Apply security-first configuration
84
88
  const securityFixes = applySecurityFirstConfiguration(projectPath)
85
89
  fixes.push('✅ Applied security-first configuration:')
@@ -201,6 +205,35 @@ function getTestTypesDocumentation(projectType) {
201
205
  )
202
206
  }
203
207
 
208
+ function copyTestStubs(projectPath) {
209
+ const stubDir = path.join(__dirname, '../templates/test-stubs')
210
+ if (!fs.existsSync(stubDir)) return
211
+
212
+ const targets = [
213
+ {
214
+ source: path.join(stubDir, 'unit.test.js'),
215
+ dest: path.join(projectPath, 'tests', 'unit', 'sample.test.js'),
216
+ },
217
+ {
218
+ source: path.join(stubDir, 'e2e.smoke.test.js'),
219
+ dest: path.join(projectPath, 'tests', 'e2e', 'smoke.test.js'),
220
+ },
221
+ ]
222
+
223
+ targets.forEach(({ source, dest }) => {
224
+ if (!fs.existsSync(source)) return
225
+
226
+ const destDir = path.dirname(dest)
227
+ if (!fs.existsSync(destDir)) {
228
+ fs.mkdirSync(destDir, { recursive: true })
229
+ }
230
+
231
+ if (!fs.existsSync(dest)) {
232
+ fs.copyFileSync(source, dest)
233
+ }
234
+ })
235
+ }
236
+
204
237
  /**
205
238
  * Generate comprehensive pre-commit hook
206
239
  * This replaces the narrow CLAUDE.md-only validation
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-qa-architect",
3
- "version": "5.0.0",
3
+ "version": "5.0.1",
4
4
  "description": "QA Architect - Bootstrap quality automation for JavaScript/TypeScript and Python projects with GitHub Actions, pre-commit hooks, linting, formatting, and smart test strategy",
5
5
  "main": "setup.js",
6
6
  "bin": {
@@ -21,17 +21,17 @@
21
21
  "validate:comprehensive": "node setup.js --comprehensive --no-markdownlint",
22
22
  "validate:all": "npm run validate:comprehensive && npm run security:audit && npm run validate:claude",
23
23
  "validate:pre-push": "npm run test:patterns --if-present && npm run lint && npm run format:check && npm run test:commands --if-present && npm test --if-present",
24
- "test": "node tests/setup.test.js && node tests/integration.test.js && node tests/error-paths.test.js && node tests/error-messages.test.js && node tests/cache-manager.test.js && node tests/parallel-validation.test.js && node tests/python-integration.test.js && node tests/interactive.test.js && node tests/monorepo.test.js && node tests/template-loader.test.js && node tests/critical-fixes.test.js && node tests/interactive-routing-fix.test.js && node tests/telemetry.test.js && node tests/error-reporter.test.js && node tests/premium-dependency-monitoring.test.js && node tests/multi-language-dependency-monitoring.test.js && node tests/cli-deps-integration.test.js && node tests/real-world-packages.test.js && node tests/validation-factory.test.js && node tests/setup-error-coverage.test.js && node tests/python-detection-sensitivity.test.js && node tests/python-parser-fixes.test.js && node tests/licensing.test.js && node tests/security-licensing.test.js && node tests/real-purchase-flow.test.js && node tests/base-validator.test.js && node tests/dependency-monitoring-basic.test.js && node tests/workflow-validation.test.js && node tests/setup-critical-paths.test.js && node tests/project-maturity.test.js && node tests/project-maturity-cli.test.js && node tests/package-manager-detection.test.js && node tests/check-docs.test.js && node tests/validate-claude-md.test.js && node tests/validate-command-patterns.test.js && node tests/gitleaks-binary-resolution.test.js && node tests/gitleaks-production-checksums.test.js && node tests/gitleaks-checksum-verification.test.js && node tests/gitleaks-real-binary-test.js && node tests/tier-enforcement.test.js",
25
- "test:unit": "node tests/setup.test.js && node tests/error-paths.test.js && node tests/error-messages.test.js && node tests/cache-manager.test.js && node tests/template-loader.test.js && node tests/telemetry.test.js && node tests/error-reporter.test.js && node tests/validation-factory.test.js && node tests/setup-error-coverage.test.js && node tests/licensing.test.js && node tests/security-licensing.test.js && node tests/base-validator.test.js && node tests/dependency-monitoring-basic.test.js && node tests/workflow-validation.test.js && node tests/setup-critical-paths.test.js && node tests/project-maturity.test.js && node tests/package-manager-detection.test.js && node tests/check-docs.test.js && node tests/validate-claude-md.test.js && node tests/validate-command-patterns.test.js && node tests/gitleaks-binary-resolution.test.js && node tests/gitleaks-production-checksums.test.js && node tests/gitleaks-checksum-verification.test.js",
24
+ "test": "export QAA_DEVELOPER=true && node tests/setup.test.js && node tests/integration.test.js && node tests/error-paths.test.js && node tests/error-messages.test.js && node tests/cache-manager.test.js && node tests/parallel-validation.test.js && node tests/python-integration.test.js && node tests/interactive.test.js && node tests/monorepo.test.js && node tests/template-loader.test.js && node tests/critical-fixes.test.js && node tests/interactive-routing-fix.test.js && node tests/telemetry.test.js && node tests/error-reporter.test.js && node tests/premium-dependency-monitoring.test.js && node tests/multi-language-dependency-monitoring.test.js && node tests/cli-deps-integration.test.js && node tests/real-world-packages.test.js && node tests/validation-factory.test.js && node tests/setup-error-coverage.test.js && node tests/python-detection-sensitivity.test.js && node tests/python-parser-fixes.test.js && node tests/licensing.test.js && node tests/security-licensing.test.js && node tests/real-purchase-flow.test.js && node tests/base-validator.test.js && node tests/dependency-monitoring-basic.test.js && node tests/workflow-validation.test.js && node tests/setup-critical-paths.test.js && node tests/project-maturity.test.js && node tests/project-maturity-cli.test.js && node tests/package-manager-detection.test.js && node tests/check-docs.test.js && node tests/validate-claude-md.test.js && node tests/validate-command-patterns.test.js && node tests/gitleaks-binary-resolution.test.js && node tests/gitleaks-production-checksums.test.js && node tests/gitleaks-checksum-verification.test.js && node tests/gitleaks-real-binary-test.js && node tests/tier-enforcement.test.js",
25
+ "test:unit": "export QAA_DEVELOPER=true && node tests/setup.test.js && node tests/error-paths.test.js && node tests/error-messages.test.js && node tests/cache-manager.test.js && node tests/template-loader.test.js && node tests/telemetry.test.js && node tests/error-reporter.test.js && node tests/validation-factory.test.js && node tests/setup-error-coverage.test.js && node tests/licensing.test.js && node tests/security-licensing.test.js && node tests/base-validator.test.js && node tests/dependency-monitoring-basic.test.js && node tests/workflow-validation.test.js && node tests/setup-critical-paths.test.js && node tests/project-maturity.test.js && node tests/package-manager-detection.test.js && node tests/check-docs.test.js && node tests/validate-claude-md.test.js && node tests/validate-command-patterns.test.js && node tests/gitleaks-binary-resolution.test.js && node tests/gitleaks-production-checksums.test.js && node tests/gitleaks-checksum-verification.test.js",
26
26
  "test:fast": "npm run test:unit",
27
27
  "test:medium": "npm run test:fast && npm run test:patterns && npm run test:commands",
28
- "test:slow": "node tests/python-integration.test.js && node tests/interactive.test.js && node tests/monorepo.test.js && node tests/critical-fixes.test.js && node tests/interactive-routing-fix.test.js && node tests/premium-dependency-monitoring.test.js && node tests/multi-language-dependency-monitoring.test.js && node tests/cli-deps-integration.test.js && node tests/real-world-packages.test.js && node tests/python-detection-sensitivity.test.js && node tests/python-parser-fixes.test.js && node tests/real-purchase-flow.test.js && node tests/project-maturity-cli.test.js && node tests/gitleaks-real-binary-test.test.js && npm run test:e2e",
28
+ "test:slow": "export QAA_DEVELOPER=true && node tests/python-integration.test.js && node tests/interactive.test.js && node tests/monorepo.test.js && node tests/critical-fixes.test.js && node tests/interactive-routing-fix.test.js && node tests/premium-dependency-monitoring.test.js && node tests/multi-language-dependency-monitoring.test.js && node tests/cli-deps-integration.test.js && node tests/real-world-packages.test.js && node tests/python-detection-sensitivity.test.js && node tests/python-parser-fixes.test.js && node tests/real-purchase-flow.test.js && node tests/project-maturity-cli.test.js && node tests/gitleaks-real-binary-test.js && npm run test:e2e",
29
29
  "test:comprehensive": "npm run test:patterns && npm test && npm run test:commands && npm run test:e2e && npm run security:audit",
30
30
  "test:real-binary": "RUN_REAL_BINARY_TEST=1 node tests/gitleaks-real-binary-test.js",
31
- "test:commands": "node tests/command-execution.test.js",
31
+ "test:commands": "export QAA_DEVELOPER=true && node tests/command-execution.test.js",
32
32
  "test:patterns": "node scripts/validate-command-patterns.js",
33
33
  "test:coverage": "c8 --reporter=html --reporter=text --reporter=lcov npm test",
34
- "test:e2e": "bash scripts/test-e2e-package.sh",
34
+ "test:e2e": "export QAA_DEVELOPER=true && bash scripts/test-e2e-package.sh",
35
35
  "test:all": "npm run test:patterns && npm test && npm run test:commands && npm run test:e2e",
36
36
  "coverage": "npm run test:coverage && echo '\nCoverage report generated in coverage/index.html'",
37
37
  "docs:check": "bash scripts/check-docs.sh",
@@ -63,14 +63,16 @@
63
63
  "security-audit"
64
64
  ],
65
65
  "author": "Brett Stark",
66
- "license": "MIT",
66
+ "license": "SEE LICENSE IN LICENSE",
67
67
  "files": [
68
68
  "setup.js",
69
69
  "create-saas-monetization.js",
70
70
  "config/",
71
+ "docs/",
71
72
  "lib/",
72
73
  "legal/",
73
74
  "marketing/",
75
+ "templates/",
74
76
  ".github/",
75
77
  ".prettierrc",
76
78
  ".prettierignore",
package/setup.js CHANGED
@@ -113,6 +113,27 @@ const STYLELINT_EXTENSION_GLOB = `*.{${STYLELINT_EXTENSIONS.join(',')}}`
113
113
  const STYLELINT_SCAN_EXCLUDES = new Set(EXCLUDE_DIRECTORIES.STYLELINT)
114
114
  const MAX_STYLELINT_SCAN_DEPTH = SCAN_LIMITS.STYLELINT_MAX_DEPTH
115
115
 
116
+ function injectCollaborationSteps(workflowContent, options = {}) {
117
+ const { enableSlackAlerts = false, enablePrComments = false } = options
118
+ let updated = workflowContent
119
+
120
+ if (workflowContent.includes('# ALERTS_PLACEHOLDER')) {
121
+ const alertsJob = enableSlackAlerts
122
+ ? ` alerts:\n runs-on: ubuntu-latest\n needs: [summary]\n if: failure() || cancelled()\n steps:\n - name: Notify Slack on failures\n env:\n SLACK_WEBHOOK_URL: \${{ secrets.SLACK_WEBHOOK_URL }}\n run: |\n if [ -z "$SLACK_WEBHOOK_URL" ]; then\n echo "::warning::SLACK_WEBHOOK_URL not set; skipping Slack notification"\n exit 0\n fi\n payload='{"text":"❌ Quality checks failed for $GITHUB_REPOSITORY ($GITHUB_REF)"}'\n curl -X POST -H 'Content-type: application/json' --data "$payload" "$SLACK_WEBHOOK_URL"\n`
123
+ : ' # Slack alerts not enabled (use --alerts-slack to add)'
124
+ updated = updated.replace('# ALERTS_PLACEHOLDER', alertsJob)
125
+ }
126
+
127
+ if (workflowContent.includes('# PR_COMMENTS_PLACEHOLDER')) {
128
+ const prSteps = enablePrComments
129
+ ? ` - name: Post PR summary comment\n if: github.event_name == 'pull_request'\n uses: actions/github-script@v7\n with:\n script: |\n const summaryPath = process.env.GITHUB_STEP_SUMMARY\n const fs = require('fs')\n const body = summaryPath && fs.existsSync(summaryPath)\n ? fs.readFileSync(summaryPath, 'utf8')\n : 'Quality checks completed.'\n const { context, github } = require('@actions/github')\n await github.rest.issues.createComment({\n owner: context.repo.owner,\n repo: context.repo.repo,\n issue_number: context.payload.pull_request.number,\n body,\n })\n`
130
+ : ' # PR comment step not enabled (use --pr-comments to add)'
131
+ updated = updated.replace('# PR_COMMENTS_PLACEHOLDER', prSteps)
132
+ }
133
+
134
+ return updated
135
+ }
136
+
116
137
  /**
117
138
  * Safely reads directory contents without throwing on permission errors
118
139
  *
@@ -312,6 +333,13 @@ function parseArguments(rawArgs) {
312
333
  const isValidateConfigMode = sanitizedArgs.includes('--validate-config')
313
334
  const isActivateLicenseMode = sanitizedArgs.includes('--activate-license')
314
335
  const isDryRun = sanitizedArgs.includes('--dry-run')
336
+ const ciProviderIndex = sanitizedArgs.findIndex(arg => arg === '--ci')
337
+ const ciProvider =
338
+ ciProviderIndex !== -1 && sanitizedArgs[ciProviderIndex + 1]
339
+ ? sanitizedArgs[ciProviderIndex + 1].toLowerCase()
340
+ : 'github'
341
+ const enableSlackAlerts = sanitizedArgs.includes('--alerts-slack')
342
+ const enablePrComments = sanitizedArgs.includes('--pr-comments')
315
343
 
316
344
  // Custom template directory - use raw args to preserve valid path characters (&, <, >, etc.)
317
345
  // Normalize path to prevent traversal attacks and make absolute
@@ -344,6 +372,9 @@ function parseArguments(rawArgs) {
344
372
  isValidateConfigMode,
345
373
  isActivateLicenseMode,
346
374
  isDryRun,
375
+ ciProvider,
376
+ enableSlackAlerts,
377
+ enablePrComments,
347
378
  customTemplatePath,
348
379
  disableNpmAudit,
349
380
  disableGitleaks,
@@ -378,6 +409,9 @@ function parseArguments(rawArgs) {
378
409
  isValidateConfigMode,
379
410
  isActivateLicenseMode,
380
411
  isDryRun,
412
+ ciProvider,
413
+ enableSlackAlerts,
414
+ enablePrComments,
381
415
  customTemplatePath,
382
416
  disableNpmAudit,
383
417
  disableGitleaks,
@@ -444,7 +478,12 @@ function parseArguments(rawArgs) {
444
478
  isTelemetryStatusMode,
445
479
  isErrorReportingStatusMode,
446
480
  isCheckMaturityMode,
481
+ isValidateConfigMode,
482
+ isActivateLicenseMode,
447
483
  isDryRun,
484
+ ciProvider,
485
+ enableSlackAlerts,
486
+ enablePrComments,
448
487
  customTemplatePath,
449
488
  disableNpmAudit,
450
489
  disableGitleaks,
@@ -481,6 +520,7 @@ SETUP OPTIONS:
481
520
  --update Update existing configuration
482
521
  --deps Add basic dependency monitoring (Free Tier)
483
522
  --dependency-monitoring Same as --deps
523
+ --ci <provider> Select CI provider: github (default) | gitlab | circleci
484
524
  --template <path> Use custom templates from specified directory
485
525
  --dry-run Preview changes without modifying files
486
526
 
@@ -506,6 +546,10 @@ GRANULAR TOOL CONTROL:
506
546
  --no-markdownlint Disable markdownlint markdown formatting checks
507
547
  --no-eslint-security Disable ESLint security rule checking
508
548
 
549
+ ALERTING & COLLABORATION (GitHub CI):
550
+ --alerts-slack Add Slack webhook notification step (expects secret SLACK_WEBHOOK_URL)
551
+ --pr-comments Add PR summary comment step (uses GitHub token)
552
+
509
553
  EXAMPLES:
510
554
  npx create-qa-architect@latest
511
555
  → Set up quality automation with all tools
@@ -697,7 +741,7 @@ HELP:
697
741
  if (!capCheck.allowed) {
698
742
  console.error(`❌ ${capCheck.reason}`)
699
743
  console.error(
700
- ' Upgrade to Pro, Team, or Enterprise for unlimited runs: https://vibebuildlab.com/cqa'
744
+ ' Upgrade to Pro, Team, or Enterprise for unlimited runs: https://vibebuildlab.com/qaa'
701
745
  )
702
746
  process.exit(1)
703
747
  }
@@ -961,7 +1005,7 @@ HELP:
961
1005
  if (!repoCheck.allowed) {
962
1006
  console.error(`\n❌ ${repoCheck.reason}`)
963
1007
  console.error(
964
- ' Upgrade to Pro for unlimited repos: https://vibebuildlab.com/cqa'
1008
+ ' Upgrade to Pro for unlimited repos: https://vibebuildlab.com/qaa'
965
1009
  )
966
1010
  process.exit(1)
967
1011
  }
@@ -969,7 +1013,7 @@ HELP:
969
1013
  // Register this repo
970
1014
  incrementUsage('repo', 1, repoId)
971
1015
  console.log(
972
- `✅ Registered repo (FREE tier: ${currentRepos.length + 1}/1 repos used)`
1016
+ `✅ Registered repo (FREE tier: ${(repoCheck.usage?.repoCount || 0) + 1}/1 repos used)`
973
1017
  )
974
1018
  }
975
1019
  }
@@ -1351,28 +1395,72 @@ HELP:
1351
1395
  process.exit(1)
1352
1396
  }
1353
1397
 
1354
- // Create .github/workflows directory if it doesn't exist
1398
+ // Create CI configuration based on provider
1355
1399
  const configSpinner = showProgress('Copying configuration files...')
1356
- const workflowDir = path.join(process.cwd(), '.github', 'workflows')
1357
- if (!fs.existsSync(workflowDir)) {
1358
- fs.mkdirSync(workflowDir, { recursive: true })
1359
- console.log('📁 Created .github/workflows directory')
1360
- }
1400
+ const githubWorkflowDir = path.join(process.cwd(), '.github', 'workflows')
1361
1401
 
1362
- // Copy workflow file if it doesn't exist
1363
- const workflowFile = path.join(workflowDir, 'quality.yml')
1364
- if (!fs.existsSync(workflowFile)) {
1365
- const templateWorkflow =
1366
- templateLoader.getTemplate(
1367
- templates,
1368
- path.join('.github', 'workflows', 'quality.yml')
1369
- ) ||
1370
- fs.readFileSync(
1371
- path.join(__dirname, '.github/workflows/quality.yml'),
1372
- 'utf8'
1373
- )
1374
- fs.writeFileSync(workflowFile, templateWorkflow)
1375
- console.log('✅ Added GitHub Actions workflow')
1402
+ if (ciProvider === 'gitlab') {
1403
+ const gitlabConfigPath = path.join(process.cwd(), '.gitlab-ci.yml')
1404
+ if (!fs.existsSync(gitlabConfigPath)) {
1405
+ const templateGitlab =
1406
+ templateLoader.getTemplate(
1407
+ templates,
1408
+ path.join('ci', 'gitlab-ci.yml')
1409
+ ) ||
1410
+ fs.readFileSync(
1411
+ path.join(__dirname, 'templates/ci/gitlab-ci.yml'),
1412
+ 'utf8'
1413
+ )
1414
+ fs.writeFileSync(gitlabConfigPath, templateGitlab)
1415
+ console.log('✅ Added GitLab CI workflow')
1416
+ }
1417
+ } else if (ciProvider === 'circleci') {
1418
+ const circleDir = path.join(process.cwd(), '.circleci')
1419
+ if (!fs.existsSync(circleDir)) {
1420
+ fs.mkdirSync(circleDir, { recursive: true })
1421
+ console.log('📁 Created .circleci directory')
1422
+ }
1423
+ const circleConfigPath = path.join(circleDir, 'config.yml')
1424
+ if (!fs.existsSync(circleConfigPath)) {
1425
+ const templateCircle =
1426
+ templateLoader.getTemplate(
1427
+ templates,
1428
+ path.join('ci', 'circleci-config.yml')
1429
+ ) ||
1430
+ fs.readFileSync(
1431
+ path.join(__dirname, 'templates/ci/circleci-config.yml'),
1432
+ 'utf8'
1433
+ )
1434
+ fs.writeFileSync(circleConfigPath, templateCircle)
1435
+ console.log('✅ Added CircleCI workflow')
1436
+ }
1437
+ } else {
1438
+ // Default: GitHub Actions
1439
+ if (!fs.existsSync(githubWorkflowDir)) {
1440
+ fs.mkdirSync(githubWorkflowDir, { recursive: true })
1441
+ console.log('📁 Created .github/workflows directory')
1442
+ }
1443
+
1444
+ const workflowFile = path.join(githubWorkflowDir, 'quality.yml')
1445
+ if (!fs.existsSync(workflowFile)) {
1446
+ let templateWorkflow =
1447
+ templateLoader.getTemplate(
1448
+ templates,
1449
+ path.join('.github', 'workflows', 'quality.yml')
1450
+ ) ||
1451
+ fs.readFileSync(
1452
+ path.join(__dirname, '.github/workflows/quality.yml'),
1453
+ 'utf8'
1454
+ )
1455
+
1456
+ templateWorkflow = injectCollaborationSteps(templateWorkflow, {
1457
+ enableSlackAlerts,
1458
+ enablePrComments,
1459
+ })
1460
+
1461
+ fs.writeFileSync(workflowFile, templateWorkflow)
1462
+ console.log('✅ Added GitHub Actions workflow')
1463
+ }
1376
1464
  }
1377
1465
 
1378
1466
  // Copy Prettier config if it doesn't exist
@@ -1602,7 +1690,7 @@ try {
1602
1690
  const CAP = 50
1603
1691
  if (usage.prePushRuns >= CAP) {
1604
1692
  console.error('❌ Free tier limit reached: ' + usage.prePushRuns + '/' + CAP + ' pre-push runs this month')
1605
- console.error(' Upgrade to Pro, Team, or Enterprise: https://vibebuildlab.com/cqa')
1693
+ console.error(' Upgrade to Pro, Team, or Enterprise: https://vibebuildlab.com/qaa')
1606
1694
  process.exit(1)
1607
1695
  }
1608
1696
 
@@ -1762,20 +1850,25 @@ echo "✅ Pre-push validation passed!"
1762
1850
  console.log('✅ Added requirements-dev.txt')
1763
1851
  }
1764
1852
 
1765
- // Copy Python workflow
1766
- const pythonWorkflowFile = path.join(workflowDir, 'quality-python.yml')
1767
- if (!fs.existsSync(pythonWorkflowFile)) {
1768
- const templatePythonWorkflow =
1769
- templateLoader.getTemplate(
1770
- templates,
1771
- path.join('config', 'quality-python.yml')
1772
- ) ||
1773
- fs.readFileSync(
1774
- path.join(__dirname, 'config/quality-python.yml'),
1775
- 'utf8'
1776
- )
1777
- fs.writeFileSync(pythonWorkflowFile, templatePythonWorkflow)
1778
- console.log('✅ Added Python GitHub Actions workflow')
1853
+ // Copy Python workflow (GitHub Actions only)
1854
+ if (ciProvider === 'github') {
1855
+ const pythonWorkflowFile = path.join(
1856
+ githubWorkflowDir,
1857
+ 'quality-python.yml'
1858
+ )
1859
+ if (!fs.existsSync(pythonWorkflowFile)) {
1860
+ const templatePythonWorkflow =
1861
+ templateLoader.getTemplate(
1862
+ templates,
1863
+ path.join('config', 'quality-python.yml')
1864
+ ) ||
1865
+ fs.readFileSync(
1866
+ path.join(__dirname, 'config/quality-python.yml'),
1867
+ 'utf8'
1868
+ )
1869
+ fs.writeFileSync(pythonWorkflowFile, templatePythonWorkflow)
1870
+ console.log('✅ Added Python GitHub Actions workflow')
1871
+ }
1779
1872
  }
1780
1873
 
1781
1874
  // Create tests directory if it doesn't exist