@sun-asterisk/sunlint 1.3.36 → 1.3.38
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 +34 -0
- package/config/rules/enhanced-rules-registry.json +387 -98
- package/config/rules/rules-registry-generated.json +202 -174
- package/config/rules-summary.json +1 -1
- package/core/architecture-integration.js +115 -17
- package/core/cli-action-handler.js +103 -28
- package/core/cli-program.js +7 -2
- package/core/github-annotate-service.js +62 -0
- package/core/impact-integration.js +31 -16
- package/core/init-command.js +261 -0
- package/core/output-service.js +64 -10
- package/core/performance-optimizer.js +1 -1
- package/core/summary-report-service.js +46 -0
- package/core/unified-rule-registry.js +4 -3
- package/docs/DART_RULE_EXECUTION_FLOW.md +1 -1
- package/docs/REGISTRY_GENERATION_DIAGRAM.md +289 -0
- package/docs/REGISTRY_GENERATION_FLOW.md +486 -0
- package/docs/skills/CREATE_NEW_DART_RULE.md +932 -0
- package/engines/eslint-engine.js +6 -0
- package/engines/heuristic-engine.js +23 -10
- 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/dart-en.md +4 -4
- package/origin-rules/security-en.md +470 -282
- package/package.json +1 -1
- package/rules/dart/D001_recommended_lint_rules/config.json +134 -0
- package/rules/index.js +6 -4
- 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,261 @@
|
|
|
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
|
+
const language = options.language || 'typescript';
|
|
124
|
+
|
|
125
|
+
// Copy skill folder usage files (root)
|
|
126
|
+
const relativePath = toolConfig.skillPath || '.agent/skills';
|
|
127
|
+
console.log(chalk.blue(` 📁 Copying skill folder to ${relativePath}/sunlint-code-quality/`));
|
|
128
|
+
|
|
129
|
+
// 1. Copy root files (SKILL.md, AGENTS.md, etc) - Non-recursive
|
|
130
|
+
fs.mkdirSync(targetPath, { recursive: true });
|
|
131
|
+
|
|
132
|
+
// Copy root files
|
|
133
|
+
if (fs.existsSync(SKILL_SOURCE_DIR)) {
|
|
134
|
+
const items = fs.readdirSync(SKILL_SOURCE_DIR);
|
|
135
|
+
for (const item of items) {
|
|
136
|
+
const sourcePath = path.join(SKILL_SOURCE_DIR, item);
|
|
137
|
+
const targetItemPath = path.join(targetPath, item);
|
|
138
|
+
const stat = fs.statSync(sourcePath);
|
|
139
|
+
|
|
140
|
+
if (!stat.isDirectory()) {
|
|
141
|
+
fs.copyFileSync(sourcePath, targetItemPath);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 2. Copy Language Rules to rules/ folder
|
|
147
|
+
const rulesSourceDir = path.join(SKILL_SOURCE_DIR, 'rules', language);
|
|
148
|
+
const rulesTargetDir = path.join(targetPath, 'rules');
|
|
149
|
+
|
|
150
|
+
if (fs.existsSync(rulesSourceDir)) {
|
|
151
|
+
console.log(chalk.blue(` 📝 Installing ${language} rules...`));
|
|
152
|
+
copyDirectoryRecursive(rulesSourceDir, rulesTargetDir);
|
|
153
|
+
} else {
|
|
154
|
+
console.warn(chalk.yellow(` ⚠️ Language '${language}' rules not found. Falling back to typescript rules.`));
|
|
155
|
+
const fallbackDir = path.join(SKILL_SOURCE_DIR, 'rules', 'typescript');
|
|
156
|
+
if (fs.existsSync(fallbackDir)) {
|
|
157
|
+
copyDirectoryRecursive(fallbackDir, rulesTargetDir);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Recursively copy a directory
|
|
164
|
+
* @param {string} source - Source directory
|
|
165
|
+
* @param {string} target - Target directory
|
|
166
|
+
*/
|
|
167
|
+
function copyDirectoryRecursive(source, target) {
|
|
168
|
+
if (!fs.existsSync(source)) {
|
|
169
|
+
// throw new Error(`Source directory not found: ${source}`);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
fs.mkdirSync(target, { recursive: true });
|
|
174
|
+
|
|
175
|
+
const items = fs.readdirSync(source);
|
|
176
|
+
|
|
177
|
+
for (const item of items) {
|
|
178
|
+
const sourcePath = path.join(source, item);
|
|
179
|
+
const targetPath = path.join(target, item);
|
|
180
|
+
|
|
181
|
+
const stat = fs.statSync(sourcePath);
|
|
182
|
+
|
|
183
|
+
if (stat.isDirectory()) {
|
|
184
|
+
copyDirectoryRecursive(sourcePath, targetPath);
|
|
185
|
+
} else {
|
|
186
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Setup AGENTS.md - append or create
|
|
193
|
+
* @param {string} targetPath - Target path for AGENTS.md
|
|
194
|
+
* @param {object} options - Command options
|
|
195
|
+
*/
|
|
196
|
+
async function setupAgentsMd(targetPath, options = {}) {
|
|
197
|
+
const force = options.force || false;
|
|
198
|
+
|
|
199
|
+
// Read the source AGENTS.md content
|
|
200
|
+
if (!fs.existsSync(AGENTS_MD_SOURCE)) {
|
|
201
|
+
throw new Error(`Source AGENTS.md not found: ${AGENTS_MD_SOURCE}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const sunlintAgentsContent = fs.readFileSync(AGENTS_MD_SOURCE, 'utf-8');
|
|
205
|
+
const separator = '\n\n---\n\n<!-- SunLint Code Quality Standards - Auto-generated by sunlint init -->\n\n';
|
|
206
|
+
|
|
207
|
+
if (fs.existsSync(targetPath)) {
|
|
208
|
+
// Check if already contains SunLint content
|
|
209
|
+
const existingContent = fs.readFileSync(targetPath, 'utf-8');
|
|
210
|
+
|
|
211
|
+
if (existingContent.includes('SunLint Code Quality & Security Standards')) {
|
|
212
|
+
if (!force) {
|
|
213
|
+
console.log(chalk.yellow(` ⚠️ AGENTS.md already contains SunLint standards.`));
|
|
214
|
+
console.log(chalk.yellow(' Use --force to replace.'));
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
// Remove existing SunLint section and append new one
|
|
218
|
+
console.log(chalk.yellow(` 🔄 Updating SunLint section in AGENTS.md...`));
|
|
219
|
+
const cleanedContent = removeSunlintSection(existingContent);
|
|
220
|
+
fs.writeFileSync(targetPath, cleanedContent + separator + sunlintAgentsContent, 'utf-8');
|
|
221
|
+
} else {
|
|
222
|
+
// Append to existing file
|
|
223
|
+
console.log(chalk.blue(` 📝 Appending SunLint standards to existing AGENTS.md`));
|
|
224
|
+
fs.appendFileSync(targetPath, separator + sunlintAgentsContent, 'utf-8');
|
|
225
|
+
}
|
|
226
|
+
} else {
|
|
227
|
+
// Create new AGENTS.md
|
|
228
|
+
console.log(chalk.blue(` 📝 Creating new AGENTS.md with SunLint standards`));
|
|
229
|
+
fs.writeFileSync(targetPath, sunlintAgentsContent, 'utf-8');
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Remove existing SunLint section from content
|
|
235
|
+
* @param {string} content - Existing file content
|
|
236
|
+
* @returns {string} - Content without SunLint section
|
|
237
|
+
*/
|
|
238
|
+
function removeSunlintSection(content) {
|
|
239
|
+
// Find and remove the SunLint section (from marker to end or next major section)
|
|
240
|
+
const sunlintMarkerStart = content.indexOf('<!-- SunLint Code Quality Standards');
|
|
241
|
+
if (sunlintMarkerStart === -1) {
|
|
242
|
+
// Try alternative marker
|
|
243
|
+
const altMarkerStart = content.indexOf('# 🦾 SunLint Code Quality');
|
|
244
|
+
if (altMarkerStart === -1) {
|
|
245
|
+
return content;
|
|
246
|
+
}
|
|
247
|
+
// Remove from this point to end
|
|
248
|
+
return content.substring(0, altMarkerStart).trim();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return content.substring(0, sunlintMarkerStart).trim();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
module.exports = {
|
|
255
|
+
initProject,
|
|
256
|
+
copySkillFolder,
|
|
257
|
+
setupAgentsMd,
|
|
258
|
+
AI_TOOL_CONFIG,
|
|
259
|
+
DEFAULT_TOOL,
|
|
260
|
+
getAvailableToolsHelp,
|
|
261
|
+
};
|
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
|
|
|
@@ -380,16 +381,22 @@ class OutputService {
|
|
|
380
381
|
if (index > 0) {
|
|
381
382
|
output += '\n';
|
|
382
383
|
}
|
|
383
|
-
output += `\n${chalk.underline(path.resolve(file))}\n`;
|
|
384
384
|
|
|
385
|
-
sortedViolations.forEach(violation => {
|
|
385
|
+
sortedViolations.forEach((violation, index) => {
|
|
386
|
+
const isDuplicateCodeRule = violation.ruleId === 'C002';
|
|
387
|
+
if (index === 0 && !isDuplicateCodeRule) {
|
|
388
|
+
output += `\n${chalk.underline(path.resolve(file))}\n`;
|
|
389
|
+
}
|
|
386
390
|
// Support both location.start.line (new format) and line (legacy format)
|
|
387
391
|
const line = (violation.location?.start?.line || violation.line || 1).toString();
|
|
388
392
|
const column = (violation.location?.start?.column || violation.column || 1).toString();
|
|
389
|
-
const severityText = violation.severity === 'error' ? '
|
|
393
|
+
const severityText = violation.severity === 'error' ? 'Error' : 'Warning';
|
|
390
394
|
const severityColor = violation.severity === 'error' ? chalk.red : chalk.yellow;
|
|
391
|
-
|
|
392
|
-
|
|
395
|
+
if (isDuplicateCodeRule) {
|
|
396
|
+
output += `${severityColor(severityText)} ${chalk.white(` ${chalk.underline(path.resolve(file))}:${line}:${column} ${violation.message} ${chalk.yellow(`(Confidence: ${violation.confidence}%)`)}`)} ${chalk.gray(violation.ruleId)}\n`;
|
|
397
|
+
} else {
|
|
398
|
+
output += ` ${chalk.blue(`${line}:${column}`)} ${severityColor(severityText)} ${violation.message} ${chalk.gray(violation.ruleId)}\n`;
|
|
399
|
+
}
|
|
393
400
|
});
|
|
394
401
|
});
|
|
395
402
|
|
|
@@ -431,10 +438,39 @@ class OutputService {
|
|
|
431
438
|
const jsonResults = [];
|
|
432
439
|
const fileGroups = {};
|
|
433
440
|
|
|
441
|
+
// Combine code quality violations with architecture violations
|
|
442
|
+
const combinedViolations = [...allViolations];
|
|
443
|
+
|
|
444
|
+
// Include architecture violations if available
|
|
445
|
+
if (results.architecture && results.architecture.violations) {
|
|
446
|
+
const cwd = process.cwd();
|
|
447
|
+
for (const archViolation of results.architecture.violations) {
|
|
448
|
+
let file = archViolation.file || 'unknown';
|
|
449
|
+
|
|
450
|
+
// Convert absolute path to relative path
|
|
451
|
+
if (file !== 'unknown' && path.isAbsolute(file)) {
|
|
452
|
+
if (file.startsWith(cwd)) {
|
|
453
|
+
file = path.relative(cwd, file);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
combinedViolations.push({
|
|
458
|
+
file: file,
|
|
459
|
+
ruleId: archViolation.ruleId,
|
|
460
|
+
severity: archViolation.severity,
|
|
461
|
+
message: archViolation.message,
|
|
462
|
+
line: archViolation.line || 1,
|
|
463
|
+
column: archViolation.column || 1,
|
|
464
|
+
category: 'architecture',
|
|
465
|
+
source: 'architecture-detection'
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
434
470
|
// Group violations by file
|
|
435
|
-
|
|
471
|
+
combinedViolations.forEach(violation => {
|
|
436
472
|
let file = violation.file || violation.filePath || 'unknown';
|
|
437
|
-
|
|
473
|
+
|
|
438
474
|
// Convert absolute path to relative path for better display
|
|
439
475
|
if (file !== 'unknown' && path.isAbsolute(file)) {
|
|
440
476
|
const cwd = process.cwd();
|
|
@@ -442,7 +478,7 @@ class OutputService {
|
|
|
442
478
|
file = path.relative(cwd, file);
|
|
443
479
|
}
|
|
444
480
|
}
|
|
445
|
-
|
|
481
|
+
|
|
446
482
|
if (!fileGroups[file]) {
|
|
447
483
|
fileGroups[file] = [];
|
|
448
484
|
}
|
|
@@ -460,7 +496,8 @@ class OutputService {
|
|
|
460
496
|
nodeType: violation.nodeType || null,
|
|
461
497
|
messageId: violation.messageId || null,
|
|
462
498
|
endLine: violation.endLine || null,
|
|
463
|
-
endColumn: violation.endColumn || null
|
|
499
|
+
endColumn: violation.endColumn || null,
|
|
500
|
+
category: violation.category || null // Include category for architecture violations
|
|
464
501
|
}));
|
|
465
502
|
|
|
466
503
|
jsonResults.push({
|
|
@@ -495,6 +532,23 @@ class OutputService {
|
|
|
495
532
|
});
|
|
496
533
|
}
|
|
497
534
|
|
|
535
|
+
// Add architecture metadata as a special entry (maintains ESLint compatibility)
|
|
536
|
+
if (results.architecture && results.architecture.summary) {
|
|
537
|
+
jsonResults.push({
|
|
538
|
+
filePath: '__sunlint_architecture__',
|
|
539
|
+
_type: 'architecture-metadata',
|
|
540
|
+
_architectureSummary: results.architecture.summary,
|
|
541
|
+
messages: [],
|
|
542
|
+
suppressedMessages: [],
|
|
543
|
+
errorCount: 0,
|
|
544
|
+
warningCount: 0,
|
|
545
|
+
fatalErrorCount: 0,
|
|
546
|
+
fixableErrorCount: 0,
|
|
547
|
+
fixableWarningCount: 0,
|
|
548
|
+
source: null
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
|
|
498
552
|
return jsonResults;
|
|
499
553
|
}
|
|
500
554
|
|
|
@@ -112,7 +112,7 @@ class PerformanceOptimizer {
|
|
|
112
112
|
break;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
// Check file count limit (skip if unlimited
|
|
115
|
+
// Check file count limit (skip if unlimited: 0)
|
|
116
116
|
if (this.fileSizeLimits.maxTotalFiles > 0 && filtered.length >= this.fileSizeLimits.maxTotalFiles) {
|
|
117
117
|
if (this.config.verbose) {
|
|
118
118
|
console.log(`⚠️ Reached file count limit: ${this.fileSizeLimits.maxTotalFiles} files`);
|
|
@@ -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
|
|
|
@@ -207,8 +207,8 @@ class UnifiedRuleRegistry {
|
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
// Also check other category directories (security, typescript, etc.)
|
|
211
|
-
const otherDirs = ['security', 'typescript', 'react'];
|
|
210
|
+
// Also check other category directories (security, typescript, dart, etc.)
|
|
211
|
+
const otherDirs = ['security', 'typescript', 'dart', 'react'];
|
|
212
212
|
for (const categoryDir of otherDirs) {
|
|
213
213
|
const categoryPath = path.join(rulesBaseDir, categoryDir);
|
|
214
214
|
|
|
@@ -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
|
|
|
@@ -66,7 +66,7 @@ node cli.js --rule=C002 \
|
|
|
66
66
|
// Line 27-51
|
|
67
67
|
function detectDartSupport(ruleId) {
|
|
68
68
|
const rulesBasePath = path.join(__dirname, '../rules');
|
|
69
|
-
const categories = ['common', 'security', 'typescript'];
|
|
69
|
+
const categories = ['common', 'security', 'typescript', 'dart'];
|
|
70
70
|
|
|
71
71
|
for (const category of categories) {
|
|
72
72
|
const categoryPath = path.join(rulesBasePath, category);
|