musubi-sdd 3.10.0 → 5.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.
Files changed (40) hide show
  1. package/README.md +24 -19
  2. package/package.json +1 -1
  3. package/src/agents/agent-loop.js +532 -0
  4. package/src/agents/agentic/code-generator.js +767 -0
  5. package/src/agents/agentic/code-reviewer.js +698 -0
  6. package/src/agents/agentic/index.js +43 -0
  7. package/src/agents/function-tool.js +432 -0
  8. package/src/agents/index.js +45 -0
  9. package/src/agents/schema-generator.js +514 -0
  10. package/src/analyzers/ast-extractor.js +863 -0
  11. package/src/analyzers/context-optimizer.js +674 -0
  12. package/src/analyzers/repository-map.js +685 -0
  13. package/src/integrations/index.js +7 -1
  14. package/src/integrations/mcp/index.js +175 -0
  15. package/src/integrations/mcp/mcp-context-provider.js +472 -0
  16. package/src/integrations/mcp/mcp-discovery.js +436 -0
  17. package/src/integrations/mcp/mcp-tool-registry.js +467 -0
  18. package/src/integrations/mcp-connector.js +818 -0
  19. package/src/integrations/tool-discovery.js +589 -0
  20. package/src/managers/index.js +7 -0
  21. package/src/managers/skill-tools.js +565 -0
  22. package/src/monitoring/quality-dashboard.js +483 -0
  23. package/src/orchestration/agent-skill-binding.js +655 -0
  24. package/src/orchestration/error-handler.js +827 -0
  25. package/src/orchestration/index.js +235 -1
  26. package/src/orchestration/mcp-tool-adapters.js +896 -0
  27. package/src/orchestration/reasoning/index.js +58 -0
  28. package/src/orchestration/reasoning/planning-engine.js +831 -0
  29. package/src/orchestration/reasoning/reasoning-engine.js +710 -0
  30. package/src/orchestration/reasoning/self-correction.js +751 -0
  31. package/src/orchestration/skill-executor.js +665 -0
  32. package/src/orchestration/skill-registry.js +650 -0
  33. package/src/orchestration/workflow-examples.js +1072 -0
  34. package/src/orchestration/workflow-executor.js +779 -0
  35. package/src/phase4-integration.js +248 -0
  36. package/src/phase5-integration.js +402 -0
  37. package/src/steering/steering-auto-update.js +572 -0
  38. package/src/steering/steering-validator.js +547 -0
  39. package/src/templates/template-constraints.js +646 -0
  40. package/src/validators/advanced-validation.js +580 -0
@@ -0,0 +1,863 @@
1
+ /**
2
+ * AST Extractor
3
+ *
4
+ * Extracts Abstract Syntax Tree information from source code files.
5
+ * Provides structured analysis of code structure, symbols, and relationships.
6
+ *
7
+ * Part of MUSUBI v4.1.0 - Codebase Intelligence
8
+ * @version 1.0.0
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const { EventEmitter } = require('events');
14
+
15
+ /**
16
+ * @typedef {Object} SymbolInfo
17
+ * @property {string} name - Symbol name
18
+ * @property {string} type - Symbol type (function, class, variable, etc.)
19
+ * @property {number} line - Line number
20
+ * @property {number} column - Column number
21
+ * @property {string} [docstring] - Documentation string
22
+ * @property {string[]} [params] - Function parameters
23
+ * @property {string} [returnType] - Return type
24
+ * @property {string} [visibility] - public, private, protected
25
+ * @property {boolean} [isExported] - Whether exported
26
+ * @property {boolean} [isAsync] - Whether async
27
+ * @property {string[]} [decorators] - Applied decorators
28
+ */
29
+
30
+ /**
31
+ * @typedef {Object} ImportInfo
32
+ * @property {string} source - Import source
33
+ * @property {string[]} names - Imported names
34
+ * @property {boolean} isDefault - Whether default import
35
+ * @property {boolean} isNamespace - Whether namespace import
36
+ * @property {number} line - Line number
37
+ */
38
+
39
+ /**
40
+ * @typedef {Object} FileAST
41
+ * @property {string} path - File path
42
+ * @property {string} language - Detected language
43
+ * @property {SymbolInfo[]} symbols - Extracted symbols
44
+ * @property {ImportInfo[]} imports - Import statements
45
+ * @property {string[]} exports - Exported names
46
+ * @property {Object} structure - Hierarchical structure
47
+ * @property {Object} metadata - Additional metadata
48
+ */
49
+
50
+ /**
51
+ * Language-specific patterns
52
+ */
53
+ const PATTERNS = {
54
+ javascript: {
55
+ // Function declarations (removed ^ to match anywhere in line)
56
+ functionDecl: /(?:^|\n)\s*(?:export\s+)?(?:async\s+)?function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(([^)]*)\)/g,
57
+ // Arrow functions with const/let/var
58
+ arrowFunc: /(?:export\s+)?(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/g,
59
+ // Class declarations
60
+ classDecl: /(?:^|\n)\s*(?:export\s+)?class\s+([a-zA-Z_$][a-zA-Z0-9_$]*)(?:\s+extends\s+([a-zA-Z_$][a-zA-Z0-9_$]*))?/g,
61
+ // Class methods
62
+ methodDecl: /\n\s+(?:static\s+)?(?:async\s+)?(?:get\s+|set\s+)?([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\([^)]*\)\s*\{/g,
63
+ // Variable declarations
64
+ constDecl: /(?:export\s+)?(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=/g,
65
+ // ES6 imports
66
+ importStmt: /import\s+(?:(\{[^}]+\})|([a-zA-Z_$][a-zA-Z0-9_$]*)(?:\s*,\s*(\{[^}]+\}))?|\*\s+as\s+([a-zA-Z_$][a-zA-Z0-9_$]*))\s+from\s+['"](.*?)['"]/g,
67
+ // CommonJS require
68
+ requireStmt: /(?:const|let|var)\s+(?:(\{[^}]+\})|([a-zA-Z_$][a-zA-Z0-9_$]*))\s*=\s*require\s*\(\s*['"](.*?)['"]\s*\)/g,
69
+ // Exports - capture function/class/const names after export
70
+ exportStmt: /export\s+(?:default\s+)?(?:(const|let|var|function|class|async\s+function)\s+)?([a-zA-Z_$][a-zA-Z0-9_$]*)?/g,
71
+ namedExport: /export\s*\{([^}]+)\}/gm,
72
+ // JSDoc comments
73
+ jsdoc: /\/\*\*\s*([\s\S]*?)\s*\*\//g,
74
+ // Single-line comments
75
+ comment: /\/\/\s*(.+)$/g
76
+ },
77
+
78
+ typescript: {
79
+ // Extends JavaScript patterns
80
+ // Interface declarations
81
+ interfaceDecl: /(?:^|\n)\s*(?:export\s+)?interface\s+([a-zA-Z_$][a-zA-Z0-9_$]*)(?:<[^>]+>)?(?:\s+extends\s+([^{]+))?/g,
82
+ // Type aliases
83
+ typeDecl: /(?:^|\n)\s*(?:export\s+)?type\s+([a-zA-Z_$][a-zA-Z0-9_$]*)(?:<[^>]+>)?\s*=/g,
84
+ // Enum declarations
85
+ enumDecl: /(?:^|\n)\s*(?:export\s+)?(?:const\s+)?enum\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g,
86
+ // Function with types
87
+ typedFunc: /(?:export\s+)?(?:async\s+)?function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)(?:<[^>]+>)?\s*\(([^)]*)\)\s*:\s*([^{]+)/g
88
+ },
89
+
90
+ python: {
91
+ // Function definitions
92
+ functionDef: /(?:^|\n)(?:async\s+)?def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(([^)]*)\)(?:\s*->\s*([^\s:]+))?/g,
93
+ // Class definitions
94
+ classDef: /(?:^|\n)class\s+([a-zA-Z_][a-zA-Z0-9_]*)(?:\s*\(([^)]*)\))?/g,
95
+ // Method definitions (inside class)
96
+ methodDef: /\n\s+(?:async\s+)?def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(self(?:,\s*)?([^)]*)\)/g,
97
+ // Decorators
98
+ decorator: /(?:^|\n)@([a-zA-Z_][a-zA-Z0-9_.]*(?:\([^)]*\))?)/g,
99
+ // Imports
100
+ importFrom: /(?:^|\n)from\s+([^\s]+)\s+import\s+(.+)/g,
101
+ importModule: /(?:^|\n)import\s+([^\s]+)(?:\s+as\s+([^\s]+))?/g,
102
+ // Docstrings
103
+ docstring: /"""([\s\S]*?)"""|'''([\s\S]*?)'''/g,
104
+ // Type hints
105
+ typeHint: /:\s*([a-zA-Z_][a-zA-Z0-9_\[\],\s]*)/g
106
+ }
107
+ };
108
+
109
+ /**
110
+ * AST Extractor class
111
+ * @extends EventEmitter
112
+ */
113
+ class ASTExtractor extends EventEmitter {
114
+ /**
115
+ * Create AST extractor
116
+ * @param {Object} options - Configuration options
117
+ * @param {string[]} [options.supportedLanguages] - Languages to support
118
+ * @param {boolean} [options.includeDocstrings=true] - Include documentation
119
+ * @param {boolean} [options.extractComments=false] - Extract inline comments
120
+ */
121
+ constructor(options = {}) {
122
+ super();
123
+ this.supportedLanguages = options.supportedLanguages || ['javascript', 'typescript', 'python'];
124
+ this.includeDocstrings = options.includeDocstrings ?? true;
125
+ this.extractComments = options.extractComments ?? false;
126
+
127
+ // Results cache
128
+ this.cache = new Map();
129
+ }
130
+
131
+ /**
132
+ * Extract AST from file
133
+ * @param {string} filePath - File path
134
+ * @returns {Promise<FileAST>}
135
+ */
136
+ async extractFromFile(filePath) {
137
+ const content = await fs.promises.readFile(filePath, 'utf-8');
138
+ const language = this.detectLanguage(filePath);
139
+
140
+ if (!this.supportedLanguages.includes(language)) {
141
+ return {
142
+ path: filePath,
143
+ language,
144
+ symbols: [],
145
+ imports: [],
146
+ exports: [],
147
+ structure: {},
148
+ metadata: { supported: false }
149
+ };
150
+ }
151
+
152
+ return this.extract(content, language, filePath);
153
+ }
154
+
155
+ /**
156
+ * Extract AST from content
157
+ * @param {string} content - Source code content
158
+ * @param {string} language - Programming language
159
+ * @param {string} [filePath='<source>'] - Optional file path
160
+ * @returns {FileAST}
161
+ */
162
+ extract(content, language, filePath = '<source>') {
163
+ this.emit('extract:start', { filePath, language });
164
+
165
+ const lines = content.split('\n');
166
+ const symbols = [];
167
+ const imports = [];
168
+ const exports = [];
169
+ const structure = {
170
+ classes: [],
171
+ functions: [],
172
+ variables: []
173
+ };
174
+
175
+ try {
176
+ switch (language) {
177
+ case 'javascript':
178
+ case 'typescript':
179
+ this.extractJavaScript(content, lines, symbols, imports, exports, structure);
180
+ if (language === 'typescript') {
181
+ this.extractTypeScript(content, lines, symbols, structure);
182
+ }
183
+ break;
184
+ case 'python':
185
+ this.extractPython(content, lines, symbols, imports, exports, structure);
186
+ break;
187
+ }
188
+
189
+ const result = {
190
+ path: filePath,
191
+ language,
192
+ symbols,
193
+ imports,
194
+ exports,
195
+ structure,
196
+ metadata: {
197
+ lineCount: lines.length,
198
+ symbolCount: symbols.length,
199
+ extractedAt: new Date().toISOString()
200
+ }
201
+ };
202
+
203
+ this.emit('extract:complete', result);
204
+ return result;
205
+
206
+ } catch (error) {
207
+ this.emit('extract:error', { filePath, error });
208
+ return {
209
+ path: filePath,
210
+ language,
211
+ symbols: [],
212
+ imports: [],
213
+ exports: [],
214
+ structure: {},
215
+ metadata: { error: error.message }
216
+ };
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Extract JavaScript/TypeScript patterns
222
+ * @private
223
+ */
224
+ extractJavaScript(content, lines, symbols, imports, exports, structure) {
225
+ const patterns = PATTERNS.javascript;
226
+
227
+ // Extract docstrings for association
228
+ const docstrings = this.extractDocstrings(content, 'javascript');
229
+
230
+ // Functions
231
+ let match;
232
+ const funcPattern = new RegExp(patterns.functionDecl.source, 'g');
233
+ while ((match = funcPattern.exec(content)) !== null) {
234
+ const line = this.getLineNumber(content, match.index);
235
+ const isExported = match[0].includes('export ');
236
+ const isAsync = match[0].includes('async');
237
+ const params = this.parseParams(match[2]);
238
+ const doc = this.findNearestDocstring(docstrings, line);
239
+
240
+ const symbol = {
241
+ name: match[1],
242
+ type: 'function',
243
+ line,
244
+ column: match.index - content.lastIndexOf('\n', match.index) - 1,
245
+ params,
246
+ isExported,
247
+ isAsync,
248
+ docstring: doc
249
+ };
250
+
251
+ symbols.push(symbol);
252
+ structure.functions.push(symbol.name);
253
+
254
+ if (isExported) {
255
+ exports.push(match[1]);
256
+ }
257
+ }
258
+
259
+ // Arrow functions
260
+ const arrowPattern = new RegExp(patterns.arrowFunc.source, 'g');
261
+ while ((match = arrowPattern.exec(content)) !== null) {
262
+ const line = this.getLineNumber(content, match.index);
263
+ const isExported = match[0].includes('export ');
264
+ const isAsync = match[0].includes('async');
265
+ const doc = this.findNearestDocstring(docstrings, line);
266
+
267
+ const symbol = {
268
+ name: match[1],
269
+ type: 'function',
270
+ line,
271
+ isExported,
272
+ isAsync,
273
+ docstring: doc
274
+ };
275
+
276
+ symbols.push(symbol);
277
+ structure.functions.push(symbol.name);
278
+
279
+ if (isExported) {
280
+ exports.push(match[1]);
281
+ }
282
+ }
283
+
284
+ // Classes
285
+ const classPattern = new RegExp(patterns.classDecl.source, 'g');
286
+ while ((match = classPattern.exec(content)) !== null) {
287
+ const line = this.getLineNumber(content, match.index);
288
+ const isExported = match[0].includes('export ');
289
+ const doc = this.findNearestDocstring(docstrings, line);
290
+
291
+ const classSymbol = {
292
+ name: match[1],
293
+ type: 'class',
294
+ line,
295
+ isExported,
296
+ extends: match[2] || null,
297
+ docstring: doc,
298
+ methods: []
299
+ };
300
+
301
+ // Extract class methods
302
+ const classEndIndex = this.findClassEnd(content, match.index);
303
+ const classContent = content.slice(match.index, classEndIndex);
304
+ const methodPattern = new RegExp(patterns.methodDecl.source, 'gm');
305
+ let methodMatch;
306
+
307
+ while ((methodMatch = methodPattern.exec(classContent)) !== null) {
308
+ classSymbol.methods.push(methodMatch[1]);
309
+
310
+ symbols.push({
311
+ name: `${match[1]}.${methodMatch[1]}`,
312
+ type: 'method',
313
+ line: line + this.getLineNumber(classContent, methodMatch.index) - 1,
314
+ parentClass: match[1]
315
+ });
316
+ }
317
+
318
+ symbols.push(classSymbol);
319
+ structure.classes.push({
320
+ name: classSymbol.name,
321
+ extends: classSymbol.extends,
322
+ methods: classSymbol.methods
323
+ });
324
+
325
+ if (isExported) {
326
+ exports.push(match[1]);
327
+ }
328
+ }
329
+
330
+ // Imports (ES6)
331
+ const importPattern = new RegExp(patterns.importStmt.source, 'gm');
332
+ while ((match = importPattern.exec(content)) !== null) {
333
+ const line = this.getLineNumber(content, match.index);
334
+ const source = match[5];
335
+ let names = [];
336
+ let isDefault = false;
337
+ let isNamespace = false;
338
+
339
+ if (match[1]) {
340
+ // Named imports { a, b }
341
+ names = match[1].replace(/[{}]/g, '').split(',').map(n => n.trim());
342
+ }
343
+ if (match[2]) {
344
+ // Default import
345
+ names.push(match[2]);
346
+ isDefault = true;
347
+ }
348
+ if (match[3]) {
349
+ // Additional named imports after default
350
+ names.push(...match[3].replace(/[{}]/g, '').split(',').map(n => n.trim()));
351
+ }
352
+ if (match[4]) {
353
+ // Namespace import * as X
354
+ names.push(match[4]);
355
+ isNamespace = true;
356
+ }
357
+
358
+ imports.push({ source, names, isDefault, isNamespace, line });
359
+ }
360
+
361
+ // CommonJS require
362
+ const requirePattern = new RegExp(patterns.requireStmt.source, 'gm');
363
+ while ((match = requirePattern.exec(content)) !== null) {
364
+ const line = this.getLineNumber(content, match.index);
365
+ const source = match[3];
366
+ let names = [];
367
+
368
+ if (match[1]) {
369
+ // Destructured require
370
+ names = match[1].replace(/[{}]/g, '').split(',').map(n => n.trim());
371
+ }
372
+ if (match[2]) {
373
+ // Simple require
374
+ names.push(match[2]);
375
+ }
376
+
377
+ imports.push({ source, names, isDefault: !!match[2], isNamespace: false, line });
378
+ }
379
+
380
+ // Named exports: export { a, b }
381
+ const namedExportPattern = new RegExp(patterns.namedExport.source, 'gm');
382
+ while ((match = namedExportPattern.exec(content)) !== null) {
383
+ const names = match[1].split(',').map(n => n.trim().split(' as ')[0].trim());
384
+ exports.push(...names);
385
+ }
386
+
387
+ // Direct exports: export const/let/var/function/class name
388
+ const exportStmtPattern = new RegExp(patterns.exportStmt.source, 'gm');
389
+ while ((match = exportStmtPattern.exec(content)) !== null) {
390
+ // match[2] is the exported name (e.g., VALUE, helper, Service)
391
+ if (match[2] && !exports.includes(match[2])) {
392
+ exports.push(match[2]);
393
+ }
394
+ }
395
+ }
396
+
397
+ /**
398
+ * Extract TypeScript-specific patterns
399
+ * @private
400
+ */
401
+ extractTypeScript(content, lines, symbols, structure) {
402
+ const patterns = PATTERNS.typescript;
403
+ let match;
404
+
405
+ // Interfaces
406
+ const interfacePattern = new RegExp(patterns.interfaceDecl.source, 'gm');
407
+ while ((match = interfacePattern.exec(content)) !== null) {
408
+ const line = this.getLineNumber(content, match.index);
409
+ const isExported = match[0].includes('export');
410
+
411
+ symbols.push({
412
+ name: match[1],
413
+ type: 'interface',
414
+ line,
415
+ isExported,
416
+ extends: match[2] ? match[2].split(',').map(s => s.trim()) : []
417
+ });
418
+ }
419
+
420
+ // Type aliases
421
+ const typePattern = new RegExp(patterns.typeDecl.source, 'gm');
422
+ while ((match = typePattern.exec(content)) !== null) {
423
+ const line = this.getLineNumber(content, match.index);
424
+ const isExported = match[0].includes('export');
425
+
426
+ symbols.push({
427
+ name: match[1],
428
+ type: 'type',
429
+ line,
430
+ isExported
431
+ });
432
+ }
433
+
434
+ // Enums
435
+ const enumPattern = new RegExp(patterns.enumDecl.source, 'gm');
436
+ while ((match = enumPattern.exec(content)) !== null) {
437
+ const line = this.getLineNumber(content, match.index);
438
+ const isExported = match[0].includes('export');
439
+
440
+ symbols.push({
441
+ name: match[1],
442
+ type: 'enum',
443
+ line,
444
+ isExported
445
+ });
446
+ }
447
+ }
448
+
449
+ /**
450
+ * Extract Python patterns
451
+ * @private
452
+ */
453
+ extractPython(content, lines, symbols, imports, exports, structure) {
454
+ const patterns = PATTERNS.python;
455
+ let match;
456
+
457
+ // Collect decorators for association
458
+ const decorators = [];
459
+ const decoratorPattern = new RegExp(patterns.decorator.source, 'gm');
460
+ while ((match = decoratorPattern.exec(content)) !== null) {
461
+ const line = this.getLineNumber(content, match.index);
462
+ decorators.push({ name: match[1], line });
463
+ }
464
+
465
+ // Extract docstrings
466
+ const docstrings = this.extractDocstrings(content, 'python');
467
+
468
+ // Functions
469
+ const funcPattern = new RegExp(patterns.functionDef.source, 'gm');
470
+ while ((match = funcPattern.exec(content)) !== null) {
471
+ const line = this.getLineNumber(content, match.index);
472
+ const isAsync = match[0].includes('async');
473
+ const params = this.parseParams(match[2]);
474
+ const returnType = match[3] || null;
475
+ const doc = this.findNearestDocstring(docstrings, line);
476
+ const funcDecorators = decorators
477
+ .filter(d => d.line === line - 1)
478
+ .map(d => d.name);
479
+
480
+ const symbol = {
481
+ name: match[1],
482
+ type: 'function',
483
+ line,
484
+ params,
485
+ returnType,
486
+ isAsync,
487
+ docstring: doc,
488
+ decorators: funcDecorators,
489
+ visibility: match[1].startsWith('_') ? 'private' : 'public'
490
+ };
491
+
492
+ symbols.push(symbol);
493
+ structure.functions.push(symbol.name);
494
+
495
+ // Python uses __all__ for explicit exports, but we mark non-underscore as potentially exported
496
+ if (!match[1].startsWith('_')) {
497
+ exports.push(match[1]);
498
+ }
499
+ }
500
+
501
+ // Classes
502
+ const classPattern = new RegExp(patterns.classDef.source, 'gm');
503
+ while ((match = classPattern.exec(content)) !== null) {
504
+ const line = this.getLineNumber(content, match.index);
505
+ const doc = this.findNearestDocstring(docstrings, line);
506
+ const baseClasses = match[2] ? match[2].split(',').map(s => s.trim()) : [];
507
+ const classDecorators = decorators
508
+ .filter(d => d.line === line - 1)
509
+ .map(d => d.name);
510
+
511
+ const classSymbol = {
512
+ name: match[1],
513
+ type: 'class',
514
+ line,
515
+ extends: baseClasses,
516
+ docstring: doc,
517
+ decorators: classDecorators,
518
+ methods: [],
519
+ visibility: match[1].startsWith('_') ? 'private' : 'public'
520
+ };
521
+
522
+ // Extract class methods
523
+ const classEndIndex = this.findPythonClassEnd(content, lines, match.index);
524
+ const classContent = content.slice(match.index, classEndIndex);
525
+ const methodPattern = new RegExp(patterns.methodDef.source, 'gm');
526
+ let methodMatch;
527
+
528
+ while ((methodMatch = methodPattern.exec(classContent)) !== null) {
529
+ const methodName = methodMatch[1];
530
+ classSymbol.methods.push(methodName);
531
+
532
+ symbols.push({
533
+ name: `${match[1]}.${methodName}`,
534
+ type: 'method',
535
+ line: line + this.getLineNumber(classContent, methodMatch.index) - 1,
536
+ parentClass: match[1],
537
+ visibility: methodName.startsWith('_') ? 'private' : 'public'
538
+ });
539
+ }
540
+
541
+ symbols.push(classSymbol);
542
+ structure.classes.push({
543
+ name: classSymbol.name,
544
+ extends: classSymbol.extends,
545
+ methods: classSymbol.methods
546
+ });
547
+
548
+ if (!match[1].startsWith('_')) {
549
+ exports.push(match[1]);
550
+ }
551
+ }
552
+
553
+ // Imports
554
+ const importFromPattern = new RegExp(patterns.importFrom.source, 'gm');
555
+ while ((match = importFromPattern.exec(content)) !== null) {
556
+ const line = this.getLineNumber(content, match.index);
557
+ const source = match[1];
558
+ const names = match[2].split(',').map(n => {
559
+ const parts = n.trim().split(' as ');
560
+ return parts[0].trim();
561
+ });
562
+
563
+ imports.push({ source, names, isDefault: false, isNamespace: false, line });
564
+ }
565
+
566
+ const importModulePattern = new RegExp(patterns.importModule.source, 'gm');
567
+ while ((match = importModulePattern.exec(content)) !== null) {
568
+ const line = this.getLineNumber(content, match.index);
569
+ const source = match[1];
570
+ const alias = match[2] || match[1];
571
+
572
+ imports.push({ source, names: [alias], isDefault: true, isNamespace: false, line });
573
+ }
574
+ }
575
+
576
+ /**
577
+ * Extract docstrings from content
578
+ * @private
579
+ */
580
+ extractDocstrings(content, language) {
581
+ const docstrings = [];
582
+ let match;
583
+
584
+ if (language === 'javascript' || language === 'typescript') {
585
+ const pattern = PATTERNS.javascript.jsdoc;
586
+ const jsdocPattern = new RegExp(pattern.source, 'gm');
587
+ while ((match = jsdocPattern.exec(content)) !== null) {
588
+ const line = this.getLineNumber(content, match.index);
589
+ const text = match[1]
590
+ .replace(/^\s*\*\s?/gm, '')
591
+ .trim();
592
+ docstrings.push({ line, text });
593
+ }
594
+ } else if (language === 'python') {
595
+ const pattern = PATTERNS.python.docstring;
596
+ const docPattern = new RegExp(pattern.source, 'gm');
597
+ while ((match = docPattern.exec(content)) !== null) {
598
+ const line = this.getLineNumber(content, match.index);
599
+ const text = (match[1] || match[2]).trim();
600
+ docstrings.push({ line, text });
601
+ }
602
+ }
603
+
604
+ return docstrings;
605
+ }
606
+
607
+ /**
608
+ * Find nearest docstring before a line
609
+ * @private
610
+ */
611
+ findNearestDocstring(docstrings, line) {
612
+ if (!this.includeDocstrings) return null;
613
+
614
+ // Look for docstring within 10 lines before or 2 lines after
615
+ // JSDoc/docstrings can span multiple lines, so we need a wider range
616
+ const candidates = docstrings.filter(d =>
617
+ d.line >= line - 10 && d.line <= line + 2
618
+ );
619
+
620
+ if (candidates.length === 0) return null;
621
+
622
+ // Return the closest one that comes before the target line
623
+ const before = candidates.filter(d => d.line <= line);
624
+ if (before.length > 0) {
625
+ // Sort by line descending to get the closest before
626
+ before.sort((a, b) => b.line - a.line);
627
+ return before[0].text;
628
+ }
629
+
630
+ // Fall back to any candidate
631
+ candidates.sort((a, b) => Math.abs(a.line - line) - Math.abs(b.line - line));
632
+ return candidates[0].text;
633
+ }
634
+
635
+ /**
636
+ * Get line number from character index
637
+ * @private
638
+ */
639
+ getLineNumber(content, index) {
640
+ return content.slice(0, index).split('\n').length;
641
+ }
642
+
643
+ /**
644
+ * Parse function parameters
645
+ * @private
646
+ */
647
+ parseParams(paramsStr) {
648
+ if (!paramsStr || !paramsStr.trim()) return [];
649
+
650
+ return paramsStr
651
+ .split(',')
652
+ .map(p => p.trim())
653
+ .filter(p => p)
654
+ .map(p => {
655
+ // Remove default values and type annotations for basic param name
656
+ const name = p.split('=')[0].split(':')[0].trim();
657
+ return name;
658
+ });
659
+ }
660
+
661
+ /**
662
+ * Find end of JavaScript class
663
+ * @private
664
+ */
665
+ findClassEnd(content, startIndex) {
666
+ let depth = 0;
667
+ let inClass = false;
668
+
669
+ for (let i = startIndex; i < content.length; i++) {
670
+ if (content[i] === '{') {
671
+ depth++;
672
+ inClass = true;
673
+ } else if (content[i] === '}') {
674
+ depth--;
675
+ if (inClass && depth === 0) {
676
+ return i + 1;
677
+ }
678
+ }
679
+ }
680
+
681
+ return content.length;
682
+ }
683
+
684
+ /**
685
+ * Find end of Python class (by indentation)
686
+ * @private
687
+ */
688
+ findPythonClassEnd(content, lines, startIndex) {
689
+ const startLine = this.getLineNumber(content, startIndex);
690
+ const classLine = lines[startLine - 1];
691
+ const classIndent = classLine.match(/^(\s*)/)[1].length;
692
+
693
+ for (let i = startLine; i < lines.length; i++) {
694
+ const line = lines[i];
695
+ if (line.trim() === '') continue;
696
+
697
+ const indent = line.match(/^(\s*)/)[1].length;
698
+ if (indent <= classIndent && i > startLine) {
699
+ // Found line with same or less indentation
700
+ let charIndex = 0;
701
+ for (let j = 0; j < i; j++) {
702
+ charIndex += lines[j].length + 1;
703
+ }
704
+ return charIndex;
705
+ }
706
+ }
707
+
708
+ return content.length;
709
+ }
710
+
711
+ /**
712
+ * Detect language from file path
713
+ * @param {string} filePath - File path
714
+ * @returns {string}
715
+ */
716
+ detectLanguage(filePath) {
717
+ const ext = path.extname(filePath).toLowerCase();
718
+ const langMap = {
719
+ '.js': 'javascript',
720
+ '.mjs': 'javascript',
721
+ '.cjs': 'javascript',
722
+ '.jsx': 'javascript',
723
+ '.ts': 'typescript',
724
+ '.tsx': 'typescript',
725
+ '.py': 'python'
726
+ };
727
+ return langMap[ext] || 'unknown';
728
+ }
729
+
730
+ /**
731
+ * Generate symbol summary for LLM context
732
+ * @param {FileAST} ast - Parsed AST
733
+ * @returns {string}
734
+ */
735
+ toSummary(ast) {
736
+ let summary = `# ${ast.path}\n\n`;
737
+ summary += `Language: ${ast.language}\n`;
738
+ summary += `Lines: ${ast.metadata.lineCount}\n`;
739
+ summary += `Symbols: ${ast.metadata.symbolCount}\n\n`;
740
+
741
+ // Imports
742
+ if (ast.imports.length > 0) {
743
+ summary += `## Dependencies\n\n`;
744
+ for (const imp of ast.imports) {
745
+ summary += `- ${imp.source}: ${imp.names.join(', ')}\n`;
746
+ }
747
+ summary += '\n';
748
+ }
749
+
750
+ // Classes
751
+ const classes = ast.symbols.filter(s => s.type === 'class');
752
+ if (classes.length > 0) {
753
+ summary += `## Classes\n\n`;
754
+ for (const cls of classes) {
755
+ summary += `### ${cls.name}`;
756
+ if (cls.extends) {
757
+ summary += ` extends ${Array.isArray(cls.extends) ? cls.extends.join(', ') : cls.extends}`;
758
+ }
759
+ summary += '\n';
760
+
761
+ if (cls.docstring) {
762
+ summary += `${cls.docstring}\n`;
763
+ }
764
+
765
+ if (cls.methods && cls.methods.length > 0) {
766
+ summary += `Methods: ${cls.methods.join(', ')}\n`;
767
+ }
768
+ summary += '\n';
769
+ }
770
+ }
771
+
772
+ // Functions
773
+ const functions = ast.symbols.filter(s => s.type === 'function');
774
+ if (functions.length > 0) {
775
+ summary += `## Functions\n\n`;
776
+ for (const func of functions) {
777
+ summary += `- \`${func.name}(${(func.params || []).join(', ')})\``;
778
+ if (func.isAsync) summary += ' (async)';
779
+ if (func.isExported) summary += ' [exported]';
780
+ summary += '\n';
781
+
782
+ if (func.docstring) {
783
+ summary += ` ${func.docstring.split('\n')[0]}\n`;
784
+ }
785
+ }
786
+ summary += '\n';
787
+ }
788
+
789
+ // Exports
790
+ if (ast.exports.length > 0) {
791
+ summary += `## Exports\n\n`;
792
+ summary += ast.exports.join(', ') + '\n';
793
+ }
794
+
795
+ return summary;
796
+ }
797
+
798
+ /**
799
+ * Get cache key
800
+ * @param {string} filePath - File path
801
+ * @param {number} mtime - Modification time
802
+ * @returns {string}
803
+ */
804
+ getCacheKey(filePath, mtime) {
805
+ return `ast:${filePath}:${mtime}`;
806
+ }
807
+
808
+ /**
809
+ * Get from cache
810
+ * @param {string} filePath - File path
811
+ * @param {number} mtime - Modification time
812
+ * @returns {FileAST|null}
813
+ */
814
+ getFromCache(filePath, mtime) {
815
+ const key = this.getCacheKey(filePath, mtime);
816
+ return this.cache.get(key) || null;
817
+ }
818
+
819
+ /**
820
+ * Add to cache
821
+ * @param {string} filePath - File path
822
+ * @param {number} mtime - Modification time
823
+ * @param {FileAST} ast - AST result
824
+ */
825
+ addToCache(filePath, mtime, ast) {
826
+ const key = this.getCacheKey(filePath, mtime);
827
+ this.cache.set(key, ast);
828
+ }
829
+
830
+ /**
831
+ * Clear cache
832
+ */
833
+ clearCache() {
834
+ this.cache.clear();
835
+ }
836
+ }
837
+
838
+ /**
839
+ * Create AST extractor
840
+ * @param {Object} options - Options
841
+ * @returns {ASTExtractor}
842
+ */
843
+ function createASTExtractor(options = {}) {
844
+ return new ASTExtractor(options);
845
+ }
846
+
847
+ /**
848
+ * Extract AST from file
849
+ * @param {string} filePath - File path
850
+ * @param {Object} options - Options
851
+ * @returns {Promise<FileAST>}
852
+ */
853
+ async function extractAST(filePath, options = {}) {
854
+ const extractor = createASTExtractor(options);
855
+ return extractor.extractFromFile(filePath);
856
+ }
857
+
858
+ module.exports = {
859
+ ASTExtractor,
860
+ createASTExtractor,
861
+ extractAST,
862
+ PATTERNS
863
+ };