@sun-asterisk/sunlint 1.3.9 → 1.3.11

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/CHANGELOG.md CHANGED
@@ -2,7 +2,97 @@
2
2
 
3
3
  ---
4
4
 
5
- ## 🔧 **v1.3.9 - File Targeting Regression Fix (October 2, 2025)**
5
+ ## **v1.4.0 - Quality Scoring & Summary Reports (October 7, 2025)**
6
+
7
+ **Release Date**: October 7, 2025
8
+ **Type**: Major Feature Release
9
+ **Branch**: `fix.sunlint.cli_options`
10
+
11
+ ### ✨ **Major Features**
12
+
13
+ #### **Quality Scoring System** 🎯
14
+ - **NEW**: Automated quality score calculation (0-100) based on violations, LOC, and rules checked
15
+ - **NEW**: Grade system (A+ to F) for easy quality interpretation
16
+ - **Formula**: `Score = 100 - (errors × 5 + warnings × 1) × (1000 / LOC) + (rules × 0.5)`
17
+ - **Normalization**: Violations normalized per 1000 lines of code (KLOC)
18
+ - **Bonus System**: Up to 10 points bonus for comprehensive rule coverage
19
+
20
+ #### **Summary Report Generation** 📊
21
+ - **NEW**: `--output-summary <file>` option for JSON summary reports
22
+ - **Format**: CI/CD-ready JSON with quality score, violations, and metrics
23
+ - **Git Integration**: Auto-detects repository URL, branch, and commit hash
24
+ - **Environment Variables**: Supports GitHub Actions env vars (GITHUB_REPOSITORY, GITHUB_SHA, etc.)
25
+ - **Violation Summary**: Aggregated violations by rule with count and severity
26
+ - **Metrics**: LOC, files analyzed, violations per KLOC, errors/warnings breakdown
27
+
28
+ #### **New Services** 🛠️
29
+ - **ScoringService**: Calculate quality scores with customizable weights
30
+ - Error penalty: -5 points
31
+ - Warning penalty: -1 point
32
+ - Rule bonus: +0.5 points (max 10)
33
+ - LOC normalization factor: 0.001
34
+ - **SummaryReportService**: Generate and save summary reports
35
+ - Git information extraction
36
+ - Violation aggregation by rule
37
+ - Text summary formatting for console
38
+
39
+ ### 🔧 **CLI Enhancements**
40
+ - **FIXED**: Default semantic analysis now enabled for heuristic engine
41
+ - Removed default `'0'` from `--max-semantic-files` option
42
+ - ts-morph now works by default without explicit `--max-semantic-files=-1`
43
+ - **ENHANCED**: Display message now shows "TypeScript/JavaScript files (TS/TSX/JS/JSX)"
44
+ - Previously only showed "TS/JS files" causing confusion about TSX support
45
+ - **IMPROVED**: Semantic engine disabled message now more informative
46
+ - Clarifies when analysis is explicitly disabled vs. using default
47
+
48
+ ### 📝 **Documentation**
49
+ - **NEW**: `docs/QUALITY_SCORING_GUIDE.md` - Comprehensive scoring guide
50
+ - Scoring formula explanation
51
+ - Grade scale interpretation
52
+ - CI/CD integration examples (GitHub Actions, GitLab CI)
53
+ - Dashboard integration patterns
54
+ - Quality gate setup
55
+ - Trending analysis examples
56
+ - **NEW**: `examples/github-actions-quality-check.yml` - GitHub Actions workflow template
57
+ - **UPDATED**: README.md with Quality Scoring section
58
+
59
+ ### 🎯 **Use Cases**
60
+ - **CI/CD Integration**: Automated quality gates in pipelines
61
+ - **Management Dashboards**: Summary reports for project quality tracking
62
+ - **Quality Trending**: Track quality metrics over time
63
+ - **PR Comments**: Automated quality feedback on pull requests
64
+ - **Slack Notifications**: Quality score alerts to team channels
65
+
66
+ ### 📊 **Example Output**
67
+ ```json
68
+ {
69
+ "quality": {
70
+ "score": 92.6,
71
+ "grade": "A",
72
+ "metrics": {
73
+ "errors": 0,
74
+ "warnings": 39,
75
+ "linesOfCode": 4954,
76
+ "violationsPerKLOC": 7.9
77
+ }
78
+ },
79
+ "violations": {
80
+ "total": 39,
81
+ "by_rule": [
82
+ { "rule_code": "C065", "count": 39, "severity": "warning" }
83
+ ]
84
+ }
85
+ }
86
+ ```
87
+
88
+ ### 🔗 **Related Issues**
89
+ - Fixed semantic engine requiring explicit CLI options for ts-morph
90
+ - Enhanced TSX file visibility in console output
91
+ - Improved default configuration for heuristic engine
92
+
93
+ ---
94
+
95
+ ## �🔧 **v1.3.9 - File Targeting Regression Fix (October 2, 2025)**
6
96
 
7
97
  **Release Date**: October 2, 2025
8
98
  **Type**: Bug Fix
package/README.md CHANGED
@@ -17,6 +17,8 @@ Sun Lint is a universal coding standards checker providing comprehensive code qu
17
17
  - ✅ **Zero Config**: Works immediately after `npm install`
18
18
  - ✅ **CI/CD Ready**: Baseline comparison, fail-on-new-violations, timeout protection
19
19
  - ✅ **Advanced File Targeting**: Include/exclude patterns, language filtering
20
+ - ✅ **Quality Scoring System**: Automated quality score (0-100) with grade (A+ to F)
21
+ - ✅ **Summary Reports**: JSON format for CI/CD dashboards and management reports
20
22
 
21
23
  ### **🏗️ Architecture**
22
24
 
@@ -64,8 +66,68 @@ sunlint --rules=C010,C006 --eslint-integration --input=src
64
66
 
65
67
  # Git integration
66
68
  sunlint --all --changed-files
69
+
70
+ # Quality scoring and summary report (for CI/CD)
71
+ sunlint --all --input=src --output-summary=quality-report.json
72
+ ```
73
+
74
+ ## 📊 **Quality Scoring & Summary Reports** 🆕
75
+
76
+ Generate comprehensive quality reports with automated scoring for CI/CD integration and management dashboards.
77
+
78
+ ### **Features**
79
+ - **Quality Score (0-100)**: Calculated based on violations, LOC, and rules checked
80
+ - **Grade System**: A+ to F grade for easy interpretation
81
+ - **JSON Format**: Ready for CI/CD pipelines and dashboard integration
82
+ - **Git Integration**: Automatically includes repository info, branch, and commit hash
83
+ - **Metrics**: Violations per KLOC, errors/warnings count, LOC analysis
84
+
85
+ ### **Quick Usage**
86
+ ```bash
87
+ # Generate summary report
88
+ sunlint --all --input=src --output-summary=quality.json
89
+
90
+ # Both detailed and summary reports
91
+ sunlint --all --input=src --output=detailed.txt --output-summary=quality.json
92
+
93
+ # In GitHub Actions (auto-detects git info)
94
+ sunlint --all --input=src --output-summary=quality.json
67
95
  ```
68
96
 
97
+ ### **Example Output**
98
+ ```json
99
+ {
100
+ "quality": {
101
+ "score": 92.6,
102
+ "grade": "A",
103
+ "metrics": {
104
+ "errors": 0,
105
+ "warnings": 39,
106
+ "linesOfCode": 4954,
107
+ "violationsPerKLOC": 7.9
108
+ }
109
+ },
110
+ "violations": {
111
+ "total": 39,
112
+ "by_rule": [
113
+ { "rule_code": "C065", "count": 39 }
114
+ ]
115
+ },
116
+ "repository": {
117
+ "repository_url": "https://github.com/org/repo",
118
+ "branch": "main",
119
+ "commit_hash": "abc123"
120
+ }
121
+ }
122
+ ```
123
+
124
+ **📚 Full Documentation**: See [docs/QUALITY_SCORING_GUIDE.md](docs/QUALITY_SCORING_GUIDE.md) for:
125
+ - Scoring formula and interpretation
126
+ - CI/CD integration examples (GitHub Actions, GitLab CI)
127
+ - Dashboard integration
128
+ - Quality gate setup
129
+ - Trending analysis
130
+
69
131
  ## 📦 **Installation**
70
132
 
71
133
  ### **Global Installation (Recommended)**
@@ -375,6 +375,24 @@
375
375
  }
376
376
  }
377
377
  },
378
+ "C060": {
379
+ "name": "Do not override superclass methods and ignore critical logic",
380
+ "description": "Preserve important behavior or lifecycle logic defined in the superclass to ensure correctness and prevent silent errors.",
381
+ "category": "logging",
382
+ "severity": "warning",
383
+ "languages": ["typescript", "javascript", "dart"],
384
+ "analyzer": "./rules/common/C060_no_override_superclass/analyzer.js",
385
+ "version": "1.0.0",
386
+ "status": "stable",
387
+ "tags": ["logging", "production", "debugging", "console"],
388
+ "strategy": {
389
+ "preferred": "regex",
390
+ "fallbacks": ["regex"],
391
+ "accuracy": {
392
+ "regex": 90
393
+ }
394
+ }
395
+ },
378
396
  "S001": {
379
397
  "name": "Fail Securely",
380
398
  "description": "Verify that if there is an error in access control, the system fails securely",
@@ -85,7 +85,8 @@ class CliActionHandler {
85
85
  // Output results
86
86
  await this.outputService.outputResults(results, this.options, {
87
87
  duration,
88
- rulesRun: rulesToRun.length
88
+ rulesRun: rulesToRun.length,
89
+ rulesChecked: rulesToRun.length
89
90
  });
90
91
 
91
92
  // Exit with appropriate code
@@ -295,6 +296,34 @@ class CliActionHandler {
295
296
  }
296
297
  }
297
298
 
299
+ // Validate upload-report option - requires output-summary to be set
300
+ if (this.options.uploadReport !== undefined) {
301
+ if (!this.options.outputSummary) {
302
+ throw new Error(
303
+ chalk.red(`❌ --upload-report requires --output-summary to be specified\n`) +
304
+ chalk.gray('Example: sunlint --all --output-summary=report.json --upload-report --input=src')
305
+ );
306
+ }
307
+
308
+ // Set default URL if no URL provided (when uploadReport is true)
309
+ if (typeof this.options.uploadReport === 'boolean' || !this.options.uploadReport) {
310
+ this.options.uploadReport = 'https://coding-standards-report.sun-asterisk.vn/api/reports';
311
+ if (this.options.verbose) {
312
+ console.log(chalk.gray(`ℹ️ Using default upload URL: ${this.options.uploadReport}`));
313
+ }
314
+ }
315
+
316
+ // Basic URL validation
317
+ try {
318
+ new URL(this.options.uploadReport);
319
+ } catch (error) {
320
+ throw new Error(
321
+ chalk.red(`❌ Invalid upload URL format: ${this.options.uploadReport}\n`) +
322
+ chalk.gray('Please provide a valid HTTP/HTTPS URL')
323
+ );
324
+ }
325
+ }
326
+
298
327
  // Priority 1: CLI --input parameter (highest priority)
299
328
  if (this.options.input) {
300
329
  // Validate CLI input path exists
@@ -34,6 +34,8 @@ function createCliProgram() {
34
34
  .option('-i, --input <path>', 'Input file or directory to analyze (REQUIRED)')
35
35
  .option('-f, --format <format>', 'Output format (eslint,json,summary,table)', 'eslint')
36
36
  .option('-o, --output <file>', 'Output file path')
37
+ .option('--output-summary <file>', 'Output summary report file path (JSON format for CI/CD)')
38
+ .option('--upload-report [url]', 'Upload summary report to API endpoint after analysis (default: Sun* Coding Standards API)')
37
39
  .option('--config <file>', 'Configuration file path (default: auto-discover)');
38
40
 
39
41
  // File targeting options
@@ -71,7 +73,7 @@ function createCliProgram() {
71
73
  .option('--debug', 'Enable debug mode')
72
74
  .option('--ai', 'Enable AI-powered analysis')
73
75
  .option('--no-ai', 'Force disable AI analysis')
74
- .option('--max-semantic-files <number>', 'Symbol table file limit for TypeScript analysis (default: auto)', '0')
76
+ .option('--max-semantic-files <number>', 'Symbol table file limit for TypeScript analysis (default: 1000, -1 for unlimited)')
75
77
  .option('--list-engines', 'List available analysis engines');
76
78
 
77
79
  // ESLint Integration options
@@ -129,6 +131,8 @@ CI/CD Integration:
129
131
  $ sunlint --all --changed-files --diff-base=origin/main --fail-on-new-violations
130
132
  $ sunlint --all --staged-files --format=summary
131
133
  $ sunlint --all --pr-mode --diff-base=origin/main
134
+ $ sunlint --all --output-summary=report.json --upload-report
135
+ $ sunlint --all --output-summary=report.json --upload-report=https://custom-api.com/reports
132
136
 
133
137
  ESLint Integration:
134
138
  $ sunlint --typescript --eslint-integration --input=src
@@ -1,14 +1,20 @@
1
1
  /**
2
2
  * Output Service
3
- * Following Rule C005: Single responsibility - handle output operations
4
3
  */
5
4
 
6
5
  const fs = require('fs');
7
6
  const path = require('path');
8
7
  const chalk = require('chalk');
8
+ const ScoringService = require('./scoring-service');
9
+ const SummaryReportService = require('./summary-report-service');
10
+ const UploadService = require('./upload-service');
9
11
 
10
12
  class OutputService {
11
- constructor() {}
13
+ constructor() {
14
+ this.scoringService = new ScoringService();
15
+ this.summaryReportService = new SummaryReportService();
16
+ this.uploadService = new UploadService();
17
+ }
12
18
 
13
19
  async outputResults(results, options, metadata = {}) {
14
20
  // Generate report based on format
@@ -27,12 +33,91 @@ class OutputService {
27
33
  console.log(chalk.green(`📄 Report saved to: ${options.output}`));
28
34
  }
29
35
 
36
+ // Summary report output (new feature for CI/CD)
37
+ if (options.outputSummary) {
38
+ const summaryReport = this.generateAndSaveSummaryReport(
39
+ report.violations,
40
+ results,
41
+ options,
42
+ metadata
43
+ );
44
+ console.log(chalk.green(`📊 Summary report saved to: ${options.outputSummary}`));
45
+
46
+ // Display summary in console
47
+ if (!options.quiet) {
48
+ console.log(this.summaryReportService.formatTextSummary(summaryReport));
49
+ }
50
+
51
+ // Upload report if upload URL is provided
52
+ if (options.uploadReport) {
53
+ await this.handleUploadReport(options.outputSummary, options.uploadReport, options);
54
+ }
55
+ }
56
+
30
57
  // Summary (skip for JSON format)
31
58
  if (!options.quiet && options.format !== 'json') {
32
59
  console.log(report.summary);
33
60
  }
34
61
  }
35
62
 
63
+ generateAndSaveSummaryReport(violations, results, options, metadata) {
64
+ const totalFiles = results.filesAnalyzed || results.summary?.totalFiles || results.totalFiles || results.fileCount || 0;
65
+
66
+ // Calculate LOC
67
+ let loc = 0;
68
+ if (options.input) {
69
+ const inputPaths = Array.isArray(options.input) ? options.input : [options.input];
70
+ for (const inputPath of inputPaths) {
71
+ if (fs.existsSync(inputPath)) {
72
+ const stat = fs.statSync(inputPath);
73
+ if (stat.isDirectory()) {
74
+ loc += this.scoringService.calculateDirectoryLOC(inputPath);
75
+ } else {
76
+ loc += this.scoringService.calculateLOC([inputPath]);
77
+ }
78
+ }
79
+ }
80
+ }
81
+
82
+ // Count violations
83
+ const errorCount = violations.filter(v => v.severity === 'error').length;
84
+ const warningCount = violations.filter(v => v.severity === 'warning').length;
85
+
86
+ // Get number of rules checked - use metadata first, then parse from options
87
+ let rulesChecked = metadata.rulesChecked;
88
+ if (!rulesChecked && options.rule) {
89
+ rulesChecked = typeof options.rule === 'string'
90
+ ? options.rule.split(',').filter(r => r.trim()).length
91
+ : Array.isArray(options.rule)
92
+ ? options.rule.length
93
+ : 1;
94
+ }
95
+ rulesChecked = rulesChecked || 1;
96
+
97
+ // Calculate score
98
+ const scoringSummary = this.scoringService.generateScoringSummary({
99
+ errorCount,
100
+ warningCount,
101
+ rulesChecked,
102
+ loc
103
+ });
104
+
105
+ // Generate and save summary report
106
+ const summaryReport = this.summaryReportService.saveSummaryReport(
107
+ violations,
108
+ scoringSummary,
109
+ options.outputSummary,
110
+ {
111
+ cwd: process.cwd(),
112
+ filesAnalyzed: totalFiles,
113
+ duration: metadata.duration,
114
+ version: metadata.version || '1.3.9'
115
+ }
116
+ );
117
+
118
+ return summaryReport;
119
+ }
120
+
36
121
  generateReport(results, metadata, options = {}) {
37
122
  const allViolations = [];
38
123
  let totalFiles = results.filesAnalyzed || results.summary?.totalFiles || results.totalFiles || results.fileCount || 0;
@@ -98,7 +183,8 @@ class OutputService {
98
183
  return {
99
184
  formatted,
100
185
  summary,
101
- raw
186
+ raw,
187
+ violations: allViolations // Add violations for summary report
102
188
  };
103
189
  }
104
190
 
@@ -244,6 +330,77 @@ class OutputService {
244
330
 
245
331
  return jsonResults;
246
332
  }
333
+
334
+ /**
335
+ * Handle uploading report to API endpoint
336
+ */
337
+ async handleUploadReport(filePath, apiUrl, options = {}) {
338
+ try {
339
+ if (!filePath) {
340
+ throw new Error('Summary report file path is required for upload');
341
+ }
342
+
343
+ if (!apiUrl) {
344
+ throw new Error('API URL is required for upload');
345
+ }
346
+
347
+ // Check if curl is available
348
+ if (!this.uploadService.checkCurlAvailability()) {
349
+ console.warn(chalk.yellow('⚠️ curl is not available. Skipping report upload.'));
350
+ return;
351
+ }
352
+
353
+ // Validate API endpoint if not in quiet mode
354
+ if (!options.quiet) {
355
+ console.log(chalk.blue('🔍 Checking API endpoint accessibility...'));
356
+ const endpointCheck = await this.uploadService.validateApiEndpoint(apiUrl, {
357
+ timeout: options.uploadTimeout || 10
358
+ });
359
+
360
+ if (!endpointCheck.accessible) {
361
+ console.warn(chalk.yellow(`⚠️ API endpoint may not be accessible: ${endpointCheck.error}`));
362
+ console.warn(chalk.yellow('Proceeding with upload attempt...'));
363
+ }
364
+ }
365
+
366
+ // Upload the report
367
+ const uploadResult = await this.uploadService.uploadReportToApi(filePath, apiUrl, {
368
+ timeout: options.uploadTimeout || 30,
369
+ verbose: options.verbose,
370
+ debug: options.debug
371
+ });
372
+
373
+ if (uploadResult.success) {
374
+ if (!options.quiet) {
375
+ console.log(chalk.green(`✅ Report successfully uploaded to: ${apiUrl}`));
376
+ if (uploadResult.statusCode) {
377
+ console.log(chalk.green(`📡 HTTP Status: ${uploadResult.statusCode}`));
378
+ }
379
+ }
380
+ } else {
381
+ console.warn(chalk.yellow(`⚠️ Failed to upload report: ${uploadResult.error}`));
382
+
383
+ if (options.verbose && uploadResult.errorContext) {
384
+ console.warn('Upload error details:', uploadResult.errorContext);
385
+ }
386
+ }
387
+
388
+ return uploadResult;
389
+
390
+ } catch (error) {
391
+ console.error(chalk.red('❌ Upload process failed:'), error.message);
392
+
393
+ if (options.debug) {
394
+ console.error('Upload error stack:', error.stack);
395
+ }
396
+
397
+ // Don't throw error to prevent interrupting the main analysis flow
398
+ return {
399
+ success: false,
400
+ error: error.message
401
+ };
402
+ }
403
+ }
247
404
  }
248
405
 
249
406
  module.exports = OutputService;
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Scoring Service
3
+ * Calculate quality score based on violations, rules, and LOC
4
+ * Following Rule C005: Single responsibility - handle scoring operations
5
+ */
6
+
7
+ const { execSync } = require('child_process');
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ class ScoringService {
12
+ constructor() {
13
+ // Scoring weights
14
+ 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
19
+ };
20
+ }
21
+
22
+ /**
23
+ * Calculate quality score
24
+ * Score formula: 100 - (errorCount * 5 + warningCount * 1) * (1000 / LOC) + (rulesChecked * 0.5)
25
+ *
26
+ * @param {Object} params
27
+ * @param {number} params.errorCount - Number of errors found
28
+ * @param {number} params.warningCount - Number of warnings found
29
+ * @param {number} params.rulesChecked - Number of rules checked
30
+ * @param {number} params.loc - Total lines of code
31
+ * @returns {number} Score between 0-100
32
+ */
33
+ calculateScore({ errorCount, warningCount, rulesChecked, loc }) {
34
+ // Base score starts at 100
35
+ let score = 100;
36
+
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;
47
+
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;
51
+
52
+ // Ensure score is between 0-100
53
+ score = Math.max(0, Math.min(100, score));
54
+
55
+ // Round to 1 decimal place
56
+ return Math.round(score * 10) / 10;
57
+ }
58
+
59
+ /**
60
+ * Calculate Lines of Code (LOC) for given files
61
+ * @param {string[]} files - Array of file paths
62
+ * @returns {number} Total lines of code
63
+ */
64
+ calculateLOC(files) {
65
+ let totalLines = 0;
66
+
67
+ for (const file of files) {
68
+ try {
69
+ if (fs.existsSync(file)) {
70
+ const content = fs.readFileSync(file, 'utf8');
71
+ const lines = content.split('\n').length;
72
+ totalLines += lines;
73
+ }
74
+ } catch (error) {
75
+ // Ignore files that can't be read
76
+ console.warn(`Warning: Could not read file ${file}`);
77
+ }
78
+ }
79
+
80
+ return totalLines;
81
+ }
82
+
83
+ /**
84
+ * Calculate LOC for a directory
85
+ * @param {string} directory - Directory path
86
+ * @param {string[]} extensions - File extensions to include (e.g., ['.ts', '.tsx', '.js', '.jsx'])
87
+ * @returns {number} Total lines of code
88
+ */
89
+ calculateDirectoryLOC(directory, extensions = ['.ts', '.tsx', '.js', '.jsx']) {
90
+ let totalLines = 0;
91
+
92
+ const processDirectory = (dir) => {
93
+ try {
94
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
95
+
96
+ for (const entry of entries) {
97
+ const fullPath = path.join(dir, entry.name);
98
+
99
+ if (entry.isDirectory()) {
100
+ // Skip common directories
101
+ if (!['node_modules', '.git', 'dist', 'build', 'coverage'].includes(entry.name)) {
102
+ processDirectory(fullPath);
103
+ }
104
+ } else if (entry.isFile()) {
105
+ const ext = path.extname(entry.name);
106
+ if (extensions.includes(ext)) {
107
+ try {
108
+ const content = fs.readFileSync(fullPath, 'utf8');
109
+ totalLines += content.split('\n').length;
110
+ } catch (error) {
111
+ // Ignore files that can't be read
112
+ }
113
+ }
114
+ }
115
+ }
116
+ } catch (error) {
117
+ // Ignore directories that can't be read
118
+ }
119
+ };
120
+
121
+ if (fs.existsSync(directory)) {
122
+ processDirectory(directory);
123
+ }
124
+
125
+ return totalLines;
126
+ }
127
+
128
+ /**
129
+ * Get score grade based on score value
130
+ * @param {number} score - Score value (0-100)
131
+ * @returns {string} Grade (A+, A, B+, B, C+, C, D, F)
132
+ */
133
+ getGrade(score) {
134
+ if (score >= 95) return 'A+';
135
+ if (score >= 90) return 'A';
136
+ if (score >= 85) return 'B+';
137
+ if (score >= 80) return 'B';
138
+ if (score >= 75) return 'C+';
139
+ if (score >= 70) return 'C';
140
+ if (score >= 60) return 'D';
141
+ return 'F';
142
+ }
143
+
144
+ /**
145
+ * Generate scoring summary
146
+ * @param {Object} params
147
+ * @returns {Object} Scoring summary with score, grade, and metrics
148
+ */
149
+ generateScoringSummary(params) {
150
+ const score = this.calculateScore(params);
151
+ const grade = this.getGrade(score);
152
+
153
+ return {
154
+ score,
155
+ grade,
156
+ metrics: {
157
+ errors: params.errorCount,
158
+ warnings: params.warningCount,
159
+ rulesChecked: params.rulesChecked,
160
+ linesOfCode: params.loc,
161
+ violationsPerKLOC: params.loc > 0
162
+ ? Math.round(((params.errorCount + params.warningCount) / params.loc * 1000) * 10) / 10
163
+ : 0
164
+ }
165
+ };
166
+ }
167
+ }
168
+
169
+ module.exports = ScoringService;
@@ -102,7 +102,7 @@ class SemanticEngine {
102
102
  );
103
103
 
104
104
  if (targetFiles) {
105
- console.log(`🎯 Targeted files received: ${targetFiles.length} total, ${semanticFiles.length} TS/JS files`);
105
+ console.log(`🎯 Targeted files received: ${targetFiles.length} total, ${semanticFiles.length} TypeScript/JavaScript files (TS/TSX/JS/JSX)`);
106
106
  if (semanticFiles.length < 10) {
107
107
  console.log(` Files: ${semanticFiles.map(f => path.basename(f)).join(', ')}`);
108
108
  }
@@ -119,7 +119,9 @@ class SemanticEngine {
119
119
  } else if (userMaxFiles === 0) {
120
120
  // Disable semantic analysis
121
121
  maxFiles = 0;
122
- console.log(`🔧 Semantic Engine config: DISABLED semantic analysis (heuristic only)`);
122
+ console.log(`🔧 Semantic Engine config: DISABLED semantic analysis (heuristic only mode)`);
123
+ console.log(` 💡 Semantic analysis explicitly disabled with --max-semantic-files=0`);
124
+ console.log(` 💡 To enable: omit the option (default: 1000) or use --max-semantic-files=1000 (or higher)`);
123
125
  } else if (userMaxFiles > 0) {
124
126
  // User-specified limit
125
127
  maxFiles = Math.min(userMaxFiles, semanticFiles.length);