emdash-core 0.1.7__py3-none-any.whl

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 (187) hide show
  1. emdash_core/__init__.py +3 -0
  2. emdash_core/agent/__init__.py +37 -0
  3. emdash_core/agent/agents.py +225 -0
  4. emdash_core/agent/code_reviewer.py +476 -0
  5. emdash_core/agent/compaction.py +143 -0
  6. emdash_core/agent/context_manager.py +140 -0
  7. emdash_core/agent/events.py +338 -0
  8. emdash_core/agent/handlers.py +224 -0
  9. emdash_core/agent/inprocess_subagent.py +377 -0
  10. emdash_core/agent/mcp/__init__.py +50 -0
  11. emdash_core/agent/mcp/client.py +346 -0
  12. emdash_core/agent/mcp/config.py +302 -0
  13. emdash_core/agent/mcp/manager.py +496 -0
  14. emdash_core/agent/mcp/tool_factory.py +213 -0
  15. emdash_core/agent/prompts/__init__.py +38 -0
  16. emdash_core/agent/prompts/main_agent.py +104 -0
  17. emdash_core/agent/prompts/subagents.py +131 -0
  18. emdash_core/agent/prompts/workflow.py +136 -0
  19. emdash_core/agent/providers/__init__.py +34 -0
  20. emdash_core/agent/providers/base.py +143 -0
  21. emdash_core/agent/providers/factory.py +80 -0
  22. emdash_core/agent/providers/models.py +220 -0
  23. emdash_core/agent/providers/openai_provider.py +463 -0
  24. emdash_core/agent/providers/transformers_provider.py +217 -0
  25. emdash_core/agent/research/__init__.py +81 -0
  26. emdash_core/agent/research/agent.py +143 -0
  27. emdash_core/agent/research/controller.py +254 -0
  28. emdash_core/agent/research/critic.py +428 -0
  29. emdash_core/agent/research/macros.py +469 -0
  30. emdash_core/agent/research/planner.py +449 -0
  31. emdash_core/agent/research/researcher.py +436 -0
  32. emdash_core/agent/research/state.py +523 -0
  33. emdash_core/agent/research/synthesizer.py +594 -0
  34. emdash_core/agent/reviewer_profile.py +475 -0
  35. emdash_core/agent/rules.py +123 -0
  36. emdash_core/agent/runner.py +601 -0
  37. emdash_core/agent/session.py +262 -0
  38. emdash_core/agent/spec_schema.py +66 -0
  39. emdash_core/agent/specification.py +479 -0
  40. emdash_core/agent/subagent.py +397 -0
  41. emdash_core/agent/subagent_prompts.py +13 -0
  42. emdash_core/agent/toolkit.py +482 -0
  43. emdash_core/agent/toolkits/__init__.py +64 -0
  44. emdash_core/agent/toolkits/base.py +96 -0
  45. emdash_core/agent/toolkits/explore.py +47 -0
  46. emdash_core/agent/toolkits/plan.py +55 -0
  47. emdash_core/agent/tools/__init__.py +141 -0
  48. emdash_core/agent/tools/analytics.py +436 -0
  49. emdash_core/agent/tools/base.py +131 -0
  50. emdash_core/agent/tools/coding.py +484 -0
  51. emdash_core/agent/tools/github_mcp.py +592 -0
  52. emdash_core/agent/tools/history.py +13 -0
  53. emdash_core/agent/tools/modes.py +153 -0
  54. emdash_core/agent/tools/plan.py +206 -0
  55. emdash_core/agent/tools/plan_write.py +135 -0
  56. emdash_core/agent/tools/search.py +412 -0
  57. emdash_core/agent/tools/spec.py +341 -0
  58. emdash_core/agent/tools/task.py +262 -0
  59. emdash_core/agent/tools/task_output.py +204 -0
  60. emdash_core/agent/tools/tasks.py +454 -0
  61. emdash_core/agent/tools/traversal.py +588 -0
  62. emdash_core/agent/tools/web.py +179 -0
  63. emdash_core/analytics/__init__.py +5 -0
  64. emdash_core/analytics/engine.py +1286 -0
  65. emdash_core/api/__init__.py +5 -0
  66. emdash_core/api/agent.py +308 -0
  67. emdash_core/api/agents.py +154 -0
  68. emdash_core/api/analyze.py +264 -0
  69. emdash_core/api/auth.py +173 -0
  70. emdash_core/api/context.py +77 -0
  71. emdash_core/api/db.py +121 -0
  72. emdash_core/api/embed.py +131 -0
  73. emdash_core/api/feature.py +143 -0
  74. emdash_core/api/health.py +93 -0
  75. emdash_core/api/index.py +162 -0
  76. emdash_core/api/plan.py +110 -0
  77. emdash_core/api/projectmd.py +210 -0
  78. emdash_core/api/query.py +320 -0
  79. emdash_core/api/research.py +122 -0
  80. emdash_core/api/review.py +161 -0
  81. emdash_core/api/router.py +76 -0
  82. emdash_core/api/rules.py +116 -0
  83. emdash_core/api/search.py +119 -0
  84. emdash_core/api/spec.py +99 -0
  85. emdash_core/api/swarm.py +223 -0
  86. emdash_core/api/tasks.py +109 -0
  87. emdash_core/api/team.py +120 -0
  88. emdash_core/auth/__init__.py +17 -0
  89. emdash_core/auth/github.py +389 -0
  90. emdash_core/config.py +74 -0
  91. emdash_core/context/__init__.py +52 -0
  92. emdash_core/context/models.py +50 -0
  93. emdash_core/context/providers/__init__.py +11 -0
  94. emdash_core/context/providers/base.py +74 -0
  95. emdash_core/context/providers/explored_areas.py +183 -0
  96. emdash_core/context/providers/touched_areas.py +360 -0
  97. emdash_core/context/registry.py +73 -0
  98. emdash_core/context/reranker.py +199 -0
  99. emdash_core/context/service.py +260 -0
  100. emdash_core/context/session.py +352 -0
  101. emdash_core/core/__init__.py +104 -0
  102. emdash_core/core/config.py +454 -0
  103. emdash_core/core/exceptions.py +55 -0
  104. emdash_core/core/models.py +265 -0
  105. emdash_core/core/review_config.py +57 -0
  106. emdash_core/db/__init__.py +67 -0
  107. emdash_core/db/auth.py +134 -0
  108. emdash_core/db/models.py +91 -0
  109. emdash_core/db/provider.py +222 -0
  110. emdash_core/db/providers/__init__.py +5 -0
  111. emdash_core/db/providers/supabase.py +452 -0
  112. emdash_core/embeddings/__init__.py +24 -0
  113. emdash_core/embeddings/indexer.py +534 -0
  114. emdash_core/embeddings/models.py +192 -0
  115. emdash_core/embeddings/providers/__init__.py +7 -0
  116. emdash_core/embeddings/providers/base.py +112 -0
  117. emdash_core/embeddings/providers/fireworks.py +141 -0
  118. emdash_core/embeddings/providers/openai.py +104 -0
  119. emdash_core/embeddings/registry.py +146 -0
  120. emdash_core/embeddings/service.py +215 -0
  121. emdash_core/graph/__init__.py +26 -0
  122. emdash_core/graph/builder.py +134 -0
  123. emdash_core/graph/connection.py +692 -0
  124. emdash_core/graph/schema.py +416 -0
  125. emdash_core/graph/writer.py +667 -0
  126. emdash_core/ingestion/__init__.py +7 -0
  127. emdash_core/ingestion/change_detector.py +150 -0
  128. emdash_core/ingestion/git/__init__.py +5 -0
  129. emdash_core/ingestion/git/commit_analyzer.py +196 -0
  130. emdash_core/ingestion/github/__init__.py +6 -0
  131. emdash_core/ingestion/github/pr_fetcher.py +296 -0
  132. emdash_core/ingestion/github/task_extractor.py +100 -0
  133. emdash_core/ingestion/orchestrator.py +540 -0
  134. emdash_core/ingestion/parsers/__init__.py +10 -0
  135. emdash_core/ingestion/parsers/base_parser.py +66 -0
  136. emdash_core/ingestion/parsers/call_graph_builder.py +121 -0
  137. emdash_core/ingestion/parsers/class_extractor.py +154 -0
  138. emdash_core/ingestion/parsers/function_extractor.py +202 -0
  139. emdash_core/ingestion/parsers/import_analyzer.py +119 -0
  140. emdash_core/ingestion/parsers/python_parser.py +123 -0
  141. emdash_core/ingestion/parsers/registry.py +72 -0
  142. emdash_core/ingestion/parsers/ts_ast_parser.js +313 -0
  143. emdash_core/ingestion/parsers/typescript_parser.py +278 -0
  144. emdash_core/ingestion/repository.py +346 -0
  145. emdash_core/models/__init__.py +38 -0
  146. emdash_core/models/agent.py +68 -0
  147. emdash_core/models/index.py +77 -0
  148. emdash_core/models/query.py +113 -0
  149. emdash_core/planning/__init__.py +7 -0
  150. emdash_core/planning/agent_api.py +413 -0
  151. emdash_core/planning/context_builder.py +265 -0
  152. emdash_core/planning/feature_context.py +232 -0
  153. emdash_core/planning/feature_expander.py +646 -0
  154. emdash_core/planning/llm_explainer.py +198 -0
  155. emdash_core/planning/similarity.py +509 -0
  156. emdash_core/planning/team_focus.py +821 -0
  157. emdash_core/server.py +153 -0
  158. emdash_core/sse/__init__.py +5 -0
  159. emdash_core/sse/stream.py +196 -0
  160. emdash_core/swarm/__init__.py +17 -0
  161. emdash_core/swarm/merge_agent.py +383 -0
  162. emdash_core/swarm/session_manager.py +274 -0
  163. emdash_core/swarm/swarm_runner.py +226 -0
  164. emdash_core/swarm/task_definition.py +137 -0
  165. emdash_core/swarm/worker_spawner.py +319 -0
  166. emdash_core/swarm/worktree_manager.py +278 -0
  167. emdash_core/templates/__init__.py +10 -0
  168. emdash_core/templates/defaults/agent-builder.md.template +82 -0
  169. emdash_core/templates/defaults/focus.md.template +115 -0
  170. emdash_core/templates/defaults/pr-review-enhanced.md.template +309 -0
  171. emdash_core/templates/defaults/pr-review.md.template +80 -0
  172. emdash_core/templates/defaults/project.md.template +85 -0
  173. emdash_core/templates/defaults/research_critic.md.template +112 -0
  174. emdash_core/templates/defaults/research_planner.md.template +85 -0
  175. emdash_core/templates/defaults/research_synthesizer.md.template +128 -0
  176. emdash_core/templates/defaults/reviewer.md.template +81 -0
  177. emdash_core/templates/defaults/spec.md.template +41 -0
  178. emdash_core/templates/defaults/tasks.md.template +78 -0
  179. emdash_core/templates/loader.py +296 -0
  180. emdash_core/utils/__init__.py +45 -0
  181. emdash_core/utils/git.py +84 -0
  182. emdash_core/utils/image.py +502 -0
  183. emdash_core/utils/logger.py +51 -0
  184. emdash_core-0.1.7.dist-info/METADATA +35 -0
  185. emdash_core-0.1.7.dist-info/RECORD +187 -0
  186. emdash_core-0.1.7.dist-info/WHEEL +4 -0
  187. emdash_core-0.1.7.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,123 @@
1
+ """Main Python AST parser - coordinates all entity extraction."""
2
+
3
+ import ast
4
+ import hashlib
5
+ from pathlib import Path
6
+ from typing import List, Optional
7
+
8
+ from ...core.exceptions import ParsingError
9
+ from ...core.models import FileEntity, FileEntities
10
+ from .base_parser import BaseLanguageParser
11
+ from .class_extractor import ClassExtractor
12
+ from .function_extractor import FunctionExtractor
13
+ from .import_analyzer import ImportAnalyzer
14
+ from .call_graph_builder import CallGraphBuilder
15
+ from ...utils.logger import log
16
+
17
+
18
+ class PythonParser(BaseLanguageParser):
19
+ """Parses Python files and extracts code entities."""
20
+
21
+ def __init__(self, file_path: Path, repo_root: Optional[Path] = None):
22
+ """Initialize Python parser.
23
+
24
+ Args:
25
+ file_path: Path to Python file
26
+ repo_root: Root directory of repository (for resolving qualified names)
27
+ """
28
+ super().__init__(file_path, repo_root)
29
+ self.tree: Optional[ast.AST] = None
30
+ self.content: Optional[str] = None
31
+
32
+ @classmethod
33
+ def get_supported_extensions(cls) -> List[str]:
34
+ """Return list of supported file extensions.
35
+
36
+ Returns:
37
+ List containing '.py'
38
+ """
39
+ return ['.py']
40
+
41
+ def parse(self) -> FileEntities:
42
+ """Parse the file and extract all entities.
43
+
44
+ Returns:
45
+ FileEntities containing all extracted entities
46
+
47
+ Raises:
48
+ ParsingError: If file cannot be parsed
49
+ """
50
+ try:
51
+ # Read file content
52
+ with open(self.file_path, 'r', encoding='utf-8', errors='ignore') as f:
53
+ self.content = f.read()
54
+
55
+ # Parse AST
56
+ try:
57
+ self.tree = ast.parse(self.content, filename=str(self.file_path))
58
+ except SyntaxError as e:
59
+ log.warning(f"Syntax error in {self.file_path}: {e}")
60
+ # Return empty entities for files with syntax errors
61
+ return FileEntities()
62
+
63
+ # Calculate module name
64
+ self.module_name = self._calculate_module_name()
65
+
66
+ # Extract file metadata
67
+ file_entity = self._extract_file_entity()
68
+
69
+ # Extract entities using specialized extractors
70
+ class_extractor = ClassExtractor(
71
+ self.tree, self.file_path, self.module_name
72
+ )
73
+ classes = class_extractor.extract()
74
+
75
+ function_extractor = FunctionExtractor(
76
+ self.tree, self.file_path, self.module_name
77
+ )
78
+ functions = function_extractor.extract()
79
+
80
+ import_analyzer = ImportAnalyzer(
81
+ self.tree, self.file_path, self.module_name
82
+ )
83
+ imports, modules = import_analyzer.extract()
84
+
85
+ call_graph_builder = CallGraphBuilder(
86
+ self.tree, self.module_name
87
+ )
88
+ call_graph_builder.build(functions)
89
+
90
+ return FileEntities(
91
+ file=file_entity,
92
+ classes=classes,
93
+ functions=functions,
94
+ modules=modules,
95
+ imports=imports,
96
+ )
97
+
98
+ except Exception as e:
99
+ raise ParsingError(f"Failed to parse {self.file_path}: {e}")
100
+
101
+ def _extract_file_entity(self) -> FileEntity:
102
+ """Extract file metadata."""
103
+ content_hash = hashlib.sha256(self.content.encode()).hexdigest()
104
+ return FileEntity.from_path(self.file_path, content_hash)
105
+
106
+
107
+ def parse_file(file_path: Path, repo_root: Optional[Path] = None) -> FileEntities:
108
+ """Convenience function to parse a single file.
109
+
110
+ Args:
111
+ file_path: Path to Python file
112
+ repo_root: Root directory of repository
113
+
114
+ Returns:
115
+ FileEntities containing all extracted entities
116
+ """
117
+ parser = PythonParser(file_path, repo_root)
118
+ return parser.parse()
119
+
120
+
121
+ # Auto-register Python parser with registry
122
+ from .registry import ParserRegistry
123
+ ParserRegistry.register(PythonParser)
@@ -0,0 +1,72 @@
1
+ """Registry for language-specific parsers."""
2
+
3
+ from typing import Dict, Type, Optional, List
4
+ from pathlib import Path
5
+
6
+ from .base_parser import BaseLanguageParser
7
+ from ...utils.logger import log
8
+
9
+
10
+ class ParserRegistry:
11
+ """Registry for language-specific parsers.
12
+
13
+ Maps file extensions to parser classes and provides
14
+ lookup functionality for multi-language support.
15
+ """
16
+
17
+ _parsers: Dict[str, Type[BaseLanguageParser]] = {}
18
+
19
+ @classmethod
20
+ def register(cls, parser_class: Type[BaseLanguageParser]):
21
+ """Register a parser for its supported file extensions.
22
+
23
+ Args:
24
+ parser_class: Parser class to register
25
+ """
26
+ extensions = parser_class.get_supported_extensions()
27
+ for ext in extensions:
28
+ cls._parsers[ext.lower()] = parser_class
29
+ log.debug(f"Registered {parser_class.__name__} for {ext}")
30
+
31
+ @classmethod
32
+ def get_parser(cls, file_path: Path) -> Optional[Type[BaseLanguageParser]]:
33
+ """Get parser class for a file based on extension.
34
+
35
+ Args:
36
+ file_path: Path to source file
37
+
38
+ Returns:
39
+ Parser class or None if not supported
40
+ """
41
+ ext = file_path.suffix.lower()
42
+ return cls._parsers.get(ext)
43
+
44
+ @classmethod
45
+ def get_all_extensions(cls) -> List[str]:
46
+ """Get all supported file extensions.
47
+
48
+ Returns:
49
+ List of file extensions (e.g., ['.py', '.ts', '.js'])
50
+ """
51
+ return list(cls._parsers.keys())
52
+
53
+ @classmethod
54
+ def is_supported(cls, file_path: Path) -> bool:
55
+ """Check if file extension is supported.
56
+
57
+ Args:
58
+ file_path: Path to check
59
+
60
+ Returns:
61
+ True if supported, False otherwise
62
+ """
63
+ return file_path.suffix.lower() in cls._parsers
64
+
65
+ @classmethod
66
+ def list_parsers(cls) -> Dict[str, str]:
67
+ """List all registered parsers.
68
+
69
+ Returns:
70
+ Dictionary mapping extension to parser class name
71
+ """
72
+ return {ext: parser.__name__ for ext, parser in cls._parsers.items()}
@@ -0,0 +1,313 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * TypeScript AST parser using TypeScript compiler API
4
+ * Outputs JSON representation of AST for Python consumption
5
+ */
6
+
7
+ const ts = require('typescript');
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ function parseTypeScriptFile(filePath) {
12
+ const content = fs.readFileSync(filePath, 'utf8');
13
+
14
+ const sourceFile = ts.createSourceFile(
15
+ path.basename(filePath),
16
+ content,
17
+ ts.ScriptTarget.Latest,
18
+ true
19
+ );
20
+
21
+ const entities = {
22
+ classes: [],
23
+ functions: [],
24
+ imports: []
25
+ };
26
+
27
+ function visit(node) {
28
+ // Extract classes and interfaces
29
+ if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) {
30
+ entities.classes.push(extractClass(node, sourceFile));
31
+ }
32
+
33
+ // Extract top-level functions (not methods)
34
+ if (ts.isFunctionDeclaration(node)) {
35
+ entities.functions.push(extractFunction(node, sourceFile, false));
36
+ }
37
+
38
+ // Extract arrow functions assigned to variables
39
+ if (ts.isVariableStatement(node)) {
40
+ node.declarationList.declarations.forEach(decl => {
41
+ if (decl.initializer && ts.isArrowFunction(decl.initializer)) {
42
+ const funcData = extractArrowFunction(decl, sourceFile);
43
+ if (funcData) {
44
+ entities.functions.push(funcData);
45
+ }
46
+ }
47
+ });
48
+ }
49
+
50
+ // Extract imports
51
+ if (ts.isImportDeclaration(node)) {
52
+ entities.imports.push(extractImport(node, sourceFile));
53
+ }
54
+
55
+ ts.forEachChild(node, visit);
56
+ }
57
+
58
+ visit(sourceFile);
59
+ return entities;
60
+ }
61
+
62
+ function extractClass(node, sourceFile) {
63
+ const pos = sourceFile.getLineAndCharacterOfPosition(node.getStart());
64
+ const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
65
+
66
+ const methods = [];
67
+ const properties = [];
68
+ const baseClasses = [];
69
+
70
+ // Extract heritage (extends/implements)
71
+ if (node.heritageClauses) {
72
+ node.heritageClauses.forEach(clause => {
73
+ clause.types.forEach(type => {
74
+ baseClasses.push(type.expression.getText(sourceFile));
75
+ });
76
+ });
77
+ }
78
+
79
+ // Extract members
80
+ const methodEntities = [];
81
+ if (node.members) {
82
+ node.members.forEach(member => {
83
+ if (ts.isMethodDeclaration(member) || ts.isMethodSignature(member)) {
84
+ const methodName = member.name ? member.name.getText(sourceFile) : 'anonymous';
85
+ methods.push(methodName);
86
+ // Also extract method as a function entity with calls
87
+ methodEntities.push(extractFunction(member, sourceFile, true));
88
+ } else if (ts.isPropertyDeclaration(member) || ts.isPropertySignature(member)) {
89
+ const propName = member.name ? member.name.getText(sourceFile) : 'anonymous';
90
+ properties.push(propName);
91
+ }
92
+ });
93
+ }
94
+
95
+ // Extract JSDoc comment
96
+ const docstring = extractDocstring(node, sourceFile);
97
+
98
+ return {
99
+ name: node.name ? node.name.text : 'anonymous',
100
+ line_start: pos.line + 1,
101
+ line_end: end.line + 1,
102
+ methods: methods,
103
+ properties: properties,
104
+ base_classes: baseClasses,
105
+ is_abstract: node.modifiers?.some(m => m.kind === ts.SyntaxKind.AbstractKeyword) || false,
106
+ decorators: extractDecorators(node),
107
+ docstring: docstring,
108
+ method_entities: methodEntities // Full method data with calls
109
+ };
110
+ }
111
+
112
+ function extractFunction(node, sourceFile, isMethod = false) {
113
+ const pos = sourceFile.getLineAndCharacterOfPosition(node.getStart());
114
+ const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
115
+
116
+ const parameters = node.parameters.map(p => p.name.getText(sourceFile));
117
+
118
+ let returnType = null;
119
+ if (node.type) {
120
+ returnType = node.type.getText(sourceFile);
121
+ }
122
+
123
+ const isAsync = node.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) || false;
124
+ const isStatic = node.modifiers?.some(m => m.kind === ts.SyntaxKind.StaticKeyword) || false;
125
+
126
+ // Extract JSDoc comment
127
+ const docstring = extractDocstring(node, sourceFile);
128
+
129
+ // Extract function calls
130
+ const calls = extractCalls(node.body, sourceFile);
131
+
132
+ return {
133
+ name: node.name ? node.name.text : 'anonymous',
134
+ line_start: pos.line + 1,
135
+ line_end: end.line + 1,
136
+ parameters: parameters,
137
+ return_type: returnType,
138
+ is_async: isAsync,
139
+ is_method: isMethod,
140
+ is_static: isStatic,
141
+ decorators: extractDecorators(node),
142
+ docstring: docstring,
143
+ calls: calls
144
+ };
145
+ }
146
+
147
+ function extractArrowFunction(variableDecl, sourceFile) {
148
+ const arrowFunc = variableDecl.initializer;
149
+ const pos = sourceFile.getLineAndCharacterOfPosition(arrowFunc.getStart());
150
+ const end = sourceFile.getLineAndCharacterOfPosition(arrowFunc.getEnd());
151
+
152
+ const name = variableDecl.name.getText(sourceFile);
153
+ const parameters = arrowFunc.parameters.map(p => p.name.getText(sourceFile));
154
+
155
+ let returnType = null;
156
+ if (arrowFunc.type) {
157
+ returnType = arrowFunc.type.getText(sourceFile);
158
+ }
159
+
160
+ // Extract function calls from arrow function body
161
+ const calls = extractCalls(arrowFunc.body, sourceFile);
162
+
163
+ return {
164
+ name: name,
165
+ line_start: pos.line + 1,
166
+ line_end: end.line + 1,
167
+ parameters: parameters,
168
+ return_type: returnType,
169
+ is_async: false, // Arrow functions don't have async modifier at declaration level
170
+ is_method: false,
171
+ is_static: false,
172
+ decorators: [],
173
+ docstring: null,
174
+ calls: calls
175
+ };
176
+ }
177
+
178
+ /**
179
+ * Extract all function calls from a node (function body)
180
+ * @param {ts.Node} node - The AST node to search for calls
181
+ * @param {ts.SourceFile} sourceFile - The source file
182
+ * @returns {string[]} - Array of called function names
183
+ */
184
+ function extractCalls(node, sourceFile) {
185
+ if (!node) return [];
186
+
187
+ const calls = new Set();
188
+
189
+ function visitForCalls(n) {
190
+ if (ts.isCallExpression(n)) {
191
+ const callName = getCallName(n.expression, sourceFile);
192
+ if (callName) {
193
+ calls.add(callName);
194
+ }
195
+ }
196
+ ts.forEachChild(n, visitForCalls);
197
+ }
198
+
199
+ visitForCalls(node);
200
+ return Array.from(calls);
201
+ }
202
+
203
+ /**
204
+ * Get the name of a called function from its expression
205
+ * @param {ts.Expression} expr - The call expression
206
+ * @param {ts.SourceFile} sourceFile - The source file
207
+ * @returns {string|null} - The function name or null
208
+ */
209
+ function getCallName(expr, sourceFile) {
210
+ // Direct call: foo()
211
+ if (ts.isIdentifier(expr)) {
212
+ return expr.text;
213
+ }
214
+
215
+ // Method call: obj.foo() or obj.bar.foo()
216
+ if (ts.isPropertyAccessExpression(expr)) {
217
+ const parts = [];
218
+ let current = expr;
219
+
220
+ while (ts.isPropertyAccessExpression(current)) {
221
+ parts.unshift(current.name.text);
222
+ current = current.expression;
223
+ }
224
+
225
+ if (ts.isIdentifier(current)) {
226
+ parts.unshift(current.text);
227
+ }
228
+
229
+ return parts.join('.');
230
+ }
231
+
232
+ // Element access: obj['foo']() - less common but handle it
233
+ if (ts.isElementAccessExpression(expr)) {
234
+ const objName = getCallName(expr.expression, sourceFile);
235
+ if (expr.argumentExpression && ts.isStringLiteral(expr.argumentExpression)) {
236
+ return objName ? `${objName}.${expr.argumentExpression.text}` : expr.argumentExpression.text;
237
+ }
238
+ return objName;
239
+ }
240
+
241
+ // Chained call: foo()() - get the inner function
242
+ if (ts.isCallExpression(expr)) {
243
+ return getCallName(expr.expression, sourceFile);
244
+ }
245
+
246
+ return null;
247
+ }
248
+
249
+ function extractImport(node, sourceFile) {
250
+ const moduleSpecifier = node.moduleSpecifier.text;
251
+ const line = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
252
+
253
+ let importedNames = [];
254
+ let alias = null;
255
+
256
+ if (node.importClause) {
257
+ // Default import: import Foo from 'module'
258
+ if (node.importClause.name) {
259
+ importedNames.push(node.importClause.name.text);
260
+ }
261
+
262
+ // Named imports: import { A, B } from 'module'
263
+ if (node.importClause.namedBindings) {
264
+ if (ts.isNamedImports(node.importClause.namedBindings)) {
265
+ node.importClause.namedBindings.elements.forEach(element => {
266
+ importedNames.push(element.name.text);
267
+ });
268
+ }
269
+ // Namespace import: import * as Foo from 'module'
270
+ else if (ts.isNamespaceImport(node.importClause.namedBindings)) {
271
+ alias = node.importClause.namedBindings.name.text;
272
+ }
273
+ }
274
+ }
275
+
276
+ return {
277
+ module: moduleSpecifier,
278
+ line_number: line,
279
+ imported_names: importedNames,
280
+ alias: alias
281
+ };
282
+ }
283
+
284
+ function extractDecorators(node) {
285
+ if (!node.decorators) return [];
286
+
287
+ return node.decorators.map(decorator => {
288
+ return decorator.expression.getText();
289
+ });
290
+ }
291
+
292
+ function extractDocstring(node, sourceFile) {
293
+ const jsDocComments = ts.getJSDocCommentsAndTags(node);
294
+ if (jsDocComments && jsDocComments.length > 0) {
295
+ return jsDocComments[0].comment || null;
296
+ }
297
+ return null;
298
+ }
299
+
300
+ // Main execution
301
+ const filePath = process.argv[2];
302
+ if (!filePath) {
303
+ console.error('Usage: node ts_ast_parser.js <file.ts>');
304
+ process.exit(1);
305
+ }
306
+
307
+ try {
308
+ const result = parseTypeScriptFile(filePath);
309
+ console.log(JSON.stringify(result, null, 2));
310
+ } catch (error) {
311
+ console.error('Error parsing TypeScript:', error.message);
312
+ process.exit(1);
313
+ }