@sun-asterisk/sunlint 1.0.7 → 1.1.4
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.
- package/.sunlint.json +35 -0
- package/CHANGELOG.md +30 -3
- package/CONTRIBUTING.md +235 -0
- package/PROJECT_STRUCTURE.md +60 -0
- package/README.md +146 -58
- package/cli.js +1 -0
- package/config/README.md +88 -0
- package/config/defaults/ai-rules-context.json +231 -0
- package/config/engines/engines.json +49 -0
- package/config/engines/eslint-rule-mapping.json +74 -0
- package/config/eslint-rule-mapping.json +126 -0
- package/config/integrations/eslint/base.config.js +125 -0
- package/config/integrations/eslint/simple.config.js +24 -0
- package/config/presets/strict.json +0 -1
- package/config/rule-analysis-strategies.js +74 -0
- package/config/{rules-registry.json → rules/rules-registry.json} +30 -7
- package/core/analysis-orchestrator.js +383 -591
- package/core/ast-modules/README.md +103 -0
- package/core/ast-modules/base-parser.js +90 -0
- package/core/ast-modules/index.js +97 -0
- package/core/ast-modules/package.json +37 -0
- package/core/ast-modules/parsers/eslint-js-parser.js +153 -0
- package/core/ast-modules/parsers/eslint-ts-parser.js +98 -0
- package/core/ast-modules/parsers/javascript-parser.js +187 -0
- package/core/ast-modules/parsers/typescript-parser.js +187 -0
- package/core/cli-action-handler.js +271 -255
- package/core/cli-program.js +18 -4
- package/core/config-manager.js +9 -3
- package/core/config-merger.js +40 -1
- package/core/config-validator.js +2 -2
- package/core/dependency-checker.js +125 -0
- package/core/enhanced-rules-registry.js +331 -0
- package/core/file-targeting-service.js +92 -23
- package/core/interfaces/analysis-engine.interface.js +100 -0
- package/core/multi-rule-runner.js +0 -221
- package/core/output-service.js +1 -1
- package/core/rule-mapping-service.js +1 -1
- package/core/rule-selection-service.js +10 -2
- package/core/smart-installer.js +164 -0
- package/docs/AI.md +163 -0
- package/docs/ARCHITECTURE.md +78 -0
- package/docs/CI-CD-GUIDE.md +315 -0
- package/docs/COMMAND-EXAMPLES.md +256 -0
- package/docs/CONFIGURATION.md +414 -0
- package/docs/DEBUG.md +86 -0
- package/docs/DEPENDENCIES.md +90 -0
- package/docs/DEPLOYMENT-STRATEGIES.md +270 -0
- package/docs/DISTRIBUTION.md +153 -0
- package/docs/ESLINT-INTEGRATION-STRATEGY.md +392 -0
- package/docs/ESLINT_INTEGRATION.md +238 -0
- package/docs/FOLDER_STRUCTURE.md +59 -0
- package/docs/FUTURE_PACKAGES.md +83 -0
- package/docs/HEURISTIC_VS_AI.md +113 -0
- package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +112 -0
- package/docs/PRODUCTION_SIZE_IMPACT.md +183 -0
- package/docs/README.md +32 -0
- package/docs/RELEASE_GUIDE.md +230 -0
- package/engines/eslint-engine.js +610 -0
- package/engines/heuristic-engine.js +864 -0
- package/engines/openai-engine.js +374 -0
- package/engines/tree-sitter-parser.js +0 -0
- package/engines/universal-ast-engine.js +0 -0
- package/integrations/eslint/README.md +99 -0
- package/integrations/eslint/configs/.eslintrc.js +98 -0
- package/integrations/eslint/configs/eslint.config.js +133 -0
- package/integrations/eslint/configs/eslint.config.simple.js +24 -0
- package/integrations/eslint/package.json +23 -0
- package/integrations/eslint/plugin/index.js +164 -0
- package/integrations/eslint/plugin/package.json +13 -0
- package/integrations/eslint/plugin/rules/common/c002-no-duplicate-code.js +204 -0
- package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +246 -0
- package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +216 -0
- package/integrations/eslint/plugin/rules/common/c010-limit-block-nesting.js +90 -0
- package/integrations/eslint/plugin/rules/common/c013-no-dead-code.js +78 -0
- package/integrations/eslint/plugin/rules/common/c014-abstract-dependency-preferred.js +38 -0
- package/integrations/eslint/plugin/rules/common/c017-limit-constructor-logic.js +146 -0
- package/integrations/eslint/plugin/rules/common/c018-no-generic-throw.js +335 -0
- package/integrations/eslint/plugin/rules/common/c023-no-duplicate-variable-name-in-scope.js +142 -0
- package/integrations/eslint/plugin/rules/common/c029-catch-block-logging.js +115 -0
- package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +294 -0
- package/integrations/eslint/plugin/rules/common/c035-no-empty-catch.js +162 -0
- package/integrations/eslint/plugin/rules/common/c041-no-config-inline.js +122 -0
- package/integrations/eslint/plugin/rules/common/c042-boolean-name-prefix.js +406 -0
- package/integrations/eslint/plugin/rules/common/c043-no-console-or-print.js +300 -0
- package/integrations/eslint/plugin/rules/common/c047-no-duplicate-retry-logic.js +239 -0
- package/integrations/eslint/plugin/rules/common/c072-one-assert-per-test.js +184 -0
- package/integrations/eslint/plugin/rules/common/c075-explicit-function-return-types.js +168 -0
- package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +254 -0
- package/integrations/eslint/plugin/rules/security/s001-fail-securely.js +381 -0
- package/integrations/eslint/plugin/rules/security/s002-idor-check.js +945 -0
- package/integrations/eslint/plugin/rules/security/s003-no-unvalidated-redirect.js +86 -0
- package/integrations/eslint/plugin/rules/security/s007-no-plaintext-otp.js +74 -0
- package/integrations/eslint/plugin/rules/security/s013-verify-tls-connection.js +47 -0
- package/integrations/eslint/plugin/rules/security/s047-secure-random-passwords.js +108 -0
- package/integrations/eslint/plugin/rules/security/s055-verification-rest-check-the-incoming-content-type.js +143 -0
- package/integrations/eslint/plugin/rules/typescript/t002-interface-prefix-i.js +42 -0
- package/integrations/eslint/plugin/rules/typescript/t003-ts-ignore-reason.js +48 -0
- package/integrations/eslint/plugin/rules/typescript/t004-no-empty-type.js +95 -0
- package/integrations/eslint/plugin/rules/typescript/t007-no-fn-in-constructor.js +52 -0
- package/integrations/eslint/plugin/rules/typescript/t010-no-nested-union-tuple.js +48 -0
- package/integrations/eslint/plugin/rules/typescript/t019-no-this-assign.js +81 -0
- package/integrations/eslint/plugin/rules/typescript/t020-no-default-multi-export.js +127 -0
- package/integrations/eslint/plugin/rules/typescript/t021-limit-nested-generics.js +150 -0
- package/integrations/eslint/tsconfig.json +27 -0
- package/package.json +61 -21
- package/rules/README.md +252 -0
- package/rules/common/C002_no_duplicate_code/analyzer.js +65 -0
- package/rules/common/C002_no_duplicate_code/config.json +23 -0
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +418 -0
- package/rules/common/C003_no_vague_abbreviations/config.json +35 -0
- package/rules/{C006_function_naming → common/C006_function_naming}/analyzer.js +13 -2
- package/rules/common/C010_limit_block_nesting/analyzer.js +389 -0
- package/rules/common/C013_no_dead_code/analyzer.js +206 -0
- package/rules/common/C014_dependency_injection/analyzer.js +338 -0
- package/rules/common/C017_constructor_logic/analyzer.js +314 -0
- package/rules/{C019_log_level_usage → common/C019_log_level_usage}/analyzer.js +5 -2
- package/rules/{C029_catch_block_logging → common/C029_catch_block_logging}/analyzer.js +49 -15
- package/rules/common/C041_no_sensitive_hardcode/analyzer.js +292 -0
- package/rules/common/C042_boolean_name_prefix/analyzer.js +300 -0
- package/rules/common/C043_no_console_or_print/analyzer.js +304 -0
- package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +351 -0
- package/rules/common/C075_explicit_return_types/analyzer.js +103 -0
- package/rules/common/C076_single_test_behavior/analyzer.js +121 -0
- package/rules/docs/C002_no_duplicate_code.md +57 -0
- package/rules/index.js +149 -0
- package/rules/migration/converter.js +385 -0
- package/rules/migration/mapping.json +164 -0
- package/rules/security/S026_json_schema_validation/analyzer.js +251 -0
- package/rules/security/S026_json_schema_validation/config.json +27 -0
- package/rules/security/S027_no_hardcoded_secrets/analyzer.js +263 -0
- package/rules/security/S027_no_hardcoded_secrets/config.json +29 -0
- package/rules/security/S029_csrf_protection/analyzer.js +264 -0
- package/rules/tests/C002_no_duplicate_code.test.js +50 -0
- package/rules/universal/C010/generic.js +0 -0
- package/rules/universal/C010/tree-sitter-analyzer.js +0 -0
- package/rules/utils/ast-utils.js +191 -0
- package/rules/utils/base-analyzer.js +98 -0
- package/rules/utils/pattern-matchers.js +239 -0
- package/rules/utils/rule-helpers.js +264 -0
- package/rules/utils/severity-constants.js +93 -0
- package/scripts/build-release.sh +117 -0
- package/scripts/ci-report.js +179 -0
- package/scripts/install.sh +196 -0
- package/scripts/manual-release.sh +338 -0
- package/scripts/merge-reports.js +424 -0
- package/scripts/pre-release-test.sh +175 -0
- package/scripts/prepare-release.sh +202 -0
- package/scripts/setup-github-registry.sh +42 -0
- package/scripts/test-scripts/README.md +22 -0
- package/scripts/test-scripts/test-c041-comparison.js +114 -0
- package/scripts/test-scripts/test-c041-eslint.js +67 -0
- package/scripts/test-scripts/test-eslint-rules.js +146 -0
- package/scripts/test-scripts/test-real-world.js +44 -0
- package/scripts/test-scripts/test-rules-on-real-projects.js +86 -0
- package/scripts/trigger-release.sh +285 -0
- package/scripts/validate-rule-structure.js +148 -0
- package/scripts/verify-install.sh +82 -0
- package/config/sunlint-schema.json +0 -159
- package/config/typescript/custom-rules.js +0 -9
- package/config/typescript/package-lock.json +0 -1585
- package/config/typescript/package.json +0 -13
- package/config/typescript/security-rules/index.js +0 -90
- package/config/typescript/tsconfig.json +0 -29
- package/core/ai-analyzer.js +0 -169
- package/core/eslint-engine-service.js +0 -312
- package/core/eslint-instance-manager.js +0 -104
- package/core/eslint-integration-service.js +0 -363
- package/core/sunlint-engine-service.js +0 -23
- package/core/typescript-analyzer.js +0 -262
- package/core/typescript-engine.js +0 -313
- /package/config/{default.json → defaults/default.json} +0 -0
- /package/config/{typescript/eslint.config.js → integrations/eslint/typescript.config.js} +0 -0
- /package/config/{typescript/custom-rules-new.js → schemas/sunlint-schema.json} +0 -0
- /package/config/{typescript → testing}/test-s005-working.ts +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s005-no-origin-auth.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s006-activation-recovery-secret-not-plaintext.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s008-crypto-agility.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s009-no-insecure-crypto.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s010-no-insecure-random-in-sensitive-context.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s011-no-insecure-uuid.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s012-hardcode-secret.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s014-insecure-tls-version.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s015-insecure-tls-certificate.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s016-sensitive-query-parameter.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s017-no-sql-injection.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s018-positive-input-validation.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s019-no-raw-user-input-in-email.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s020-no-eval-dynamic-execution.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s022-output-encoding.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s023-no-json-injection.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s025-server-side-input-validation.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s026-json-schema-validation.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s027-no-hardcoded-secrets.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s029-require-csrf-protection.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s030-no-directory-browsing.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s033-require-samesite-cookie.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s034-require-host-cookie-prefix.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s035-cookie-specific-path.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s036-no-unsafe-file-include.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s037-require-anti-cache-headers.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s038-no-version-disclosure.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s039-no-session-token-in-url.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s041-require-session-invalidate-on-logout.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s042-require-periodic-reauthentication.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s043-terminate-sessions-on-password-change.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s044-require-full-session-for-sensitive-operations.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s045-anti-automation-controls.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s046-secure-notification-on-auth-change.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s048-password-credential-recovery.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s050-session-token-weak-hash.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s052-secure-random-authentication-code.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s054-verification-default-account.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s057-utc-logging.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s058-no-ssrf.js +0 -0
- /package/rules/{C006_function_naming → common/C006_function_naming}/config.json +0 -0
- /package/rules/{C019_log_level_usage → common/C019_log_level_usage}/config.json +0 -0
- /package/rules/{C029_catch_block_logging → common/C029_catch_block_logging}/config.json +0 -0
- /package/rules/{C031_validation_separation → common/C031_validation_separation}/analyzer.js +0 -0
- /package/rules/{C031_validation_separation/README.md → docs/C031_validation_separation.md} +0 -0
package/core/cli-program.js
CHANGED
|
@@ -29,12 +29,12 @@ function createCliProgram() {
|
|
|
29
29
|
.option('--typescript-engine <engine>', 'TypeScript analysis engine (eslint,sunlint,hybrid)', 'sunlint')
|
|
30
30
|
.option('--ensure-deps', 'Ensure ESLint dependencies are installed');
|
|
31
31
|
|
|
32
|
-
// Input/Output options
|
|
32
|
+
// Input/Output options (v1.x: explicit --input required)
|
|
33
33
|
program
|
|
34
|
-
.option('-i, --input <path>', 'Input file or directory to analyze
|
|
34
|
+
.option('-i, --input <path>', 'Input file or directory to analyze (REQUIRED)')
|
|
35
35
|
.option('-f, --format <format>', 'Output format (eslint,json,summary,table)', 'eslint')
|
|
36
36
|
.option('-o, --output <file>', 'Output file path')
|
|
37
|
-
.option('--config <file>', 'Configuration file path
|
|
37
|
+
.option('--config <file>', 'Configuration file path (default: auto-discover)');
|
|
38
38
|
|
|
39
39
|
// File targeting options
|
|
40
40
|
program
|
|
@@ -58,12 +58,16 @@ function createCliProgram() {
|
|
|
58
58
|
|
|
59
59
|
// Advanced options
|
|
60
60
|
program
|
|
61
|
+
.option('--engine <engine>', 'Force specific analysis engine (eslint,sunlint)', '')
|
|
61
62
|
.option('--dry-run', 'Show what would be analyzed without running')
|
|
62
63
|
.option('--verbose', 'Enable verbose logging')
|
|
63
64
|
.option('--quiet', 'Suppress non-error output')
|
|
64
65
|
.option('--debug', 'Enable debug mode')
|
|
65
66
|
.option('--ai', 'Enable AI-powered analysis')
|
|
66
|
-
.option('--no-ai', 'Force disable AI analysis (use heuristic only)')
|
|
67
|
+
.option('--no-ai', 'Force disable AI analysis (use heuristic only)')
|
|
68
|
+
.option('--legacy', 'Use legacy analysis architecture')
|
|
69
|
+
.option('--modern', 'Use modern plugin-based architecture (default)')
|
|
70
|
+
.option('--list-engines', 'List available analysis engines');
|
|
67
71
|
|
|
68
72
|
// ESLint Integration options
|
|
69
73
|
program
|
|
@@ -77,6 +81,7 @@ function createCliProgram() {
|
|
|
77
81
|
program.addHelpText('after', `
|
|
78
82
|
Examples:
|
|
79
83
|
$ sunlint --rule=C019 --input=src
|
|
84
|
+
$ sunlint --rule C019 --input src
|
|
80
85
|
$ sunlint --rules=C019,C006,S005 --input=src
|
|
81
86
|
$ sunlint --all --input=src
|
|
82
87
|
$ sunlint --quality --input=src
|
|
@@ -95,6 +100,15 @@ TypeScript Analysis (Phase 1):
|
|
|
95
100
|
$ sunlint --rules=C019,S005 --typescript --input=src
|
|
96
101
|
$ sunlint --typescript-engine=eslint --input=src
|
|
97
102
|
|
|
103
|
+
Version Strategy:
|
|
104
|
+
v1.x: ESLint-first with SunLint fallback (current)
|
|
105
|
+
v2.x: SunLint-first with ESLint integration (--eslint-integration)
|
|
106
|
+
|
|
107
|
+
Engine Configuration:
|
|
108
|
+
$ sunlint --all --input=src # Use config engine setting
|
|
109
|
+
$ sunlint --all --input=src --engine=eslint # Force ESLint engine
|
|
110
|
+
$ sunlint --all --input=src --engine=sunlint # Force SunLint engine
|
|
111
|
+
|
|
98
112
|
CI/CD Integration:
|
|
99
113
|
$ sunlint --all --changed-files --format=summary --no-ai
|
|
100
114
|
$ sunlint --all --changed-files --diff-base=origin/main --fail-on-new-violations
|
package/core/config-manager.js
CHANGED
|
@@ -166,7 +166,8 @@ class ConfigManager {
|
|
|
166
166
|
|
|
167
167
|
// 4. Auto-discover project config if not explicitly provided
|
|
168
168
|
let resolvedConfigPath = configPath;
|
|
169
|
-
if (!configPath
|
|
169
|
+
if (!configPath) {
|
|
170
|
+
// Only auto-discover if no config path was provided
|
|
170
171
|
const discoveredConfig = this.findConfigFile(cliOptions.input || process.cwd());
|
|
171
172
|
if (discoveredConfig) {
|
|
172
173
|
resolvedConfigPath = discoveredConfig;
|
|
@@ -174,6 +175,11 @@ class ConfigManager {
|
|
|
174
175
|
console.log(chalk.gray(`🔍 Auto-discovered config: ${discoveredConfig}`));
|
|
175
176
|
}
|
|
176
177
|
}
|
|
178
|
+
} else {
|
|
179
|
+
// Use the explicitly provided config path
|
|
180
|
+
if (cliOptions.verbose) {
|
|
181
|
+
console.log(chalk.gray(`📄 Using explicit config: ${configPath}`));
|
|
182
|
+
}
|
|
177
183
|
}
|
|
178
184
|
|
|
179
185
|
// 5. Load project config (explicit or discovered)
|
|
@@ -331,7 +337,7 @@ class ConfigManager {
|
|
|
331
337
|
if (extendPath.startsWith('@sun/sunlint/')) {
|
|
332
338
|
// Load preset from rules registry
|
|
333
339
|
const presetName = extendPath.replace('@sun/sunlint/', '');
|
|
334
|
-
const rulesRegistry = require('../config/rules-registry.json');
|
|
340
|
+
const rulesRegistry = require('../config/rules/rules-registry.json');
|
|
335
341
|
return this.presetResolver.resolvePresetFromRegistry(presetName, rulesRegistry);
|
|
336
342
|
} else {
|
|
337
343
|
// Load from file path
|
|
@@ -357,7 +363,7 @@ class ConfigManager {
|
|
|
357
363
|
* Rule C014: Delegate to validator
|
|
358
364
|
*/
|
|
359
365
|
getEffectiveRuleConfiguration(ruleId, config) {
|
|
360
|
-
const rulesRegistry = require('../config/rules-registry.json');
|
|
366
|
+
const rulesRegistry = require('../config/rules/rules-registry.json');
|
|
361
367
|
return this.validator.getEffectiveRuleConfiguration(ruleId, config, rulesRegistry);
|
|
362
368
|
}
|
|
363
369
|
|
package/core/config-merger.js
CHANGED
|
@@ -14,7 +14,16 @@ class ConfigMerger {
|
|
|
14
14
|
|
|
15
15
|
for (const [key, value] of Object.entries(override)) {
|
|
16
16
|
if (key === 'rules' && typeof value === 'object') {
|
|
17
|
-
|
|
17
|
+
if (Array.isArray(value)) {
|
|
18
|
+
// Convert array of rule names to object format
|
|
19
|
+
const rulesObj = {};
|
|
20
|
+
value.forEach(rule => {
|
|
21
|
+
rulesObj[rule] = 'warn';
|
|
22
|
+
});
|
|
23
|
+
merged.rules = { ...merged.rules, ...rulesObj };
|
|
24
|
+
} else {
|
|
25
|
+
merged.rules = { ...merged.rules, ...value };
|
|
26
|
+
}
|
|
18
27
|
} else if (key === 'categories' && typeof value === 'object') {
|
|
19
28
|
merged.categories = { ...merged.categories, ...value };
|
|
20
29
|
} else if (typeof value === 'object' && !Array.isArray(value)) {
|
|
@@ -34,6 +43,36 @@ class ConfigMerger {
|
|
|
34
43
|
applyCLIOverrides(config, options) {
|
|
35
44
|
const overrides = { ...config };
|
|
36
45
|
|
|
46
|
+
// Rules override - only override if config file didn't specify rules
|
|
47
|
+
if (options.rules && (!config.rules || Object.keys(config.rules).length === 0)) {
|
|
48
|
+
if (typeof options.rules === 'string') {
|
|
49
|
+
const ruleList = options.rules.split(',').map(r => r.trim());
|
|
50
|
+
overrides.rules = {};
|
|
51
|
+
ruleList.forEach(rule => {
|
|
52
|
+
overrides.rules[rule] = 'warn';
|
|
53
|
+
});
|
|
54
|
+
} else if (Array.isArray(options.rules)) {
|
|
55
|
+
overrides.rules = {};
|
|
56
|
+
options.rules.forEach(rule => {
|
|
57
|
+
overrides.rules[rule] = 'warn';
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Include/Input override - only if config file didn't specify
|
|
63
|
+
if (options.include && (!config.include || config.include.length === 0)) {
|
|
64
|
+
overrides.include = Array.isArray(options.include) ? options.include : [options.include];
|
|
65
|
+
}
|
|
66
|
+
if (options.input && (!config.input || config.input.length === 0)) {
|
|
67
|
+
overrides.input = Array.isArray(options.input) ? options.input : [options.input];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Exclude override - merge with config file
|
|
71
|
+
if (options.exclude) {
|
|
72
|
+
const excludeList = Array.isArray(options.exclude) ? options.exclude : [options.exclude];
|
|
73
|
+
overrides.exclude = [...new Set([...(config.exclude || []), ...excludeList])];
|
|
74
|
+
}
|
|
75
|
+
|
|
37
76
|
// Languages override
|
|
38
77
|
if (options.languages) {
|
|
39
78
|
overrides.languages = options.languages.split(',').map(l => l.trim());
|
package/core/config-validator.js
CHANGED
|
@@ -46,8 +46,8 @@ class ConfigValidator {
|
|
|
46
46
|
* Rule C031: Specific validation logic
|
|
47
47
|
*/
|
|
48
48
|
validateLanguagesSection(languages) {
|
|
49
|
-
if (languages && !Array.isArray(languages)) {
|
|
50
|
-
throw new Error('Config error: languages must be an array');
|
|
49
|
+
if (languages && !Array.isArray(languages) && typeof languages !== 'object') {
|
|
50
|
+
throw new Error('Config error: languages must be an array or object');
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency Checker
|
|
3
|
+
* Checks for optional peer dependencies and provides helpful messages
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class DependencyChecker {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.checkedDependencies = new Set();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Check if a dependency is available
|
|
13
|
+
*/
|
|
14
|
+
isDependencyAvailable(packageName) {
|
|
15
|
+
try {
|
|
16
|
+
require.resolve(packageName);
|
|
17
|
+
return true;
|
|
18
|
+
} catch (error) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Check for ESLint engine dependencies
|
|
25
|
+
*/
|
|
26
|
+
checkESLintDependencies() {
|
|
27
|
+
const dependencies = {
|
|
28
|
+
'eslint': { required: false, description: 'ESLint engine support' },
|
|
29
|
+
'@typescript-eslint/eslint-plugin': { required: false, description: 'TypeScript ESLint rules' },
|
|
30
|
+
'@typescript-eslint/parser': { required: false, description: 'TypeScript ESLint parsing' },
|
|
31
|
+
'typescript': { required: false, description: 'TypeScript compiler' }
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const missing = [];
|
|
35
|
+
const available = [];
|
|
36
|
+
|
|
37
|
+
for (const [pkg, info] of Object.entries(dependencies)) {
|
|
38
|
+
if (this.isDependencyAvailable(pkg)) {
|
|
39
|
+
available.push(pkg);
|
|
40
|
+
} else {
|
|
41
|
+
missing.push({ pkg, ...info });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return { available, missing };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Check for AST parsing dependencies
|
|
50
|
+
*/
|
|
51
|
+
checkASTDependencies() {
|
|
52
|
+
const dependencies = {
|
|
53
|
+
'@babel/parser': { required: false, description: 'JavaScript AST parsing' },
|
|
54
|
+
'espree': { required: false, description: 'ECMAScript AST parsing' }
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const missing = [];
|
|
58
|
+
const available = [];
|
|
59
|
+
|
|
60
|
+
for (const [pkg, info] of Object.entries(dependencies)) {
|
|
61
|
+
if (this.isDependencyAvailable(pkg)) {
|
|
62
|
+
available.push(pkg);
|
|
63
|
+
} else {
|
|
64
|
+
missing.push({ pkg, ...info });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { available, missing };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Show installation instructions for missing dependencies
|
|
73
|
+
*/
|
|
74
|
+
showInstallationInstructions(missing, context = 'general') {
|
|
75
|
+
if (missing.length === 0) return;
|
|
76
|
+
|
|
77
|
+
console.log('\n📦 Optional dependencies not found:');
|
|
78
|
+
|
|
79
|
+
for (const { pkg, description } of missing) {
|
|
80
|
+
console.log(` • ${pkg} - ${description}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const packages = missing.map(m => m.pkg).join(' ');
|
|
84
|
+
|
|
85
|
+
if (context === 'eslint') {
|
|
86
|
+
console.log('\n💡 To enable ESLint engine features, install:');
|
|
87
|
+
console.log(` npm install ${packages}`);
|
|
88
|
+
console.log('\n Or add to your project dependencies if you already have them.');
|
|
89
|
+
} else if (context === 'ast') {
|
|
90
|
+
console.log('\n💡 To enable AST analysis features, install:');
|
|
91
|
+
console.log(` npm install ${packages}`);
|
|
92
|
+
} else {
|
|
93
|
+
console.log('\n💡 To enable full functionality, install:');
|
|
94
|
+
console.log(` npm install ${packages}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.log('\n SunLint will continue using heuristic analysis.\n');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Check and notify about missing dependencies (only once per session)
|
|
102
|
+
*/
|
|
103
|
+
checkAndNotify(type = 'all') {
|
|
104
|
+
const key = `checked_${type}`;
|
|
105
|
+
if (this.checkedDependencies.has(key)) return;
|
|
106
|
+
|
|
107
|
+
this.checkedDependencies.add(key);
|
|
108
|
+
|
|
109
|
+
if (type === 'eslint' || type === 'all') {
|
|
110
|
+
const { missing } = this.checkESLintDependencies();
|
|
111
|
+
if (missing.length > 0) {
|
|
112
|
+
this.showInstallationInstructions(missing, 'eslint');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (type === 'ast' || type === 'all') {
|
|
117
|
+
const { missing } = this.checkASTDependencies();
|
|
118
|
+
if (missing.length > 0) {
|
|
119
|
+
this.showInstallationInstructions(missing, 'ast');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = new DependencyChecker();
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced Rules Registry
|
|
3
|
+
* Following Rule C005: Single responsibility - registry management
|
|
4
|
+
* Following Rule C015: Use domain language - clear registry terms
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
|
|
11
|
+
class EnhancedRulesRegistry {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.rulesRegistry = {};
|
|
14
|
+
this.enginePreferences = new Map();
|
|
15
|
+
this.ruleAnalyzers = new Map();
|
|
16
|
+
this.aiContexts = {};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Load enhanced rules registry
|
|
21
|
+
* Following Rule C006: Verb-noun naming
|
|
22
|
+
* @param {Object} options - Options including verbose flag
|
|
23
|
+
*/
|
|
24
|
+
async loadRegistry(options = {}) {
|
|
25
|
+
try {
|
|
26
|
+
// Load base rules registry
|
|
27
|
+
await this.loadBaseRulesRegistry(options);
|
|
28
|
+
|
|
29
|
+
// Load AI contexts
|
|
30
|
+
await this.loadAIContexts(options);
|
|
31
|
+
|
|
32
|
+
// Load engine preferences
|
|
33
|
+
await this.loadEnginePreferences(options);
|
|
34
|
+
|
|
35
|
+
// Scan for existing analyzers
|
|
36
|
+
await this.scanExistingAnalyzers(options);
|
|
37
|
+
|
|
38
|
+
if (options.verbose) {
|
|
39
|
+
if (options.verbose) {
|
|
40
|
+
console.log(chalk.green(`📋 Enhanced registry loaded: ${Object.keys(this.rulesRegistry).length} rules`));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error('Failed to load enhanced rules registry:', error.message);
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Load base rules registry
|
|
52
|
+
* Following Rule C006: Verb-noun naming
|
|
53
|
+
*/
|
|
54
|
+
async loadBaseRulesRegistry(options = {}) {
|
|
55
|
+
const registryPath = path.resolve(__dirname, '../config/rules/rules-registry.json');
|
|
56
|
+
|
|
57
|
+
if (fs.existsSync(registryPath)) {
|
|
58
|
+
const registry = require(registryPath);
|
|
59
|
+
this.rulesRegistry = registry.rules || {};
|
|
60
|
+
if (options.verbose) {
|
|
61
|
+
console.log(chalk.blue(`📋 Loaded ${Object.keys(this.rulesRegistry).length} base rules`));
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
console.warn('⚠️ Base rules registry not found at', registryPath);
|
|
65
|
+
this.rulesRegistry = {};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Load AI contexts
|
|
71
|
+
* Following Rule C006: Verb-noun naming
|
|
72
|
+
*/
|
|
73
|
+
async loadAIContexts(options = {}) {
|
|
74
|
+
const contextsPath = path.resolve(__dirname, '../config/defaults/ai-rules-context.json');
|
|
75
|
+
|
|
76
|
+
if (fs.existsSync(contextsPath)) {
|
|
77
|
+
const contexts = require(contextsPath);
|
|
78
|
+
this.aiContexts = contexts.contexts || {};
|
|
79
|
+
if (options.verbose) {
|
|
80
|
+
console.log(chalk.blue(`🤖 Loaded ${Object.keys(this.aiContexts).length} AI contexts`));
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
console.warn('⚠️ AI contexts not found at', contextsPath);
|
|
84
|
+
this.aiContexts = {};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Load engine preferences
|
|
90
|
+
* Following Rule C006: Verb-noun naming
|
|
91
|
+
*/
|
|
92
|
+
loadEnginePreferences(options = {}) {
|
|
93
|
+
// Define engine preferences based on rule complexity and capabilities
|
|
94
|
+
const preferences = {
|
|
95
|
+
// ESLint-friendly rules (static analysis)
|
|
96
|
+
'C001': ['eslint', 'heuristic', 'openai'],
|
|
97
|
+
'C002': ['eslint', 'heuristic', 'openai'],
|
|
98
|
+
'C003': ['heuristic', 'eslint', 'openai'],
|
|
99
|
+
'C004': ['eslint', 'heuristic', 'openai'],
|
|
100
|
+
'C006': ['eslint', 'heuristic', 'openai'],
|
|
101
|
+
'C007': ['eslint', 'heuristic', 'openai'],
|
|
102
|
+
'C014': ['eslint', 'heuristic', 'openai'],
|
|
103
|
+
'C033': ['eslint', 'heuristic'],
|
|
104
|
+
'C040': ['eslint', 'heuristic'],
|
|
105
|
+
|
|
106
|
+
// AI-enhanced rules (complex logic analysis)
|
|
107
|
+
'C005': ['openai', 'heuristic'],
|
|
108
|
+
'C012': ['openai', 'heuristic'],
|
|
109
|
+
'C015': ['openai', 'heuristic'],
|
|
110
|
+
'C032': ['openai', 'heuristic'],
|
|
111
|
+
'C034': ['openai', 'heuristic'],
|
|
112
|
+
'C035': ['openai', 'heuristic'],
|
|
113
|
+
'C037': ['openai', 'heuristic', 'eslint'],
|
|
114
|
+
'C038': ['openai', 'heuristic']
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
for (const [ruleId, engines] of Object.entries(preferences)) {
|
|
118
|
+
this.enginePreferences.set(ruleId, engines);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (options.verbose) {
|
|
122
|
+
console.log(chalk.blue(`⚙️ Loaded ${this.enginePreferences.size} engine preferences`));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Scan existing analyzers
|
|
128
|
+
* Following Rule C006: Verb-noun naming
|
|
129
|
+
*/
|
|
130
|
+
async scanExistingAnalyzers(options = {}) {
|
|
131
|
+
const rulesDir = path.resolve(__dirname, '../rules');
|
|
132
|
+
|
|
133
|
+
if (!fs.existsSync(rulesDir)) {
|
|
134
|
+
console.warn('⚠️ Rules directory not found');
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const ruleFolders = fs.readdirSync(rulesDir, { withFileTypes: true })
|
|
140
|
+
.filter(dirent => dirent.isDirectory())
|
|
141
|
+
.map(dirent => dirent.name);
|
|
142
|
+
|
|
143
|
+
for (const ruleFolder of ruleFolders) {
|
|
144
|
+
const ruleId = this.extractRuleId(ruleFolder);
|
|
145
|
+
const analyzerPath = path.join(rulesDir, ruleFolder, 'analyzer.js');
|
|
146
|
+
|
|
147
|
+
if (fs.existsSync(analyzerPath)) {
|
|
148
|
+
this.ruleAnalyzers.set(ruleId, {
|
|
149
|
+
path: analyzerPath,
|
|
150
|
+
folder: ruleFolder,
|
|
151
|
+
available: true
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (options.verbose) {
|
|
157
|
+
console.log(chalk.blue(`🔍 Found ${this.ruleAnalyzers.size} existing analyzers`));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
} catch (error) {
|
|
161
|
+
console.warn('⚠️ Failed to scan existing analyzers:', error.message);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Extract rule ID from folder name
|
|
167
|
+
* Following Rule C006: Verb-noun naming
|
|
168
|
+
*/
|
|
169
|
+
extractRuleId(folderName) {
|
|
170
|
+
// Extract from patterns like "C019_log_level_usage" or "S005_sql_injection"
|
|
171
|
+
const match = folderName.match(/^([CST]\d{3})/);
|
|
172
|
+
return match ? match[1] : folderName;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Get rule information with engine preferences
|
|
177
|
+
* Following Rule C006: Verb-noun naming
|
|
178
|
+
*/
|
|
179
|
+
getRuleInfo(ruleId) {
|
|
180
|
+
const baseRule = this.rulesRegistry[ruleId];
|
|
181
|
+
if (!baseRule) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
...baseRule,
|
|
187
|
+
id: ruleId,
|
|
188
|
+
enginePreferences: this.enginePreferences.get(ruleId) || ['heuristic', 'openai'],
|
|
189
|
+
hasAnalyzer: this.ruleAnalyzers.has(ruleId),
|
|
190
|
+
hasAIContext: this.aiContexts[ruleId] !== undefined,
|
|
191
|
+
aiContext: this.aiContexts[ruleId] || null,
|
|
192
|
+
analyzer: this.ruleAnalyzers.get(ruleId) || null
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Get rules by category
|
|
198
|
+
* Following Rule C006: Verb-noun naming
|
|
199
|
+
*/
|
|
200
|
+
getRulesByCategory(category) {
|
|
201
|
+
const rules = [];
|
|
202
|
+
|
|
203
|
+
for (const [ruleId, rule] of Object.entries(this.rulesRegistry)) {
|
|
204
|
+
if (rule.category === category) {
|
|
205
|
+
rules.push(this.getRuleInfo(ruleId));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return rules;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Get rules by engine preference
|
|
214
|
+
* Following Rule C006: Verb-noun naming
|
|
215
|
+
*/
|
|
216
|
+
getRulesByEngine(engineId) {
|
|
217
|
+
const rules = [];
|
|
218
|
+
|
|
219
|
+
for (const [ruleId, preferences] of this.enginePreferences.entries()) {
|
|
220
|
+
if (preferences.includes(engineId)) {
|
|
221
|
+
const ruleInfo = this.getRuleInfo(ruleId);
|
|
222
|
+
if (ruleInfo) {
|
|
223
|
+
rules.push(ruleInfo);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return rules;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Get all available rules
|
|
233
|
+
* Following Rule C006: Verb-noun naming
|
|
234
|
+
*/
|
|
235
|
+
getAllRules() {
|
|
236
|
+
const rules = [];
|
|
237
|
+
|
|
238
|
+
for (const ruleId of Object.keys(this.rulesRegistry)) {
|
|
239
|
+
const ruleInfo = this.getRuleInfo(ruleId);
|
|
240
|
+
if (ruleInfo) {
|
|
241
|
+
rules.push(ruleInfo);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return rules;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Get rules with analyzers
|
|
250
|
+
* Following Rule C006: Verb-noun naming
|
|
251
|
+
*/
|
|
252
|
+
getRulesWithAnalyzers() {
|
|
253
|
+
return this.getAllRules().filter(rule => rule.hasAnalyzer);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Get rules with AI context
|
|
258
|
+
* Following Rule C006: Verb-noun naming
|
|
259
|
+
*/
|
|
260
|
+
getRulesWithAIContext() {
|
|
261
|
+
return this.getAllRules().filter(rule => rule.hasAIContext);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Get registry statistics
|
|
266
|
+
* Following Rule C006: Verb-noun naming
|
|
267
|
+
*/
|
|
268
|
+
getRegistryStats() {
|
|
269
|
+
const allRules = this.getAllRules();
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
totalRules: allRules.length,
|
|
273
|
+
rulesWithAnalyzers: allRules.filter(r => r.hasAnalyzer).length,
|
|
274
|
+
rulesWithAIContext: allRules.filter(r => r.hasAIContext).length,
|
|
275
|
+
engineCoverage: {
|
|
276
|
+
heuristic: this.getRulesByEngine('heuristic').length,
|
|
277
|
+
openai: this.getRulesByEngine('openai').length,
|
|
278
|
+
eslint: this.getRulesByEngine('eslint').length
|
|
279
|
+
},
|
|
280
|
+
categories: this.getCategoryStats(allRules)
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Get category statistics
|
|
286
|
+
* Following Rule C006: Verb-noun naming
|
|
287
|
+
*/
|
|
288
|
+
getCategoryStats(rules) {
|
|
289
|
+
const categories = {};
|
|
290
|
+
|
|
291
|
+
for (const rule of rules) {
|
|
292
|
+
if (!categories[rule.category]) {
|
|
293
|
+
categories[rule.category] = 0;
|
|
294
|
+
}
|
|
295
|
+
categories[rule.category]++;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return categories;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Display registry overview
|
|
303
|
+
* Following Rule C006: Verb-noun naming
|
|
304
|
+
*/
|
|
305
|
+
displayOverview(options = {}) {
|
|
306
|
+
if (!options.verbose) return;
|
|
307
|
+
|
|
308
|
+
const stats = this.getRegistryStats();
|
|
309
|
+
|
|
310
|
+
console.log(chalk.blue.bold('📊 Enhanced Rules Registry Overview'));
|
|
311
|
+
console.log(chalk.gray('='.repeat(50)));
|
|
312
|
+
console.log(chalk.green(`📋 Total Rules: ${stats.totalRules}`));
|
|
313
|
+
console.log(chalk.blue(`🔍 With Analyzers: ${stats.rulesWithAnalyzers}`));
|
|
314
|
+
console.log(chalk.cyan(`🤖 With AI Context: ${stats.rulesWithAIContext}`));
|
|
315
|
+
console.log();
|
|
316
|
+
|
|
317
|
+
console.log(chalk.yellow('🎯 Engine Coverage:'));
|
|
318
|
+
Object.entries(stats.engineCoverage).forEach(([engine, count]) => {
|
|
319
|
+
console.log(` • ${engine}: ${count} rules`);
|
|
320
|
+
});
|
|
321
|
+
console.log();
|
|
322
|
+
|
|
323
|
+
console.log(chalk.yellow('📂 Categories:'));
|
|
324
|
+
Object.entries(stats.categories).forEach(([category, count]) => {
|
|
325
|
+
console.log(` • ${category}: ${count} rules`);
|
|
326
|
+
});
|
|
327
|
+
console.log();
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
module.exports = EnhancedRulesRegistry;
|