@stackguide/mcp-server 2.3.2 → 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.
Files changed (56) hide show
  1. package/README.md +82 -16
  2. package/dist/config/healthWeights.d.ts +87 -0
  3. package/dist/config/healthWeights.d.ts.map +1 -0
  4. package/dist/config/healthWeights.js +238 -0
  5. package/dist/config/healthWeights.js.map +1 -0
  6. package/dist/config/types.d.ts +104 -0
  7. package/dist/config/types.d.ts.map +1 -1
  8. package/dist/config/types.js.map +1 -1
  9. package/dist/handlers/generate.d.ts +24 -0
  10. package/dist/handlers/generate.d.ts.map +1 -0
  11. package/dist/handlers/generate.js +845 -0
  12. package/dist/handlers/generate.js.map +1 -0
  13. package/dist/handlers/health.d.ts +19 -0
  14. package/dist/handlers/health.d.ts.map +1 -0
  15. package/dist/handlers/health.js +401 -0
  16. package/dist/handlers/health.js.map +1 -0
  17. package/dist/handlers/help.d.ts +1 -1
  18. package/dist/handlers/help.d.ts.map +1 -1
  19. package/dist/handlers/help.js +70 -10
  20. package/dist/handlers/help.js.map +1 -1
  21. package/dist/handlers/index.d.ts +2 -0
  22. package/dist/handlers/index.d.ts.map +1 -1
  23. package/dist/handlers/index.js +2 -0
  24. package/dist/handlers/index.js.map +1 -1
  25. package/dist/handlers/review.d.ts +12 -0
  26. package/dist/handlers/review.d.ts.map +1 -1
  27. package/dist/handlers/review.js +220 -21
  28. package/dist/handlers/review.js.map +1 -1
  29. package/dist/handlers/setup.d.ts +1 -0
  30. package/dist/handlers/setup.d.ts.map +1 -1
  31. package/dist/handlers/setup.js +102 -5
  32. package/dist/handlers/setup.js.map +1 -1
  33. package/dist/index.js +6 -2
  34. package/dist/index.js.map +1 -1
  35. package/dist/services/analysisCache.d.ts +110 -0
  36. package/dist/services/analysisCache.d.ts.map +1 -0
  37. package/dist/services/analysisCache.js +233 -0
  38. package/dist/services/analysisCache.js.map +1 -0
  39. package/dist/services/codeAnalyzer.d.ts +73 -22
  40. package/dist/services/codeAnalyzer.d.ts.map +1 -1
  41. package/dist/services/codeAnalyzer.js +462 -44
  42. package/dist/services/codeAnalyzer.js.map +1 -1
  43. package/dist/services/conventionDetector.d.ts +40 -0
  44. package/dist/services/conventionDetector.d.ts.map +1 -0
  45. package/dist/services/conventionDetector.js +465 -0
  46. package/dist/services/conventionDetector.js.map +1 -0
  47. package/dist/services/cursorDirectory.d.ts +29 -2
  48. package/dist/services/cursorDirectory.d.ts.map +1 -1
  49. package/dist/services/cursorDirectory.js +260 -9
  50. package/dist/services/cursorDirectory.js.map +1 -1
  51. package/dist/tools/definitions.d.ts +125 -0
  52. package/dist/tools/definitions.d.ts.map +1 -1
  53. package/dist/tools/definitions.js +62 -1
  54. package/dist/tools/definitions.js.map +1 -1
  55. package/dist/utils/validation.d.ts +2 -2
  56. package/package.json +1 -1
@@ -1,309 +1,621 @@
1
1
  /**
2
- * Code Analyzer Service
3
- * Analyzes code against rules and patterns to find issues
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
- // Pattern-based rules for common issues
7
- const PATTERN_RULES = [
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
- languages: ['javascript', 'typescript']
19
+ languages: ['javascript', 'typescript'],
20
+ enabled: true,
21
+ priority: 100,
22
+ source: 'builtin',
23
+ quickFix: (match) => ({
24
+ description: 'Replace eval() with JSON.parse()',
25
+ before: match,
26
+ after: 'JSON.parse('
27
+ })
17
28
  },
18
29
  {
19
30
  id: 'SEC002',
31
+ type: 'pattern',
20
32
  category: 'security',
21
33
  pattern: /innerHTML\s*=/g,
22
34
  severity: 'warning',
23
35
  message: 'innerHTML can lead to XSS vulnerabilities',
24
36
  suggestion: 'Use textContent or sanitize HTML input',
25
- languages: ['javascript', 'typescript']
37
+ languages: ['javascript', 'typescript'],
38
+ enabled: true,
39
+ priority: 100,
40
+ source: 'builtin',
41
+ quickFix: (match) => ({
42
+ description: 'Replace innerHTML with textContent',
43
+ before: match,
44
+ after: 'textContent ='
45
+ })
26
46
  },
27
47
  {
28
48
  id: 'SEC003',
49
+ type: 'pattern',
29
50
  category: 'security',
30
51
  pattern: /dangerouslySetInnerHTML/g,
31
52
  severity: 'warning',
32
53
  message: 'dangerouslySetInnerHTML can lead to XSS - ensure content is sanitized',
33
54
  suggestion: 'Sanitize HTML with DOMPurify or similar library',
34
- languages: ['javascript', 'typescript', 'jsx', 'tsx']
55
+ languages: ['javascript', 'typescript', 'jsx', 'tsx'],
56
+ enabled: true,
57
+ priority: 100,
58
+ source: 'builtin'
35
59
  },
36
60
  {
37
61
  id: 'SEC004',
62
+ type: 'pattern',
38
63
  category: 'security',
39
64
  pattern: /password\s*[:=]\s*['"`][^'"`]+['"`]/gi,
40
65
  severity: 'error',
41
66
  message: 'Hardcoded password detected',
42
67
  suggestion: 'Use environment variables for sensitive data',
68
+ enabled: true,
69
+ priority: 100,
70
+ source: 'builtin',
71
+ quickFix: (match) => ({
72
+ description: 'Replace hardcoded password with environment variable',
73
+ before: match,
74
+ after: 'password = process.env.PASSWORD'
75
+ })
43
76
  },
44
77
  {
45
78
  id: 'SEC005',
79
+ type: 'pattern',
46
80
  category: 'security',
47
81
  pattern: /api[_-]?key\s*[:=]\s*['"`][a-zA-Z0-9]{20,}['"`]/gi,
48
82
  severity: 'error',
49
83
  message: 'Hardcoded API key detected',
50
84
  suggestion: 'Use environment variables for API keys',
85
+ enabled: true,
86
+ priority: 100,
87
+ source: 'builtin',
88
+ quickFix: (match) => ({
89
+ description: 'Replace hardcoded API key with environment variable',
90
+ before: match,
91
+ after: 'apiKey = process.env.API_KEY'
92
+ })
51
93
  },
52
94
  {
53
95
  id: 'SEC006',
96
+ type: 'pattern',
54
97
  category: 'security',
55
98
  pattern: /SELECT\s+\*\s+FROM\s+\w+\s+WHERE.*\+|SELECT\s+\*\s+FROM\s+\w+\s+WHERE.*\$\{/gi,
56
99
  severity: 'error',
57
100
  message: 'Potential SQL injection - string concatenation in query',
58
101
  suggestion: 'Use parameterized queries or an ORM',
102
+ enabled: true,
103
+ priority: 100,
104
+ source: 'builtin'
59
105
  },
60
106
  {
61
107
  id: 'SEC007',
108
+ type: 'pattern',
62
109
  category: 'security',
63
110
  pattern: /exec\s*\([^)]*\+|exec\s*\([^)]*\$\{/g,
64
111
  severity: 'error',
65
112
  message: 'Command injection risk - dynamic command execution',
66
113
  suggestion: 'Validate and sanitize all user input',
67
- languages: ['python']
114
+ languages: ['python'],
115
+ enabled: true,
116
+ priority: 100,
117
+ source: 'builtin'
68
118
  },
69
119
  {
70
120
  id: 'SEC008',
121
+ type: 'pattern',
71
122
  category: 'security',
72
123
  pattern: /jwt\.decode\s*\([^)]*verify\s*=\s*False/gi,
73
124
  severity: 'error',
74
125
  message: 'JWT decoded without verification',
75
126
  suggestion: 'Always verify JWT signatures',
76
- languages: ['python']
127
+ languages: ['python'],
128
+ enabled: true,
129
+ priority: 100,
130
+ source: 'builtin'
77
131
  },
78
132
  // Performance
79
133
  {
80
134
  id: 'PERF001',
135
+ type: 'pattern',
81
136
  category: 'performance',
82
137
  pattern: /document\.querySelectorAll\([^)]+\)\.forEach/g,
83
138
  severity: 'info',
84
139
  message: 'Consider caching querySelectorAll result for multiple operations',
85
140
  suggestion: 'Store the result in a variable before iterating',
86
- languages: ['javascript', 'typescript']
141
+ languages: ['javascript', 'typescript'],
142
+ enabled: true,
143
+ priority: 50,
144
+ source: 'builtin'
87
145
  },
88
146
  {
89
147
  id: 'PERF002',
148
+ type: 'pattern',
90
149
  category: 'performance',
91
150
  pattern: /JSON\.parse\(JSON\.stringify\(/g,
92
151
  severity: 'warning',
93
152
  message: 'Deep clone with JSON is slow for large objects',
94
153
  suggestion: 'Use structuredClone() or a dedicated library like lodash.cloneDeep',
154
+ enabled: true,
155
+ priority: 50,
156
+ source: 'builtin'
95
157
  },
96
158
  {
97
159
  id: 'PERF003',
160
+ type: 'pattern',
98
161
  category: 'performance',
99
162
  pattern: /useEffect\(\s*\(\)\s*=>\s*\{[^}]*\},\s*\[\s*\]\s*\)/gs,
100
163
  severity: 'info',
101
164
  message: 'Empty dependency array - effect runs only once',
102
165
  suggestion: 'Verify this is intentional behavior',
103
- languages: ['javascript', 'typescript', 'jsx', 'tsx']
166
+ languages: ['javascript', 'typescript', 'jsx', 'tsx'],
167
+ enabled: true,
168
+ priority: 50,
169
+ source: 'builtin'
104
170
  },
105
171
  {
106
172
  id: 'PERF004',
173
+ type: 'pattern',
107
174
  category: 'performance',
108
175
  pattern: /await\s+\w+\([^)]*\)\s*;\s*await\s+\w+\([^)]*\)\s*;/g,
109
176
  severity: 'suggestion',
110
177
  message: 'Sequential awaits might be parallelizable',
111
178
  suggestion: 'Consider Promise.all() for independent async operations',
179
+ enabled: true,
180
+ priority: 50,
181
+ source: 'builtin'
112
182
  },
113
183
  {
114
184
  id: 'PERF005',
185
+ type: 'pattern',
115
186
  category: 'performance',
116
187
  pattern: /\.map\([^)]+\)\.filter\([^)]+\)/g,
117
188
  severity: 'suggestion',
118
189
  message: 'Chained map().filter() iterates twice',
119
190
  suggestion: 'Consider using reduce() for single-pass transformation',
191
+ enabled: true,
192
+ priority: 50,
193
+ source: 'builtin'
120
194
  },
121
195
  // Coding Standards
122
196
  {
123
197
  id: 'STD001',
198
+ type: 'pattern',
124
199
  category: 'coding-standards',
125
200
  pattern: /console\.(log|debug|info)\s*\(/g,
126
201
  severity: 'warning',
127
202
  message: 'Console statement found - remove for production',
128
203
  suggestion: 'Use a proper logging library or remove before deployment',
204
+ enabled: true,
205
+ priority: 50,
206
+ source: 'builtin',
207
+ quickFix: (match) => ({
208
+ description: 'Remove console statement',
209
+ before: match,
210
+ after: '// ' + match + ' // TODO: Remove or replace with logger'
211
+ })
129
212
  },
130
213
  {
131
214
  id: 'STD002',
215
+ type: 'pattern',
132
216
  category: 'coding-standards',
133
217
  pattern: /\/\/\s*TODO:|\/\/\s*FIXME:|\/\/\s*HACK:/gi,
134
218
  severity: 'info',
135
219
  message: 'TODO/FIXME comment found - address before release',
220
+ enabled: true,
221
+ priority: 25,
222
+ source: 'builtin'
136
223
  },
137
224
  {
138
225
  id: 'STD003',
226
+ type: 'pattern',
139
227
  category: 'coding-standards',
140
228
  pattern: /debugger\s*;/g,
141
229
  severity: 'error',
142
230
  message: 'Debugger statement found - remove before deployment',
143
- languages: ['javascript', 'typescript']
231
+ languages: ['javascript', 'typescript'],
232
+ enabled: true,
233
+ priority: 100,
234
+ source: 'builtin',
235
+ quickFix: () => ({
236
+ description: 'Remove debugger statement',
237
+ before: 'debugger;',
238
+ after: ''
239
+ })
144
240
  },
145
241
  {
146
242
  id: 'STD004',
243
+ type: 'pattern',
147
244
  category: 'coding-standards',
148
- pattern: /var\s+\w+\s*=/g,
245
+ pattern: /var\s+(\w+)\s*=/g,
149
246
  severity: 'warning',
150
247
  message: 'Prefer const or let over var',
151
248
  suggestion: 'Use const for immutable values, let for mutable',
152
- languages: ['javascript', 'typescript']
249
+ languages: ['javascript', 'typescript'],
250
+ enabled: true,
251
+ priority: 50,
252
+ source: 'builtin',
253
+ quickFix: (match) => ({
254
+ description: 'Replace var with const',
255
+ before: match,
256
+ after: match.replace('var ', 'const ')
257
+ })
153
258
  },
154
259
  {
155
260
  id: 'STD005',
261
+ type: 'pattern',
156
262
  category: 'coding-standards',
157
263
  pattern: /==(?!=)/g,
158
264
  severity: 'warning',
159
265
  message: 'Use strict equality (===) instead of loose equality (==)',
160
- languages: ['javascript', 'typescript']
266
+ languages: ['javascript', 'typescript'],
267
+ enabled: true,
268
+ priority: 50,
269
+ source: 'builtin',
270
+ quickFix: () => ({
271
+ description: 'Replace == with ===',
272
+ before: '==',
273
+ after: '==='
274
+ })
161
275
  },
162
276
  {
163
277
  id: 'STD006',
278
+ type: 'pattern',
164
279
  category: 'coding-standards',
165
280
  pattern: /!=(?!=)/g,
166
281
  severity: 'warning',
167
282
  message: 'Use strict inequality (!==) instead of loose inequality (!=)',
168
- languages: ['javascript', 'typescript']
283
+ languages: ['javascript', 'typescript'],
284
+ enabled: true,
285
+ priority: 50,
286
+ source: 'builtin',
287
+ quickFix: () => ({
288
+ description: 'Replace != with !==',
289
+ before: '!=',
290
+ after: '!=='
291
+ })
169
292
  },
170
293
  {
171
294
  id: 'STD007',
295
+ type: 'pattern',
172
296
  category: 'coding-standards',
173
297
  pattern: /function\s+\w+\s*\([^)]*\)\s*\{[\s\S]{500,}\}/g,
174
298
  severity: 'warning',
175
299
  message: 'Function appears too long (>500 chars)',
176
300
  suggestion: 'Consider breaking into smaller functions',
301
+ enabled: true,
302
+ priority: 25,
303
+ source: 'builtin'
177
304
  },
178
305
  {
179
306
  id: 'STD008',
307
+ type: 'pattern',
180
308
  category: 'coding-standards',
181
309
  pattern: /catch\s*\(\s*\w*\s*\)\s*\{\s*\}/g,
182
310
  severity: 'warning',
183
311
  message: 'Empty catch block - errors are silently ignored',
184
312
  suggestion: 'Log the error or handle it appropriately',
313
+ enabled: true,
314
+ priority: 75,
315
+ source: 'builtin'
185
316
  },
186
317
  // Architecture
187
318
  {
188
319
  id: 'ARCH001',
320
+ type: 'pattern',
189
321
  category: 'architecture',
190
322
  pattern: /import.*from\s+['"]\.\.\/\.\.\/\.\.\/\.\./g,
191
323
  severity: 'warning',
192
324
  message: 'Deep relative import - consider path aliases',
193
325
  suggestion: 'Configure path aliases in tsconfig.json',
194
- languages: ['javascript', 'typescript']
326
+ languages: ['javascript', 'typescript'],
327
+ enabled: true,
328
+ priority: 25,
329
+ source: 'builtin'
195
330
  },
196
331
  {
197
332
  id: 'ARCH002',
333
+ type: 'pattern',
198
334
  category: 'architecture',
199
335
  pattern: /class\s+\w+\s*\{[\s\S]*constructor\s*\([^)]{200,}\)/g,
200
336
  severity: 'warning',
201
337
  message: 'Constructor has many parameters - consider dependency injection',
202
338
  suggestion: 'Use a DI container or builder pattern',
339
+ enabled: true,
340
+ priority: 25,
341
+ source: 'builtin'
203
342
  },
204
343
  {
205
344
  id: 'ARCH003',
345
+ type: 'pattern',
206
346
  category: 'architecture',
207
347
  pattern: /new\s+(Date|Math\.random)\s*\(\)/g,
208
348
  severity: 'suggestion',
209
349
  message: 'Direct instantiation makes testing harder',
210
350
  suggestion: 'Inject dependencies for better testability',
351
+ enabled: true,
352
+ priority: 25,
353
+ source: 'builtin'
211
354
  },
212
355
  // Python specific
213
356
  {
214
357
  id: 'PY001',
215
- category: 'coding-standards',
358
+ type: 'pattern',
359
+ category: 'python',
216
360
  pattern: /except\s*:/g,
217
361
  severity: 'warning',
218
362
  message: 'Bare except catches all exceptions including SystemExit',
219
363
  suggestion: 'Specify exception type: except Exception:',
220
- languages: ['python']
364
+ languages: ['python'],
365
+ enabled: true,
366
+ priority: 75,
367
+ source: 'builtin'
221
368
  },
222
369
  {
223
370
  id: 'PY002',
224
- category: 'coding-standards',
371
+ type: 'pattern',
372
+ category: 'python',
225
373
  pattern: /print\s*\(/g,
226
374
  severity: 'info',
227
375
  message: 'Print statement found - consider using logging',
228
376
  suggestion: 'Use the logging module for production code',
229
- languages: ['python']
377
+ languages: ['python'],
378
+ enabled: true,
379
+ priority: 25,
380
+ source: 'builtin'
230
381
  },
231
382
  {
232
383
  id: 'PY003',
384
+ type: 'pattern',
233
385
  category: 'security',
234
386
  pattern: /pickle\.load|pickle\.loads/g,
235
387
  severity: 'error',
236
388
  message: 'Pickle is unsafe for untrusted data',
237
389
  suggestion: 'Use JSON or a safe serialization format',
238
- languages: ['python']
390
+ languages: ['python'],
391
+ enabled: true,
392
+ priority: 100,
393
+ source: 'builtin'
239
394
  },
240
395
  {
241
396
  id: 'PY004',
242
- category: 'coding-standards',
397
+ type: 'pattern',
398
+ category: 'python',
243
399
  pattern: /from\s+\w+\s+import\s+\*/g,
244
400
  severity: 'warning',
245
401
  message: 'Wildcard imports make code harder to understand',
246
402
  suggestion: 'Import specific names explicitly',
247
- languages: ['python']
403
+ languages: ['python'],
404
+ enabled: true,
405
+ priority: 50,
406
+ source: 'builtin'
248
407
  },
249
408
  // Go specific
250
409
  {
251
410
  id: 'GO001',
252
- category: 'coding-standards',
411
+ type: 'pattern',
412
+ category: 'go',
253
413
  pattern: /fmt\.Print|fmt\.Println/g,
254
414
  severity: 'info',
255
415
  message: 'fmt.Print found - consider using structured logging',
256
416
  suggestion: 'Use log/slog or a logging library',
257
- languages: ['go']
417
+ languages: ['go'],
418
+ enabled: true,
419
+ priority: 25,
420
+ source: 'builtin'
258
421
  },
259
422
  {
260
423
  id: 'GO002',
261
- category: 'coding-standards',
424
+ type: 'pattern',
425
+ category: 'go',
262
426
  pattern: /panic\s*\(/g,
263
427
  severity: 'warning',
264
428
  message: 'Panic should be used sparingly',
265
429
  suggestion: 'Return errors instead of panicking',
266
- languages: ['go']
430
+ languages: ['go'],
431
+ enabled: true,
432
+ priority: 75,
433
+ source: 'builtin'
267
434
  },
268
435
  {
269
436
  id: 'GO003',
270
- category: 'coding-standards',
437
+ type: 'pattern',
438
+ category: 'go',
271
439
  pattern: /if\s+err\s*!=\s*nil\s*\{\s*return\s+nil\s*,?\s*err\s*\}/g,
272
440
  severity: 'suggestion',
273
441
  message: 'Consider wrapping errors with context',
274
442
  suggestion: 'Use fmt.Errorf("context: %w", err)',
275
- languages: ['go']
443
+ languages: ['go'],
444
+ enabled: true,
445
+ priority: 25,
446
+ source: 'builtin'
276
447
  },
277
448
  // Rust specific
278
449
  {
279
450
  id: 'RS001',
280
- category: 'coding-standards',
451
+ type: 'pattern',
452
+ category: 'rust',
281
453
  pattern: /\.unwrap\(\)/g,
282
454
  severity: 'warning',
283
455
  message: 'unwrap() can panic on None/Err',
284
456
  suggestion: 'Use ? operator or match/if let for proper error handling',
285
- languages: ['rust']
457
+ languages: ['rust'],
458
+ enabled: true,
459
+ priority: 75,
460
+ source: 'builtin'
286
461
  },
287
462
  {
288
463
  id: 'RS002',
289
- category: 'coding-standards',
464
+ type: 'pattern',
465
+ category: 'rust',
290
466
  pattern: /\.expect\("[^"]*"\)/g,
291
467
  severity: 'info',
292
468
  message: 'expect() can panic - ensure this is intentional',
293
469
  suggestion: 'Use ? operator for propagating errors',
294
- languages: ['rust']
470
+ languages: ['rust'],
471
+ enabled: true,
472
+ priority: 50,
473
+ source: 'builtin'
295
474
  },
296
475
  {
297
476
  id: 'RS003',
298
- category: 'coding-standards',
477
+ type: 'pattern',
478
+ category: 'rust',
299
479
  pattern: /unsafe\s*\{/g,
300
480
  severity: 'warning',
301
481
  message: 'Unsafe block found - ensure safety invariants are documented',
302
482
  suggestion: 'Add SAFETY comment explaining why this is safe',
303
- languages: ['rust']
483
+ languages: ['rust'],
484
+ enabled: true,
485
+ priority: 75,
486
+ source: 'builtin'
304
487
  }
305
488
  ];
306
- function detectLanguage(file, content) {
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) {
307
619
  const ext = file.split('.').pop()?.toLowerCase() || '';
308
620
  const langMap = {
309
621
  'ts': 'typescript',
@@ -325,34 +637,56 @@ function detectLanguage(file, content) {
325
637
  function getLineNumber(content, index) {
326
638
  return content.substring(0, index).split('\n').length;
327
639
  }
640
+ /**
641
+ * Analyze code using the unified rule pipeline
642
+ */
328
643
  export function analyzeCode(file, content, focus = 'all') {
329
644
  const language = detectLanguage(file, content);
330
645
  const issues = [];
331
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 };
332
651
  // Filter rules by focus and language
333
- const applicableRules = PATTERN_RULES.filter(rule => {
652
+ const applicableRules = allRules.filter(rule => {
334
653
  if (focus !== 'all' && rule.category !== focus)
335
654
  return false;
336
655
  if (rule.languages && !rule.languages.includes(language))
337
656
  return false;
338
657
  return true;
339
658
  });
659
+ logger.debug('Applying rules', {
660
+ totalRules: allRules.length,
661
+ applicableRules: applicableRules.length,
662
+ focus,
663
+ language
664
+ });
340
665
  // Apply pattern rules
341
666
  for (const rule of applicableRules) {
667
+ if (rule.type !== 'pattern')
668
+ continue; // Skip non-pattern rules for now
342
669
  let match;
343
670
  // Reset regex state
344
671
  rule.pattern.lastIndex = 0;
345
672
  while ((match = rule.pattern.exec(content)) !== null) {
346
673
  const line = getLineNumber(content, match.index);
674
+ const matchedText = match[0];
675
+ // Generate quick fix if available
676
+ const quickFix = rule.quickFix ? rule.quickFix(matchedText) : undefined;
347
677
  issues.push({
348
678
  severity: rule.severity,
349
679
  rule: rule.id,
350
680
  category: rule.category,
351
681
  message: rule.message,
352
682
  line,
353
- code: match[0].substring(0, 100),
354
- suggestion: rule.suggestion
683
+ code: matchedText.substring(0, 100),
684
+ suggestion: rule.suggestion,
685
+ quickFix,
686
+ source: rule.source
355
687
  });
688
+ // Track which source contributed
689
+ rulesApplied[rule.source]++;
356
690
  // Prevent infinite loop on zero-length matches
357
691
  if (match.index === rule.pattern.lastIndex) {
358
692
  rule.pattern.lastIndex++;
@@ -381,13 +715,26 @@ export function analyzeCode(file, content, focus = 'all') {
381
715
  score -= summary.info * 1;
382
716
  score -= summary.suggestions * 0.5;
383
717
  score = Math.max(0, Math.min(100, score));
384
- logger.info('Analysis complete', { file, issuesFound: issues.length, score });
718
+ // Collect unique quick fixes
719
+ const quickFixes = issues
720
+ .filter(i => i.quickFix)
721
+ .map(i => i.quickFix)
722
+ .filter((fix, idx, arr) => arr.findIndex(f => f.before === fix.before) === idx);
723
+ logger.info('Analysis complete', {
724
+ file,
725
+ issuesFound: issues.length,
726
+ score,
727
+ quickFixesAvailable: quickFixes.length,
728
+ rulesApplied
729
+ });
385
730
  return {
386
731
  file,
387
732
  language,
388
733
  issues,
389
734
  score: Math.round(score),
390
- summary
735
+ summary,
736
+ quickFixes: quickFixes.length > 0 ? quickFixes : undefined,
737
+ rulesApplied
391
738
  };
392
739
  }
393
740
  export function analyzeMultipleFiles(files, focus = 'all') {
@@ -402,13 +749,19 @@ export function analyzeMultipleFiles(files, focus = 'all') {
402
749
  info: results.reduce((sum, r) => sum + r.summary.info, 0),
403
750
  suggestions: results.reduce((sum, r) => sum + r.summary.suggestions, 0)
404
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
+ };
405
757
  return {
406
758
  files: results,
407
759
  overall: {
408
760
  totalFiles: results.length,
409
761
  totalIssues,
410
762
  averageScore,
411
- summary
763
+ summary,
764
+ rulesApplied
412
765
  }
413
766
  };
414
767
  }
@@ -416,6 +769,7 @@ export function formatAnalysisReport(result) {
416
769
  const lines = [];
417
770
  lines.push(`## Code Review: ${result.file}`);
418
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`);
419
773
  lines.push('');
420
774
  if (result.issues.length === 0) {
421
775
  lines.push('✅ No issues found!');
@@ -432,16 +786,80 @@ export function formatAnalysisReport(result) {
432
786
  const icon = issue.severity === 'error' ? '🔴' :
433
787
  issue.severity === 'warning' ? '🟡' :
434
788
  issue.severity === 'info' ? '🔵' : '💡';
435
- lines.push(`#### ${icon} [${issue.rule}] ${issue.message}`);
789
+ const sourceTag = issue.source !== 'builtin' ? ` [${issue.source}]` : '';
790
+ lines.push(`#### ${icon} [${issue.rule}]${sourceTag} ${issue.message}`);
436
791
  if (issue.line)
437
792
  lines.push(`- Line: ${issue.line}`);
438
793
  if (issue.code)
439
794
  lines.push(`- Code: \`${issue.code}\``);
440
795
  if (issue.suggestion)
441
796
  lines.push(`- 💡 ${issue.suggestion}`);
797
+ if (issue.quickFix) {
798
+ lines.push(`- 🔧 **Quick Fix:** ${issue.quickFix.description}`);
799
+ lines.push(` - Replace: \`${issue.quickFix.before}\``);
800
+ lines.push(` - With: \`${issue.quickFix.after}\``);
801
+ }
802
+ lines.push('');
803
+ }
804
+ // Add quick fixes summary at the end
805
+ if (result.quickFixes && result.quickFixes.length > 0) {
806
+ lines.push('### 🔧 Available Quick Fixes');
807
+ lines.push('');
808
+ for (const fix of result.quickFixes) {
809
+ lines.push(`- **${fix.description}**`);
810
+ lines.push(` \`${fix.before}\` → \`${fix.after}\``);
811
+ }
442
812
  lines.push('');
443
813
  }
444
814
  }
445
815
  return lines.join('\n');
446
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
+ }
447
865
  //# sourceMappingURL=codeAnalyzer.js.map