@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.
Files changed (38) 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 +6 -0
  10. package/dist/handlers/generate.d.ts.map +1 -1
  11. package/dist/handlers/generate.js +81 -5
  12. package/dist/handlers/generate.js.map +1 -1
  13. package/dist/handlers/health.d.ts +7 -0
  14. package/dist/handlers/health.d.ts.map +1 -1
  15. package/dist/handlers/health.js +151 -69
  16. package/dist/handlers/health.js.map +1 -1
  17. package/dist/handlers/review.d.ts +12 -0
  18. package/dist/handlers/review.d.ts.map +1 -1
  19. package/dist/handlers/review.js +220 -21
  20. package/dist/handlers/review.js.map +1 -1
  21. package/dist/services/analysisCache.d.ts +110 -0
  22. package/dist/services/analysisCache.d.ts.map +1 -0
  23. package/dist/services/analysisCache.js +233 -0
  24. package/dist/services/analysisCache.js.map +1 -0
  25. package/dist/services/codeAnalyzer.d.ts +73 -30
  26. package/dist/services/codeAnalyzer.d.ts.map +1 -1
  27. package/dist/services/codeAnalyzer.js +384 -36
  28. package/dist/services/codeAnalyzer.js.map +1 -1
  29. package/dist/services/conventionDetector.d.ts +40 -0
  30. package/dist/services/conventionDetector.d.ts.map +1 -0
  31. package/dist/services/conventionDetector.js +465 -0
  32. package/dist/services/conventionDetector.js.map +1 -0
  33. package/dist/services/cursorDirectory.d.ts +29 -2
  34. package/dist/services/cursorDirectory.d.ts.map +1 -1
  35. package/dist/services/cursorDirectory.js +260 -9
  36. package/dist/services/cursorDirectory.js.map +1 -1
  37. package/dist/utils/validation.d.ts +2 -2
  38. package/package.json +1 -1
@@ -1,19 +1,25 @@
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
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
- category: 'coding-standards',
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
- category: 'coding-standards',
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
- category: 'coding-standards',
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
- category: 'coding-standards',
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
- category: 'coding-standards',
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
- category: 'coding-standards',
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
- category: 'coding-standards',
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
- category: 'coding-standards',
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
- category: 'coding-standards',
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
- 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) {
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 = PATTERN_RULES.filter(rule => {
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', { file, issuesFound: issues.length, score, quickFixesAvailable: quickFixes.length });
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
- lines.push(`#### ${icon} [${issue.rule}] ${issue.message}`);
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