@sun-asterisk/sunlint 1.3.37 → 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 CHANGED
@@ -25,15 +25,16 @@ program
25
25
  .description('Initialize a project with SunLint code quality skill and AGENTS.md')
26
26
  .option('-f, --force', 'Overwrite existing files')
27
27
  .option('-t, --tool <tool>', `Target AI tool (default: ${DEFAULT_TOOL})`, DEFAULT_TOOL)
28
+ .option('-l, --language <language>', `Target language (default: typescript)`, 'typescript')
28
29
  .addHelpText('after', `
29
30
  AI Tool Options:
30
31
  ${getAvailableToolsHelp()}
31
32
 
32
33
  Examples:
33
- $ sunlint init # Use default (antigravity)
34
+ $ sunlint init # Use default (antigravity, typescript)
34
35
  $ sunlint init -t cursor # For Cursor AI
35
- $ sunlint init -t claude # For Claude
36
- $ sunlint init -t github-copilot # For GitHub Copilot
36
+ $ sunlint init -l csharp # For C# project
37
+ $ sunlint init -t github-copilot -l csharp # For GitHub Copilot with C# rules
37
38
  $ sunlint init ./my-project -t cursor --force
38
39
  `)
39
40
  .action(async (directory, options) => {
@@ -3640,6 +3640,39 @@
3640
3640
  "headers",
3641
3641
  "privacy"
3642
3642
  ]
3643
+ },
3644
+ "D001": {
3645
+ "id": "D001",
3646
+ "name": "Recommended Lint Rules Should Be Enabled",
3647
+ "description": "Ensure recommended lint rules from flutter_lints or very_good_analysis are enabled in analysis_options.yaml with appropriate severity",
3648
+ "category": "dart",
3649
+ "severity": "warning",
3650
+ "languages": [
3651
+ "dart"
3652
+ ],
3653
+ "analyzer": "dart",
3654
+ "config": "./rules/dart/D001_recommended_lint_rules/config.json",
3655
+ "version": "1.0.0",
3656
+ "status": "stable",
3657
+ "tags": [
3658
+ "dart",
3659
+ "flutter",
3660
+ "lint",
3661
+ "best-practices",
3662
+ "code-quality"
3663
+ ],
3664
+ "strategy": {
3665
+ "preferred": "dart",
3666
+ "fallbacks": [],
3667
+ "accuracy": {
3668
+ "dart": 95
3669
+ }
3670
+ },
3671
+ "engineMappings": {
3672
+ "dart": [
3673
+ "D001_recommended_lint_rules"
3674
+ ]
3675
+ }
3643
3676
  }
3644
3677
  }
3645
3678
  }
@@ -1833,8 +1833,8 @@
1833
1833
  ]
1834
1834
  },
1835
1835
  "D001": {
1836
- "name": "Keep parameter names consistent when overriding methods",
1837
- "description": "Maintain consistency between inherited classes",
1836
+ "name": "Recommended Lint Rules Should Be Enabled",
1837
+ "description": "Ensure code quality through standard lint configurations",
1838
1838
  "category": "Common",
1839
1839
  "severity": "major",
1840
1840
  "languages": [
@@ -1848,7 +1848,9 @@
1848
1848
  "code-quality"
1849
1849
  ],
1850
1850
  "tools": [
1851
- "`dart lint` (`avoid_renaming_method_parameters`)"
1851
+ "`dart lint` (flutter_lints",
1852
+ "very_good_analysis",
1853
+ "lints)"
1852
1854
  ],
1853
1855
  "framework": "All",
1854
1856
  "principles": [
@@ -404,7 +404,7 @@
404
404
  },
405
405
  {
406
406
  "id": "D001",
407
- "name": "Keep parameter names consistent when overriding methods",
407
+ "name": "Recommended Lint Rules Should Be Enabled",
408
408
  "severity": "major",
409
409
  "status": "activated"
410
410
  },
@@ -170,7 +170,8 @@ class CliActionHandler {
170
170
  performanceMode: this.options.performanceMode,
171
171
  ruleBatchSize: parseInt(this.options.ruleBatchSize) || 10,
172
172
  fileBatchSize: parseInt(this.options.fileBatchSize) || 50,
173
- maxFiles: parseInt(this.options.maxFiles) || 1000,
173
+ // maxFiles: 0 or undefined = unlimited, positive number = limit
174
+ maxFiles: parseInt(this.options.maxFiles) || 0,
174
175
  enableFileFiltering: !this.options.noFileFiltering,
175
176
  enableBatching: !this.options.noBatching
176
177
  }
@@ -85,8 +85,8 @@ function createCliProgram() {
85
85
  program
86
86
  .option('--performance <mode>', 'Performance mode: auto, fast, careful', 'auto')
87
87
  .option('--timeout <ms>', 'Analysis timeout in milliseconds', '0')
88
- .option('--max-files <n>', 'Maximum files to analyze', '0')
89
- .option('--max-semantic-files <n>', 'TypeScript symbol table limit (default: 1000)');
88
+ .option('--max-files <n>', 'Maximum files to analyze (0=unlimited)', '0')
89
+ .option('--max-semantic-files <n>', 'TypeScript symbol table limit (0=unlimited, default=1000)');
90
90
 
91
91
  // ──────────────────────────────────────────────────────────────
92
92
  // Output Control
@@ -120,10 +120,43 @@ async function copySkillFolder(targetPath, options = {}, toolConfig = {}) {
120
120
  fs.mkdirSync(parentDir, { recursive: true });
121
121
  }
122
122
 
123
- // Copy skill folder recursively
123
+ const language = options.language || 'typescript';
124
+
125
+ // Copy skill folder usage files (root)
124
126
  const relativePath = toolConfig.skillPath || '.agent/skills';
125
127
  console.log(chalk.blue(` 📁 Copying skill folder to ${relativePath}/sunlint-code-quality/`));
126
- copyDirectoryRecursive(SKILL_SOURCE_DIR, targetPath);
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
+ }
127
160
  }
128
161
 
129
162
  /**
@@ -133,7 +166,8 @@ async function copySkillFolder(targetPath, options = {}, toolConfig = {}) {
133
166
  */
134
167
  function copyDirectoryRecursive(source, target) {
135
168
  if (!fs.existsSync(source)) {
136
- throw new Error(`Source directory not found: ${source}`);
169
+ // throw new Error(`Source directory not found: ${source}`);
170
+ return;
137
171
  }
138
172
 
139
173
  fs.mkdirSync(target, { recursive: true });
@@ -381,16 +381,22 @@ class OutputService {
381
381
  if (index > 0) {
382
382
  output += '\n';
383
383
  }
384
- output += `\n${chalk.underline(path.resolve(file))}\n`;
385
384
 
386
- 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
+ }
387
390
  // Support both location.start.line (new format) and line (legacy format)
388
391
  const line = (violation.location?.start?.line || violation.line || 1).toString();
389
392
  const column = (violation.location?.start?.column || violation.column || 1).toString();
390
- const severityText = violation.severity === 'error' ? 'error' : 'warning';
393
+ const severityText = violation.severity === 'error' ? 'Error' : 'Warning';
391
394
  const severityColor = violation.severity === 'error' ? chalk.red : chalk.yellow;
392
-
393
- output += ` ${chalk.dim(`${line}:${column}`)} ${severityColor(severityText)} ${violation.message} ${chalk.gray(violation.ruleId)}\n`;
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
+ }
394
400
  });
395
401
  });
396
402
 
@@ -112,7 +112,7 @@ class PerformanceOptimizer {
112
112
  break;
113
113
  }
114
114
 
115
- // Check file count limit (skip if unlimited -1)
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`);
@@ -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
 
@@ -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);
@@ -0,0 +1,289 @@
1
+ # Registry Generation - Visual Diagram
2
+
3
+ > Quick visual reference cho registry generation flow
4
+
5
+ ---
6
+
7
+ ## 🔄 High-Level Overview
8
+
9
+ ```
10
+ EDIT HERE AUTO-COPY AUTO-GENERATE USED HERE
11
+ ↓ ↓ ↓ ↓
12
+ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
13
+ │ Markdown │ → │ origin-rules │ → │ Registry │ → │ SunLint │
14
+ │ Files │ │ (copies) │ │ JSON │ │ CLI/VSCode │
15
+ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
16
+ dart-en.md dart-en.md (copy) rules-registry- Display rule names
17
+ common-en.md common-en.md generated.json Show descriptions
18
+ Run analysis
19
+ ```
20
+
21
+ ---
22
+
23
+ ## 📂 Directory Structure
24
+
25
+ ```
26
+ engineer-excellence/
27
+
28
+ ├── coding-quality/
29
+ │ │
30
+ │ ├── rules/ ← 📝 EDIT HERE (Step 1)
31
+ │ │ ├── dart-en.md ← Source of Truth
32
+ │ │ ├── dart.md ← Vietnamese version
33
+ │ │ ├── common-en.md
34
+ │ │ ├── security-en.md
35
+ │ │ ├── examples/
36
+ │ │ │ ├── en/D001.md ← Create examples
37
+ │ │ │ └── vi/D001.md
38
+ │ │ └── ...
39
+ │ │
40
+ │ └── extensions/sunlint/
41
+ │ │
42
+ │ ├── origin-rules/ ← 📋 AUTO-COPIED (Step 2)
43
+ │ │ ├── dart-en.md ← Copy from ../../../rules/
44
+ │ │ ├── common-en.md
45
+ │ │ └── ...
46
+ │ │
47
+ │ ├── config/
48
+ │ │ └── rules/
49
+ │ │ └── rules-registry-generated.json ← 🤖 AUTO-GENERATED (Step 3)
50
+ │ │
51
+ │ └── scripts/
52
+ │ ├── copy-rules.js ← Run this first
53
+ │ └── generate-rules-registry.js ← Then run this
54
+ ```
55
+
56
+ ---
57
+
58
+ ## 🔄 Step-by-Step Flow
59
+
60
+ ### Step 1: Edit Markdown (SOURCE OF TRUTH)
61
+
62
+ ```
63
+ 📝 Edit: coding-quality/rules/dart-en.md
64
+
65
+ ┌─────────────────────────────────────────────────────────────┐
66
+ │ ### 📘 Rule D001 – Recommended Lint Rules Should Be Enabled │
67
+ │ │
68
+ │ - **Objective**: Ensure code quality... │
69
+ │ - **Details**: The `analysis_options.yaml`... │
70
+ │ - **Applies to**: Flutter/Dart │
71
+ │ - **Tools**: `dart lint` (flutter_lints...) │
72
+ │ - **Principles**: CODE_QUALITY │
73
+ │ - **Version**: 1.0 │
74
+ │ - **Status**: activated │
75
+ │ - **Severity**: major │
76
+ └─────────────────────────────────────────────────────────────┘
77
+
78
+ This is what users
79
+ will see in CLI
80
+ ```
81
+
82
+ ### Step 2: Copy Rules Script
83
+
84
+ ```bash
85
+ $ cd coding-quality/extensions/sunlint
86
+ $ node scripts/copy-rules.js
87
+ ```
88
+
89
+ ```
90
+ 📋 Copy Process:
91
+
92
+ Source Destination
93
+ ↓ ↓
94
+ ../../../rules/ origin-rules/
95
+ ├── dart-en.md → ├── dart-en.md
96
+ ├── common-en.md → ├── common-en.md
97
+ ├── security-en.md → ├── security-en.md
98
+ └── ... → └── ...
99
+
100
+ ✅ Successfully copied 8 rule files
101
+ ```
102
+
103
+ ### Step 3: Generate Registry Script
104
+
105
+ ```bash
106
+ $ node scripts/generate-rules-registry.js
107
+ ```
108
+
109
+ ```
110
+ 🤖 Generation Process:
111
+
112
+ origin-rules/ Parser Logic config/rules/
113
+ ↓ ↓ ↓
114
+ dart-en.md → Extract: rules-registry-generated.json
115
+ common-en.md → - Rule ID (D001) {
116
+ security-en.md → - Name "rules": {
117
+ ... → - Description "D001": {
118
+ - Tools "name": "Recommended...",
119
+ - Severity "description": "...",
120
+ - etc. "severity": "major",
121
+ ...
122
+ }
123
+ }
124
+ }
125
+
126
+ ✅ Generated registry with 257 rules
127
+ ```
128
+
129
+ ### Step 4: Used by SunLint
130
+
131
+ ```
132
+ config/rules/ UnifiedRuleRegistry CLI Output
133
+ ↓ ↓ ↓
134
+ rules-registry- Loads registry $ sunlint --rule=D001
135
+ generated.json → Finds D001 entry →
136
+ { Reads: D001: Recommended Lint
137
+ "D001": { - name Rules Should Be Enabled
138
+ "name": "...", - description ─────────────────────
139
+ "description": "..." - severity analysis_options.yaml
140
+ } 1:1 warning ...
141
+ }
142
+ ```
143
+
144
+ ---
145
+
146
+ ## 🔁 Complete Cycle with Example
147
+
148
+ ### Scenario: Rename Rule D001
149
+
150
+ ```
151
+ ┌─ STEP 1: Edit Markdown ────────────────────────────────────┐
152
+ │ │
153
+ │ $ vim ../../../rules/dart-en.md │
154
+ │ │
155
+ │ BEFORE: │
156
+ │ ### 📘 Rule D001 – Keep parameter names consistent │
157
+ │ │
158
+ │ AFTER: │
159
+ │ ### 📘 Rule D001 – Recommended Lint Rules Should Be Enabled │
160
+ │ │
161
+ └──────────────────────────────────────────────────────────────┘
162
+
163
+ ┌─ STEP 2: Copy Rules ───────────────────────────────────────┐
164
+ │ │
165
+ │ $ node scripts/copy-rules.js │
166
+ │ │
167
+ │ Output: │
168
+ │ ✅ Copied: dart-en.md │
169
+ │ ✅ Successfully copied 8 rule files │
170
+ │ │
171
+ └──────────────────────────────────────────────────────────────┘
172
+
173
+ ┌─ STEP 3: Generate Registry ────────────────────────────────┐
174
+ │ │
175
+ │ $ node scripts/generate-rules-registry.js │
176
+ │ │
177
+ │ Output: │
178
+ │ Parsed rule D001: 0 good examples, 0 bad examples │
179
+ │ ✅ Generated registry with 257 rules │
180
+ │ │
181
+ └──────────────────────────────────────────────────────────────┘
182
+
183
+ ┌─ STEP 4: Verify ───────────────────────────────────────────┐
184
+ │ │
185
+ │ $ grep -A 3 '"D001"' config/rules/rules-registry- │
186
+ │ generated.json │
187
+ │ │
188
+ │ Output: │
189
+ │ "D001": { │
190
+ │ "name": "Recommended Lint Rules Should Be Enabled", │
191
+ │ "description": "Ensure code quality...", │
192
+ │ ... │
193
+ │ } │
194
+ │ │
195
+ └──────────────────────────────────────────────────────────────┘
196
+
197
+ ┌─ STEP 5: Test ─────────────────────────────────────────────┐
198
+ │ │
199
+ │ $ node cli.js --rule=D001 --input=./test --languages=dart │
200
+ │ │
201
+ │ Output shows NEW NAME: │
202
+ │ D001: Recommended Lint Rules Should Be Enabled │
203
+ │ ───────────────────────────────────────────────── │
204
+ │ analysis_options.yaml │
205
+ │ 1:1 warning ... │
206
+ │ │
207
+ └──────────────────────────────────────────────────────────────┘
208
+ ```
209
+
210
+ ---
211
+
212
+ ## ❌ Wrong Way vs ✅ Right Way
213
+
214
+ ### ❌ WRONG: Edit JSON Directly
215
+
216
+ ```
217
+ ┌─ WRONG APPROACH ──────────────────────────────────────────┐
218
+ │ │
219
+ │ $ vim config/rules/rules-registry-generated.json │
220
+ │ │
221
+ │ Change: │
222
+ │ "name": "Old Name" │
223
+ │ To: │
224
+ │ "name": "New Name" │
225
+ │ │
226
+ │ ❌ Problem: Will be overwritten on next generation! │
227
+ │ │
228
+ └─────────────────────────────────────────────────────────────┘
229
+ ```
230
+
231
+ ### ✅ RIGHT: Edit Markdown → Regenerate
232
+
233
+ ```
234
+ ┌─ CORRECT APPROACH ────────────────────────────────────────┐
235
+ │ │
236
+ │ STEP 1: Edit source │
237
+ │ $ vim ../../../rules/dart-en.md │
238
+ │ Change: ### 📘 Rule D001 – Old Name │
239
+ │ To: ### 📘 Rule D001 – New Name │
240
+ │ │
241
+ │ STEP 2 & 3: Regenerate │
242
+ │ $ node scripts/copy-rules.js │
243
+ │ $ node scripts/generate-rules-registry.js │
244
+ │ │
245
+ │ ✅ Result: Changes persist, properly tracked in git │
246
+ │ │
247
+ └─────────────────────────────────────────────────────────────┘
248
+ ```
249
+
250
+ ---
251
+
252
+ ## 🎯 Quick Reference Card
253
+
254
+ ```
255
+ ╔═══════════════════════════════════════════════════════════╗
256
+ ║ REGISTRY GENERATION CHEATSHEET ║
257
+ ╠═══════════════════════════════════════════════════════════╣
258
+ ║ ║
259
+ ║ 📝 To change rule name: ║
260
+ ║ 1. Edit: ../../../rules/dart-en.md ║
261
+ ║ 2. Run: node scripts/copy-rules.js ║
262
+ ║ 3. Run: node scripts/generate-rules-registry.js ║
263
+ ║ ║
264
+ ║ 🔍 To verify changes: ║
265
+ ║ grep '"D001"' config/rules/rules-registry- ║
266
+ ║ generated.json ║
267
+ ║ ║
268
+ ║ ✅ Files you SHOULD edit: ║
269
+ ║ - coding-quality/rules/dart-en.md ║
270
+ ║ - coding-quality/rules/dart.md ║
271
+ ║ - coding-quality/rules/examples/en/D001.md ║
272
+ ║ ║
273
+ ║ ❌ Files you should NEVER edit: ║
274
+ ║ - origin-rules/*.md (auto-copied) ║
275
+ ║ - config/rules/rules-registry-generated.json ║
276
+ ║ ║
277
+ ║ 📖 Read more: ║
278
+ ║ docs/REGISTRY_GENERATION_FLOW.md ║
279
+ ║ ║
280
+ ╚═══════════════════════════════════════════════════════════╝
281
+ ```
282
+
283
+ ---
284
+
285
+ ## 🔗 Related Documents
286
+
287
+ - [REGISTRY_GENERATION_FLOW.md](./REGISTRY_GENERATION_FLOW.md) - Detailed explanation
288
+ - [CREATE_NEW_DART_RULE.md](./skills/CREATE_NEW_DART_RULE.md) - How to create rules
289
+ - [PROJECT_STRUCTURE.md](./PROJECT_STRUCTURE.md) - Overall structure