create-qa-architect 5.13.6 → 5.14.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.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * License Validator (user-side)
3
3
  *
4
- * - No Stripe dependencies (secrets stay server-side)
4
+ * - No payment provider dependencies (provider secrets stay server-side)
5
5
  * - Fetches a signed license registry from a configurable HTTPS endpoint
6
6
  * - Caches locally for offline use with graceful fallback
7
7
  */
@@ -22,6 +22,41 @@ const {
22
22
  verifyRegistryMetadata,
23
23
  } = require('@buildproven/license-core')
24
24
 
25
+ function loadBundledPublicKey() {
26
+ try {
27
+ const keyPath = path.join(__dirname, '..', 'public-key.pem')
28
+ if (fs.existsSync(keyPath)) {
29
+ return fs.readFileSync(keyPath, 'utf8')
30
+ }
31
+ } catch {
32
+ // bundled key unavailable; caller will warn
33
+ }
34
+ return null
35
+ }
36
+
37
+ /**
38
+ * Minimal semver compare for the major.minor.patch core (ignores pre-release
39
+ * tags). Returns 1 if a > b, -1 if a < b, 0 if equal. Avoids depending on the
40
+ * transitive `semver` package for a single comparison. Unparseable input → 0
41
+ * (treated as equal, so a malformed recommended version never triggers a warn).
42
+ */
43
+ function compareSemver(a, b) {
44
+ const parse = v =>
45
+ String(v)
46
+ .split('-')[0]
47
+ .split('.')
48
+ .map(n => Number.parseInt(n, 10))
49
+ const pa = parse(a)
50
+ const pb = parse(b)
51
+ for (let i = 0; i < 3; i++) {
52
+ const x = Number.isFinite(pa[i]) ? pa[i] : 0
53
+ const y = Number.isFinite(pb[i]) ? pb[i] : 0
54
+ if (x > y) return 1
55
+ if (x < y) return -1
56
+ }
57
+ return 0
58
+ }
59
+
25
60
  /**
26
61
  * TD10 fix: Timing-safe string comparison to prevent timing attacks
27
62
  * on hash/signature verification.
@@ -90,15 +125,24 @@ class LicenseValidator {
90
125
  process.env.QAA_LICENSE_DB_URL ||
91
126
  'https://licenses.buildproven.ai/api/licenses/qa-architect.json'
92
127
 
93
- this.licensePublicKey = loadKeyFromEnv(
94
- process.env.QAA_LICENSE_PUBLIC_KEY,
95
- process.env.QAA_LICENSE_PUBLIC_KEY_PATH
96
- )
128
+ this.licensePublicKey =
129
+ loadKeyFromEnv(
130
+ process.env.QAA_LICENSE_PUBLIC_KEY,
131
+ process.env.QAA_LICENSE_PUBLIC_KEY_PATH
132
+ ) || loadBundledPublicKey()
97
133
 
98
134
  // DR14 fix: Add in-memory cache with 5-minute TTL
99
135
  this.dbCache = null
100
136
  this.dbCacheTime = 0
101
137
  this.cacheTTL = 5 * 60 * 1000 // 5 minutes
138
+
139
+ // Periodic re-validation: an activated license is re-checked against the
140
+ // signed registry on this cadence so a cancelled/revoked subscription stops
141
+ // unlocking Pro. Offline runs fail OPEN (keep access) so we never lock out
142
+ // a paying user without network; only a successful fresh fetch can downgrade.
143
+ const days = Number(process.env.QAA_LICENSE_REVALIDATE_DAYS)
144
+ this.revalidateIntervalMs =
145
+ (Number.isFinite(days) && days > 0 ? days : 7) * 24 * 60 * 60 * 1000
102
146
  }
103
147
 
104
148
  normalizeLicenseKey(key) {
@@ -286,6 +330,155 @@ class LicenseValidator {
286
330
  }
287
331
  }
288
332
 
333
+ /**
334
+ * Fetch + verify the signed registry from the network, reporting whether the
335
+ * result is genuinely fresh. Unlike fetchLegitimateDatabase (which silently
336
+ * falls back to cache on network failure), this distinguishes a live fetch
337
+ * from a stale cache so revocation logic never downgrades on stale data.
338
+ * Returns { fresh: boolean, registry: object|null }.
339
+ */
340
+ async fetchRegistryStrict() {
341
+ try {
342
+ const parsedUrl = new URL(this.licenseDbUrl)
343
+ const isTest = process.argv.join(' ').includes('test')
344
+ if (
345
+ parsedUrl.protocol !== 'https:' &&
346
+ !process.env.QAA_ALLOW_INSECURE_LICENSE_DB &&
347
+ !isTest
348
+ ) {
349
+ throw new Error('license database URL must use HTTPS')
350
+ }
351
+
352
+ const controller = new AbortController()
353
+ const timeout = setTimeout(() => controller.abort(), 10000)
354
+ const response = await fetch(this.licenseDbUrl, {
355
+ signal: controller.signal,
356
+ headers: { 'User-Agent': 'create-qa-architect-cli' },
357
+ })
358
+ clearTimeout(timeout)
359
+
360
+ if (!response.ok) {
361
+ throw new Error(`HTTP ${response.status}`)
362
+ }
363
+
364
+ const database = await response.json()
365
+ if (!database || typeof database !== 'object' || !database._metadata) {
366
+ throw new Error('invalid database format')
367
+ }
368
+
369
+ // Throws if the signed registry fails verification.
370
+ this.verifyRegistrySignature(database)
371
+
372
+ // Refresh the local cache so offline runs use the latest known-good copy.
373
+ this.ensureLicenseDir()
374
+ fs.writeFileSync(this.legitimateDBFile, JSON.stringify(database, null, 2))
375
+
376
+ return { fresh: true, registry: database }
377
+ } catch (error) {
378
+ // Network/verification failure → not fresh. Caller fails open (offline).
379
+ if (process.env.DEBUG) {
380
+ console.warn(`⚠️ Registry re-check skipped: ${error.message}`)
381
+ }
382
+ return { fresh: false, registry: null }
383
+ }
384
+ }
385
+
386
+ /**
387
+ * Whether an activated license is due for a server re-check.
388
+ * Uses the verifiedAt timestamp persisted by saveLicense().
389
+ */
390
+ needsRevalidation(localLicense) {
391
+ if (!localLicense) return false
392
+ const last = Date.parse(
393
+ localLicense.verifiedAt || localLicense.activated || ''
394
+ )
395
+ if (!Number.isFinite(last)) return true // unknown age → re-check
396
+ return Date.now() - last > this.revalidateIntervalMs
397
+ }
398
+
399
+ /**
400
+ * Re-confirm an already-activated license against the signed registry.
401
+ * Only ever downgrades on a genuinely fresh fetch; offline → keeps access.
402
+ * Returns { active: boolean, reason?: string, registry?: object }.
403
+ */
404
+ async revalidateLocalLicense(localLicense) {
405
+ const { fresh, registry } = await this.fetchRegistryStrict()
406
+ if (!fresh) {
407
+ // Offline or registry unreachable — fail open, do not lock out.
408
+ return { active: true, reason: 'offline-kept' }
409
+ }
410
+
411
+ const { _metadata: _meta, ...entries } = registry
412
+ void _meta
413
+ const key = this.normalizeLicenseKey(
414
+ localLicense.licenseKey || localLicense.key
415
+ )
416
+ const entry = entries[key]
417
+
418
+ if (!entry || entry.status === 'revoked') {
419
+ // Clear the local file so every later getLicenseInfo()/hasFeature() read
420
+ // resolves to FREE — the downgrade must persist, not be in-memory only.
421
+ this.removeLicense()
422
+ return { active: false, reason: 'revoked-or-removed', registry }
423
+ }
424
+
425
+ // Re-verify the signed entry (catches tier tampering / key rotation).
426
+ const verification = validateRegistryEntry({
427
+ licenseKey: key,
428
+ entry,
429
+ publicKeyPem: this.licensePublicKey,
430
+ userEmailHash: localLicense.payload?.emailHash || undefined,
431
+ })
432
+ if (!verification.valid) {
433
+ this.removeLicense()
434
+ return { active: false, reason: 'verification-failed', registry }
435
+ }
436
+
437
+ // Still valid — stamp a fresh verifiedAt so we don't re-check every run.
438
+ this.refreshVerifiedAt()
439
+
440
+ // Non-fatal: nudge Pro users onto the latest CLI. The server controls the
441
+ // recommended floor via signed registry metadata; we never block on it.
442
+ this.warnIfOutdated(registry?._metadata?.minRecommendedVersion)
443
+
444
+ return { active: true, reason: 'confirmed', registry }
445
+ }
446
+
447
+ /**
448
+ * Print a loud but non-fatal warning if the installed CLI is older than the
449
+ * server-recommended minimum. No-op when the field is absent or unparseable.
450
+ */
451
+ warnIfOutdated(minRecommendedVersion) {
452
+ if (!minRecommendedVersion) return
453
+ const current = require('../package.json').version
454
+ if (compareSemver(current, minRecommendedVersion) >= 0) return
455
+ console.warn('')
456
+ console.warn(
457
+ `⚠️ A newer QA Architect is available (recommended: ${minRecommendedVersion}, you have ${current}).`
458
+ )
459
+ console.warn(
460
+ ' Pro licensing and expiry handling improve with each release.'
461
+ )
462
+ console.warn(' Update: npx create-qa-architect@latest')
463
+ console.warn('')
464
+ }
465
+
466
+ /**
467
+ * Update only the verifiedAt timestamp on the persisted license file.
468
+ */
469
+ refreshVerifiedAt() {
470
+ try {
471
+ if (!fs.existsSync(this.licenseFile)) return
472
+ const record = JSON.parse(fs.readFileSync(this.licenseFile, 'utf8'))
473
+ record.verifiedAt = new Date().toISOString()
474
+ fs.writeFileSync(this.licenseFile, JSON.stringify(record, null, 2))
475
+ } catch (error) {
476
+ if (process.env.DEBUG) {
477
+ console.warn(`⚠️ Could not refresh verifiedAt: ${error.message}`)
478
+ }
479
+ }
480
+ }
481
+
289
482
  /**
290
483
  * Validate license key (fetches latest database, then validates locally)
291
484
  */
@@ -677,7 +870,8 @@ class LicenseValidator {
677
870
  }
678
871
 
679
872
  /**
680
- * Remove license (for testing)
873
+ * Remove the persisted local license file (used on confirmed revocation and
874
+ * by tests). After this, getLicenseInfo() resolves to FREE.
681
875
  */
682
876
  removeLicense() {
683
877
  try {
package/lib/licensing.js CHANGED
@@ -48,7 +48,7 @@ Object.defineProperty(exports, 'LICENSE_FILE', {
48
48
  *
49
49
  * Pricing:
50
50
  * - FREE: $0 (Hobby/OSS - capped)
51
- * - PRO: $49/mo or $490/yr (Solo Devs/Small Teams)
51
+ * - PRO: $29/mo or $290/yr (Solo Devs/Small Teams)
52
52
  */
53
53
  // DR23 fix: Freeze object to prevent accidental or malicious mutation
54
54
  const LICENSE_TIERS = Object.freeze({
@@ -119,6 +119,14 @@ const FEATURES = deepFreeze({
119
119
  envValidation: false, // ❌ PRO feature - env vars audit
120
120
  // CI/CD optimization
121
121
  ciCostAnalysis: false, // ❌ PRO feature - GitHub Actions cost analysis
122
+ ciDoctor: false, // ❌ PRO feature - flaky test + waste detection
123
+ // Release confidence (AI-assisted dev gates)
124
+ shipCheck: false, // ❌ PRO feature - unified release readiness report
125
+ prCheck: false, // ❌ PRO feature - diff-aware risk classifier
126
+ historicalSecretsScan: false, // ❌ PRO feature - full-history secrets audit
127
+ // Vibe-code audit
128
+ auditBasic: true, // ✅ FREE - semgrep SAST + npm audit (5/7 categories)
129
+ auditPro: false, // ❌ PRO - hallucination check + --fix Claude Code prompts
122
130
  roadmap: [
123
131
  '✅ ESLint, Prettier, Stylelint configuration',
124
132
  '✅ Basic Husky pre-commit hooks',
@@ -126,9 +134,15 @@ const FEATURES = deepFreeze({
126
134
  '✅ Lighthouse CI (basic, no thresholds)',
127
135
  '✅ axe-core accessibility testing',
128
136
  '✅ Conventional commits (commitlint)',
137
+ '✅ Security audit (semgrep SAST + npm CVEs) — qa-architect --audit',
129
138
  '⚠️ Limited: 1 private repo, JS/TS only',
130
- '❌ No security scanning (Gitleaks, ESLint security)',
139
+ '❌ No Gitleaks secrets scanning (Pro)',
140
+ '❌ No hallucinated package detection (Pro)',
141
+ '❌ No --fix Claude Code prompts (Pro)',
131
142
  '❌ No Smart Test Strategy',
143
+ '❌ No release readiness gate (--ship-check)',
144
+ '❌ No diff risk review (--pr-check)',
145
+ '❌ No CI doctor or historical secrets scan',
132
146
  ],
133
147
  },
134
148
  [LICENSE_TIERS.PRO]: {
@@ -164,10 +178,21 @@ const FEATURES = deepFreeze({
164
178
  envValidation: true, // ✅ Env vars audit
165
179
  // CI/CD optimization
166
180
  ciCostAnalysis: true, // ✅ GitHub Actions cost analysis
181
+ ciDoctor: true, // ✅ Flaky test + waste detection
182
+ // Release confidence (AI-assisted dev gates)
183
+ shipCheck: true, // ✅ Unified release readiness report
184
+ prCheck: true, // ✅ Diff-aware risk classifier
185
+ historicalSecretsScan: true, // ✅ Full-history secrets audit
186
+ // Vibe-code audit
187
+ auditBasic: true, // ✅ semgrep SAST + npm audit
188
+ auditPro: true, // ✅ hallucination check + --fix Claude Code prompts
167
189
  roadmap: [
168
190
  '✅ Unlimited repos and runs',
191
+ '✅ Security audit — semgrep SAST + npm CVEs + hallucination detection',
192
+ '✅ --fix flag: Claude Code prompts for every Critical/High finding',
169
193
  '✅ Smart Test Strategy (70% faster pre-push validation)',
170
194
  '✅ Security scanning (Gitleaks + ESLint security rules)',
195
+ '✅ Historical secrets scan (full git history audit)',
171
196
  '✅ TypeScript production protection',
172
197
  '✅ Multi-language (Python, Rust, Ruby)',
173
198
  '✅ Framework-aware dependency grouping',
@@ -175,6 +200,9 @@ const FEATURES = deepFreeze({
175
200
  '✅ Bundle size limits (size-limit)',
176
201
  '✅ Coverage threshold enforcement',
177
202
  '✅ Pre-launch validation with env vars audit',
203
+ '✅ Release readiness report (--ship-check)',
204
+ '✅ Diff risk review (--pr-check) for AI-assisted dev',
205
+ '✅ CI doctor (flaky tests + workflow waste detection)',
178
206
  '✅ Email support (24-48h response)',
179
207
  ],
180
208
  },
@@ -221,7 +249,7 @@ function isDeveloperMode() {
221
249
  }
222
250
 
223
251
  /**
224
- * Check if user has a valid license file (USER-FACING - NO STRIPE DEPENDENCIES)
252
+ * Check if user has a valid license file (USER-FACING no payment provider dependencies)
225
253
  */
226
254
  function getLicenseInfo() {
227
255
  try {
@@ -303,14 +331,73 @@ function getLicenseInfo() {
303
331
  }
304
332
 
305
333
  /**
306
- * License key validation with Stripe integration
307
- * Supports both legacy format and new Stripe-generated keys
334
+ * Periodic online re-check for an already-activated license.
335
+ *
336
+ * getLicenseInfo() is synchronous and trusts the locally-signed license file.
337
+ * That alone cannot notice a cancelled/revoked subscription, because the signed
338
+ * payload carries no expiry. This async gate, awaited at the top of every Pro
339
+ * command handler, re-confirms the key against the signed registry on a cadence
340
+ * and downgrades to FREE if it has been revoked or removed.
341
+ *
342
+ * Fails OPEN when offline (keeps Pro) so a paying user is never locked out
343
+ * without network. Only a genuinely fresh, verified registry fetch downgrades.
344
+ */
345
+ async function ensureLicenseFresh() {
346
+ const license = getLicenseInfo()
347
+
348
+ // Nothing to re-check for FREE, developer mode, or an already-invalid file.
349
+ if (
350
+ license.isDeveloper ||
351
+ license.tier === LICENSE_TIERS.FREE ||
352
+ !license.valid
353
+ ) {
354
+ return license
355
+ }
356
+
357
+ try {
358
+ const { LicenseValidator } = require('./license-validator')
359
+ const validator = new LicenseValidator()
360
+ const localLicense = validator.getLocalLicense()
361
+
362
+ if (!validator.needsRevalidation(localLicense)) {
363
+ return license // checked recently — fast path
364
+ }
365
+
366
+ const result = await validator.revalidateLocalLicense(localLicense)
367
+ if (!result.active) {
368
+ console.warn('')
369
+ console.warn(
370
+ '⚠️ Your Pro license is no longer active (subscription cancelled or revoked).'
371
+ )
372
+ console.warn(
373
+ ' Reactivate: npx create-qa-architect@latest --activate-license'
374
+ )
375
+ console.warn('')
376
+ return {
377
+ tier: LICENSE_TIERS.FREE,
378
+ valid: true,
379
+ error: `License no longer active: ${result.reason}`,
380
+ }
381
+ }
382
+ } catch (error) {
383
+ // Never let a re-check error block a paying user — fail open, but surface it.
384
+ if (process.env.DEBUG) {
385
+ console.warn(`⚠️ License re-check error: ${error.message}`)
386
+ }
387
+ }
388
+
389
+ return license
390
+ }
391
+
392
+ /**
393
+ * License key validation.
394
+ * Supports both legacy format and webhook-issued keys.
308
395
  */
309
396
  function validateLicenseKey(key, tier) {
310
397
  const normalizedKey = normalizeLicenseKey(key)
311
398
  // TD15 fix: Use shared constant for license key pattern
312
399
  if (LICENSE_KEY_PATTERN.test(normalizedKey)) {
313
- // Stripe-generated key - always valid if properly formatted
400
+ // Webhook-issued key valid if properly formatted
314
401
  return true
315
402
  }
316
403
 
@@ -320,7 +407,7 @@ function validateLicenseKey(key, tier) {
320
407
  }
321
408
 
322
409
  /**
323
- * Verify license signature using the same algorithm as StripeIntegration
410
+ * Verify license signature against the public registry key.
324
411
  */
325
412
  function verifyLicenseSignature(payload, signature) {
326
413
  try {
@@ -388,7 +475,7 @@ function showUpgradeMessage(feature) {
388
475
  if (license.tier === LICENSE_TIERS.FREE) {
389
476
  console.log('\n🚀 Upgrade to PRO')
390
477
  console.log('')
391
- console.log(' 💰 $49/month or $490/year (save $98)')
478
+ console.log(' 💰 $29/month or $290/year (save $58)')
392
479
  console.log('')
393
480
  console.log(' ✅ Unlimited repos, LOC, and runs')
394
481
  console.log(' ✅ Smart Test Strategy (70% faster pre-push)')
@@ -504,7 +591,7 @@ function saveLicenseWithSignature(tier, key, email, validation) {
504
591
 
505
592
  const licenseData = {
506
593
  tier,
507
- licenseKey: normalizedKey, // ✅ Changed from 'key' to 'licenseKey' to match StripeIntegration
594
+ licenseKey: normalizedKey,
508
595
  email,
509
596
  expires: validation.expires,
510
597
  activated: new Date().toISOString(),
@@ -542,11 +629,11 @@ function removeLicense() {
542
629
  }
543
630
 
544
631
  /**
545
- * Activate license (USER-FACING - NO STRIPE DEPENDENCIES)
632
+ * Activate license (USER-FACING no payment provider dependencies)
546
633
  */
547
634
  async function activateLicense(licenseKey, email) {
548
635
  try {
549
- // Use pure license validator (no Stripe dependencies)
636
+ // Use pure license validator (no payment provider dependencies)
550
637
  const { LicenseValidator } = require('./license-validator')
551
638
  const validator = new LicenseValidator()
552
639
 
@@ -1146,6 +1233,7 @@ module.exports = {
1146
1233
  LICENSE_TIERS,
1147
1234
  FEATURES,
1148
1235
  getLicenseInfo,
1236
+ ensureLicenseFresh,
1149
1237
  hasFeature,
1150
1238
  getDependencyMonitoringLevel,
1151
1239
  getSupportedLanguages,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-qa-architect",
3
- "version": "5.13.6",
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",
3
+ "version": "5.14.0",
4
+ "description": "QA Architect - Security audit and quality automation for AI-generated codebases. Scans for OWASP Top-10 vulnerabilities, CVEs, and common vibe-coding mistakes.",
5
5
  "main": "setup.js",
6
6
  "bin": {
7
7
  "create-qa-architect": "setup.js"
@@ -20,8 +20,8 @@
20
20
  "validate:comprehensive": "node setup.js --comprehensive --no-markdownlint",
21
21
  "validate:all": "npm run validate:comprehensive && npm run security:audit",
22
22
  "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",
23
- "test": "export QAA_DEVELOPER=true && node tests/result-types.test.js && 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/deps-edge-cases.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/workflow-tiers.test.js && node tests/analyze-ci.test.js && node tests/analyze-ci-integration.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-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 && node tests/lazy-loader.test.js && node tests/template-content-validation.test.js && node tests/ci-environment.test.js && node tests/turborepo-detection.test.js && node tests/consumer-workflow-integration.test.js && node tests/esm-project-support.test.js && node tests/blob-storage.test.js",
24
- "test:unit": "export QAA_DEVELOPER=true && node tests/result-types.test.js && 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/workflow-tiers.test.js && node tests/analyze-ci.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-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/lazy-loader.test.js && node tests/template-content-validation.test.js && node tests/ci-environment.test.js && node tests/turborepo-detection.test.js && node tests/esm-project-support.test.js && node tests/blob-storage.test.js",
23
+ "test": "export QAA_DEVELOPER=true && node tests/result-types.test.js && 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/deps-edge-cases.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/workflow-tiers.test.js && node tests/analyze-ci.test.js && node tests/analyze-ci-integration.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-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 && node tests/lazy-loader.test.js && node tests/template-content-validation.test.js && node tests/ci-environment.test.js && node tests/turborepo-detection.test.js && node tests/consumer-workflow-integration.test.js && node tests/esm-project-support.test.js && node tests/blob-storage.test.js && node tests/audit.test.js && node tests/audit-packaging.test.js && node tests/ship-check.test.js && node tests/pr-check.test.js && node tests/ci-doctor.test.js && node tests/history-scan.test.js && node tests/risk-policy-gate.test.js && node tests/license-revalidation.test.js",
24
+ "test:unit": "export QAA_DEVELOPER=true && node tests/result-types.test.js && 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/workflow-tiers.test.js && node tests/analyze-ci.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-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/lazy-loader.test.js && node tests/template-content-validation.test.js && node tests/ci-environment.test.js && node tests/turborepo-detection.test.js && node tests/esm-project-support.test.js && node tests/blob-storage.test.js && node tests/audit.test.js && node tests/audit-packaging.test.js && node tests/risk-policy-gate.test.js && node tests/license-revalidation.test.js",
25
25
  "test:fast": "npm run test:unit",
26
26
  "test:medium": "npm run test:fast && npm run test:patterns && npm run test:commands",
27
27
  "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",
@@ -81,7 +81,7 @@
81
81
  "security-audit"
82
82
  ],
83
83
  "author": "BuildProven",
84
- "license": "SEE LICENSE IN LICENSE",
84
+ "license": "Apache-2.0",
85
85
  "files": [
86
86
  "setup.js",
87
87
  "config/",
@@ -91,6 +91,7 @@
91
91
  "marketing/",
92
92
  "templates/",
93
93
  "scripts/",
94
+ ".semgrep/",
94
95
  "LICENSE",
95
96
  ".github/",
96
97
  ".prettierrc",
@@ -124,7 +125,7 @@
124
125
  },
125
126
  "picomatch": "^2.3.2",
126
127
  "basic-ftp": "^5.3.0",
127
- "tmp": "^0.2.5",
128
+ "tmp": "^0.2.7",
128
129
  "external-editor": "^3.1.0",
129
130
  "inquirer": "^13.4.1",
130
131
  "esbuild": "^0.25.0"
@@ -15,10 +15,12 @@ set -euo pipefail
15
15
  # 3. If CI passes, deploy to remaining repos
16
16
  # 4. If CI fails, abort rollout (prevents cascading failures)
17
17
  #
18
- # Auto-discovers repos by scanning ~/Projects for .github/workflows/quality.yml
19
- # files that contain the WORKFLOW_MODE marker from qa-architect.
18
+ # Auto-discovers repos by scanning ~/Projects, ~/Projects/internal, ~/Projects/products,
19
+ # and ~/Projects/personal for .github/workflows/quality.yml files containing the
20
+ # WORKFLOW_MODE marker from qa-architect.
20
21
 
21
- PROJECTS_DIR="$HOME/Projects"
22
+ # Scan these directories for consumer repos (space-separated)
23
+ PROJECTS_DIRS=("$HOME/Projects" "$HOME/Projects/internal" "$HOME/Projects/products" "$HOME/Projects/personal")
22
24
  QA_ARCHITECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
23
25
  CANARY_REPO="buildproven"
24
26
  PUSH=false
@@ -97,7 +99,9 @@ echo ""
97
99
  # Excludes qa-architect itself
98
100
  CONSUMERS=()
99
101
  CANARY_DIR=""
100
- for workflow in "$PROJECTS_DIR"/*/".github/workflows/quality.yml"; do
102
+ for projects_dir in "${PROJECTS_DIRS[@]}"; do
103
+ [ -d "$projects_dir" ] || continue
104
+ for workflow in "$projects_dir"/*/".github/workflows/quality.yml"; do
101
105
  [ -f "$workflow" ] || continue
102
106
  repo_dir="$(dirname "$(dirname "$(dirname "$workflow")")")"
103
107
  repo_name="$(basename "$repo_dir")"
@@ -113,6 +117,7 @@ for workflow in "$PROJECTS_DIR"/*/".github/workflows/quality.yml"; do
113
117
  CONSUMERS+=("$repo_dir")
114
118
  fi
115
119
  fi
120
+ done
116
121
  done
117
122
 
118
123
  # Ensure canary was found
@@ -160,7 +165,7 @@ wait_for_ci() {
160
165
 
161
166
  # Extract owner/repo from URL (handles both HTTPS and SSH)
162
167
  local repo_slug
163
- repo_slug=$(echo "$remote_url" | sed -E 's|^.*[:/]([^/]+/[^/]+)(\.git)?$|\1|')
168
+ repo_slug=$(echo "$remote_url" | sed -E 's|\.git$||' | sed -E 's|^.*[:/]([^/]+/[^/]+)$|\1|')
164
169
 
165
170
  # Get the current branch
166
171
  local branch