@sun-asterisk/sunlint 1.3.11 → 1.3.12

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
@@ -18,12 +18,20 @@
18
18
  - **Bonus System**: Up to 10 points bonus for comprehensive rule coverage
19
19
 
20
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.)
21
+ - **NEW**: `--output-summary <file>` option for API-compatible JSON summary reports
22
+ - **Format**: Direct dashboard API compatibility - **no transformation needed!**
23
+ - **Git Integration**: Auto-detects complete repository and commit information
24
+ - Repository: URL, name, project path (mono-repo support)
25
+ - Commit: hash, message, author name, author email
26
+ - PR tracking: Automatic PR number extraction from commit messages/branch names
27
+ - **Environment Variables**: Full GitHub Actions support
28
+ - `GITHUB_REPOSITORY`, `GITHUB_REF_NAME`, `GITHUB_SHA`
29
+ - `GITHUB_EVENT_PATH` for commit details
30
+ - Automatic fallback to git commands if env vars unavailable
31
+ - **Mono-Repo Support**: Automatic `project_path` detection for multi-project repositories
25
32
  - **Violation Summary**: Aggregated violations by rule with count and severity
26
33
  - **Metrics**: LOC, files analyzed, violations per KLOC, errors/warnings breakdown
34
+ - **API-Ready**: Flat structure format ready for direct POST to dashboard APIs
27
35
 
28
36
  #### **New Services** 🛠️
29
37
  - **ScoringService**: Calculate quality scores with customizable weights
@@ -111,7 +111,7 @@ class OutputService {
111
111
  cwd: process.cwd(),
112
112
  filesAnalyzed: totalFiles,
113
113
  duration: metadata.duration,
114
- version: metadata.version || '1.3.9'
114
+ version: metadata.version || '1.3.12'
115
115
  }
116
116
  );
117
117
 
@@ -14,13 +14,19 @@ class SummaryReportService {
14
14
  /**
15
15
  * Get Git repository information
16
16
  * @param {string} cwd - Working directory
17
- * @returns {Object} Git info including repository URL, branch, and commit hash
17
+ * @returns {Object} Git info including repository URL, branch, commit hash, and commit details
18
18
  */
19
19
  getGitInfo(cwd = process.cwd()) {
20
20
  const gitInfo = {
21
21
  repository_url: null,
22
+ repository_name: null,
23
+ project_path: null,
22
24
  branch: null,
23
- commit_hash: null
25
+ commit_hash: null,
26
+ commit_message: null,
27
+ author_email: null,
28
+ author_name: null,
29
+ pr_number: null
24
30
  };
25
31
 
26
32
  try {
@@ -39,6 +45,12 @@ class SummaryReportService {
39
45
  } else {
40
46
  gitInfo.repository_url = remoteUrl.replace('.git', '');
41
47
  }
48
+
49
+ // Extract repository name from URL
50
+ const urlMatch = gitInfo.repository_url.match(/\/([^\/]+)$/);
51
+ if (urlMatch) {
52
+ gitInfo.repository_name = urlMatch[1];
53
+ }
42
54
  } catch (error) {
43
55
  // No remote configured
44
56
  }
@@ -56,6 +68,55 @@ class SummaryReportService {
56
68
  } catch (error) {
57
69
  // Can't get commit hash
58
70
  }
71
+
72
+ // Get commit message
73
+ try {
74
+ gitInfo.commit_message = execSync('git log -1 --pretty=%B', { cwd, encoding: 'utf8' }).trim();
75
+ } catch (error) {
76
+ // Can't get commit message
77
+ }
78
+
79
+ // Get author email
80
+ try {
81
+ gitInfo.author_email = execSync('git log -1 --pretty=%ae', { cwd, encoding: 'utf8' }).trim();
82
+ } catch (error) {
83
+ // Can't get author email
84
+ }
85
+
86
+ // Get author name
87
+ try {
88
+ gitInfo.author_name = execSync('git log -1 --pretty=%an', { cwd, encoding: 'utf8' }).trim();
89
+ } catch (error) {
90
+ // Can't get author name
91
+ }
92
+
93
+ // Get project path (relative to repo root for mono-repo)
94
+ try {
95
+ const repoRoot = execSync('git rev-parse --show-toplevel', { cwd, encoding: 'utf8' }).trim();
96
+ if (cwd !== repoRoot) {
97
+ gitInfo.project_path = path.relative(repoRoot, cwd);
98
+ }
99
+ } catch (error) {
100
+ // Can't determine project path
101
+ }
102
+
103
+ // Try to extract PR number from commit message or branch name
104
+ try {
105
+ // Check commit message for PR patterns like "#123" or "pull request #123"
106
+ const commitMsg = gitInfo.commit_message || '';
107
+ const prMatch = commitMsg.match(/#(\d+)/);
108
+ if (prMatch) {
109
+ gitInfo.pr_number = parseInt(prMatch[1]);
110
+ } else {
111
+ // Check branch name for PR patterns
112
+ const branchMatch = gitInfo.branch.match(/(?:pr|pull)[/-]?(\d+)/i);
113
+ if (branchMatch) {
114
+ gitInfo.pr_number = parseInt(branchMatch[1]);
115
+ }
116
+ }
117
+ } catch (error) {
118
+ // Can't extract PR number
119
+ }
59
120
  } catch (error) {
60
121
  // Not a git repository - return nulls
61
122
  }
@@ -79,8 +140,42 @@ class SummaryReportService {
79
140
  ? `https://github.com/${process.env.GITHUB_REPOSITORY}`
80
141
  : gitInfo.repository_url;
81
142
 
143
+ const repository_name = process.env.GITHUB_REPOSITORY
144
+ ? process.env.GITHUB_REPOSITORY.split('/')[1]
145
+ : (gitInfo.repository_name || null);
146
+
82
147
  const branch = process.env.GITHUB_REF_NAME || gitInfo.branch;
83
148
  const commit_hash = process.env.GITHUB_SHA || gitInfo.commit_hash;
149
+
150
+ // Get commit details from GitHub context or git
151
+ const commit_message = process.env.GITHUB_EVENT_HEAD_COMMIT_MESSAGE
152
+ || (process.env.GITHUB_EVENT_PATH
153
+ ? this._getGitHubEventData('head_commit.message')
154
+ : null)
155
+ || gitInfo.commit_message;
156
+
157
+ const author_email = process.env.GITHUB_EVENT_HEAD_COMMIT_AUTHOR_EMAIL
158
+ || (process.env.GITHUB_EVENT_PATH
159
+ ? this._getGitHubEventData('head_commit.author.email')
160
+ : null)
161
+ || gitInfo.author_email;
162
+
163
+ const author_name = process.env.GITHUB_EVENT_HEAD_COMMIT_AUTHOR_NAME
164
+ || (process.env.GITHUB_EVENT_PATH
165
+ ? this._getGitHubEventData('head_commit.author.name')
166
+ : null)
167
+ || gitInfo.author_name;
168
+
169
+ // Get PR number from GitHub event or git
170
+ let pr_number = null;
171
+ if (process.env.GITHUB_EVENT_PATH) {
172
+ pr_number = this._getGitHubEventData('pull_request.number')
173
+ || this._getGitHubEventData('number');
174
+ }
175
+ pr_number = pr_number || gitInfo.pr_number;
176
+
177
+ // Get project path (for mono-repo support)
178
+ const project_path = gitInfo.project_path;
84
179
 
85
180
  // Count violations by rule
86
181
  const violationsByRule = {};
@@ -100,42 +195,77 @@ class SummaryReportService {
100
195
  const violationsSummary = Object.values(violationsByRule)
101
196
  .sort((a, b) => b.count - a.count);
102
197
 
103
- // Build the summary report
198
+ // Build the summary report compatible with coding-standards-report API
104
199
  const summaryReport = {
200
+ // API-compatible fields (flat structure)
201
+ repository_url,
202
+ repository_name,
203
+ project_path,
204
+ branch,
205
+ commit_hash,
206
+ commit_message,
207
+ author_email,
208
+ author_name,
209
+ pr_number,
210
+ score: scoringSummary.score,
211
+ total_violations: violations.length,
212
+ error_count: scoringSummary.metrics.errors,
213
+ warning_count: scoringSummary.metrics.warnings,
214
+ info_count: 0, // Reserved for future use
215
+ lines_of_code: scoringSummary.metrics.linesOfCode,
216
+ files_analyzed: options.filesAnalyzed || 0,
217
+ sunlint_version: options.version || '1.3.12',
218
+ analysis_duration_ms: options.duration || 0,
219
+ violations: violationsSummary,
220
+
221
+ // Additional metadata for backwards compatibility
105
222
  metadata: {
106
223
  generated_at: new Date().toISOString(),
107
224
  tool: 'SunLint',
108
- version: options.version || '1.3.9',
225
+ version: options.version || '1.3.12',
109
226
  analysis_duration_ms: options.duration || 0
110
227
  },
111
- repository: {
112
- repository_url,
113
- branch,
114
- commit_hash
115
- },
116
228
  quality: {
117
229
  score: scoringSummary.score,
118
230
  grade: scoringSummary.grade,
119
231
  metrics: scoringSummary.metrics
120
- },
121
- violations: {
122
- total: violations.length,
123
- by_severity: {
124
- errors: scoringSummary.metrics.errors,
125
- warnings: scoringSummary.metrics.warnings
126
- },
127
- by_rule: violationsSummary
128
- },
129
- analysis: {
130
- files_analyzed: options.filesAnalyzed || 0,
131
- rules_checked: scoringSummary.metrics.rulesChecked,
132
- lines_of_code: scoringSummary.metrics.linesOfCode
133
232
  }
134
233
  };
135
234
 
136
235
  return summaryReport;
137
236
  }
138
237
 
238
+ /**
239
+ * Helper method to extract data from GitHub event JSON
240
+ * @param {string} path - Dot-notation path to the data (e.g., 'head_commit.message')
241
+ * @returns {any} Extracted value or null
242
+ * @private
243
+ */
244
+ _getGitHubEventData(path) {
245
+ try {
246
+ const eventPath = process.env.GITHUB_EVENT_PATH;
247
+ if (!eventPath || !fs.existsSync(eventPath)) {
248
+ return null;
249
+ }
250
+
251
+ const eventData = JSON.parse(fs.readFileSync(eventPath, 'utf8'));
252
+ const keys = path.split('.');
253
+ let value = eventData;
254
+
255
+ for (const key of keys) {
256
+ if (value && typeof value === 'object' && key in value) {
257
+ value = value[key];
258
+ } else {
259
+ return null;
260
+ }
261
+ }
262
+
263
+ return value;
264
+ } catch (error) {
265
+ return null;
266
+ }
267
+ }
268
+
139
269
  /**
140
270
  * Generate summary report and save to file
141
271
  * @param {Array} violations - Array of violations
@@ -0,0 +1,370 @@
1
+ # API-Compatible Summary Report Format
2
+
3
+ ## Overview
4
+
5
+ SunLint v1.4.0+ generates summary reports in a format that's **directly compatible** with dashboard APIs, eliminating the need for manual transformation in CI/CD pipelines.
6
+
7
+ ## Output Format
8
+
9
+ When using `--output-summary=<file>`, SunLint generates a JSON file with the following structure:
10
+
11
+ ```json
12
+ {
13
+ "repository_url": "https://github.com/sun-asterisk/engineer-excellence",
14
+ "repository_name": "engineer-excellence",
15
+ "project_path": "coding-quality/extensions/sunlint",
16
+ "branch": "main",
17
+ "commit_hash": "abc123def456",
18
+ "commit_message": "Merge pull request #123\n\nFix: Update quality scoring",
19
+ "author_email": "user@example.com",
20
+ "author_name": "John Doe",
21
+ "pr_number": 123,
22
+ "score": 92.6,
23
+ "total_violations": 39,
24
+ "error_count": 0,
25
+ "warning_count": 39,
26
+ "info_count": 0,
27
+ "lines_of_code": 4954,
28
+ "files_analyzed": 22,
29
+ "sunlint_version": "1.3.9",
30
+ "analysis_duration_ms": 1464,
31
+ "violations": [
32
+ {
33
+ "rule_code": "C065",
34
+ "count": 39,
35
+ "severity": "warning"
36
+ }
37
+ ],
38
+ "metadata": {
39
+ "generated_at": "2025-10-09T06:59:57.680Z",
40
+ "tool": "SunLint",
41
+ "version": "1.3.9",
42
+ "analysis_duration_ms": 1464
43
+ },
44
+ "quality": {
45
+ "score": 92.6,
46
+ "grade": "A",
47
+ "metrics": {
48
+ "errors": 0,
49
+ "warnings": 39,
50
+ "rulesChecked": 1,
51
+ "linesOfCode": 4954,
52
+ "violationsPerKLOC": 7.9
53
+ }
54
+ }
55
+ }
56
+ ```
57
+
58
+ ## Field Descriptions
59
+
60
+ ### Repository Information
61
+
62
+ | Field | Type | Description | Source |
63
+ |-------|------|-------------|--------|
64
+ | `repository_url` | string | Full HTTPS URL to repository | Git remote or `GITHUB_REPOSITORY` env var |
65
+ | `repository_name` | string | Repository name (without owner) | Extracted from URL or repo name |
66
+ | `project_path` | string\|null | Relative path from repo root (for mono-repo) | Calculated from git root |
67
+ | `branch` | string | Current branch name | Git or `GITHUB_REF_NAME` |
68
+ | `commit_hash` | string | Full commit SHA | Git or `GITHUB_SHA` |
69
+ | `commit_message` | string | Commit message (may include newlines) | Git log or GitHub event |
70
+ | `author_email` | string | Commit author email | Git log or GitHub event |
71
+ | `author_name` | string | Commit author name | Git log or GitHub event |
72
+ | `pr_number` | number\|null | Pull request number (if applicable) | Extracted from commit message or branch name |
73
+
74
+ ### Quality Metrics
75
+
76
+ | Field | Type | Description |
77
+ |-------|------|-------------|
78
+ | `score` | number | Quality score (0-100) |
79
+ | `total_violations` | number | Total number of violations found |
80
+ | `error_count` | number | Number of error-level violations |
81
+ | `warning_count` | number | Number of warning-level violations |
82
+ | `info_count` | number | Number of info-level violations (reserved) |
83
+ | `lines_of_code` | number | Total lines of code analyzed |
84
+ | `files_analyzed` | number | Number of files analyzed |
85
+ | `sunlint_version` | string | Version of SunLint used |
86
+ | `analysis_duration_ms` | number | Time taken for analysis in milliseconds |
87
+
88
+ ### Violations Array
89
+
90
+ Each violation object contains:
91
+
92
+ | Field | Type | Description |
93
+ |-------|------|-------------|
94
+ | `rule_code` | string | Rule identifier (e.g., "C065") |
95
+ | `count` | number | Number of times this rule was violated |
96
+ | `severity` | string | Severity level ("error" or "warning") |
97
+
98
+ ## Auto-Detection Features
99
+
100
+ ### 1. Mono-Repo Support
101
+
102
+ **Automatic `project_path` Detection:**
103
+ - If running from repo root → `project_path: null`
104
+ - If running from subdirectory → `project_path: "relative/path/from/root"`
105
+
106
+ **Example Mono-Repo Structure:**
107
+ ```
108
+ my-monorepo/
109
+ ├── project-a/
110
+ │ └── src/
111
+ ├── project-b/
112
+ │ └── src/
113
+ └── shared/
114
+ ```
115
+
116
+ Running from `my-monorepo/project-a/`:
117
+ ```json
118
+ {
119
+ "project_path": "project-a",
120
+ "repository_name": "my-monorepo"
121
+ }
122
+ ```
123
+
124
+ ### 2. PR Number Extraction
125
+
126
+ SunLint automatically extracts PR numbers from:
127
+
128
+ **Commit Messages:**
129
+ - `"Merge pull request #123"` → `pr_number: 123`
130
+ - `"Fix #456: Update scoring"` → `pr_number: 456`
131
+ - `"Closes #789"` → `pr_number: 789`
132
+
133
+ **Branch Names:**
134
+ - `"pr-123"` → `pr_number: 123`
135
+ - `"pull/456"` → `pr_number: 456`
136
+ - `"PR-789-feature"` → `pr_number: 789`
137
+
138
+ ### 3. Environment Variable Priority
139
+
140
+ CI/CD environment variables take precedence over git commands:
141
+
142
+ ```javascript
143
+ // Priority order:
144
+ 1. GITHUB_REPOSITORY → repository_url
145
+ 2. GITHUB_REF_NAME → branch
146
+ 3. GITHUB_SHA → commit_hash
147
+ 4. GITHUB_EVENT_PATH (JSON) → commit details
148
+ 5. Git commands → fallback for all fields
149
+ ```
150
+
151
+ ## Usage in CI/CD
152
+
153
+ ### Before v1.4.0 (Manual Transformation Required)
154
+
155
+ ```yaml
156
+ - name: Generate Report
157
+ run: sunlint --all --output-summary=report.json
158
+
159
+ - name: Transform for API
160
+ run: |
161
+ jq -n \
162
+ --arg repo_url "${{ github.repository }}" \
163
+ --arg commit "${{ github.sha }}" \
164
+ --argjson report "$(cat report.json)" \
165
+ '{
166
+ repository_url: $repo_url,
167
+ commit_hash: $commit,
168
+ score: $report.quality.score,
169
+ total_violations: $report.violations.total,
170
+ # ... manual mapping for 15+ fields
171
+ }' > api-payload.json
172
+ ```
173
+
174
+ ### v1.4.0+ (Direct Use - No Transformation!)
175
+
176
+ ```yaml
177
+ - name: Generate Report
178
+ run: sunlint --all --output-summary=report.json
179
+
180
+ - name: Upload to Dashboard
181
+ run: |
182
+ curl -X POST https://dashboard.example.com/api/reports \
183
+ -H "Content-Type: application/json" \
184
+ -H "Authorization: Bearer ${{ secrets.API_TOKEN }}" \
185
+ -d @report.json
186
+ ```
187
+
188
+ **That's it!** No `jq` transformation needed. 🎉
189
+
190
+ ## GitHub Actions Integration
191
+
192
+ ### Full Example
193
+
194
+ ```yaml
195
+ name: Code Quality Check
196
+
197
+ on:
198
+ push:
199
+ branches: [ main ]
200
+ pull_request:
201
+ branches: [ main ]
202
+
203
+ jobs:
204
+ quality:
205
+ runs-on: ubuntu-latest
206
+ steps:
207
+ - uses: actions/checkout@v3
208
+ with:
209
+ fetch-depth: 0 # Full history for git info
210
+
211
+ - name: Run SunLint
212
+ run: |
213
+ npx @sun-asterisk/sunlint \
214
+ --input=src \
215
+ --all \
216
+ --output-summary=quality-report.json
217
+ # Environment variables are automatically available
218
+ # No need to set them manually!
219
+
220
+ - name: Upload to Dashboard
221
+ run: |
222
+ curl -X POST ${{ secrets.DASHBOARD_URL }}/api/reports \
223
+ -H "Content-Type: application/json" \
224
+ -H "Authorization: Bearer ${{ secrets.DASHBOARD_TOKEN }}" \
225
+ -d @quality-report.json
226
+ ```
227
+
228
+ ### Available GitHub Actions Environment Variables
229
+
230
+ These are **automatically** available and used by SunLint:
231
+
232
+ ```yaml
233
+ # Automatically set by GitHub Actions:
234
+ GITHUB_REPOSITORY: "owner/repo"
235
+ GITHUB_REF_NAME: "main"
236
+ GITHUB_SHA: "abc123..."
237
+ GITHUB_EVENT_PATH: "/github/workflow/event.json"
238
+
239
+ # From event.json:
240
+ - head_commit.message
241
+ - head_commit.author.email
242
+ - head_commit.author.name
243
+ - pull_request.number (for PR events)
244
+ ```
245
+
246
+ ## Local Development
247
+
248
+ When running locally (not in CI/CD), SunLint uses git commands:
249
+
250
+ ```bash
251
+ # Local run - auto-detects from git
252
+ sunlint --input=src --all --output-summary=local-report.json
253
+
254
+ # Result uses local git information:
255
+ {
256
+ "repository_url": "https://github.com/owner/repo", # from git remote
257
+ "branch": "feature/my-branch", # from git branch
258
+ "commit_hash": "def456...", # from git log
259
+ "commit_message": "WIP: My feature", # from git log
260
+ "author_email": "me@example.com", # from git config
261
+ "author_name": "My Name", # from git config
262
+ "pr_number": null # no PR locally
263
+ }
264
+ ```
265
+
266
+ ## Compatibility
267
+
268
+ ### Dashboard API Requirements
269
+
270
+ The output format is compatible with APIs expecting:
271
+
272
+ ✅ Flat structure (no nested objects for main fields)
273
+ ✅ Repository metadata (URL, name, path, branch, commit)
274
+ ✅ Commit details (message, author email, author name)
275
+ ✅ PR tracking (PR number)
276
+ ✅ Quality metrics (score, violations, LOC)
277
+ ✅ Violations summary (grouped by rule)
278
+
279
+ ### Backwards Compatibility
280
+
281
+ Additional nested objects are included for backwards compatibility:
282
+
283
+ ```json
284
+ {
285
+ // API-compatible flat fields
286
+ "score": 92.6,
287
+ "total_violations": 39,
288
+
289
+ // Legacy nested structure (for backwards compatibility)
290
+ "metadata": { ... },
291
+ "quality": { ... }
292
+ }
293
+ ```
294
+
295
+ ## Troubleshooting
296
+
297
+ ### Missing Git Information
298
+
299
+ **Problem**: Some fields are `null` in the output
300
+
301
+ **Solution**: Ensure you're running from a git repository with commits
302
+
303
+ ```bash
304
+ # Check git status
305
+ git status
306
+
307
+ # Verify remote is configured
308
+ git remote -v
309
+
310
+ # Ensure commits exist
311
+ git log -1
312
+ ```
313
+
314
+ ### Incorrect project_path
315
+
316
+ **Problem**: `project_path` is not null when it should be
317
+
318
+ **Solution**: Run SunLint from the repository root:
319
+
320
+ ```bash
321
+ cd /path/to/repo-root
322
+ sunlint --input=. --all --output-summary=report.json
323
+ ```
324
+
325
+ ### PR Number Not Detected
326
+
327
+ **Problem**: `pr_number` is `null` in PR builds
328
+
329
+ **Solution**:
330
+
331
+ 1. For GitHub Actions PR events, use `pull_request` trigger
332
+ 2. Ensure commit message includes PR reference (e.g., "#123")
333
+ 3. Use PR branch naming convention (e.g., "pr-123" or "pull/123")
334
+
335
+ ```yaml
336
+ on:
337
+ pull_request: # This sets pull_request.number in event.json
338
+ branches: [ main ]
339
+ ```
340
+
341
+ ## Migration Guide
342
+
343
+ ### From Manual Transformation to Direct API Upload
344
+
345
+ **Before:**
346
+ ```yaml
347
+ - run: sunlint --all --output-summary=sunlint-report.json
348
+ - run: |
349
+ # 30+ lines of jq transformation
350
+ jq -n --arg repo ... > api-payload.json
351
+ - run: curl -X POST ... -d @api-payload.json
352
+ ```
353
+
354
+ **After:**
355
+ ```yaml
356
+ - run: sunlint --all --output-summary=sunlint-report.json
357
+ - run: curl -X POST ... -d @sunlint-report.json
358
+ ```
359
+
360
+ **Benefits:**
361
+ - ✅ **Simpler**: 3 lines instead of 30+
362
+ - ✅ **Faster**: No jq processing overhead
363
+ - ✅ **More reliable**: No transformation errors
364
+ - ✅ **Maintainable**: SunLint handles format updates
365
+
366
+ ## See Also
367
+
368
+ - [Quality Scoring Guide](QUALITY_SCORING_GUIDE.md)
369
+ - [CI/CD Integration Guide](CI-CD-GUIDE.md)
370
+ - [Configuration Guide](CONFIGURATION.md)
@@ -152,13 +152,25 @@ jobs:
152
152
 
153
153
  The summary report automatically detects and uses Git information from environment variables (commonly available in CI/CD):
154
154
 
155
- | Variable | Description | Source |
156
- |----------|-------------|--------|
157
- | `GITHUB_REPOSITORY` | Repository name (e.g., `owner/repo`) | GitHub Actions |
158
- | `GITHUB_REF_NAME` | Branch or tag name | GitHub Actions |
159
- | `GITHUB_SHA` | Commit hash | GitHub Actions |
160
-
161
- If these variables are not available, the tool will automatically detect Git information from the local repository.
155
+ | Variable | Description | Source | Fallback |
156
+ |----------|-------------|--------|----------|
157
+ | `GITHUB_REPOSITORY` | Repository name (e.g., `owner/repo`) | GitHub Actions | Git remote URL |
158
+ | `GITHUB_REF_NAME` | Branch or tag name | GitHub Actions | `git rev-parse --abbrev-ref HEAD` |
159
+ | `GITHUB_SHA` | Commit hash | GitHub Actions | `git rev-parse HEAD` |
160
+ | `GITHUB_EVENT_PATH` | Path to event payload JSON | GitHub Actions | - |
161
+ | `GITHUB_EVENT_HEAD_COMMIT_MESSAGE` | Commit message | GitHub Actions | `git log -1 --pretty=%B` |
162
+ | `GITHUB_EVENT_HEAD_COMMIT_AUTHOR_EMAIL` | Author email | GitHub Actions | `git log -1 --pretty=%ae` |
163
+ | `GITHUB_EVENT_HEAD_COMMIT_AUTHOR_NAME` | Author name | GitHub Actions | `git log -1 --pretty=%an` |
164
+
165
+ **Additional Fields Auto-Detected:**
166
+ - **`repository_name`**: Extracted from repository URL
167
+ - **`project_path`**: Relative path from repo root (for mono-repo support)
168
+ - **`commit_message`**: From git log or GitHub event
169
+ - **`author_email`**: From git log or GitHub event
170
+ - **`author_name`**: From git log or GitHub event
171
+ - **`pr_number`**: Extracted from commit message (e.g., "#123") or branch name (e.g., "pr-123")
172
+
173
+ If environment variables are not available, the tool will automatically detect all Git information from the local repository.
162
174
 
163
175
  ## CLI Options
164
176
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sun-asterisk/sunlint",
3
- "version": "1.3.11",
3
+ "version": "1.3.12",
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": {
@@ -66,8 +66,8 @@ class C023SymbolBasedAnalyzer {
66
66
  }
67
67
  }
68
68
 
69
- checkScope(node, violations) {
70
- const seen = new Map();
69
+ checkScope(node, violations, parentSeen = new Map()) {
70
+ const seen = new Map(parentSeen);
71
71
 
72
72
  node.forEachChild((child) => {
73
73
  switch (child.getKind()) {
@@ -83,56 +83,55 @@ class C023SymbolBasedAnalyzer {
83
83
  case SyntaxKind.ArrowFunction:
84
84
  case SyntaxKind.MethodDeclaration:
85
85
  case SyntaxKind.Constructor: { // ✅ also cover constructors
86
- const func = child;
87
- // Params
88
- func.getParameters().forEach((p) =>
89
- this.checkDuplicate(p, seen, violations)
86
+ const funcSeen = new Map();
87
+ child.getParameters().forEach((p) =>
88
+ this.checkDuplicate(p, funcSeen, violations)
90
89
  );
91
- // Body as new scope
92
- const body = func.getBody?.();
93
- if (body) this.checkScope(body, violations);
90
+ const body = child.getBody && child.getBody();
91
+ if (body) this.checkScope(body, violations, funcSeen);
94
92
  break;
95
93
  }
96
94
 
97
95
  case SyntaxKind.Block: {
98
- this.checkScope(child, violations);
96
+ this.checkScope(child, violations, new Map(seen));
99
97
  break;
100
98
  }
101
99
 
102
100
  case SyntaxKind.CatchClause: {
101
+ const catchSeen = new Map(seen);
103
102
  const catchVar = child.getVariableDeclaration();
104
103
  if (catchVar) {
105
- // register catch param in scope
106
- this.checkDuplicate(catchVar, seen, violations);
104
+ this.checkDuplicate(catchVar, catchSeen, violations);
107
105
  }
108
- // Traverse catch block (same scope as catch param)
109
- this.checkScope(child.getBlock(), violations);
106
+ this.checkScope(child.getBlock(), violations, catchSeen);
110
107
  break;
111
108
  }
112
109
 
113
110
  case SyntaxKind.ForStatement:
114
111
  case SyntaxKind.ForOfStatement:
115
112
  case SyntaxKind.ForInStatement: {
116
- const initializer = child.getInitializer?.();
117
- if (initializer) {
118
- initializer.getDeclarations?.().forEach((decl) =>
119
- this.checkDuplicate(decl, seen, violations)
113
+ const loopSeen = new Map(seen);
114
+ const initializer = child.getInitializer && child.getInitializer();
115
+ if (initializer && initializer.getDeclarations) {
116
+ initializer.getDeclarations().forEach((decl) =>
117
+ this.checkDuplicate(decl, loopSeen, violations)
120
118
  );
121
119
  }
122
- const statement = child.getStatement?.();
123
- if (statement) this.checkScope(statement, violations);
120
+ const statement = child.getStatement && child.getStatement();
121
+ if (statement) this.checkScope(statement, violations, loopSeen);
124
122
  break;
125
123
  }
126
124
 
127
125
  default: {
128
- this.checkScope(child, violations);
126
+ this.checkScope(child, violations, seen);
129
127
  }
130
128
  }
131
129
  });
132
130
  }
133
131
 
134
132
  checkDuplicate(node, seen, violations) {
135
- const name = node.getName?.();
133
+ const nameNode = node.getNameNode();
134
+ const name = nameNode.getText();
136
135
  if (!name) return;
137
136
  const filePath = node.getSourceFile().getFilePath();
138
137
 
package/docs/AI.md DELETED
@@ -1,163 +0,0 @@
1
- # 🤖 AI-Powered Analysis
2
-
3
- Sunlint supports AI-powered code analysis alongside traditional pattern-based analysis for more intelligent and context-aware rule checking.
4
-
5
- ## Overview
6
-
7
- - **🎯 Smart Analysis**: Uses AI to understand code context and intent
8
- - **🔄 Fallback Strategy**: Automatically falls back to pattern analysis if AI fails
9
- - **⚡ Performance**: AI analysis runs per-file with caching support
10
- - **🔧 Configurable**: Multiple AI providers and models supported
11
-
12
- ## Configuration
13
-
14
- ### In `.sunlint.json`:
15
-
16
- ```json
17
- {
18
- "ai": {
19
- "enabled": true,
20
- "provider": "openai",
21
- "model": "gpt-4o-mini",
22
- "apiKey": "${OPENAI_API_KEY}",
23
- "fallbackToPattern": true
24
- }
25
- }
26
- ```
27
-
28
- ### Environment Variables:
29
-
30
- ```bash
31
- export OPENAI_API_KEY="your-openai-api-key"
32
- ```
33
-
34
- ## Supported Providers
35
-
36
- ### OpenAI
37
- - **Models**: `gpt-4`, `gpt-4o-mini`, `gpt-3.5-turbo`
38
- - **API Key**: Required via `OPENAI_API_KEY` environment variable
39
- - **Cost**: Pay-per-use based on OpenAI pricing
40
-
41
- ### GitHub Copilot (Planned)
42
- - **Integration**: VS Code extension integration
43
- - **Models**: GitHub Copilot models
44
- - **Authentication**: VS Code Copilot session
45
-
46
- ## AI-Enhanced Rules
47
-
48
- ### C019 - Log Level Usage
49
- ✅ **AI-Enabled**: Understands code context to determine appropriate log levels
50
-
51
- **AI Analysis Features:**
52
- - **Context Understanding**: Analyzes surrounding code to determine error criticality
53
- - **Intent Recognition**: Understands whether errors are expected or exceptional
54
- - **Semantic Analysis**: Goes beyond pattern matching to understand meaning
55
-
56
- **Example:**
57
- ```typescript
58
- // AI understands this is a validation error, suggests warn level
59
- if (!user.email) {
60
- console.error('Missing email'); // AI: Should use console.warn()
61
- }
62
-
63
- // AI understands this is a critical system error, keeps error level
64
- try {
65
- await database.connect();
66
- } catch (error) {
67
- console.error('Database connection failed:', error); // AI: Appropriate error level
68
- }
69
- ```
70
-
71
- ## Usage
72
-
73
- ### CLI Commands
74
-
75
- **Enable AI for specific rule:**
76
- ```bash
77
- sunlint --rule=C019 --input=src --ai
78
- ```
79
-
80
- **Enable AI for all rules:**
81
- ```bash
82
- sunlint --quality --input=src --ai
83
- ```
84
-
85
- **Debug AI analysis:**
86
- ```bash
87
- sunlint --rule=C019 --input=src --ai --verbose
88
- ```
89
-
90
- ### VS Code Integration
91
-
92
- 1. **Debug Configuration**: Use "Debug Sunlint - AI Analysis"
93
- 2. **Task**: Run "Sunlint: AI Analysis Test"
94
- 3. **Set API Key**: Configure `OPENAI_API_KEY` in environment
95
-
96
- ## Output Differences
97
-
98
- ### Pattern Analysis Output:
99
- ```
100
- WARNING: Error log level used for non-critical issue - should use warn/info level
101
- at src/user.ts:15:5 (C019)
102
- ```
103
-
104
- ### AI Analysis Output:
105
- ```
106
- WARNING: Email validation error should use console.warn() - this is user input validation, not a system error
107
- at src/user.ts:15:5 (C019)
108
- Suggestion: Change to console.warn('User email validation failed:', email)
109
- ```
110
-
111
- ## Performance Considerations
112
-
113
- - **Caching**: AI responses are cached per file content hash
114
- - **Concurrency**: AI calls are made concurrently with rate limiting
115
- - **Timeout**: 30-second timeout per AI request
116
- - **Cost**: Monitor API usage in OpenAI dashboard
117
-
118
- ## Troubleshooting
119
-
120
- ### Common Issues
121
-
122
- **API Key Not Found:**
123
- ```bash
124
- ⚠️ AI API key not found, falling back to pattern analysis
125
- ```
126
- Solution: Set `OPENAI_API_KEY` environment variable
127
-
128
- **API Rate Limit:**
129
- ```bash
130
- AI Analysis failed: OpenAI API error: 429 Too Many Requests
131
- ```
132
- Solution: Reduce `maxConcurrentRules` in config or wait
133
-
134
- **Network Issues:**
135
- ```bash
136
- AI Analysis failed: OpenAI API error: Network timeout
137
- ```
138
- Solution: Check internet connection, increase `timeoutMs`
139
-
140
- ### Debug AI Issues
141
-
142
- 1. **Enable verbose mode**: `--verbose`
143
- 2. **Check API key**: `echo $OPENAI_API_KEY`
144
- 3. **Test connection**: Use debug configuration
145
- 4. **Check API quota**: Visit OpenAI dashboard
146
-
147
- ## Future Enhancements
148
-
149
- - **🔄 GitHub Copilot Integration**: Direct integration with VS Code Copilot
150
- - **📊 Custom Models**: Support for fine-tuned models
151
- - **🎯 Rule-Specific Prompts**: Specialized prompts per rule type
152
- - **💾 Smart Caching**: Semantic caching across similar code patterns
153
- - **📈 Analytics**: AI vs Pattern analysis effectiveness metrics
154
-
155
- ## Cost Estimation
156
-
157
- **OpenAI API Costs** (approximate):
158
- - **gpt-4o-mini**: ~$0.001 per 1K tokens
159
- - **gpt-4**: ~$0.03 per 1K tokens
160
- - **Average file**: ~500 tokens
161
- - **1000 files with gpt-4o-mini**: ~$0.50
162
-
163
- **Recommendation**: Start with `gpt-4o-mini` for cost-effectiveness.