code-sentinel-mcp 0.1.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.
@@ -0,0 +1,801 @@
1
+ const patternMatchers = [
2
+ // Architectural patterns
3
+ {
4
+ id: 'CS-ARCH001',
5
+ name: 'Repository Pattern',
6
+ level: 'architectural',
7
+ patterns: [
8
+ /class\s+\w*Repository/g,
9
+ /interface\s+I?\w*Repository/g,
10
+ /(?:find|get|save|delete|update)(?:All|By|One)\s*\(/g
11
+ ],
12
+ description: 'Data access abstraction layer separating business logic from data persistence'
13
+ },
14
+ {
15
+ id: 'CS-ARCH002',
16
+ name: 'Service Layer',
17
+ level: 'architectural',
18
+ patterns: [
19
+ /class\s+\w*Service/g,
20
+ /(?:export\s+)?(?:const|function)\s+\w*Service/g
21
+ ],
22
+ description: 'Business logic encapsulation in dedicated service classes/modules'
23
+ },
24
+ {
25
+ id: 'CS-ARCH003',
26
+ name: 'Controller/Handler Pattern',
27
+ level: 'architectural',
28
+ patterns: [
29
+ /class\s+\w*Controller/g,
30
+ /(?:export\s+)?(?:const|function)\s+\w*(?:Controller|Handler)/g,
31
+ /@(?:Controller|Get|Post|Put|Delete)\s*\(/g
32
+ ],
33
+ description: 'HTTP request handling separated from business logic'
34
+ },
35
+ {
36
+ id: 'CS-ARCH004',
37
+ name: 'Middleware Pattern',
38
+ level: 'architectural',
39
+ patterns: [
40
+ /(?:app|router)\.use\s*\(/g,
41
+ /export\s+(?:const|function)\s+\w*Middleware/g,
42
+ /\(req,\s*res,\s*next\)/g
43
+ ],
44
+ description: 'Request/response pipeline with composable handlers'
45
+ },
46
+ // Design patterns
47
+ {
48
+ id: 'CS-DES001',
49
+ name: 'Factory Pattern',
50
+ level: 'design',
51
+ patterns: [
52
+ /(?:create|make|build)\w*\s*\([^)]*\)\s*(?::\s*\w+)?\s*(?:=>)?\s*\{?/g,
53
+ /class\s+\w*Factory/g,
54
+ /function\s+create\w+/g
55
+ ],
56
+ description: 'Object creation encapsulated in factory functions/classes'
57
+ },
58
+ {
59
+ id: 'CS-DES002',
60
+ name: 'Singleton Pattern',
61
+ level: 'design',
62
+ patterns: [
63
+ /static\s+(?:get)?[Ii]nstance/g,
64
+ /let\s+instance\s*[=:]/g,
65
+ /export\s+default\s+new\s+\w+\(\)/g
66
+ ],
67
+ description: 'Single instance shared across the application'
68
+ },
69
+ {
70
+ id: 'CS-DES003',
71
+ name: 'Observer/Event Pattern',
72
+ level: 'design',
73
+ patterns: [
74
+ /\.on\s*\(\s*['"`]\w+['"`]/g,
75
+ /\.emit\s*\(\s*['"`]\w+['"`]/g,
76
+ /addEventListener\s*\(/g,
77
+ /(?:subscribe|unsubscribe)\s*\(/g,
78
+ /new\s+(?:Event)?Emitter/g
79
+ ],
80
+ description: 'Pub/sub mechanism for loose coupling between components'
81
+ },
82
+ {
83
+ id: 'CS-DES004',
84
+ name: 'Strategy Pattern',
85
+ level: 'design',
86
+ patterns: [
87
+ /interface\s+\w*Strategy/g,
88
+ /type\s+\w*Strategy\s*=/g,
89
+ /strategies\s*(?::\s*(?:Record|Map|{))?\s*[=:]/g
90
+ ],
91
+ description: 'Interchangeable algorithms selected at runtime'
92
+ },
93
+ {
94
+ id: 'CS-DES005',
95
+ name: 'Builder Pattern',
96
+ level: 'design',
97
+ patterns: [
98
+ /class\s+\w*Builder/g,
99
+ /\.set\w+\([^)]+\)\s*\.\s*set\w+/g,
100
+ /return\s+this\s*;?\s*}\s*\w+\s*\(/g
101
+ ],
102
+ description: 'Step-by-step object construction with fluent interface'
103
+ },
104
+ {
105
+ id: 'CS-DES006',
106
+ name: 'Dependency Injection',
107
+ level: 'design',
108
+ patterns: [
109
+ /constructor\s*\(\s*(?:private|public|readonly)\s+\w+\s*:/g,
110
+ /@(?:Inject|Injectable|Service)\s*\(/g,
111
+ /\.register\s*\(\s*['"`]\w+['"`]/g
112
+ ],
113
+ description: 'Dependencies provided externally rather than created internally'
114
+ },
115
+ // Code-level patterns
116
+ {
117
+ id: 'CS-CODE001',
118
+ name: 'Async/Await',
119
+ level: 'code',
120
+ patterns: [
121
+ /async\s+(?:function|\w+\s*=|(?:\w+\s*)?=>)/g,
122
+ /await\s+/g
123
+ ],
124
+ description: 'Modern asynchronous code handling'
125
+ },
126
+ {
127
+ id: 'CS-CODE002',
128
+ name: 'Promise Chains',
129
+ level: 'code',
130
+ patterns: [
131
+ /\.then\s*\(\s*(?:\w+\s*=>|\([^)]*\)\s*=>|function)/g,
132
+ /\.catch\s*\(\s*(?:\w+\s*=>|\([^)]*\)\s*=>|function)/g
133
+ ],
134
+ description: 'Promise-based asynchronous handling with chaining'
135
+ },
136
+ {
137
+ id: 'CS-CODE003',
138
+ name: 'Callback Pattern',
139
+ level: 'code',
140
+ patterns: [
141
+ /function\s*\([^)]*,\s*(?:callback|cb|done|next)\s*\)/g,
142
+ /\(\s*(?:err|error)\s*,\s*(?:result|data|response)\s*\)/g
143
+ ],
144
+ description: 'Traditional callback-based async handling'
145
+ },
146
+ {
147
+ id: 'CS-CODE004',
148
+ name: 'Result/Either Pattern',
149
+ level: 'code',
150
+ patterns: [
151
+ /(?:Result|Either|Ok|Err|Success|Failure)<\w+/g,
152
+ /return\s+\{\s*(?:success|ok|error|data)\s*:/g,
153
+ /\.isOk\(\)|\.isErr\(\)|\.unwrap\(\)/g
154
+ ],
155
+ description: 'Explicit success/failure return types instead of exceptions'
156
+ },
157
+ {
158
+ id: 'CS-CODE005',
159
+ name: 'Guard Clauses',
160
+ level: 'code',
161
+ patterns: [
162
+ /if\s*\([^)]+\)\s*(?:return|throw)\s*[^{]/g,
163
+ /if\s*\(\s*!\w+\s*\)\s*(?:return|throw)/g
164
+ ],
165
+ description: 'Early returns for edge cases before main logic'
166
+ },
167
+ {
168
+ id: 'CS-CODE006',
169
+ name: 'Null Object Pattern',
170
+ level: 'code',
171
+ patterns: [
172
+ /(?:Null|Empty|Default|Noop)\w+\s*(?:implements|extends|=)/g,
173
+ /\?\?\s*\{\s*\w+\s*:\s*\(\)\s*=>/g
174
+ ],
175
+ description: 'Default objects instead of null checks'
176
+ }
177
+ ];
178
+ const inconsistencyDetectors = [
179
+ {
180
+ id: 'CS-INC001',
181
+ title: 'Mixed Async Styles',
182
+ level: 'code',
183
+ variants: [
184
+ { name: 'async/await', pattern: /async\s+\w|await\s+/g },
185
+ { name: 'Promise chains', pattern: /\.then\s*\(/g },
186
+ { name: 'Callbacks', pattern: /,\s*(?:callback|cb)\s*\)|,\s*\(err,/g }
187
+ ],
188
+ recommendation: 'Standardize on async/await for consistency and readability. Convert Promise chains with await and refactor callbacks to return Promises.'
189
+ },
190
+ {
191
+ id: 'CS-INC002',
192
+ title: 'Mixed Error Handling',
193
+ level: 'code',
194
+ variants: [
195
+ { name: 'try/catch', pattern: /try\s*\{[\s\S]*?catch/g },
196
+ { name: '.catch()', pattern: /\.catch\s*\(/g },
197
+ { name: 'Error callbacks', pattern: /\(\s*err(?:or)?\s*(?:,|\))/g },
198
+ { name: 'Result types', pattern: /(?:Result|Either|Ok|Err)</g }
199
+ ],
200
+ recommendation: 'Choose one primary error handling strategy. Result types are explicit, try/catch is familiar. Avoid mixing.'
201
+ },
202
+ {
203
+ id: 'CS-INC003',
204
+ title: 'Mixed Export Styles',
205
+ level: 'code',
206
+ variants: [
207
+ { name: 'Named exports', pattern: /export\s+(?:const|function|class|interface|type)\s+\w+/g },
208
+ { name: 'Default exports', pattern: /export\s+default/g },
209
+ { name: 'module.exports', pattern: /module\.exports\s*=/g }
210
+ ],
211
+ recommendation: 'Prefer named exports for better tree-shaking and IDE support. Reserve default exports for main entry points only.'
212
+ },
213
+ {
214
+ id: 'CS-INC004',
215
+ title: 'Mixed Null Handling',
216
+ level: 'code',
217
+ variants: [
218
+ { name: 'Optional chaining (?.)', pattern: /\?\./g },
219
+ { name: 'Nullish coalescing (??)', pattern: /\?\?/g },
220
+ { name: 'Logical OR (||)', pattern: /\|\|\s*(?:null|undefined|''|""|\[\]|\{\})/g },
221
+ { name: 'Explicit checks', pattern: /(?:!==?|===?)\s*(?:null|undefined)/g }
222
+ ],
223
+ recommendation: 'Use ?. and ?? consistently. They handle null/undefined specifically, while || treats all falsy values the same.'
224
+ },
225
+ {
226
+ id: 'CS-INC005',
227
+ title: 'Mixed Function Styles',
228
+ level: 'code',
229
+ variants: [
230
+ { name: 'Arrow functions', pattern: /(?:const|let)\s+\w+\s*=\s*(?:\([^)]*\)|[^=])\s*=>/g },
231
+ { name: 'Function declarations', pattern: /function\s+\w+\s*\(/g },
232
+ { name: 'Method shorthand', pattern: /\w+\s*\([^)]*\)\s*\{/g }
233
+ ],
234
+ recommendation: 'Use arrow functions for callbacks and short functions. Use declarations for hoisting needs and named stack traces.'
235
+ }
236
+ ];
237
+ const suggestionRules = [
238
+ {
239
+ id: 'CS-SUG001',
240
+ title: 'Consider Result Pattern for Error Handling',
241
+ level: 'code',
242
+ detect: /return\s+null\s*;|return\s+undefined\s*;/g,
243
+ notPresent: /Result<|Either<|Ok\(|Err\(/g,
244
+ condition: 'present',
245
+ threshold: 2,
246
+ suggestion: {
247
+ name: 'Result/Either Pattern',
248
+ description: 'Explicit success/failure return types that force callers to handle both cases',
249
+ why: 'Returning null/undefined on errors makes it impossible to distinguish "not found" from "operation failed"',
250
+ benefits: [
251
+ 'Callers must handle error cases explicitly',
252
+ 'Type system catches unhandled errors',
253
+ 'Clear distinction between "no data" and "error"',
254
+ 'Self-documenting function signatures'
255
+ ],
256
+ tradeoffs: [
257
+ 'More verbose return type handling',
258
+ 'Requires consistent adoption across codebase',
259
+ 'Learning curve for team'
260
+ ],
261
+ example: `// Before
262
+ function getUser(id: string): User | null {
263
+ if (error) return null;
264
+ return user;
265
+ }
266
+
267
+ // After
268
+ type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };
269
+
270
+ function getUser(id: string): Result<User, 'not_found' | 'db_error'> {
271
+ if (notFound) return { ok: false, error: 'not_found' };
272
+ if (dbError) return { ok: false, error: 'db_error' };
273
+ return { ok: true, value: user };
274
+ }`
275
+ },
276
+ action: {
277
+ type: 'consider',
278
+ description: 'Introduce Result type for functions that can fail',
279
+ steps: [
280
+ 'Define a Result<T, E> type in a shared types file',
281
+ 'Update function return types to use Result',
282
+ 'Replace return null with { ok: false, error: reason }',
283
+ 'Update callers to check result.ok before accessing value'
284
+ ],
285
+ codeChange: {
286
+ before: 'return null;',
287
+ after: "return { ok: false, error: 'operation_failed' };"
288
+ }
289
+ }
290
+ },
291
+ {
292
+ id: 'CS-SUG002',
293
+ title: 'Extract Factory for Complex Object Creation',
294
+ level: 'design',
295
+ detect: /new\s+\w+\(\s*\{[\s\S]{100,}?\}\s*\)/g,
296
+ condition: 'present',
297
+ threshold: 2,
298
+ suggestion: {
299
+ name: 'Factory Pattern',
300
+ description: 'Encapsulate complex object creation in dedicated factory functions',
301
+ why: 'Large inline object construction is hard to test and reuse',
302
+ benefits: [
303
+ 'Centralized object creation logic',
304
+ 'Easier to test with mock factories',
305
+ 'Reusable default configurations',
306
+ 'Clear creation intent in code'
307
+ ],
308
+ tradeoffs: [
309
+ 'Additional indirection',
310
+ 'More files/functions to maintain'
311
+ ],
312
+ example: `// Before
313
+ const user = new User({
314
+ name: data.name,
315
+ email: data.email,
316
+ role: data.role || 'user',
317
+ createdAt: new Date(),
318
+ // ... many more fields
319
+ });
320
+
321
+ // After
322
+ function createUser(data: CreateUserInput): User {
323
+ return new User({
324
+ ...defaultUserConfig,
325
+ ...data,
326
+ createdAt: new Date(),
327
+ });
328
+ }
329
+
330
+ const user = createUser(data);`
331
+ },
332
+ action: {
333
+ type: 'refactor',
334
+ description: 'Extract factory function for complex object creation',
335
+ steps: [
336
+ 'Identify the class being instantiated',
337
+ 'Create a createXxx function that encapsulates the construction',
338
+ 'Move default values and transformations into the factory',
339
+ 'Replace new Xxx({...}) calls with createXxx(...)'
340
+ ]
341
+ }
342
+ },
343
+ {
344
+ id: 'CS-SUG003',
345
+ title: 'Add Repository Layer for Data Access',
346
+ level: 'architectural',
347
+ detect: /(?:prisma|db|knex|sequelize|mongoose)\.\w+\.\w+/g,
348
+ notPresent: /Repository/g,
349
+ condition: 'present',
350
+ threshold: 3,
351
+ suggestion: {
352
+ name: 'Repository Pattern',
353
+ description: 'Abstract data access behind repository interfaces',
354
+ why: 'Direct ORM/database calls scattered in business logic make testing hard and create tight coupling',
355
+ benefits: [
356
+ 'Business logic independent of data layer',
357
+ 'Easy to mock for testing',
358
+ 'Can swap databases without changing business code',
359
+ 'Centralizes query logic'
360
+ ],
361
+ tradeoffs: [
362
+ 'Additional abstraction layer',
363
+ 'More boilerplate code',
364
+ 'Can be overkill for simple CRUD apps'
365
+ ],
366
+ example: `// Before (in service)
367
+ async function getActiveUsers() {
368
+ return prisma.user.findMany({ where: { active: true } });
369
+ }
370
+
371
+ // After
372
+ // userRepository.ts
373
+ interface UserRepository {
374
+ findActive(): Promise<User[]>;
375
+ }
376
+
377
+ class PrismaUserRepository implements UserRepository {
378
+ async findActive() {
379
+ return prisma.user.findMany({ where: { active: true } });
380
+ }
381
+ }
382
+
383
+ // userService.ts
384
+ class UserService {
385
+ constructor(private userRepo: UserRepository) {}
386
+
387
+ async getActiveUsers() {
388
+ return this.userRepo.findActive();
389
+ }
390
+ }`
391
+ },
392
+ action: {
393
+ type: 'refactor',
394
+ description: 'Create repository interfaces and implementations',
395
+ steps: [
396
+ 'Identify all direct database calls in services/controllers',
397
+ 'Group related queries by entity (User, Order, etc.)',
398
+ 'Create a Repository interface for each entity',
399
+ 'Implement the interface with your ORM',
400
+ 'Inject repositories into services via constructor'
401
+ ]
402
+ }
403
+ },
404
+ {
405
+ id: 'CS-SUG004',
406
+ title: 'Use Guard Clauses for Cleaner Logic',
407
+ level: 'code',
408
+ detect: /if\s*\([^)]+\)\s*\{[\s\S]{50,}?\}\s*else\s*\{[\s\S]{10,}?\}/g,
409
+ condition: 'present',
410
+ threshold: 2,
411
+ suggestion: {
412
+ name: 'Guard Clauses',
413
+ description: 'Early returns for edge cases to reduce nesting and clarify happy path',
414
+ why: 'Deeply nested if-else blocks are hard to follow and maintain',
415
+ benefits: [
416
+ 'Reduced cognitive load',
417
+ 'Clear separation of edge cases and main logic',
418
+ 'Easier to add new conditions',
419
+ 'More linear code flow'
420
+ ],
421
+ tradeoffs: [
422
+ 'Multiple return points (some style guides discourage)',
423
+ 'May not work well with resource cleanup needs'
424
+ ],
425
+ example: `// Before
426
+ function processOrder(order) {
427
+ if (order) {
428
+ if (order.items.length > 0) {
429
+ if (order.payment) {
430
+ // actual logic here
431
+ return result;
432
+ } else {
433
+ throw new Error('No payment');
434
+ }
435
+ } else {
436
+ throw new Error('Empty order');
437
+ }
438
+ } else {
439
+ throw new Error('No order');
440
+ }
441
+ }
442
+
443
+ // After
444
+ function processOrder(order) {
445
+ if (!order) throw new Error('No order');
446
+ if (order.items.length === 0) throw new Error('Empty order');
447
+ if (!order.payment) throw new Error('No payment');
448
+
449
+ // actual logic here - no nesting
450
+ return result;
451
+ }`
452
+ },
453
+ action: {
454
+ type: 'refactor',
455
+ description: 'Flatten nested conditionals with early returns',
456
+ steps: [
457
+ 'Identify the deepest nested condition (usually the happy path)',
458
+ 'Invert outer conditions and return/throw early',
459
+ 'Move main logic to the end, unnested',
460
+ 'Remove else blocks that are no longer needed'
461
+ ]
462
+ }
463
+ },
464
+ {
465
+ id: 'CS-SUG005',
466
+ title: 'Consider Strategy Pattern for Conditional Logic',
467
+ level: 'design',
468
+ detect: /switch\s*\([^)]+\)\s*\{(?:[^}]*case\s+['"`]?\w+['"`]?\s*:){4,}/g,
469
+ condition: 'present',
470
+ suggestion: {
471
+ name: 'Strategy Pattern',
472
+ description: 'Replace complex switch/if-else with strategy objects',
473
+ why: 'Large switch statements violate Open/Closed principle - adding new cases requires modifying existing code',
474
+ benefits: [
475
+ 'Add new behaviors without changing existing code',
476
+ 'Each strategy is independently testable',
477
+ 'Strategies can be swapped at runtime',
478
+ 'Cleaner separation of concerns'
479
+ ],
480
+ tradeoffs: [
481
+ 'More files/classes',
482
+ 'Overhead for simple cases',
483
+ 'Need to manage strategy registration'
484
+ ],
485
+ example: `// Before
486
+ function calculatePrice(type, amount) {
487
+ switch (type) {
488
+ case 'standard': return amount;
489
+ case 'premium': return amount * 0.9;
490
+ case 'vip': return amount * 0.8;
491
+ case 'enterprise': return amount * 0.7;
492
+ // adding new type = modify this function
493
+ }
494
+ }
495
+
496
+ // After
497
+ const pricingStrategies = {
498
+ standard: (amount) => amount,
499
+ premium: (amount) => amount * 0.9,
500
+ vip: (amount) => amount * 0.8,
501
+ enterprise: (amount) => amount * 0.7,
502
+ };
503
+
504
+ function calculatePrice(type, amount) {
505
+ const strategy = pricingStrategies[type];
506
+ if (!strategy) throw new Error('Unknown type');
507
+ return strategy(amount);
508
+ }
509
+
510
+ // Adding new type = just add to strategies object`
511
+ },
512
+ action: {
513
+ type: 'refactor',
514
+ description: 'Extract switch cases into strategy object',
515
+ steps: [
516
+ 'Create a strategies object/map',
517
+ 'Move each case logic into a strategy function',
518
+ 'Replace switch with strategy lookup and execution',
519
+ 'Add error handling for unknown strategies'
520
+ ]
521
+ }
522
+ }
523
+ ];
524
+ function detectPatterns(code) {
525
+ const detected = [];
526
+ const lines = code.split('\n');
527
+ for (const matcher of patternMatchers) {
528
+ const locations = [];
529
+ for (const pattern of matcher.patterns) {
530
+ pattern.lastIndex = 0;
531
+ let match;
532
+ while ((match = pattern.exec(code)) !== null) {
533
+ const beforeMatch = code.substring(0, match.index);
534
+ const lineNumber = beforeMatch.split('\n').length;
535
+ locations.push({
536
+ line: lineNumber,
537
+ code: lines[lineNumber - 1]?.trim() || ''
538
+ });
539
+ if (!pattern.global)
540
+ break;
541
+ }
542
+ }
543
+ if (locations.length > 0) {
544
+ const uniqueLocations = locations.filter((loc, idx, arr) => arr.findIndex(l => l.line === loc.line) === idx);
545
+ detected.push({
546
+ id: matcher.id,
547
+ name: matcher.name,
548
+ level: matcher.level,
549
+ confidence: uniqueLocations.length >= 3 ? 'high' : uniqueLocations.length >= 2 ? 'medium' : 'low',
550
+ description: matcher.description,
551
+ locations: uniqueLocations.slice(0, 5)
552
+ });
553
+ }
554
+ }
555
+ return detected;
556
+ }
557
+ function detectInconsistencies(code) {
558
+ const inconsistencies = [];
559
+ const lines = code.split('\n');
560
+ for (const detector of inconsistencyDetectors) {
561
+ const variants = [];
562
+ for (const variant of detector.variants) {
563
+ variant.pattern.lastIndex = 0;
564
+ const matches = [];
565
+ let match;
566
+ while ((match = variant.pattern.exec(code)) !== null) {
567
+ const beforeMatch = code.substring(0, match.index);
568
+ const lineNumber = beforeMatch.split('\n').length;
569
+ matches.push({
570
+ line: lineNumber,
571
+ code: lines[lineNumber - 1]?.trim() || ''
572
+ });
573
+ if (!variant.pattern.global)
574
+ break;
575
+ }
576
+ if (matches.length > 0) {
577
+ variants.push({
578
+ approach: variant.name,
579
+ locations: matches.slice(0, 3),
580
+ count: matches.length
581
+ });
582
+ }
583
+ }
584
+ if (variants.length >= 2) {
585
+ inconsistencies.push({
586
+ id: detector.id,
587
+ title: detector.title,
588
+ level: detector.level,
589
+ severity: variants.length >= 3 ? 'medium' : 'low',
590
+ description: `Found ${variants.length} different approaches being used`,
591
+ variants,
592
+ recommendation: detector.recommendation
593
+ });
594
+ }
595
+ }
596
+ return inconsistencies;
597
+ }
598
+ function generateSuggestions(code, detected) {
599
+ const suggestions = [];
600
+ const lines = code.split('\n');
601
+ for (const rule of suggestionRules) {
602
+ rule.detect.lastIndex = 0;
603
+ const matches = code.match(rule.detect) || [];
604
+ let shouldSuggest = false;
605
+ if (rule.condition === 'present' && matches.length >= (rule.threshold || 1)) {
606
+ if (rule.notPresent) {
607
+ rule.notPresent.lastIndex = 0;
608
+ shouldSuggest = !rule.notPresent.test(code);
609
+ }
610
+ else {
611
+ shouldSuggest = true;
612
+ }
613
+ }
614
+ if (shouldSuggest) {
615
+ rule.detect.lastIndex = 0;
616
+ const firstMatch = rule.detect.exec(code);
617
+ const lineNumber = firstMatch
618
+ ? code.substring(0, firstMatch.index).split('\n').length
619
+ : undefined;
620
+ suggestions.push({
621
+ id: rule.id,
622
+ title: rule.title,
623
+ level: rule.level,
624
+ priority: matches.length >= 5 ? 'high' : matches.length >= 3 ? 'medium' : 'low',
625
+ currentApproach: {
626
+ name: 'Current Implementation',
627
+ description: `Found ${matches.length} instance(s) of this pattern`,
628
+ example: firstMatch ? firstMatch[0] : '',
629
+ line: lineNumber
630
+ },
631
+ suggestedApproach: rule.suggestion,
632
+ action: rule.action
633
+ });
634
+ }
635
+ }
636
+ return suggestions;
637
+ }
638
+ function generateActionItems(inconsistencies, suggestions) {
639
+ const items = [];
640
+ for (const inc of inconsistencies) {
641
+ const dominant = inc.variants.reduce((a, b) => a.count > b.count ? a : b);
642
+ items.push({
643
+ id: `ACT-${inc.id}`,
644
+ priority: inc.severity === 'high' ? 1 : inc.severity === 'medium' ? 2 : 3,
645
+ type: 'fix_inconsistency',
646
+ title: `Standardize: ${inc.title}`,
647
+ reason: inc.description,
648
+ effort: inc.variants.reduce((sum, v) => sum + v.count, 0) > 10 ? 'high' : 'medium',
649
+ steps: [
650
+ {
651
+ order: 1,
652
+ instruction: `Adopt "${dominant.approach}" as the standard (most commonly used: ${dominant.count} instances)`
653
+ },
654
+ ...inc.variants
655
+ .filter(v => v.approach !== dominant.approach)
656
+ .map((v, i) => ({
657
+ order: i + 2,
658
+ instruction: `Convert ${v.count} instance(s) of "${v.approach}" to "${dominant.approach}"`
659
+ }))
660
+ ],
661
+ acceptPrompt: `Shall I refactor to use "${dominant.approach}" consistently?`
662
+ });
663
+ }
664
+ for (const sug of suggestions) {
665
+ items.push({
666
+ id: `ACT-${sug.id}`,
667
+ priority: sug.priority === 'high' ? 1 : sug.priority === 'medium' ? 2 : 3,
668
+ type: sug.action.type === 'refactor' ? 'refactor' : 'implement_pattern',
669
+ title: sug.title,
670
+ reason: sug.suggestedApproach.why,
671
+ effort: sug.action.steps.length > 4 ? 'high' : sug.action.steps.length > 2 ? 'medium' : 'low',
672
+ steps: sug.action.steps.map((step, i) => ({
673
+ order: i + 1,
674
+ instruction: step,
675
+ code: i === 0 && sug.action.codeChange ? {
676
+ action: 'replace',
677
+ target: sug.action.codeChange.before,
678
+ content: sug.action.codeChange.after
679
+ } : undefined
680
+ })),
681
+ acceptPrompt: `Would you like me to implement the ${sug.suggestedApproach.name}?`
682
+ });
683
+ }
684
+ return items.sort((a, b) => a.priority - b.priority);
685
+ }
686
+ export function analyzePatterns(code, filename, level = 'all') {
687
+ let detected = detectPatterns(code);
688
+ let inconsistencies = detectInconsistencies(code);
689
+ if (level !== 'all') {
690
+ detected = detected.filter(p => p.level === level);
691
+ inconsistencies = inconsistencies.filter(i => i.level === level);
692
+ }
693
+ const suggestions = generateSuggestions(code, detected)
694
+ .filter(s => level === 'all' || s.level === level);
695
+ const actionItems = generateActionItems(inconsistencies, suggestions);
696
+ const totalVariants = inconsistencies.reduce((sum, i) => sum + i.variants.length, 0);
697
+ const overallConsistency = inconsistencies.length === 0 ? 'high' :
698
+ inconsistencies.length <= 2 && totalVariants <= 6 ? 'medium' : 'low';
699
+ return {
700
+ level,
701
+ summary: {
702
+ patternsDetected: detected.length,
703
+ inconsistencies: inconsistencies.length,
704
+ suggestions: suggestions.length,
705
+ overallConsistency
706
+ },
707
+ detectedPatterns: detected,
708
+ inconsistencies,
709
+ suggestions,
710
+ actionItems
711
+ };
712
+ }
713
+ export function inferLevelFromQuery(query) {
714
+ const lower = query.toLowerCase();
715
+ if (/architect|structure|layer|module|organization/.test(lower)) {
716
+ return 'architectural';
717
+ }
718
+ if (/design pattern|factory|singleton|observer|strategy|builder|injection/.test(lower)) {
719
+ return 'design';
720
+ }
721
+ if (/code style|async|error handling|naming|function|variable/.test(lower)) {
722
+ return 'code';
723
+ }
724
+ if (/all|everything|full|complete/.test(lower)) {
725
+ return 'all';
726
+ }
727
+ return null;
728
+ }
729
+ export function analyzeDesignPatterns(code, filename) {
730
+ // Get design-level patterns only
731
+ const detected = detectPatterns(code).filter(p => p.level === 'design');
732
+ const suggestions = generateSuggestions(code, detected).filter(s => s.level === 'design');
733
+ // Build action items from suggestions
734
+ const actionItems = suggestions.map(sug => ({
735
+ id: `ACT-${sug.id}`,
736
+ priority: sug.priority === 'high' ? 1 : sug.priority === 'medium' ? 2 : 3,
737
+ type: sug.action.type === 'refactor' ? 'refactor' :
738
+ sug.action.type === 'consider' ? 'consider' : 'implement_pattern',
739
+ title: sug.title,
740
+ reason: sug.suggestedApproach.why,
741
+ effort: sug.action.steps.length > 4 ? 'high' :
742
+ sug.action.steps.length > 2 ? 'medium' : 'low',
743
+ steps: sug.action.steps.map((step, i) => ({
744
+ order: i + 1,
745
+ instruction: step
746
+ })),
747
+ acceptPrompt: `Would you like me to implement the ${sug.suggestedApproach.name}?`
748
+ }));
749
+ // Identify which design patterns are present and missing
750
+ const designPatternNames = patternMatchers
751
+ .filter(p => p.level === 'design')
752
+ .map(p => p.name);
753
+ const detectedNames = detected.map(d => d.name);
754
+ const missingPatterns = designPatternNames.filter(name => !detectedNames.includes(name));
755
+ // Find dominant patterns (high confidence or multiple locations)
756
+ const dominantPatterns = detected
757
+ .filter(p => p.confidence === 'high' || p.locations.length >= 3)
758
+ .map(p => p.name);
759
+ return {
760
+ patterns: detected.map(p => ({
761
+ id: p.id,
762
+ name: p.name,
763
+ confidence: p.confidence,
764
+ description: p.description,
765
+ locations: p.locations,
766
+ relatedPatterns: getRelatedPatterns(p.name)
767
+ })),
768
+ suggestions: suggestions.map(s => ({
769
+ id: s.id,
770
+ title: s.title,
771
+ priority: s.priority,
772
+ currentApproach: s.currentApproach.name,
773
+ suggestedPattern: s.suggestedApproach.name,
774
+ why: s.suggestedApproach.why,
775
+ benefits: s.suggestedApproach.benefits,
776
+ tradeoffs: s.suggestedApproach.tradeoffs,
777
+ example: s.suggestedApproach.example
778
+ })),
779
+ actionItems,
780
+ summary: {
781
+ patternsDetected: detected.length,
782
+ suggestionsCount: suggestions.length,
783
+ dominantPatterns,
784
+ missingPatterns
785
+ }
786
+ };
787
+ }
788
+ function getRelatedPatterns(patternName) {
789
+ const relationships = {
790
+ 'Factory Pattern': ['Builder Pattern', 'Singleton Pattern'],
791
+ 'Singleton Pattern': ['Factory Pattern', 'Dependency Injection'],
792
+ 'Observer/Event Pattern': ['Strategy Pattern'],
793
+ 'Strategy Pattern': ['Factory Pattern', 'Dependency Injection'],
794
+ 'Builder Pattern': ['Factory Pattern'],
795
+ 'Dependency Injection': ['Factory Pattern', 'Strategy Pattern']
796
+ };
797
+ return relationships[patternName] || [];
798
+ }
799
+ export function formatDesignAnalysis(result) {
800
+ return JSON.stringify(result, null, 2);
801
+ }