@sun-asterisk/sunlint 1.0.7 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/.sunlint.json +35 -0
  2. package/CHANGELOG.md +30 -3
  3. package/CONTRIBUTING.md +235 -0
  4. package/PROJECT_STRUCTURE.md +60 -0
  5. package/README.md +73 -52
  6. package/cli.js +1 -0
  7. package/config/README.md +88 -0
  8. package/config/defaults/ai-rules-context.json +231 -0
  9. package/config/engines/engines.json +49 -0
  10. package/config/engines/eslint-rule-mapping.json +74 -0
  11. package/config/eslint-rule-mapping.json +126 -0
  12. package/config/integrations/eslint/base.config.js +125 -0
  13. package/config/integrations/eslint/simple.config.js +24 -0
  14. package/config/presets/strict.json +0 -1
  15. package/config/rule-analysis-strategies.js +74 -0
  16. package/config/{rules-registry.json → rules/rules-registry.json} +22 -0
  17. package/core/analysis-orchestrator.js +383 -591
  18. package/core/ast-modules/README.md +103 -0
  19. package/core/ast-modules/base-parser.js +90 -0
  20. package/core/ast-modules/index.js +97 -0
  21. package/core/ast-modules/package.json +37 -0
  22. package/core/ast-modules/parsers/eslint-js-parser.js +147 -0
  23. package/core/ast-modules/parsers/eslint-ts-parser.js +106 -0
  24. package/core/ast-modules/parsers/javascript-parser.js +187 -0
  25. package/core/ast-modules/parsers/typescript-parser.js +187 -0
  26. package/core/cli-action-handler.js +271 -255
  27. package/core/cli-program.js +18 -4
  28. package/core/config-manager.js +9 -3
  29. package/core/config-merger.js +40 -1
  30. package/core/config-validator.js +2 -2
  31. package/core/enhanced-rules-registry.js +331 -0
  32. package/core/file-targeting-service.js +92 -23
  33. package/core/interfaces/analysis-engine.interface.js +100 -0
  34. package/core/multi-rule-runner.js +0 -221
  35. package/core/output-service.js +1 -1
  36. package/core/rule-mapping-service.js +1 -1
  37. package/core/rule-selection-service.js +10 -2
  38. package/docs/AI.md +163 -0
  39. package/docs/ARCHITECTURE.md +78 -0
  40. package/docs/CI-CD-GUIDE.md +315 -0
  41. package/docs/COMMAND-EXAMPLES.md +256 -0
  42. package/docs/CONFIGURATION.md +414 -0
  43. package/docs/DEBUG.md +86 -0
  44. package/docs/DEPLOYMENT-STRATEGIES.md +270 -0
  45. package/docs/DISTRIBUTION.md +153 -0
  46. package/docs/ESLINT-INTEGRATION-STRATEGY.md +392 -0
  47. package/docs/ESLINT_INTEGRATION.md +238 -0
  48. package/docs/FOLDER_STRUCTURE.md +59 -0
  49. package/docs/HEURISTIC_VS_AI.md +113 -0
  50. package/docs/README.md +32 -0
  51. package/docs/RELEASE_GUIDE.md +230 -0
  52. package/engines/eslint-engine.js +601 -0
  53. package/engines/heuristic-engine.js +860 -0
  54. package/engines/openai-engine.js +374 -0
  55. package/engines/tree-sitter-parser.js +0 -0
  56. package/engines/universal-ast-engine.js +0 -0
  57. package/integrations/eslint/README.md +99 -0
  58. package/integrations/eslint/configs/.eslintrc.js +98 -0
  59. package/integrations/eslint/configs/eslint.config.js +133 -0
  60. package/integrations/eslint/configs/eslint.config.simple.js +24 -0
  61. package/integrations/eslint/package.json +23 -0
  62. package/integrations/eslint/plugin/index.js +164 -0
  63. package/integrations/eslint/plugin/package.json +13 -0
  64. package/integrations/eslint/plugin/rules/common/c002-no-duplicate-code.js +204 -0
  65. package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +246 -0
  66. package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +216 -0
  67. package/integrations/eslint/plugin/rules/common/c010-limit-block-nesting.js +90 -0
  68. package/integrations/eslint/plugin/rules/common/c013-no-dead-code.js +78 -0
  69. package/integrations/eslint/plugin/rules/common/c014-abstract-dependency-preferred.js +38 -0
  70. package/integrations/eslint/plugin/rules/common/c017-limit-constructor-logic.js +146 -0
  71. package/integrations/eslint/plugin/rules/common/c018-no-generic-throw.js +335 -0
  72. package/integrations/eslint/plugin/rules/common/c023-no-duplicate-variable-name-in-scope.js +142 -0
  73. package/integrations/eslint/plugin/rules/common/c029-catch-block-logging.js +115 -0
  74. package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +294 -0
  75. package/integrations/eslint/plugin/rules/common/c035-no-empty-catch.js +162 -0
  76. package/integrations/eslint/plugin/rules/common/c041-no-config-inline.js +122 -0
  77. package/integrations/eslint/plugin/rules/common/c042-boolean-name-prefix.js +406 -0
  78. package/integrations/eslint/plugin/rules/common/c043-no-console-or-print.js +300 -0
  79. package/integrations/eslint/plugin/rules/common/c047-no-duplicate-retry-logic.js +239 -0
  80. package/integrations/eslint/plugin/rules/common/c072-one-assert-per-test.js +184 -0
  81. package/integrations/eslint/plugin/rules/common/c075-explicit-function-return-types.js +168 -0
  82. package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +254 -0
  83. package/integrations/eslint/plugin/rules/security/s001-fail-securely.js +381 -0
  84. package/integrations/eslint/plugin/rules/security/s002-idor-check.js +945 -0
  85. package/integrations/eslint/plugin/rules/security/s003-no-unvalidated-redirect.js +86 -0
  86. package/integrations/eslint/plugin/rules/security/s007-no-plaintext-otp.js +74 -0
  87. package/integrations/eslint/plugin/rules/security/s013-verify-tls-connection.js +47 -0
  88. package/integrations/eslint/plugin/rules/security/s047-secure-random-passwords.js +108 -0
  89. package/integrations/eslint/plugin/rules/security/s055-verification-rest-check-the-incoming-content-type.js +143 -0
  90. package/integrations/eslint/plugin/rules/typescript/t002-interface-prefix-i.js +42 -0
  91. package/integrations/eslint/plugin/rules/typescript/t003-ts-ignore-reason.js +48 -0
  92. package/integrations/eslint/plugin/rules/typescript/t004-no-empty-type.js +95 -0
  93. package/integrations/eslint/plugin/rules/typescript/t007-no-fn-in-constructor.js +52 -0
  94. package/integrations/eslint/plugin/rules/typescript/t010-no-nested-union-tuple.js +48 -0
  95. package/integrations/eslint/plugin/rules/typescript/t019-no-this-assign.js +81 -0
  96. package/integrations/eslint/plugin/rules/typescript/t020-no-default-multi-export.js +127 -0
  97. package/integrations/eslint/plugin/rules/typescript/t021-limit-nested-generics.js +150 -0
  98. package/integrations/eslint/test-c041-rule.js +87 -0
  99. package/integrations/eslint/tsconfig.json +27 -0
  100. package/package.json +29 -16
  101. package/rules/README.md +252 -0
  102. package/rules/common/C002_no_duplicate_code/analyzer.js +65 -0
  103. package/rules/common/C002_no_duplicate_code/config.json +23 -0
  104. package/rules/common/C003_no_vague_abbreviations/analyzer.js +418 -0
  105. package/rules/common/C003_no_vague_abbreviations/config.json +35 -0
  106. package/rules/{C006_function_naming → common/C006_function_naming}/analyzer.js +13 -2
  107. package/rules/common/C010_limit_block_nesting/analyzer.js +389 -0
  108. package/rules/common/C013_no_dead_code/analyzer.js +206 -0
  109. package/rules/common/C014_dependency_injection/analyzer.js +338 -0
  110. package/rules/common/C017_constructor_logic/analyzer.js +314 -0
  111. package/rules/{C019_log_level_usage → common/C019_log_level_usage}/analyzer.js +5 -2
  112. package/rules/{C029_catch_block_logging → common/C029_catch_block_logging}/analyzer.js +49 -15
  113. package/rules/common/C041_no_sensitive_hardcode/analyzer.js +292 -0
  114. package/rules/common/C042_boolean_name_prefix/analyzer.js +300 -0
  115. package/rules/common/C043_no_console_or_print/analyzer.js +304 -0
  116. package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +351 -0
  117. package/rules/common/C075_explicit_return_types/analyzer.js +103 -0
  118. package/rules/common/C076_single_test_behavior/analyzer.js +121 -0
  119. package/rules/docs/C002_no_duplicate_code.md +57 -0
  120. package/rules/index.js +149 -0
  121. package/rules/migration/converter.js +385 -0
  122. package/rules/migration/mapping.json +164 -0
  123. package/rules/security/S026_json_schema_validation/analyzer.js +251 -0
  124. package/rules/security/S026_json_schema_validation/config.json +27 -0
  125. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +263 -0
  126. package/rules/security/S027_no_hardcoded_secrets/config.json +29 -0
  127. package/rules/security/S029_csrf_protection/analyzer.js +264 -0
  128. package/rules/tests/C002_no_duplicate_code.test.js +50 -0
  129. package/rules/universal/C010/generic.js +0 -0
  130. package/rules/universal/C010/tree-sitter-analyzer.js +0 -0
  131. package/rules/utils/ast-utils.js +191 -0
  132. package/rules/utils/base-analyzer.js +98 -0
  133. package/rules/utils/pattern-matchers.js +239 -0
  134. package/rules/utils/rule-helpers.js +264 -0
  135. package/rules/utils/severity-constants.js +93 -0
  136. package/scripts/build-release.sh +117 -0
  137. package/scripts/ci-report.js +179 -0
  138. package/scripts/install.sh +196 -0
  139. package/scripts/manual-release.sh +338 -0
  140. package/scripts/merge-reports.js +424 -0
  141. package/scripts/pre-release-test.sh +175 -0
  142. package/scripts/prepare-release.sh +202 -0
  143. package/scripts/setup-github-registry.sh +42 -0
  144. package/scripts/test-scripts/README.md +22 -0
  145. package/scripts/test-scripts/test-c041-comparison.js +114 -0
  146. package/scripts/test-scripts/test-c041-eslint.js +67 -0
  147. package/scripts/test-scripts/test-eslint-rules.js +146 -0
  148. package/scripts/test-scripts/test-real-world.js +44 -0
  149. package/scripts/test-scripts/test-rules-on-real-projects.js +86 -0
  150. package/scripts/trigger-release.sh +285 -0
  151. package/scripts/validate-rule-structure.js +148 -0
  152. package/scripts/verify-install.sh +82 -0
  153. package/config/sunlint-schema.json +0 -159
  154. package/config/typescript/custom-rules.js +0 -9
  155. package/config/typescript/package-lock.json +0 -1585
  156. package/config/typescript/package.json +0 -13
  157. package/config/typescript/security-rules/index.js +0 -90
  158. package/config/typescript/tsconfig.json +0 -29
  159. package/core/ai-analyzer.js +0 -169
  160. package/core/eslint-engine-service.js +0 -312
  161. package/core/eslint-instance-manager.js +0 -104
  162. package/core/eslint-integration-service.js +0 -363
  163. package/core/sunlint-engine-service.js +0 -23
  164. package/core/typescript-analyzer.js +0 -262
  165. package/core/typescript-engine.js +0 -313
  166. /package/config/{default.json → defaults/default.json} +0 -0
  167. /package/config/{typescript/eslint.config.js → integrations/eslint/typescript.config.js} +0 -0
  168. /package/config/{typescript/custom-rules-new.js → schemas/sunlint-schema.json} +0 -0
  169. /package/config/{typescript → testing}/test-s005-working.ts +0 -0
  170. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s005-no-origin-auth.js +0 -0
  171. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s006-activation-recovery-secret-not-plaintext.js +0 -0
  172. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s008-crypto-agility.js +0 -0
  173. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s009-no-insecure-crypto.js +0 -0
  174. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s010-no-insecure-random-in-sensitive-context.js +0 -0
  175. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s011-no-insecure-uuid.js +0 -0
  176. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s012-hardcode-secret.js +0 -0
  177. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s014-insecure-tls-version.js +0 -0
  178. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s015-insecure-tls-certificate.js +0 -0
  179. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s016-sensitive-query-parameter.js +0 -0
  180. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s017-no-sql-injection.js +0 -0
  181. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s018-positive-input-validation.js +0 -0
  182. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s019-no-raw-user-input-in-email.js +0 -0
  183. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s020-no-eval-dynamic-execution.js +0 -0
  184. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s022-output-encoding.js +0 -0
  185. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s023-no-json-injection.js +0 -0
  186. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s025-server-side-input-validation.js +0 -0
  187. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s026-json-schema-validation.js +0 -0
  188. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s027-no-hardcoded-secrets.js +0 -0
  189. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s029-require-csrf-protection.js +0 -0
  190. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s030-no-directory-browsing.js +0 -0
  191. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s033-require-samesite-cookie.js +0 -0
  192. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s034-require-host-cookie-prefix.js +0 -0
  193. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s035-cookie-specific-path.js +0 -0
  194. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s036-no-unsafe-file-include.js +0 -0
  195. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s037-require-anti-cache-headers.js +0 -0
  196. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s038-no-version-disclosure.js +0 -0
  197. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s039-no-session-token-in-url.js +0 -0
  198. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s041-require-session-invalidate-on-logout.js +0 -0
  199. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s042-require-periodic-reauthentication.js +0 -0
  200. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s043-terminate-sessions-on-password-change.js +0 -0
  201. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s044-require-full-session-for-sensitive-operations.js +0 -0
  202. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s045-anti-automation-controls.js +0 -0
  203. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s046-secure-notification-on-auth-change.js +0 -0
  204. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s048-password-credential-recovery.js +0 -0
  205. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s050-session-token-weak-hash.js +0 -0
  206. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s052-secure-random-authentication-code.js +0 -0
  207. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s054-verification-default-account.js +0 -0
  208. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s057-utc-logging.js +0 -0
  209. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s058-no-ssrf.js +0 -0
  210. /package/rules/{C006_function_naming → common/C006_function_naming}/config.json +0 -0
  211. /package/rules/{C019_log_level_usage → common/C019_log_level_usage}/config.json +0 -0
  212. /package/rules/{C029_catch_block_logging → common/C029_catch_block_logging}/config.json +0 -0
  213. /package/rules/{C031_validation_separation → common/C031_validation_separation}/analyzer.js +0 -0
  214. /package/rules/{C031_validation_separation/README.md → docs/C031_validation_separation.md} +0 -0
@@ -0,0 +1,860 @@
1
+ /**
2
+ * Heuristic Analysis Engine Plugin
3
+ * Following Rule C005: Single responsibility - Pattern-based analysis with AST enhancement
4
+ * Following Rule C014: Dependency injection - implements interface
5
+ * Following Rule C015: Use domain language - clear heuristic analysis terms
6
+ */
7
+
8
+ const AnalysisEngineInterface = require('../core/interfaces/analysis-engine.interface');
9
+ const ASTModuleRegistry = require('../core/ast-modules/index');
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+
13
+ class HeuristicEngine extends AnalysisEngineInterface {
14
+ constructor() {
15
+ super('heuristic', '2.0', ['typescript', 'javascript', 'dart', 'swift', 'kotlin', 'java', 'python', 'go', 'rust', 'all']);
16
+
17
+ this.ruleAnalyzers = new Map();
18
+ this.supportedRulesList = [];
19
+ this.rulesRegistry = {};
20
+ this.astRegistry = ASTModuleRegistry;
21
+ }
22
+
23
+ /**
24
+ * Initialize Heuristic engine with configuration
25
+ * Following Rule C006: Verb-noun naming
26
+ * @param {Object} config - Engine configuration
27
+ */
28
+ async initialize(config) {
29
+ try {
30
+ // Store verbosity setting
31
+ this.verbose = config?.verbose || false;
32
+
33
+ // Load rules registry
34
+ await this.loadRulesRegistry(config);
35
+
36
+ // Scan for available rule analyzers
37
+ await this.scanRuleAnalyzers(config);
38
+
39
+ this.initialized = true;
40
+ if (this.verbose) {
41
+ console.log(`🔍 Heuristic engine v2.0 initialized with ${this.supportedRulesList.length} rules (AST-enhanced)`);
42
+ }
43
+
44
+ } catch (error) {
45
+ console.error('Failed to initialize Heuristic engine:', error.message);
46
+ throw error;
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Load rules registry configuration
52
+ * Following Rule C006: Verb-noun naming
53
+ */
54
+ async loadRulesRegistry(config = {}) {
55
+ try {
56
+ const registryPath = path.resolve(__dirname, '../config/rules/rules-registry.json');
57
+ if (fs.existsSync(registryPath)) {
58
+ this.rulesRegistry = require(registryPath);
59
+ if (config.verbose) {
60
+ if (this.verbose) {
61
+ console.log('📋 Loaded rules registry');
62
+ }
63
+ }
64
+ } else {
65
+ console.warn('⚠️ Rules registry not found, using minimal support');
66
+ this.rulesRegistry = { rules: {} };
67
+ }
68
+ } catch (error) {
69
+ console.warn('⚠️ Failed to load rules registry:', error.message);
70
+ this.rulesRegistry = { rules: {} };
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Scan for available rule analyzers
76
+ * Following Rule C006: Verb-noun naming
77
+ */
78
+ async scanRuleAnalyzers(config = {}) {
79
+ const rulesDir = path.resolve(__dirname, '../rules');
80
+
81
+ if (!fs.existsSync(rulesDir)) {
82
+ console.warn('⚠️ Rules directory not found');
83
+ return;
84
+ }
85
+
86
+ try {
87
+ // Scan category folders (common, security, typescript, etc.)
88
+ const categoryFolders = fs.readdirSync(rulesDir, { withFileTypes: true })
89
+ .filter(dirent => dirent.isDirectory())
90
+ .filter(dirent => !['tests', 'docs', 'utils', 'migration'].includes(dirent.name))
91
+ .map(dirent => dirent.name);
92
+
93
+ for (const categoryFolder of categoryFolders) {
94
+ const categoryPath = path.join(rulesDir, categoryFolder);
95
+
96
+ // Scan rule folders within category
97
+ const ruleFolders = fs.readdirSync(categoryPath, { withFileTypes: true })
98
+ .filter(dirent => dirent.isDirectory())
99
+ .map(dirent => dirent.name);
100
+
101
+ for (const ruleFolder of ruleFolders) {
102
+ const ruleId = this.extractRuleIdFromFolder(ruleFolder);
103
+ const analyzerPath = path.join(categoryPath, ruleFolder, 'analyzer.js');
104
+
105
+ if (fs.existsSync(analyzerPath)) {
106
+ try {
107
+ // Load analyzer dynamically - handle both class and instance exports
108
+ const analyzerModule = require(analyzerPath);
109
+ const analyzer = analyzerModule.default || analyzerModule;
110
+
111
+ // Check if it's a class constructor, instance, or factory function
112
+ if (typeof analyzer === 'function') {
113
+ // It's a class constructor
114
+ this.ruleAnalyzers.set(ruleId, {
115
+ path: analyzerPath,
116
+ class: analyzer,
117
+ folder: ruleFolder,
118
+ category: categoryFolder,
119
+ type: 'class'
120
+ });
121
+ this.supportedRulesList.push(ruleId);
122
+ } else if (analyzer && typeof analyzer === 'object' && analyzer.analyze) {
123
+ // It's an analyzer instance with analyze method
124
+ this.ruleAnalyzers.set(ruleId, {
125
+ path: analyzerPath,
126
+ instance: analyzer,
127
+ folder: ruleFolder,
128
+ category: categoryFolder,
129
+ type: 'instance'
130
+ });
131
+ this.supportedRulesList.push(ruleId);
132
+ } else {
133
+ console.warn(`⚠️ Analyzer for ${ruleId} has unsupported format:`, typeof analyzer);
134
+ }
135
+
136
+ } catch (error) {
137
+ console.warn(`⚠️ Failed to load analyzer for ${ruleId}:`, error.message);
138
+ }
139
+ }
140
+ }
141
+ }
142
+
143
+ if (config.verbose) {
144
+ if (this.verbose) {
145
+ console.log(`🔍 Found ${this.supportedRulesList.length} heuristic analyzers`);
146
+ console.log(`🔍 Supported rules: ${this.supportedRulesList.join(', ')}`);
147
+ }
148
+ }
149
+
150
+ } catch (error) {
151
+ console.error('Failed to scan rule analyzers:', error.message);
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Extract rule ID from folder name
157
+ * Following Rule C006: Verb-noun naming
158
+ * @param {string} folderName - Rule folder name
159
+ * @returns {string} Rule ID
160
+ */
161
+ extractRuleIdFromFolder(folderName) {
162
+ // Extract rule ID from patterns like "C019_log_level_usage"
163
+ const match = folderName.match(/^([CST]\d{3})/);
164
+ return match ? match[1] : folderName;
165
+ }
166
+
167
+ /**
168
+ * Analyze files using heuristic patterns
169
+ * Following Rule C006: Verb-noun naming
170
+ * @param {string[]} files - Files to analyze
171
+ * @param {Object[]} rules - Rules to apply
172
+ * @param {Object} options - Analysis options
173
+ * @returns {Promise<Object>} Analysis results
174
+ */
175
+ async analyze(files, rules, options) {
176
+ if (!this.initialized) {
177
+ throw new Error('Heuristic engine not initialized');
178
+ }
179
+
180
+ if (options.verbose) {
181
+ console.log(`🔍 [HeuristicEngine] Analyzing ${files.length} files with ${rules.length} rules`);
182
+ console.log(`🔍 [HeuristicEngine] Files: ${files.map(f => path.basename(f)).join(', ')}`);
183
+ console.log(`🔍 [HeuristicEngine] Rules: ${rules.map(r => r.id).join(', ')}`);
184
+ }
185
+
186
+ const results = {
187
+ results: [],
188
+ filesAnalyzed: files.length,
189
+ engine: 'heuristic',
190
+ metadata: {
191
+ rulesAnalyzed: rules.map(r => r.id),
192
+ analyzersUsed: []
193
+ }
194
+ };
195
+
196
+ // Group files by language for efficient processing
197
+ const filesByLanguage = this.groupFilesByLanguage(files);
198
+
199
+ for (const rule of rules) {
200
+ if (!this.isRuleSupported(rule.id)) {
201
+ if (options.verbose) {
202
+ console.warn(`⚠️ Rule ${rule.id} not supported by Heuristic engine, skipping...`);
203
+ }
204
+ continue;
205
+ }
206
+
207
+ try {
208
+ const ruleViolations = await this.analyzeRule(rule, filesByLanguage, options);
209
+
210
+ if (ruleViolations.length > 0) {
211
+ // Group violations by file
212
+ const violationsByFile = this.groupViolationsByFile(ruleViolations);
213
+
214
+ for (const [filePath, violations] of violationsByFile) {
215
+ // Find or create file result
216
+ let fileResult = results.results.find(r => r.file === filePath);
217
+ if (!fileResult) {
218
+ fileResult = { file: filePath, violations: [] };
219
+ results.results.push(fileResult);
220
+ }
221
+ fileResult.violations.push(...violations);
222
+ }
223
+ }
224
+
225
+ results.metadata.analyzersUsed.push(rule.id);
226
+
227
+ } catch (error) {
228
+ console.error(`❌ Failed to analyze rule ${rule.id}:`, error.message);
229
+ // Continue with other rules
230
+ }
231
+ }
232
+
233
+ return results;
234
+ }
235
+
236
+ /**
237
+ * Analyze a specific rule across files
238
+ * Following Rule C006: Verb-noun naming
239
+ * @param {Object} rule - Rule to analyze
240
+ * @param {Object} filesByLanguage - Files grouped by language
241
+ * @param {Object} options - Analysis options
242
+ * @returns {Promise<Object[]>} Rule violations
243
+ */
244
+ async analyzeRule(rule, filesByLanguage, options) {
245
+ const analyzerInfo = this.ruleAnalyzers.get(rule.id);
246
+ if (!analyzerInfo) {
247
+ return [];
248
+ }
249
+
250
+ try {
251
+ // Get analyzer - handle both class and instance types
252
+ const analyzerInfo = this.ruleAnalyzers.get(rule.id);
253
+ let analyzer;
254
+
255
+ if (analyzerInfo.type === 'class') {
256
+ // Create analyzer instance from class
257
+ const AnalyzerClass = analyzerInfo.class;
258
+ try {
259
+ analyzer = new AnalyzerClass();
260
+ } catch (constructorError) {
261
+ throw new Error(`Failed to instantiate analyzer class: ${constructorError.message}`);
262
+ }
263
+ } else if (analyzerInfo.type === 'instance') {
264
+ // Use existing analyzer instance
265
+ analyzer = analyzerInfo.instance;
266
+ } else {
267
+ throw new Error(`Unknown analyzer type: ${analyzerInfo.type}`);
268
+ }
269
+
270
+ // Verify analyzer has required methods
271
+ if (!analyzer.analyze || typeof analyzer.analyze !== 'function') {
272
+ console.warn(`⚠️ Analyzer for ${rule.id} missing analyze method`);
273
+ return [];
274
+ }
275
+
276
+ const allViolations = [];
277
+
278
+ // Run analyzer for each supported language
279
+ const ruleLanguages = this.getRuleLanguages(rule);
280
+ for (const language of ruleLanguages) {
281
+ const languageFiles = filesByLanguage[language] || [];
282
+ if (languageFiles.length === 0) continue;
283
+
284
+ try {
285
+ // Load rule config
286
+ const ruleConfig = await this.loadRuleConfig(rule.id, analyzerInfo.folder, analyzerInfo.category);
287
+
288
+ // Run analysis with AST enhancement
289
+ if (options.verbose) {
290
+ console.log(`🔧 [DEBUG] About to call runEnhancedAnalysis for rule ${rule.id}, language ${language}`);
291
+ }
292
+
293
+ const languageViolations = await this.runEnhancedAnalysis(
294
+ analyzer,
295
+ rule.id,
296
+ languageFiles,
297
+ language,
298
+ { ...ruleConfig, ...options }
299
+ );
300
+
301
+ allViolations.push(...languageViolations);
302
+
303
+ } catch (error) {
304
+ console.error(`❌ Rule ${rule.id} failed for ${language}:`, error.message);
305
+ }
306
+ }
307
+
308
+ return allViolations;
309
+
310
+ } catch (error) {
311
+ console.error(`❌ Failed to create analyzer for rule ${rule.id}:`, error.message);
312
+ return [];
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Get supported languages for a rule
318
+ * Following Rule C006: Verb-noun naming
319
+ * @param {Object} rule - Rule object
320
+ * @returns {string[]} Supported languages
321
+ */
322
+ getRuleLanguages(rule) {
323
+ // Get from rules registry
324
+ const registryRule = this.rulesRegistry.rules?.[rule.id];
325
+ if (registryRule?.languages) {
326
+ return registryRule.languages;
327
+ }
328
+
329
+ // Fallback to rule object
330
+ if (rule.languages) {
331
+ return rule.languages;
332
+ }
333
+
334
+ // Default to common languages
335
+ return ['typescript', 'javascript'];
336
+ }
337
+
338
+ /**
339
+ * Load rule configuration
340
+ * Following Rule C006: Verb-noun naming
341
+ * @param {string} ruleId - Rule ID
342
+ * @param {string} ruleFolder - Rule folder name
343
+ * @param {string} category - Rule category (common, security, etc)
344
+ * @returns {Promise<Object>} Rule configuration
345
+ */
346
+ async loadRuleConfig(ruleId, ruleFolder, category = 'common') {
347
+ try {
348
+ const configPath = path.resolve(__dirname, '../rules', category, ruleFolder, 'config.json');
349
+ if (fs.existsSync(configPath)) {
350
+ return require(configPath);
351
+ }
352
+ } catch (error) {
353
+ console.warn(`⚠️ Failed to load config for ${ruleId}:`, error.message);
354
+ }
355
+
356
+ // Return minimal config
357
+ return {
358
+ ruleId,
359
+ name: `Rule ${ruleId}`,
360
+ description: `Analysis for rule ${ruleId}`,
361
+ severity: 'warning'
362
+ };
363
+ }
364
+
365
+ /**
366
+ * Group files by programming language
367
+ * Following Rule C006: Verb-noun naming
368
+ * @param {string[]} files - Files to group
369
+ * @returns {Object} Files grouped by language
370
+ */
371
+ groupFilesByLanguage(files) {
372
+ const groups = {};
373
+
374
+ for (const file of files) {
375
+ const language = this.detectLanguage(file);
376
+ if (!groups[language]) {
377
+ groups[language] = [];
378
+ }
379
+ groups[language].push(file);
380
+ }
381
+
382
+ return groups;
383
+ }
384
+
385
+ /**
386
+ * Detect programming language from file extension
387
+ * Following Rule C006: Verb-noun naming
388
+ * @param {string} filePath - File path
389
+ * @returns {string} Detected language
390
+ */
391
+ detectLanguage(filePath) {
392
+ const ext = path.extname(filePath).toLowerCase();
393
+
394
+ switch (ext) {
395
+ case '.ts': case '.tsx': return 'typescript';
396
+ case '.js': case '.jsx': return 'javascript';
397
+ case '.dart': return 'dart';
398
+ case '.swift': return 'swift';
399
+ case '.kt': case '.kts': return 'kotlin';
400
+ default: return 'unknown';
401
+ }
402
+ }
403
+
404
+ /**
405
+ * Group violations by file path
406
+ * Following Rule C006: Verb-noun naming
407
+ * @param {Object[]} violations - Array of violations
408
+ * @returns {Map} Violations grouped by file
409
+ */
410
+ groupViolationsByFile(violations) {
411
+ const groups = new Map();
412
+
413
+ for (const violation of violations) {
414
+ const filePath = violation.file || violation.filePath;
415
+ if (!filePath) continue;
416
+
417
+ if (!groups.has(filePath)) {
418
+ groups.set(filePath, []);
419
+ }
420
+ groups.get(filePath).push(violation);
421
+ }
422
+
423
+ return groups;
424
+ }
425
+
426
+ /**
427
+ * Get supported rules
428
+ * Following Rule C006: Verb-noun naming
429
+ * @returns {string[]} Supported rule IDs
430
+ */
431
+ getSupportedRules() {
432
+ return this.supportedRulesList;
433
+ }
434
+
435
+ /**
436
+ * Run enhanced analysis with multiple strategies
437
+ * Automatically selects optimal analysis method per rule
438
+ * Following Rule C006: Verb-noun naming
439
+ */
440
+ async runEnhancedAnalysis(analyzer, ruleId, files, language, options) {
441
+ // Create debug config from options
442
+ const debugConfig = {
443
+ enabled: options.debug || options.verbose || false,
444
+ logger: (component, message) => console.log(`🔧 [${component}] ${message}`)
445
+ };
446
+
447
+ // Debug logging based on debug flag
448
+ if (debugConfig.enabled) {
449
+ debugConfig.logger(this.constructor.name, `runEnhancedAnalysis called: rule=${ruleId}, language=${language}, files=${files.length}`);
450
+ }
451
+
452
+ if (options.verbose) {
453
+ console.log(`🔧 [DEBUG] runEnhancedAnalysis called: rule=${ruleId}, language=${language}, files=${files.length}`);
454
+ }
455
+
456
+ // Load rule analysis strategy
457
+ const strategy = await this.getRuleAnalysisStrategy(ruleId, language);
458
+
459
+ // Debug logging based on debug flag
460
+ if (debugConfig.enabled) {
461
+ debugConfig.logger(this.constructor.name, `Rule ${ruleId}: ${strategy.primary} (approach: ${strategy.approach})`);
462
+ }
463
+
464
+ if (options.verbose) {
465
+ console.log(`🔧 [Strategy] Rule ${ruleId}: ${strategy.primary} (fallback: ${strategy.fallback || 'none'})`);
466
+ }
467
+
468
+ let violations = [];
469
+ let analysisResults = {
470
+ methods: [],
471
+ totalViolations: 0,
472
+ accuracy: 'unknown'
473
+ };
474
+
475
+ // Execute analysis based on strategy
476
+ switch (strategy.approach) {
477
+ case 'ast-primary':
478
+ violations = await this.runASTPrimaryAnalysis(analyzer, ruleId, files, language, options, strategy, debugConfig);
479
+ break;
480
+
481
+ case 'regex-optimal':
482
+ violations = await this.runRegexOptimalAnalysis(analyzer, ruleId, files, language, options);
483
+ break;
484
+
485
+ case 'hybrid-combined':
486
+ violations = await this.runHybridAnalysis(analyzer, ruleId, files, language, options, strategy);
487
+ break;
488
+
489
+ case 'progressive-enhancement':
490
+ violations = await this.runProgressiveAnalysis(analyzer, ruleId, files, language, options, strategy);
491
+ break;
492
+
493
+ default:
494
+ // Fallback to existing logic
495
+ violations = await this.runLegacyAnalysis(analyzer, ruleId, files, language, options);
496
+ }
497
+
498
+ if (options.verbose && violations.length > 0) {
499
+ console.log(`📊 [Analysis] Found ${violations.length} violations using ${strategy.approach}`);
500
+ }
501
+
502
+ return violations;
503
+ }
504
+
505
+ /**
506
+ * Get optimal analysis strategy for a rule
507
+ */
508
+ async getRuleAnalysisStrategy(ruleId, language) {
509
+ try {
510
+ const strategies = require('../config/rule-analysis-strategies');
511
+
512
+ // Check AST-preferred rules
513
+ if (strategies.astPreferred[ruleId]) {
514
+ const astAvailable = this.astRegistry.isASTSupportAvailable(language);
515
+ return {
516
+ approach: 'ast-primary',
517
+ primary: 'ast',
518
+ fallback: 'regex',
519
+ astAvailable,
520
+ config: strategies.astPreferred[ruleId]
521
+ };
522
+ }
523
+
524
+ // Check regex-optimal rules
525
+ if (strategies.regexOptimal[ruleId]) {
526
+ return {
527
+ approach: 'regex-optimal',
528
+ primary: 'regex',
529
+ config: strategies.regexOptimal[ruleId]
530
+ };
531
+ }
532
+
533
+ // Check hybrid rules
534
+ if (strategies.hybridOptimal[ruleId]) {
535
+ return {
536
+ approach: 'hybrid-combined',
537
+ primary: strategies.hybridOptimal[ruleId].strategy.split('-')[0],
538
+ config: strategies.hybridOptimal[ruleId]
539
+ };
540
+ }
541
+
542
+ // Check experimental rules
543
+ if (strategies.experimental[ruleId]) {
544
+ return {
545
+ approach: 'progressive-enhancement',
546
+ primary: 'regex',
547
+ fallback: 'ast',
548
+ config: strategies.experimental[ruleId]
549
+ };
550
+ }
551
+
552
+ // Default strategy
553
+ return {
554
+ approach: 'ast-primary',
555
+ primary: 'ast',
556
+ fallback: 'regex',
557
+ astAvailable: this.astRegistry.isASTSupportAvailable(language)
558
+ };
559
+
560
+ } catch (error) {
561
+ // Fallback if strategy config is not available
562
+ return {
563
+ approach: 'ast-primary',
564
+ primary: 'ast',
565
+ fallback: 'regex',
566
+ astAvailable: this.astRegistry.isASTSupportAvailable(language)
567
+ };
568
+ }
569
+ }
570
+
571
+ /**
572
+ * AST-primary analysis: Try AST first, fallback to regex
573
+ */
574
+ async runASTPrimaryAnalysis(analyzer, ruleId, files, language, options, strategy, debugConfig) {
575
+ const violations = [];
576
+
577
+ if (debugConfig.enabled) {
578
+ debugConfig.logger(this.constructor.name, `Starting AST-primary analysis for ${ruleId}, AST available: ${strategy.astAvailable}`);
579
+ }
580
+
581
+ for (const filePath of files) {
582
+ try {
583
+ const code = fs.readFileSync(filePath, 'utf8');
584
+ let analysisResult = null;
585
+
586
+ // Try AST analysis first if available
587
+ if (strategy.astAvailable) {
588
+ if (debugConfig.enabled) {
589
+ debugConfig.logger(this.constructor.name, `Attempting AST for file: ${filePath}`);
590
+ }
591
+
592
+ try {
593
+ const astResult = await this.astRegistry.analyzeRule(ruleId, code, language, filePath);
594
+ if (astResult && astResult.length > 0) {
595
+ analysisResult = astResult.map(violation => ({
596
+ ...violation,
597
+ filePath,
598
+ analysisMethod: 'ast',
599
+ confidence: strategy.config?.accuracy?.ast || 90
600
+ }));
601
+
602
+ if (debugConfig.enabled) {
603
+ debugConfig.logger(this.constructor.name, `AST found ${astResult.length} violations in ${filePath}`);
604
+ }
605
+ }
606
+ } catch (astError) {
607
+ if (debugConfig.enabled) {
608
+ debugConfig.logger(this.constructor.name, `AST failed for ${filePath}: ${astError.message}`);
609
+ }
610
+
611
+ if (options.verbose) {
612
+ console.warn(`⚠️ AST analysis failed for ${filePath}, falling back to regex`);
613
+ }
614
+ }
615
+ } else {
616
+ if (debugConfig.enabled) {
617
+ debugConfig.logger(this.constructor.name, `AST not available, skipping to fallback for ${filePath}`);
618
+ }
619
+ }
620
+
621
+ // Fallback to regex if AST failed or not available
622
+ if (!analysisResult) {
623
+ if (debugConfig.enabled) {
624
+ debugConfig.logger(this.constructor.name, `Using regex fallback for ${filePath}`);
625
+ }
626
+ const regexResult = await analyzer.analyze([filePath], language, options);
627
+ if (regexResult && regexResult.length > 0) {
628
+ analysisResult = regexResult.map(violation => ({
629
+ ...violation,
630
+ analysisMethod: 'regex',
631
+ confidence: strategy.config?.accuracy?.regex || 75
632
+ }));
633
+ }
634
+ }
635
+
636
+ if (analysisResult) {
637
+ violations.push(...analysisResult);
638
+ }
639
+
640
+ } catch (error) {
641
+ if (options.verbose) {
642
+ console.error(`❌ Analysis failed for ${filePath}:`, error.message);
643
+ }
644
+ }
645
+ }
646
+
647
+ return violations;
648
+ }
649
+
650
+ /**
651
+ * Regex-optimal analysis: Use regex as primary method
652
+ */
653
+ async runRegexOptimalAnalysis(analyzer, ruleId, files, language, options) {
654
+ const violations = [];
655
+
656
+ const regexResult = await analyzer.analyze(files, language, options);
657
+ if (regexResult && regexResult.length > 0) {
658
+ violations.push(...regexResult.map(violation => ({
659
+ ...violation,
660
+ analysisMethod: 'regex',
661
+ confidence: 95 // High confidence for regex-optimal rules
662
+ })));
663
+ }
664
+
665
+ return violations;
666
+ }
667
+
668
+ /**
669
+ * Hybrid analysis: Combine multiple methods for best results
670
+ */
671
+ async runHybridAnalysis(analyzer, ruleId, files, language, options, strategy) {
672
+ const violations = [];
673
+ const astViolations = [];
674
+ const regexViolations = [];
675
+
676
+ for (const filePath of files) {
677
+ try {
678
+ const code = fs.readFileSync(filePath, 'utf8');
679
+
680
+ // Run both AST and regex analysis
681
+ const analysisPromises = [];
682
+
683
+ // AST analysis
684
+ if (this.astRegistry.isASTSupportAvailable(language)) {
685
+ analysisPromises.push(
686
+ this.astRegistry.analyzeRule(ruleId, code, language, filePath)
687
+ .then(result => ({ type: 'ast', result, filePath }))
688
+ .catch(() => ({ type: 'ast', result: null, filePath }))
689
+ );
690
+ }
691
+
692
+ // Regex analysis
693
+ analysisPromises.push(
694
+ analyzer.analyze([filePath], language, options)
695
+ .then(result => ({ type: 'regex', result, filePath }))
696
+ .catch(() => ({ type: 'regex', result: null, filePath }))
697
+ );
698
+
699
+ const results = await Promise.all(analysisPromises);
700
+
701
+ // Process results based on strategy
702
+ for (const { type, result, filePath } of results) {
703
+ if (result && result.length > 0) {
704
+ const violations = result.map(violation => ({
705
+ ...violation,
706
+ filePath,
707
+ analysisMethod: type,
708
+ confidence: strategy.config?.accuracy?.[type] || 85
709
+ }));
710
+
711
+ if (type === 'ast') {
712
+ astViolations.push(...violations);
713
+ } else {
714
+ regexViolations.push(...violations);
715
+ }
716
+ }
717
+ }
718
+
719
+ } catch (error) {
720
+ if (options.verbose) {
721
+ console.error(`❌ Hybrid analysis failed for ${filePath}:`, error.message);
722
+ }
723
+ }
724
+ }
725
+
726
+ // Combine results intelligently
727
+ const combinedViolations = this.combineHybridResults(
728
+ astViolations,
729
+ regexViolations,
730
+ strategy.config?.strategy || 'ast-primary-regex-fallback'
731
+ );
732
+
733
+ return combinedViolations;
734
+ }
735
+
736
+ /**
737
+ * Progressive enhancement: Start simple, enhance with advanced methods
738
+ */
739
+ async runProgressiveAnalysis(analyzer, ruleId, files, language, options, strategy) {
740
+ const violations = [];
741
+
742
+ // Start with basic regex analysis
743
+ const regexResult = await analyzer.analyze(files, language, options);
744
+ if (regexResult) {
745
+ violations.push(...regexResult.map(violation => ({
746
+ ...violation,
747
+ analysisMethod: 'regex',
748
+ confidence: 75
749
+ })));
750
+ }
751
+
752
+ // Enhance with AST if available and beneficial
753
+ if (this.astRegistry.isASTSupportAvailable(language) && violations.length > 0) {
754
+ // TODO: Implement AST enhancement for specific violation types
755
+ // This could involve re-analyzing files with violations for better precision
756
+ }
757
+
758
+ return violations;
759
+ }
760
+
761
+ /**
762
+ * Legacy analysis method (for backward compatibility)
763
+ */
764
+ async runLegacyAnalysis(analyzer, ruleId, files, language, options) {
765
+ const regexResult = await analyzer.analyze(files, language, options);
766
+ return regexResult || [];
767
+ }
768
+
769
+ /**
770
+ * Intelligently combine AST and regex results
771
+ */
772
+ combineHybridResults(astViolations, regexViolations, strategy) {
773
+ switch (strategy) {
774
+ case 'ast-primary-regex-fallback':
775
+ // Use AST results where available, fill gaps with regex
776
+ return this.mergeViolationsWithPriority(astViolations, regexViolations, 'ast');
777
+
778
+ case 'regex-primary-ast-enhancement':
779
+ // Use regex as base, enhance with AST insights
780
+ return this.mergeViolationsWithPriority(regexViolations, astViolations, 'regex');
781
+
782
+ case 'union':
783
+ // Combine all violations (may have duplicates)
784
+ return [...astViolations, ...regexViolations];
785
+
786
+ case 'intersection':
787
+ // Only violations found by both methods
788
+ return this.findIntersectionViolations(astViolations, regexViolations);
789
+
790
+ default:
791
+ return astViolations.length > 0 ? astViolations : regexViolations;
792
+ }
793
+ }
794
+
795
+ /**
796
+ * Merge violations with priority given to one method
797
+ */
798
+ mergeViolationsWithPriority(primary, secondary, primaryType) {
799
+ const merged = [...primary];
800
+ const primaryLocations = new Set(
801
+ primary.map(v => `${v.filePath}:${v.line}:${v.column}`)
802
+ );
803
+
804
+ // Add secondary violations that don't conflict with primary
805
+ for (const violation of secondary) {
806
+ const location = `${violation.filePath}:${violation.line}:${violation.column}`;
807
+ if (!primaryLocations.has(location)) {
808
+ merged.push({
809
+ ...violation,
810
+ isSecondary: true,
811
+ primaryMethod: primaryType
812
+ });
813
+ }
814
+ }
815
+
816
+ return merged;
817
+ }
818
+
819
+ /**
820
+ * Find violations detected by both methods (high confidence)
821
+ */
822
+ findIntersectionViolations(astViolations, regexViolations) {
823
+ const intersection = [];
824
+
825
+ for (const astViolation of astViolations) {
826
+ const matching = regexViolations.find(regexViolation =>
827
+ astViolation.filePath === regexViolation.filePath &&
828
+ Math.abs(astViolation.line - regexViolation.line) <= 2 // Allow small line differences
829
+ );
830
+
831
+ if (matching) {
832
+ intersection.push({
833
+ ...astViolation,
834
+ analysisMethod: 'hybrid-intersection',
835
+ confidence: 98, // Very high confidence
836
+ confirmedBy: ['ast', 'regex']
837
+ });
838
+ }
839
+ }
840
+
841
+ return intersection;
842
+ }
843
+
844
+ /**
845
+ * Cleanup Heuristic engine resources
846
+ * Following Rule C006: Verb-noun naming
847
+ */
848
+ async cleanup() {
849
+ // Clear analyzer cache
850
+ this.ruleAnalyzers.clear();
851
+ this.supportedRulesList = [];
852
+
853
+ await super.cleanup();
854
+ if (this.verbose) {
855
+ console.log('🔍 Heuristic engine cleanup completed');
856
+ }
857
+ }
858
+ }
859
+
860
+ module.exports = HeuristicEngine;