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.
- package/.semgrep/defensive-patterns.yaml +356 -0
- package/.semgrep/vibe-audit-rules.yaml +332 -0
- package/LICENSE +192 -39
- package/README.md +98 -46
- package/config/requirements-dev.txt +2 -2
- package/docs/POLAR-DEPLOYMENT.md +157 -0
- package/docs/plans/PLAN-vibe-code-auditor.md +130 -0
- package/docs/plans/POLAR-MIGRATION.md +111 -0
- package/docs/plans/pro-features-2026-05.md +159 -0
- package/lib/commands/analyze-ci.js +20 -11
- package/lib/commands/audit.js +668 -0
- package/lib/commands/ci-doctor.js +341 -0
- package/lib/commands/history-scan.js +342 -0
- package/lib/commands/index.js +8 -0
- package/lib/commands/pr-check.js +484 -0
- package/lib/commands/prelaunch-setup.js +4 -0
- package/lib/commands/ship-check.js +570 -0
- package/lib/license-validator.js +200 -6
- package/lib/licensing.js +99 -11
- package/package.json +7 -6
- package/scripts/deploy-consumers.sh +10 -5
- package/scripts/risk-policy-gate.js +410 -0
- package/setup.js +132 -4
- /package/docs/{STRIPE-LIVE-MODE-DEPLOYMENT.md → _archive/STRIPE-LIVE-MODE-DEPLOYMENT.md} +0 -0
- /package/lib/{billing-dashboard.html → _archive/billing-dashboard.html} +0 -0
package/lib/license-validator.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* License Validator (user-side)
|
|
3
3
|
*
|
|
4
|
-
* - No
|
|
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 =
|
|
94
|
-
|
|
95
|
-
|
|
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 (
|
|
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: $
|
|
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
|
|
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
|
|
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
|
-
*
|
|
307
|
-
*
|
|
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
|
-
//
|
|
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
|
|
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(' 💰 $
|
|
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,
|
|
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
|
|
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
|
|
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.
|
|
4
|
-
"description": "QA Architect -
|
|
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": "
|
|
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.
|
|
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
|
|
19
|
-
#
|
|
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
|
-
|
|
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
|
|
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|^.*[:/]([^/]+/[^/]+)
|
|
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
|