create-qa-architect 5.13.2 → 5.13.4

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.
@@ -22,8 +22,12 @@ jobs:
22
22
  node-version: '22'
23
23
  registry-url: 'https://registry.npmjs.org'
24
24
 
25
- - name: Upgrade npm for OIDC trusted publishing
26
- run: npm install -g npm@latest
25
+ # OIDC trusted publishing requires npm >= 11.5. We pin to 11.5.2 (the
26
+ # first OIDC-stable release) instead of `npm@latest` to avoid the
27
+ # MODULE_NOT_FOUND (promise-retry) regression hit when self-upgrading
28
+ # over the setup-node-provided npm on top of the newest 11.x.
29
+ - name: Pin npm for OIDC trusted publishing
30
+ run: npm install -g npm@11.5.2
27
31
 
28
32
  - name: Install dependencies
29
33
  run: npm ci --no-audit
package/LICENSE CHANGED
@@ -1,11 +1,11 @@
1
1
  VIBE BUILD LAB COMMERCIAL LICENSE
2
2
 
3
- Copyright (c) 2025 Vibe Build Lab LLC. All rights reserved.
3
+ Copyright (c) 2025 BuildProven. All rights reserved.
4
4
 
5
5
  COMMERCIAL SOFTWARE - FREEMIUM MODEL
6
6
 
7
7
  This software and associated documentation files (the "Software") are
8
- proprietary commercial products of Vibe Build Lab LLC.
8
+ proprietary commercial products of BuildProven.
9
9
 
10
10
  TERMS OF USE:
11
11
 
@@ -22,21 +22,9 @@ TERMS OF USE:
22
22
  - Smart Test Strategy
23
23
  - Multi-language support
24
24
  - Unlimited repos
25
- - Team: Contact us (coming soon)
26
- - All Pro features
27
- - RBAC and team policies
28
- - Slack alerts
29
- - Multi-repo dashboard
30
- - Enterprise: Contact us (coming soon)
31
- - All Team features
32
- - SSO/SAML integration
33
- - Custom policies
34
- - Compliance pack
35
- - Dedicated TAM
36
25
 
37
26
  3. VIBE LAB PRO BUNDLE
38
27
  Pro tier is included in the Vibe Lab Pro subscription.
39
- Team and Enterprise tiers are standalone purchases.
40
28
 
41
29
  4. PERMITTED USES
42
30
  - Use the free tier without restriction
@@ -62,5 +50,5 @@ For licensing inquiries: support@buildproven.ai
62
50
 
63
51
  ---
64
52
 
65
- Vibe Build Lab LLC (d/b/a BuildProven)
53
+ BuildProven
66
54
  https://buildproven.ai
package/README.md CHANGED
@@ -7,7 +7,7 @@ Quality automation CLI for JavaScript/TypeScript, Python, and shell script proje
7
7
  ---
8
8
 
9
9
  > **Maintainer & Ownership**
10
- > This project is maintained by **Vibe Build Lab LLC (d/b/a BuildProven)**, a studio focused on AI-assisted product development, micro-SaaS, and "vibe coding" workflows for solo founders and small teams.
10
+ > This project is maintained by **BuildProven**, a studio focused on AI-assisted product development, micro-SaaS, and "vibe coding" workflows for solo founders and small teams.
11
11
  > Learn more at **https://buildproven.ai**.
12
12
 
13
13
  ---
@@ -158,7 +158,7 @@ npx create-qa-architect@latest --workflow-minimal
158
158
 
159
159
  **Best for:** Small teams, client projects, production apps
160
160
 
161
- - Matrix testing (Node 20 + 22) **only on main branch**
161
+ - Single Node 22 testing **only on main branch**
162
162
  - Security scans run monthly
163
163
  - Path filters enabled
164
164
  - **Runtime:** ~15-20 min/commit
@@ -276,6 +276,8 @@ npm install
276
276
  npm run lint
277
277
  ```
278
278
 
279
+ `--update` refreshes the existing `quality.yml` from the latest template while preserving the detected workflow tier and existing matrix setting unless you explicitly override the tier with `--workflow-minimal`, `--workflow-standard`, or `--workflow-comprehensive`.
280
+
279
281
  ### Dependency Monitoring (Free)
280
282
 
281
283
  ```bash
@@ -453,4 +455,4 @@ Commercial freemium license — the base CLI is free to use; Pro features requir
453
455
 
454
456
  ---
455
457
 
456
- > **Vibe Build Lab LLC (d/b/a BuildProven)** · [buildproven.ai](https://buildproven.ai)
458
+ > **BuildProven** · [buildproven.ai](https://buildproven.ai)
@@ -1,5 +1,7 @@
1
1
  'use strict'
2
2
 
3
+ const { detectDepMajor } = require('../lib/project-module-type')
4
+
3
5
  const STYLELINT_EXTENSIONS = ['css', 'scss', 'sass', 'less', 'pcss']
4
6
  const DEFAULT_STYLELINT_TARGET = `**/*.{${STYLELINT_EXTENSIONS.join(',')}}`
5
7
 
@@ -19,7 +21,7 @@ const baseScripts = {
19
21
  'test:coverage': 'vitest run --coverage',
20
22
  'test:changed': 'vitest run --changed HEAD~1 --passWithNoTests',
21
23
  'security:audit':
22
- '[ -f pnpm-lock.yaml ] && pnpm audit --audit-level high || [ -f yarn.lock ] && yarn audit || npm audit --audit-level high',
24
+ 'if [ -f pnpm-lock.yaml ]; then pnpm audit --audit-level high; elif [ -f yarn.lock ]; then yarn audit; else npm audit --audit-level high; fi',
23
25
  'security:secrets':
24
26
  "node -e \"const fs=require('fs');const content=fs.readFileSync('package.json','utf8');if(/[\\\"\\'][a-zA-Z0-9+/]{20,}[\\\"\\']/.test(content)){console.error('❌ Potential hardcoded secrets in package.json');process.exit(1)}else{console.log('✅ No secrets detected in package.json')}\"",
25
27
  'security:config': 'npx create-qa-architect@latest --security-config',
@@ -28,8 +30,7 @@ const baseScripts = {
28
30
  'validate:docs': 'npx create-qa-architect@latest --validate-docs',
29
31
  'validate:comprehensive': 'npx create-qa-architect@latest --comprehensive',
30
32
  'validate:all': 'npm run validate:comprehensive && npm run security:audit',
31
- 'validate:pre-push':
32
- 'npm run test:patterns --if-present && npm run test:commands --if-present && npm run test:changed --if-present || npm test --if-present',
33
+ 'validate:pre-push': `npm run test:patterns --if-present && npm run test:commands --if-present && if node -e "const pkg=require('./package.json');process.exit(pkg.scripts&&pkg.scripts['test:changed']?0:1)" 2>/dev/null; then npm run test:changed; else npm test --if-present; fi`,
33
34
  }
34
35
 
35
36
  const normalizeStylelintTargets = stylelintTargets => {
@@ -124,13 +125,27 @@ function getDefaultScripts({ stylelintTargets } = {}) {
124
125
  }
125
126
 
126
127
  /**
127
- * @param {DefaultsOptions} [options]
128
+ * @param {DefaultsOptions & {projectPath?: string}} [options]
128
129
  */
129
- function getDefaultDevDependencies({ typescript } = {}) {
130
+ function getDefaultDevDependencies({ typescript, projectPath } = {}) {
130
131
  const devDeps = { ...clone(baseDevDependencies) }
131
132
  if (typescript) {
132
133
  Object.assign(devDeps, typeScriptDevDependencies)
133
134
  }
135
+
136
+ // Align @vitest/coverage-v8 with the target project's installed vitest
137
+ // major. vitest and its coverage adapter must share a major or npm install
138
+ // fails with ERESOLVE. Without this, a project already on vitest@4 gets
139
+ // our @vitest/coverage-v8@^2.x injected and breaks.
140
+ if (projectPath) {
141
+ const vitestMajor = detectDepMajor(projectPath, 'vitest')
142
+ if (vitestMajor && vitestMajor >= 3) {
143
+ devDeps['@vitest/coverage-v8'] = `^${vitestMajor}.0.0`
144
+ // Also drop our pinned vitest so we don't downgrade the project
145
+ delete devDeps.vitest
146
+ }
147
+ }
148
+
134
149
  return devDeps
135
150
  }
136
151
 
@@ -0,0 +1,208 @@
1
+ # Stripe Live Mode Deployment Guide
2
+
3
+ Deploy the `webhook-handler.js` server to process real Stripe payments and issue signed Pro licenses automatically.
4
+
5
+ ## Overview
6
+
7
+ The payment flow:
8
+ 1. Customer purchases Pro at `buildproven.ai/qa-architect`
9
+ 2. Stripe fires a `checkout.session.completed` webhook to your server
10
+ 3. `webhook-handler.js` validates the event, generates a signed license key, and saves it to Vercel Blob
11
+ 4. Customer runs `npx create-qa-architect@latest --activate-license` and enters their key
12
+
13
+ ---
14
+
15
+ ## Prerequisites
16
+
17
+ - A [Stripe](https://stripe.com) account with live mode enabled
18
+ - A [Vercel](https://vercel.com) account (for Blob storage and hosting)
19
+ - Node.js >=20
20
+ - The ED25519 private key used to sign licenses (in `public-key.pem` + your private key)
21
+
22
+ ---
23
+
24
+ ## Step 1: Create Stripe Products and Prices
25
+
26
+ In the [Stripe Dashboard](https://dashboard.stripe.com/products) → Products → Add product:
27
+
28
+ | Product | Price | Billing | Price ID (example) |
29
+ |---|---|---|---|
30
+ | QA Architect Pro | $49.00 | Monthly | `price_1St9K2Gv7Su9XNJbdYoH3K32` |
31
+ | QA Architect Pro | $490.00 | Yearly | `price_1St9KGGv7Su9XNJbrwKMsh1R` |
32
+
33
+ The price IDs above are already mapped in `webhook-handler.js:315-318`. If your actual Stripe price IDs differ, update `mapPriceToTier()` in `webhook-handler.js`.
34
+
35
+ ---
36
+
37
+ ## Step 2: Set Up Vercel Blob Storage
38
+
39
+ ```bash
40
+ # Install Vercel CLI
41
+ npm install -g vercel
42
+
43
+ # Link your project
44
+ vercel link
45
+
46
+ # Create a Blob store in the Vercel dashboard
47
+ # Dashboard → Storage → Create → Blob → name it "qa-architect-licenses"
48
+ # Copy the BLOB_READ_WRITE_TOKEN
49
+ ```
50
+
51
+ ---
52
+
53
+ ## Step 3: Deploy to Vercel
54
+
55
+ `webhook-handler.js` exports an Express app compatible with Vercel serverless.
56
+
57
+ Create `vercel.json` in the project root (do not commit secrets):
58
+
59
+ ```json
60
+ {
61
+ "version": 2,
62
+ "builds": [{ "src": "webhook-handler.js", "use": "@vercel/node" }],
63
+ "routes": [{ "src": "/(.*)", "dest": "/webhook-handler.js" }]
64
+ }
65
+ ```
66
+
67
+ Deploy:
68
+
69
+ ```bash
70
+ vercel --prod
71
+ ```
72
+
73
+ Your webhook URL will be: `https://your-project.vercel.app/webhook`
74
+
75
+ ---
76
+
77
+ ## Step 4: Set Environment Variables
78
+
79
+ In the Vercel dashboard → Project → Settings → Environment Variables, add:
80
+
81
+ | Variable | Value | Notes |
82
+ |---|---|---|
83
+ | `STRIPE_SECRET_KEY` | `sk_live_...` | From Stripe Dashboard → Developers → API keys |
84
+ | `STRIPE_WEBHOOK_SECRET` | `whsec_...` | Generated in Step 5 below |
85
+ | `LICENSE_REGISTRY_PRIVATE_KEY` | Base64-encoded ED25519 private key | See note below |
86
+ | `LICENSE_REGISTRY_KEY_ID` | `default` | Or a versioned ID like `v1` |
87
+ | `BLOB_READ_WRITE_TOKEN` | `vercel_blob_...` | From Vercel Blob store settings |
88
+ | `STATUS_API_TOKEN` | A strong random string | Protects the `/status` endpoint |
89
+ | `NODE_ENV` | `production` | Enables HSTS and production error handling |
90
+
91
+ **Private key format:** The key must be a PEM-encoded ED25519 private key, base64-encoded as a single line (no newlines):
92
+
93
+ ```bash
94
+ # Encode your private key for the env var
95
+ base64 -w 0 < your-private-key.pem
96
+ ```
97
+
98
+ The `loadKeyFromEnv()` function in `lib/license-signing.js` decodes it automatically.
99
+
100
+ ---
101
+
102
+ ## Step 5: Register the Stripe Webhook
103
+
104
+ In the [Stripe Dashboard](https://dashboard.stripe.com/webhooks) → Webhooks → Add endpoint:
105
+
106
+ - **URL:** `https://your-project.vercel.app/webhook`
107
+ - **Events to listen for:**
108
+ - `checkout.session.completed`
109
+ - `invoice.payment_succeeded`
110
+ - `customer.subscription.deleted`
111
+
112
+ Copy the **Signing secret** (`whsec_...`) and add it as `STRIPE_WEBHOOK_SECRET` in Vercel.
113
+
114
+ ---
115
+
116
+ ## Step 6: Verify the Deployment
117
+
118
+ ```bash
119
+ # Health check
120
+ curl https://your-project.vercel.app/health
121
+
122
+ # Expected response:
123
+ # {"status":"ok","timestamp":"...","database":"missing"}
124
+ # (missing is fine before any licenses are issued)
125
+
126
+ # Test license database endpoint (used by CLI)
127
+ curl https://your-project.vercel.app/legitimate-licenses.json
128
+
129
+ # Test status endpoint (replace TOKEN with your STATUS_API_TOKEN)
130
+ curl -H "Authorization: Bearer TOKEN" https://your-project.vercel.app/status
131
+ ```
132
+
133
+ ---
134
+
135
+ ## Step 7: Test with Stripe Test Mode First
136
+
137
+ Before going live, test the full flow:
138
+
139
+ ```bash
140
+ # Install Stripe CLI
141
+ brew install stripe/stripe-cli/stripe
142
+
143
+ # Forward webhooks to your local server
144
+ stripe listen --forward-to localhost:3000/webhook
145
+
146
+ # Trigger a test checkout event
147
+ stripe trigger checkout.session.completed
148
+
149
+ # Verify a license was created
150
+ curl http://localhost:3000/legitimate-licenses.json
151
+ ```
152
+
153
+ Switch to live mode keys once the test flow works end-to-end.
154
+
155
+ ---
156
+
157
+ ## Step 8: Connect Your Checkout Page
158
+
159
+ Your Stripe Checkout session must:
160
+ - Use one of the two price IDs mapped in `mapPriceToTier()`
161
+ - Be a **subscription** mode checkout (not one-time payment)
162
+ - Collect a customer email
163
+
164
+ Example Stripe Checkout session creation (server-side):
165
+
166
+ ```javascript
167
+ const session = await stripe.checkout.sessions.create({
168
+ mode: 'subscription',
169
+ payment_method_types: ['card'],
170
+ line_items: [{ price: 'price_1St9K2Gv7Su9XNJbdYoH3K32', quantity: 1 }],
171
+ success_url: 'https://buildproven.ai/qa-architect/success?session_id={CHECKOUT_SESSION_ID}',
172
+ cancel_url: 'https://buildproven.ai/qa-architect',
173
+ })
174
+ ```
175
+
176
+ ---
177
+
178
+ ## License Key Delivery
179
+
180
+ After a successful checkout, the license key is stored in Vercel Blob. The customer retrieves it by running:
181
+
182
+ ```bash
183
+ npx create-qa-architect@latest --activate-license
184
+ ```
185
+
186
+ They enter their email and the license key format `QAA-XXXX-XXXX-XXXX-XXXX`.
187
+
188
+ The key is deterministic from the Stripe customer ID — you can regenerate it at any time using `admin-license.js` or `scripts/generate-license-keys.js`.
189
+
190
+ > **Note:** The current flow requires customers to manually enter their key. Consider adding an email delivery step in `handleCheckoutCompleted()` in `webhook-handler.js` (the comment at line 511 marks where to add this).
191
+
192
+ ---
193
+
194
+ ## Cancellation Handling
195
+
196
+ When a subscription is canceled in Stripe, the `customer.subscription.deleted` event fires and `handleSubscriptionCanceled()` marks the license as `status: "canceled"` in the database. The CLI checks this status during `getLicenseInfo()` and downgrades the user to FREE tier automatically.
197
+
198
+ ---
199
+
200
+ ## Troubleshooting
201
+
202
+ | Symptom | Likely cause | Fix |
203
+ |---|---|---|
204
+ | Webhook returns 400 | Wrong `STRIPE_WEBHOOK_SECRET` | Re-copy the signing secret from Stripe Dashboard |
205
+ | License not created after payment | Unknown price ID | Update `mapPriceToTier()` with your actual Stripe price IDs |
206
+ | CLI can't find license | Wrong blob URL | Check `BLOB_PATHS` in `lib/blob-storage.js` matches your Vercel Blob store |
207
+ | `sk_test_` warning in logs | Test key in production | Replace with `sk_live_...` key |
208
+ | `/status` returns 503 | `STATUS_API_TOKEN` not set | Add the env var in Vercel settings |
@@ -149,7 +149,7 @@ Turborepo caches and parallelizes tasks based on `turbo.json`:
149
149
 
150
150
  ### Issue: "package.json not found" in subdirectory
151
151
 
152
- This is expected in monorepos. qa-architect gracefully handles missing package.json in workspace subdirectories (see `docs/MONOREPO-COMPATIBILITY-FIX.md`).
152
+ This is expected in monorepos. qa-architect gracefully handles missing package.json in workspace subdirectories.
153
153
 
154
154
  ## Testing
155
155
 
@@ -165,7 +165,6 @@ npx create-qa-architect@latest --dry-run
165
165
 
166
166
  ## Related Documentation
167
167
 
168
- - [Monorepo Compatibility Fix](./MONOREPO-COMPATIBILITY-FIX.md) - Handling workspaces
169
168
  - [CI Cost Analysis](./CI-COST-ANALYSIS.md) - Workflow tier pricing
170
169
  - [pnpm CI Example](../.github/workflows/pnpm-ci.yml.example) - Complete example
171
170
 
@@ -11,7 +11,7 @@ QA Architect (`create-qa-architect`) is a CLI tool that bootstraps quality autom
11
11
 
12
12
  **Entry point:** `setup.js` — CLI argument parsing and orchestration. Run as `npx create-qa-architect` or `node setup.js`.
13
13
 
14
- **npm package:** `create-qa-architect` v5.13.2 (published via GitHub trusted publishing)
14
+ **npm package:** `create-qa-architect` v5.13.4 (published via GitHub trusted publishing)
15
15
 
16
16
  ## Directory Structure
17
17
 
@@ -442,15 +442,9 @@
442
442
  )
443
443
  } finally {
444
444
  checkoutBtn.disabled = false
445
- if (selectedTier === 'pro') {
446
- checkoutBtn.textContent = isFounder
447
- ? 'Start Pro Subscription - $24.50/month (Founder Price)'
448
- : 'Start Pro Subscription - $49/month'
449
- } else {
450
- checkoutBtn.textContent = isFounder
451
- ? 'Start Enterprise Subscription - $74.50/month (Founder Price)'
452
- : 'Start Enterprise Subscription - $149/month'
453
- }
445
+ checkoutBtn.textContent = isFounder
446
+ ? 'Start Pro Subscription - $24.50/month (Founder Price)'
447
+ : 'Start Pro Subscription - $49/month'
454
448
  }
455
449
  }
456
450
 
@@ -12,6 +12,7 @@ const path = require('path')
12
12
  const { execSync } = require('child_process')
13
13
  const yaml = require('js-yaml')
14
14
  const { showProgress } = require('../ui-helpers')
15
+ const { hasFeature, showUpgradeMessage } = require('../licensing')
15
16
 
16
17
  const DAYS_PER_MONTH = 30
17
18
  const DEFAULT_PULL_REQUEST_FACTOR = 0.8
@@ -383,17 +384,20 @@ function calculateMonthlyCosts(workflows, commitsPerDay, options = {}) {
383
384
  const workflowRunsPerDay = totalWorkflowRunsPerMonth / DAYS_PER_MONTH
384
385
 
385
386
  // GitHub Actions pricing (as of 2024)
386
- const FREE_TIER_MINUTES = 2000 // Free tier monthly limit
387
- const TEAM_TIER_MINUTES = 3000 // Team tier monthly limit
387
+ const FREE_TIER_MINUTES = 2000 // GitHub Free plan monthly limit
388
+ const GITHUB_TEAM_PLAN_MINUTES = 3000 // GitHub Team plan monthly limit ($4/user/month)
388
389
  const COST_PER_MINUTE = 0.008 // $0.008/min for private repos
389
390
  const TARGET_BUDGET_MINUTES = 1000
390
391
  const STRETCH_BUDGET_MINUTES = 1500
391
392
 
392
393
  const freeOverage = Math.max(0, minutesPerMonth - FREE_TIER_MINUTES)
393
- const teamOverage = Math.max(0, minutesPerMonth - TEAM_TIER_MINUTES)
394
+ const githubTeamOverage = Math.max(
395
+ 0,
396
+ minutesPerMonth - GITHUB_TEAM_PLAN_MINUTES
397
+ )
394
398
 
395
399
  const freeOverageCost = freeOverage * COST_PER_MINUTE
396
- const teamOverageCost = teamOverage * COST_PER_MINUTE
400
+ const githubTeamOverageCost = githubTeamOverage * COST_PER_MINUTE
397
401
 
398
402
  return {
399
403
  minutesPerMonth,
@@ -419,11 +423,11 @@ function calculateMonthlyCosts(workflows, commitsPerDay, options = {}) {
419
423
  withinLimit: minutesPerMonth <= FREE_TIER_MINUTES,
420
424
  },
421
425
  team: {
422
- limit: TEAM_TIER_MINUTES,
423
- overage: teamOverage,
424
- cost: teamOverageCost,
425
- withinLimit: minutesPerMonth <= TEAM_TIER_MINUTES,
426
- monthlyCost: 4, // $4/user/month
426
+ limit: GITHUB_TEAM_PLAN_MINUTES,
427
+ overage: githubTeamOverage,
428
+ cost: githubTeamOverageCost,
429
+ withinLimit: minutesPerMonth <= GITHUB_TEAM_PLAN_MINUTES,
430
+ monthlyCost: 4, // $4/user/month (GitHub Team plan price)
427
431
  },
428
432
  },
429
433
  }
@@ -672,10 +676,10 @@ function generateReport(analysis) {
672
676
  console.log('')
673
677
  console.log('Alternative options:')
674
678
 
675
- // Team tier comparison
679
+ // GitHub Team plan comparison (GitHub's billing tier, not QA Architect tier)
676
680
  if (costs.tiers.team.withinLimit) {
677
681
  console.log(
678
- ` Team plan ($${costs.tiers.team.monthlyCost}/user/month): ✅ Stays within ${costs.tiers.team.limit.toLocaleString()} min limit`
682
+ ` GitHub Team plan ($${costs.tiers.team.monthlyCost}/user/month): ✅ Stays within ${costs.tiers.team.limit.toLocaleString()} min limit`
679
683
  )
680
684
  const savings = costs.tiers.free.cost - costs.tiers.team.monthlyCost
681
685
  if (savings > 0) {
@@ -683,7 +687,7 @@ function generateReport(analysis) {
683
687
  }
684
688
  } else {
685
689
  console.log(
686
- ` Team plan ($${costs.tiers.team.monthlyCost}/user/month): Still exceeds (${costs.tiers.team.overage.toLocaleString()} min overage)`
690
+ ` GitHub Team plan ($${costs.tiers.team.monthlyCost}/user/month): Still exceeds (${costs.tiers.team.overage.toLocaleString()} min overage)`
687
691
  )
688
692
  console.log(
689
693
  ` Total cost: $${(costs.tiers.team.monthlyCost + costs.tiers.team.cost).toFixed(2)}/month`
@@ -772,13 +776,10 @@ function generateReport(analysis) {
772
776
  async function handleAnalyzeCi() {
773
777
  const projectPath = process.cwd()
774
778
 
775
- // Check if Pro feature (FREE tier for now during development)
776
- // TODO: Enable Pro gating after testing
777
- // const license = getLicenseInfo()
778
- // if (!hasFeature('ciCostAnalysis')) {
779
- // showUpgradeMessage('GitHub Actions cost analysis')
780
- // process.exit(1)
781
- // }
779
+ if (!hasFeature('ciCostAnalysis')) {
780
+ showUpgradeMessage('GitHub Actions cost analysis')
781
+ process.exit(1)
782
+ }
782
783
 
783
784
  const spinner = showProgress('Analyzing GitHub Actions workflows...')
784
785
 
@@ -60,7 +60,7 @@ function detectRubyProject(projectPath) {
60
60
  }
61
61
 
62
62
  /**
63
- * Handle dependency monitoring command (Free/Pro/Team/Enterprise)
63
+ * Handle dependency monitoring command (Free/Pro)
64
64
  */
65
65
  async function handleDependencyMonitoring() {
66
66
  const projectPath = process.cwd()
@@ -95,7 +95,7 @@ async function handleDependencyMonitoring() {
95
95
  if (!capCheck.allowed) {
96
96
  console.error(`❌ ${capCheck.reason}`)
97
97
  console.error(
98
- ' Upgrade to Pro, Team, or Enterprise for unlimited runs: https://buildproven.ai/qa-architect'
98
+ ' Upgrade to Pro for unlimited runs: https://buildproven.ai/qa-architect'
99
99
  )
100
100
  process.exit(1)
101
101
  }
@@ -108,7 +108,7 @@ async function handleDependencyMonitoring() {
108
108
  // Free tier only supports npm projects. Fail fast with a clear message.
109
109
  if (!shouldUsePremium && !hasNpm && (hasPython || hasRust || hasRuby)) {
110
110
  console.error(
111
- '❌ Dependency monitoring for this project requires a Pro, Team, or Enterprise license.'
111
+ '❌ Dependency monitoring for this project requires a Pro license.'
112
112
  )
113
113
  console.error(
114
114
  ' Free tier supports npm projects only. Detected non-npm ecosystems.'
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Premium Dependency Monitoring Library (Pro/Team/Enterprise Tiers)
2
+ * Premium Dependency Monitoring Library (Pro Tier)
3
3
  * Framework-aware dependency grouping with intelligent batching
4
4
  *
5
5
  * @module dependency-monitoring-premium
@@ -691,4 +691,4 @@ class LicenseValidator {
691
691
  }
692
692
  }
693
693
 
694
- module.exports = { LicenseValidator }
694
+ module.exports = { LicenseValidator, validateLicenseDir }
package/lib/licensing.js CHANGED
@@ -16,15 +16,16 @@ const {
16
16
  verifyPayload,
17
17
  loadKeyFromEnv,
18
18
  } = require('./license-signing')
19
+ const { validateLicenseDir } = require('./license-validator')
19
20
 
20
21
  // License storage locations
21
22
  // Support environment variable override for testing (like telemetry/error-reporter)
22
23
  // Use getter functions to allow env override before module load
23
24
  function getLicenseDir() {
24
- return (
25
+ const requestedDir =
25
26
  process.env.QAA_LICENSE_DIR ||
26
27
  path.join(os.homedir(), '.create-qa-architect')
27
- )
28
+ return validateLicenseDir(requestedDir)
28
29
  }
29
30
 
30
31
  function getLicenseFile() {
@@ -586,10 +587,7 @@ async function addLegitimateKey(
586
587
  }
587
588
 
588
589
  const normalizedKey = normalizeLicenseKey(licenseKey)
589
- // Use the same license directory as the CLI
590
- const licenseDir =
591
- process.env.QAA_LICENSE_DIR ||
592
- path.join(os.homedir(), '.create-qa-architect')
590
+ const licenseDir = getLicenseDir()
593
591
  const legitimateDBFile = path.join(licenseDir, 'legitimate-licenses.json')
594
592
  const privateKey = loadKeyFromEnv(
595
593
  process.env.LICENSE_REGISTRY_PRIVATE_KEY,
@@ -949,9 +947,9 @@ function saveUsage(usage) {
949
947
 
950
948
  throw error // Don't allow FREE tier to continue without tracking
951
949
  } else {
952
- // Pro/Team/Enterprise - warn but don't fail
950
+ // Pro - warn but don't fail
953
951
  console.warn(`⚠️ Failed to save usage data: ${error.message}`)
954
- console.warn(` This won't affect Pro/Team/Enterprise functionality`)
952
+ console.warn(` This won't affect Pro functionality`)
955
953
  return false
956
954
  }
957
955
  }
@@ -0,0 +1,66 @@
1
+ 'use strict'
2
+
3
+ const fs = require('fs')
4
+ const path = require('path')
5
+
6
+ /**
7
+ * Detect the target project's module system.
8
+ *
9
+ * Returns 'esm' if package.json has `"type": "module"`, otherwise 'cjs'.
10
+ * Missing or unreadable package.json falls back to 'cjs' (Node's default).
11
+ *
12
+ * @param {string} projectPath - Path to project root
13
+ * @returns {'esm'|'cjs'}
14
+ */
15
+ function detectModuleType(projectPath) {
16
+ const packageJsonPath = path.join(projectPath, 'package.json')
17
+ if (!fs.existsSync(packageJsonPath)) {
18
+ return 'cjs'
19
+ }
20
+ try {
21
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
22
+ return pkg.type === 'module' ? 'esm' : 'cjs'
23
+ } catch {
24
+ return 'cjs'
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Convenience: true when the target project is ESM.
30
+ * @param {string} projectPath
31
+ * @returns {boolean}
32
+ */
33
+ function isESMProject(projectPath) {
34
+ return detectModuleType(projectPath) === 'esm'
35
+ }
36
+
37
+ /**
38
+ * Detect the major version of an installed dep in the target project's
39
+ * package.json (checks dependencies + devDependencies). Returns null if
40
+ * not present or unparseable.
41
+ *
42
+ * @param {string} projectPath
43
+ * @param {string} depName
44
+ * @returns {number|null}
45
+ */
46
+ function detectDepMajor(projectPath, depName) {
47
+ const packageJsonPath = path.join(projectPath, 'package.json')
48
+ if (!fs.existsSync(packageJsonPath)) return null
49
+ try {
50
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
51
+ const range =
52
+ (pkg.dependencies && pkg.dependencies[depName]) ||
53
+ (pkg.devDependencies && pkg.devDependencies[depName])
54
+ if (!range) return null
55
+ const match = String(range).match(/(\d+)\./)
56
+ return match ? parseInt(match[1], 10) : null
57
+ } catch {
58
+ return null
59
+ }
60
+ }
61
+
62
+ module.exports = {
63
+ detectModuleType,
64
+ isESMProject,
65
+ detectDepMajor,
66
+ }
@@ -10,6 +10,7 @@
10
10
 
11
11
  const fs = require('fs')
12
12
  const path = require('path')
13
+ const { isESMProject } = require('./project-module-type')
13
14
 
14
15
  /**
15
16
  * Generate Lighthouse CI configuration
@@ -435,11 +436,17 @@ function writeSizeLimitConfig(projectPath, options = {}) {
435
436
  }
436
437
 
437
438
  /**
438
- * Write commitlint config to project
439
+ * Write commitlint config to project.
440
+ * ESM projects ("type": "module" in package.json) need `.cjs` so Node
441
+ * loads our CommonJS `module.exports` template without throwing
442
+ * "module is not defined in ES module scope".
439
443
  * @param {string} projectPath - Path to project
440
444
  */
441
445
  function writeCommitlintConfig(projectPath) {
442
- const configPath = path.join(projectPath, 'commitlint.config.js')
446
+ const filename = isESMProject(projectPath)
447
+ ? 'commitlint.config.cjs'
448
+ : 'commitlint.config.js'
449
+ const configPath = path.join(projectPath, filename)
443
450
  const config = generateCommitlintConfig()
444
451
 
445
452
  try {
@@ -65,6 +65,37 @@ function detectExistingWorkflowMode(projectPath) {
65
65
  }
66
66
  }
67
67
 
68
+ /**
69
+ * Detect whether matrix testing is enabled in an existing workflow.
70
+ * @param {string} projectPath - Path to project
71
+ * @returns {boolean} True when matrix testing is enabled
72
+ */
73
+ function detectExistingMatrix(projectPath) {
74
+ const workflowPath = path.join(
75
+ projectPath,
76
+ '.github',
77
+ 'workflows',
78
+ 'quality.yml'
79
+ )
80
+
81
+ if (!fs.existsSync(workflowPath)) {
82
+ return false
83
+ }
84
+
85
+ try {
86
+ const content = fs.readFileSync(workflowPath, 'utf8')
87
+ return (
88
+ content.includes('# MATRIX_ENABLED: true') ||
89
+ content.includes('node-version: [20, 22]')
90
+ )
91
+ } catch (error) {
92
+ console.warn(
93
+ `⚠️ Could not detect existing matrix configuration: ${error.message}`
94
+ )
95
+ return false
96
+ }
97
+ }
98
+
68
99
  /**
69
100
  * Strip a named section from workflow content.
70
101
  * Removes everything between # {{NAME_BEGIN}} and # {{NAME_END}} markers (inclusive).
@@ -135,6 +166,39 @@ function removeTriggerPathsIgnore(content, triggerName) {
135
166
  return output.join('\n')
136
167
  }
137
168
 
169
+ /**
170
+ * Restrict the tests job to main branch pushes in standard mode.
171
+ * @param {string} content - Workflow content
172
+ * @returns {string} Updated content
173
+ */
174
+ function addStandardTestsBranchGate(content) {
175
+ const lines = content.split('\n')
176
+ const output = []
177
+ let inTestsJob = false
178
+ let branchGateInserted = false
179
+
180
+ for (const line of lines) {
181
+ if (line === ' tests:') {
182
+ inTestsJob = true
183
+ } else if (
184
+ inTestsJob &&
185
+ line.startsWith(' ') &&
186
+ !line.startsWith(' ')
187
+ ) {
188
+ inTestsJob = false
189
+ }
190
+
191
+ output.push(line)
192
+
193
+ if (inTestsJob && line === ' if: |' && !branchGateInserted) {
194
+ output.push(" github.ref == 'refs/heads/main' &&")
195
+ branchGateInserted = true
196
+ }
197
+ }
198
+
199
+ return output.join('\n')
200
+ }
201
+
138
202
  /**
139
203
  * Inject workflow mode-specific configuration into quality.yml
140
204
  * Uses section markers (# {{SECTION_BEGIN/END}}) for reliable content removal
@@ -162,19 +226,13 @@ function injectWorkflowMode(workflowContent, mode) {
162
226
 
163
227
  // Mode-specific transformations
164
228
  if (mode === 'standard') {
165
- // Standard: Add main branch condition to tests job
229
+ // Standard: run tests on main only to keep CI costs bounded.
166
230
  if (
167
- updated.includes('tests:') &&
168
- updated.includes('fromJSON(needs.detect-maturity.outputs.test-count)')
231
+ updated.includes(' tests:') &&
232
+ updated.includes(' if: |') &&
233
+ !updated.includes("github.ref == 'refs/heads/main'")
169
234
  ) {
170
- updated = updated.replace(
171
- /(tests:\s+runs-on:[^\n]+\s+needs:[^\n]+\s+if: \|\s*\n\s+)fromJSON\(needs\.detect-maturity\.outputs\.test-count\)/,
172
- "$1github.ref == 'refs/heads/main' &&\n fromJSON(needs.detect-maturity.outputs.test-count)"
173
- )
174
- updated = updated.replace(
175
- /(\s+tests:\s+runs-on:[^\n]+\s+needs:[^\n]+\s+)if: fromJSON\(needs\.detect-maturity\.outputs\.test-count\) > 0/,
176
- "$1if: github.ref == 'refs/heads/main' && fromJSON(needs.detect-maturity.outputs.test-count) > 0"
177
- )
235
+ updated = addStandardTestsBranchGate(updated)
178
236
  }
179
237
  } else if (mode === 'comprehensive') {
180
238
  // Comprehensive: Remove paths-ignore blocks
@@ -284,6 +342,7 @@ function injectMatrix(workflowContent, enableMatrix) {
284
342
 
285
343
  module.exports = {
286
344
  detectExistingWorkflowMode,
345
+ detectExistingMatrix,
287
346
  injectWorkflowMode,
288
347
  injectMatrix,
289
348
  stripSection,
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "create-qa-architect",
3
- "version": "5.13.2",
3
+ "version": "5.13.4",
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": {
7
- "create-qa-architect": "./setup.js"
7
+ "create-qa-architect": "setup.js"
8
8
  },
9
9
  "scripts": {
10
10
  "prepare": "[ \"$CI\" = \"true\" ] && echo 'Skipping Husky in CI' || husky",
@@ -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",
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",
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",
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",
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",
@@ -80,7 +80,7 @@
80
80
  "dependency-monitoring",
81
81
  "security-audit"
82
82
  ],
83
- "author": "Brett Stark",
83
+ "author": "BuildProven",
84
84
  "license": "SEE LICENSE IN LICENSE",
85
85
  "files": [
86
86
  "setup.js",
@@ -121,7 +121,13 @@
121
121
  "table": {
122
122
  "ajv": "$ajv"
123
123
  }
124
- }
124
+ },
125
+ "picomatch": "^2.3.2",
126
+ "basic-ftp": "^5.3.0",
127
+ "tmp": "^0.2.5",
128
+ "external-editor": "^3.1.0",
129
+ "inquirer": "^13.4.1",
130
+ "esbuild": "^0.25.0"
125
131
  },
126
132
  "devDependencies": {
127
133
  "@commitlint/cli": "^19.0.0",
package/setup.js CHANGED
@@ -125,6 +125,7 @@ const { runInteractiveFlow } = require('./lib/interactive/questions')
125
125
  const { TemplateLoader } = require('./lib/template-loader')
126
126
  const {
127
127
  detectExistingWorkflowMode,
128
+ detectExistingMatrix,
128
129
  injectWorkflowMode,
129
130
  injectMatrix,
130
131
  } = require('./lib/workflow-config')
@@ -693,7 +694,7 @@ Usage: npx create-qa-architect@latest [options]
693
694
  SETUP OPTIONS:
694
695
  (no args) Run complete quality automation setup
695
696
  --interactive Interactive mode with guided configuration prompts
696
- --update Update existing configuration
697
+ --update Update existing configuration (refreshes quality.yml, preserves detected workflow tier)
697
698
  --deps Add basic dependency monitoring (Free Tier)
698
699
  --dependency-monitoring Same as --deps
699
700
  --ci <provider> Select CI provider: github (default) | gitlab | circleci
@@ -703,7 +704,7 @@ SETUP OPTIONS:
703
704
  WORKFLOW TIERS (GitHub Actions optimization):
704
705
  --workflow-minimal Minimal CI (default) - Single Node version, monthly security
705
706
  Budget-first mode: detection-only CI (target <1000 min/month)
706
- --workflow-standard Standard CI - Matrix testing on main, monthly security
707
+ --workflow-standard Standard CI - Main-branch tests, monthly security
707
708
  ~15-20 min/commit, ~$5-20/mo for typical projects
708
709
  --workflow-comprehensive Comprehensive CI - Matrix on every push, security inline
709
710
  ~50-100 min/commit, ~$100-350/mo for typical projects
@@ -722,7 +723,7 @@ VALIDATION OPTIONS:
722
723
 
723
724
  LICENSE, TELEMETRY & ERROR REPORTING:
724
725
  --license-status Show current license tier and available features
725
- --activate-license Activate Pro/Team/Enterprise license key from Stripe purchase
726
+ --activate-license Activate Pro license key from Stripe purchase
726
727
  --telemetry-status Show telemetry status and opt-in instructions
727
728
  --error-reporting-status Show error reporting status and privacy information
728
729
 
@@ -749,7 +750,7 @@ EXAMPLES:
749
750
  → Show current license tier and upgrade options
750
751
 
751
752
  npx create-qa-architect@latest --activate-license
752
- → Activate Pro/Team/Enterprise license after Stripe purchase
753
+ → Activate Pro license after Stripe purchase
753
754
 
754
755
  npx create-qa-architect@latest --telemetry-status
755
756
  → Show telemetry status and privacy information
@@ -1397,6 +1398,7 @@ HELP:
1397
1398
  console.log('📦 Adding devDependencies...')
1398
1399
  const defaultDevDependencies = getDefaultDevDependencies({
1399
1400
  typescript: usesTypeScript,
1401
+ projectPath: process.cwd(),
1400
1402
  })
1401
1403
  packageJson.devDependencies = mergeDevDependencies(
1402
1404
  packageJson.devDependencies || {},
@@ -1651,6 +1653,16 @@ HELP:
1651
1653
  }
1652
1654
  }
1653
1655
 
1656
+ const hasExplicitWorkflowMode =
1657
+ isWorkflowMinimal || isWorkflowStandard || isWorkflowComprehensive
1658
+ const existingMatrixEnabled =
1659
+ fs.existsSync(workflowFile) &&
1660
+ !isMatrixEnabled &&
1661
+ !hasExplicitWorkflowMode
1662
+ ? detectExistingMatrix(process.cwd())
1663
+ : false
1664
+ const matrixEnabled = isMatrixEnabled || existingMatrixEnabled
1665
+
1654
1666
  if (!fs.existsSync(workflowFile)) {
1655
1667
  let templateWorkflow =
1656
1668
  templateLoader.getTemplate(
@@ -1666,7 +1678,7 @@ HELP:
1666
1678
  templateWorkflow = injectWorkflowMode(templateWorkflow, workflowMode)
1667
1679
 
1668
1680
  // Inject matrix testing if enabled (for library authors)
1669
- templateWorkflow = injectMatrix(templateWorkflow, isMatrixEnabled)
1681
+ templateWorkflow = injectMatrix(templateWorkflow, matrixEnabled)
1670
1682
 
1671
1683
  // Inject collaboration steps
1672
1684
  templateWorkflow = injectCollaborationSteps(templateWorkflow, {
@@ -1677,58 +1689,47 @@ HELP:
1677
1689
  fs.writeFileSync(workflowFile, templateWorkflow)
1678
1690
  console.log(`✅ Added GitHub Actions workflow (${workflowMode} mode)`)
1679
1691
  } else if (isUpdateMode) {
1680
- // Update existing workflow with new mode if explicitly specified
1681
- if (
1682
- isWorkflowMinimal ||
1683
- isWorkflowStandard ||
1684
- isWorkflowComprehensive
1685
- ) {
1686
- // Load fresh template and re-inject
1687
- let templateWorkflow =
1688
- templateLoader.getTemplate(
1689
- templates,
1690
- path.join('.github', 'workflows', 'quality.yml')
1691
- ) ||
1692
- fs.readFileSync(
1693
- path.join(__dirname, '.github/workflows/quality.yml'),
1694
- 'utf8'
1695
- )
1696
-
1697
- // Inject workflow mode configuration
1698
- templateWorkflow = injectWorkflowMode(
1699
- templateWorkflow,
1700
- workflowMode
1692
+ // Refresh existing workflow from the current template.
1693
+ let templateWorkflow =
1694
+ templateLoader.getTemplate(
1695
+ templates,
1696
+ path.join('.github', 'workflows', 'quality.yml')
1697
+ ) ||
1698
+ fs.readFileSync(
1699
+ path.join(__dirname, '.github/workflows/quality.yml'),
1700
+ 'utf8'
1701
1701
  )
1702
1702
 
1703
- // Inject matrix testing if enabled (for library authors)
1704
- templateWorkflow = injectMatrix(templateWorkflow, isMatrixEnabled)
1703
+ // Inject workflow mode configuration
1704
+ templateWorkflow = injectWorkflowMode(templateWorkflow, workflowMode)
1705
1705
 
1706
- // Inject collaboration steps (preserve from existing if present)
1707
- const existingWorkflow = fs.readFileSync(workflowFile, 'utf8')
1708
- const hasSlackAlerts =
1709
- existingWorkflow.includes('SLACK_WEBHOOK_URL')
1710
- const hasPrComments = existingWorkflow.includes(
1711
- 'PR_COMMENT_PLACEHOLDER'
1712
- )
1706
+ // Inject matrix testing if enabled (for library authors)
1707
+ templateWorkflow = injectMatrix(templateWorkflow, matrixEnabled)
1713
1708
 
1714
- templateWorkflow = injectCollaborationSteps(templateWorkflow, {
1715
- enableSlackAlerts: hasSlackAlerts,
1716
- enablePrComments: hasPrComments,
1717
- })
1709
+ // Inject collaboration steps (preserve from existing if present)
1710
+ const existingWorkflow = fs.readFileSync(workflowFile, 'utf8')
1711
+ const hasSlackAlerts = existingWorkflow.includes('SLACK_WEBHOOK_URL')
1712
+ const hasPrComments = existingWorkflow.includes(
1713
+ 'PR_COMMENT_PLACEHOLDER'
1714
+ )
1718
1715
 
1719
- fs.writeFileSync(workflowFile, templateWorkflow)
1720
- if (workflowMode === 'minimal') {
1721
- console.log(
1722
- '⚠️ Minimal mode disables tests and security scans in CI (detection-only).'
1723
- )
1724
- console.log(
1725
- ' Use --workflow-standard to re-enable full CI checks.'
1726
- )
1727
- }
1716
+ templateWorkflow = injectCollaborationSteps(templateWorkflow, {
1717
+ enableSlackAlerts: hasSlackAlerts,
1718
+ enablePrComments: hasPrComments,
1719
+ })
1720
+
1721
+ fs.writeFileSync(workflowFile, templateWorkflow)
1722
+ if (workflowMode === 'minimal') {
1728
1723
  console.log(
1729
- `♻️ Updated GitHub Actions workflow to ${workflowMode} mode`
1724
+ '⚠️ Minimal mode disables tests and security scans in CI (detection-only).'
1725
+ )
1726
+ console.log(
1727
+ ' Use --workflow-standard to re-enable full CI checks.'
1730
1728
  )
1731
1729
  }
1730
+ console.log(
1731
+ `♻️ Updated GitHub Actions workflow to ${workflowMode} mode`
1732
+ )
1732
1733
  }
1733
1734
  }
1734
1735
 
@@ -1918,8 +1919,26 @@ const fs = require('fs')
1918
1919
  const path = require('path')
1919
1920
  const os = require('os')
1920
1921
 
1921
- const licenseDir =
1922
+ function validateLicenseDir(dirPath) {
1923
+ const resolved = path.resolve(dirPath)
1924
+ const home = os.homedir()
1925
+ const tmp = os.tmpdir()
1926
+ const isInHome = resolved.startsWith(home + path.sep) || resolved === home
1927
+ const isInTmp = resolved.startsWith(tmp + path.sep) || resolved === tmp
1928
+
1929
+ if (!isInHome && !isInTmp) {
1930
+ console.warn(
1931
+ '⚠️ QAA_LICENSE_DIR must be within home or temp directory, ignoring: ' + dirPath
1932
+ )
1933
+ return path.join(home, '.create-qa-architect')
1934
+ }
1935
+
1936
+ return resolved
1937
+ }
1938
+
1939
+ const requestedLicenseDir =
1922
1940
  process.env.QAA_LICENSE_DIR || path.join(os.homedir(), '.create-qa-architect')
1941
+ const licenseDir = validateLicenseDir(requestedLicenseDir)
1923
1942
  const licenseFile = path.join(licenseDir, 'license.json')
1924
1943
  const usageFile = path.join(licenseDir, 'usage.json')
1925
1944
  const now = new Date()
@@ -1956,7 +1975,7 @@ try {
1956
1975
  const CAP = 50
1957
1976
  if (usage.prePushRuns >= CAP) {
1958
1977
  console.error('❌ Free tier limit reached: ' + usage.prePushRuns + '/' + CAP + ' pre-push runs this month')
1959
- console.error(' Upgrade to Pro, Team, or Enterprise: https://buildproven.ai/qa-architect')
1978
+ console.error(' Upgrade to Pro: https://buildproven.ai/qa-architect')
1960
1979
  process.exit(1)
1961
1980
  }
1962
1981
 
@@ -1,6 +1,6 @@
1
1
  #!/bin/bash
2
2
  # Smart Test Strategy - {{PROJECT_NAME}}
3
- # Generated by create-qa-architect (Pro/Team/Enterprise feature)
3
+ # Generated by create-qa-architect (Pro feature)
4
4
  # https://buildproven.ai/qa-architect
5
5
  set -e
6
6