@sun-asterisk/sunlint 1.2.2 → 1.3.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 (64) hide show
  1. package/CHANGELOG.md +40 -1
  2. package/CONTRIBUTING.md +533 -70
  3. package/README.md +16 -2
  4. package/config/engines/engines-enhanced.json +86 -0
  5. package/config/engines/semantic-config.json +114 -0
  6. package/config/eslint-rule-mapping.json +50 -38
  7. package/config/rules/enhanced-rules-registry.json +2503 -0
  8. package/config/rules/rules-registry-generated.json +785 -837
  9. package/core/adapters/sunlint-rule-adapter.js +25 -30
  10. package/core/analysis-orchestrator.js +42 -2
  11. package/core/categories.js +52 -0
  12. package/core/category-constants.js +39 -0
  13. package/core/cli-action-handler.js +32 -5
  14. package/core/config-manager.js +111 -0
  15. package/core/config-merger.js +61 -0
  16. package/core/constants/categories.js +168 -0
  17. package/core/constants/defaults.js +165 -0
  18. package/core/constants/engines.js +185 -0
  19. package/core/constants/index.js +30 -0
  20. package/core/constants/rules.js +215 -0
  21. package/core/file-targeting-service.js +128 -7
  22. package/core/interfaces/rule-plugin.interface.js +207 -0
  23. package/core/plugin-manager.js +448 -0
  24. package/core/rule-selection-service.js +42 -15
  25. package/core/semantic-engine.js +560 -0
  26. package/core/semantic-rule-base.js +433 -0
  27. package/core/unified-rule-registry.js +484 -0
  28. package/docs/CONSTANTS-ARCHITECTURE.md +288 -0
  29. package/engines/core/base-engine.js +249 -0
  30. package/engines/engine-factory.js +275 -0
  31. package/engines/eslint-engine.js +171 -19
  32. package/engines/heuristic-engine.js +511 -78
  33. package/integrations/eslint/plugin/index.js +27 -27
  34. package/package.json +10 -6
  35. package/rules/common/C003_no_vague_abbreviations/analyzer.js +1 -1
  36. package/rules/common/C029_catch_block_logging/analyzer.js +17 -5
  37. package/rules/common/C047_no_duplicate_retry_logic/c047-semantic-rule.js +278 -0
  38. package/rules/common/C047_no_duplicate_retry_logic/symbol-analyzer-enhanced.js +968 -0
  39. package/rules/common/C047_no_duplicate_retry_logic/symbol-config.json +71 -0
  40. package/rules/index.js +7 -0
  41. package/scripts/category-manager.js +150 -0
  42. package/scripts/generate-rules-registry.js +88 -0
  43. package/scripts/migrate-rule-registry.js +157 -0
  44. package/scripts/validate-system.js +48 -0
  45. package/.sunlint.json +0 -35
  46. package/config/README.md +0 -88
  47. package/config/engines/eslint-rule-mapping.json +0 -74
  48. package/config/schemas/sunlint-schema.json +0 -0
  49. package/config/testing/test-s005-working.ts +0 -22
  50. package/core/multi-rule-runner.js +0 -0
  51. package/engines/tree-sitter-parser.js +0 -0
  52. package/engines/universal-ast-engine.js +0 -0
  53. package/rules/common/C029_catch_block_logging/analyzer-backup.js +0 -426
  54. package/rules/common/C029_catch_block_logging/analyzer-fixed.js +0 -130
  55. package/rules/common/C029_catch_block_logging/analyzer-multi-tech.js +0 -487
  56. package/rules/common/C029_catch_block_logging/analyzer-simple.js +0 -110
  57. package/rules/common/C029_catch_block_logging/ast-analyzer-backup.js +0 -441
  58. package/rules/common/C029_catch_block_logging/ast-analyzer-new.js +0 -127
  59. package/rules/common/C029_catch_block_logging/ast-analyzer.js +0 -133
  60. package/rules/common/C029_catch_block_logging/cfg-analyzer.js +0 -408
  61. package/rules/common/C029_catch_block_logging/dataflow-analyzer.js +0 -454
  62. package/rules/common/C029_catch_block_logging/multi-language-ast-engine.js +0 -700
  63. package/rules/common/C029_catch_block_logging/pattern-learning-analyzer.js +0 -568
  64. package/rules/common/C029_catch_block_logging/semantic-analyzer.js +0 -459
@@ -1,4 +1,5 @@
1
1
  const { SimpleRuleParser } = require('../../rules/parser/rule-parser-simple');
2
+ const { CATEGORY_PRINCIPLE_MAP, getCategoryPrinciples } = require('../constants/categories');
2
3
  const fs = require('fs');
3
4
  const path = require('path');
4
5
 
@@ -139,7 +140,23 @@ class SunlintRuleAdapter {
139
140
  return null;
140
141
  }
141
142
 
142
- // Try registry first
143
+ // Normalize rule ID to uppercase for consistency
144
+ const normalizedRuleId = ruleId.toUpperCase();
145
+
146
+ // Try registry first with normalized ID
147
+ if (this.registryCache && this.registryCache[normalizedRuleId]) {
148
+ return this.normalizeRule({
149
+ id: normalizedRuleId,
150
+ ...this.registryCache[normalizedRuleId]
151
+ });
152
+ }
153
+
154
+ // Try cache with normalized ID
155
+ if (this.rulesCache.has(normalizedRuleId)) {
156
+ return this.rulesCache.get(normalizedRuleId);
157
+ }
158
+
159
+ // Also try original case for backward compatibility
143
160
  if (this.registryCache && this.registryCache[ruleId]) {
144
161
  return this.normalizeRule({
145
162
  id: ruleId,
@@ -147,7 +164,6 @@ class SunlintRuleAdapter {
147
164
  });
148
165
  }
149
166
 
150
- // Try cache
151
167
  if (this.rulesCache.has(ruleId)) {
152
168
  return this.rulesCache.get(ruleId);
153
169
  }
@@ -225,20 +241,9 @@ class SunlintRuleAdapter {
225
241
  * Based on actual principles in the rule catalog
226
242
  */
227
243
  getRulesByStandardCategory(category) {
228
- const categoryPrincipleMap = {
229
- 'security': ['SECURITY'],
230
- 'quality': ['CODE_QUALITY'],
231
- 'performance': ['PERFORMANCE'],
232
- 'maintainability': ['MAINTAINABILITY'],
233
- 'testability': ['TESTABILITY'],
234
- 'reliability': ['RELIABILITY'],
235
- 'design': ['DESIGN_PATTERNS'],
236
- 'integration': ['INTEGRATION'],
237
- 'usability': ['USABILITY']
238
- };
239
-
240
- const principles = categoryPrincipleMap[category.toLowerCase()];
241
- if (!principles) {
244
+ // Use centralized mapping instead of hardcoded
245
+ const principles = getCategoryPrinciples(category);
246
+ if (!principles || principles.length === 0) {
242
247
  console.warn(`⚠️ Unknown category: ${category}`);
243
248
  return [];
244
249
  }
@@ -276,20 +281,10 @@ class SunlintRuleAdapter {
276
281
  */
277
282
  getStandardCategoryRules(category) {
278
283
  const coreRules = this.getCoreRules();
279
- const categoryPrincipleMap = {
280
- 'security': ['SECURITY'],
281
- 'quality': ['CODE_QUALITY'],
282
- 'performance': ['PERFORMANCE'],
283
- 'maintainability': ['MAINTAINABILITY'],
284
- 'testability': ['TESTABILITY'],
285
- 'reliability': ['RELIABILITY'],
286
- 'design': ['DESIGN_PATTERNS'],
287
- 'integration': ['INTEGRATION'],
288
- 'usability': ['USABILITY']
289
- };
290
-
291
- const principles = categoryPrincipleMap[category.toLowerCase()];
292
- if (!principles) {
284
+
285
+ // Use centralized mapping
286
+ const principles = getCategoryPrinciples(category);
287
+ if (!principles || principles.length === 0) {
293
288
  console.warn(`⚠️ Unknown standard category: ${category}`);
294
289
  return [];
295
290
  }
@@ -233,6 +233,8 @@ class AnalysisOrchestrator {
233
233
  */
234
234
  groupRulesByEngine(rulesToRun, config) {
235
235
  const groups = new Map();
236
+ const skippedRules = [];
237
+
236
238
  if (config.verbose) {
237
239
  console.log(`📊 [Orchestrator] Grouping ${rulesToRun.length} rules by engine...`);
238
240
  }
@@ -249,6 +251,16 @@ class AnalysisOrchestrator {
249
251
  }
250
252
 
251
253
  const selectedEngine = this.selectBestEngine(enginePreference, rule, config);
254
+
255
+ // If rule is skipped (no engine supports it when specific engine requested)
256
+ if (selectedEngine === null) {
257
+ skippedRules.push(rule);
258
+ if (config.verbose) {
259
+ console.log(`⚠️ [Orchestrator] Skipped rule ${rule.id} - not supported by requested engine`);
260
+ }
261
+ continue;
262
+ }
263
+
252
264
  if (config.verbose) {
253
265
  console.log(`✅ [Orchestrator] Selected engine for ${rule.id}: ${selectedEngine}`);
254
266
  }
@@ -259,6 +271,12 @@ class AnalysisOrchestrator {
259
271
  groups.get(selectedEngine).push(rule);
260
272
  }
261
273
 
274
+ // Report skipped rules if any
275
+ if (skippedRules.length > 0) {
276
+ const skippedRuleIds = skippedRules.map(r => r.id).join(', ');
277
+ console.warn(`⚠️ [Orchestrator] Skipped ${skippedRules.length} rules not supported by requested engine: ${skippedRuleIds}`);
278
+ }
279
+
262
280
  if (config.verbose) {
263
281
  console.log(`📋 [Orchestrator] Final groups:`, Array.from(groups.entries()).map(([k, v]) => [k, v.length]));
264
282
  }
@@ -273,6 +291,16 @@ class AnalysisOrchestrator {
273
291
  * @returns {string[]} Array of engine names in preference order
274
292
  */
275
293
  getEnginePreference(rule, config) {
294
+ // If user specified a specific engine via --engine option, use only that engine
295
+ if (config.requestedEngine) {
296
+ return [config.requestedEngine];
297
+ }
298
+
299
+ // Special preference for C047: Always use semantic analysis (heuristic engine)
300
+ if (rule.id === 'C047') {
301
+ return ['heuristic', 'openai'];
302
+ }
303
+
276
304
  // Check config-level rule preferences
277
305
  const ruleConfig = config.rules?.[rule.id];
278
306
  if (ruleConfig?.engines) {
@@ -306,13 +334,17 @@ class AnalysisOrchestrator {
306
334
  * @param {string[]} preferences - Engine preferences in order
307
335
  * @param {Object} rule - Rule object
308
336
  * @param {Object} config - Configuration with verbose flag
309
- * @returns {string} Selected engine name
337
+ * @returns {string|null} Selected engine name or null if no engine supports the rule
310
338
  */
311
339
  selectBestEngine(preferences, rule, config) {
312
340
  if (config.verbose) {
313
341
  console.log(`🎯 [Orchestrator] Selecting engine for rule ${rule.id}, preferences:`, preferences);
314
342
  }
315
343
 
344
+ // If user specified a specific engine (--engine=eslint), only use that engine
345
+ // Don't fallback to other engines to maintain consistency
346
+ const isSpecificEngineRequested = config.requestedEngine && preferences.length === 1;
347
+
316
348
  for (const engineName of preferences) {
317
349
  const engine = this.engines.get(engineName);
318
350
  if (config.verbose) {
@@ -327,11 +359,19 @@ class AnalysisOrchestrator {
327
359
  }
328
360
  }
329
361
 
362
+ // If specific engine is requested and it doesn't support the rule, skip fallback
363
+ if (isSpecificEngineRequested) {
364
+ if (config.verbose) {
365
+ console.log(`⚠️ [Orchestrator] Rule ${rule.id} not supported by requested engine ${preferences[0]}, skipping`);
366
+ }
367
+ return null; // Skip this rule
368
+ }
369
+
330
370
  if (config.verbose) {
331
371
  console.log(`🔄 [Orchestrator] No preferred engine supports ${rule.id}, checking all engines...`);
332
372
  }
333
373
 
334
- // Fallback to first available engine that supports the rule
374
+ // Fallback to first available engine that supports the rule (only when no specific engine requested)
335
375
  for (const [engineName, engine] of this.engines) {
336
376
  if (config.verbose) {
337
377
  console.log(`🔍 [Orchestrator] Fallback checking engine ${engineName}`);
@@ -0,0 +1,52 @@
1
+ /**
2
+ * SunLint Categories - Legacy Module
3
+ * @deprecated This module is deprecated. Use core/constants/categories.js instead.
4
+ * Maintained for backward compatibility only.
5
+ */
6
+
7
+ // Import from the new centralized constants
8
+ const {
9
+ SUNLINT_PRINCIPLES,
10
+ CATEGORY_PRINCIPLE_MAP,
11
+ CATEGORY_DESCRIPTIONS,
12
+ getValidCategories,
13
+ getCategoryPrinciples,
14
+ isValidCategory,
15
+ getCategoryDescription,
16
+ getDefaultCategory,
17
+ normalizeCategory,
18
+ getCategoryForPrinciple,
19
+ addCategoryMapping,
20
+ getCategoryStats
21
+ } = require('./constants/categories');
22
+
23
+ // Legacy constants for backward compatibility
24
+ const SUNLINT_CATEGORIES = {
25
+ CODE_QUALITY: 'quality',
26
+ DESIGN_PATTERNS: 'design',
27
+ INTEGRATION: 'integration',
28
+ MAINTAINABILITY: 'maintainability',
29
+ PERFORMANCE: 'performance',
30
+ RELIABILITY: 'reliability',
31
+ SECURITY: 'security',
32
+ TESTABILITY: 'testability',
33
+ USABILITY: 'usability'
34
+ };
35
+
36
+ module.exports = {
37
+ // Legacy exports (for backward compatibility)
38
+ SUNLINT_CATEGORIES,
39
+ CATEGORY_DESCRIPTIONS,
40
+ CATEGORY_PRINCIPLE_MAP,
41
+
42
+ // Function exports (now from centralized source)
43
+ isValidCategory,
44
+ getValidCategories,
45
+ getCategoryDescription,
46
+ getCategoryPrinciples,
47
+
48
+ // New exports from centralized system
49
+ SUNLINT_PRINCIPLES,
50
+ getDefaultCategory,
51
+ normalizeCategory
52
+ };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * SunLint Category Constants - Legacy Proxy
3
+ * @deprecated This file is deprecated. Use core/constants/categories.js instead.
4
+ * Maintained for backward compatibility only.
5
+ */
6
+
7
+ // Import from the new centralized location
8
+ const {
9
+ SUNLINT_PRINCIPLES,
10
+ CATEGORY_PRINCIPLE_MAP,
11
+ CATEGORY_DESCRIPTIONS,
12
+ getValidCategories,
13
+ getCategoryPrinciples,
14
+ isValidCategory,
15
+ getCategoryDescription,
16
+ getDefaultCategory,
17
+ normalizeCategory,
18
+ getCategoryForPrinciple,
19
+ addCategoryMapping,
20
+ getCategoryStats
21
+ } = require('./constants/categories');
22
+
23
+ module.exports = {
24
+ // Constants
25
+ SUNLINT_PRINCIPLES,
26
+ CATEGORY_PRINCIPLE_MAP,
27
+ CATEGORY_DESCRIPTIONS,
28
+
29
+ // Utility functions
30
+ getValidCategories,
31
+ getCategoryPrinciples,
32
+ isValidCategory,
33
+ getCategoryDescription,
34
+ getDefaultCategory,
35
+ normalizeCategory,
36
+ getCategoryForPrinciple,
37
+ addCategoryMapping,
38
+ getCategoryStats
39
+ };
@@ -49,7 +49,9 @@ class CliActionHandler {
49
49
 
50
50
  // Show dry run preview if requested
51
51
  if (this.options.dryRun) {
52
- await this.showDryRunPreview(config);
52
+ // We need to get rules first for accurate dry run info
53
+ const rulesToRun = await this.ruleSelectionService.selectRules(config, this.options);
54
+ await this.showDryRunPreview(config, rulesToRun);
53
55
  return;
54
56
  }
55
57
 
@@ -129,7 +131,9 @@ class CliActionHandler {
129
131
  config: {
130
132
  ...config,
131
133
  verbose: this.options.verbose,
132
- quiet: this.options.quiet
134
+ quiet: this.options.quiet,
135
+ // Pass requested engine to enable strict engine mode (no fallback)
136
+ requestedEngine: this.options.engine
133
137
  }
134
138
  });
135
139
 
@@ -141,7 +145,9 @@ class CliActionHandler {
141
145
  const analysisConfig = {
142
146
  ...config,
143
147
  verbose: this.options.verbose,
144
- quiet: this.options.quiet
148
+ quiet: this.options.quiet,
149
+ // Pass requested engine to enable strict engine mode (no fallback)
150
+ requestedEngine: this.options.engine
145
151
  };
146
152
 
147
153
  return await this.orchestrator.runAnalysis(rulesToRun, this.options, analysisConfig);
@@ -329,15 +335,36 @@ class CliActionHandler {
329
335
  }
330
336
  }
331
337
 
338
+ /**
339
+ * Get preset name based on CLI options
340
+ */
341
+ getPresetName() {
342
+ if (this.options.all) return 'all preset';
343
+ if (this.options.quality) return 'quality preset';
344
+ if (this.options.security) return 'security preset';
345
+ if (this.options.category) return `${this.options.category} preset`;
346
+ if (this.options.rule) return 'single rule';
347
+ if (this.options.rules) return 'multiple rules';
348
+ return 'config-based';
349
+ }
350
+
332
351
  /**
333
352
  * Show dry run preview
334
353
  * Following Rule C006: Verb-noun naming
335
354
  */
336
- async showDryRunPreview(config) {
355
+ async showDryRunPreview(config, rulesToRun = null) {
337
356
  console.log(chalk.blue('🔍 Dry Run Preview'));
338
357
  console.log(chalk.gray('This would analyze the following configuration:'));
358
+
359
+ let rulesInfo;
360
+ if (rulesToRun) {
361
+ rulesInfo = `${rulesToRun.length} rules (${this.getPresetName()})`;
362
+ } else {
363
+ rulesInfo = this.options.rules || 'config-based';
364
+ }
365
+
339
366
  console.log(JSON.stringify({
340
- rules: this.options.rules || 'all',
367
+ rules: rulesInfo,
341
368
  files: this.options.targetFiles?.length || 'auto-detected',
342
369
  engines: this.determineEnabledEngines(config),
343
370
  mode: this.isModernMode ? 'modern' : 'legacy'
@@ -2,6 +2,7 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const chalk = require('chalk');
4
4
  const os = require('os');
5
+ const { minimatch } = require('minimatch');
5
6
 
6
7
  // Rule C014: Dependency injection instead of direct instantiation
7
8
  const ConfigSourceLoader = require('./config-source-loader');
@@ -240,6 +241,19 @@ class ConfigManager {
240
241
  // 10. Validate config
241
242
  this.validator.validateConfiguration(config);
242
243
 
244
+ // 11. Add metadata for enhanced file targeting
245
+ const analysisScope = this.determineAnalysisScope(cliOptions.input);
246
+ config._metadata = {
247
+ analysisScope: analysisScope,
248
+ shouldBypassProjectDiscovery: this.shouldBypassProjectDiscovery(analysisScope, cliOptions),
249
+ targetInput: cliOptions.input,
250
+ hasCliRules: this.hasRuleConfigInCLI(cliOptions)
251
+ };
252
+
253
+ if (cliOptions.verbose) {
254
+ console.log(chalk.gray(`📋 Enhanced Config: Scope=${analysisScope}, Bypass=${config._metadata.shouldBypassProjectDiscovery}`));
255
+ }
256
+
243
257
  return config;
244
258
  }
245
259
 
@@ -500,6 +514,103 @@ class ConfigManager {
500
514
  validateConfig(config) {
501
515
  return this.validator.validateConfiguration(config);
502
516
  }
517
+
518
+ /**
519
+ * ENHANCED CONFIG STRATEGY METHODS
520
+ * ================================
521
+ */
522
+
523
+ /**
524
+ * Determine if CLI has rule configuration
525
+ */
526
+ hasRuleConfigInCLI(cliOptions) {
527
+ return !!(
528
+ cliOptions.rule ||
529
+ cliOptions.rules ||
530
+ cliOptions.all ||
531
+ cliOptions.quality ||
532
+ cliOptions.security ||
533
+ cliOptions.category
534
+ );
535
+ }
536
+
537
+ /**
538
+ * Determine analysis scope based on input
539
+ */
540
+ determineAnalysisScope(inputPath) {
541
+ if (!inputPath) return 'project';
542
+
543
+ const resolvedPath = path.resolve(inputPath);
544
+
545
+ if (!fs.existsSync(resolvedPath)) {
546
+ return 'project'; // Fallback for non-existent paths
547
+ }
548
+
549
+ const stat = fs.statSync(resolvedPath);
550
+
551
+ if (stat.isFile()) {
552
+ return 'file';
553
+ } else if (stat.isDirectory()) {
554
+ // More specific logic for directory scope
555
+ const currentDir = process.cwd();
556
+
557
+ // If input is current directory, it's project scope
558
+ if (path.resolve(inputPath) === currentDir) {
559
+ return 'project';
560
+ }
561
+
562
+ // If input is a project root (has project markers), it's project scope
563
+ if (this.isProjectRoot(resolvedPath)) {
564
+ return 'project';
565
+ }
566
+
567
+ // Otherwise it's folder scope
568
+ return 'folder';
569
+ }
570
+
571
+ return 'project';
572
+ }
573
+
574
+ /**
575
+ * Check if directory is a project root
576
+ */
577
+ isProjectRoot(dirPath) {
578
+ const projectMarkers = [
579
+ 'package.json',
580
+ 'pubspec.yaml',
581
+ 'build.gradle',
582
+ 'build.gradle.kts',
583
+ 'pom.xml',
584
+ 'Cargo.toml',
585
+ 'go.mod',
586
+ '.git',
587
+ 'tsconfig.json',
588
+ 'angular.json',
589
+ 'next.config.js',
590
+ 'nuxt.config.js'
591
+ ];
592
+
593
+ return projectMarkers.some(marker =>
594
+ fs.existsSync(path.join(dirPath, marker))
595
+ );
596
+ }
597
+
598
+ /**
599
+ * Determine if project discovery should be bypassed for performance
600
+ */
601
+ shouldBypassProjectDiscovery(analysisScope, cliOptions) {
602
+ // Single file input - always bypass
603
+ if (analysisScope === 'file') {
604
+ return true;
605
+ }
606
+
607
+ // Folder scope (not project root) and has CLI rules - target folder only
608
+ if (analysisScope === 'folder' && this.hasRuleConfigInCLI(cliOptions)) {
609
+ return true;
610
+ }
611
+
612
+ return false; // Use project-wide discovery
613
+ }
503
614
  }
504
615
 
505
616
  module.exports = ConfigManager;
@@ -1,3 +1,6 @@
1
+ const { minimatch } = require('minimatch');
2
+ const chalk = require('chalk');
3
+
1
4
  /**
2
5
  * Handles configuration merging and CLI overrides
3
6
  * Rule C005: Single responsibility - chỉ merge và override config
@@ -89,6 +92,11 @@ class ConfigMerger {
89
92
  // Performance overrides
90
93
  overrides.performance = this.applyPerformanceOverrides(overrides.performance, options);
91
94
 
95
+ // Auto-expand include patterns if input doesn't match any existing patterns
96
+ const expandedOverrides = this.autoExpandIncludePatterns(overrides, options);
97
+ // Copy expanded properties back to overrides
98
+ Object.assign(overrides, expandedOverrides);
99
+
92
100
  return overrides;
93
101
  }
94
102
 
@@ -161,6 +169,59 @@ class ConfigMerger {
161
169
  return updatedConfig;
162
170
  }
163
171
 
172
+ /**
173
+ * Rule C006: autoExpandIncludePatterns - verb-noun naming
174
+ * Rule C005: Single responsibility - auto-expand include patterns for input paths
175
+ */
176
+ autoExpandIncludePatterns(config, options) {
177
+ if (!options.input) {
178
+ return config;
179
+ }
180
+
181
+ const result = { ...config };
182
+ const inputPaths = Array.isArray(options.input) ? options.input : [options.input];
183
+ const currentInclude = result.include || [];
184
+
185
+ console.log(chalk.gray(`🔍 AUTO-EXPANSION: Checking input paths: ${inputPaths.join(', ')}`));
186
+ console.log(chalk.gray(`🔍 AUTO-EXPANSION: Current include patterns: ${currentInclude.join(', ')}`));
187
+
188
+ let needsExpansion = false;
189
+ for (const inputPath of inputPaths) {
190
+ const matchesExisting = currentInclude.some(pattern => {
191
+ const match1 = minimatch(inputPath, pattern);
192
+ const match2 = minimatch(inputPath + '/**', pattern);
193
+ const match3 = minimatch('**/' + inputPath, pattern);
194
+ const match4 = minimatch('**/' + inputPath + '/**', pattern);
195
+
196
+ console.log(chalk.gray(` AUTO-EXPANSION: ${inputPath} vs ${pattern}: ${match1 || match2 || match3 || match4}`));
197
+
198
+ return match1 || match2 || match3 || match4;
199
+ });
200
+
201
+ if (!matchesExisting) {
202
+ needsExpansion = true;
203
+ console.log(chalk.gray(` 🔄 AUTO-EXPANSION: ${inputPath} needs expansion`));
204
+ break;
205
+ }
206
+ }
207
+
208
+ if (needsExpansion) {
209
+ // Add flexible patterns for input paths
210
+ const expandedInclude = [...currentInclude];
211
+ for (const inputPath of inputPaths) {
212
+ expandedInclude.push(inputPath + '/**');
213
+ expandedInclude.push('**/' + inputPath + '/**');
214
+ }
215
+ result.include = expandedInclude;
216
+
217
+ console.log(chalk.gray(`📁 AUTO-EXPANSION: Auto-expanded include patterns: ${expandedInclude.join(', ')}`));
218
+ } else {
219
+ console.log(chalk.gray(`✅ AUTO-EXPANSION: No expansion needed`));
220
+ }
221
+
222
+ return result;
223
+ }
224
+
164
225
  /**
165
226
  * Rule C006: processIgnorePatterns - verb-noun naming
166
227
  * Convert deprecated ignorePatterns to exclude for backward compatibility