create-universal-ai-context 2.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.
- package/LICENSE +21 -0
- package/README.md +171 -0
- package/bin/create-ai-context.js +337 -0
- package/lib/adapters/antigravity.js +160 -0
- package/lib/adapters/claude.js +122 -0
- package/lib/adapters/cline.js +111 -0
- package/lib/adapters/copilot.js +117 -0
- package/lib/adapters/index.js +69 -0
- package/lib/ai-context-generator.js +234 -0
- package/lib/ai-orchestrator.js +431 -0
- package/lib/call-tracer.js +444 -0
- package/lib/detector.js +726 -0
- package/lib/environment-detector.js +239 -0
- package/lib/index.js +310 -0
- package/lib/installer.js +418 -0
- package/lib/migrate.js +319 -0
- package/lib/placeholder.js +541 -0
- package/lib/prompts.js +287 -0
- package/lib/spinner.js +60 -0
- package/lib/static-analyzer.js +729 -0
- package/lib/template-populator.js +843 -0
- package/lib/template-renderer.js +382 -0
- package/lib/validate.js +155 -0
- package/package.json +70 -0
- package/templates/AI_CONTEXT.md.template +245 -0
- package/templates/base/README.md +257 -0
- package/templates/base/RPI_WORKFLOW_PLAN.md +320 -0
- package/templates/base/agents/api-developer.md +76 -0
- package/templates/base/agents/context-engineer.md +525 -0
- package/templates/base/agents/core-architect.md +76 -0
- package/templates/base/agents/database-ops.md +76 -0
- package/templates/base/agents/deployment-ops.md +76 -0
- package/templates/base/agents/integration-hub.md +76 -0
- package/templates/base/analytics/README.md +114 -0
- package/templates/base/automation/config.json +58 -0
- package/templates/base/automation/generators/code-mapper.js +308 -0
- package/templates/base/automation/generators/index-builder.js +321 -0
- package/templates/base/automation/hooks/post-commit.sh +83 -0
- package/templates/base/automation/hooks/pre-commit.sh +103 -0
- package/templates/base/ci-templates/README.md +108 -0
- package/templates/base/ci-templates/github-actions/context-check.yml +144 -0
- package/templates/base/ci-templates/github-actions/validate-docs.yml +105 -0
- package/templates/base/commands/analytics.md +238 -0
- package/templates/base/commands/auto-sync.md +172 -0
- package/templates/base/commands/collab.md +194 -0
- package/templates/base/commands/help.md +450 -0
- package/templates/base/commands/rpi-implement.md +115 -0
- package/templates/base/commands/rpi-plan.md +93 -0
- package/templates/base/commands/rpi-research.md +88 -0
- package/templates/base/commands/session-resume.md +144 -0
- package/templates/base/commands/session-save.md +112 -0
- package/templates/base/commands/validate-all.md +77 -0
- package/templates/base/commands/verify-docs-current.md +86 -0
- package/templates/base/config/base.json +57 -0
- package/templates/base/config/environments/development.json +13 -0
- package/templates/base/config/environments/production.json +17 -0
- package/templates/base/config/environments/staging.json +13 -0
- package/templates/base/config/local.json.example +21 -0
- package/templates/base/context/.meta/generated-at.json +18 -0
- package/templates/base/context/ARCHITECTURE_SNAPSHOT.md +156 -0
- package/templates/base/context/CODE_TO_WORKFLOW_MAP.md +94 -0
- package/templates/base/context/FILE_OWNERSHIP.md +57 -0
- package/templates/base/context/INTEGRATION_POINTS.md +92 -0
- package/templates/base/context/KNOWN_GOTCHAS.md +195 -0
- package/templates/base/context/TESTING_MAP.md +95 -0
- package/templates/base/context/WORKFLOW_INDEX.md +129 -0
- package/templates/base/context/workflows/WORKFLOW_TEMPLATE.md +294 -0
- package/templates/base/indexes/agents/CAPABILITY_MATRIX.md +255 -0
- package/templates/base/indexes/agents/CATEGORY_INDEX.md +44 -0
- package/templates/base/indexes/code/CATEGORY_INDEX.md +38 -0
- package/templates/base/indexes/routing/CATEGORY_INDEX.md +39 -0
- package/templates/base/indexes/search/CATEGORY_INDEX.md +39 -0
- package/templates/base/indexes/workflows/CATEGORY_INDEX.md +38 -0
- package/templates/base/knowledge/README.md +98 -0
- package/templates/base/knowledge/sessions/README.md +88 -0
- package/templates/base/knowledge/sessions/TEMPLATE.md +150 -0
- package/templates/base/knowledge/shared/decisions/0001-adopt-context-engineering.md +144 -0
- package/templates/base/knowledge/shared/decisions/README.md +49 -0
- package/templates/base/knowledge/shared/decisions/TEMPLATE.md +123 -0
- package/templates/base/knowledge/shared/patterns/README.md +62 -0
- package/templates/base/knowledge/shared/patterns/TEMPLATE.md +120 -0
- package/templates/base/plans/PLAN_TEMPLATE.md +250 -0
- package/templates/base/plans/active/.gitkeep +0 -0
- package/templates/base/plans/completed/.gitkeep +0 -0
- package/templates/base/research/RESEARCH_TEMPLATE.md +153 -0
- package/templates/base/research/active/.gitkeep +0 -0
- package/templates/base/research/completed/.gitkeep +0 -0
- package/templates/base/schemas/agent.schema.json +141 -0
- package/templates/base/schemas/anchors.schema.json +54 -0
- package/templates/base/schemas/automation.schema.json +93 -0
- package/templates/base/schemas/command.schema.json +134 -0
- package/templates/base/schemas/hashes.schema.json +40 -0
- package/templates/base/schemas/manifest.schema.json +117 -0
- package/templates/base/schemas/plan.schema.json +136 -0
- package/templates/base/schemas/research.schema.json +115 -0
- package/templates/base/schemas/roles.schema.json +34 -0
- package/templates/base/schemas/session.schema.json +77 -0
- package/templates/base/schemas/settings.schema.json +244 -0
- package/templates/base/schemas/staleness.schema.json +53 -0
- package/templates/base/schemas/team-config.schema.json +42 -0
- package/templates/base/schemas/workflow.schema.json +126 -0
- package/templates/base/session/checkpoints/.gitkeep +2 -0
- package/templates/base/session/current/state.json +20 -0
- package/templates/base/session/history/.gitkeep +2 -0
- package/templates/base/settings.json +3 -0
- package/templates/base/standards/COMPATIBILITY.md +219 -0
- package/templates/base/standards/EXTENSION_GUIDELINES.md +280 -0
- package/templates/base/standards/QUALITY_CHECKLIST.md +211 -0
- package/templates/base/standards/README.md +66 -0
- package/templates/base/sync/anchors.json +6 -0
- package/templates/base/sync/hashes.json +6 -0
- package/templates/base/sync/staleness.json +10 -0
- package/templates/base/team/README.md +168 -0
- package/templates/base/team/config.json +79 -0
- package/templates/base/team/roles.json +145 -0
- package/templates/base/tools/bin/claude-context.js +151 -0
- package/templates/base/tools/lib/anchor-resolver.js +276 -0
- package/templates/base/tools/lib/config-loader.js +363 -0
- package/templates/base/tools/lib/detector.js +350 -0
- package/templates/base/tools/lib/diagnose.js +206 -0
- package/templates/base/tools/lib/drift-detector.js +373 -0
- package/templates/base/tools/lib/errors.js +199 -0
- package/templates/base/tools/lib/index.js +36 -0
- package/templates/base/tools/lib/init.js +192 -0
- package/templates/base/tools/lib/logger.js +230 -0
- package/templates/base/tools/lib/placeholder.js +201 -0
- package/templates/base/tools/lib/session-manager.js +354 -0
- package/templates/base/tools/lib/validate.js +521 -0
- package/templates/base/tools/package.json +49 -0
- package/templates/handlebars/antigravity.hbs +337 -0
- package/templates/handlebars/claude.hbs +184 -0
- package/templates/handlebars/cline.hbs +63 -0
- package/templates/handlebars/copilot.hbs +131 -0
- package/templates/handlebars/partials/gotcha-list.hbs +11 -0
- package/templates/handlebars/partials/header.hbs +3 -0
- package/templates/handlebars/partials/workflow-summary.hbs +16 -0
|
@@ -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
|
+
};
|