@merlean/analyzer 2.0.0 → 2.2.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 (2) hide show
  1. package/lib/analyzer.js +431 -106
  2. package/package.json +1 -1
package/lib/analyzer.js CHANGED
@@ -1,66 +1,107 @@
1
1
  /**
2
- * Frontend-focused Codebase Scanner
2
+ * Framework-Agnostic Codebase Scanner
3
3
  *
4
- * Scans FRONTEND code to learn how it communicates with the backend.
5
- * Extracts: fetch(), axios, $.ajax, API calls, form submissions
4
+ * Scans ANY codebase (frontend or backend) to extract API patterns:
5
+ * - Express/Fastify/Koa/Hapi route definitions
6
+ * - NestJS decorators (@Get, @Post, @Body, @Query, etc.)
7
+ * - Frontend HTTP calls (fetch, axios, HttpClient, etc.)
8
+ * - Swagger/OpenAPI annotations
9
+ * - Request body schemas and query parameters
10
+ * - TypeScript interfaces/DTOs
11
+ * - Validation schemas (Zod, Joi, class-validator)
6
12
  *
7
- * This is what the bot needs - it runs in the browser and should know
8
- * what API calls the frontend already makes.
13
+ * The goal is to understand what APIs exist, their parameters,
14
+ * and body structures - regardless of framework or code style.
9
15
  */
10
16
 
11
17
  const fs = require('fs');
12
18
  const path = require('path');
13
19
  const { glob } = require('glob');
14
20
 
15
- // Frontend file patterns (prioritize frontend code)
16
- const FRONTEND_PATTERNS = [
21
+ // File patterns to scan (be inclusive)
22
+ const FILE_PATTERNS = [
17
23
  '**/*.js',
18
24
  '**/*.jsx',
19
25
  '**/*.ts',
20
26
  '**/*.tsx',
21
27
  '**/*.vue',
22
28
  '**/*.svelte',
23
- '**/app.js',
24
- '**/main.js',
25
- '**/index.js',
26
- '**/*api*.js',
27
- '**/*service*.js',
28
- '**/*fetch*.js',
29
- '**/*http*.js'
29
+ '**/*.mjs',
30
+ '**/*.cjs'
30
31
  ];
31
32
 
32
- // Directories to ignore
33
+ // Only ignore truly irrelevant directories
33
34
  const IGNORE_PATTERNS = [
34
35
  '**/node_modules/**',
35
36
  '**/vendor/**',
36
37
  '**/.git/**',
37
38
  '**/dist/**',
38
39
  '**/build/**',
40
+ '**/coverage/**',
39
41
  '**/__pycache__/**',
40
42
  '**/venv/**',
41
43
  '**/*.min.js',
42
44
  '**/*.map',
43
- '**/server.js', // Skip backend files
44
- '**/server/**',
45
- '**/backend/**',
46
- '**/api/**', // Skip backend API folders
47
- '**/controllers/**'
45
+ '**/*.d.ts', // TypeScript declaration files
46
+ '**/*.spec.ts', // Test files
47
+ '**/*.spec.js',
48
+ '**/*.test.ts',
49
+ '**/*.test.js',
50
+ '**/__tests__/**',
51
+ '**/__mocks__/**'
48
52
  ];
49
53
 
50
- // Keywords to prioritize files
51
- const PRIORITY_KEYWORDS = [
52
- 'fetch', 'axios', 'api', 'service', 'http', 'request',
53
- 'ajax', 'client', 'frontend', 'app', 'main', 'store'
54
+ // Files that are highly likely to contain API definitions
55
+ const HIGH_PRIORITY_PATTERNS = [
56
+ /routes?\.ts$/i,
57
+ /routes?\.js$/i,
58
+ /router\.ts$/i,
59
+ /router\.js$/i,
60
+ /controller\.ts$/i,
61
+ /controller\.js$/i,
62
+ /\.controller\.ts$/i,
63
+ /\.controller\.js$/i,
64
+ /service\.ts$/i,
65
+ /service\.js$/i,
66
+ /\.service\.ts$/i,
67
+ /\.service\.js$/i,
68
+ /api\.ts$/i,
69
+ /api\.js$/i,
70
+ /endpoints?\.ts$/i,
71
+ /endpoints?\.js$/i,
72
+ /http\.ts$/i,
73
+ /http\.js$/i,
74
+ /client\.ts$/i,
75
+ /client\.js$/i,
76
+ /dto\.ts$/i,
77
+ /\.dto\.ts$/i,
78
+ /interfaces?\.ts$/i,
79
+ /types?\.ts$/i,
80
+ /schema\.ts$/i,
81
+ /schemas?\.ts$/i,
82
+ /validation\.ts$/i
83
+ ];
84
+
85
+ // Medium priority - might contain API patterns
86
+ const MEDIUM_PRIORITY_PATTERNS = [
87
+ /index\.ts$/i,
88
+ /index\.js$/i,
89
+ /app\.ts$/i,
90
+ /app\.js$/i,
91
+ /main\.ts$/i,
92
+ /main\.js$/i,
93
+ /server\.ts$/i,
94
+ /server\.js$/i
54
95
  ];
55
96
 
56
97
  /**
57
- * Scan codebase and collect frontend API patterns
98
+ * Scan codebase and collect API patterns from any framework
58
99
  */
59
100
  async function scanCodebase(codebasePath) {
60
- console.log(' Scanning frontend files...');
101
+ console.log(' Scanning files...');
61
102
 
62
- // Get files to scan
63
- const files = await glob(FRONTEND_PATTERNS, {
103
+ // Get all matching files
104
+ const files = await glob(FILE_PATTERNS, {
64
105
  cwd: codebasePath,
65
106
  ignore: IGNORE_PATTERNS,
66
107
  absolute: true
@@ -68,23 +109,34 @@ async function scanCodebase(codebasePath) {
68
109
 
69
110
  console.log(` Found ${files.length} files`);
70
111
 
71
- // Prioritize frontend-focused files
72
- const prioritizedFiles = prioritizeFiles(files, codebasePath);
73
- const filesToAnalyze = prioritizedFiles.slice(0, 30); // Fewer files, but more content
112
+ // Categorize and prioritize files
113
+ const { highPriority, mediumPriority, other } = categorizeFiles(files, codebasePath);
114
+
115
+ console.log(` High priority: ${highPriority.length}, Medium: ${mediumPriority.length}, Other: ${other.length}`);
116
+
117
+ // Analyze high priority files first (routes, controllers, services, DTOs)
118
+ // Then medium priority, then scan others for API patterns
119
+ const filesToAnalyze = [
120
+ ...highPriority,
121
+ ...mediumPriority.slice(0, 20),
122
+ ...other.slice(0, 50)
123
+ ];
74
124
 
75
- console.log(` Analyzing ${filesToAnalyze.length} frontend files...`);
125
+ console.log(` Analyzing ${filesToAnalyze.length} files for API patterns...`);
76
126
 
77
- // Read and extract API patterns from files
78
127
  const fileContents = [];
128
+ let filesWithPatterns = 0;
129
+
79
130
  for (const file of filesToAnalyze) {
80
131
  try {
81
132
  const content = fs.readFileSync(file, 'utf-8');
82
133
  const relativePath = path.relative(codebasePath, file);
83
134
 
84
- // Extract API calls from the file
85
- const extracted = extractApiPatterns(content, relativePath);
135
+ // Extract API patterns from the file
136
+ const extracted = extractApiPatterns(content, relativePath, file);
86
137
 
87
- if (extracted.hasApiCalls) {
138
+ if (extracted.hasApiPatterns) {
139
+ filesWithPatterns++;
88
140
  fileContents.push({
89
141
  path: relativePath,
90
142
  content: extracted.content
@@ -95,61 +147,367 @@ async function scanCodebase(codebasePath) {
95
147
  }
96
148
  }
97
149
 
98
- console.log(` Found API patterns in ${fileContents.length} files`);
150
+ console.log(` Found API patterns in ${filesWithPatterns} files`);
99
151
 
100
152
  return fileContents;
101
153
  }
102
154
 
103
155
  /**
104
- * Extract API call patterns from file content
156
+ * Categorize files by priority
157
+ */
158
+ function categorizeFiles(files, basePath) {
159
+ const highPriority = [];
160
+ const mediumPriority = [];
161
+ const other = [];
162
+
163
+ for (const file of files) {
164
+ const relativePath = path.relative(basePath, file);
165
+
166
+ if (HIGH_PRIORITY_PATTERNS.some(p => p.test(relativePath))) {
167
+ highPriority.push(file);
168
+ } else if (MEDIUM_PRIORITY_PATTERNS.some(p => p.test(relativePath))) {
169
+ mediumPriority.push(file);
170
+ } else {
171
+ other.push(file);
172
+ }
173
+ }
174
+
175
+ return { highPriority, mediumPriority, other };
176
+ }
177
+
178
+ /**
179
+ * Extract Swagger/OpenAPI documentation blocks
180
+ */
181
+ function extractSwaggerBlocks(content) {
182
+ const swaggerBlocks = [];
183
+
184
+ // Match JSDoc blocks with @swagger
185
+ const swaggerRegex = /\/\*\*[\s\S]*?@swagger[\s\S]*?\*\//g;
186
+ let match;
187
+
188
+ while ((match = swaggerRegex.exec(content)) !== null) {
189
+ swaggerBlocks.push(match[0]);
190
+ }
191
+
192
+ return swaggerBlocks;
193
+ }
194
+
195
+ /**
196
+ * Extract TypeScript interfaces and types that look like DTOs/schemas
105
197
  */
106
- function extractApiPatterns(content, filePath) {
107
- const apiPatterns = [];
198
+ function extractTypeDefinitions(content, filePath) {
199
+ const typeBlocks = [];
108
200
  const lines = content.split('\n');
109
201
 
110
- // Patterns that indicate API calls
111
- const patterns = [
112
- // fetch() calls
113
- { regex: /fetch\s*\(\s*[`'"](.*?)[`'"]/g, type: 'fetch' },
114
- { regex: /fetch\s*\(\s*`([^`]*)`/g, type: 'fetch-template' },
115
- { regex: /fetch\s*\(\s*(['"])?\/api\//g, type: 'fetch-api' },
202
+ // Look for interfaces and types that might be request/response schemas
203
+ const typeKeywords = [
204
+ /interface\s+\w*(Request|Response|Dto|Body|Query|Params|Payload|Input|Output)\w*\s*\{/i,
205
+ /type\s+\w*(Request|Response|Dto|Body|Query|Params|Payload|Input|Output)\w*\s*=/i,
206
+ /interface\s+\w+\s*\{/, // Any interface in DTO/schema files
207
+ /type\s+\w+\s*=/ // Any type in DTO/schema files
208
+ ];
209
+
210
+ // Only extract type definitions from files that look like DTOs/types
211
+ const isDtoFile = /dto|interface|type|schema|model/i.test(filePath);
212
+
213
+ for (let i = 0; i < lines.length; i++) {
214
+ const line = lines[i];
116
215
 
117
- // axios calls
118
- { regex: /axios\.(get|post|put|patch|delete)\s*\(\s*[`'"](.*?)[`'"]/g, type: 'axios' },
119
- { regex: /axios\s*\(\s*\{[^}]*url\s*:\s*[`'"](.*?)[`'"]/g, type: 'axios-config' },
216
+ const isTypeDefinition = typeKeywords.some(regex => regex.test(line));
120
217
 
121
- // jQuery ajax
122
- { regex: /\$\.(ajax|get|post)\s*\(\s*[`'"](.*?)[`'"]/g, type: 'jquery' },
218
+ if (isTypeDefinition || (isDtoFile && /^(export\s+)?(interface|type)\s+/.test(line))) {
219
+ // Find the complete type block (until closing brace at same indent level)
220
+ let braceCount = 0;
221
+ let started = false;
222
+ let endLine = i;
223
+
224
+ for (let j = i; j < lines.length && j < i + 50; j++) {
225
+ const l = lines[j];
226
+ for (const char of l) {
227
+ if (char === '{') {
228
+ braceCount++;
229
+ started = true;
230
+ } else if (char === '}') {
231
+ braceCount--;
232
+ }
233
+ }
234
+ endLine = j;
235
+ if (started && braceCount === 0) break;
236
+ }
237
+
238
+ const block = lines.slice(i, endLine + 1)
239
+ .map((l, idx) => `${i + idx + 1}: ${l}`)
240
+ .join('\n');
241
+
242
+ typeBlocks.push(block);
243
+ i = endLine; // Skip processed lines
244
+ }
245
+ }
246
+
247
+ return typeBlocks;
248
+ }
249
+
250
+ /**
251
+ * Extract request body and query parameter patterns
252
+ */
253
+ function extractRequestPatterns(content) {
254
+ const patterns = [];
255
+ const lines = content.split('\n');
256
+
257
+ for (let i = 0; i < lines.length; i++) {
258
+ const line = lines[i];
123
259
 
124
- // Generic API URLs
125
- { regex: /['"`](\/api\/[^'"`\s]+)['"`]/g, type: 'api-url' },
126
- { regex: /['"`](https?:\/\/[^'"`\s]*\/api[^'"`\s]*)['"`]/g, type: 'full-url' },
260
+ // Destructuring patterns for req.body, req.query, req.params
261
+ if (/(?:const|let|var)\s*\{[^}]+\}\s*=\s*req\.(body|query|params)/i.test(line) ||
262
+ /req\.(body|query|params)\s*[;.]/.test(line)) {
263
+
264
+ // Get context around it
265
+ const startLine = Math.max(0, i - 2);
266
+ const endLine = Math.min(lines.length - 1, i + 5);
267
+
268
+ const block = lines.slice(startLine, endLine + 1)
269
+ .map((l, idx) => `${startLine + idx + 1}: ${l}`)
270
+ .join('\n');
271
+
272
+ patterns.push(`// Request parameter extraction:\n${block}`);
273
+ i = endLine;
274
+ }
127
275
 
128
- // Method + URL patterns
129
- { regex: /(GET|POST|PUT|PATCH|DELETE)\s*[,:]?\s*['"`](\/[^'"`]+)['"`]/gi, type: 'method-url' },
130
- ];
276
+ // NestJS decorators: @Body(), @Query(), @Param()
277
+ if (/@(Body|Query|Param|Headers)\s*\(/i.test(line)) {
278
+ const startLine = Math.max(0, i - 1);
279
+ const endLine = Math.min(lines.length - 1, i + 3);
280
+
281
+ const block = lines.slice(startLine, endLine + 1)
282
+ .map((l, idx) => `${startLine + idx + 1}: ${l}`)
283
+ .join('\n');
284
+
285
+ patterns.push(`// NestJS parameter decorator:\n${block}`);
286
+ i = endLine;
287
+ }
288
+
289
+ // Validation schemas: Joi, Zod, class-validator
290
+ if (/Joi\.(object|string|number|array|boolean)\s*\(/i.test(line) ||
291
+ /z\.(object|string|number|array|boolean)\s*\(/i.test(line) ||
292
+ /@(IsString|IsNumber|IsArray|IsBoolean|IsOptional|ValidateNested)/i.test(line)) {
293
+
294
+ const startLine = Math.max(0, i - 2);
295
+ const endLine = Math.min(lines.length - 1, i + 10);
296
+
297
+ const block = lines.slice(startLine, endLine + 1)
298
+ .map((l, idx) => `${startLine + idx + 1}: ${l}`)
299
+ .join('\n');
300
+
301
+ patterns.push(`// Validation schema:\n${block}`);
302
+ i = endLine;
303
+ }
304
+ }
305
+
306
+ return patterns;
307
+ }
131
308
 
132
- let hasApiCalls = false;
309
+ /**
310
+ * Extract API patterns from file content - framework agnostic
311
+ */
312
+ function extractApiPatterns(content, filePath, absolutePath) {
313
+ const lines = content.split('\n');
314
+ let hasApiPatterns = false;
133
315
  const extractedBlocks = [];
134
316
 
317
+ // Check if this is a route/controller file - if so, include more context
318
+ const isRouteFile = HIGH_PRIORITY_PATTERNS.some(p => p.test(filePath));
319
+ const isDtoFile = /dto|interface|types?|schema/i.test(filePath);
320
+
321
+ // ============================================
322
+ // PATTERN 0: Swagger/OpenAPI documentation
323
+ // ============================================
324
+ const swaggerBlocks = extractSwaggerBlocks(content);
325
+ if (swaggerBlocks.length > 0) {
326
+ hasApiPatterns = true;
327
+ extractedBlocks.push(`// Swagger/OpenAPI documentation found:\n${swaggerBlocks.join('\n\n')}`);
328
+ }
329
+
330
+ // ============================================
331
+ // PATTERN 1: TypeScript interfaces/types (DTOs, schemas)
332
+ // ============================================
333
+ const typeBlocks = extractTypeDefinitions(content, filePath);
334
+ if (typeBlocks.length > 0) {
335
+ hasApiPatterns = true;
336
+ extractedBlocks.push(`// TypeScript type definitions:\n${typeBlocks.join('\n\n')}`);
337
+ }
338
+
339
+ // ============================================
340
+ // PATTERN 2: Request body/query extraction
341
+ // ============================================
342
+ const requestPatterns = extractRequestPatterns(content);
343
+ if (requestPatterns.length > 0) {
344
+ hasApiPatterns = true;
345
+ extractedBlocks.push(requestPatterns.join('\n\n'));
346
+ }
347
+
348
+ // ============================================
349
+ // PATTERN 3: Express/Koa/Fastify Router definitions
350
+ // ============================================
351
+ const routerPatterns = [
352
+ // Express Router: router.get('/path', handler)
353
+ /\.(get|post|put|patch|delete|all|use)\s*\(\s*['"`]([^'"`]+)['"`]/gi,
354
+ // Express app: app.get('/path', handler)
355
+ /app\.(get|post|put|patch|delete|all|use)\s*\(\s*['"`]([^'"`]+)['"`]/gi,
356
+ // Fastify: fastify.get('/path', handler)
357
+ /fastify\.(get|post|put|patch|delete|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi,
358
+ ];
359
+
360
+ // ============================================
361
+ // PATTERN 4: NestJS/Decorators
362
+ // ============================================
363
+ const decoratorPatterns = [
364
+ /@(Get|Post|Put|Patch|Delete|All)\s*\(\s*['"`]?([^'"`\)]*)/gi,
365
+ /@Controller\s*\(\s*['"`]([^'"`]+)/gi,
366
+ ];
367
+
368
+ // ============================================
369
+ // PATTERN 5: Frontend HTTP calls
370
+ // ============================================
371
+ const httpCallPatterns = [
372
+ // fetch() calls
373
+ /fetch\s*\(\s*[`'"](.*?)[`'"]/g,
374
+ /fetch\s*\(\s*`([^`]*)`/g,
375
+ // axios calls
376
+ /axios\.(get|post|put|patch|delete)\s*\(\s*[`'"](.*?)[`'"]/g,
377
+ /axios\s*\(\s*\{[^}]*url\s*:\s*[`'"](.*?)[`'"]/g,
378
+ // Angular HttpClient
379
+ /this\.http\.(get|post|put|patch|delete)\s*[<(]/g,
380
+ /httpClient\.(get|post|put|patch|delete)\s*[<(]/g,
381
+ // jQuery ajax
382
+ /\$\.(ajax|get|post)\s*\(\s*[`'"](.*?)[`'"]/g,
383
+ // Generic request libraries
384
+ /request\.(get|post|put|patch|delete)\s*\(/g,
385
+ /got\.(get|post|put|patch|delete)\s*\(/g,
386
+ /superagent\.(get|post|put|patch|delete)\s*\(/g,
387
+ ];
388
+
389
+ // ============================================
390
+ // PATTERN 6: API URL definitions
391
+ // ============================================
392
+ const urlPatterns = [
393
+ // API endpoints in strings
394
+ /['"`](\/api\/[^'"`\s]+)['"`]/g,
395
+ /['"`](\/v\d+\/[^'"`\s]+)['"`]/g,
396
+ /['"`](https?:\/\/[^'"`\s]*\/api[^'"`\s]*)['"`]/g,
397
+ // Base URL definitions
398
+ /(?:API_BASE|API_URL|BASE_URL|baseURL|apiUrl|apiBase|API_ENDPOINT|BACKEND_URL)\s*[:=]\s*['"`]([^'"`]+)['"`]/gi,
399
+ ];
400
+
401
+ // ============================================
402
+ // PATTERN 7: Method + URL combinations
403
+ // ============================================
404
+ const methodUrlPatterns = [
405
+ /(GET|POST|PUT|PATCH|DELETE)\s*[,:]?\s*['"`](\/[^'"`]+)['"`]/gi,
406
+ /method:\s*['"`](GET|POST|PUT|PATCH|DELETE)['"`]/gi,
407
+ ];
408
+
409
+ // Combine all patterns for line scanning
410
+ const allPatterns = [
411
+ ...routerPatterns,
412
+ ...decoratorPatterns,
413
+ ...httpCallPatterns,
414
+ ...urlPatterns,
415
+ ...methodUrlPatterns
416
+ ];
417
+
418
+ // If this is a route/controller file with swagger docs, include the whole file
419
+ if (isRouteFile && swaggerBlocks.length > 0) {
420
+ hasApiPatterns = true;
421
+ // Include entire file content (truncated if too long)
422
+ const maxLines = 300;
423
+ const truncatedContent = lines.length > maxLines
424
+ ? lines.slice(0, maxLines).join('\n') + `\n// ... ${lines.length - maxLines} more lines ...`
425
+ : content;
426
+
427
+ return {
428
+ hasApiPatterns: true,
429
+ content: `// File: ${filePath}\n// Route/Controller file with Swagger docs - full content:\n\n${truncatedContent}`
430
+ };
431
+ }
432
+
433
+ // If this is a route file without swagger, still include more content
434
+ if (isRouteFile) {
435
+ const hasRouteContent = allPatterns.some(p => {
436
+ p.lastIndex = 0;
437
+ return p.test(content);
438
+ });
439
+
440
+ if (hasRouteContent) {
441
+ hasApiPatterns = true;
442
+ const maxLines = 200;
443
+ const truncatedContent = lines.length > maxLines
444
+ ? lines.slice(0, maxLines).join('\n') + `\n// ... ${lines.length - maxLines} more lines ...`
445
+ : content;
446
+
447
+ return {
448
+ hasApiPatterns: true,
449
+ content: `// File: ${filePath}\n// Route/Controller file - full content:\n\n${truncatedContent}`
450
+ };
451
+ }
452
+ }
453
+
454
+ // If this is a DTO/types file, include full content
455
+ if (isDtoFile && typeBlocks.length > 0) {
456
+ hasApiPatterns = true;
457
+ const maxLines = 150;
458
+ const truncatedContent = lines.length > maxLines
459
+ ? lines.slice(0, maxLines).join('\n') + `\n// ... ${lines.length - maxLines} more lines ...`
460
+ : content;
461
+
462
+ return {
463
+ hasApiPatterns: true,
464
+ content: `// File: ${filePath}\n// DTO/Types file - full content:\n\n${truncatedContent}`
465
+ };
466
+ }
467
+
468
+ // For non-route files, extract relevant sections
469
+ // First, extract imports and base URL definitions
470
+ const baseUrlLines = [];
471
+
472
+ for (let i = 0; i < Math.min(lines.length, 50); i++) {
473
+ const line = lines[i];
474
+ if (/(?:API_BASE|API_URL|BASE_URL|baseURL|apiUrl|BACKEND)/i.test(line)) {
475
+ baseUrlLines.push(`${i + 1}: ${line}`);
476
+ }
477
+ }
478
+
479
+ if (baseUrlLines.length > 0) {
480
+ extractedBlocks.push('// Base URL definitions:\n' + baseUrlLines.join('\n'));
481
+ hasApiPatterns = true;
482
+ }
483
+
135
484
  // Line-by-line extraction with context
136
485
  for (let i = 0; i < lines.length; i++) {
137
486
  const line = lines[i];
138
487
 
139
- // Check for API patterns
140
- const hasPattern = patterns.some(p => p.regex.test(line));
141
- // Reset regex lastIndex
142
- patterns.forEach(p => p.regex.lastIndex = 0);
488
+ // Check all patterns
489
+ let hasPattern = false;
490
+ for (const pattern of allPatterns) {
491
+ pattern.lastIndex = 0;
492
+ if (pattern.test(line)) {
493
+ hasPattern = true;
494
+ break;
495
+ }
496
+ }
143
497
 
144
498
  // Also check for common API keywords
145
- const hasKeyword = /fetch|axios|\.ajax|\.get\(|\.post\(|\.put\(|\.delete\(|\/api\/|endpoint|baseURL/i.test(line);
499
+ const hasKeyword = /\.get\(|\.post\(|\.put\(|\.patch\(|\.delete\(|fetch\(|axios|\/api\/|endpoint|@Get|@Post|@Put|@Delete/i.test(line);
146
500
 
147
501
  if (hasPattern || hasKeyword) {
148
- hasApiCalls = true;
502
+ hasApiPatterns = true;
149
503
 
150
- // Get context: 3 lines before and 5 lines after
151
- const startLine = Math.max(0, i - 3);
152
- const endLine = Math.min(lines.length - 1, i + 5);
504
+ // Determine context needed
505
+ const isPostOrPut = /post|put|patch/i.test(line);
506
+ const contextBefore = isPostOrPut ? 20 : 5; // More context for mutations
507
+ const contextAfter = isPostOrPut ? 10 : 5;
508
+
509
+ const startLine = Math.max(0, i - contextBefore);
510
+ const endLine = Math.min(lines.length - 1, i + contextAfter);
153
511
 
154
512
  const block = lines.slice(startLine, endLine + 1)
155
513
  .map((l, idx) => `${startLine + idx + 1}: ${l}`)
@@ -162,49 +520,16 @@ function extractApiPatterns(content, filePath) {
162
520
  }
163
521
  }
164
522
 
165
- // If we found API patterns, return extracted content
166
- if (hasApiCalls && extractedBlocks.length > 0) {
523
+ if (hasApiPatterns && extractedBlocks.length > 0) {
524
+ // Deduplicate blocks
525
+ const uniqueBlocks = [...new Set(extractedBlocks)];
167
526
  return {
168
- hasApiCalls: true,
169
- content: `// File: ${filePath}\n// API patterns found:\n\n${extractedBlocks.join('\n\n// ---\n\n')}`
527
+ hasApiPatterns: true,
528
+ content: `// File: ${filePath}\n// API patterns extracted:\n\n${uniqueBlocks.join('\n\n// ---\n\n')}`
170
529
  };
171
530
  }
172
531
 
173
- // Fallback: include first 5000 chars if file looks relevant
174
- if (/api|fetch|axios|service|http/i.test(filePath)) {
175
- return {
176
- hasApiCalls: true,
177
- content: `// File: ${filePath}\n${content.slice(0, 5000)}`
178
- };
179
- }
180
-
181
- return { hasApiCalls: false, content: '' };
182
- }
183
-
184
- /**
185
- * Prioritize frontend files based on keywords
186
- */
187
- function prioritizeFiles(files, basePath) {
188
- return files.sort((a, b) => {
189
- const aPath = path.relative(basePath, a).toLowerCase();
190
- const bPath = path.relative(basePath, b).toLowerCase();
191
-
192
- // Deprioritize test files
193
- if (aPath.includes('test') || aPath.includes('spec')) return 1;
194
- if (bPath.includes('test') || bPath.includes('spec')) return -1;
195
-
196
- // Prioritize src/frontend folders
197
- if (aPath.includes('src/') || aPath.includes('frontend/')) {
198
- if (!bPath.includes('src/') && !bPath.includes('frontend/')) return -1;
199
- }
200
-
201
- const aScore = PRIORITY_KEYWORDS.reduce((score, kw) =>
202
- aPath.includes(kw) ? score + 1 : score, 0);
203
- const bScore = PRIORITY_KEYWORDS.reduce((score, kw) =>
204
- bPath.includes(kw) ? score + 1 : score, 0);
205
-
206
- return bScore - aScore;
207
- });
532
+ return { hasApiPatterns: false, content: '' };
208
533
  }
209
534
 
210
535
  module.exports = { scanCodebase };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@merlean/analyzer",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "description": "AI Bot codebase analyzer - generates site maps for AI assistant integration",
5
5
  "keywords": ["ai", "bot", "analyzer", "claude", "anthropic", "widget"],
6
6
  "author": "zmaren",