create-qa-architect 5.0.1 → 5.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/.github/RELEASE_CHECKLIST.md +2 -4
  2. package/.github/workflows/daily-deploy-check.yml +136 -0
  3. package/.github/workflows/dependabot-auto-merge.yml +32 -0
  4. package/.github/workflows/nightly-gitleaks-verification.yml +1 -1
  5. package/.github/workflows/release.yml +12 -10
  6. package/.github/workflows/weekly-audit.yml +173 -0
  7. package/LICENSE +3 -3
  8. package/README.md +11 -17
  9. package/config/defaults.js +22 -1
  10. package/config/quality-config.schema.json +1 -1
  11. package/create-saas-monetization.js +65 -27
  12. package/docs/ARCHITECTURE.md +16 -13
  13. package/docs/DEPLOYMENT.md +1 -2
  14. package/docs/PREFLIGHT_REPORT.md +100 -0
  15. package/docs/TESTING.md +4 -6
  16. package/lib/billing-dashboard.html +6 -12
  17. package/lib/config-validator.js +8 -2
  18. package/lib/dependency-monitoring-premium.js +21 -19
  19. package/lib/github-api.js +249 -0
  20. package/lib/interactive/questions.js +4 -0
  21. package/lib/license-validator.js +1 -1
  22. package/lib/licensing.js +16 -18
  23. package/lib/package-utils.js +9 -8
  24. package/lib/project-maturity.js +1 -1
  25. package/lib/template-loader.js +2 -0
  26. package/lib/ui-helpers.js +2 -1
  27. package/lib/validation/base-validator.js +5 -1
  28. package/lib/validation/cache-manager.js +1 -0
  29. package/lib/validation/config-security.js +9 -4
  30. package/lib/validation/validation-factory.js +1 -1
  31. package/lib/validation/workflow-validation.js +27 -22
  32. package/lib/yaml-utils.js +15 -10
  33. package/package.json +17 -14
  34. package/scripts/check-docs.sh +63 -0
  35. package/scripts/smart-test-strategy.sh +98 -0
  36. package/scripts/test-e2e-package.sh +283 -0
  37. package/scripts/validate-command-patterns.js +112 -0
  38. package/setup.js +38 -9
  39. package/templates/QUALITY_TROUBLESHOOTING.md +32 -33
  40. package/templates/scripts/smart-test-strategy.sh +1 -1
@@ -39,7 +39,7 @@ const FOUNDER_ENTERPRISE_PRICE = '74.50'
39
39
 
40
40
  class SaaSMonetizationBootstrap {
41
41
  constructor() {
42
- this.projectRoot = process.cwd()
42
+ this.projectRoot = path.resolve(process.cwd())
43
43
  this.config = {}
44
44
  this.templates = {
45
45
  stripe: this.getStripeTemplate(),
@@ -50,6 +50,56 @@ class SaaSMonetizationBootstrap {
50
50
  }
51
51
  }
52
52
 
53
+ resolveProjectPath(relativePath) {
54
+ const normalizedRoot = this.projectRoot.endsWith(path.sep)
55
+ ? this.projectRoot
56
+ : `${this.projectRoot}${path.sep}`
57
+ const resolvedPath = path.resolve(this.projectRoot, relativePath)
58
+
59
+ if (
60
+ resolvedPath !== this.projectRoot &&
61
+ !resolvedPath.startsWith(normalizedRoot)
62
+ ) {
63
+ throw new Error(
64
+ `Refusing to access path outside project root: ${relativePath}`
65
+ )
66
+ }
67
+
68
+ return resolvedPath
69
+ }
70
+
71
+ ensureDir(relativePath) {
72
+ const target = this.resolveProjectPath(relativePath)
73
+ // Path is constrained to the project root before touching the filesystem
74
+ // eslint-disable-next-line security/detect-non-literal-fs-filename
75
+ if (!fs.existsSync(target)) {
76
+ // eslint-disable-next-line security/detect-non-literal-fs-filename
77
+ fs.mkdirSync(target, { recursive: true })
78
+ }
79
+ return target
80
+ }
81
+
82
+ writeProjectFile(relativePath, content) {
83
+ const target = this.resolveProjectPath(relativePath)
84
+ // Path is constrained to the project root before touching the filesystem
85
+ // eslint-disable-next-line security/detect-non-literal-fs-filename
86
+ fs.writeFileSync(target, content)
87
+ }
88
+
89
+ readProjectFile(relativePath) {
90
+ const target = this.resolveProjectPath(relativePath)
91
+ // Path is constrained to the project root before touching the filesystem
92
+ // eslint-disable-next-line security/detect-non-literal-fs-filename
93
+ return fs.readFileSync(target, 'utf8')
94
+ }
95
+
96
+ projectFileExists(relativePath) {
97
+ const target = this.resolveProjectPath(relativePath)
98
+ // Path is constrained to the project root before touching the filesystem
99
+ // eslint-disable-next-line security/detect-non-literal-fs-filename
100
+ return fs.existsSync(target)
101
+ }
102
+
53
103
  async run() {
54
104
  console.log('🚀 Create SaaS Monetization')
55
105
  console.log('═══════════════════════════════════')
@@ -159,10 +209,7 @@ class SaaSMonetizationBootstrap {
159
209
  const dirs = ['lib/monetization', 'legal', 'marketing', 'billing']
160
210
 
161
211
  dirs.forEach(dir => {
162
- const fullPath = path.join(this.projectRoot, dir)
163
- if (!fs.existsSync(fullPath)) {
164
- fs.mkdirSync(fullPath, { recursive: true })
165
- }
212
+ this.ensureDir(dir)
166
213
  })
167
214
  }
168
215
 
@@ -172,8 +219,8 @@ class SaaSMonetizationBootstrap {
172
219
  .replace(/{{PRO_PRICE}}/g, this.config.proPrice)
173
220
  .replace(/{{ENTERPRISE_PRICE}}/g, this.config.enterprisePrice)
174
221
 
175
- fs.writeFileSync(
176
- path.join(this.projectRoot, 'lib/monetization/stripe-integration.js'),
222
+ this.writeProjectFile(
223
+ path.join('lib/monetization', 'stripe-integration.js'),
177
224
  stripeCode
178
225
  )
179
226
  }
@@ -188,8 +235,8 @@ class SaaSMonetizationBootstrap {
188
235
  .replace(/{{FOUNDER_PRO_PRICE}}/g, this.config.founderProPrice)
189
236
  .replace(/{{DOMAIN}}/g, this.config.domain)
190
237
 
191
- fs.writeFileSync(
192
- path.join(this.projectRoot, 'lib/monetization/licensing.js'),
238
+ this.writeProjectFile(
239
+ path.join('lib/monetization', 'licensing.js'),
193
240
  licensingCode
194
241
  )
195
242
  }
@@ -204,7 +251,7 @@ class SaaSMonetizationBootstrap {
204
251
  .replace(/{{DESCRIPTION}}/g, this.config.description)
205
252
  .replace(/{{DATE}}/g, new Date().toISOString().split('T')[0])
206
253
 
207
- fs.writeFileSync(path.join(this.projectRoot, 'legal', filename), content)
254
+ this.writeProjectFile(path.join('legal', filename), content)
208
255
  }
209
256
  }
210
257
 
@@ -233,10 +280,7 @@ class SaaSMonetizationBootstrap {
233
280
  )
234
281
  .replace(/{{SUPPORT_EMAIL}}/g, this.config.supportEmail)
235
282
 
236
- fs.writeFileSync(
237
- path.join(this.projectRoot, 'marketing', filename),
238
- content
239
- )
283
+ this.writeProjectFile(path.join('marketing', filename), content)
240
284
  }
241
285
  }
242
286
 
@@ -252,17 +296,14 @@ class SaaSMonetizationBootstrap {
252
296
  )
253
297
  .replace(/{{PREMIUM_FEATURES}}/g, this.config.premiumFeatures)
254
298
 
255
- fs.writeFileSync(
256
- path.join(this.projectRoot, 'billing/dashboard.html'),
257
- billingCode
258
- )
299
+ this.writeProjectFile(path.join('billing', 'dashboard.html'), billingCode)
259
300
  }
260
301
 
261
302
  async updatePackageJson() {
262
- const packagePath = path.join(this.projectRoot, 'package.json')
303
+ const packagePath = 'package.json'
263
304
 
264
- if (fs.existsSync(packagePath)) {
265
- const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'))
305
+ if (this.projectFileExists(packagePath)) {
306
+ const pkg = JSON.parse(this.readProjectFile(packagePath))
266
307
 
267
308
  // Add monetization scripts
268
309
  pkg.scripts = pkg.scripts || {}
@@ -276,7 +317,7 @@ class SaaSMonetizationBootstrap {
276
317
  pkg.dependencies.stripe = '^14.15.0'
277
318
  pkg.dependencies.crypto = '^1.0.1'
278
319
 
279
- fs.writeFileSync(packagePath, JSON.stringify(pkg, null, 2))
320
+ this.writeProjectFile(packagePath, JSON.stringify(pkg, null, 2))
280
321
  }
281
322
  }
282
323
 
@@ -307,7 +348,7 @@ COMPANY_NAME=${this.config.companyName}
307
348
  # STRIPE_PUBLISHABLE_KEY=pk_live_your_live_key_here
308
349
  `
309
350
 
310
- fs.writeFileSync(path.join(this.projectRoot, '.env.template'), envTemplate)
351
+ this.writeProjectFile('.env.template', envTemplate)
311
352
  }
312
353
 
313
354
  async generateDeploymentGuide() {
@@ -483,10 +524,7 @@ For implementation questions:
483
524
  **Revenue Potential**: $1,500-5,000/month recurring
484
525
  `
485
526
 
486
- fs.writeFileSync(
487
- path.join(this.projectRoot, 'MONETIZATION_GUIDE.md'),
488
- guide
489
- )
527
+ this.writeProjectFile('MONETIZATION_GUIDE.md', guide)
490
528
  }
491
529
 
492
530
  // Template methods (condensed versions of our implementations)
@@ -9,18 +9,18 @@ QA Architect is a CLI tool that bootstraps quality automation in JavaScript/Type
9
9
  ```
10
10
  create-qa-architect/
11
11
  ├── setup.js # Main CLI entry point
12
- ├── lib/
13
- │ ├── smart-strategy-generator.js # Smart test strategy (Pro)
14
- │ ├── dependency-monitoring-*.js # Dependency monitoring
15
- │ └── validation/ # Validation utilities
12
+ ├── lib/ # Core logic (validation, licensing, maturity, telemetry, dependency monitoring)
16
13
  ├── templates/ # Project templates
17
- │ ├── eslint.config.cjs
18
- │ ├── .prettierrc
19
- │ ├── .husky/
20
- └── scripts/
21
- └── config/ # Language-specific configs
22
- ├── pyproject.toml
23
- └── quality-python.yml
14
+ │ ├── ci/ # GitHub Actions + CircleCI/GitLab samples
15
+ │ ├── scripts/ # Helper scripts (smart test strategy, etc.)
16
+ │ ├── integration-tests/# Starter integration tests
17
+ ├── test-stubs/ # Unit/E2E placeholders
18
+ │ ├── python/ # Python quality config
19
+ │ └── QUALITY_TROUBLESHOOTING.md
20
+ ├── config/ # Defaults and language-specific configs
21
+ │ ├── pyproject.toml
22
+ │ └── quality-python.yml
23
+ └── docs/ # Architecture/testing/SLA/security docs
24
24
  ```
25
25
 
26
26
  ## Data Flow
@@ -50,5 +50,8 @@ Risk-based pre-push validation that adapts to change context:
50
50
  - `--deps` - Dependency monitoring only
51
51
  - `--security-config` - Security validation
52
52
  - `--check-maturity` - Project maturity report
53
- - `--comprehensive` - Full validation suite
54
-
53
+ - `--validate` / `--comprehensive` - Full validation suite
54
+ - `--validate-docs` - Documentation validation only
55
+ - `--validate-config` - Validate `.qualityrc.json`
56
+ - `--alerts-slack` / `--pr-comments` - Collaboration hooks
57
+ - `--license-status` - Show current tier/features
@@ -59,5 +59,4 @@ npm deprecate create-qa-architect@VERSION "Critical bug, use VERSION instead"
59
59
  ## npm Registry
60
60
 
61
61
  - Package: https://www.npmjs.com/package/create-qa-architect
62
- - Documentation: https://github.com/vibebuildlab/create-qa-architect
63
-
62
+ - Documentation: https://github.com/vibebuildlab/qa-architect
@@ -0,0 +1,100 @@
1
+ # Preflight Review: QA Architect (create-qa-architect)
2
+
3
+ **Depth**: standard
4
+ **Date**: 2025-12-13
5
+ **Version**: 5.0.7
6
+
7
+ ---
8
+
9
+ ## Overall Status: ✅ PASS (prerelease suite)
10
+
11
+ Prerelease (`npm run prerelease`) executed for 5.0.7, including docs check, command patterns, full test suite, command tests, and e2e package validation.
12
+
13
+ ---
14
+
15
+ ## Critical Issues (P0) - Must Fix
16
+
17
+ | Issue | Category | Location | Fix |
18
+ | ----- | -------- | -------- | --- |
19
+ | None | - | - | - |
20
+
21
+ ---
22
+
23
+ ## Important Issues (P1) - Should Fix
24
+
25
+ | Issue | Category | Location | Recommendation |
26
+ | ------------------------ | -------- | ---------------- | ------------------------------------------------------------------------------------------------------------ |
27
+ | Gitleaks false positives | Security | tests/\*.test.js | Test fixtures use fake API key patterns (QAA-XXXX format); consider a scoped `.gitleaksignore` for fixtures. |
28
+ | Publish verification | Release | package.json | Confirm npm shows 5.0.7 after publishing; update if propagation is pending. |
29
+
30
+ ---
31
+
32
+ ## P0 Functional Checks
33
+
34
+ | Check | Status | Notes |
35
+ | ----------------- | ------ | ------------------------------------------------------------------ |
36
+ | All tests passing | ✅ | `npm run prerelease` (includes full test suite) |
37
+ | npm audit | ⚠️ | Not run in prerelease; run `npm run security:audit` before publish |
38
+ | ESLint | ⚠️ | Not run in prerelease; run `npm run lint` if desired |
39
+ | Build/validation | ✅ | Covered via prerelease command + e2e package test |
40
+
41
+ ---
42
+
43
+ ## P0 Security Checks
44
+
45
+ | Check | Status | Notes |
46
+ | ------------------------- | ------ | ------------------------------------------------------------------------------------- |
47
+ | npm audit (high/critical) | ⚠️ | Not run in prerelease; run `npm run security:audit` |
48
+ | Hardcoded secrets scan | ⚠️ | Re-run gitleaks/`npm run security:secrets`; expect fixture false positives (QAA-XXXX) |
49
+ | No production secrets | ✅ | No `.env` files, no real API keys committed |
50
+
51
+ ---
52
+
53
+ ## Product Packaging
54
+
55
+ | Item | Status | Notes |
56
+ | ------------ | ------ | ------------------------------ |
57
+ | CHANGELOG.md | ✅ | Present |
58
+ | LICENSE | ✅ | Present |
59
+ | README.md | ✅ | Present |
60
+ | .env.example | N/A | Not needed for CLI tool |
61
+ | Version tags | ⚠️ | Confirm v5.0.7 tag pushed |
62
+ | Git status | ⚠️ | Verify clean before publishing |
63
+
64
+ ---
65
+
66
+ ## Quality Violations
67
+
68
+ | Type | Count | Assessment |
69
+ | ----------------------- | ----- | ------------------------------------------------------------ |
70
+ | eslint-disable comments | 24 | All have security justification comments explaining why safe |
71
+ | any types | 0 | JavaScript project, N/A |
72
+
73
+ **Note**: The `eslint-disable` comments are all for security-related ESLint rules (detect-unsafe-regex, detect-non-literal-fs-filename, detect-object-injection) and each includes a detailed safety justification explaining why the pattern is safe in context.
74
+
75
+ ---
76
+
77
+ ## Silent Killer Check (N/A for npm package)
78
+
79
+ | Integration | Status | Notes |
80
+ | --------------- | ------ | --------------------- |
81
+ | Stripe webhooks | N/A | CLI tool, no webhooks |
82
+ | OAuth redirects | N/A | CLI tool |
83
+ | API keys | N/A | CLI tool |
84
+ | CORS config | N/A | CLI tool |
85
+
86
+ ---
87
+
88
+ ## Next Steps
89
+
90
+ 1. Run `npm run security:audit` (and optional gitleaks scan) before publish
91
+ 2. Confirm npm publish and tag for 5.0.7 are visible on npm/GitHub
92
+ 3. Add `.gitleaksignore` scoped to test fixtures if false positives remain
93
+
94
+ ---
95
+
96
+ ## Recommendation
97
+
98
+ **✅ Cleared for launch (5.0.7)**
99
+
100
+ Prerelease suite passed for 5.0.7. Run `npm run security:audit`, confirm publish/tag visibility, and handle fixture gitleaks ignores if needed; then proceed with release comms. This remains an npm CLI package (no web surface), so focus stays on docs/CI/security validation.
package/docs/TESTING.md CHANGED
@@ -2,14 +2,13 @@
2
2
 
3
3
  ## Overview
4
4
 
5
- QA Architect uses Jest for testing with a focus on integration tests that validate real CLI workflows.
5
+ QA Architect uses plain Node-based test runners (no Jest) with a heavy focus on integration tests that validate real CLI workflows end to end.
6
6
 
7
7
  ## Running Tests
8
8
 
9
9
  ```bash
10
- npm test # Run all tests
11
- npm run test:coverage # Run with coverage report
12
- npm run test:watch # Watch mode for development
10
+ npm test # Run all tests (sequential Node scripts)
11
+ npm run test:coverage # Run with coverage report via c8
13
12
  ```
14
13
 
15
14
  ## Test Structure
@@ -48,7 +47,7 @@ Use real packages from the ecosystem, not toy examples:
48
47
  const TOP_PYTHON_PACKAGES = [
49
48
  'django-cors-headers',
50
49
  'scikit-learn',
51
- 'pytest-cov'
50
+ 'pytest-cov',
52
51
  ]
53
52
  ```
54
53
 
@@ -59,4 +58,3 @@ Always run before release:
59
58
  ```bash
60
59
  npm run prerelease # Runs docs:check + all tests
61
60
  ```
62
-
@@ -311,9 +311,9 @@
311
311
  onclick="selectTier('pro')"
312
312
  >
313
313
  <div class="tier-name">Pro</div>
314
- <div class="tier-price">$59<span class="period">/month</span></div>
314
+ <div class="tier-price">$19<span class="period">/month</span></div>
315
315
  <div style="color: #22c55e; font-size: 0.9rem">
316
- or $590/year (save $118)
316
+ or $190/year (save $38)
317
317
  </div>
318
318
 
319
319
  <ul class="tier-features">
@@ -329,12 +329,8 @@
329
329
  <!-- Team Tier -->
330
330
  <div class="tier-card" data-tier="team" onclick="selectTier('team')">
331
331
  <div class="tier-name">Team</div>
332
- <div class="tier-price">
333
- $15<span class="period">/user/month</span>
334
- </div>
335
- <div style="color: #666; font-size: 0.9rem">
336
- 5-seat minimum ($75/mo)
337
- </div>
332
+ <div class="tier-price">Contact us</div>
333
+ <div style="color: #666; font-size: 0.9rem">Coming soon</div>
338
334
 
339
335
  <ul class="tier-features">
340
336
  <li>All PRO features included</li>
@@ -353,10 +349,8 @@
353
349
  onclick="selectTier('enterprise')"
354
350
  >
355
351
  <div class="tier-name">Enterprise</div>
356
- <div class="tier-price">$249<span class="period">/month</span></div>
357
- <div style="color: #666; font-size: 0.9rem">
358
- annual + $499 onboarding
359
- </div>
352
+ <div class="tier-price">Contact us</div>
353
+ <div style="color: #666; font-size: 0.9rem">Coming soon</div>
360
354
 
361
355
  <ul class="tier-features">
362
356
  <li>All TEAM features included</li>
@@ -1,10 +1,16 @@
1
1
  'use strict'
2
2
 
3
- const Ajv = require('ajv')
4
- const addFormats = require('ajv-formats')
3
+ const AjvImport = require('ajv')
4
+ const addFormatsImport = require('ajv-formats')
5
5
  const fs = require('fs')
6
6
  const path = require('path')
7
7
 
8
+ // Handle CJS/ESM interop for Ajv and ajv-formats in JS type-checking
9
+ const Ajv = /** @type {any} */ (AjvImport.default || AjvImport)
10
+ const addFormats = /** @type {(ajv: any) => void} */ (
11
+ addFormatsImport.default || addFormatsImport
12
+ )
13
+
8
14
  function validateQualityConfig(configPath) {
9
15
  const result = {
10
16
  valid: false,
@@ -289,7 +289,7 @@ function matchesPattern(depName, pattern) {
289
289
  if (!regex) {
290
290
  // Cache miss - compile and store
291
291
  const regexPattern = pattern.replace(/\*/g, '.*')
292
- // eslint-disable-next-line security/detect-non-literal-regexp
292
+ // eslint-disable-next-line security/detect-non-literal-regexp -- Safe: pattern from internal config, only allows * wildcards replaced with .*, anchored with ^$
293
293
  regex = new RegExp(`^${regexPattern}$`)
294
294
 
295
295
  // Implement size-limited cache with FIFO eviction
@@ -348,7 +348,7 @@ function generateReactGroups(_frameworkInfo) {
348
348
  /**
349
349
  * Generate dependency groups for Vue ecosystem
350
350
  *
351
- * @param {Object} frameworkInfo - Vue detection results
351
+ * @param {Object} _frameworkInfo - Vue detection results
352
352
  * @returns {Object} Dependabot groups configuration
353
353
  */
354
354
  function generateVueGroups(_frameworkInfo) {
@@ -376,7 +376,7 @@ function generateVueGroups(_frameworkInfo) {
376
376
  /**
377
377
  * Generate dependency groups for Angular ecosystem
378
378
  *
379
- * @param {Object} frameworkInfo - Angular detection results
379
+ * @param {Object} _frameworkInfo - Angular detection results
380
380
  * @returns {Object} Dependabot groups configuration
381
381
  */
382
382
  function generateAngularGroups(_frameworkInfo) {
@@ -404,7 +404,7 @@ function generateAngularGroups(_frameworkInfo) {
404
404
  /**
405
405
  * Generate dependency groups for testing frameworks
406
406
  *
407
- * @param {Object} frameworkInfo - Testing framework detection results
407
+ * @param {Object} _frameworkInfo - Testing framework detection results
408
408
  * @returns {Object} Dependabot groups configuration
409
409
  */
410
410
  function generateTestingGroups(_frameworkInfo) {
@@ -428,7 +428,7 @@ function generateTestingGroups(_frameworkInfo) {
428
428
  /**
429
429
  * Generate dependency groups for build tools
430
430
  *
431
- * @param {Object} frameworkInfo - Build tool detection results
431
+ * @param {Object} _frameworkInfo - Build tool detection results
432
432
  * @returns {Object} Dependabot groups configuration
433
433
  */
434
434
  function generateBuildToolGroups(_frameworkInfo) {
@@ -446,7 +446,7 @@ function generateBuildToolGroups(_frameworkInfo) {
446
446
  /**
447
447
  * Generate Storybook dependency groups
448
448
  *
449
- * @param {Object} frameworkInfo - Storybook detection results
449
+ * @param {Object} _frameworkInfo - Storybook detection results
450
450
  * @returns {Object} Dependabot groups configuration
451
451
  */
452
452
  function generateStorybookGroups(_frameworkInfo) {
@@ -490,7 +490,7 @@ function hasPythonProject(projectPath) {
490
490
  * attacks with maliciously large requirements files.
491
491
  *
492
492
  * @param {string} requirementsPath - Path to requirements.txt file
493
- * @returns {Object<string, string>} Map of package names to version specifiers
493
+ * @returns {Record<string, string>} Map of package names to version specifiers
494
494
  * @throws {Error} If file exceeds MAX_REQUIREMENTS_FILE_SIZE
495
495
  *
496
496
  * @example
@@ -512,6 +512,7 @@ function parsePipRequirements(requirementsPath) {
512
512
  }
513
513
 
514
514
  const content = fs.readFileSync(requirementsPath, 'utf8')
515
+ /** @type {Record<string, string>} */
515
516
  const dependencies = {}
516
517
 
517
518
  content.split('\n').forEach(line => {
@@ -529,7 +530,7 @@ function parsePipRequirements(requirementsPath) {
529
530
  // Support dotted names (zope.interface), hyphens (pytest-cov), underscores (google_cloud)
530
531
  // Also handle extras like fastapi[all] by capturing everything before the bracket
531
532
  // Fixed: Replaced (.*) with ([^\s]*) to prevent catastrophic backtracking
532
- // eslint-disable-next-line security/detect-unsafe-regex
533
+ // eslint-disable-next-line security/detect-unsafe-regex -- Safe: bounded character classes [\w.-], [\w,\s-], [^\s], anchored ^$, no nested quantifiers
533
534
  const match = line.match(/^([\w.-]+)(\[[\w,\s-]+\])?([><=!~]+)?([^\s]*)$/)
534
535
  if (match) {
535
536
  const [, name, _extras, operator, version] = match
@@ -552,12 +553,13 @@ function parsePipRequirements(requirementsPath) {
552
553
  */
553
554
  function parsePyprojectToml(pyprojectPath) {
554
555
  const content = fs.readFileSync(pyprojectPath, 'utf8')
556
+ /** @type {Record<string, string>} */
555
557
  const dependencies = {}
556
558
 
557
559
  // Parse PEP 621 list-style dependencies: dependencies = ["package>=1.0.0", ...]
558
560
  // Match main dependencies array: dependencies = [...]
559
561
  // Allow optional whitespace/comments after ] to handle: ] # end of deps
560
- // eslint-disable-next-line security/detect-unsafe-regex
562
+ // eslint-disable-next-line security/detect-unsafe-regex -- Safe: lazy quantifier *? prevents backtracking, anchored ^$, bounded alternation
561
563
  const mainDepPattern = /^dependencies\s*=\s*\[([\s\S]*?)\]\s*(?:#.*)?$/m
562
564
  const mainMatch = mainDepPattern.exec(content)
563
565
 
@@ -574,7 +576,7 @@ function parsePyprojectToml(pyprojectPath) {
574
576
  // Support dotted names, hyphens, underscores, and extras
575
577
  // Fixed: Replaced ($|.*) with ([^\s]*) to prevent catastrophic backtracking
576
578
  const match = depString.match(
577
- /^([\w.-]+)(\[[\w,\s-]+\])?([><=!~]+)?([^\s]*)$/ // eslint-disable-line security/detect-unsafe-regex
579
+ /^([\w.-]+)(\[[\w,\s-]+\])?([><=!~]+)?([^\s]*)$/ // eslint-disable-line security/detect-unsafe-regex -- Safe: bounded character classes, anchored ^$, no nested quantifiers
578
580
  )
579
581
  if (match) {
580
582
  const [, name, _extras, operator, version] = match
@@ -606,7 +608,7 @@ function parsePyprojectToml(pyprojectPath) {
606
608
  const depString = pkgMatch[1].trim()
607
609
 
608
610
  const match = depString.match(
609
- /^([\w.-]+)(\[[\w,\s-]+\])?([><=!~]+)?($|.*)$/ // eslint-disable-line security/detect-unsafe-regex
611
+ /^([\w.-]+)(\[[\w,\s-]+\])?([><=!~]+)?([^\s]*)$/ // eslint-disable-line security/detect-unsafe-regex -- Safe: bounded character classes, anchored ^$, no nested quantifiers
610
612
  )
611
613
  if (match) {
612
614
  const [, name, _extras, operator, version] = match
@@ -829,7 +831,7 @@ function parseCargoToml(cargoPath) {
829
831
  if (!trimmed || trimmed.startsWith('#')) continue
830
832
 
831
833
  // Match simple pattern: name = "version"
832
- // eslint-disable-next-line security/detect-unsafe-regex
834
+ // eslint-disable-next-line security/detect-unsafe-regex -- Safe: bounded groups \w+, [^"']+, anchored ^, no nested quantifiers
833
835
  const simpleMatch = trimmed.match(/^(\w+(?:-\w+)*)\s*=\s*["']([^"']+)["']/)
834
836
  if (simpleMatch) {
835
837
  const [, name, version] = simpleMatch
@@ -840,7 +842,7 @@ function parseCargoToml(cargoPath) {
840
842
 
841
843
  // Match complex pattern: name = { version = "...", ... }
842
844
  const complexMatch = trimmed.match(
843
- // eslint-disable-next-line security/detect-unsafe-regex
845
+ // eslint-disable-next-line security/detect-unsafe-regex -- Safe: bounded negated class [^}]*, anchored ^, no nested quantifiers
844
846
  /^(\w+(?:-\w+)*)\s*=\s*\{[^}]*version\s*=\s*["']([^"']+)["']/
845
847
  )
846
848
  if (complexMatch) {
@@ -970,7 +972,7 @@ function parseGemfile(gemfilePath) {
970
972
 
971
973
  // Match: gem 'rails', '~> 7.0' or gem 'rails'
972
974
  const gemMatch = trimmed.match(
973
- // eslint-disable-next-line security/detect-unsafe-regex
975
+ // eslint-disable-next-line security/detect-unsafe-regex -- Safe: bounded negated class [^'"]+, no nested quantifiers, processed line-by-line
974
976
  /gem\s+['"]([^'"]+)['"]\s*(?:,\s*['"]([^'"]+)['"])?/
975
977
  )
976
978
  if (gemMatch) {
@@ -1270,11 +1272,11 @@ function generateBundlerGroups(bundlerFrameworks) {
1270
1272
  /**
1271
1273
  * Generate premium Dependabot configuration with multi-language framework-aware grouping
1272
1274
  *
1273
- * @param {Object} options - Configuration options
1274
- * @param {string} options.projectPath - Path to project directory
1275
- * @param {string} options.schedule - Update schedule (daily, weekly, monthly)
1276
- * @param {string} options.day - Day of week for updates
1277
- * @param {string} options.time - Time for updates
1275
+ * @param {Object} [options] - Configuration options
1276
+ * @param {string} [options.projectPath='.'] - Path to project directory
1277
+ * @param {string} [options.schedule='weekly'] - Update schedule (daily, weekly, monthly)
1278
+ * @param {string} [options.day='monday'] - Day of week for updates
1279
+ * @param {string} [options.time='09:00'] - Time for updates
1278
1280
  * @returns {Object|null} Dependabot configuration object or null if not licensed
1279
1281
  */
1280
1282
  function generatePremiumDependabotConfig(options = {}) {