create-qa-architect 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/.editorconfig +12 -0
  2. package/.github/CLAUDE_MD_AUTOMATION.md +248 -0
  3. package/.github/PROGRESSIVE_QUALITY_IMPLEMENTATION.md +408 -0
  4. package/.github/PROGRESSIVE_QUALITY_PROPOSAL.md +443 -0
  5. package/.github/RELEASE_CHECKLIST.md +100 -0
  6. package/.github/dependabot.yml +50 -0
  7. package/.github/git-sync.sh +48 -0
  8. package/.github/workflows/claude-md-validation.yml +82 -0
  9. package/.github/workflows/nightly-gitleaks-verification.yml +176 -0
  10. package/.github/workflows/pnpm-ci.yml.example +53 -0
  11. package/.github/workflows/python-ci.yml.example +69 -0
  12. package/.github/workflows/quality-legacy.yml.backup +165 -0
  13. package/.github/workflows/quality-progressive.yml.example +291 -0
  14. package/.github/workflows/quality.yml +436 -0
  15. package/.github/workflows/release.yml +53 -0
  16. package/.nvmrc +1 -0
  17. package/.prettierignore +14 -0
  18. package/.prettierrc +9 -0
  19. package/.stylelintrc.json +5 -0
  20. package/README.md +212 -0
  21. package/config/.lighthouserc.js +45 -0
  22. package/config/.pre-commit-config.yaml +66 -0
  23. package/config/constants.js +128 -0
  24. package/config/defaults.js +124 -0
  25. package/config/pyproject.toml +124 -0
  26. package/config/quality-config.schema.json +97 -0
  27. package/config/quality-python.yml +89 -0
  28. package/config/requirements-dev.txt +15 -0
  29. package/create-saas-monetization.js +1465 -0
  30. package/eslint.config.cjs +117 -0
  31. package/eslint.config.ts.cjs +99 -0
  32. package/legal/README.md +106 -0
  33. package/legal/copyright.md +76 -0
  34. package/legal/disclaimer.md +146 -0
  35. package/legal/privacy-policy.html +324 -0
  36. package/legal/privacy-policy.md +196 -0
  37. package/legal/terms-of-service.md +224 -0
  38. package/lib/billing-dashboard.html +645 -0
  39. package/lib/config-validator.js +163 -0
  40. package/lib/dependency-monitoring-basic.js +185 -0
  41. package/lib/dependency-monitoring-premium.js +1490 -0
  42. package/lib/error-reporter.js +444 -0
  43. package/lib/interactive/prompt.js +128 -0
  44. package/lib/interactive/questions.js +146 -0
  45. package/lib/license-validator.js +403 -0
  46. package/lib/licensing.js +989 -0
  47. package/lib/package-utils.js +187 -0
  48. package/lib/project-maturity.js +516 -0
  49. package/lib/security-enhancements.js +340 -0
  50. package/lib/setup-enhancements.js +317 -0
  51. package/lib/smart-strategy-generator.js +344 -0
  52. package/lib/telemetry.js +323 -0
  53. package/lib/template-loader.js +252 -0
  54. package/lib/typescript-config-generator.js +210 -0
  55. package/lib/ui-helpers.js +74 -0
  56. package/lib/validation/base-validator.js +174 -0
  57. package/lib/validation/cache-manager.js +158 -0
  58. package/lib/validation/config-security.js +741 -0
  59. package/lib/validation/documentation.js +326 -0
  60. package/lib/validation/index.js +186 -0
  61. package/lib/validation/validation-factory.js +153 -0
  62. package/lib/validation/workflow-validation.js +172 -0
  63. package/lib/yaml-utils.js +120 -0
  64. package/marketing/beta-user-email-campaign.md +372 -0
  65. package/marketing/landing-page.html +721 -0
  66. package/package.json +165 -0
  67. package/setup.js +2076 -0
@@ -0,0 +1,187 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * Package.json merge utilities
5
+ * Shared between setup script and tests to avoid duplication
6
+ */
7
+
8
+ /**
9
+ * Merge scripts into package.json, preserving existing ones
10
+ * @param {Object} initialScripts - Existing scripts object
11
+ * @param {Object} defaultScripts - Default scripts to add
12
+ * @returns {Object} Merged scripts object
13
+ */
14
+ function mergeScripts(initialScripts = {}, defaultScripts) {
15
+ const scripts = { ...initialScripts }
16
+ Object.entries(defaultScripts).forEach(([name, command]) => {
17
+ if (!scripts[name]) {
18
+ scripts[name] = command
19
+ }
20
+ })
21
+
22
+ // Ensure husky command is present in prepare script
23
+ const prepareScript = scripts.prepare
24
+ if (!prepareScript) {
25
+ scripts.prepare = 'husky'
26
+ } else if (prepareScript.includes('husky install')) {
27
+ scripts.prepare = prepareScript.replace(/husky install/g, 'husky')
28
+ } else if (!prepareScript.includes('husky')) {
29
+ scripts.prepare = `${prepareScript} && husky`
30
+ }
31
+
32
+ return scripts
33
+ }
34
+
35
+ /**
36
+ * Merge devDependencies into package.json, preserving existing ones
37
+ * @param {Object} initialDevDeps - Existing devDependencies object
38
+ * @param {Object} defaultDevDeps - Default devDependencies to add
39
+ * @returns {Object} Merged devDependencies object
40
+ */
41
+ function mergeDevDependencies(initialDevDeps = {}, defaultDevDeps) {
42
+ const devDeps = { ...initialDevDeps }
43
+ Object.entries(defaultDevDeps).forEach(([dependency, version]) => {
44
+ if (!devDeps[dependency]) {
45
+ devDeps[dependency] = version
46
+ }
47
+ })
48
+ return devDeps
49
+ }
50
+
51
+ /**
52
+ * Merge lint-staged configuration, preserving existing patterns
53
+ * @param {Object} existing - Existing lint-staged config
54
+ * @param {Object} defaults - Default lint-staged config
55
+ * @param {Object} options - Merge options
56
+ * @param {Function} patternChecker - Function to check if a pattern matches certain criteria
57
+ * @returns {Object} Merged lint-staged config
58
+ */
59
+ function mergeLintStaged(
60
+ existing = {},
61
+ defaults,
62
+ options = {},
63
+ patternChecker = null
64
+ ) {
65
+ const merged = { ...existing }
66
+ const stylelintTargets = options.stylelintTargets || []
67
+ const stylelintTargetSet = new Set(stylelintTargets)
68
+
69
+ // Check if existing config has CSS patterns
70
+ const hasExistingCssPatterns =
71
+ patternChecker && Object.keys(existing).some(patternChecker)
72
+
73
+ Object.entries(defaults).forEach(([pattern, commands]) => {
74
+ const isStylelintPattern = stylelintTargetSet.has(pattern)
75
+ if (isStylelintPattern && hasExistingCssPatterns) {
76
+ return // Skip stylelint patterns if existing CSS patterns exist
77
+ }
78
+
79
+ if (!merged[pattern]) {
80
+ merged[pattern] = commands
81
+ return
82
+ }
83
+
84
+ // Merge commands for existing patterns
85
+
86
+ const existingCommands = Array.isArray(merged[pattern])
87
+ ? [...merged[pattern]]
88
+ : [merged[pattern]]
89
+
90
+ const newCommands = [...existingCommands]
91
+ commands.forEach(command => {
92
+ if (!newCommands.includes(command)) {
93
+ newCommands.push(command)
94
+ }
95
+ })
96
+
97
+ merged[pattern] = newCommands
98
+ })
99
+
100
+ return merged
101
+ }
102
+
103
+ /**
104
+ * Detect which package manager is being used in the project
105
+ * @param {string} projectPath - Path to the project directory
106
+ * @returns {string} Package manager name: 'pnpm', 'yarn', 'bun', or 'npm'
107
+ */
108
+ function detectPackageManager(projectPath = process.cwd()) {
109
+ const fs = require('fs')
110
+ const path = require('path')
111
+
112
+ // Check for lockfiles in order of preference
113
+ if (fs.existsSync(path.join(projectPath, 'pnpm-lock.yaml'))) {
114
+ return 'pnpm'
115
+ }
116
+ if (fs.existsSync(path.join(projectPath, 'yarn.lock'))) {
117
+ return 'yarn'
118
+ }
119
+ if (fs.existsSync(path.join(projectPath, 'bun.lockb'))) {
120
+ return 'bun'
121
+ }
122
+ if (fs.existsSync(path.join(projectPath, 'package-lock.json'))) {
123
+ return 'npm'
124
+ }
125
+
126
+ // Check package.json for packageManager field (Corepack)
127
+ const packageJsonPath = path.join(projectPath, 'package.json')
128
+ if (fs.existsSync(packageJsonPath)) {
129
+ try {
130
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
131
+ if (packageJson.packageManager) {
132
+ // Format: "pnpm@8.0.0" or "yarn@3.0.0"
133
+ const pmName = packageJson.packageManager.split('@')[0]
134
+ if (['pnpm', 'yarn', 'bun', 'npm'].includes(pmName)) {
135
+ return pmName
136
+ }
137
+ }
138
+ } catch {
139
+ // Ignore parse errors
140
+ }
141
+ }
142
+
143
+ // Default to npm if no lockfile found
144
+ return 'npm'
145
+ }
146
+
147
+ /**
148
+ * Get install command for detected package manager
149
+ * @param {string} packageManager - Package manager name
150
+ * @param {boolean} frozen - Use frozen/immutable lockfile (CI mode)
151
+ * @returns {string} Install command
152
+ */
153
+ function getInstallCommand(packageManager, frozen = true) {
154
+ const commands = {
155
+ pnpm: frozen ? 'pnpm install --frozen-lockfile' : 'pnpm install',
156
+ yarn: frozen ? 'yarn install --frozen-lockfile' : 'yarn install',
157
+ bun: frozen ? 'bun install --frozen-lockfile' : 'bun install',
158
+ npm: frozen ? 'npm ci' : 'npm install',
159
+ }
160
+
161
+ return commands[packageManager] || 'npm install'
162
+ }
163
+
164
+ /**
165
+ * Get audit command for detected package manager
166
+ * @param {string} packageManager - Package manager name
167
+ * @returns {string} Audit command
168
+ */
169
+ function getAuditCommand(packageManager) {
170
+ const commands = {
171
+ pnpm: 'pnpm audit',
172
+ yarn: 'yarn audit',
173
+ bun: 'bun audit', // Bun has audit support
174
+ npm: 'npm audit',
175
+ }
176
+
177
+ return commands[packageManager] || 'npm audit'
178
+ }
179
+
180
+ module.exports = {
181
+ mergeScripts,
182
+ mergeDevDependencies,
183
+ mergeLintStaged,
184
+ detectPackageManager,
185
+ getInstallCommand,
186
+ getAuditCommand,
187
+ }
@@ -0,0 +1,516 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Project Maturity Detection for Progressive Quality Automation
5
+ *
6
+ * Automatically detects project maturity level and recommends appropriate quality checks.
7
+ *
8
+ * Maturity Levels:
9
+ * - minimal: Just package.json, maybe README (< 5 files)
10
+ * - bootstrap: Some source files, no tests yet (1-2 source files)
11
+ * - development: Active development with tests (≄ 3 source files, has tests)
12
+ * - production-ready: Full project (≄ 10 source files, tests, docs)
13
+ */
14
+
15
+ const fs = require('fs')
16
+ const path = require('path')
17
+ const {
18
+ MATURITY_THRESHOLDS,
19
+ SCAN_LIMITS,
20
+ EXCLUDE_DIRECTORIES,
21
+ } = require('../config/constants')
22
+
23
+ /**
24
+ * Maturity level definitions with check recommendations
25
+ */
26
+ const MATURITY_LEVELS = {
27
+ minimal: {
28
+ name: 'Minimal',
29
+ description: 'Just getting started - basic setup only',
30
+ checks: {
31
+ required: ['prettier'],
32
+ optional: [],
33
+ disabled: [
34
+ 'eslint',
35
+ 'stylelint',
36
+ 'tests',
37
+ 'coverage',
38
+ 'security-audit',
39
+ 'documentation',
40
+ ],
41
+ },
42
+ message:
43
+ '⚔ Minimal project - only basic formatting checks enabled. Add source files to enable linting.',
44
+ },
45
+
46
+ bootstrap: {
47
+ name: 'Bootstrap',
48
+ description: 'Early development - has some source files',
49
+ checks: {
50
+ required: ['prettier', 'eslint'],
51
+ optional: ['stylelint'],
52
+ disabled: ['tests', 'coverage', 'security-audit', 'documentation'],
53
+ },
54
+ message:
55
+ 'šŸš€ Bootstrap project - linting enabled. Add tests to enable test coverage checks.',
56
+ },
57
+
58
+ development: {
59
+ name: 'Development',
60
+ description: 'Active development - has source files and tests',
61
+ checks: {
62
+ required: ['prettier', 'eslint', 'stylelint', 'tests'],
63
+ optional: ['coverage', 'security-audit'],
64
+ disabled: ['documentation'],
65
+ },
66
+ message:
67
+ 'šŸ”Ø Development project - most checks enabled. Add documentation to enable doc validation.',
68
+ },
69
+
70
+ 'production-ready': {
71
+ name: 'Production Ready',
72
+ description: 'Mature project - full quality automation',
73
+ checks: {
74
+ required: [
75
+ 'prettier',
76
+ 'eslint',
77
+ 'stylelint',
78
+ 'tests',
79
+ 'coverage',
80
+ 'security-audit',
81
+ 'documentation',
82
+ ],
83
+ optional: ['lighthouse'],
84
+ disabled: [],
85
+ },
86
+ message: 'āœ… Production-ready project - all quality checks enabled.',
87
+ },
88
+ }
89
+
90
+ class ProjectMaturityDetector {
91
+ constructor(options = {}) {
92
+ this.verbose = options.verbose || false
93
+ this.projectPath = options.projectPath || process.cwd()
94
+ }
95
+
96
+ /**
97
+ * Detect project maturity level
98
+ * @returns {string} Maturity level: 'minimal', 'bootstrap', 'development', or 'production-ready'
99
+ */
100
+ detect() {
101
+ const stats = this.analyzeProject()
102
+
103
+ if (this.verbose) {
104
+ console.log('šŸ“Š Project Analysis:')
105
+ console.log(` Source files: ${stats.totalSourceFiles}`)
106
+ console.log(` Test files: ${stats.testFiles}`)
107
+ console.log(` Has documentation: ${stats.hasDocumentation}`)
108
+ console.log(` Has dependencies: ${stats.hasDependencies}`)
109
+ console.log(` Has CSS files: ${stats.hasCssFiles}`)
110
+ }
111
+
112
+ // Determine maturity level based on project characteristics
113
+ let maturity = 'minimal'
114
+
115
+ if (stats.totalSourceFiles === 0) {
116
+ maturity = 'minimal'
117
+ } else if (
118
+ stats.totalSourceFiles < MATURITY_THRESHOLDS.MIN_BOOTSTRAP_FILES &&
119
+ stats.testFiles === 0
120
+ ) {
121
+ maturity = 'bootstrap'
122
+ } else if (
123
+ stats.testFiles > 0 &&
124
+ stats.totalSourceFiles >= MATURITY_THRESHOLDS.MIN_BOOTSTRAP_FILES &&
125
+ stats.totalSourceFiles < MATURITY_THRESHOLDS.MIN_PRODUCTION_FILES
126
+ ) {
127
+ maturity = 'development'
128
+ } else if (
129
+ stats.testFiles >= MATURITY_THRESHOLDS.MIN_PRODUCTION_TESTS &&
130
+ stats.totalSourceFiles >= MATURITY_THRESHOLDS.MIN_PRODUCTION_FILES &&
131
+ (stats.hasDocumentation || stats.hasDependencies)
132
+ ) {
133
+ maturity = 'production-ready'
134
+ } else if (
135
+ stats.totalSourceFiles >= MATURITY_THRESHOLDS.MIN_BOOTSTRAP_FILES
136
+ ) {
137
+ maturity = 'development'
138
+ }
139
+
140
+ if (this.verbose) {
141
+ const level = MATURITY_LEVELS[maturity]
142
+ console.log(`\n${level.message}`)
143
+ }
144
+
145
+ return maturity
146
+ }
147
+
148
+ /**
149
+ * Get detailed project statistics
150
+ * @returns {Object} Project statistics
151
+ */
152
+ analyzeProject() {
153
+ return {
154
+ totalSourceFiles: this.countSourceFiles(),
155
+ testFiles: this.countTestFiles(),
156
+ hasDocumentation: this.hasDocumentation(),
157
+ hasTests: this.hasTests(),
158
+ hasDependencies: this.hasDependencies(),
159
+ hasCssFiles: this.hasCssFiles(),
160
+ packageJsonExists: this.packageJsonExists(),
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Count JavaScript/TypeScript source files (excluding tests)
166
+ * @returns {number} Number of source files
167
+ */
168
+ countSourceFiles() {
169
+ const extensions = ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs']
170
+ const testPatterns = ['.test.', '.spec.', '__tests__', '__mocks__']
171
+
172
+ return this.countFilesRecursive(this.projectPath, {
173
+ extensions,
174
+ excludeDirs: EXCLUDE_DIRECTORIES.PROJECT_MATURITY,
175
+ excludePatterns: testPatterns,
176
+ maxDepth: 5,
177
+ })
178
+ }
179
+
180
+ /**
181
+ * Count test files
182
+ * @returns {number} Number of test files
183
+ */
184
+ countTestFiles() {
185
+ const extensions = ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs']
186
+ const testPatterns = ['.test.', '.spec.', '__tests__']
187
+
188
+ return this.countFilesRecursive(this.projectPath, {
189
+ extensions,
190
+ excludeDirs: EXCLUDE_DIRECTORIES.PROJECT_MATURITY,
191
+ includePatterns: testPatterns,
192
+ maxDepth: 5,
193
+ })
194
+ }
195
+
196
+ /**
197
+ * Check if project has documentation
198
+ * @returns {boolean} True if documentation exists
199
+ */
200
+ hasDocumentation() {
201
+ const docIndicators = [
202
+ 'docs',
203
+ 'documentation',
204
+ 'doc',
205
+ '.github/CONTRIBUTING.md',
206
+ '.github/CODE_OF_CONDUCT.md',
207
+ ]
208
+
209
+ // Check for docs directory
210
+ for (const indicator of docIndicators) {
211
+ const docPath = path.join(this.projectPath, indicator)
212
+ if (fs.existsSync(docPath)) {
213
+ return true
214
+ }
215
+ }
216
+
217
+ // Check for substantial README (> MIN_LINES_FOR_DOCS lines)
218
+ const readmePath = path.join(this.projectPath, 'README.md')
219
+ if (fs.existsSync(readmePath)) {
220
+ const content = fs.readFileSync(readmePath, 'utf8')
221
+ const lines = content.split('\n').length
222
+ if (lines > MATURITY_THRESHOLDS.README_MIN_LINES_FOR_DOCS) {
223
+ return true
224
+ }
225
+ }
226
+
227
+ return false
228
+ }
229
+
230
+ /**
231
+ * Check if project has tests directory or test files
232
+ * @returns {boolean} True if tests exist
233
+ */
234
+ hasTests() {
235
+ const testDirs = [
236
+ 'test',
237
+ 'tests',
238
+ '__tests__',
239
+ 'spec',
240
+ 'specs',
241
+ '__specs__',
242
+ ]
243
+
244
+ for (const dir of testDirs) {
245
+ const testPath = path.join(this.projectPath, dir)
246
+ if (fs.existsSync(testPath)) {
247
+ return true
248
+ }
249
+ }
250
+
251
+ return this.countTestFiles() > 0
252
+ }
253
+
254
+ /**
255
+ * Check if package.json has dependencies
256
+ * @returns {boolean} True if dependencies exist
257
+ */
258
+ hasDependencies() {
259
+ const packageJsonPath = path.join(this.projectPath, 'package.json')
260
+ if (!fs.existsSync(packageJsonPath)) {
261
+ return false
262
+ }
263
+
264
+ try {
265
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
266
+ const deps = packageJson.dependencies || {}
267
+ const devDeps = packageJson.devDependencies || {}
268
+ return Object.keys(deps).length > 0 || Object.keys(devDeps).length > 0
269
+ } catch {
270
+ return false
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Check if project has CSS/SCSS files
276
+ * @returns {boolean} True if CSS files exist
277
+ */
278
+ hasCssFiles() {
279
+ const extensions = ['.css', '.scss', '.sass', '.less', '.pcss']
280
+
281
+ return (
282
+ this.countFilesRecursive(this.projectPath, {
283
+ extensions,
284
+ excludeDirs: EXCLUDE_DIRECTORIES.PROJECT_MATURITY,
285
+ maxDepth: 4,
286
+ }) > 0
287
+ )
288
+ }
289
+
290
+ /**
291
+ * Check if package.json exists
292
+ * @returns {boolean} True if package.json exists
293
+ */
294
+ packageJsonExists() {
295
+ return fs.existsSync(path.join(this.projectPath, 'package.json'))
296
+ }
297
+
298
+ /**
299
+ * Recursively counts files in a directory tree with advanced filtering
300
+ *
301
+ * Provides flexible file counting with multiple filter options for project
302
+ * analysis. Supports extension filtering, directory exclusion, pattern
303
+ * matching, and depth limiting. Skips symlinks for safety.
304
+ *
305
+ * Algorithm:
306
+ * 1. Read directory entries (skip if depth > maxDepth)
307
+ * 2. Skip excluded directories and symbolic links
308
+ * 3. For subdirectories: recursively count (depth + 1)
309
+ * 4. For files: apply all filters (extension, include, exclude)
310
+ * 5. Return total count across all matching files
311
+ *
312
+ * Filter priority:
313
+ * 1. Extension filter (if specified, must match)
314
+ * 2. Include patterns (if specified, must match at least one)
315
+ * 3. Exclude patterns (if any match, file is excluded)
316
+ *
317
+ * @param {string} dir - Directory to search (absolute path)
318
+ * @param {Object} [options={}] - Search and filter options
319
+ * @param {string[]} [options.extensions=[]] - File extensions to count (e.g., ['.js', '.ts'])
320
+ * @param {string[]} [options.excludeDirs=[]] - Directory names to skip (e.g., ['node_modules'])
321
+ * @param {string[]} [options.includePatterns=[]] - Filename patterns to include (substring match)
322
+ * @param {string[]} [options.excludePatterns=[]] - Filename patterns to exclude (substring match)
323
+ * @param {number} [options.maxDepth=5] - Maximum recursion depth
324
+ * @param {number} [options.currentDepth=0] - Current depth (for internal recursion)
325
+ * @returns {number} Count of files matching all filters
326
+ *
327
+ * @example
328
+ * // Count all JavaScript files
329
+ * const jsCount = maturity.countFilesRecursive('./src', {
330
+ * extensions: ['.js', '.jsx']
331
+ * })
332
+ *
333
+ * @example
334
+ * // Count test files in src/, excluding node_modules
335
+ * const testCount = maturity.countFilesRecursive('./src', {
336
+ * includePatterns: ['.test.', '.spec.'],
337
+ * excludeDirs: ['node_modules', 'dist'],
338
+ * maxDepth: 10
339
+ * })
340
+ */
341
+ countFilesRecursive(dir, options = {}) {
342
+ const {
343
+ extensions = [],
344
+ excludeDirs = [],
345
+ includePatterns = [],
346
+ excludePatterns = [],
347
+ maxDepth = SCAN_LIMITS.FILE_COUNT_MAX_DEPTH,
348
+ currentDepth = 0,
349
+ } = options
350
+
351
+ if (currentDepth > maxDepth) {
352
+ return 0
353
+ }
354
+
355
+ let count = 0
356
+
357
+ try {
358
+ const entries = fs.readdirSync(dir, { withFileTypes: true })
359
+
360
+ for (const entry of entries) {
361
+ if (entry.isSymbolicLink()) {
362
+ continue
363
+ }
364
+
365
+ const fullPath = path.join(dir, entry.name)
366
+
367
+ if (entry.isDirectory()) {
368
+ if (excludeDirs.includes(entry.name)) {
369
+ continue
370
+ }
371
+
372
+ count += this.countFilesRecursive(fullPath, {
373
+ ...options,
374
+ currentDepth: currentDepth + 1,
375
+ })
376
+ } else if (entry.isFile()) {
377
+ const ext = path.extname(entry.name).toLowerCase()
378
+
379
+ // Check extension
380
+ if (extensions.length > 0 && !extensions.includes(ext)) {
381
+ continue
382
+ }
383
+
384
+ // Check include patterns
385
+ if (includePatterns.length > 0) {
386
+ const matches = includePatterns.some(pattern =>
387
+ entry.name.includes(pattern)
388
+ )
389
+ if (!matches) {
390
+ continue
391
+ }
392
+ }
393
+
394
+ // Check exclude patterns
395
+ if (excludePatterns.length > 0) {
396
+ const excluded = excludePatterns.some(pattern =>
397
+ entry.name.includes(pattern)
398
+ )
399
+ if (excluded) {
400
+ continue
401
+ }
402
+ }
403
+
404
+ count++
405
+ }
406
+ }
407
+ } catch (error) {
408
+ // Silently ignore permission errors
409
+ if (this.verbose) {
410
+ console.warn(`Warning: Could not read directory ${dir}:`, error.message)
411
+ }
412
+ }
413
+
414
+ return count
415
+ }
416
+
417
+ /**
418
+ * Get maturity level details
419
+ * @param {string} maturity - Maturity level
420
+ * @returns {Object} Maturity level details
421
+ */
422
+ getMaturityDetails(maturity) {
423
+ return MATURITY_LEVELS[maturity] || MATURITY_LEVELS.minimal
424
+ }
425
+
426
+ /**
427
+ * Get recommended checks for maturity level
428
+ * @param {string} maturity - Maturity level
429
+ * @returns {Object} Recommended checks
430
+ */
431
+ getRecommendedChecks(maturity) {
432
+ const level = this.getMaturityDetails(maturity)
433
+ return level.checks
434
+ }
435
+
436
+ /**
437
+ * Generate GitHub Actions outputs for maturity detection
438
+ * @returns {string} GitHub Actions output format
439
+ */
440
+ generateGitHubActionsOutput() {
441
+ const maturity = this.detect()
442
+ const stats = this.analyzeProject()
443
+ const checks = this.getRecommendedChecks(maturity)
444
+
445
+ return {
446
+ maturity,
447
+ sourceCount: stats.totalSourceFiles,
448
+ testCount: stats.testFiles,
449
+ hasDeps: stats.hasDependencies,
450
+ hasDocs: stats.hasDocumentation,
451
+ hasCss: stats.hasCssFiles,
452
+ requiredChecks: checks.required.join(','),
453
+ optionalChecks: checks.optional.join(','),
454
+ disabledChecks: checks.disabled.join(','),
455
+ }
456
+ }
457
+
458
+ /**
459
+ * Print human-readable maturity report
460
+ */
461
+ printReport() {
462
+ const maturity = this.detect()
463
+ const stats = this.analyzeProject()
464
+ const level = this.getMaturityDetails(maturity)
465
+
466
+ console.log('\nšŸ“Š Project Maturity Report\n')
467
+ console.log(`Maturity Level: ${level.name}`)
468
+ console.log(`Description: ${level.description}\n`)
469
+
470
+ console.log('Project Statistics:')
471
+ console.log(` • Source files: ${stats.totalSourceFiles}`)
472
+ console.log(` • Test files: ${stats.testFiles}`)
473
+ console.log(` • Documentation: ${stats.hasDocumentation ? 'Yes' : 'No'}`)
474
+ console.log(` • Dependencies: ${stats.hasDependencies ? 'Yes' : 'No'}`)
475
+ console.log(` • CSS files: ${stats.hasCssFiles ? 'Yes' : 'No'}\n`)
476
+
477
+ console.log('Quality Checks:')
478
+ console.log(` āœ… Required: ${level.checks.required.join(', ')}`)
479
+ if (level.checks.optional.length > 0) {
480
+ console.log(` šŸ”µ Optional: ${level.checks.optional.join(', ')}`)
481
+ }
482
+ if (level.checks.disabled.length > 0) {
483
+ console.log(` ā­ļø Disabled: ${level.checks.disabled.join(', ')}`)
484
+ }
485
+
486
+ console.log(`\n${level.message}\n`)
487
+ }
488
+ }
489
+
490
+ // CLI usage
491
+ if (require.main === module) {
492
+ const args = process.argv.slice(2)
493
+ const verbose = args.includes('--verbose') || args.includes('-v')
494
+ const githubActions = args.includes('--github-actions')
495
+
496
+ const detector = new ProjectMaturityDetector({ verbose })
497
+
498
+ if (githubActions) {
499
+ // Output for GitHub Actions
500
+ const output = detector.generateGitHubActionsOutput()
501
+ console.log(`maturity=${output.maturity}`)
502
+ console.log(`source-count=${output.sourceCount}`)
503
+ console.log(`test-count=${output.testCount}`)
504
+ console.log(`has-deps=${output.hasDeps}`)
505
+ console.log(`has-docs=${output.hasDocs}`)
506
+ console.log(`has-css=${output.hasCss}`)
507
+ console.log(`required-checks=${output.requiredChecks}`)
508
+ console.log(`optional-checks=${output.optionalChecks}`)
509
+ console.log(`disabled-checks=${output.disabledChecks}`)
510
+ } else {
511
+ // Human-readable report
512
+ detector.printReport()
513
+ }
514
+ }
515
+
516
+ module.exports = { ProjectMaturityDetector, MATURITY_LEVELS }