@sun-asterisk/sunlint 1.3.32 → 1.3.33

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/README.md CHANGED
@@ -19,6 +19,7 @@ Sun Lint is a universal coding standards checker providing comprehensive code qu
19
19
  - ✅ **Advanced File Targeting**: Include/exclude patterns, language filtering
20
20
  - ✅ **Quality Scoring System**: Automated quality score (0-100) with grade (A+ to F)
21
21
  - ✅ **Summary Reports**: JSON format for CI/CD dashboards and management reports
22
+ - ✅ **Architecture Detection**: Detect architecture patterns (Layered, Modular, MVVM, VIPER) with health scoring
22
23
 
23
24
  ### **🏗️ Architecture**
24
25
 
@@ -128,6 +129,52 @@ sunlint --all --input=src --output-summary=quality.json
128
129
  - Quality gate setup
129
130
  - Trending analysis
130
131
 
132
+ ## 🏛️ **Architecture Detection** 🆕
133
+
134
+ Detect and analyze architecture patterns in your codebase with health scoring and violation detection.
135
+
136
+ ### **Features**
137
+ - **Pattern Detection**: Layered, Modular, MVVM, VIPER, Clean Architecture
138
+ - **Health Score (0-100)**: Architecture compliance scoring
139
+ - **Violation Detection**: Identify architecture anti-patterns
140
+ - **Markdown Reports**: Detailed analysis reports for documentation
141
+
142
+ ### **Quick Usage**
143
+ ```bash
144
+ # Architecture analysis only
145
+ sunlint --architecture --input=src
146
+
147
+ # Combined: Code quality + Architecture
148
+ sunlint --all --architecture --input=src
149
+
150
+ # Generate markdown report
151
+ sunlint --architecture --arch-report --input=src
152
+
153
+ # Target specific patterns
154
+ sunlint --architecture --arch-patterns=mvvm,layered --input=src
155
+ ```
156
+
157
+ ### **Example Output**
158
+ ```
159
+ 🏛️ Architecture Analysis:
160
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
161
+ • Pattern: LAYERED (69% confidence)
162
+ • Health Score: 85/100
163
+ • Violations: 3
164
+
165
+ Top Architecture Violations:
166
+ ⚠ Interface/Contract Definitions - score 20% (threshold: 50%)
167
+ ⚠ Middleware/Interceptor Layer - score 0% (threshold: 50%)
168
+ ... and 1 more
169
+ ```
170
+
171
+ ### **Options**
172
+ | Option | Description |
173
+ |--------|-------------|
174
+ | `--architecture` | Enable architecture pattern detection |
175
+ | `--arch-patterns <patterns>` | Target specific patterns (comma-separated: layered, modular, mvvm, viper) |
176
+ | `--arch-report` | Generate separate markdown report |
177
+
131
178
  ## 📦 **Installation**
132
179
 
133
180
  ### **Global Installation (Recommended)**
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Architecture Integration for SunLint
3
+ * Wraps architecture-detection module for seamless integration
4
+ * Following Rule C005: Single responsibility - handle architecture analysis integration
5
+ */
6
+
7
+ const path = require('path');
8
+ const fs = require('fs');
9
+ const chalk = require('chalk');
10
+
11
+ class ArchitectureIntegration {
12
+ constructor(options = {}) {
13
+ this.options = options;
14
+ this.archModule = null;
15
+ }
16
+
17
+ /**
18
+ * Load architecture detection module
19
+ * Tries bundled version first, then falls back to local development path
20
+ */
21
+ async loadArchitectureModule() {
22
+ if (this.archModule) {
23
+ return this.archModule;
24
+ }
25
+
26
+ // Try bundled version first (engines/arch-detect)
27
+ const bundledPath = path.join(__dirname, '..', 'engines', 'arch-detect', 'index.js');
28
+
29
+ if (fs.existsSync(bundledPath)) {
30
+ try {
31
+ this.archModule = require(bundledPath);
32
+ if (this.options.verbose) {
33
+ console.log(chalk.gray('📦 Loaded bundled architecture-detection'));
34
+ }
35
+ return this.archModule;
36
+ } catch (error) {
37
+ if (this.options.verbose) {
38
+ console.log(chalk.yellow(`⚠️ Failed to load bundled module: ${error.message}`));
39
+ }
40
+ }
41
+ }
42
+
43
+ // Fallback: Try local development path
44
+ const devPaths = [
45
+ path.join(__dirname, '..', '..', '..', '..', 'architecture-detection', 'dist', 'index.js'),
46
+ path.join(__dirname, '..', '..', '..', 'architecture-detection', 'dist', 'index.js'),
47
+ ];
48
+
49
+ for (const devPath of devPaths) {
50
+ if (fs.existsSync(devPath)) {
51
+ try {
52
+ this.archModule = require(devPath);
53
+ if (this.options.verbose) {
54
+ console.log(chalk.gray(`📦 Loaded architecture-detection from: ${devPath}`));
55
+ }
56
+ return this.archModule;
57
+ } catch (error) {
58
+ if (this.options.verbose) {
59
+ console.log(chalk.yellow(`⚠️ Failed to load from ${devPath}: ${error.message}`));
60
+ }
61
+ }
62
+ }
63
+ }
64
+
65
+ throw new Error(
66
+ 'Architecture detection module not found. Run "npm run build" to bundle it, ' +
67
+ 'or ensure architecture-detection is built in the parent directory.'
68
+ );
69
+ }
70
+
71
+ /**
72
+ * Parse architecture patterns from CLI option
73
+ */
74
+ parsePatterns() {
75
+ if (!this.options.archPatterns) {
76
+ return undefined; // Use default patterns
77
+ }
78
+
79
+ const patternMap = {
80
+ 'layered': 'LAYERED',
81
+ 'modular': 'MODULAR',
82
+ 'mvvm': 'MVVM',
83
+ 'viper': 'VIPER',
84
+ 'presentation': 'PRESENTATION',
85
+ 'clean': 'CLEAN_ARCHITECTURE',
86
+ 'tdd': 'TDD_CLEAN_ARCHITECTURE',
87
+ };
88
+
89
+ const patterns = this.options.archPatterns
90
+ .split(',')
91
+ .map(p => p.trim().toLowerCase())
92
+ .map(p => patternMap[p] || p.toUpperCase())
93
+ .filter(Boolean);
94
+
95
+ return patterns.length > 0 ? patterns : undefined;
96
+ }
97
+
98
+ /**
99
+ * Run architecture analysis on project
100
+ * @param {string} projectPath - Path to analyze
101
+ * @returns {Object} Architecture analysis results
102
+ */
103
+ async analyze(projectPath) {
104
+ const archModule = await this.loadArchitectureModule();
105
+ const { ArchitectureAnalyzer } = archModule;
106
+
107
+ if (!ArchitectureAnalyzer) {
108
+ throw new Error('ArchitectureAnalyzer not found in architecture-detection module');
109
+ }
110
+
111
+ const analyzer = new ArchitectureAnalyzer({
112
+ patterns: this.parsePatterns(),
113
+ respectGitignore: true,
114
+ verbose: this.options.verbose,
115
+ });
116
+
117
+ if (this.options.verbose) {
118
+ console.log(chalk.blue(`🏛️ Analyzing architecture: ${projectPath}`));
119
+ }
120
+
121
+ const result = await analyzer.analyze(projectPath);
122
+
123
+ // Convert to SunLint-compatible format
124
+ return this.convertToSunLintFormat(result, analyzer, projectPath);
125
+ }
126
+
127
+ /**
128
+ * Convert architecture results to SunLint format
129
+ */
130
+ convertToSunLintFormat(result, analyzer, projectPath) {
131
+ const violations = [];
132
+
133
+ // Convert architecture violations to SunLint violation format
134
+ // Check violationAssessment.violations from the new format
135
+ const violationAssessment = result.violationAssessment;
136
+ if (violationAssessment && violationAssessment.violations && violationAssessment.violations.length > 0) {
137
+ for (const violation of violationAssessment.violations) {
138
+ violations.push({
139
+ ruleId: `ARCH-${violation.type || 'VIOLATION'}`,
140
+ severity: this.mapSeverity(violation.impact),
141
+ message: violation.description || violation.ruleName,
142
+ file: violation.affectedFiles?.[0] || projectPath,
143
+ line: 1,
144
+ column: 1,
145
+ category: 'architecture',
146
+ source: 'architecture-detection',
147
+ details: {
148
+ ruleName: violation.ruleName,
149
+ impactReason: violation.impactReason,
150
+ suggestedFix: violation.suggestedFix,
151
+ affectedFiles: violation.affectedFiles,
152
+ },
153
+ });
154
+ }
155
+ }
156
+
157
+ // Generate markdown report if requested
158
+ let markdownReport = null;
159
+ if (this.options.archReport) {
160
+ try {
161
+ markdownReport = analyzer.formatAsMarkdown(result);
162
+ } catch (error) {
163
+ if (this.options.verbose) {
164
+ console.log(chalk.yellow(`⚠️ Could not generate markdown report: ${error.message}`));
165
+ }
166
+ }
167
+ }
168
+
169
+ // Map from hybridAnalysis structure
170
+ const hybridAnalysis = result.hybridAnalysis || {};
171
+ const healthScore = violationAssessment?.healthScore || 100;
172
+
173
+ return {
174
+ summary: {
175
+ primaryPattern: hybridAnalysis.primaryPattern || result.primaryPattern || 'UNKNOWN',
176
+ primaryConfidence: hybridAnalysis.confidence || 0,
177
+ secondaryPatterns: (hybridAnalysis.secondaryPatterns || []).map(p => ({
178
+ pattern: p,
179
+ confidence: 0.5, // Default confidence for secondary patterns
180
+ })),
181
+ healthScore: healthScore,
182
+ violationCount: violations.length,
183
+ analysisTime: result.metadata?.analysisTimeMs || 0,
184
+ isHybrid: hybridAnalysis.isHybrid || false,
185
+ combination: hybridAnalysis.combination || null,
186
+ },
187
+ violations,
188
+ markdownReport,
189
+ raw: result,
190
+ };
191
+ }
192
+
193
+ /**
194
+ * Map architecture severity to SunLint severity
195
+ */
196
+ mapSeverity(severity) {
197
+ const severityMap = {
198
+ 'critical': 'error',
199
+ 'high': 'error',
200
+ 'medium': 'warning',
201
+ 'low': 'info',
202
+ };
203
+ return severityMap[severity?.toLowerCase()] || 'warning';
204
+ }
205
+
206
+ /**
207
+ * Save markdown report to file
208
+ */
209
+ async saveReport(markdownContent, projectPath) {
210
+ const projectName = path.basename(projectPath);
211
+ const date = new Date().toISOString().split('T')[0].replace(/-/g, '_');
212
+ const fileName = `sun_arch_report_${projectName}_${date}.md`;
213
+ const outputPath = path.join(process.cwd(), fileName);
214
+
215
+ fs.writeFileSync(outputPath, markdownContent, 'utf8');
216
+ return outputPath;
217
+ }
218
+ }
219
+
220
+ module.exports = { ArchitectureIntegration };
@@ -14,6 +14,7 @@ const AnalysisOrchestrator = require('./analysis-orchestrator');
14
14
  const OutputService = require('./output-service');
15
15
  const GitUtils = require('./git-utils');
16
16
  const FileTargetingService = require('./file-targeting-service');
17
+ const { ArchitectureIntegration } = require('./architecture-integration');
17
18
 
18
19
  // Legacy orchestrator for fallback
19
20
  // const LegacyOrchestrator = require('./legacy-analysis-orchestrator'); // Removed
@@ -80,11 +81,25 @@ class CliActionHandler {
80
81
 
81
82
  // Run analysis with appropriate orchestrator
82
83
  const startTime = Date.now();
83
- const results = await this.runModernAnalysis(rulesToRun, targetingResult.files, config);
84
+ let results = null;
85
+
86
+ // Run code quality analysis (unless --architecture is used alone)
87
+ if (rulesToRun.length > 0 && !this.isArchitectureOnly()) {
88
+ results = await this.runModernAnalysis(rulesToRun, targetingResult.files, config);
89
+ } else {
90
+ results = { results: [], summary: { total: 0, errors: 0, warnings: 0 } };
91
+ }
92
+
93
+ // Run architecture analysis if requested
94
+ if (this.options.architecture) {
95
+ const architectureResults = await this.runArchitectureAnalysis();
96
+ results.architecture = architectureResults;
97
+ }
98
+
84
99
  const duration = Date.now() - startTime;
85
100
 
86
101
  // Output results
87
- await this.outputService.outputResults(results, this.options, {
102
+ await this.outputService.outputResults(results, this.options, {
88
103
  duration,
89
104
  rulesRun: rulesToRun.length,
90
105
  rulesChecked: rulesToRun.length
@@ -494,18 +509,64 @@ class CliActionHandler {
494
509
  */
495
510
  handleExit(results) {
496
511
  if (this.options.noExit) return;
497
-
512
+
498
513
  // Check if any violations were found
499
- const hasViolations = results.results?.some(result =>
514
+ const hasViolations = results.results?.some(result =>
500
515
  result.violations && result.violations.length > 0
501
516
  );
502
-
517
+
503
518
  if (hasViolations && this.options.failOnViolations !== false) {
504
519
  process.exit(1);
505
520
  } else {
506
521
  process.exit(0);
507
522
  }
508
523
  }
524
+
525
+ /**
526
+ * Check if only architecture analysis was requested (no code quality rules)
527
+ * Following Rule C006: Verb-noun naming
528
+ */
529
+ isArchitectureOnly() {
530
+ return this.options.architecture &&
531
+ !this.options.all &&
532
+ !this.options.rule &&
533
+ !this.options.rules &&
534
+ !this.options.quality &&
535
+ !this.options.security &&
536
+ !this.options.category;
537
+ }
538
+
539
+ /**
540
+ * Run architecture analysis using architecture-detection module
541
+ * Following Rule C006: Verb-noun naming
542
+ */
543
+ async runArchitectureAnalysis() {
544
+ if (!this.options.quiet) {
545
+ console.log(chalk.blue('🏛️ Running architecture analysis...'));
546
+ }
547
+
548
+ try {
549
+ const integration = new ArchitectureIntegration(this.options);
550
+ const projectPath = this.getProjectPath();
551
+ const results = await integration.analyze(projectPath);
552
+
553
+ // Save markdown report if requested
554
+ if (this.options.archReport && results.markdownReport) {
555
+ const reportPath = await integration.saveReport(results.markdownReport, projectPath);
556
+ if (!this.options.quiet) {
557
+ console.log(chalk.green(`📄 Architecture report saved: ${reportPath}`));
558
+ }
559
+ }
560
+
561
+ return results;
562
+ } catch (error) {
563
+ console.error(chalk.yellow(`⚠️ Architecture analysis failed: ${error.message}`));
564
+ if (this.options.debug) {
565
+ console.error(error.stack);
566
+ }
567
+ return null;
568
+ }
569
+ }
509
570
  }
510
571
 
511
572
  module.exports = CliActionHandler;
@@ -23,6 +23,12 @@ function createCliProgram() {
23
23
  .option('--quality', 'Run all code quality rules')
24
24
  .option('--security', 'Run all secure coding rules');
25
25
 
26
+ // Architecture Analysis options
27
+ program
28
+ .option('--architecture', 'Enable architecture pattern detection (layered, modular, mvvm, viper)')
29
+ .option('--arch-patterns <patterns>', 'Target specific architecture patterns (comma-separated)')
30
+ .option('--arch-report', 'Generate separate architecture MD report');
31
+
26
32
  // TypeScript specific options (Phase 1 focus)
27
33
  program
28
34
  .option('--typescript', 'Enable TypeScript-specific analysis')
@@ -156,11 +162,17 @@ Advanced File Targeting:
156
162
 
157
163
  Large Project Optimization:
158
164
  $ sunlint --all --input=. --max-semantic-files=500 # Conservative analysis
159
- $ sunlint --all --input=. --max-semantic-files=2000 # Comprehensive analysis
165
+ $ sunlint --all --input=. --max-semantic-files=2000 # Comprehensive analysis
160
166
  $ sunlint --all --input=. --max-semantic-files=-1 # Unlimited (all files)
161
167
  $ sunlint --all --input=. --max-semantic-files=0 # Disable semantic analysis
162
168
  $ sunlint --all --changed-files --max-semantic-files=300 # Fast CI analysis
163
169
 
170
+ Architecture Analysis:
171
+ $ sunlint --all --architecture --input=src # Code quality + architecture
172
+ $ sunlint --architecture --input=src # Architecture only
173
+ $ sunlint --architecture --arch-report --input=src # Generate MD report
174
+ $ sunlint --architecture --arch-patterns=mvvm,layered --input=src
175
+
164
176
  Sun* Engineering - Coding Standards Made Simple ☀️
165
177
  `);
166
178
 
@@ -115,6 +115,74 @@ class OutputService {
115
115
  if (!options.quiet && options.format !== 'json') {
116
116
  console.log(report.summary);
117
117
  }
118
+
119
+ // Output architecture results if available
120
+ if (results.architecture && !options.quiet) {
121
+ this.outputArchitectureResults(results.architecture, options);
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Output architecture analysis results
127
+ * @param {Object} archResults - Architecture analysis results
128
+ * @param {Object} options - Output options
129
+ */
130
+ outputArchitectureResults(archResults, options) {
131
+ if (!archResults || !archResults.summary) {
132
+ return;
133
+ }
134
+
135
+ const summary = archResults.summary;
136
+
137
+ console.log(chalk.blue('\n🏛️ Architecture Analysis:'));
138
+ console.log('━'.repeat(50));
139
+
140
+ // Primary pattern
141
+ const confidence = Math.round(summary.primaryConfidence * 100);
142
+ console.log(`• Pattern: ${chalk.cyan(summary.primaryPattern)} (${confidence}% confidence)`);
143
+
144
+ // Secondary patterns
145
+ if (summary.secondaryPatterns && summary.secondaryPatterns.length > 0) {
146
+ const secondary = summary.secondaryPatterns
147
+ .map(p => `${p.pattern} (${Math.round(p.confidence * 100)}%)`)
148
+ .join(', ');
149
+ console.log(`• Secondary: ${chalk.gray(secondary)}`);
150
+ }
151
+
152
+ // Health score
153
+ const healthScore = Math.round(summary.healthScore);
154
+ const healthColor = healthScore >= 80 ? chalk.green :
155
+ healthScore >= 60 ? chalk.yellow : chalk.red;
156
+ console.log(`• Health Score: ${healthColor(healthScore + '/100')}`);
157
+
158
+ // Violations
159
+ if (summary.violationCount > 0) {
160
+ console.log(`• Violations: ${chalk.red(summary.violationCount)}`);
161
+
162
+ // Show first 5 violations
163
+ if (archResults.violations && archResults.violations.length > 0) {
164
+ console.log(chalk.gray('\nTop Architecture Violations:'));
165
+ archResults.violations.slice(0, 5).forEach((v, i) => {
166
+ const severity = v.severity === 'error' ? chalk.red('✗') : chalk.yellow('⚠');
167
+ console.log(` ${severity} ${v.message}`);
168
+ if (v.file) {
169
+ console.log(chalk.gray(` → ${v.file}:${v.line || 1}`));
170
+ }
171
+ });
172
+ if (archResults.violations.length > 5) {
173
+ console.log(chalk.gray(` ... and ${archResults.violations.length - 5} more`));
174
+ }
175
+ }
176
+ } else {
177
+ console.log(`• Violations: ${chalk.green('None')}`);
178
+ }
179
+
180
+ // Report file info
181
+ if (archResults.markdownReport && options.archReport) {
182
+ console.log(chalk.gray('\n📄 Full architecture report saved'));
183
+ }
184
+
185
+ console.log();
118
186
  }
119
187
 
120
188
  generateAndSaveSummaryReport(violations, results, options, metadata) {
@@ -463,35 +531,30 @@ class OutputService {
463
531
  });
464
532
 
465
533
  if (uploadResult.success) {
466
- if (!options.quiet) {
467
- console.log(chalk.green(`✅ Report successfully uploaded to: ${apiUrl}`));
468
- if (uploadResult.statusCode) {
469
- console.log(chalk.green(`📡 HTTP Status: ${uploadResult.statusCode}`));
470
- }
471
- if (uploadResult.response) {
472
- try {
473
- const responseData = JSON.parse(uploadResult.response);
474
- if (responseData.message) {
475
- console.log(chalk.blue(`💬 Server response: ${responseData.message}`));
476
- }
477
- if (responseData.report_id) {
478
- console.log(chalk.blue(`📝 Report ID: ${responseData.report_id}`));
479
- }
480
- if (responseData.repository) {
481
- console.log(chalk.blue(`🏠 Repository: ${responseData.repository}`));
482
- }
483
- if (responseData.actor) {
484
- console.log(chalk.blue(`👤 Submitted by: ${responseData.actor}`));
485
- }
486
- } catch (parseError) {
487
- // If response is not JSON, show raw response
534
+ if (!options.quiet && uploadResult.response) {
535
+ try {
536
+ const responseData = JSON.parse(uploadResult.response);
537
+ if (responseData.message) {
538
+ console.log(chalk.blue(`💬 Server response: ${responseData.message}`));
539
+ }
540
+ if (responseData.report_id) {
541
+ console.log(chalk.blue(`📝 Report ID: ${responseData.report_id}`));
542
+ }
543
+ if (responseData.repository) {
544
+ console.log(chalk.blue(`🏠 Repository: ${responseData.repository}`));
545
+ }
546
+ if (responseData.actor) {
547
+ console.log(chalk.blue(`👤 Submitted by: ${responseData.actor}`));
548
+ }
549
+ } catch (parseError) {
550
+ // If response is not JSON, show raw response if verbose
551
+ if (options.verbose) {
488
552
  console.log(chalk.gray(`📄 Response: ${uploadResult.response.substring(0, 200)}...`));
489
553
  }
490
554
  }
491
555
  }
492
556
  } else {
493
- console.warn(chalk.yellow(`⚠️ Failed to upload report: ${uploadResult.error}`));
494
-
557
+ // Error already logged in upload-service.js
495
558
  if (options.verbose && uploadResult.errorContext) {
496
559
  console.warn('Upload error details:', uploadResult.errorContext);
497
560
  }
@@ -10,19 +10,50 @@ const path = require('path');
10
10
 
11
11
  class ScoringService {
12
12
  constructor() {
13
- // Scoring weights
13
+ // Scoring weights based on violations per KLOC (1000 lines)
14
+ //
15
+ // Calibration targets:
16
+ // - 0 violations/KLOC = 100 (A+)
17
+ // - 1-2 violations/KLOC = 90-95 (A/A+)
18
+ // - 3-4 violations/KLOC = 80-89 (B/B+)
19
+ // - 5-7 violations/KLOC = 70-79 (C/C+)
20
+ // - 8-10 violations/KLOC = 60-69 (D)
21
+ // - >10 violations/KLOC = <60 (F)
22
+ //
14
23
  this.weights = {
15
- errorPenalty: 5, // Each error reduces score by 5 points
16
- warningPenalty: 1, // Each warning reduces score by 1 point
17
- ruleBonus: 0.5, // Bonus for each rule checked
18
- locFactor: 0.001 // Factor for normalizing by LOC
24
+ // Penalty per violation type (per KLOC)
25
+ // Errors are 3x more severe than warnings
26
+ errorPenaltyPerKLOC: 6, // Each error per KLOC reduces score by 6 points
27
+ warningPenaltyPerKLOC: 2, // Each warning per KLOC reduces score by 2 points
28
+
29
+ // Absolute penalty thresholds (regardless of LOC)
30
+ // Large projects should still be penalized for raw violation counts
31
+ absoluteErrorThreshold: 20, // Start penalizing if errors > 20
32
+ absoluteErrorPenalty: 0.05, // Each error above threshold reduces score by 0.05 (max 15 pts)
33
+
34
+ absoluteWarningThreshold: 100, // Start penalizing if warnings > 100
35
+ absoluteWarningPenalty: 0.01, // Each warning above threshold reduces score by 0.01 (max 10 pts)
36
+ };
37
+
38
+ // Thresholds for violations per KLOC (used for grading reference)
39
+ this.thresholds = {
40
+ excellent: 1, // < 1 violation per KLOC = excellent (A+/A)
41
+ good: 3, // < 3 violations per KLOC = good (B+/B)
42
+ acceptable: 5, // < 5 violations per KLOC = acceptable (C+/C)
43
+ poor: 10, // < 10 violations per KLOC = poor (D)
44
+ // >= 10 = very poor (F)
19
45
  };
20
46
  }
21
47
 
22
48
  /**
23
49
  * Calculate quality score
24
- * Score formula: 100 - (errorCount * 5 + warningCount * 1) * (1000 / LOC) + (rulesChecked * 0.5)
25
- *
50
+ *
51
+ * New formula (v2):
52
+ * 1. Calculate violations per KLOC (errorsPerKLOC, warningsPerKLOC)
53
+ * 2. Apply penalty based on density: errorsPerKLOC * 2 + warningsPerKLOC * 0.5
54
+ * 3. Apply absolute penalty for projects with too many errors
55
+ * 4. Add small bonus for rules checked
56
+ *
26
57
  * @param {Object} params
27
58
  * @param {number} params.errorCount - Number of errors found
28
59
  * @param {number} params.warningCount - Number of warnings found
@@ -34,20 +65,34 @@ class ScoringService {
34
65
  // Base score starts at 100
35
66
  let score = 100;
36
67
 
37
- // Calculate violations penalty
38
- const totalPenalty = (errorCount * this.weights.errorPenalty) +
39
- (warningCount * this.weights.warningPenalty);
40
-
41
- // Normalize penalty by LOC (per 1000 lines)
42
- const locNormalization = loc > 0 ? 1000 / loc : 1;
43
- const normalizedPenalty = totalPenalty * locNormalization;
44
-
45
- // Apply penalty
46
- score -= normalizedPenalty;
68
+ // Calculate KLOC (thousands of lines of code)
69
+ const kloc = Math.max(loc / 1000, 1); // Minimum 1 KLOC to avoid division issues
70
+
71
+ // Calculate violations per KLOC
72
+ const errorsPerKLOC = errorCount / kloc;
73
+ const warningsPerKLOC = warningCount / kloc;
74
+ const totalViolationsPerKLOC = errorsPerKLOC + warningsPerKLOC;
75
+
76
+ // 1. Density-based penalty (main scoring factor)
77
+ // This penalizes based on how "dense" the violations are
78
+ const densityPenalty = (errorsPerKLOC * this.weights.errorPenaltyPerKLOC) +
79
+ (warningsPerKLOC * this.weights.warningPenaltyPerKLOC);
80
+ score -= densityPenalty;
81
+
82
+ // 2. Absolute penalty for projects with too many errors
83
+ // Even large codebases should not have hundreds of errors
84
+ if (errorCount > this.weights.absoluteErrorThreshold) {
85
+ const excessErrors = errorCount - this.weights.absoluteErrorThreshold;
86
+ const absoluteErrorPenalty = excessErrors * this.weights.absoluteErrorPenalty;
87
+ score -= Math.min(absoluteErrorPenalty, 15); // Cap at 15 points
88
+ }
47
89
 
48
- // Add bonus for rules checked (more rules = more thorough check)
49
- const ruleBonus = Math.min(rulesChecked * this.weights.ruleBonus, 10); // Max 10 points bonus
50
- score += ruleBonus;
90
+ // 3. Absolute penalty for projects with too many warnings
91
+ if (warningCount > this.weights.absoluteWarningThreshold) {
92
+ const excessWarnings = warningCount - this.weights.absoluteWarningThreshold;
93
+ const absoluteWarningPenalty = excessWarnings * this.weights.absoluteWarningPenalty;
94
+ score -= Math.min(absoluteWarningPenalty, 10); // Cap at 10 points
95
+ }
51
96
 
52
97
  // Ensure score is between 0-100
53
98
  score = Math.max(0, Math.min(100, score));
@@ -24,15 +24,49 @@ class UploadService {
24
24
  }
25
25
 
26
26
  const uploadResult = await this.executeUploadCommand(filePath, apiUrl, options);
27
-
28
- console.log(chalk.green(`✅ Report uploaded successfully!`));
29
- return {
30
- success: true,
31
- url: apiUrl,
32
- filePath: filePath,
33
- response: uploadResult.response,
34
- statusCode: uploadResult.statusCode
35
- };
27
+
28
+ // Check if upload was actually successful based on HTTP status code
29
+ const statusCode = uploadResult.statusCode;
30
+ const isSuccess = statusCode >= 200 && statusCode < 300;
31
+
32
+ if (isSuccess) {
33
+ console.log(chalk.green(`✅ Report uploaded successfully!`));
34
+ console.log(chalk.green(`📡 HTTP Status: ${statusCode}`));
35
+ return {
36
+ success: true,
37
+ url: apiUrl,
38
+ filePath: filePath,
39
+ response: uploadResult.response,
40
+ statusCode: statusCode
41
+ };
42
+ } else {
43
+ // Handle non-success HTTP status codes
44
+ const errorMessages = {
45
+ 401: 'Unauthorized - Authentication required. Make sure OIDC token is configured correctly.',
46
+ 403: 'Forbidden - Access denied. Check your permissions.',
47
+ 404: 'Not Found - API endpoint does not exist.',
48
+ 500: 'Internal Server Error - Server-side issue.',
49
+ 502: 'Bad Gateway - Server is temporarily unavailable.',
50
+ 503: 'Service Unavailable - Server is overloaded or under maintenance.'
51
+ };
52
+
53
+ const errorMessage = errorMessages[statusCode] || `HTTP Error ${statusCode}`;
54
+ console.error(chalk.red(`❌ Upload failed: ${errorMessage}`));
55
+ console.error(chalk.red(`📡 HTTP Status: ${statusCode}`));
56
+
57
+ if (uploadResult.response) {
58
+ console.error(chalk.yellow(`📄 Response: ${uploadResult.response}`));
59
+ }
60
+
61
+ return {
62
+ success: false,
63
+ url: apiUrl,
64
+ filePath: filePath,
65
+ response: uploadResult.response,
66
+ statusCode: statusCode,
67
+ error: errorMessage
68
+ };
69
+ }
36
70
 
37
71
  } catch (error) {
38
72
  const errorContext = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sun-asterisk/sunlint",
3
- "version": "1.3.32",
3
+ "version": "1.3.33",
4
4
  "description": "☀️ SunLint - Multi-language static analysis tool for code quality and security | Sun* Engineering Standards",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -41,18 +41,19 @@
41
41
  "demo:file-targeting": "./demo-file-targeting.sh",
42
42
  "lint": "node cli.js --config=.sunlint.json --input=.",
43
43
  "lint:eslint-integration": "node cli.js --all --eslint-integration --input=.",
44
- "build": "npm run copy-rules && npm run generate-registry && echo 'Build completed with rules copy and registry generation'",
44
+ "build": "npm run copy-rules && npm run generate-registry && npm run copy-arch-detect && echo 'Build completed'",
45
45
  "copy-rules": "node scripts/copy-rules.js",
46
46
  "generate-registry": "node scripts/generate-rules-registry.js",
47
- "clean": "rm -rf coverage/ *.log reports/ *.tgz",
47
+ "copy-arch-detect": "node scripts/copy-arch-detect.js",
48
+ "clean": "rm -rf coverage/ *.log reports/ *.tgz engines/arch-detect",
48
49
  "postpack": "echo '📦 Package created successfully! Size: ' && ls -lh *.tgz | awk '{print $5}'",
49
50
  "start": "node cli.js --help",
50
51
  "version": "node cli.js --version",
51
- "pack": "npm run copy-rules && npm run generate-registry && npm pack",
52
+ "pack": "npm run build && npm pack",
52
53
  "publish:github": "npm publish --registry=https://npm.pkg.github.com",
53
54
  "publish:npmjs": "npm publish --registry=https://registry.npmjs.org",
54
55
  "publish:test": "npm publish --dry-run --registry=https://registry.npmjs.org",
55
- "prepublishOnly": "npm run clean && npm run copy-rules && npm run generate-registry"
56
+ "prepublishOnly": "npm run clean && npm run build"
56
57
  },
57
58
  "keywords": [
58
59
  "linting",
@@ -114,6 +114,25 @@ class C024SymbolBasedAnalyzer {
114
114
  'commit',
115
115
  'useState',
116
116
  'useReducer',
117
+ // Logging functions
118
+ 'console.log',
119
+ 'console.error',
120
+ 'console.warn',
121
+ 'console.info',
122
+ 'console.debug',
123
+ 'console.trace',
124
+ 'logger.log',
125
+ 'logger.error',
126
+ 'logger.warn',
127
+ 'logger.info',
128
+ 'logger.debug',
129
+ 'this.logger',
130
+ 'log',
131
+ 'error',
132
+ 'warn',
133
+ 'info',
134
+ 'debug',
135
+ 'trace',
117
136
  ];
118
137
 
119
138
  // === String patterns that are acceptable (not magic strings) ===
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Copy architecture-detection dist to engines/arch-detect
3
+ * This script is run during build to bundle architecture detection
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ const SOURCE = path.resolve(__dirname, '../../../../architecture-detection/dist');
10
+ const DEST = path.resolve(__dirname, '../engines/arch-detect');
11
+
12
+ function copyDir(src, dest) {
13
+ if (!fs.existsSync(src)) {
14
+ console.log('⚠️ architecture-detection/dist not found. Run "npm run build" in architecture-detection first.');
15
+ return false;
16
+ }
17
+
18
+ // Clean destination
19
+ if (fs.existsSync(dest)) {
20
+ fs.rmSync(dest, { recursive: true });
21
+ }
22
+ fs.mkdirSync(dest, { recursive: true });
23
+
24
+ // Copy files recursively
25
+ const entries = fs.readdirSync(src, { withFileTypes: true });
26
+
27
+ for (const entry of entries) {
28
+ const srcPath = path.join(src, entry.name);
29
+ const destPath = path.join(dest, entry.name);
30
+
31
+ // Skip source maps and declaration files
32
+ if (entry.name.endsWith('.map') || entry.name.endsWith('.d.ts')) {
33
+ continue;
34
+ }
35
+
36
+ // Skip CLI and LLM folders
37
+ if (entry.name === 'cli.js' || entry.name === 'llm' || entry.name === 'llm-primary') {
38
+ continue;
39
+ }
40
+
41
+ if (entry.isDirectory()) {
42
+ copyDir(srcPath, destPath);
43
+ } else {
44
+ fs.copyFileSync(srcPath, destPath);
45
+ }
46
+ }
47
+
48
+ return true;
49
+ }
50
+
51
+ console.log('📦 Copying architecture-detection...');
52
+
53
+ if (copyDir(SOURCE, DEST)) {
54
+ const size = getTotalSize(DEST);
55
+ console.log(`✅ Copied to engines/arch-detect (${formatSize(size)})`);
56
+ } else {
57
+ console.log('⚠️ Skipped architecture-detection copy');
58
+ }
59
+
60
+ function getTotalSize(dir) {
61
+ let size = 0;
62
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
63
+ for (const entry of entries) {
64
+ const fullPath = path.join(dir, entry.name);
65
+ if (entry.isDirectory()) {
66
+ size += getTotalSize(fullPath);
67
+ } else {
68
+ size += fs.statSync(fullPath).size;
69
+ }
70
+ }
71
+ return size;
72
+ }
73
+
74
+ function formatSize(bytes) {
75
+ if (bytes < 1024) return bytes + ' B';
76
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
77
+ return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
78
+ }