create-qa-architect 5.12.1 → 5.13.3

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 (38) hide show
  1. package/.github/dependabot.yml +10 -30
  2. package/.github/workflows/claude-md-validation.yml +5 -7
  3. package/.github/workflows/dependabot-auto-merge.yml +1 -0
  4. package/.github/workflows/quality.yml +26 -12
  5. package/.github/workflows/release.yml +2 -1
  6. package/.github/workflows/stale-prs.yml +42 -0
  7. package/.github/workflows/weekly-gitleaks-verification.yml +6 -4
  8. package/LICENSE +5 -5
  9. package/README.md +22 -21
  10. package/config/defaults.js +2 -3
  11. package/config/quality-config.schema.json +1 -1
  12. package/docs/CI-COST-ANALYSIS.md +8 -8
  13. package/docs/DEPLOYMENT.md +1 -1
  14. package/docs/DEVELOPMENT-WORKFLOW.md +2 -2
  15. package/docs/TURBOREPO-SUPPORT.md +3 -3
  16. package/docs/dev_guide/CONVENTIONS.md +132 -0
  17. package/eslint.config.cjs +25 -0
  18. package/lib/blob-storage.js +57 -0
  19. package/lib/commands/analyze-ci.js +267 -27
  20. package/lib/commands/deps.js +5 -5
  21. package/lib/commands/license-commands.js +2 -2
  22. package/lib/commands/maturity-check.js +20 -2
  23. package/lib/dependency-monitoring-basic.js +4 -4
  24. package/lib/dependency-monitoring-premium.js +5 -5
  25. package/lib/license-validator.js +2 -2
  26. package/lib/licensing.js +7 -9
  27. package/lib/smart-strategy-generator.js +1 -1
  28. package/lib/validation/documentation.js +2 -0
  29. package/lib/workflow-config.js +176 -73
  30. package/package.json +53 -23
  31. package/scripts/deploy-consumers.sh +369 -0
  32. package/scripts/pattern-check.sh +607 -0
  33. package/scripts/run-semgrep.sh +244 -0
  34. package/scripts/smart-test-strategy.sh +1 -1
  35. package/setup.js +119 -71
  36. package/templates/CLAUDE_WORKFLOW_POLICY.md +3 -3
  37. package/templates/scripts/smart-test-strategy.sh +1 -1
  38. package/.github/workflows/auto-release.yml +0 -39
@@ -10,11 +10,29 @@
10
10
  */
11
11
  function handleMaturityCheck() {
12
12
  const { ProjectMaturityDetector } = require('../project-maturity')
13
+ const githubActions = process.argv.includes('--github-actions')
13
14
  const detector = new ProjectMaturityDetector({
14
15
  projectPath: process.cwd(),
15
- verbose: true,
16
+ verbose: !githubActions,
16
17
  })
17
- detector.printReport()
18
+
19
+ if (githubActions) {
20
+ const output = detector.generateGitHubActionsOutput()
21
+ console.log(`maturity=${output.maturity}`)
22
+ console.log(`source-count=${output.sourceCount}`)
23
+ console.log(`test-count=${output.testCount}`)
24
+ console.log(`has-deps=${output.hasDeps}`)
25
+ console.log(`has-docs=${output.hasDocs}`)
26
+ console.log(`has-css=${output.hasCss}`)
27
+ console.log(`has-shell=${output.hasShell}`)
28
+ console.log(`shell-count=${output.shellCount}`)
29
+ console.log(`is-shell-project=${output.isShellProject}`)
30
+ console.log(`required-checks=${output.requiredChecks}`)
31
+ console.log(`optional-checks=${output.optionalChecks}`)
32
+ console.log(`disabled-checks=${output.disabledChecks}`)
33
+ } else {
34
+ detector.printReport()
35
+ }
18
36
  process.exit(0)
19
37
  }
20
38
 
@@ -22,7 +22,7 @@ function hasNpmProject(projectPath) {
22
22
  function generateBasicDependabotConfig(options = {}) {
23
23
  const {
24
24
  projectPath = '.',
25
- schedule = 'weekly',
25
+ schedule = 'monthly',
26
26
  day = 'monday',
27
27
  time = '09:00',
28
28
  monorepoInfo = null, // Optional monorepo detection result
@@ -50,7 +50,7 @@ function generateBasicDependabotConfig(options = {}) {
50
50
  day: day,
51
51
  time: time,
52
52
  },
53
- 'open-pull-requests-limit': 5,
53
+ 'open-pull-requests-limit': 2,
54
54
  labels: ['dependencies', 'root'],
55
55
  'commit-message': {
56
56
  prefix: 'deps(root)',
@@ -69,7 +69,7 @@ function generateBasicDependabotConfig(options = {}) {
69
69
  day: day,
70
70
  time: time,
71
71
  },
72
- 'open-pull-requests-limit': 3,
72
+ 'open-pull-requests-limit': 2,
73
73
  labels: ['dependencies', pkg.name],
74
74
  'commit-message': {
75
75
  prefix: `deps(${pkg.name})`,
@@ -87,7 +87,7 @@ function generateBasicDependabotConfig(options = {}) {
87
87
  day: day,
88
88
  time: time,
89
89
  },
90
- 'open-pull-requests-limit': 5,
90
+ 'open-pull-requests-limit': 2,
91
91
  labels: ['dependencies'],
92
92
  'commit-message': {
93
93
  prefix: 'deps',
@@ -1294,7 +1294,7 @@ function generatePremiumDependabotConfig(options = {}) {
1294
1294
 
1295
1295
  const {
1296
1296
  projectPath = '.',
1297
- schedule = 'weekly',
1297
+ schedule = 'monthly',
1298
1298
  day = 'monday',
1299
1299
  time = '09:00',
1300
1300
  } = options
@@ -1316,7 +1316,7 @@ function generatePremiumDependabotConfig(options = {}) {
1316
1316
  'package-ecosystem': 'npm',
1317
1317
  directory: '/',
1318
1318
  schedule: { interval: schedule, day, time },
1319
- 'open-pull-requests-limit': 10,
1319
+ 'open-pull-requests-limit': 3,
1320
1320
  labels: ['dependencies', 'npm'],
1321
1321
  'commit-message': { prefix: 'deps(npm)', include: 'scope' },
1322
1322
  ...(Object.keys(npmGroups).length > 0 && { groups: npmGroups }),
@@ -1330,7 +1330,7 @@ function generatePremiumDependabotConfig(options = {}) {
1330
1330
  'package-ecosystem': 'pip',
1331
1331
  directory: '/',
1332
1332
  schedule: { interval: schedule, day, time },
1333
- 'open-pull-requests-limit': 10,
1333
+ 'open-pull-requests-limit': 3,
1334
1334
  labels: ['dependencies', 'python'],
1335
1335
  'commit-message': { prefix: 'deps(python)' },
1336
1336
  ...(Object.keys(pipGroups).length > 0 && { groups: pipGroups }),
@@ -1344,7 +1344,7 @@ function generatePremiumDependabotConfig(options = {}) {
1344
1344
  'package-ecosystem': 'cargo',
1345
1345
  directory: '/',
1346
1346
  schedule: { interval: schedule, day, time },
1347
- 'open-pull-requests-limit': 10,
1347
+ 'open-pull-requests-limit': 3,
1348
1348
  labels: ['dependencies', 'rust'],
1349
1349
  'commit-message': { prefix: 'deps(rust)' },
1350
1350
  ...(Object.keys(cargoGroups).length > 0 && { groups: cargoGroups }),
@@ -1358,7 +1358,7 @@ function generatePremiumDependabotConfig(options = {}) {
1358
1358
  'package-ecosystem': 'bundler',
1359
1359
  directory: '/',
1360
1360
  schedule: { interval: schedule, day, time },
1361
- 'open-pull-requests-limit': 10,
1361
+ 'open-pull-requests-limit': 3,
1362
1362
  labels: ['dependencies', 'ruby'],
1363
1363
  'commit-message': { prefix: 'deps(ruby)' },
1364
1364
  ...(Object.keys(bundlerGroups).length > 0 && { groups: bundlerGroups }),
@@ -85,7 +85,7 @@ class LicenseValidator {
85
85
  // Allow enterprises to host their own registry
86
86
  this.licenseDbUrl =
87
87
  process.env.QAA_LICENSE_DB_URL ||
88
- 'https://vibebuildlab.com/api/licenses/qa-architect.json'
88
+ 'https://buildproven.ai/api/licenses/qa-architect.json'
89
89
 
90
90
  this.licensePublicKey = loadKeyFromEnv(
91
91
  process.env.QAA_LICENSE_PUBLIC_KEY,
@@ -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() {
@@ -399,7 +400,7 @@ function showUpgradeMessage(feature) {
399
400
  console.log('')
400
401
  console.log(' 🎁 Start 14-day free trial - no credit card required')
401
402
  console.log('')
402
- console.log('🚀 Upgrade: https://vibebuildlab.com/qa-architect')
403
+ console.log('🚀 Upgrade: https://buildproven.ai/qa-architect')
403
404
  console.log(
404
405
  '🔑 Activate: npx create-qa-architect@latest --activate-license'
405
406
  )
@@ -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,
@@ -745,7 +743,7 @@ async function promptLicenseActivation() {
745
743
  console.log(
746
744
  ' If you purchased this license, please contact support at:'
747
745
  )
748
- console.log(' Email: support@vibebuildlab.com')
746
+ console.log(' Email: support@buildproven.ai')
749
747
  console.log(
750
748
  ' Include your license key and purchase email for verification.'
751
749
  )
@@ -1140,7 +1138,7 @@ function showLicenseStatus() {
1140
1138
  // Show upgrade path
1141
1139
  if (license.tier === LICENSE_TIERS.FREE) {
1142
1140
  console.log('\n💡 Upgrade to PRO for unlimited access + security scanning')
1143
- console.log(' → https://vibebuildlab.com/qa-architect')
1141
+ console.log(' → https://buildproven.ai/qa-architect')
1144
1142
  }
1145
1143
  }
1146
1144
 
@@ -255,7 +255,7 @@ function generateSmartStrategy(options = {}) {
255
255
  `Troubleshooting steps:\n` +
256
256
  `1. Reinstall the package: npm install -g create-qa-architect@latest\n` +
257
257
  `2. Check file permissions: ls -la ${path.dirname(templatePath)}\n` +
258
- `3. Report issue if problem persists: https://github.com/vibebuildlab/qa-architect/issues/new`
258
+ `3. Report issue if problem persists: https://github.com/buildproven/qa-architect/issues/new`
259
259
  )
260
260
  }
261
261
 
@@ -267,6 +267,8 @@ class DocumentationValidator {
267
267
  'quality-python.yml',
268
268
  'ci.yml', // Common workflow name users might have
269
269
  'test.yml', // Common workflow name users might have
270
+ 'tests.yml', // Common workflow name users might have
271
+ 'quality-legacy.yml', // Legacy workflow name referenced in cleanup docs
270
272
  // Optional tooling configs
271
273
  '.lighthouserc.js',
272
274
  'vercel.json',
@@ -65,8 +65,144 @@ 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
+
99
+ /**
100
+ * Strip a named section from workflow content.
101
+ * Removes everything between # {{NAME_BEGIN}} and # {{NAME_END}} markers (inclusive).
102
+ * Leaves a single newline to avoid collapsing adjacent YAML blocks.
103
+ * @param {string} content - Workflow content
104
+ * @param {string} sectionName - Section name (e.g. 'QA_ARCHITECT_ONLY', 'FULL_DETECTION')
105
+ * @returns {string} Content with section removed
106
+ */
107
+ function stripSection(content, sectionName) {
108
+ if (!/^[A-Z_]+$/.test(sectionName)) {
109
+ throw new Error(`Invalid section name: ${sectionName}`)
110
+ }
111
+ // eslint-disable-next-line security/detect-non-literal-regexp -- sectionName validated above
112
+ const pattern = new RegExp(
113
+ `[^\\S\\n]*# \\{\\{${sectionName}_BEGIN\\}\\}[\\s\\S]*?# \\{\\{${sectionName}_END\\}\\}\\n?`,
114
+ 'g'
115
+ )
116
+ return content.replace(pattern, '')
117
+ }
118
+
119
+ /**
120
+ * Remove a paths-ignore block from a top-level workflow trigger.
121
+ * @param {string} content - Workflow content
122
+ * @param {string} triggerName - Trigger key (e.g. push, pull_request)
123
+ * @returns {string} Updated content
124
+ */
125
+ function removeTriggerPathsIgnore(content, triggerName) {
126
+ const lines = content.split('\n')
127
+ const output = []
128
+
129
+ for (let index = 0; index < lines.length; index += 1) {
130
+ const line = lines[index]
131
+ output.push(line)
132
+
133
+ if (
134
+ line !== ` ${triggerName}:` &&
135
+ !line.startsWith(` ${triggerName}: `)
136
+ ) {
137
+ continue
138
+ }
139
+
140
+ let scanIndex = index + 1
141
+ while (scanIndex < lines.length) {
142
+ const nextLine = lines[scanIndex]
143
+
144
+ if (nextLine.startsWith(' ') && !nextLine.startsWith(' ')) {
145
+ break
146
+ }
147
+
148
+ if (nextLine === ' paths-ignore:') {
149
+ scanIndex += 1
150
+ while (
151
+ scanIndex < lines.length &&
152
+ lines[scanIndex].startsWith(' - ')
153
+ ) {
154
+ scanIndex += 1
155
+ }
156
+ index = scanIndex - 1
157
+ break
158
+ }
159
+
160
+ output.push(nextLine)
161
+ index = scanIndex
162
+ scanIndex += 1
163
+ }
164
+ }
165
+
166
+ return output.join('\n')
167
+ }
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
+
68
202
  /**
69
203
  * Inject workflow mode-specific configuration into quality.yml
204
+ * Uses section markers (# {{SECTION_BEGIN/END}}) for reliable content removal
205
+ * instead of fragile structural regex patterns.
70
206
  * @param {string} workflowContent - Template content
71
207
  * @param {'minimal'|'standard'|'comprehensive'} mode - Selected mode
72
208
  * @returns {string} Modified workflow content
@@ -74,101 +210,66 @@ function detectExistingWorkflowMode(projectPath) {
74
210
  function injectWorkflowMode(workflowContent, mode) {
75
211
  let updated = workflowContent
76
212
 
213
+ // Set workflow mode marker
77
214
  const versionMarker = `# WORKFLOW_MODE: ${mode}`
78
215
  if (updated.includes('# WORKFLOW_MODE:')) {
79
- // Replace existing marker with new mode
80
216
  updated = updated.replace(
81
217
  /# WORKFLOW_MODE: (minimal|standard|comprehensive)/,
82
218
  versionMarker
83
219
  )
84
220
  } else {
85
- // Add marker before jobs:
86
221
  updated = updated.replace(/(\n\njobs:)/, `\n${versionMarker}\n$1`)
87
222
  }
88
223
 
89
- // Controlled regexes for YAML workflow template matching - these patterns match static
90
- // template content, not user input. Safe from ReDoS as they match known structure.
224
+ // All consumer workflows: strip qa-architect-only content
225
+ updated = stripSection(updated, 'QA_ARCHITECT_ONLY')
91
226
 
92
- // Handle mode-specific transformations
227
+ // Mode-specific transformations
93
228
  if (mode === 'standard') {
94
- // Standard: Add main branch condition to tests job
95
- // Handle both single-line and multi-line if: formats
96
-
97
- // First try multi-line format (if: | block)
229
+ // Standard: run tests on main only to keep CI costs bounded.
98
230
  if (
99
- updated.includes('tests:') &&
100
- 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'")
101
234
  ) {
102
- // Multi-line if: | block - add main branch check at the start
103
- updated = updated.replace(
104
- /(tests:\s+runs-on:[^\n]+\s+needs:[^\n]+\s+if: \|\s*\n\s+)fromJSON\(needs\.detect-maturity\.outputs\.test-count\)/,
105
- "$1github.ref == 'refs/heads/main' &&\n fromJSON(needs.detect-maturity.outputs.test-count)"
106
- )
107
-
108
- // Also try single-line format as fallback
109
- updated = updated.replace(
110
- /(\s+tests:\s+runs-on:[^\n]+\s+needs:[^\n]+\s+)if: fromJSON\(needs\.detect-maturity\.outputs\.test-count\) > 0/,
111
- "$1if: github.ref == 'refs/heads/main' && fromJSON(needs.detect-maturity.outputs.test-count) > 0"
112
- )
235
+ updated = addStandardTestsBranchGate(updated)
113
236
  }
114
-
115
- // Standard: Change matrix to [20, 22]
116
- updated = updated.replace(/node-version: \[22\]/g, 'node-version: [20, 22]')
117
237
  } else if (mode === 'comprehensive') {
118
- // Comprehensive: Remove paths-ignore blocks from push and pull_request
119
- updated = updated.replace(
120
- /(\s+push:\s+branches:[^\n]+)\s+paths-ignore:\s+- '\*\*\.md'\s+- 'docs\/\*\*'\s+- 'LICENSE'\s+- '\.gitignore'\s+- '\.editorconfig'/g,
121
- '$1'
122
- )
123
- updated = updated.replace(
124
- /(\s+pull_request:\s+branches:[^\n]+)\s+paths-ignore:\s+- '\*\*\.md'\s+- 'docs\/\*\*'\s+- 'LICENSE'\s+- '\.gitignore'\s+- '\.editorconfig'/g,
125
- '$1'
126
- )
127
-
238
+ // Comprehensive: Remove paths-ignore blocks
239
+ updated = removeTriggerPathsIgnore(updated, 'push')
240
+ updated = removeTriggerPathsIgnore(updated, 'pull_request')
128
241
  // Comprehensive: Remove schedule trigger (security runs inline)
129
242
  updated = updated.replace(/\s+schedule:\s+- cron:[^\n]+[^\n]*\n?/g, '\n')
130
-
131
243
  // Comprehensive: Remove schedule condition from security job
132
244
  updated = updated.replace(
133
245
  /if: \(github\.event_name == 'schedule' \|\| github\.event_name == 'workflow_dispatch'\) && /g,
134
246
  'if: '
135
247
  )
136
-
137
- // Comprehensive: Change matrix to [20, 22]
138
248
  updated = updated.replace(/node-version: \[22\]/g, 'node-version: [20, 22]')
139
249
  }
140
- // Minimal mode: Remove expensive dependency install and maturity detection steps
141
- // Keep only: checkout, setup-node, detect-pm, setup-pnpm/bun, simplified report
142
- if (mode === 'minimal') {
143
- // 1. Remove "Install dependencies for maturity detection" step
144
- updated = updated.replace(
145
- /\s+- name: Install dependencies for maturity detection\s+run: \$\{\{ steps\.detect-pm\.outputs\.install-cmd \}\}\s*\n/,
146
- '\n'
147
- )
148
250
 
149
- // 2. Remove "Detect Project Maturity" step (multiline)
150
- updated = updated.replace(
151
- /\s+- name: Detect Project Maturity\s+id: detect\s+run: \|[\s\S]*?fi\s*\n/,
152
- '\n'
153
- )
251
+ // Minimal mode: use section markers to strip detection, hardcode outputs
252
+ if (mode === 'minimal') {
253
+ // Strip full detection and report sections via markers
254
+ updated = stripSection(updated, 'FULL_DETECTION')
255
+ updated = stripSection(updated, 'FULL_REPORT')
154
256
 
155
- // 3. Hardcode maturity outputs (since we skip detection)
156
- // These sensible defaults allow basic checks to run without expensive detection
257
+ // Hardcode maturity outputs (since we skip detection)
157
258
  updated = updated.replace(
158
259
  /maturity: \$\{\{ steps\.detect\.outputs\.maturity \}\}/,
159
260
  "maturity: 'minimal'"
160
261
  )
161
262
  updated = updated.replace(
162
263
  /source-count: \$\{\{ steps\.detect\.outputs\.source-count \}\}/,
163
- "source-count: '10'"
264
+ "source-count: '0'"
164
265
  )
165
266
  updated = updated.replace(
166
267
  /test-count: \$\{\{ steps\.detect\.outputs\.test-count \}\}/,
167
- "test-count: '1'"
268
+ "test-count: '0'"
168
269
  )
169
270
  updated = updated.replace(
170
271
  /has-deps: \$\{\{ steps\.detect\.outputs\.has-deps \}\}/,
171
- "has-deps: 'true'"
272
+ "has-deps: 'false'"
172
273
  )
173
274
  updated = updated.replace(
174
275
  /has-docs: \$\{\{ steps\.detect\.outputs\.has-docs \}\}/,
@@ -179,30 +280,30 @@ function injectWorkflowMode(workflowContent, mode) {
179
280
  "has-css: 'false'"
180
281
  )
181
282
 
182
- // 4. Remove dev-only gitleaks binary test steps (only relevant for qa-architect itself)
183
- // The restore-keys block has 3 lines after `|`, match all of them to avoid stray lines
184
- updated = updated.replace(
185
- /\s+- name: Cache gitleaks binary for real download test[\s\S]*?restore-keys: \|[^\n]*\n[^\n]*\n[^\n]*\n/,
186
- '\n'
187
- )
188
- updated = updated.replace(
189
- /\s+- name: Run real gitleaks binary verification test[\s\S]*?node tests\/gitleaks-real-binary-test\.js\n/,
190
- '\n'
191
- )
192
-
193
- // 5. Simplify "Display Detection Report" to show only package manager info
194
- updated = updated.replace(
195
- /- name: Display Detection Report\s+run: \|[\s\S]*?echo "Has CSS files:[^"]*"/,
196
- `- name: Display Detection Report
283
+ // Insert simplified detection report after the last setup step
284
+ const minimalReport = ` - name: Display Detection Report
197
285
  run: |
198
286
  echo "📊 Project Detection Results (Minimal Mode)"
199
287
  echo "Package Manager: \${{ steps.detect-pm.outputs.manager }}"
200
288
  echo "Install Command: \${{ steps.detect-pm.outputs.install-cmd }}"
201
289
  echo "Turborepo: \${{ steps.detect-pm.outputs.is-turborepo }}"
202
- echo "Note: Maturity detection skipped in minimal mode for faster CI"`
203
- )
290
+ echo "Mode goal: keep GitHub Actions usage under ~1000 min/month by default"
291
+ echo "Checks: detection-only in CI (tests/security/docs disabled in minimal mode)"
292
+ echo "Use --workflow-standard or --workflow-comprehensive to enable heavier CI checks"`
293
+
294
+ // Insert report before the "Note: Lint/format" comment that follows detect-maturity job
295
+ if (!updated.includes('Display Detection Report')) {
296
+ updated = updated.replace(
297
+ /(\n {2}# Note: Lint\/format jobs REMOVED)/,
298
+ `\n${minimalReport}\n$1`
299
+ )
300
+ }
204
301
  }
205
302
 
303
+ // Strip any remaining section markers from output (belt-and-suspenders)
304
+ // Use [ \t]* (horizontal whitespace only) — \s* would eat newlines and collapse YAML lines
305
+ updated = updated.replace(/[ \t]*# \{\{[A-Z_]+_(BEGIN|END)\}\}\n?/g, '')
306
+
206
307
  return updated
207
308
  }
208
309
 
@@ -241,6 +342,8 @@ function injectMatrix(workflowContent, enableMatrix) {
241
342
 
242
343
  module.exports = {
243
344
  detectExistingWorkflowMode,
345
+ detectExistingMatrix,
244
346
  injectWorkflowMode,
245
347
  injectMatrix,
348
+ stripSection,
246
349
  }