@sun-asterisk/sunlint 1.1.7 → 1.2.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 (74) hide show
  1. package/.sunlint.json +1 -1
  2. package/CHANGELOG.md +83 -0
  3. package/README.md +66 -4
  4. package/config/presets/all.json +125 -0
  5. package/config/presets/beginner.json +16 -8
  6. package/config/presets/ci.json +12 -4
  7. package/config/presets/maintainability.json +38 -0
  8. package/config/presets/performance.json +32 -0
  9. package/config/presets/quality.json +103 -0
  10. package/config/presets/recommended.json +36 -12
  11. package/config/presets/security.json +88 -0
  12. package/config/presets/strict.json +15 -5
  13. package/config/rules/rules-registry-generated.json +6312 -0
  14. package/config/rules-summary.json +1941 -0
  15. package/core/adapters/sunlint-rule-adapter.js +452 -0
  16. package/core/analysis-orchestrator.js +4 -4
  17. package/core/config-manager.js +28 -5
  18. package/core/rule-selection-service.js +52 -55
  19. package/docs/CONFIGURATION.md +111 -3
  20. package/docs/LANGUAGE-SPECIFIC-RULES.md +308 -0
  21. package/docs/README.md +3 -0
  22. package/docs/STANDARDIZED-CATEGORY-FILTERING.md +156 -0
  23. package/engines/eslint-engine.js +92 -2
  24. package/engines/heuristic-engine.js +8 -31
  25. package/origin-rules/common-en.md +1320 -0
  26. package/origin-rules/dart-en.md +289 -0
  27. package/origin-rules/java-en.md +60 -0
  28. package/origin-rules/kotlin-mobile-en.md +453 -0
  29. package/origin-rules/reactjs-en.md +102 -0
  30. package/origin-rules/security-en.md +1055 -0
  31. package/origin-rules/swift-en.md +449 -0
  32. package/origin-rules/typescript-en.md +136 -0
  33. package/package.json +6 -5
  34. package/scripts/copy-rules.js +86 -0
  35. package/rules/README.md +0 -252
  36. package/rules/common/C002_no_duplicate_code/analyzer.js +0 -65
  37. package/rules/common/C002_no_duplicate_code/config.json +0 -23
  38. package/rules/common/C003_no_vague_abbreviations/analyzer.js +0 -418
  39. package/rules/common/C003_no_vague_abbreviations/config.json +0 -35
  40. package/rules/common/C006_function_naming/analyzer.js +0 -349
  41. package/rules/common/C006_function_naming/config.json +0 -86
  42. package/rules/common/C010_limit_block_nesting/analyzer.js +0 -389
  43. package/rules/common/C013_no_dead_code/analyzer.js +0 -206
  44. package/rules/common/C014_dependency_injection/analyzer.js +0 -338
  45. package/rules/common/C017_constructor_logic/analyzer.js +0 -314
  46. package/rules/common/C019_log_level_usage/analyzer.js +0 -362
  47. package/rules/common/C019_log_level_usage/config.json +0 -121
  48. package/rules/common/C029_catch_block_logging/analyzer.js +0 -373
  49. package/rules/common/C029_catch_block_logging/config.json +0 -59
  50. package/rules/common/C031_validation_separation/analyzer.js +0 -186
  51. package/rules/common/C041_no_sensitive_hardcode/analyzer.js +0 -292
  52. package/rules/common/C042_boolean_name_prefix/analyzer.js +0 -300
  53. package/rules/common/C043_no_console_or_print/analyzer.js +0 -304
  54. package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +0 -351
  55. package/rules/common/C075_explicit_return_types/analyzer.js +0 -103
  56. package/rules/common/C076_single_test_behavior/analyzer.js +0 -121
  57. package/rules/docs/C002_no_duplicate_code.md +0 -57
  58. package/rules/docs/C031_validation_separation.md +0 -72
  59. package/rules/index.js +0 -149
  60. package/rules/migration/converter.js +0 -385
  61. package/rules/migration/mapping.json +0 -164
  62. package/rules/security/S026_json_schema_validation/analyzer.js +0 -251
  63. package/rules/security/S026_json_schema_validation/config.json +0 -27
  64. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +0 -263
  65. package/rules/security/S027_no_hardcoded_secrets/config.json +0 -29
  66. package/rules/security/S029_csrf_protection/analyzer.js +0 -264
  67. package/rules/tests/C002_no_duplicate_code.test.js +0 -50
  68. package/rules/universal/C010/generic.js +0 -0
  69. package/rules/universal/C010/tree-sitter-analyzer.js +0 -0
  70. package/rules/utils/ast-utils.js +0 -191
  71. package/rules/utils/base-analyzer.js +0 -98
  72. package/rules/utils/pattern-matchers.js +0 -239
  73. package/rules/utils/rule-helpers.js +0 -264
  74. package/rules/utils/severity-constants.js +0 -93
@@ -0,0 +1,452 @@
1
+ const { SimpleRuleParser } = require('../../rules/parser/rule-parser-simple');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ /**
6
+ * SunLint Rule Adapter - Unified interface for rule access
7
+ *
8
+ * Follows the same pattern as sunlint-vscode RuleAdapter for consistency
9
+ * Provides abstraction layer between business logic and rule storage
10
+ *
11
+ * Features:
12
+ * - Singleton pattern for global rule access
13
+ * - Caching for performance
14
+ * - Unified interface across SunLint CLI and VSCode extension
15
+ * - Support for multiple rule sources (origin-rules, registry)
16
+ */
17
+ class SunlintRuleAdapter {
18
+ constructor() {
19
+ this.parser = new SimpleRuleParser();
20
+ this.rulesCache = new Map();
21
+ this.registryCache = null;
22
+ this.isInitialized = false;
23
+ }
24
+
25
+ /**
26
+ * Get singleton instance (same pattern as VSCode)
27
+ */
28
+ static getInstance() {
29
+ if (!SunlintRuleAdapter.instance) {
30
+ SunlintRuleAdapter.instance = new SunlintRuleAdapter();
31
+ }
32
+ return SunlintRuleAdapter.instance;
33
+ }
34
+
35
+ /**
36
+ * Initialize the adapter - loads rules from available sources
37
+ */
38
+ async initialize(options = {}) {
39
+ if (this.isInitialized) {
40
+ return true;
41
+ }
42
+
43
+ try {
44
+ const { rulesDir, useRegistry = true } = options;
45
+
46
+ // Try to load from generated registry first (preferred)
47
+ if (useRegistry) {
48
+ await this.loadFromRegistry();
49
+ }
50
+
51
+ // Fallback to origin-rules parsing
52
+ if (!this.registryCache) {
53
+ await this.loadFromOriginRules(rulesDir);
54
+ }
55
+
56
+ this.isInitialized = true;
57
+ console.log(`✅ SunlintRuleAdapter initialized with ${this.getAllRuleIds().length} rules`);
58
+ return true;
59
+
60
+ } catch (error) {
61
+ console.error('❌ Failed to initialize SunlintRuleAdapter:', error.message);
62
+ return false;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Load rules from generated registry (preferred method)
68
+ */
69
+ async loadFromRegistry() {
70
+ try {
71
+ const registryPath = path.join(__dirname, '../../config/rules/rules-registry-generated.json');
72
+
73
+ if (fs.existsSync(registryPath)) {
74
+ const registryData = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
75
+ this.registryCache = registryData.rules || {};
76
+ console.log(`📊 Loaded ${Object.keys(this.registryCache).length} rules from registry`);
77
+ return true;
78
+ }
79
+
80
+ return false;
81
+ } catch (error) {
82
+ console.warn('⚠️ Failed to load registry:', error.message);
83
+ return false;
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Load rules from origin-rules directory (fallback method)
89
+ */
90
+ async loadFromOriginRules(customRulesDir = null) {
91
+ try {
92
+ const rules = this.parser.parseAllRules(customRulesDir);
93
+
94
+ // Convert to cache format
95
+ this.rulesCache.clear();
96
+ rules.forEach(rule => {
97
+ if (rule.id) {
98
+ this.rulesCache.set(rule.id, this.normalizeRule(rule));
99
+ }
100
+ });
101
+
102
+ console.log(`📋 Loaded ${this.rulesCache.size} rules from origin-rules`);
103
+ return true;
104
+ } catch (error) {
105
+ console.error('❌ Failed to load from origin-rules:', error.message);
106
+ return false;
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Normalize rule format for consistent interface
112
+ */
113
+ normalizeRule(rule) {
114
+ return {
115
+ id: rule.id,
116
+ name: rule.title || rule.name || `${rule.id} Rule`,
117
+ description: rule.description || 'No description available',
118
+ title: rule.title || rule.name || `${rule.id} Rule`,
119
+ details: Array.isArray(rule.details) ? rule.details : [rule.details || ''],
120
+ tools: rule.tools || [],
121
+ principles: rule.principles || [],
122
+ version: rule.version || '1.0.0',
123
+ status: rule.status || 'activated',
124
+ severity: rule.severity || 'warning',
125
+ category: rule.category || 'quality',
126
+ languages: rule.languages || ['typescript', 'javascript'],
127
+ framework: rule.framework,
128
+ // Metadata
129
+ source: this.registryCache ? 'registry' : 'origin-rules'
130
+ };
131
+ }
132
+
133
+ /**
134
+ * Get rule by ID
135
+ */
136
+ getRuleById(ruleId) {
137
+ if (!this.isInitialized) {
138
+ console.warn('⚠️ RuleAdapter not initialized. Call initialize() first.');
139
+ return null;
140
+ }
141
+
142
+ // Try registry first
143
+ if (this.registryCache && this.registryCache[ruleId]) {
144
+ return this.normalizeRule({
145
+ id: ruleId,
146
+ ...this.registryCache[ruleId]
147
+ });
148
+ }
149
+
150
+ // Try cache
151
+ if (this.rulesCache.has(ruleId)) {
152
+ return this.rulesCache.get(ruleId);
153
+ }
154
+
155
+ return null;
156
+ }
157
+
158
+ /**
159
+ * Get all rules
160
+ */
161
+ getAllRules() {
162
+ if (!this.isInitialized) {
163
+ console.warn('⚠️ RuleAdapter not initialized. Call initialize() first.');
164
+ return [];
165
+ }
166
+
167
+ const rules = [];
168
+
169
+ if (this.registryCache) {
170
+ // From registry
171
+ Object.entries(this.registryCache).forEach(([ruleId, ruleData]) => {
172
+ rules.push(this.normalizeRule({ id: ruleId, ...ruleData }));
173
+ });
174
+ } else {
175
+ // From cache
176
+ rules.push(...this.rulesCache.values());
177
+ }
178
+
179
+ return rules;
180
+ }
181
+
182
+ /**
183
+ * Get all rule IDs
184
+ */
185
+ getAllRuleIds() {
186
+ if (this.registryCache) {
187
+ return Object.keys(this.registryCache);
188
+ }
189
+ return Array.from(this.rulesCache.keys());
190
+ }
191
+
192
+ /**
193
+ * Validate rule ID
194
+ */
195
+ isValidRuleId(ruleId) {
196
+ return this.getAllRuleIds().includes(ruleId);
197
+ }
198
+
199
+ /**
200
+ * Get rules by category
201
+ */
202
+ getRulesByCategory(category) {
203
+ return this.getAllRules().filter(rule =>
204
+ rule.category && rule.category.toLowerCase() === category.toLowerCase()
205
+ );
206
+ }
207
+
208
+ /**
209
+ * Get rules by principles (enhanced for category filtering)
210
+ */
211
+ getRulesByPrinciples(principles) {
212
+ const principlesArray = Array.isArray(principles) ? principles : [principles];
213
+ return this.getAllRules().filter(rule => {
214
+ if (!rule.principles || rule.principles.length === 0) return false;
215
+ return principlesArray.some(principle =>
216
+ rule.principles.some(rulePrinciple =>
217
+ rulePrinciple.toLowerCase().includes(principle.toLowerCase())
218
+ )
219
+ );
220
+ });
221
+ }
222
+
223
+ /**
224
+ * Get rules for specific category using core files approach
225
+ * Based on actual principles in the rule catalog
226
+ */
227
+ 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) {
242
+ console.warn(`⚠️ Unknown category: ${category}`);
243
+ return [];
244
+ }
245
+
246
+ return this.getRulesByPrinciples(principles);
247
+ }
248
+
249
+ /**
250
+ * Get rules from core files only (common-en.md + security-en.md)
251
+ * This follows the standardization approach for category commands
252
+ */
253
+ getCoreRules() {
254
+ // For now, we filter by source files if available in metadata
255
+ // In future, we can enhance this to track source files
256
+ return this.getAllRules().filter(rule => {
257
+ // If we have metadata about source files, use it
258
+ if (rule.sourceFile) {
259
+ return rule.sourceFile.includes('common-en') ||
260
+ rule.sourceFile.includes('security-en');
261
+ }
262
+
263
+ // Fallback: include rules that don't seem language-specific
264
+ const languageSpecificPrefixes = ['T0', 'J0', 'D0', 'K0', 'SW0', 'P0', 'R0'];
265
+ const isLanguageSpecific = languageSpecificPrefixes.some(prefix =>
266
+ rule.id.startsWith(prefix)
267
+ );
268
+
269
+ return !isLanguageSpecific;
270
+ });
271
+ }
272
+
273
+ /**
274
+ * Get standardized category rules (core files + principle filtering)
275
+ * This is the recommended method for --security, --quality, etc.
276
+ */
277
+ getStandardCategoryRules(category) {
278
+ 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) {
293
+ console.warn(`⚠️ Unknown standard category: ${category}`);
294
+ return [];
295
+ }
296
+
297
+ // Filter core rules by principles
298
+ return coreRules.filter(rule => {
299
+ if (!rule.principles || rule.principles.length === 0) return false;
300
+ return principles.some(principle =>
301
+ rule.principles.some(rulePrinciple =>
302
+ rulePrinciple.toLowerCase().includes(principle.toLowerCase())
303
+ )
304
+ );
305
+ });
306
+ }
307
+
308
+ /**
309
+ * Search rules by text
310
+ */
311
+ searchRules(query) {
312
+ const lowerQuery = query.toLowerCase();
313
+ return this.getAllRules().filter(rule =>
314
+ rule.id.toLowerCase().includes(lowerQuery) ||
315
+ rule.name.toLowerCase().includes(lowerQuery) ||
316
+ rule.description.toLowerCase().includes(lowerQuery)
317
+ );
318
+ }
319
+
320
+ /**
321
+ * Get rules summary
322
+ */
323
+ getRulesSummary() {
324
+ const rules = this.getAllRules();
325
+ const categories = {};
326
+
327
+ rules.forEach(rule => {
328
+ const category = rule.category || 'unknown';
329
+ categories[category] = (categories[category] || 0) + 1;
330
+ });
331
+
332
+ return {
333
+ total: rules.length,
334
+ categories: categories,
335
+ source: this.registryCache ? 'registry' : 'origin-rules'
336
+ };
337
+ }
338
+
339
+ /**
340
+ * Filter rules by criteria (unified interface)
341
+ */
342
+ filterRules(criteria = {}) {
343
+ const {
344
+ ruleIds,
345
+ categories,
346
+ principles,
347
+ status = 'activated',
348
+ engines,
349
+ severity
350
+ } = criteria;
351
+
352
+ let rules = this.getAllRules();
353
+
354
+ // Filter by rule IDs
355
+ if (ruleIds && ruleIds.length > 0) {
356
+ rules = rules.filter(rule => ruleIds.includes(rule.id));
357
+ }
358
+
359
+ // Filter by categories
360
+ if (categories && categories.length > 0) {
361
+ rules = rules.filter(rule =>
362
+ categories.some(cat =>
363
+ rule.category && rule.category.toLowerCase() === cat.toLowerCase()
364
+ )
365
+ );
366
+ }
367
+
368
+ // Filter by principles
369
+ if (principles && principles.length > 0) {
370
+ rules = rules.filter(rule => {
371
+ if (!rule.principles) return false;
372
+ return principles.some(principle =>
373
+ rule.principles.some(rp =>
374
+ rp.toLowerCase().includes(principle.toLowerCase())
375
+ )
376
+ );
377
+ });
378
+ }
379
+
380
+ // Filter by status
381
+ if (status) {
382
+ rules = rules.filter(rule =>
383
+ rule.status && rule.status.toLowerCase() === status.toLowerCase()
384
+ );
385
+ }
386
+
387
+ // Filter by severity
388
+ if (severity) {
389
+ rules = rules.filter(rule =>
390
+ rule.severity && rule.severity.toLowerCase() === severity.toLowerCase()
391
+ );
392
+ }
393
+
394
+ return rules;
395
+ }
396
+
397
+ /**
398
+ * Generate AI context for specific rules
399
+ */
400
+ generateAIContext(ruleIds) {
401
+ const rules = ruleIds.map(id => this.getRuleById(id)).filter(Boolean);
402
+
403
+ return rules.map(rule => ({
404
+ id: rule.id,
405
+ title: rule.name,
406
+ description: rule.description,
407
+ details: Array.isArray(rule.details) ? rule.details.join('\n') : rule.details,
408
+ category: rule.category,
409
+ severity: rule.severity,
410
+ principles: rule.principles
411
+ }));
412
+ }
413
+
414
+ /**
415
+ * Get configuration for specific engine
416
+ */
417
+ getEngineRules(engine = 'heuristic') {
418
+ const allRules = this.getAllRules();
419
+
420
+ switch (engine.toLowerCase()) {
421
+ case 'heuristic':
422
+ // Heuristic engine supports most rules except ESLint-specific
423
+ return allRules.filter(rule => !rule.id.startsWith('T'));
424
+
425
+ case 'eslint':
426
+ // ESLint engine supports TypeScript rules and some C-series
427
+ return allRules.filter(rule =>
428
+ rule.id.startsWith('T') ||
429
+ ['C006', 'C010', 'C019', 'C029', 'C031'].includes(rule.id)
430
+ );
431
+
432
+ case 'ai':
433
+ // AI engine supports all rules
434
+ return allRules;
435
+
436
+ default:
437
+ return allRules;
438
+ }
439
+ }
440
+
441
+ /**
442
+ * Clear cache and reinitialize
443
+ */
444
+ async refresh(options = {}) {
445
+ this.rulesCache.clear();
446
+ this.registryCache = null;
447
+ this.isInitialized = false;
448
+ return await this.initialize(options);
449
+ }
450
+ }
451
+
452
+ module.exports = SunlintRuleAdapter;
@@ -9,14 +9,14 @@ const chalk = require('chalk');
9
9
  const path = require('path');
10
10
  const fs = require('fs');
11
11
  const AnalysisEngineInterface = require('./interfaces/analysis-engine.interface');
12
- const EnhancedRulesRegistry = require('./enhanced-rules-registry');
12
+ const SunlintRuleAdapter = require('./adapters/sunlint-rule-adapter');
13
13
 
14
14
  class AnalysisOrchestrator {
15
15
  constructor() {
16
16
  this.engines = new Map(); // Plugin registry
17
17
  this.initialized = false;
18
18
  this.defaultTimeout = 30000; // 30 seconds default timeout
19
- this.rulesRegistry = new EnhancedRulesRegistry();
19
+ this.ruleAdapter = SunlintRuleAdapter.getInstance();
20
20
  this.enginesConfigPath = path.join(__dirname, '..', 'config', 'engines', 'engines.json');
21
21
  }
22
22
 
@@ -114,8 +114,8 @@ class AnalysisOrchestrator {
114
114
  console.log(chalk.blue(`🔧 Initializing ${this.engines.size} analysis engines...`));
115
115
  }
116
116
 
117
- // Load enhanced rules registry
118
- await this.rulesRegistry.loadRegistry();
117
+ // Initialize rule adapter
118
+ await this.ruleAdapter.initialize();
119
119
 
120
120
  // Initialize enabled engines
121
121
  const enabledEngines = config.enabledEngines || Array.from(this.engines.keys());
@@ -9,12 +9,14 @@ const ConfigPresetResolver = require('./config-preset-resolver');
9
9
  const ConfigMerger = require('./config-merger');
10
10
  const ConfigValidator = require('./config-validator');
11
11
  const ConfigOverrideProcessor = require('./config-override-processor');
12
+ const SunlintRuleAdapter = require('./adapters/sunlint-rule-adapter');
12
13
 
13
14
  /**
14
15
  * Main configuration manager - orchestrates config loading process
15
16
  * Rule C005: Single responsibility - orchestrates other config services
16
17
  * Rule C015: Domain language - ConfigManager as main coordinator
17
18
  * Rule C014: Uses dependency injection for all services
19
+ * REFACTORED: Now uses SunlintRuleAdapter instead of direct registry access
18
20
  */
19
21
  class ConfigManager {
20
22
  constructor() {
@@ -24,6 +26,8 @@ class ConfigManager {
24
26
  this.merger = new ConfigMerger();
25
27
  this.validator = new ConfigValidator();
26
28
  this.overrideProcessor = new ConfigOverrideProcessor();
29
+ this.ruleAdapter = SunlintRuleAdapter.getInstance();
30
+ this.initialized = false;
27
31
 
28
32
  this.defaultConfig = {
29
33
  rules: {},
@@ -150,8 +154,15 @@ class ConfigManager {
150
154
  * Rule C006: loadConfiguration - verb-noun naming
151
155
  * Rule C005: Single responsibility - orchestrates config loading
152
156
  * Rule C012: Command method - loads and returns config
157
+ * REFACTORED: Now initializes rule adapter
153
158
  */
154
159
  async loadConfiguration(configPath, cliOptions = {}) {
160
+ // Initialize rule adapter
161
+ if (!this.initialized) {
162
+ await this.ruleAdapter.initialize();
163
+ this.initialized = true;
164
+ }
165
+
155
166
  // 1. Start with built-in defaults
156
167
  let config = { ...this.defaultConfig };
157
168
 
@@ -332,13 +343,18 @@ class ConfigManager {
332
343
 
333
344
  /**
334
345
  * Rule C006: loadExtendedConfig - verb-noun naming
346
+ * REFACTORED: Now loads presets directly instead of through registry
335
347
  */
336
348
  async loadExtendedConfig(extendPath) {
337
349
  if (extendPath.startsWith('@sun/sunlint/')) {
338
- // Load preset from rules registry
350
+ // Load preset directly from preset file
339
351
  const presetName = extendPath.replace('@sun/sunlint/', '');
340
- const rulesRegistry = require('../config/rules/rules-registry.json');
341
- return this.presetResolver.resolvePresetFromRegistry(presetName, rulesRegistry);
352
+ const presetPath = path.join(__dirname, '../config/presets', `${presetName}.json`);
353
+ if (fs.existsSync(presetPath)) {
354
+ return JSON.parse(fs.readFileSync(presetPath, 'utf8'));
355
+ } else {
356
+ throw new Error(`Preset not found: ${extendPath}`);
357
+ }
342
358
  } else {
343
359
  // Load from file path
344
360
  const configPath = path.resolve(extendPath);
@@ -361,10 +377,17 @@ class ConfigManager {
361
377
  /**
362
378
  * Rule C006: getEffectiveRuleConfiguration - verb-noun naming
363
379
  * Rule C014: Delegate to validator
380
+ * REFACTORED: Now uses rule adapter for rule validation
364
381
  */
365
382
  getEffectiveRuleConfiguration(ruleId, config) {
366
- const rulesRegistry = require('../config/rules/rules-registry.json');
367
- return this.validator.getEffectiveRuleConfiguration(ruleId, config, rulesRegistry);
383
+ // Validate rule exists via adapter
384
+ const rule = this.ruleAdapter.getRuleById(ruleId);
385
+ if (!rule) {
386
+ console.warn(`⚠️ Rule ${ruleId} not found in registry`);
387
+ return null;
388
+ }
389
+
390
+ return this.validator.getEffectiveRuleConfiguration(ruleId, config, { rules: { [ruleId]: rule } });
368
391
  }
369
392
 
370
393
  /**