@sun-asterisk/sunlint 1.3.36 → 1.3.37

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 (103) hide show
  1. package/cli.js +33 -0
  2. package/config/rules/enhanced-rules-registry.json +354 -98
  3. package/config/rules/rules-registry-generated.json +197 -171
  4. package/core/architecture-integration.js +115 -17
  5. package/core/cli-action-handler.js +101 -27
  6. package/core/cli-program.js +5 -0
  7. package/core/github-annotate-service.js +62 -0
  8. package/core/impact-integration.js +31 -16
  9. package/core/init-command.js +227 -0
  10. package/core/output-service.js +53 -5
  11. package/core/summary-report-service.js +46 -0
  12. package/core/unified-rule-registry.js +2 -1
  13. package/engines/eslint-engine.js +6 -0
  14. package/engines/impact/core/detectors/database-detector.js +1 -1
  15. package/engines/impact/core/detectors/endpoint-detector.js +1 -1
  16. package/engines/impact/core/report-generator.js +235 -73
  17. package/origin-rules/security-en.md +470 -282
  18. package/package.json +1 -1
  19. package/rules/security/S001_backend_auth_communications/dart/analyzer.js +44 -0
  20. package/rules/security/S001_backend_auth_communications/index.js +87 -0
  21. package/rules/security/S001_backend_auth_communications/typescript/analyzer.js +164 -0
  22. package/rules/security/S002_os_command_injection/dart/analyzer.js +44 -0
  23. package/rules/security/S002_os_command_injection/index.js +87 -0
  24. package/rules/security/S002_os_command_injection/typescript/analyzer.js +194 -0
  25. package/rules/security/S008_svg_content_validation/dart/analyzer.js +44 -0
  26. package/rules/security/S008_svg_content_validation/index.js +87 -0
  27. package/rules/security/S008_svg_content_validation/typescript/analyzer.js +216 -0
  28. package/rules/security/S018_no_sensitive_browser_storage/dart/analyzer.js +44 -0
  29. package/rules/security/S018_no_sensitive_browser_storage/index.js +86 -0
  30. package/rules/security/S018_no_sensitive_browser_storage/typescript/analyzer.js +193 -0
  31. package/rules/security/S021_referrer_policy/dart/analyzer.js +44 -0
  32. package/rules/security/S021_referrer_policy/index.js +86 -0
  33. package/rules/security/S021_referrer_policy/typescript/analyzer.js +183 -0
  34. package/rules/security/S023_no_json_injection/config.json +133 -44
  35. package/rules/security/S023_no_json_injection/dart/analyzer.js +7 -6
  36. package/rules/security/S023_no_json_injection/typescript/analyzer.js +402 -126
  37. package/rules/security/S023_no_json_injection/typescript/ast-analyzer.js +571 -154
  38. package/rules/security/S026_tls_all_connections/config.json +30 -0
  39. package/rules/security/S026_tls_all_connections/typescript/analyzer.js +339 -0
  40. package/rules/security/S027_mtls_certificate_validation/config.json +30 -0
  41. package/rules/security/S027_mtls_certificate_validation/typescript/analyzer.js +225 -0
  42. package/rules/security/S035_separate_app_hostnames/config.json +28 -0
  43. package/rules/security/S035_separate_app_hostnames/typescript/analyzer.js +186 -0
  44. package/rules/security/S036_lfi_rfi_protection/config.json +2 -2
  45. package/rules/security/S039_tls_certificate_validation/config.json +29 -0
  46. package/rules/security/S039_tls_certificate_validation/typescript/analyzer.js +229 -0
  47. package/rules/security/S046_jwt_algorithm_allowlist/config.json +28 -0
  48. package/rules/security/S046_jwt_algorithm_allowlist/dart/analyzer.js +44 -0
  49. package/rules/security/S046_jwt_algorithm_allowlist/index.js +87 -0
  50. package/rules/security/S046_jwt_algorithm_allowlist/typescript/analyzer.js +235 -0
  51. package/rules/security/S047_oauth_pkce_protection/config.json +31 -0
  52. package/rules/security/S047_oauth_pkce_protection/dart/analyzer.js +44 -0
  53. package/rules/security/S047_oauth_pkce_protection/index.js +86 -0
  54. package/rules/security/S047_oauth_pkce_protection/typescript/analyzer.js +78 -0
  55. package/rules/security/S048_oauth_redirect_uri_validation/config.json +30 -0
  56. package/rules/security/S048_oauth_redirect_uri_validation/typescript/analyzer.js +278 -0
  57. package/rules/security/S049_short_validity_tokens/typescript/config.json +10 -3
  58. package/rules/security/S050_reference_tokens_entropy/config.json +28 -0
  59. package/rules/security/S050_reference_tokens_entropy/dart/analyzer.js +45 -0
  60. package/rules/security/S050_reference_tokens_entropy/index.js +86 -0
  61. package/rules/security/S050_reference_tokens_entropy/typescript/analyzer.js +74 -0
  62. package/rules/security/S053_generic_error_messages/config.json +28 -0
  63. package/rules/security/S053_generic_error_messages/dart/analyzer.js +45 -0
  64. package/rules/security/S053_generic_error_messages/index.js +86 -0
  65. package/rules/security/S053_generic_error_messages/typescript/analyzer.js +80 -0
  66. package/rules/security/S055_content_type_validation/typescript/symbol-based-analyzer.js +64 -2
  67. package/rules/security/S059_disable_debug_mode/config.json +28 -0
  68. package/rules/security/S059_disable_debug_mode/dart/analyzer.js +45 -0
  69. package/rules/security/S059_disable_debug_mode/index.js +86 -0
  70. package/rules/security/S059_disable_debug_mode/typescript/analyzer.js +85 -0
  71. package/rules/security/S060_password_minimum_length/config.json +28 -0
  72. package/rules/security/S060_password_minimum_length/dart/analyzer.js +45 -0
  73. package/rules/security/S060_password_minimum_length/index.js +86 -0
  74. package/rules/security/S060_password_minimum_length/typescript/analyzer.js +78 -0
  75. package/rules/security/S026_json_schema_validation/config.json +0 -27
  76. package/rules/security/S026_json_schema_validation/typescript/analyzer.js +0 -251
  77. package/rules/security/S027_no_hardcoded_secrets/config.json +0 -29
  78. package/rules/security/S027_no_hardcoded_secrets/typescript/analyzer.js +0 -309
  79. package/rules/security/S027_no_hardcoded_secrets/typescript/categories.json +0 -153
  80. package/rules/security/S035_path_session_cookies/config.json +0 -99
  81. package/rules/security/S035_path_session_cookies/typescript/analyzer.js +0 -316
  82. package/rules/security/S035_path_session_cookies/typescript/regex-based-analyzer.js +0 -724
  83. package/rules/security/S035_path_session_cookies/typescript/symbol-based-analyzer.js +0 -373
  84. package/rules/security/S039_no_session_tokens_in_url/config.json +0 -92
  85. package/rules/security/S039_no_session_tokens_in_url/typescript/analyzer.js +0 -262
  86. package/rules/security/S039_no_session_tokens_in_url/typescript/regex-based-analyzer.js +0 -337
  87. package/rules/security/S039_no_session_tokens_in_url/typescript/symbol-based-analyzer.js +0 -443
  88. package/rules/security/S048_no_current_password_in_reset/config.json +0 -48
  89. package/rules/security/S048_no_current_password_in_reset/typescript/analyzer.js +0 -366
  90. /package/rules/security/{S026_json_schema_validation → S026_tls_all_connections}/dart/analyzer.js +0 -0
  91. /package/rules/security/{S026_json_schema_validation → S026_tls_all_connections}/index.js +0 -0
  92. /package/rules/security/{S027_no_hardcoded_secrets → S027_mtls_certificate_validation}/dart/analyzer.js +0 -0
  93. /package/rules/security/{S027_no_hardcoded_secrets → S027_mtls_certificate_validation}/index.js +0 -0
  94. /package/rules/security/{S027_no_hardcoded_secrets → S027_mtls_certificate_validation}/typescript/categorized-analyzer.js +0 -0
  95. /package/rules/security/{S035_path_session_cookies → S035_separate_app_hostnames}/dart/analyzer.js +0 -0
  96. /package/rules/security/{S035_path_session_cookies → S035_separate_app_hostnames}/index.js +0 -0
  97. /package/rules/security/{S035_path_session_cookies → S035_separate_app_hostnames}/typescript/README.md +0 -0
  98. /package/rules/security/{S039_no_session_tokens_in_url → S039_tls_certificate_validation}/dart/analyzer.js +0 -0
  99. /package/rules/security/{S039_no_session_tokens_in_url → S039_tls_certificate_validation}/index.js +0 -0
  100. /package/rules/security/{S039_no_session_tokens_in_url → S039_tls_certificate_validation}/typescript/README.md +0 -0
  101. /package/rules/security/{S048_no_current_password_in_reset → S048_oauth_redirect_uri_validation}/dart/analyzer.js +0 -0
  102. /package/rules/security/{S048_no_current_password_in_reset → S048_oauth_redirect_uri_validation}/index.js +0 -0
  103. /package/rules/security/{S048_no_current_password_in_reset → S048_oauth_redirect_uri_validation}/typescript/README.md +0 -0
@@ -0,0 +1,227 @@
1
+ /**
2
+ * SunLint Init Command
3
+ * Initializes a project with SunLint code quality skill and AGENTS.md
4
+ * Following Rule C005: Single responsibility - only handle init logic
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const chalk = require('chalk');
10
+
11
+ // Path to the bundled skill directory (relative to this file in the package)
12
+ const SKILL_SOURCE_DIR = path.join(__dirname, '..', 'skill-assets', 'sunlint-code-quality');
13
+ const AGENTS_MD_SOURCE = path.join(SKILL_SOURCE_DIR, 'AGENTS.md');
14
+
15
+ /**
16
+ * AI Tool configuration mapping
17
+ * Each tool has its own skill folder path convention
18
+ */
19
+ const AI_TOOL_CONFIG = {
20
+ 'antigravity': {
21
+ name: 'Google Antigravity',
22
+ skillPath: '.agent/skills',
23
+ description: 'Google Gemini Agent (.agent/skills/)'
24
+ },
25
+ 'cursor': {
26
+ name: 'Cursor AI',
27
+ skillPath: '.cursor/skills',
28
+ description: 'Cursor AI Agent (.cursor/skills/)'
29
+ },
30
+ 'claude': {
31
+ name: 'Claude',
32
+ skillPath: '.claude/skills',
33
+ description: 'Anthropic Claude Agent (.claude/skills/)'
34
+ },
35
+ 'github-copilot': {
36
+ name: 'GitHub Copilot',
37
+ skillPath: '.github/skills',
38
+ description: 'GitHub Copilot Agent (.github/skills/)'
39
+ }
40
+ };
41
+
42
+ // Default tool if not specified
43
+ const DEFAULT_TOOL = 'antigravity';
44
+
45
+ /**
46
+ * Get available tools list for help text
47
+ */
48
+ function getAvailableToolsHelp() {
49
+ return Object.entries(AI_TOOL_CONFIG)
50
+ .map(([key, config]) => ` ${key.padEnd(16)} - ${config.description}`)
51
+ .join('\n');
52
+ }
53
+
54
+ /**
55
+ * Initialize a project with SunLint skill and AGENTS.md
56
+ * @param {string} targetDir - The target project directory
57
+ * @param {object} options - Command options
58
+ */
59
+ async function initProject(targetDir, options = {}) {
60
+ const resolvedTargetDir = path.resolve(targetDir);
61
+ const tool = options.tool || DEFAULT_TOOL;
62
+
63
+ // Validate tool option
64
+ if (!AI_TOOL_CONFIG[tool]) {
65
+ console.error(chalk.red(`\n❌ Unknown tool: ${tool}`));
66
+ console.log(chalk.yellow('\nAvailable tools:'));
67
+ console.log(chalk.gray(getAvailableToolsHelp()));
68
+ throw new Error(`Unknown tool: ${tool}. Use one of: ${Object.keys(AI_TOOL_CONFIG).join(', ')}`);
69
+ }
70
+
71
+ const toolConfig = AI_TOOL_CONFIG[tool];
72
+
73
+ console.log(chalk.cyan('\n☀️ SunLint Init - Setting up code quality standards...\n'));
74
+ console.log(chalk.blue(` 🤖 Target AI Tool: ${toolConfig.name}`));
75
+
76
+ // Validate target directory exists
77
+ if (!fs.existsSync(resolvedTargetDir)) {
78
+ throw new Error(`Target directory does not exist: ${resolvedTargetDir}`);
79
+ }
80
+
81
+ const skillTargetDir = path.join(resolvedTargetDir, toolConfig.skillPath, 'sunlint-code-quality');
82
+ const agentsTargetPath = path.join(resolvedTargetDir, 'AGENTS.md');
83
+
84
+ // Step 1: Copy skill folder
85
+ await copySkillFolder(skillTargetDir, options, toolConfig);
86
+
87
+ // Step 2: Append/Create AGENTS.md
88
+ await setupAgentsMd(agentsTargetPath, options);
89
+
90
+ console.log(chalk.green('\n✅ SunLint initialization complete!\n'));
91
+ console.log(chalk.white(' Files created/updated:'));
92
+ console.log(chalk.gray(` • ${path.relative(resolvedTargetDir, skillTargetDir)}/`));
93
+ console.log(chalk.gray(` • AGENTS.md`));
94
+ console.log(chalk.cyan(`\n📖 ${toolConfig.name} will now follow SunLint code quality standards.\n`));
95
+ }
96
+
97
+ /**
98
+ * Copy the skill folder to target location
99
+ * @param {string} targetPath - Target path for the skill folder
100
+ * @param {object} options - Command options
101
+ * @param {object} toolConfig - AI tool configuration
102
+ */
103
+ async function copySkillFolder(targetPath, options = {}, toolConfig = {}) {
104
+ const force = options.force || false;
105
+
106
+ // Check if skill folder already exists
107
+ if (fs.existsSync(targetPath)) {
108
+ if (!force) {
109
+ console.log(chalk.yellow(` ⚠️ Skill folder already exists: ${targetPath}`));
110
+ console.log(chalk.yellow(' Use --force to overwrite.'));
111
+ return;
112
+ }
113
+ console.log(chalk.yellow(` 🔄 Overwriting existing skill folder...`));
114
+ fs.rmSync(targetPath, { recursive: true });
115
+ }
116
+
117
+ // Create parent directories if they don't exist
118
+ const parentDir = path.dirname(targetPath);
119
+ if (!fs.existsSync(parentDir)) {
120
+ fs.mkdirSync(parentDir, { recursive: true });
121
+ }
122
+
123
+ // Copy skill folder recursively
124
+ const relativePath = toolConfig.skillPath || '.agent/skills';
125
+ console.log(chalk.blue(` 📁 Copying skill folder to ${relativePath}/sunlint-code-quality/`));
126
+ copyDirectoryRecursive(SKILL_SOURCE_DIR, targetPath);
127
+ }
128
+
129
+ /**
130
+ * Recursively copy a directory
131
+ * @param {string} source - Source directory
132
+ * @param {string} target - Target directory
133
+ */
134
+ function copyDirectoryRecursive(source, target) {
135
+ if (!fs.existsSync(source)) {
136
+ throw new Error(`Source directory not found: ${source}`);
137
+ }
138
+
139
+ fs.mkdirSync(target, { recursive: true });
140
+
141
+ const items = fs.readdirSync(source);
142
+
143
+ for (const item of items) {
144
+ const sourcePath = path.join(source, item);
145
+ const targetPath = path.join(target, item);
146
+
147
+ const stat = fs.statSync(sourcePath);
148
+
149
+ if (stat.isDirectory()) {
150
+ copyDirectoryRecursive(sourcePath, targetPath);
151
+ } else {
152
+ fs.copyFileSync(sourcePath, targetPath);
153
+ }
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Setup AGENTS.md - append or create
159
+ * @param {string} targetPath - Target path for AGENTS.md
160
+ * @param {object} options - Command options
161
+ */
162
+ async function setupAgentsMd(targetPath, options = {}) {
163
+ const force = options.force || false;
164
+
165
+ // Read the source AGENTS.md content
166
+ if (!fs.existsSync(AGENTS_MD_SOURCE)) {
167
+ throw new Error(`Source AGENTS.md not found: ${AGENTS_MD_SOURCE}`);
168
+ }
169
+
170
+ const sunlintAgentsContent = fs.readFileSync(AGENTS_MD_SOURCE, 'utf-8');
171
+ const separator = '\n\n---\n\n<!-- SunLint Code Quality Standards - Auto-generated by sunlint init -->\n\n';
172
+
173
+ if (fs.existsSync(targetPath)) {
174
+ // Check if already contains SunLint content
175
+ const existingContent = fs.readFileSync(targetPath, 'utf-8');
176
+
177
+ if (existingContent.includes('SunLint Code Quality & Security Standards')) {
178
+ if (!force) {
179
+ console.log(chalk.yellow(` ⚠️ AGENTS.md already contains SunLint standards.`));
180
+ console.log(chalk.yellow(' Use --force to replace.'));
181
+ return;
182
+ }
183
+ // Remove existing SunLint section and append new one
184
+ console.log(chalk.yellow(` 🔄 Updating SunLint section in AGENTS.md...`));
185
+ const cleanedContent = removeSunlintSection(existingContent);
186
+ fs.writeFileSync(targetPath, cleanedContent + separator + sunlintAgentsContent, 'utf-8');
187
+ } else {
188
+ // Append to existing file
189
+ console.log(chalk.blue(` 📝 Appending SunLint standards to existing AGENTS.md`));
190
+ fs.appendFileSync(targetPath, separator + sunlintAgentsContent, 'utf-8');
191
+ }
192
+ } else {
193
+ // Create new AGENTS.md
194
+ console.log(chalk.blue(` 📝 Creating new AGENTS.md with SunLint standards`));
195
+ fs.writeFileSync(targetPath, sunlintAgentsContent, 'utf-8');
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Remove existing SunLint section from content
201
+ * @param {string} content - Existing file content
202
+ * @returns {string} - Content without SunLint section
203
+ */
204
+ function removeSunlintSection(content) {
205
+ // Find and remove the SunLint section (from marker to end or next major section)
206
+ const sunlintMarkerStart = content.indexOf('<!-- SunLint Code Quality Standards');
207
+ if (sunlintMarkerStart === -1) {
208
+ // Try alternative marker
209
+ const altMarkerStart = content.indexOf('# 🦾 SunLint Code Quality');
210
+ if (altMarkerStart === -1) {
211
+ return content;
212
+ }
213
+ // Remove from this point to end
214
+ return content.substring(0, altMarkerStart).trim();
215
+ }
216
+
217
+ return content.substring(0, sunlintMarkerStart).trim();
218
+ }
219
+
220
+ module.exports = {
221
+ initProject,
222
+ copySkillFolder,
223
+ setupAgentsMd,
224
+ AI_TOOL_CONFIG,
225
+ DEFAULT_TOOL,
226
+ getAvailableToolsHelp,
227
+ };
@@ -242,7 +242,8 @@ class OutputService {
242
242
  cwd: process.cwd(),
243
243
  filesAnalyzed: totalFiles,
244
244
  duration: metadata.duration,
245
- version: metadata.version || this.version
245
+ version: metadata.version || this.version,
246
+ architecture: results.architecture || null
246
247
  }
247
248
  );
248
249
 
@@ -431,10 +432,39 @@ class OutputService {
431
432
  const jsonResults = [];
432
433
  const fileGroups = {};
433
434
 
435
+ // Combine code quality violations with architecture violations
436
+ const combinedViolations = [...allViolations];
437
+
438
+ // Include architecture violations if available
439
+ if (results.architecture && results.architecture.violations) {
440
+ const cwd = process.cwd();
441
+ for (const archViolation of results.architecture.violations) {
442
+ let file = archViolation.file || 'unknown';
443
+
444
+ // Convert absolute path to relative path
445
+ if (file !== 'unknown' && path.isAbsolute(file)) {
446
+ if (file.startsWith(cwd)) {
447
+ file = path.relative(cwd, file);
448
+ }
449
+ }
450
+
451
+ combinedViolations.push({
452
+ file: file,
453
+ ruleId: archViolation.ruleId,
454
+ severity: archViolation.severity,
455
+ message: archViolation.message,
456
+ line: archViolation.line || 1,
457
+ column: archViolation.column || 1,
458
+ category: 'architecture',
459
+ source: 'architecture-detection'
460
+ });
461
+ }
462
+ }
463
+
434
464
  // Group violations by file
435
- allViolations.forEach(violation => {
465
+ combinedViolations.forEach(violation => {
436
466
  let file = violation.file || violation.filePath || 'unknown';
437
-
467
+
438
468
  // Convert absolute path to relative path for better display
439
469
  if (file !== 'unknown' && path.isAbsolute(file)) {
440
470
  const cwd = process.cwd();
@@ -442,7 +472,7 @@ class OutputService {
442
472
  file = path.relative(cwd, file);
443
473
  }
444
474
  }
445
-
475
+
446
476
  if (!fileGroups[file]) {
447
477
  fileGroups[file] = [];
448
478
  }
@@ -460,7 +490,8 @@ class OutputService {
460
490
  nodeType: violation.nodeType || null,
461
491
  messageId: violation.messageId || null,
462
492
  endLine: violation.endLine || null,
463
- endColumn: violation.endColumn || null
493
+ endColumn: violation.endColumn || null,
494
+ category: violation.category || null // Include category for architecture violations
464
495
  }));
465
496
 
466
497
  jsonResults.push({
@@ -495,6 +526,23 @@ class OutputService {
495
526
  });
496
527
  }
497
528
 
529
+ // Add architecture metadata as a special entry (maintains ESLint compatibility)
530
+ if (results.architecture && results.architecture.summary) {
531
+ jsonResults.push({
532
+ filePath: '__sunlint_architecture__',
533
+ _type: 'architecture-metadata',
534
+ _architectureSummary: results.architecture.summary,
535
+ messages: [],
536
+ suppressedMessages: [],
537
+ errorCount: 0,
538
+ warningCount: 0,
539
+ fatalErrorCount: 0,
540
+ fixableErrorCount: 0,
541
+ fixableWarningCount: 0,
542
+ source: null
543
+ });
544
+ }
545
+
498
546
  return jsonResults;
499
547
  }
500
548
 
@@ -268,6 +268,52 @@ class SummaryReportService {
268
268
  }
269
269
  };
270
270
 
271
+ // Add architecture analysis if available
272
+ if (options.architecture && options.architecture.summary) {
273
+ const archSummary = options.architecture.summary;
274
+ summaryReport.architecture = {
275
+ primary_pattern: archSummary.primaryPattern || 'UNKNOWN',
276
+ primary_confidence: Math.round((archSummary.primaryConfidence || 0) * 100),
277
+ health_score: Math.round(archSummary.healthScore || 0),
278
+ violation_count: archSummary.violationCount || 0,
279
+ rules_checked: archSummary.rulesChecked || 0,
280
+ rules_passed: archSummary.rulesPassed || 0,
281
+ rules_failed: archSummary.rulesFailed || 0,
282
+ is_hybrid: archSummary.isHybrid || false,
283
+ combination: archSummary.combination || null,
284
+ secondary_patterns: (archSummary.secondaryPatterns || []).map(p => ({
285
+ pattern: p.pattern,
286
+ confidence: Math.round((p.confidence || 0) * 100)
287
+ })),
288
+ analysis_time_ms: archSummary.analysisTime || 0
289
+ };
290
+
291
+ // Add all rules with score > 0 (passed + failed) to report
292
+ if (options.architecture.allRules && options.architecture.allRules.length > 0) {
293
+ summaryReport.architecture.rules = options.architecture.allRules
294
+ .filter(r => r.score > 0) // Only include rules with evidence (score > 0)
295
+ .map(r => ({
296
+ rule_code: r.ruleId,
297
+ rule_name: r.ruleName,
298
+ score: r.score,
299
+ status: r.status,
300
+ passed: r.passed
301
+ }));
302
+ }
303
+
304
+ // Add architecture violations to violations summary (only score > 0)
305
+ if (options.architecture.violations && options.architecture.violations.length > 0) {
306
+ summaryReport.architecture.violations = options.architecture.violations.map(v => ({
307
+ rule_code: v.ruleId,
308
+ score: v.score || 0,
309
+ severity: v.severity,
310
+ message: v.message,
311
+ file: v.file,
312
+ line: v.line || 1
313
+ }));
314
+ }
315
+ }
316
+
271
317
  return summaryReport;
272
318
  }
273
319
 
@@ -310,7 +310,8 @@ class UnifiedRuleRegistry {
310
310
  */
311
311
  registerEngineCapabilities() {
312
312
  // Define what each engine can handle
313
- this.engineCapabilities.set('heuristic', ['semantic', 'ast', 'regex']);
313
+ // 'heuristic' strategy type added to support rules that use heuristic-based detection
314
+ this.engineCapabilities.set('heuristic', ['semantic', 'ast', 'regex', 'heuristic']);
314
315
  this.engineCapabilities.set('eslint', ['ast', 'regex']);
315
316
  this.engineCapabilities.set('openai', ['semantic']);
316
317
 
@@ -1228,6 +1228,12 @@ export default [
1228
1228
  results.metadata.warnings = ['ESLint analysis failed due to plugin compatibility issues'];
1229
1229
  return results;
1230
1230
  }
1231
+ } else if (lintError.message && lintError.message.includes('Could not find config file')) {
1232
+ // No ESLint config found - gracefully skip ESLint analysis
1233
+ console.warn('⚠️ [ESLintEngine] No ESLint config file found in project');
1234
+ console.warn('💡 [ESLintEngine] Create eslint.config.js or use --engine=heuristic to skip ESLint');
1235
+ results.metadata.warnings = ['ESLint config not found - analysis skipped'];
1236
+ return results;
1231
1237
  } else {
1232
1238
  // Re-throw other errors
1233
1239
  throw lintError;
@@ -23,7 +23,7 @@ export class DatabaseDetector {
23
23
  */
24
24
  async detect(changedFiles) {
25
25
  const startTime = Date.now();
26
- this.logger.info('\n 🔍 Analyzing database impacts...');
26
+ this.logger.verbose('DatabaseDetector', '\n 🔍 Analyzing database impacts...');
27
27
  this.logger.verbose('DatabaseDetector', `Processing ${changedFiles.length} files`);
28
28
 
29
29
  const databaseChanges = {
@@ -33,7 +33,7 @@ export class EndpointDetector {
33
33
  return [];
34
34
  }
35
35
 
36
- this.logger.info(`\n 🔍 Finding affected endpoints for ${allChangedMethods.length} changed methods...`);
36
+ this.logger.verbose('EndpointDetector', `\n 🔍 Finding affected endpoints for ${allChangedMethods.length} changed methods...`);
37
37
  this.logger.verbose('EndpointDetector', `Call graph size: ${this.methodCallGraph.methodCallMap.size} entries`);
38
38
 
39
39
  const affectedEndpoints = this.methodCallGraph.findAffectedEndpoints(allChangedMethods);