@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.
- package/cli.js +33 -0
- package/config/rules/enhanced-rules-registry.json +354 -98
- package/config/rules/rules-registry-generated.json +197 -171
- package/core/architecture-integration.js +115 -17
- package/core/cli-action-handler.js +101 -27
- package/core/cli-program.js +5 -0
- package/core/github-annotate-service.js +62 -0
- package/core/impact-integration.js +31 -16
- package/core/init-command.js +227 -0
- package/core/output-service.js +53 -5
- package/core/summary-report-service.js +46 -0
- package/core/unified-rule-registry.js +2 -1
- package/engines/eslint-engine.js +6 -0
- package/engines/impact/core/detectors/database-detector.js +1 -1
- package/engines/impact/core/detectors/endpoint-detector.js +1 -1
- package/engines/impact/core/report-generator.js +235 -73
- package/origin-rules/security-en.md +470 -282
- package/package.json +1 -1
- package/rules/security/S001_backend_auth_communications/dart/analyzer.js +44 -0
- package/rules/security/S001_backend_auth_communications/index.js +87 -0
- package/rules/security/S001_backend_auth_communications/typescript/analyzer.js +164 -0
- package/rules/security/S002_os_command_injection/dart/analyzer.js +44 -0
- package/rules/security/S002_os_command_injection/index.js +87 -0
- package/rules/security/S002_os_command_injection/typescript/analyzer.js +194 -0
- package/rules/security/S008_svg_content_validation/dart/analyzer.js +44 -0
- package/rules/security/S008_svg_content_validation/index.js +87 -0
- package/rules/security/S008_svg_content_validation/typescript/analyzer.js +216 -0
- package/rules/security/S018_no_sensitive_browser_storage/dart/analyzer.js +44 -0
- package/rules/security/S018_no_sensitive_browser_storage/index.js +86 -0
- package/rules/security/S018_no_sensitive_browser_storage/typescript/analyzer.js +193 -0
- package/rules/security/S021_referrer_policy/dart/analyzer.js +44 -0
- package/rules/security/S021_referrer_policy/index.js +86 -0
- package/rules/security/S021_referrer_policy/typescript/analyzer.js +183 -0
- package/rules/security/S023_no_json_injection/config.json +133 -44
- package/rules/security/S023_no_json_injection/dart/analyzer.js +7 -6
- package/rules/security/S023_no_json_injection/typescript/analyzer.js +402 -126
- package/rules/security/S023_no_json_injection/typescript/ast-analyzer.js +571 -154
- package/rules/security/S026_tls_all_connections/config.json +30 -0
- package/rules/security/S026_tls_all_connections/typescript/analyzer.js +339 -0
- package/rules/security/S027_mtls_certificate_validation/config.json +30 -0
- package/rules/security/S027_mtls_certificate_validation/typescript/analyzer.js +225 -0
- package/rules/security/S035_separate_app_hostnames/config.json +28 -0
- package/rules/security/S035_separate_app_hostnames/typescript/analyzer.js +186 -0
- package/rules/security/S036_lfi_rfi_protection/config.json +2 -2
- package/rules/security/S039_tls_certificate_validation/config.json +29 -0
- package/rules/security/S039_tls_certificate_validation/typescript/analyzer.js +229 -0
- package/rules/security/S046_jwt_algorithm_allowlist/config.json +28 -0
- package/rules/security/S046_jwt_algorithm_allowlist/dart/analyzer.js +44 -0
- package/rules/security/S046_jwt_algorithm_allowlist/index.js +87 -0
- package/rules/security/S046_jwt_algorithm_allowlist/typescript/analyzer.js +235 -0
- package/rules/security/S047_oauth_pkce_protection/config.json +31 -0
- package/rules/security/S047_oauth_pkce_protection/dart/analyzer.js +44 -0
- package/rules/security/S047_oauth_pkce_protection/index.js +86 -0
- package/rules/security/S047_oauth_pkce_protection/typescript/analyzer.js +78 -0
- package/rules/security/S048_oauth_redirect_uri_validation/config.json +30 -0
- package/rules/security/S048_oauth_redirect_uri_validation/typescript/analyzer.js +278 -0
- package/rules/security/S049_short_validity_tokens/typescript/config.json +10 -3
- package/rules/security/S050_reference_tokens_entropy/config.json +28 -0
- package/rules/security/S050_reference_tokens_entropy/dart/analyzer.js +45 -0
- package/rules/security/S050_reference_tokens_entropy/index.js +86 -0
- package/rules/security/S050_reference_tokens_entropy/typescript/analyzer.js +74 -0
- package/rules/security/S053_generic_error_messages/config.json +28 -0
- package/rules/security/S053_generic_error_messages/dart/analyzer.js +45 -0
- package/rules/security/S053_generic_error_messages/index.js +86 -0
- package/rules/security/S053_generic_error_messages/typescript/analyzer.js +80 -0
- package/rules/security/S055_content_type_validation/typescript/symbol-based-analyzer.js +64 -2
- package/rules/security/S059_disable_debug_mode/config.json +28 -0
- package/rules/security/S059_disable_debug_mode/dart/analyzer.js +45 -0
- package/rules/security/S059_disable_debug_mode/index.js +86 -0
- package/rules/security/S059_disable_debug_mode/typescript/analyzer.js +85 -0
- package/rules/security/S060_password_minimum_length/config.json +28 -0
- package/rules/security/S060_password_minimum_length/dart/analyzer.js +45 -0
- package/rules/security/S060_password_minimum_length/index.js +86 -0
- package/rules/security/S060_password_minimum_length/typescript/analyzer.js +78 -0
- package/rules/security/S026_json_schema_validation/config.json +0 -27
- package/rules/security/S026_json_schema_validation/typescript/analyzer.js +0 -251
- package/rules/security/S027_no_hardcoded_secrets/config.json +0 -29
- package/rules/security/S027_no_hardcoded_secrets/typescript/analyzer.js +0 -309
- package/rules/security/S027_no_hardcoded_secrets/typescript/categories.json +0 -153
- package/rules/security/S035_path_session_cookies/config.json +0 -99
- package/rules/security/S035_path_session_cookies/typescript/analyzer.js +0 -316
- package/rules/security/S035_path_session_cookies/typescript/regex-based-analyzer.js +0 -724
- package/rules/security/S035_path_session_cookies/typescript/symbol-based-analyzer.js +0 -373
- package/rules/security/S039_no_session_tokens_in_url/config.json +0 -92
- package/rules/security/S039_no_session_tokens_in_url/typescript/analyzer.js +0 -262
- package/rules/security/S039_no_session_tokens_in_url/typescript/regex-based-analyzer.js +0 -337
- package/rules/security/S039_no_session_tokens_in_url/typescript/symbol-based-analyzer.js +0 -443
- package/rules/security/S048_no_current_password_in_reset/config.json +0 -48
- package/rules/security/S048_no_current_password_in_reset/typescript/analyzer.js +0 -366
- /package/rules/security/{S026_json_schema_validation → S026_tls_all_connections}/dart/analyzer.js +0 -0
- /package/rules/security/{S026_json_schema_validation → S026_tls_all_connections}/index.js +0 -0
- /package/rules/security/{S027_no_hardcoded_secrets → S027_mtls_certificate_validation}/dart/analyzer.js +0 -0
- /package/rules/security/{S027_no_hardcoded_secrets → S027_mtls_certificate_validation}/index.js +0 -0
- /package/rules/security/{S027_no_hardcoded_secrets → S027_mtls_certificate_validation}/typescript/categorized-analyzer.js +0 -0
- /package/rules/security/{S035_path_session_cookies → S035_separate_app_hostnames}/dart/analyzer.js +0 -0
- /package/rules/security/{S035_path_session_cookies → S035_separate_app_hostnames}/index.js +0 -0
- /package/rules/security/{S035_path_session_cookies → S035_separate_app_hostnames}/typescript/README.md +0 -0
- /package/rules/security/{S039_no_session_tokens_in_url → S039_tls_certificate_validation}/dart/analyzer.js +0 -0
- /package/rules/security/{S039_no_session_tokens_in_url → S039_tls_certificate_validation}/index.js +0 -0
- /package/rules/security/{S039_no_session_tokens_in_url → S039_tls_certificate_validation}/typescript/README.md +0 -0
- /package/rules/security/{S048_no_current_password_in_reset → S048_oauth_redirect_uri_validation}/dart/analyzer.js +0 -0
- /package/rules/security/{S048_no_current_password_in_reset → S048_oauth_redirect_uri_validation}/index.js +0 -0
- /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
|
+
};
|
package/core/output-service.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
package/engines/eslint-engine.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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);
|