@stackguide/mcp-server 2.4.0 → 3.0.0
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 +82 -16
- package/dist/config/healthWeights.d.ts +87 -0
- package/dist/config/healthWeights.d.ts.map +1 -0
- package/dist/config/healthWeights.js +238 -0
- package/dist/config/healthWeights.js.map +1 -0
- package/dist/config/types.d.ts +104 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js.map +1 -1
- package/dist/handlers/generate.d.ts +6 -0
- package/dist/handlers/generate.d.ts.map +1 -1
- package/dist/handlers/generate.js +81 -5
- package/dist/handlers/generate.js.map +1 -1
- package/dist/handlers/health.d.ts +7 -0
- package/dist/handlers/health.d.ts.map +1 -1
- package/dist/handlers/health.js +151 -69
- package/dist/handlers/health.js.map +1 -1
- package/dist/handlers/review.d.ts +12 -0
- package/dist/handlers/review.d.ts.map +1 -1
- package/dist/handlers/review.js +220 -21
- package/dist/handlers/review.js.map +1 -1
- package/dist/services/analysisCache.d.ts +110 -0
- package/dist/services/analysisCache.d.ts.map +1 -0
- package/dist/services/analysisCache.js +233 -0
- package/dist/services/analysisCache.js.map +1 -0
- package/dist/services/codeAnalyzer.d.ts +73 -30
- package/dist/services/codeAnalyzer.d.ts.map +1 -1
- package/dist/services/codeAnalyzer.js +384 -36
- package/dist/services/codeAnalyzer.js.map +1 -1
- package/dist/services/conventionDetector.d.ts +40 -0
- package/dist/services/conventionDetector.d.ts.map +1 -0
- package/dist/services/conventionDetector.js +465 -0
- package/dist/services/conventionDetector.js.map +1 -0
- package/dist/services/cursorDirectory.d.ts +29 -2
- package/dist/services/cursorDirectory.d.ts.map +1 -1
- package/dist/services/cursorDirectory.js +260 -9
- package/dist/services/cursorDirectory.js.map +1 -1
- package/dist/utils/validation.d.ts +2 -2
- package/package.json +1 -1
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Code Analyzer Service
|
|
3
|
-
*
|
|
2
|
+
* Code Analyzer Service - v3.0.0
|
|
3
|
+
* Unified rule pipeline that supports builtin, user, and project rules
|
|
4
4
|
*/
|
|
5
5
|
import { logger } from '../utils/logger.js';
|
|
6
|
-
//
|
|
7
|
-
|
|
6
|
+
// =============================================================================
|
|
7
|
+
// BUILTIN PATTERN RULES
|
|
8
|
+
// =============================================================================
|
|
9
|
+
const BUILTIN_PATTERN_RULES = [
|
|
8
10
|
// Security
|
|
9
11
|
{
|
|
10
12
|
id: 'SEC001',
|
|
13
|
+
type: 'pattern',
|
|
11
14
|
category: 'security',
|
|
12
15
|
pattern: /eval\s*\(/g,
|
|
13
16
|
severity: 'error',
|
|
14
17
|
message: 'Avoid using eval() - it can execute arbitrary code',
|
|
15
18
|
suggestion: 'Use JSON.parse() for JSON data or safer alternatives',
|
|
16
19
|
languages: ['javascript', 'typescript'],
|
|
20
|
+
enabled: true,
|
|
21
|
+
priority: 100,
|
|
22
|
+
source: 'builtin',
|
|
17
23
|
quickFix: (match) => ({
|
|
18
24
|
description: 'Replace eval() with JSON.parse()',
|
|
19
25
|
before: match,
|
|
@@ -22,12 +28,16 @@ const PATTERN_RULES = [
|
|
|
22
28
|
},
|
|
23
29
|
{
|
|
24
30
|
id: 'SEC002',
|
|
31
|
+
type: 'pattern',
|
|
25
32
|
category: 'security',
|
|
26
33
|
pattern: /innerHTML\s*=/g,
|
|
27
34
|
severity: 'warning',
|
|
28
35
|
message: 'innerHTML can lead to XSS vulnerabilities',
|
|
29
36
|
suggestion: 'Use textContent or sanitize HTML input',
|
|
30
37
|
languages: ['javascript', 'typescript'],
|
|
38
|
+
enabled: true,
|
|
39
|
+
priority: 100,
|
|
40
|
+
source: 'builtin',
|
|
31
41
|
quickFix: (match) => ({
|
|
32
42
|
description: 'Replace innerHTML with textContent',
|
|
33
43
|
before: match,
|
|
@@ -36,20 +46,28 @@ const PATTERN_RULES = [
|
|
|
36
46
|
},
|
|
37
47
|
{
|
|
38
48
|
id: 'SEC003',
|
|
49
|
+
type: 'pattern',
|
|
39
50
|
category: 'security',
|
|
40
51
|
pattern: /dangerouslySetInnerHTML/g,
|
|
41
52
|
severity: 'warning',
|
|
42
53
|
message: 'dangerouslySetInnerHTML can lead to XSS - ensure content is sanitized',
|
|
43
54
|
suggestion: 'Sanitize HTML with DOMPurify or similar library',
|
|
44
|
-
languages: ['javascript', 'typescript', 'jsx', 'tsx']
|
|
55
|
+
languages: ['javascript', 'typescript', 'jsx', 'tsx'],
|
|
56
|
+
enabled: true,
|
|
57
|
+
priority: 100,
|
|
58
|
+
source: 'builtin'
|
|
45
59
|
},
|
|
46
60
|
{
|
|
47
61
|
id: 'SEC004',
|
|
62
|
+
type: 'pattern',
|
|
48
63
|
category: 'security',
|
|
49
64
|
pattern: /password\s*[:=]\s*['"`][^'"`]+['"`]/gi,
|
|
50
65
|
severity: 'error',
|
|
51
66
|
message: 'Hardcoded password detected',
|
|
52
67
|
suggestion: 'Use environment variables for sensitive data',
|
|
68
|
+
enabled: true,
|
|
69
|
+
priority: 100,
|
|
70
|
+
source: 'builtin',
|
|
53
71
|
quickFix: (match) => ({
|
|
54
72
|
description: 'Replace hardcoded password with environment variable',
|
|
55
73
|
before: match,
|
|
@@ -58,11 +76,15 @@ const PATTERN_RULES = [
|
|
|
58
76
|
},
|
|
59
77
|
{
|
|
60
78
|
id: 'SEC005',
|
|
79
|
+
type: 'pattern',
|
|
61
80
|
category: 'security',
|
|
62
81
|
pattern: /api[_-]?key\s*[:=]\s*['"`][a-zA-Z0-9]{20,}['"`]/gi,
|
|
63
82
|
severity: 'error',
|
|
64
83
|
message: 'Hardcoded API key detected',
|
|
65
84
|
suggestion: 'Use environment variables for API keys',
|
|
85
|
+
enabled: true,
|
|
86
|
+
priority: 100,
|
|
87
|
+
source: 'builtin',
|
|
66
88
|
quickFix: (match) => ({
|
|
67
89
|
description: 'Replace hardcoded API key with environment variable',
|
|
68
90
|
before: match,
|
|
@@ -71,81 +93,117 @@ const PATTERN_RULES = [
|
|
|
71
93
|
},
|
|
72
94
|
{
|
|
73
95
|
id: 'SEC006',
|
|
96
|
+
type: 'pattern',
|
|
74
97
|
category: 'security',
|
|
75
98
|
pattern: /SELECT\s+\*\s+FROM\s+\w+\s+WHERE.*\+|SELECT\s+\*\s+FROM\s+\w+\s+WHERE.*\$\{/gi,
|
|
76
99
|
severity: 'error',
|
|
77
100
|
message: 'Potential SQL injection - string concatenation in query',
|
|
78
101
|
suggestion: 'Use parameterized queries or an ORM',
|
|
102
|
+
enabled: true,
|
|
103
|
+
priority: 100,
|
|
104
|
+
source: 'builtin'
|
|
79
105
|
},
|
|
80
106
|
{
|
|
81
107
|
id: 'SEC007',
|
|
108
|
+
type: 'pattern',
|
|
82
109
|
category: 'security',
|
|
83
110
|
pattern: /exec\s*\([^)]*\+|exec\s*\([^)]*\$\{/g,
|
|
84
111
|
severity: 'error',
|
|
85
112
|
message: 'Command injection risk - dynamic command execution',
|
|
86
113
|
suggestion: 'Validate and sanitize all user input',
|
|
87
|
-
languages: ['python']
|
|
114
|
+
languages: ['python'],
|
|
115
|
+
enabled: true,
|
|
116
|
+
priority: 100,
|
|
117
|
+
source: 'builtin'
|
|
88
118
|
},
|
|
89
119
|
{
|
|
90
120
|
id: 'SEC008',
|
|
121
|
+
type: 'pattern',
|
|
91
122
|
category: 'security',
|
|
92
123
|
pattern: /jwt\.decode\s*\([^)]*verify\s*=\s*False/gi,
|
|
93
124
|
severity: 'error',
|
|
94
125
|
message: 'JWT decoded without verification',
|
|
95
126
|
suggestion: 'Always verify JWT signatures',
|
|
96
|
-
languages: ['python']
|
|
127
|
+
languages: ['python'],
|
|
128
|
+
enabled: true,
|
|
129
|
+
priority: 100,
|
|
130
|
+
source: 'builtin'
|
|
97
131
|
},
|
|
98
132
|
// Performance
|
|
99
133
|
{
|
|
100
134
|
id: 'PERF001',
|
|
135
|
+
type: 'pattern',
|
|
101
136
|
category: 'performance',
|
|
102
137
|
pattern: /document\.querySelectorAll\([^)]+\)\.forEach/g,
|
|
103
138
|
severity: 'info',
|
|
104
139
|
message: 'Consider caching querySelectorAll result for multiple operations',
|
|
105
140
|
suggestion: 'Store the result in a variable before iterating',
|
|
106
|
-
languages: ['javascript', 'typescript']
|
|
141
|
+
languages: ['javascript', 'typescript'],
|
|
142
|
+
enabled: true,
|
|
143
|
+
priority: 50,
|
|
144
|
+
source: 'builtin'
|
|
107
145
|
},
|
|
108
146
|
{
|
|
109
147
|
id: 'PERF002',
|
|
148
|
+
type: 'pattern',
|
|
110
149
|
category: 'performance',
|
|
111
150
|
pattern: /JSON\.parse\(JSON\.stringify\(/g,
|
|
112
151
|
severity: 'warning',
|
|
113
152
|
message: 'Deep clone with JSON is slow for large objects',
|
|
114
153
|
suggestion: 'Use structuredClone() or a dedicated library like lodash.cloneDeep',
|
|
154
|
+
enabled: true,
|
|
155
|
+
priority: 50,
|
|
156
|
+
source: 'builtin'
|
|
115
157
|
},
|
|
116
158
|
{
|
|
117
159
|
id: 'PERF003',
|
|
160
|
+
type: 'pattern',
|
|
118
161
|
category: 'performance',
|
|
119
162
|
pattern: /useEffect\(\s*\(\)\s*=>\s*\{[^}]*\},\s*\[\s*\]\s*\)/gs,
|
|
120
163
|
severity: 'info',
|
|
121
164
|
message: 'Empty dependency array - effect runs only once',
|
|
122
165
|
suggestion: 'Verify this is intentional behavior',
|
|
123
|
-
languages: ['javascript', 'typescript', 'jsx', 'tsx']
|
|
166
|
+
languages: ['javascript', 'typescript', 'jsx', 'tsx'],
|
|
167
|
+
enabled: true,
|
|
168
|
+
priority: 50,
|
|
169
|
+
source: 'builtin'
|
|
124
170
|
},
|
|
125
171
|
{
|
|
126
172
|
id: 'PERF004',
|
|
173
|
+
type: 'pattern',
|
|
127
174
|
category: 'performance',
|
|
128
175
|
pattern: /await\s+\w+\([^)]*\)\s*;\s*await\s+\w+\([^)]*\)\s*;/g,
|
|
129
176
|
severity: 'suggestion',
|
|
130
177
|
message: 'Sequential awaits might be parallelizable',
|
|
131
178
|
suggestion: 'Consider Promise.all() for independent async operations',
|
|
179
|
+
enabled: true,
|
|
180
|
+
priority: 50,
|
|
181
|
+
source: 'builtin'
|
|
132
182
|
},
|
|
133
183
|
{
|
|
134
184
|
id: 'PERF005',
|
|
185
|
+
type: 'pattern',
|
|
135
186
|
category: 'performance',
|
|
136
187
|
pattern: /\.map\([^)]+\)\.filter\([^)]+\)/g,
|
|
137
188
|
severity: 'suggestion',
|
|
138
189
|
message: 'Chained map().filter() iterates twice',
|
|
139
190
|
suggestion: 'Consider using reduce() for single-pass transformation',
|
|
191
|
+
enabled: true,
|
|
192
|
+
priority: 50,
|
|
193
|
+
source: 'builtin'
|
|
140
194
|
},
|
|
141
195
|
// Coding Standards
|
|
142
196
|
{
|
|
143
197
|
id: 'STD001',
|
|
198
|
+
type: 'pattern',
|
|
144
199
|
category: 'coding-standards',
|
|
145
200
|
pattern: /console\.(log|debug|info)\s*\(/g,
|
|
146
201
|
severity: 'warning',
|
|
147
202
|
message: 'Console statement found - remove for production',
|
|
148
203
|
suggestion: 'Use a proper logging library or remove before deployment',
|
|
204
|
+
enabled: true,
|
|
205
|
+
priority: 50,
|
|
206
|
+
source: 'builtin',
|
|
149
207
|
quickFix: (match) => ({
|
|
150
208
|
description: 'Remove console statement',
|
|
151
209
|
before: match,
|
|
@@ -154,18 +212,26 @@ const PATTERN_RULES = [
|
|
|
154
212
|
},
|
|
155
213
|
{
|
|
156
214
|
id: 'STD002',
|
|
215
|
+
type: 'pattern',
|
|
157
216
|
category: 'coding-standards',
|
|
158
217
|
pattern: /\/\/\s*TODO:|\/\/\s*FIXME:|\/\/\s*HACK:/gi,
|
|
159
218
|
severity: 'info',
|
|
160
219
|
message: 'TODO/FIXME comment found - address before release',
|
|
220
|
+
enabled: true,
|
|
221
|
+
priority: 25,
|
|
222
|
+
source: 'builtin'
|
|
161
223
|
},
|
|
162
224
|
{
|
|
163
225
|
id: 'STD003',
|
|
226
|
+
type: 'pattern',
|
|
164
227
|
category: 'coding-standards',
|
|
165
228
|
pattern: /debugger\s*;/g,
|
|
166
229
|
severity: 'error',
|
|
167
230
|
message: 'Debugger statement found - remove before deployment',
|
|
168
231
|
languages: ['javascript', 'typescript'],
|
|
232
|
+
enabled: true,
|
|
233
|
+
priority: 100,
|
|
234
|
+
source: 'builtin',
|
|
169
235
|
quickFix: () => ({
|
|
170
236
|
description: 'Remove debugger statement',
|
|
171
237
|
before: 'debugger;',
|
|
@@ -174,12 +240,16 @@ const PATTERN_RULES = [
|
|
|
174
240
|
},
|
|
175
241
|
{
|
|
176
242
|
id: 'STD004',
|
|
243
|
+
type: 'pattern',
|
|
177
244
|
category: 'coding-standards',
|
|
178
245
|
pattern: /var\s+(\w+)\s*=/g,
|
|
179
246
|
severity: 'warning',
|
|
180
247
|
message: 'Prefer const or let over var',
|
|
181
248
|
suggestion: 'Use const for immutable values, let for mutable',
|
|
182
249
|
languages: ['javascript', 'typescript'],
|
|
250
|
+
enabled: true,
|
|
251
|
+
priority: 50,
|
|
252
|
+
source: 'builtin',
|
|
183
253
|
quickFix: (match) => ({
|
|
184
254
|
description: 'Replace var with const',
|
|
185
255
|
before: match,
|
|
@@ -188,11 +258,15 @@ const PATTERN_RULES = [
|
|
|
188
258
|
},
|
|
189
259
|
{
|
|
190
260
|
id: 'STD005',
|
|
261
|
+
type: 'pattern',
|
|
191
262
|
category: 'coding-standards',
|
|
192
263
|
pattern: /==(?!=)/g,
|
|
193
264
|
severity: 'warning',
|
|
194
265
|
message: 'Use strict equality (===) instead of loose equality (==)',
|
|
195
266
|
languages: ['javascript', 'typescript'],
|
|
267
|
+
enabled: true,
|
|
268
|
+
priority: 50,
|
|
269
|
+
source: 'builtin',
|
|
196
270
|
quickFix: () => ({
|
|
197
271
|
description: 'Replace == with ===',
|
|
198
272
|
before: '==',
|
|
@@ -201,11 +275,15 @@ const PATTERN_RULES = [
|
|
|
201
275
|
},
|
|
202
276
|
{
|
|
203
277
|
id: 'STD006',
|
|
278
|
+
type: 'pattern',
|
|
204
279
|
category: 'coding-standards',
|
|
205
280
|
pattern: /!=(?!=)/g,
|
|
206
281
|
severity: 'warning',
|
|
207
282
|
message: 'Use strict inequality (!==) instead of loose inequality (!=)',
|
|
208
283
|
languages: ['javascript', 'typescript'],
|
|
284
|
+
enabled: true,
|
|
285
|
+
priority: 50,
|
|
286
|
+
source: 'builtin',
|
|
209
287
|
quickFix: () => ({
|
|
210
288
|
description: 'Replace != with !==',
|
|
211
289
|
before: '!=',
|
|
@@ -214,141 +292,330 @@ const PATTERN_RULES = [
|
|
|
214
292
|
},
|
|
215
293
|
{
|
|
216
294
|
id: 'STD007',
|
|
295
|
+
type: 'pattern',
|
|
217
296
|
category: 'coding-standards',
|
|
218
297
|
pattern: /function\s+\w+\s*\([^)]*\)\s*\{[\s\S]{500,}\}/g,
|
|
219
298
|
severity: 'warning',
|
|
220
299
|
message: 'Function appears too long (>500 chars)',
|
|
221
300
|
suggestion: 'Consider breaking into smaller functions',
|
|
301
|
+
enabled: true,
|
|
302
|
+
priority: 25,
|
|
303
|
+
source: 'builtin'
|
|
222
304
|
},
|
|
223
305
|
{
|
|
224
306
|
id: 'STD008',
|
|
307
|
+
type: 'pattern',
|
|
225
308
|
category: 'coding-standards',
|
|
226
309
|
pattern: /catch\s*\(\s*\w*\s*\)\s*\{\s*\}/g,
|
|
227
310
|
severity: 'warning',
|
|
228
311
|
message: 'Empty catch block - errors are silently ignored',
|
|
229
312
|
suggestion: 'Log the error or handle it appropriately',
|
|
313
|
+
enabled: true,
|
|
314
|
+
priority: 75,
|
|
315
|
+
source: 'builtin'
|
|
230
316
|
},
|
|
231
317
|
// Architecture
|
|
232
318
|
{
|
|
233
319
|
id: 'ARCH001',
|
|
320
|
+
type: 'pattern',
|
|
234
321
|
category: 'architecture',
|
|
235
322
|
pattern: /import.*from\s+['"]\.\.\/\.\.\/\.\.\/\.\./g,
|
|
236
323
|
severity: 'warning',
|
|
237
324
|
message: 'Deep relative import - consider path aliases',
|
|
238
325
|
suggestion: 'Configure path aliases in tsconfig.json',
|
|
239
|
-
languages: ['javascript', 'typescript']
|
|
326
|
+
languages: ['javascript', 'typescript'],
|
|
327
|
+
enabled: true,
|
|
328
|
+
priority: 25,
|
|
329
|
+
source: 'builtin'
|
|
240
330
|
},
|
|
241
331
|
{
|
|
242
332
|
id: 'ARCH002',
|
|
333
|
+
type: 'pattern',
|
|
243
334
|
category: 'architecture',
|
|
244
335
|
pattern: /class\s+\w+\s*\{[\s\S]*constructor\s*\([^)]{200,}\)/g,
|
|
245
336
|
severity: 'warning',
|
|
246
337
|
message: 'Constructor has many parameters - consider dependency injection',
|
|
247
338
|
suggestion: 'Use a DI container or builder pattern',
|
|
339
|
+
enabled: true,
|
|
340
|
+
priority: 25,
|
|
341
|
+
source: 'builtin'
|
|
248
342
|
},
|
|
249
343
|
{
|
|
250
344
|
id: 'ARCH003',
|
|
345
|
+
type: 'pattern',
|
|
251
346
|
category: 'architecture',
|
|
252
347
|
pattern: /new\s+(Date|Math\.random)\s*\(\)/g,
|
|
253
348
|
severity: 'suggestion',
|
|
254
349
|
message: 'Direct instantiation makes testing harder',
|
|
255
350
|
suggestion: 'Inject dependencies for better testability',
|
|
351
|
+
enabled: true,
|
|
352
|
+
priority: 25,
|
|
353
|
+
source: 'builtin'
|
|
256
354
|
},
|
|
257
355
|
// Python specific
|
|
258
356
|
{
|
|
259
357
|
id: 'PY001',
|
|
260
|
-
|
|
358
|
+
type: 'pattern',
|
|
359
|
+
category: 'python',
|
|
261
360
|
pattern: /except\s*:/g,
|
|
262
361
|
severity: 'warning',
|
|
263
362
|
message: 'Bare except catches all exceptions including SystemExit',
|
|
264
363
|
suggestion: 'Specify exception type: except Exception:',
|
|
265
|
-
languages: ['python']
|
|
364
|
+
languages: ['python'],
|
|
365
|
+
enabled: true,
|
|
366
|
+
priority: 75,
|
|
367
|
+
source: 'builtin'
|
|
266
368
|
},
|
|
267
369
|
{
|
|
268
370
|
id: 'PY002',
|
|
269
|
-
|
|
371
|
+
type: 'pattern',
|
|
372
|
+
category: 'python',
|
|
270
373
|
pattern: /print\s*\(/g,
|
|
271
374
|
severity: 'info',
|
|
272
375
|
message: 'Print statement found - consider using logging',
|
|
273
376
|
suggestion: 'Use the logging module for production code',
|
|
274
|
-
languages: ['python']
|
|
377
|
+
languages: ['python'],
|
|
378
|
+
enabled: true,
|
|
379
|
+
priority: 25,
|
|
380
|
+
source: 'builtin'
|
|
275
381
|
},
|
|
276
382
|
{
|
|
277
383
|
id: 'PY003',
|
|
384
|
+
type: 'pattern',
|
|
278
385
|
category: 'security',
|
|
279
386
|
pattern: /pickle\.load|pickle\.loads/g,
|
|
280
387
|
severity: 'error',
|
|
281
388
|
message: 'Pickle is unsafe for untrusted data',
|
|
282
389
|
suggestion: 'Use JSON or a safe serialization format',
|
|
283
|
-
languages: ['python']
|
|
390
|
+
languages: ['python'],
|
|
391
|
+
enabled: true,
|
|
392
|
+
priority: 100,
|
|
393
|
+
source: 'builtin'
|
|
284
394
|
},
|
|
285
395
|
{
|
|
286
396
|
id: 'PY004',
|
|
287
|
-
|
|
397
|
+
type: 'pattern',
|
|
398
|
+
category: 'python',
|
|
288
399
|
pattern: /from\s+\w+\s+import\s+\*/g,
|
|
289
400
|
severity: 'warning',
|
|
290
401
|
message: 'Wildcard imports make code harder to understand',
|
|
291
402
|
suggestion: 'Import specific names explicitly',
|
|
292
|
-
languages: ['python']
|
|
403
|
+
languages: ['python'],
|
|
404
|
+
enabled: true,
|
|
405
|
+
priority: 50,
|
|
406
|
+
source: 'builtin'
|
|
293
407
|
},
|
|
294
408
|
// Go specific
|
|
295
409
|
{
|
|
296
410
|
id: 'GO001',
|
|
297
|
-
|
|
411
|
+
type: 'pattern',
|
|
412
|
+
category: 'go',
|
|
298
413
|
pattern: /fmt\.Print|fmt\.Println/g,
|
|
299
414
|
severity: 'info',
|
|
300
415
|
message: 'fmt.Print found - consider using structured logging',
|
|
301
416
|
suggestion: 'Use log/slog or a logging library',
|
|
302
|
-
languages: ['go']
|
|
417
|
+
languages: ['go'],
|
|
418
|
+
enabled: true,
|
|
419
|
+
priority: 25,
|
|
420
|
+
source: 'builtin'
|
|
303
421
|
},
|
|
304
422
|
{
|
|
305
423
|
id: 'GO002',
|
|
306
|
-
|
|
424
|
+
type: 'pattern',
|
|
425
|
+
category: 'go',
|
|
307
426
|
pattern: /panic\s*\(/g,
|
|
308
427
|
severity: 'warning',
|
|
309
428
|
message: 'Panic should be used sparingly',
|
|
310
429
|
suggestion: 'Return errors instead of panicking',
|
|
311
|
-
languages: ['go']
|
|
430
|
+
languages: ['go'],
|
|
431
|
+
enabled: true,
|
|
432
|
+
priority: 75,
|
|
433
|
+
source: 'builtin'
|
|
312
434
|
},
|
|
313
435
|
{
|
|
314
436
|
id: 'GO003',
|
|
315
|
-
|
|
437
|
+
type: 'pattern',
|
|
438
|
+
category: 'go',
|
|
316
439
|
pattern: /if\s+err\s*!=\s*nil\s*\{\s*return\s+nil\s*,?\s*err\s*\}/g,
|
|
317
440
|
severity: 'suggestion',
|
|
318
441
|
message: 'Consider wrapping errors with context',
|
|
319
442
|
suggestion: 'Use fmt.Errorf("context: %w", err)',
|
|
320
|
-
languages: ['go']
|
|
443
|
+
languages: ['go'],
|
|
444
|
+
enabled: true,
|
|
445
|
+
priority: 25,
|
|
446
|
+
source: 'builtin'
|
|
321
447
|
},
|
|
322
448
|
// Rust specific
|
|
323
449
|
{
|
|
324
450
|
id: 'RS001',
|
|
325
|
-
|
|
451
|
+
type: 'pattern',
|
|
452
|
+
category: 'rust',
|
|
326
453
|
pattern: /\.unwrap\(\)/g,
|
|
327
454
|
severity: 'warning',
|
|
328
455
|
message: 'unwrap() can panic on None/Err',
|
|
329
456
|
suggestion: 'Use ? operator or match/if let for proper error handling',
|
|
330
|
-
languages: ['rust']
|
|
457
|
+
languages: ['rust'],
|
|
458
|
+
enabled: true,
|
|
459
|
+
priority: 75,
|
|
460
|
+
source: 'builtin'
|
|
331
461
|
},
|
|
332
462
|
{
|
|
333
463
|
id: 'RS002',
|
|
334
|
-
|
|
464
|
+
type: 'pattern',
|
|
465
|
+
category: 'rust',
|
|
335
466
|
pattern: /\.expect\("[^"]*"\)/g,
|
|
336
467
|
severity: 'info',
|
|
337
468
|
message: 'expect() can panic - ensure this is intentional',
|
|
338
469
|
suggestion: 'Use ? operator for propagating errors',
|
|
339
|
-
languages: ['rust']
|
|
470
|
+
languages: ['rust'],
|
|
471
|
+
enabled: true,
|
|
472
|
+
priority: 50,
|
|
473
|
+
source: 'builtin'
|
|
340
474
|
},
|
|
341
475
|
{
|
|
342
476
|
id: 'RS003',
|
|
343
|
-
|
|
477
|
+
type: 'pattern',
|
|
478
|
+
category: 'rust',
|
|
344
479
|
pattern: /unsafe\s*\{/g,
|
|
345
480
|
severity: 'warning',
|
|
346
481
|
message: 'Unsafe block found - ensure safety invariants are documented',
|
|
347
482
|
suggestion: 'Add SAFETY comment explaining why this is safe',
|
|
348
|
-
languages: ['rust']
|
|
483
|
+
languages: ['rust'],
|
|
484
|
+
enabled: true,
|
|
485
|
+
priority: 75,
|
|
486
|
+
source: 'builtin'
|
|
349
487
|
}
|
|
350
488
|
];
|
|
351
|
-
|
|
489
|
+
// =============================================================================
|
|
490
|
+
// RULE REGISTRY - Unified Management
|
|
491
|
+
// =============================================================================
|
|
492
|
+
/**
|
|
493
|
+
* In-memory registry of all active rules
|
|
494
|
+
*/
|
|
495
|
+
class RuleRegistry {
|
|
496
|
+
builtinRules = [...BUILTIN_PATTERN_RULES];
|
|
497
|
+
userRules = [];
|
|
498
|
+
projectRules = [];
|
|
499
|
+
/**
|
|
500
|
+
* Get all builtin rules
|
|
501
|
+
*/
|
|
502
|
+
getBuiltinRules() {
|
|
503
|
+
return this.builtinRules.filter(r => r.enabled);
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Get all user-defined rules
|
|
507
|
+
*/
|
|
508
|
+
getUserRules() {
|
|
509
|
+
return this.userRules.filter(r => r.enabled);
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Get all project-specific rules
|
|
513
|
+
*/
|
|
514
|
+
getProjectRules() {
|
|
515
|
+
return this.projectRules.filter(r => r.enabled);
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Get all active rules, sorted by priority (highest first)
|
|
519
|
+
*/
|
|
520
|
+
getAllRules() {
|
|
521
|
+
const all = [
|
|
522
|
+
...this.builtinRules,
|
|
523
|
+
...this.userRules,
|
|
524
|
+
...this.projectRules
|
|
525
|
+
].filter(r => r.enabled);
|
|
526
|
+
// Sort by priority (higher priority = runs first)
|
|
527
|
+
return all.sort((a, b) => b.priority - a.priority);
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Register a user-defined pattern rule
|
|
531
|
+
*/
|
|
532
|
+
registerUserRule(rule) {
|
|
533
|
+
const fullRule = {
|
|
534
|
+
...rule,
|
|
535
|
+
type: 'pattern',
|
|
536
|
+
source: 'user'
|
|
537
|
+
};
|
|
538
|
+
// Remove existing rule with same ID
|
|
539
|
+
this.userRules = this.userRules.filter(r => r.id !== rule.id);
|
|
540
|
+
this.userRules.push(fullRule);
|
|
541
|
+
logger.info('Registered user rule', { ruleId: rule.id });
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Register a project-specific pattern rule
|
|
545
|
+
*/
|
|
546
|
+
registerProjectRule(rule) {
|
|
547
|
+
const fullRule = {
|
|
548
|
+
...rule,
|
|
549
|
+
type: 'pattern',
|
|
550
|
+
source: 'project'
|
|
551
|
+
};
|
|
552
|
+
// Remove existing rule with same ID
|
|
553
|
+
this.projectRules = this.projectRules.filter(r => r.id !== rule.id);
|
|
554
|
+
this.projectRules.push(fullRule);
|
|
555
|
+
logger.info('Registered project rule', { ruleId: rule.id });
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Register multiple project rules at once
|
|
559
|
+
*/
|
|
560
|
+
registerProjectRules(rules) {
|
|
561
|
+
for (const rule of rules) {
|
|
562
|
+
this.registerProjectRule(rule);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Clear all user rules
|
|
567
|
+
*/
|
|
568
|
+
clearUserRules() {
|
|
569
|
+
this.userRules = [];
|
|
570
|
+
logger.info('Cleared user rules');
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Clear all project rules
|
|
574
|
+
*/
|
|
575
|
+
clearProjectRules() {
|
|
576
|
+
this.projectRules = [];
|
|
577
|
+
logger.info('Cleared project rules');
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Disable a builtin rule by ID
|
|
581
|
+
*/
|
|
582
|
+
disableBuiltinRule(ruleId) {
|
|
583
|
+
const rule = this.builtinRules.find(r => r.id === ruleId);
|
|
584
|
+
if (rule) {
|
|
585
|
+
rule.enabled = false;
|
|
586
|
+
logger.info('Disabled builtin rule', { ruleId });
|
|
587
|
+
return true;
|
|
588
|
+
}
|
|
589
|
+
return false;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Enable a builtin rule by ID
|
|
593
|
+
*/
|
|
594
|
+
enableBuiltinRule(ruleId) {
|
|
595
|
+
const rule = this.builtinRules.find(r => r.id === ruleId);
|
|
596
|
+
if (rule) {
|
|
597
|
+
rule.enabled = true;
|
|
598
|
+
logger.info('Enabled builtin rule', { ruleId });
|
|
599
|
+
return true;
|
|
600
|
+
}
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Get statistics about registered rules
|
|
605
|
+
*/
|
|
606
|
+
getStats() {
|
|
607
|
+
const builtin = this.builtinRules.filter(r => r.enabled).length;
|
|
608
|
+
const user = this.userRules.filter(r => r.enabled).length;
|
|
609
|
+
const project = this.projectRules.filter(r => r.enabled).length;
|
|
610
|
+
return { builtin, user, project, total: builtin + user + project };
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
// Global rule registry instance
|
|
614
|
+
export const ruleRegistry = new RuleRegistry();
|
|
615
|
+
// =============================================================================
|
|
616
|
+
// ANALYSIS FUNCTIONS
|
|
617
|
+
// =============================================================================
|
|
618
|
+
function detectLanguage(file, _content) {
|
|
352
619
|
const ext = file.split('.').pop()?.toLowerCase() || '';
|
|
353
620
|
const langMap = {
|
|
354
621
|
'ts': 'typescript',
|
|
@@ -370,20 +637,35 @@ function detectLanguage(file, content) {
|
|
|
370
637
|
function getLineNumber(content, index) {
|
|
371
638
|
return content.substring(0, index).split('\n').length;
|
|
372
639
|
}
|
|
640
|
+
/**
|
|
641
|
+
* Analyze code using the unified rule pipeline
|
|
642
|
+
*/
|
|
373
643
|
export function analyzeCode(file, content, focus = 'all') {
|
|
374
644
|
const language = detectLanguage(file, content);
|
|
375
645
|
const issues = [];
|
|
376
646
|
logger.debug('Analyzing code', { file, language, focus, contentLength: content.length });
|
|
647
|
+
// Get all active rules from registry
|
|
648
|
+
const allRules = ruleRegistry.getAllRules();
|
|
649
|
+
// Track rules applied by source
|
|
650
|
+
const rulesApplied = { builtin: 0, user: 0, project: 0 };
|
|
377
651
|
// Filter rules by focus and language
|
|
378
|
-
const applicableRules =
|
|
652
|
+
const applicableRules = allRules.filter(rule => {
|
|
379
653
|
if (focus !== 'all' && rule.category !== focus)
|
|
380
654
|
return false;
|
|
381
655
|
if (rule.languages && !rule.languages.includes(language))
|
|
382
656
|
return false;
|
|
383
657
|
return true;
|
|
384
658
|
});
|
|
659
|
+
logger.debug('Applying rules', {
|
|
660
|
+
totalRules: allRules.length,
|
|
661
|
+
applicableRules: applicableRules.length,
|
|
662
|
+
focus,
|
|
663
|
+
language
|
|
664
|
+
});
|
|
385
665
|
// Apply pattern rules
|
|
386
666
|
for (const rule of applicableRules) {
|
|
667
|
+
if (rule.type !== 'pattern')
|
|
668
|
+
continue; // Skip non-pattern rules for now
|
|
387
669
|
let match;
|
|
388
670
|
// Reset regex state
|
|
389
671
|
rule.pattern.lastIndex = 0;
|
|
@@ -400,8 +682,11 @@ export function analyzeCode(file, content, focus = 'all') {
|
|
|
400
682
|
line,
|
|
401
683
|
code: matchedText.substring(0, 100),
|
|
402
684
|
suggestion: rule.suggestion,
|
|
403
|
-
quickFix
|
|
685
|
+
quickFix,
|
|
686
|
+
source: rule.source
|
|
404
687
|
});
|
|
688
|
+
// Track which source contributed
|
|
689
|
+
rulesApplied[rule.source]++;
|
|
405
690
|
// Prevent infinite loop on zero-length matches
|
|
406
691
|
if (match.index === rule.pattern.lastIndex) {
|
|
407
692
|
rule.pattern.lastIndex++;
|
|
@@ -435,14 +720,21 @@ export function analyzeCode(file, content, focus = 'all') {
|
|
|
435
720
|
.filter(i => i.quickFix)
|
|
436
721
|
.map(i => i.quickFix)
|
|
437
722
|
.filter((fix, idx, arr) => arr.findIndex(f => f.before === fix.before) === idx);
|
|
438
|
-
logger.info('Analysis complete', {
|
|
723
|
+
logger.info('Analysis complete', {
|
|
724
|
+
file,
|
|
725
|
+
issuesFound: issues.length,
|
|
726
|
+
score,
|
|
727
|
+
quickFixesAvailable: quickFixes.length,
|
|
728
|
+
rulesApplied
|
|
729
|
+
});
|
|
439
730
|
return {
|
|
440
731
|
file,
|
|
441
732
|
language,
|
|
442
733
|
issues,
|
|
443
734
|
score: Math.round(score),
|
|
444
735
|
summary,
|
|
445
|
-
quickFixes: quickFixes.length > 0 ? quickFixes : undefined
|
|
736
|
+
quickFixes: quickFixes.length > 0 ? quickFixes : undefined,
|
|
737
|
+
rulesApplied
|
|
446
738
|
};
|
|
447
739
|
}
|
|
448
740
|
export function analyzeMultipleFiles(files, focus = 'all') {
|
|
@@ -457,13 +749,19 @@ export function analyzeMultipleFiles(files, focus = 'all') {
|
|
|
457
749
|
info: results.reduce((sum, r) => sum + r.summary.info, 0),
|
|
458
750
|
suggestions: results.reduce((sum, r) => sum + r.summary.suggestions, 0)
|
|
459
751
|
};
|
|
752
|
+
const rulesApplied = {
|
|
753
|
+
builtin: results.reduce((sum, r) => sum + r.rulesApplied.builtin, 0),
|
|
754
|
+
user: results.reduce((sum, r) => sum + r.rulesApplied.user, 0),
|
|
755
|
+
project: results.reduce((sum, r) => sum + r.rulesApplied.project, 0)
|
|
756
|
+
};
|
|
460
757
|
return {
|
|
461
758
|
files: results,
|
|
462
759
|
overall: {
|
|
463
760
|
totalFiles: results.length,
|
|
464
761
|
totalIssues,
|
|
465
762
|
averageScore,
|
|
466
|
-
summary
|
|
763
|
+
summary,
|
|
764
|
+
rulesApplied
|
|
467
765
|
}
|
|
468
766
|
};
|
|
469
767
|
}
|
|
@@ -471,6 +769,7 @@ export function formatAnalysisReport(result) {
|
|
|
471
769
|
const lines = [];
|
|
472
770
|
lines.push(`## Code Review: ${result.file}`);
|
|
473
771
|
lines.push(`**Language:** ${result.language} | **Score:** ${result.score}/100`);
|
|
772
|
+
lines.push(`**Rules Applied:** ${result.rulesApplied.builtin} builtin, ${result.rulesApplied.user} user, ${result.rulesApplied.project} project`);
|
|
474
773
|
lines.push('');
|
|
475
774
|
if (result.issues.length === 0) {
|
|
476
775
|
lines.push('✅ No issues found!');
|
|
@@ -487,7 +786,8 @@ export function formatAnalysisReport(result) {
|
|
|
487
786
|
const icon = issue.severity === 'error' ? '🔴' :
|
|
488
787
|
issue.severity === 'warning' ? '🟡' :
|
|
489
788
|
issue.severity === 'info' ? '🔵' : '💡';
|
|
490
|
-
|
|
789
|
+
const sourceTag = issue.source !== 'builtin' ? ` [${issue.source}]` : '';
|
|
790
|
+
lines.push(`#### ${icon} [${issue.rule}]${sourceTag} ${issue.message}`);
|
|
491
791
|
if (issue.line)
|
|
492
792
|
lines.push(`- Line: ${issue.line}`);
|
|
493
793
|
if (issue.code)
|
|
@@ -514,4 +814,52 @@ export function formatAnalysisReport(result) {
|
|
|
514
814
|
}
|
|
515
815
|
return lines.join('\n');
|
|
516
816
|
}
|
|
817
|
+
// =============================================================================
|
|
818
|
+
// HELPER: Convert user rules from RuleManager format to PatternRule
|
|
819
|
+
// =============================================================================
|
|
820
|
+
/**
|
|
821
|
+
* Parse a user-defined rule content to extract pattern rules
|
|
822
|
+
* This bridges the gap between RuleManager's documentation rules and analysis rules
|
|
823
|
+
*/
|
|
824
|
+
export function parseUserRuleToPatternRule(ruleId, content, category) {
|
|
825
|
+
// Look for patterns defined in markdown code blocks with special syntax
|
|
826
|
+
// Example: ```pattern:error
|
|
827
|
+
// /console\.log/g
|
|
828
|
+
// ```
|
|
829
|
+
const patternMatch = content.match(/```pattern:(error|warning|info|suggestion)\s*\n(\/[^/]+\/[gimsu]*)\s*\n```/);
|
|
830
|
+
if (!patternMatch) {
|
|
831
|
+
return null;
|
|
832
|
+
}
|
|
833
|
+
const severity = patternMatch[1];
|
|
834
|
+
const patternStr = patternMatch[2];
|
|
835
|
+
try {
|
|
836
|
+
// Parse the regex string
|
|
837
|
+
const regexMatch = patternStr.match(/^\/(.+)\/([gimsu]*)$/);
|
|
838
|
+
if (!regexMatch)
|
|
839
|
+
return null;
|
|
840
|
+
const pattern = new RegExp(regexMatch[1], regexMatch[2] || 'g');
|
|
841
|
+
// Extract message from the rule content
|
|
842
|
+
const messageMatch = content.match(/##?\s*(?:Rule|Message):\s*(.+)/i);
|
|
843
|
+
const message = messageMatch?.[1] || `Custom rule: ${ruleId}`;
|
|
844
|
+
// Extract suggestion if present
|
|
845
|
+
const suggestionMatch = content.match(/##?\s*Suggestion:\s*(.+)/i);
|
|
846
|
+
const suggestion = suggestionMatch?.[1];
|
|
847
|
+
return {
|
|
848
|
+
id: ruleId,
|
|
849
|
+
type: 'pattern',
|
|
850
|
+
category: category,
|
|
851
|
+
pattern,
|
|
852
|
+
severity,
|
|
853
|
+
message,
|
|
854
|
+
suggestion,
|
|
855
|
+
enabled: true,
|
|
856
|
+
priority: 150, // User rules have higher priority than builtin
|
|
857
|
+
source: 'user'
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
catch {
|
|
861
|
+
logger.warn('Failed to parse pattern rule', { ruleId });
|
|
862
|
+
return null;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
517
865
|
//# sourceMappingURL=codeAnalyzer.js.map
|