create-universal-ai-context 2.4.0 → 2.6.0-final

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 (153) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +331 -294
  3. package/bin/create-ai-context.js +1507 -764
  4. package/lib/adapters/aider.js +131 -131
  5. package/lib/adapters/antigravity.js +205 -205
  6. package/lib/adapters/claude.js +397 -397
  7. package/lib/adapters/cline.js +125 -125
  8. package/lib/adapters/continue.js +138 -138
  9. package/lib/adapters/copilot.js +131 -131
  10. package/lib/adapters/index.js +78 -78
  11. package/lib/adapters/windsurf.js +138 -138
  12. package/lib/ai-context-generator.js +234 -234
  13. package/lib/ai-orchestrator.js +432 -432
  14. package/lib/call-tracer.js +444 -444
  15. package/lib/content-preservation.js +243 -243
  16. package/lib/cross-tool-sync/file-watcher.js +274 -274
  17. package/lib/cross-tool-sync/index.js +41 -40
  18. package/lib/cross-tool-sync/sync-manager.js +540 -512
  19. package/lib/cross-tool-sync/sync-service.js +297 -297
  20. package/lib/detector.js +726 -726
  21. package/lib/doc-discovery.js +741 -741
  22. package/lib/drift-checker.js +920 -920
  23. package/lib/environment-detector.js +239 -239
  24. package/lib/index.js +399 -399
  25. package/lib/install-hooks.js +82 -82
  26. package/lib/installer.js +419 -419
  27. package/lib/migrate.js +328 -328
  28. package/lib/placeholder.js +632 -632
  29. package/lib/prompts.js +341 -341
  30. package/lib/smart-merge.js +540 -540
  31. package/lib/spinner.js +60 -60
  32. package/lib/static-analyzer.js +729 -729
  33. package/lib/template-coordination.js +148 -148
  34. package/lib/template-populator.js +843 -843
  35. package/lib/template-renderer.js +392 -392
  36. package/lib/utils/fs-wrapper.js +79 -79
  37. package/lib/utils/path-utils.js +60 -60
  38. package/lib/validate.js +155 -155
  39. package/package.json +1 -1
  40. package/templates/AI_CONTEXT.md.template +245 -245
  41. package/templates/base/README.md +260 -257
  42. package/templates/base/RPI_WORKFLOW_PLAN.md +325 -320
  43. package/templates/base/agents/api-developer.md +76 -76
  44. package/templates/base/agents/context-engineer.md +525 -525
  45. package/templates/base/agents/core-architect.md +76 -76
  46. package/templates/base/agents/database-ops.md +76 -76
  47. package/templates/base/agents/deployment-ops.md +76 -76
  48. package/templates/base/agents/integration-hub.md +76 -76
  49. package/templates/base/analytics/README.md +114 -114
  50. package/templates/base/automation/config.json +58 -58
  51. package/templates/base/automation/generators/code-mapper.js +308 -308
  52. package/templates/base/automation/generators/index-builder.js +321 -321
  53. package/templates/base/automation/hooks/post-commit.sh +83 -83
  54. package/templates/base/automation/hooks/pre-commit.sh +103 -103
  55. package/templates/base/ci-templates/README.md +108 -108
  56. package/templates/base/ci-templates/github-actions/context-check.yml +144 -144
  57. package/templates/base/ci-templates/github-actions/validate-docs.yml +105 -105
  58. package/templates/base/commands/analytics.md +238 -238
  59. package/templates/base/commands/auto-sync.md +172 -172
  60. package/templates/base/commands/collab.md +194 -194
  61. package/templates/base/commands/context-optimize.md +226 -0
  62. package/templates/base/commands/help.md +485 -450
  63. package/templates/base/commands/rpi-implement.md +164 -115
  64. package/templates/base/commands/rpi-plan.md +147 -93
  65. package/templates/base/commands/rpi-research.md +145 -88
  66. package/templates/base/commands/session-resume.md +144 -144
  67. package/templates/base/commands/session-save.md +112 -112
  68. package/templates/base/commands/validate-all.md +77 -77
  69. package/templates/base/commands/verify-docs-current.md +86 -86
  70. package/templates/base/config/base.json +57 -57
  71. package/templates/base/config/environments/development.json +13 -13
  72. package/templates/base/config/environments/production.json +17 -17
  73. package/templates/base/config/environments/staging.json +13 -13
  74. package/templates/base/config/local.json.example +21 -21
  75. package/templates/base/context/.meta/generated-at.json +18 -18
  76. package/templates/base/context/ARCHITECTURE_SNAPSHOT.md +156 -156
  77. package/templates/base/context/CODE_TO_WORKFLOW_MAP.md +94 -94
  78. package/templates/base/context/FILE_OWNERSHIP.md +57 -57
  79. package/templates/base/context/INTEGRATION_POINTS.md +92 -92
  80. package/templates/base/context/KNOWN_GOTCHAS.md +195 -195
  81. package/templates/base/context/TESTING_MAP.md +95 -95
  82. package/templates/base/context/WORKFLOW_INDEX.md +129 -129
  83. package/templates/base/context/workflows/WORKFLOW_TEMPLATE.md +294 -294
  84. package/templates/base/indexes/agents/CAPABILITY_MATRIX.md +255 -255
  85. package/templates/base/indexes/agents/CATEGORY_INDEX.md +44 -44
  86. package/templates/base/indexes/code/CATEGORY_INDEX.md +38 -38
  87. package/templates/base/indexes/routing/CATEGORY_INDEX.md +39 -39
  88. package/templates/base/indexes/search/CATEGORY_INDEX.md +39 -39
  89. package/templates/base/indexes/workflows/CATEGORY_INDEX.md +38 -38
  90. package/templates/base/knowledge/README.md +98 -98
  91. package/templates/base/knowledge/sessions/README.md +88 -88
  92. package/templates/base/knowledge/sessions/TEMPLATE.md +150 -150
  93. package/templates/base/knowledge/shared/decisions/0001-adopt-context-engineering.md +144 -144
  94. package/templates/base/knowledge/shared/decisions/README.md +49 -49
  95. package/templates/base/knowledge/shared/decisions/TEMPLATE.md +123 -123
  96. package/templates/base/knowledge/shared/patterns/README.md +62 -62
  97. package/templates/base/knowledge/shared/patterns/TEMPLATE.md +120 -120
  98. package/templates/base/plans/PLAN_TEMPLATE.md +316 -250
  99. package/templates/base/research/RESEARCH_TEMPLATE.md +245 -153
  100. package/templates/base/schemas/agent.schema.json +141 -141
  101. package/templates/base/schemas/anchors.schema.json +54 -54
  102. package/templates/base/schemas/automation.schema.json +93 -93
  103. package/templates/base/schemas/command.schema.json +134 -134
  104. package/templates/base/schemas/hashes.schema.json +40 -40
  105. package/templates/base/schemas/manifest.schema.json +117 -117
  106. package/templates/base/schemas/plan.schema.json +136 -136
  107. package/templates/base/schemas/research.schema.json +115 -115
  108. package/templates/base/schemas/roles.schema.json +34 -34
  109. package/templates/base/schemas/session.schema.json +77 -77
  110. package/templates/base/schemas/settings.schema.json +244 -244
  111. package/templates/base/schemas/staleness.schema.json +53 -53
  112. package/templates/base/schemas/team-config.schema.json +42 -42
  113. package/templates/base/schemas/workflow.schema.json +126 -126
  114. package/templates/base/session/checkpoints/.gitkeep +2 -2
  115. package/templates/base/session/current/state.json +20 -20
  116. package/templates/base/session/history/.gitkeep +2 -2
  117. package/templates/base/settings.json +3 -3
  118. package/templates/base/standards/COMPATIBILITY.md +219 -219
  119. package/templates/base/standards/EXTENSION_GUIDELINES.md +280 -280
  120. package/templates/base/standards/QUALITY_CHECKLIST.md +211 -211
  121. package/templates/base/standards/README.md +66 -66
  122. package/templates/base/sync/anchors.json +6 -6
  123. package/templates/base/sync/hashes.json +6 -6
  124. package/templates/base/sync/staleness.json +10 -10
  125. package/templates/base/team/README.md +168 -168
  126. package/templates/base/team/config.json +79 -79
  127. package/templates/base/team/roles.json +145 -145
  128. package/templates/base/tools/bin/claude-context.js +151 -151
  129. package/templates/base/tools/lib/anchor-resolver.js +276 -276
  130. package/templates/base/tools/lib/config-loader.js +363 -363
  131. package/templates/base/tools/lib/detector.js +350 -350
  132. package/templates/base/tools/lib/diagnose.js +206 -206
  133. package/templates/base/tools/lib/drift-detector.js +373 -373
  134. package/templates/base/tools/lib/errors.js +199 -199
  135. package/templates/base/tools/lib/index.js +36 -36
  136. package/templates/base/tools/lib/init.js +192 -192
  137. package/templates/base/tools/lib/logger.js +230 -230
  138. package/templates/base/tools/lib/placeholder.js +201 -201
  139. package/templates/base/tools/lib/session-manager.js +354 -354
  140. package/templates/base/tools/lib/validate.js +521 -521
  141. package/templates/base/tools/package.json +49 -49
  142. package/templates/handlebars/aider-config.hbs +146 -80
  143. package/templates/handlebars/antigravity.hbs +377 -377
  144. package/templates/handlebars/claude.hbs +183 -183
  145. package/templates/handlebars/cline.hbs +62 -62
  146. package/templates/handlebars/continue-config.hbs +116 -116
  147. package/templates/handlebars/copilot.hbs +130 -130
  148. package/templates/handlebars/partials/gotcha-list.hbs +11 -11
  149. package/templates/handlebars/partials/header.hbs +3 -3
  150. package/templates/handlebars/partials/workflow-summary.hbs +16 -16
  151. package/templates/handlebars/windsurf-rules.hbs +69 -69
  152. package/templates/hooks/post-commit.hbs +28 -29
  153. package/templates/hooks/pre-commit.hbs +46 -46
@@ -1,444 +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
- };
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
+ };