create-claude-context 1.2.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -6
- package/bin/create-claude-context.js +15 -1
- package/lib/ai-orchestrator.js +423 -0
- package/lib/call-tracer.js +444 -0
- package/lib/detector.js +83 -0
- package/lib/environment-detector.js +239 -0
- package/lib/index.js +128 -27
- package/lib/placeholder.js +80 -19
- package/lib/static-analyzer.js +729 -0
- package/lib/template-populator.js +835 -0
- package/package.json +1 -1
- package/templates/base/settings.json +1 -77
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Call Tracer
|
|
3
|
+
*
|
|
4
|
+
* Performs static call chain analysis to trace function calls.
|
|
5
|
+
* Used for workflow documentation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Language-specific patterns for call extraction
|
|
13
|
+
*/
|
|
14
|
+
const CALL_PATTERNS = {
|
|
15
|
+
javascript: {
|
|
16
|
+
functionCall: /(\w+)\s*\(/g,
|
|
17
|
+
import: /(?:import\s+(?:(?:\{[^}]+\})|(?:\*\s+as\s+\w+)|(?:\w+))\s+from\s+['"]([^'"]+)['"])|(?:require\s*\(\s*['"]([^'"]+)['"]\s*\))/g,
|
|
18
|
+
methodCall: /(\w+)\.(\w+)\s*\(/g,
|
|
19
|
+
functionDef: /(?:function\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s*)?\(|(\w+)\s*[:=]\s*(?:async\s*)?\([^)]*\)\s*=>)/g,
|
|
20
|
+
asyncAwait: /await\s+(\w+)(?:\.(\w+))?\s*\(/g
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
typescript: {
|
|
24
|
+
functionCall: /(\w+)\s*\(/g,
|
|
25
|
+
import: /import\s+(?:\{[^}]+\}|(?:\*\s+as\s+\w+)|\w+)\s+from\s+['"]([^'"]+)['"]/g,
|
|
26
|
+
methodCall: /(\w+)\.(\w+)\s*\(/g,
|
|
27
|
+
functionDef: /(?:function\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s*)?\(|(\w+)\s*[:=]\s*(?:async\s*)?\([^)]*\)\s*=>)/g,
|
|
28
|
+
asyncAwait: /await\s+(\w+)(?:\.(\w+))?\s*\(/g,
|
|
29
|
+
classMethod: /(?:async\s+)?(\w+)\s*\([^)]*\)\s*(?::\s*\w+)?\s*\{/g
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
python: {
|
|
33
|
+
functionCall: /(\w+)\s*\(/g,
|
|
34
|
+
import: /(?:from\s+(\S+)\s+import)|(?:import\s+(\S+))/g,
|
|
35
|
+
methodCall: /(\w+)\.(\w+)\s*\(/g,
|
|
36
|
+
functionDef: /def\s+(\w+)\s*\(/g,
|
|
37
|
+
classMethod: /def\s+(\w+)\s*\(\s*self/g,
|
|
38
|
+
asyncDef: /async\s+def\s+(\w+)\s*\(/g
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
go: {
|
|
42
|
+
functionCall: /(\w+)\s*\(/g,
|
|
43
|
+
import: /import\s+(?:\(\s*)?["']([^"']+)["']/g,
|
|
44
|
+
methodCall: /(\w+)\.(\w+)\s*\(/g,
|
|
45
|
+
functionDef: /func\s+(\w+)\s*\(/g,
|
|
46
|
+
methodDef: /func\s+\([^)]+\)\s+(\w+)\s*\(/g
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
ruby: {
|
|
50
|
+
functionCall: /(\w+)\s*[\(\s]/g,
|
|
51
|
+
methodCall: /(\w+)\.(\w+)/g,
|
|
52
|
+
functionDef: /def\s+(\w+)/g,
|
|
53
|
+
classMethod: /def\s+self\.(\w+)/g
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Keywords to exclude from call analysis
|
|
59
|
+
*/
|
|
60
|
+
const EXCLUDED_CALLS = new Set([
|
|
61
|
+
// Control flow
|
|
62
|
+
'if', 'else', 'for', 'while', 'switch', 'case', 'try', 'catch', 'finally',
|
|
63
|
+
// Built-ins
|
|
64
|
+
'console', 'log', 'error', 'warn', 'info', 'debug',
|
|
65
|
+
'print', 'println', 'printf',
|
|
66
|
+
'require', 'import', 'export', 'from',
|
|
67
|
+
// Common JS
|
|
68
|
+
'map', 'filter', 'reduce', 'forEach', 'find', 'some', 'every',
|
|
69
|
+
'push', 'pop', 'shift', 'unshift', 'slice', 'splice',
|
|
70
|
+
'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval',
|
|
71
|
+
'Promise', 'resolve', 'reject', 'then', 'catch', 'finally',
|
|
72
|
+
'async', 'await',
|
|
73
|
+
// Common Python
|
|
74
|
+
'len', 'range', 'list', 'dict', 'set', 'tuple', 'str', 'int', 'float',
|
|
75
|
+
'open', 'read', 'write', 'close',
|
|
76
|
+
'append', 'extend', 'update', 'get', 'keys', 'values', 'items',
|
|
77
|
+
// Types
|
|
78
|
+
'String', 'Number', 'Boolean', 'Array', 'Object', 'Function',
|
|
79
|
+
'typeof', 'instanceof'
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Detect language from file extension
|
|
84
|
+
* @param {string} filePath - File path
|
|
85
|
+
* @returns {string} Language name
|
|
86
|
+
*/
|
|
87
|
+
function detectLanguage(filePath) {
|
|
88
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
89
|
+
const langMap = {
|
|
90
|
+
'.js': 'javascript',
|
|
91
|
+
'.jsx': 'javascript',
|
|
92
|
+
'.mjs': 'javascript',
|
|
93
|
+
'.cjs': 'javascript',
|
|
94
|
+
'.ts': 'typescript',
|
|
95
|
+
'.tsx': 'typescript',
|
|
96
|
+
'.py': 'python',
|
|
97
|
+
'.go': 'go',
|
|
98
|
+
'.rb': 'ruby',
|
|
99
|
+
'.rs': 'rust'
|
|
100
|
+
};
|
|
101
|
+
return langMap[ext] || 'javascript';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Extract function body from content
|
|
106
|
+
* @param {string} content - File content
|
|
107
|
+
* @param {string} functionName - Function name to find
|
|
108
|
+
* @param {string} language - Programming language
|
|
109
|
+
* @returns {object|null} Function body and line number
|
|
110
|
+
*/
|
|
111
|
+
function extractFunctionBody(content, functionName, language) {
|
|
112
|
+
const lines = content.split('\n');
|
|
113
|
+
|
|
114
|
+
// Language-specific function patterns
|
|
115
|
+
const patterns = {
|
|
116
|
+
javascript: [
|
|
117
|
+
new RegExp(`function\\s+${functionName}\\s*\\(`),
|
|
118
|
+
new RegExp(`(?:const|let|var)\\s+${functionName}\\s*=\\s*(?:async\\s*)?\\(`),
|
|
119
|
+
new RegExp(`${functionName}\\s*[:=]\\s*(?:async\\s*)?\\([^)]*\\)\\s*=>`),
|
|
120
|
+
new RegExp(`${functionName}\\s*\\([^)]*\\)\\s*\\{`)
|
|
121
|
+
],
|
|
122
|
+
typescript: [
|
|
123
|
+
new RegExp(`function\\s+${functionName}\\s*[<(]`),
|
|
124
|
+
new RegExp(`(?:const|let|var)\\s+${functionName}\\s*=\\s*(?:async\\s*)?\\(`),
|
|
125
|
+
new RegExp(`${functionName}\\s*[:=]\\s*(?:async\\s*)?\\([^)]*\\)\\s*=>`),
|
|
126
|
+
new RegExp(`(?:async\\s+)?${functionName}\\s*\\([^)]*\\)\\s*(?::\\s*\\w+)?\\s*\\{`)
|
|
127
|
+
],
|
|
128
|
+
python: [
|
|
129
|
+
new RegExp(`def\\s+${functionName}\\s*\\(`),
|
|
130
|
+
new RegExp(`async\\s+def\\s+${functionName}\\s*\\(`)
|
|
131
|
+
],
|
|
132
|
+
go: [
|
|
133
|
+
new RegExp(`func\\s+${functionName}\\s*\\(`),
|
|
134
|
+
new RegExp(`func\\s+\\([^)]+\\)\\s+${functionName}\\s*\\(`)
|
|
135
|
+
],
|
|
136
|
+
ruby: [
|
|
137
|
+
new RegExp(`def\\s+${functionName}\\b`)
|
|
138
|
+
]
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const langPatterns = patterns[language] || patterns.javascript;
|
|
142
|
+
|
|
143
|
+
// Find function start
|
|
144
|
+
let startLine = -1;
|
|
145
|
+
for (let i = 0; i < lines.length; i++) {
|
|
146
|
+
for (const pattern of langPatterns) {
|
|
147
|
+
if (pattern.test(lines[i])) {
|
|
148
|
+
startLine = i;
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (startLine >= 0) break;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (startLine === -1) return null;
|
|
156
|
+
|
|
157
|
+
// Extract function body (simplified - track braces/indentation)
|
|
158
|
+
const bodyLines = [];
|
|
159
|
+
let braceCount = 0;
|
|
160
|
+
let inFunction = false;
|
|
161
|
+
|
|
162
|
+
for (let i = startLine; i < lines.length && i < startLine + 100; i++) {
|
|
163
|
+
const line = lines[i];
|
|
164
|
+
bodyLines.push(line);
|
|
165
|
+
|
|
166
|
+
if (language === 'python' || language === 'ruby') {
|
|
167
|
+
// Indentation-based
|
|
168
|
+
if (i > startLine && line.trim() && !line.match(/^\s/)) {
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
// Brace-based
|
|
173
|
+
braceCount += (line.match(/\{/g) || []).length;
|
|
174
|
+
braceCount -= (line.match(/\}/g) || []).length;
|
|
175
|
+
|
|
176
|
+
if (braceCount > 0) inFunction = true;
|
|
177
|
+
if (inFunction && braceCount === 0) break;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
body: bodyLines.join('\n'),
|
|
183
|
+
startLine: startLine + 1,
|
|
184
|
+
endLine: startLine + bodyLines.length
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Find function calls in code
|
|
190
|
+
* @param {string} code - Code to analyze
|
|
191
|
+
* @param {string} language - Programming language
|
|
192
|
+
* @returns {object[]} Array of function calls
|
|
193
|
+
*/
|
|
194
|
+
function findFunctionCalls(code, language = 'javascript') {
|
|
195
|
+
const calls = [];
|
|
196
|
+
const patterns = CALL_PATTERNS[language] || CALL_PATTERNS.javascript;
|
|
197
|
+
|
|
198
|
+
// Find regular function calls
|
|
199
|
+
const funcCallPattern = patterns.functionCall;
|
|
200
|
+
let match;
|
|
201
|
+
funcCallPattern.lastIndex = 0;
|
|
202
|
+
|
|
203
|
+
while ((match = funcCallPattern.exec(code)) !== null) {
|
|
204
|
+
const name = match[1];
|
|
205
|
+
if (!EXCLUDED_CALLS.has(name) && !name.match(/^[A-Z_]+$/)) {
|
|
206
|
+
calls.push({
|
|
207
|
+
name,
|
|
208
|
+
type: 'function',
|
|
209
|
+
position: match.index
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Find method calls
|
|
215
|
+
const methodCallPattern = patterns.methodCall;
|
|
216
|
+
methodCallPattern.lastIndex = 0;
|
|
217
|
+
|
|
218
|
+
while ((match = methodCallPattern.exec(code)) !== null) {
|
|
219
|
+
const obj = match[1];
|
|
220
|
+
const method = match[2];
|
|
221
|
+
if (!EXCLUDED_CALLS.has(obj) && !EXCLUDED_CALLS.has(method)) {
|
|
222
|
+
calls.push({
|
|
223
|
+
name: `${obj}.${method}`,
|
|
224
|
+
object: obj,
|
|
225
|
+
method,
|
|
226
|
+
type: 'method',
|
|
227
|
+
position: match.index
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Find async/await calls (JS/TS)
|
|
233
|
+
if (patterns.asyncAwait) {
|
|
234
|
+
const asyncPattern = patterns.asyncAwait;
|
|
235
|
+
asyncPattern.lastIndex = 0;
|
|
236
|
+
|
|
237
|
+
while ((match = asyncPattern.exec(code)) !== null) {
|
|
238
|
+
const name = match[2] ? `${match[1]}.${match[2]}` : match[1];
|
|
239
|
+
if (!EXCLUDED_CALLS.has(match[1])) {
|
|
240
|
+
calls.push({
|
|
241
|
+
name,
|
|
242
|
+
type: 'async',
|
|
243
|
+
position: match.index
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Deduplicate by name
|
|
250
|
+
const seen = new Set();
|
|
251
|
+
return calls.filter(c => {
|
|
252
|
+
if (seen.has(c.name)) return false;
|
|
253
|
+
seen.add(c.name);
|
|
254
|
+
return true;
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Resolve a call to its file location
|
|
260
|
+
* @param {object} call - Call object
|
|
261
|
+
* @param {string} currentFile - Current file path
|
|
262
|
+
* @param {string} projectRoot - Project root
|
|
263
|
+
* @returns {string|null} Resolved file path
|
|
264
|
+
*/
|
|
265
|
+
function resolveCallLocation(call, currentFile, projectRoot) {
|
|
266
|
+
// This is a simplified resolution - real resolution would need import analysis
|
|
267
|
+
const callName = call.method || call.name;
|
|
268
|
+
const currentDir = path.dirname(currentFile);
|
|
269
|
+
|
|
270
|
+
// Check same directory
|
|
271
|
+
const extensions = ['.js', '.ts', '.py', '.go', '.rb'];
|
|
272
|
+
for (const ext of extensions) {
|
|
273
|
+
const sameDirFile = path.join(currentDir, callName + ext);
|
|
274
|
+
const fullPath = path.join(projectRoot, sameDirFile);
|
|
275
|
+
if (fs.existsSync(fullPath)) {
|
|
276
|
+
return sameDirFile;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Check common directories
|
|
281
|
+
const commonDirs = ['lib', 'src', 'utils', 'helpers', 'services'];
|
|
282
|
+
for (const dir of commonDirs) {
|
|
283
|
+
for (const ext of extensions) {
|
|
284
|
+
const filePath = path.join(dir, callName + ext);
|
|
285
|
+
const fullPath = path.join(projectRoot, filePath);
|
|
286
|
+
if (fs.existsSync(fullPath)) {
|
|
287
|
+
return filePath;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Trace call chain from an entry point
|
|
297
|
+
* @param {string} entryFile - Entry file path (relative to project root)
|
|
298
|
+
* @param {string} entryFunction - Entry function name
|
|
299
|
+
* @param {string} projectRoot - Project root directory
|
|
300
|
+
* @param {object} options - Options
|
|
301
|
+
* @returns {object[]} Call chain
|
|
302
|
+
*/
|
|
303
|
+
function traceCallChain(entryFile, entryFunction, projectRoot, options = {}) {
|
|
304
|
+
const maxDepth = options.maxDepth || 3;
|
|
305
|
+
const visited = new Set();
|
|
306
|
+
const chain = [];
|
|
307
|
+
|
|
308
|
+
function trace(file, func, depth) {
|
|
309
|
+
const key = `${file}:${func}`;
|
|
310
|
+
|
|
311
|
+
if (depth > maxDepth || visited.has(key)) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
visited.add(key);
|
|
316
|
+
|
|
317
|
+
const fullPath = path.join(projectRoot, file);
|
|
318
|
+
if (!fs.existsSync(fullPath)) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
324
|
+
const language = detectLanguage(file);
|
|
325
|
+
const funcData = extractFunctionBody(content, func, language);
|
|
326
|
+
|
|
327
|
+
if (!funcData) {
|
|
328
|
+
chain.push({
|
|
329
|
+
file,
|
|
330
|
+
function: func,
|
|
331
|
+
depth,
|
|
332
|
+
calls: [],
|
|
333
|
+
status: 'not-found'
|
|
334
|
+
});
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const calls = findFunctionCalls(funcData.body, language);
|
|
339
|
+
|
|
340
|
+
chain.push({
|
|
341
|
+
file,
|
|
342
|
+
function: func,
|
|
343
|
+
depth,
|
|
344
|
+
startLine: funcData.startLine,
|
|
345
|
+
endLine: funcData.endLine,
|
|
346
|
+
calls: calls.map(c => c.name),
|
|
347
|
+
status: 'traced'
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// Trace each call (up to first 5)
|
|
351
|
+
for (const call of calls.slice(0, 5)) {
|
|
352
|
+
const callName = call.method || call.name;
|
|
353
|
+
const resolvedFile = resolveCallLocation(call, file, projectRoot);
|
|
354
|
+
|
|
355
|
+
if (resolvedFile) {
|
|
356
|
+
trace(resolvedFile, callName, depth + 1);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
} catch (error) {
|
|
360
|
+
chain.push({
|
|
361
|
+
file,
|
|
362
|
+
function: func,
|
|
363
|
+
depth,
|
|
364
|
+
calls: [],
|
|
365
|
+
status: 'error',
|
|
366
|
+
error: error.message
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
trace(entryFile, entryFunction, 0);
|
|
372
|
+
|
|
373
|
+
return chain;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Format call chain as ASCII tree
|
|
378
|
+
* @param {object[]} chain - Call chain from traceCallChain
|
|
379
|
+
* @returns {string} ASCII tree representation
|
|
380
|
+
*/
|
|
381
|
+
function formatCallChainAscii(chain) {
|
|
382
|
+
if (!chain || chain.length === 0) {
|
|
383
|
+
return 'No call chain traced';
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
let output = '';
|
|
387
|
+
const maxDepth = Math.max(...chain.map(c => c.depth));
|
|
388
|
+
|
|
389
|
+
for (const item of chain) {
|
|
390
|
+
const indent = ' '.repeat(item.depth);
|
|
391
|
+
const prefix = item.depth === 0 ? '' : '├─ ';
|
|
392
|
+
const status = item.status === 'traced' ? '' : ` [${item.status}]`;
|
|
393
|
+
|
|
394
|
+
output += `${indent}${prefix}${item.function}()${status}\n`;
|
|
395
|
+
|
|
396
|
+
if (item.startLine) {
|
|
397
|
+
output += `${indent} └─ ${item.file}:${item.startLine}\n`;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Show immediate calls (depth 0 and 1 only)
|
|
401
|
+
if (item.depth < 2 && item.calls && item.calls.length > 0) {
|
|
402
|
+
const callsToShow = item.calls.slice(0, 5);
|
|
403
|
+
for (let i = 0; i < callsToShow.length; i++) {
|
|
404
|
+
const isLast = i === callsToShow.length - 1 && item.depth === maxDepth;
|
|
405
|
+
const callPrefix = isLast ? '└─' : '├─';
|
|
406
|
+
output += `${indent} ${callPrefix} → ${callsToShow[i]}()\n`;
|
|
407
|
+
}
|
|
408
|
+
if (item.calls.length > 5) {
|
|
409
|
+
output += `${indent} └─ ... and ${item.calls.length - 5} more\n`;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return output;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Generate call chain summary
|
|
419
|
+
* @param {object[]} chain - Call chain from traceCallChain
|
|
420
|
+
* @returns {object} Summary statistics
|
|
421
|
+
*/
|
|
422
|
+
function summarizeCallChain(chain) {
|
|
423
|
+
return {
|
|
424
|
+
totalFunctions: chain.length,
|
|
425
|
+
maxDepth: Math.max(...chain.map(c => c.depth), 0),
|
|
426
|
+
tracedSuccessfully: chain.filter(c => c.status === 'traced').length,
|
|
427
|
+
notFound: chain.filter(c => c.status === 'not-found').length,
|
|
428
|
+
errors: chain.filter(c => c.status === 'error').length,
|
|
429
|
+
uniqueFiles: [...new Set(chain.map(c => c.file))].length,
|
|
430
|
+
allCalls: [...new Set(chain.flatMap(c => c.calls || []))]
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
module.exports = {
|
|
435
|
+
traceCallChain,
|
|
436
|
+
formatCallChainAscii,
|
|
437
|
+
summarizeCallChain,
|
|
438
|
+
findFunctionCalls,
|
|
439
|
+
extractFunctionBody,
|
|
440
|
+
detectLanguage,
|
|
441
|
+
resolveCallLocation,
|
|
442
|
+
CALL_PATTERNS,
|
|
443
|
+
EXCLUDED_CALLS
|
|
444
|
+
};
|
package/lib/detector.js
CHANGED
|
@@ -156,6 +156,84 @@ const TECH_SIGNATURES = {
|
|
|
156
156
|
},
|
|
157
157
|
};
|
|
158
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Entry point patterns for different frameworks
|
|
161
|
+
*/
|
|
162
|
+
const ENTRY_POINT_PATTERNS = {
|
|
163
|
+
express: {
|
|
164
|
+
regex: /app\.(get|post|put|delete|patch)\s*\(\s*['"`]([^'"`]+)['"`]/g,
|
|
165
|
+
extractor: (match) => ({ method: match[1].toUpperCase(), route: match[2] })
|
|
166
|
+
},
|
|
167
|
+
fastapi: {
|
|
168
|
+
regex: /@app\.(get|post|put|delete|patch)\s*\(\s*['"`]([^'"`]+)['"`]/g,
|
|
169
|
+
extractor: (match) => ({ method: match[1].toUpperCase(), route: match[2] })
|
|
170
|
+
},
|
|
171
|
+
nextjs: {
|
|
172
|
+
regex: /export\s+(async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH)/g,
|
|
173
|
+
extractor: (match) => ({ method: match[2], route: 'app-router' })
|
|
174
|
+
},
|
|
175
|
+
django: {
|
|
176
|
+
regex: /path\s*\(\s*['"`]([^'"`]+)['"`]/g,
|
|
177
|
+
extractor: (match) => ({ method: 'ANY', route: match[1] })
|
|
178
|
+
},
|
|
179
|
+
rails: {
|
|
180
|
+
regex: /(get|post|put|delete|patch)\s+['"`]([^'"`]+)['"`]/g,
|
|
181
|
+
extractor: (match) => ({ method: match[1].toUpperCase(), route: match[2] })
|
|
182
|
+
},
|
|
183
|
+
nestjs: {
|
|
184
|
+
regex: /@(Get|Post|Put|Delete|Patch)\s*\(\s*['"`]?([^'"`)\s]*)['"`]?\s*\)/g,
|
|
185
|
+
extractor: (match) => ({ method: match[1].toUpperCase(), route: match[2] || '/' })
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Detect entry points in source files
|
|
191
|
+
*/
|
|
192
|
+
async function detectEntryPoints(projectRoot, frameworks) {
|
|
193
|
+
const entryPoints = [];
|
|
194
|
+
|
|
195
|
+
for (const framework of frameworks) {
|
|
196
|
+
const pattern = ENTRY_POINT_PATTERNS[framework];
|
|
197
|
+
if (!pattern) continue;
|
|
198
|
+
|
|
199
|
+
// Get appropriate file extension
|
|
200
|
+
const lang = TECH_SIGNATURES.frameworks[framework]?.language || 'javascript';
|
|
201
|
+
const ext = TECH_SIGNATURES.languages[lang]?.extensions?.[0] || '.js';
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
const files = await glob(`**/*${ext}`, {
|
|
205
|
+
cwd: projectRoot,
|
|
206
|
+
ignore: ['node_modules/**', 'vendor/**', '.git/**', 'dist/**', 'build/**', '__pycache__/**'],
|
|
207
|
+
nodir: true,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
for (const file of files.slice(0, 20)) { // Limit to 20 files
|
|
211
|
+
try {
|
|
212
|
+
const content = fs.readFileSync(path.join(projectRoot, file), 'utf8');
|
|
213
|
+
let match;
|
|
214
|
+
|
|
215
|
+
while ((match = pattern.regex.exec(content)) !== null) {
|
|
216
|
+
const entry = pattern.extractor(match);
|
|
217
|
+
entryPoints.push({
|
|
218
|
+
...entry,
|
|
219
|
+
file,
|
|
220
|
+
line: content.substring(0, match.index).split('\n').length,
|
|
221
|
+
framework
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
pattern.regex.lastIndex = 0; // Reset regex
|
|
225
|
+
} catch {
|
|
226
|
+
// Skip unreadable files
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
} catch {
|
|
230
|
+
// Skip glob errors
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return entryPoints;
|
|
235
|
+
}
|
|
236
|
+
|
|
159
237
|
/**
|
|
160
238
|
* Detect technology stack
|
|
161
239
|
*/
|
|
@@ -342,6 +420,9 @@ async function detectTechStack(projectRoot, options = {}) {
|
|
|
342
420
|
}
|
|
343
421
|
}
|
|
344
422
|
|
|
423
|
+
// Detect entry points for discovered frameworks
|
|
424
|
+
result.entryPoints = await detectEntryPoints(projectRoot, result.frameworks);
|
|
425
|
+
|
|
345
426
|
// Build stack string
|
|
346
427
|
const stackParts = [];
|
|
347
428
|
if (result.languages.length > 0) {
|
|
@@ -369,5 +450,7 @@ function capitalize(str) {
|
|
|
369
450
|
|
|
370
451
|
module.exports = {
|
|
371
452
|
detectTechStack,
|
|
453
|
+
detectEntryPoints,
|
|
372
454
|
TECH_SIGNATURES,
|
|
455
|
+
ENTRY_POINT_PATTERNS,
|
|
373
456
|
};
|