jarvis-ai-assistant 0.1.129__py3-none-any.whl → 0.1.131__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.

Potentially problematic release.


This version of jarvis-ai-assistant might be problematic. Click here for more details.

Files changed (61) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +41 -27
  3. jarvis/jarvis_agent/builtin_input_handler.py +73 -0
  4. jarvis/{jarvis_code_agent → jarvis_agent}/file_input_handler.py +1 -1
  5. jarvis/jarvis_agent/main.py +1 -1
  6. jarvis/jarvis_agent/patch.py +461 -0
  7. jarvis/{jarvis_code_agent → jarvis_agent}/shell_input_handler.py +0 -1
  8. jarvis/jarvis_code_agent/code_agent.py +94 -89
  9. jarvis/jarvis_codebase/main.py +5 -5
  10. jarvis/jarvis_dev/main.py +833 -741
  11. jarvis/jarvis_git_squash/main.py +1 -1
  12. jarvis/jarvis_lsp/base.py +2 -26
  13. jarvis/jarvis_lsp/cpp.py +2 -14
  14. jarvis/jarvis_lsp/go.py +0 -13
  15. jarvis/jarvis_lsp/python.py +1 -30
  16. jarvis/jarvis_lsp/registry.py +10 -14
  17. jarvis/jarvis_lsp/rust.py +0 -12
  18. jarvis/jarvis_multi_agent/__init__.py +63 -53
  19. jarvis/jarvis_platform/registry.py +1 -2
  20. jarvis/jarvis_platform_manager/main.py +3 -3
  21. jarvis/jarvis_rag/main.py +1 -1
  22. jarvis/jarvis_tools/ask_codebase.py +40 -20
  23. jarvis/jarvis_tools/code_review.py +180 -143
  24. jarvis/jarvis_tools/create_code_agent.py +76 -72
  25. jarvis/jarvis_tools/create_sub_agent.py +31 -21
  26. jarvis/jarvis_tools/execute_shell.py +2 -2
  27. jarvis/jarvis_tools/execute_shell_script.py +1 -1
  28. jarvis/jarvis_tools/file_operation.py +2 -2
  29. jarvis/jarvis_tools/git_commiter.py +88 -68
  30. jarvis/jarvis_tools/lsp_find_definition.py +83 -67
  31. jarvis/jarvis_tools/lsp_find_references.py +62 -46
  32. jarvis/jarvis_tools/lsp_get_diagnostics.py +90 -74
  33. jarvis/jarvis_tools/methodology.py +3 -3
  34. jarvis/jarvis_tools/read_code.py +2 -2
  35. jarvis/jarvis_tools/search_web.py +18 -20
  36. jarvis/jarvis_tools/tool_generator.py +1 -1
  37. jarvis/jarvis_tools/treesitter_analyzer.py +331 -0
  38. jarvis/jarvis_treesitter/README.md +104 -0
  39. jarvis/jarvis_treesitter/__init__.py +20 -0
  40. jarvis/jarvis_treesitter/database.py +258 -0
  41. jarvis/jarvis_treesitter/example.py +115 -0
  42. jarvis/jarvis_treesitter/grammar_builder.py +182 -0
  43. jarvis/jarvis_treesitter/language.py +117 -0
  44. jarvis/jarvis_treesitter/symbol.py +31 -0
  45. jarvis/jarvis_treesitter/tools_usage.md +121 -0
  46. jarvis/jarvis_utils/git_utils.py +10 -2
  47. jarvis/jarvis_utils/input.py +3 -1
  48. jarvis/jarvis_utils/methodology.py +1 -1
  49. jarvis/jarvis_utils/output.py +2 -2
  50. jarvis/jarvis_utils/utils.py +3 -3
  51. {jarvis_ai_assistant-0.1.129.dist-info → jarvis_ai_assistant-0.1.131.dist-info}/METADATA +2 -4
  52. jarvis_ai_assistant-0.1.131.dist-info/RECORD +85 -0
  53. jarvis/jarvis_code_agent/builtin_input_handler.py +0 -43
  54. jarvis/jarvis_code_agent/patch.py +0 -276
  55. jarvis/jarvis_tools/lsp_get_document_symbols.py +0 -87
  56. jarvis/jarvis_tools/lsp_prepare_rename.py +0 -130
  57. jarvis_ai_assistant-0.1.129.dist-info/RECORD +0 -78
  58. {jarvis_ai_assistant-0.1.129.dist-info → jarvis_ai_assistant-0.1.131.dist-info}/LICENSE +0 -0
  59. {jarvis_ai_assistant-0.1.129.dist-info → jarvis_ai_assistant-0.1.131.dist-info}/WHEEL +0 -0
  60. {jarvis_ai_assistant-0.1.129.dist-info → jarvis_ai_assistant-0.1.131.dist-info}/entry_points.txt +0 -0
  61. {jarvis_ai_assistant-0.1.129.dist-info → jarvis_ai_assistant-0.1.131.dist-info}/top_level.txt +0 -0
@@ -11,7 +11,12 @@ class LSPGetDiagnosticsTool:
11
11
  # 工具参数定义
12
12
  parameters = {
13
13
  "file_path": "Path to the file to analyze",
14
- "language": f"Programming language of the file ({', '.join(LSPRegistry.get_global_lsp_registry().get_supported_languages())})"
14
+ "language": f"Programming language of the file ({', '.join(LSPRegistry.get_global_lsp_registry().get_supported_languages())})",
15
+ "root_dir": {
16
+ "type": "string",
17
+ "description": "Root directory for LSP operations (optional)",
18
+ "default": "."
19
+ }
15
20
  }
16
21
 
17
22
  @staticmethod
@@ -24,6 +29,7 @@ class LSPGetDiagnosticsTool:
24
29
  """执行工具的主要逻辑"""
25
30
  file_path = args.get("file_path", "")
26
31
  language = args.get("language", "")
32
+ root_dir = args.get("root_dir", ".")
27
33
 
28
34
  # 验证输入参数
29
35
  if not all([file_path, language]):
@@ -41,89 +47,99 @@ class LSPGetDiagnosticsTool:
41
47
  "stdout": ""
42
48
  }
43
49
 
44
- # 获取LSP实例
45
- registry = LSPRegistry.get_global_lsp_registry()
46
- lsp = registry.create_lsp(language)
50
+ # 存储当前目录
51
+ original_dir = os.getcwd()
47
52
 
48
- # 检查语言是否支持
49
- if not lsp:
50
- return {
51
- "success": False,
52
- "stderr": f"No LSP support for language: {language}",
53
- "stdout": ""
54
- }
55
-
56
53
  try:
57
- # 初始化LSP
58
- if not lsp.initialize(os.path.abspath(os.getcwd())):
54
+ # 切换到root_dir
55
+ os.chdir(root_dir)
56
+
57
+ # 获取LSP实例
58
+ registry = LSPRegistry.get_global_lsp_registry()
59
+ lsp = registry.create_lsp(language)
60
+
61
+ # 检查语言是否支持
62
+ if not lsp:
59
63
  return {
60
64
  "success": False,
61
- "stderr": "LSP initialization failed",
65
+ "stderr": f"No LSP support for language: {language}",
62
66
  "stdout": ""
63
67
  }
64
68
 
65
- # 获取诊断信息
66
- diagnostics = lsp.get_diagnostics(file_path)
67
-
68
- # 如果没有诊断信息
69
- if not diagnostics:
69
+ try:
70
+ # 初始化LSP
71
+ if not lsp.initialize(os.path.abspath(os.getcwd())):
72
+ return {
73
+ "success": False,
74
+ "stderr": "LSP initialization failed",
75
+ "stdout": ""
76
+ }
77
+
78
+ # 获取诊断信息
79
+ diagnostics = lsp.get_diagnostics(file_path)
80
+
81
+ # 如果没有诊断信息
82
+ if not diagnostics:
83
+ return {
84
+ "success": True,
85
+ "stdout": "No issues found in the file",
86
+ "stderr": ""
87
+ }
88
+
89
+ # 格式化输出
90
+ output = ["Diagnostics:"]
91
+ # 严重程度映射
92
+ severity_map = {1: "Error", 2: "Warning", 3: "Info", 4: "Hint"}
93
+
94
+ # 按严重程度和行号排序诊断信息
95
+ sorted_diagnostics = sorted(
96
+ diagnostics,
97
+ key=lambda x: (x["severity"], x["range"]["start"]["line"])
98
+ )
99
+
100
+ # 处理每个诊断信息
101
+ for diag in sorted_diagnostics:
102
+ severity = severity_map.get(diag["severity"], "Unknown")
103
+ start = diag["range"]["start"]
104
+ line = LSPRegistry.get_line_at_position(file_path, start["line"]).strip()
105
+
106
+ output.extend([
107
+ f"\n{severity} at line {start['line'] + 1}, column {start['character'] + 1}:",
108
+ f"Message: {diag['message']}",
109
+ f"Code: {line}",
110
+ "-" * 60
111
+ ])
112
+
113
+ # 处理相关附加信息
114
+ if diag.get("relatedInformation"):
115
+ output.append("Related information:")
116
+ for info in diag["relatedInformation"]:
117
+ info_line = LSPRegistry.get_line_at_position(
118
+ info["location"]["uri"],
119
+ info["location"]["range"]["start"]["line"]
120
+ ).strip()
121
+ output.extend([
122
+ f" - {info['message']}",
123
+ f" at {info['location']['uri']}:{info['location']['range']['start']['line'] + 1}",
124
+ f" {info_line}"
125
+ ])
126
+
70
127
  return {
71
128
  "success": True,
72
- "stdout": "No issues found in the file",
129
+ "stdout": "\n".join(output),
73
130
  "stderr": ""
74
131
  }
75
132
 
76
- # 格式化输出
77
- output = ["Diagnostics:"]
78
- # 严重程度映射
79
- severity_map = {1: "Error", 2: "Warning", 3: "Info", 4: "Hint"}
80
-
81
- # 按严重程度和行号排序诊断信息
82
- sorted_diagnostics = sorted(
83
- diagnostics,
84
- key=lambda x: (x["severity"], x["range"]["start"]["line"])
85
- )
86
-
87
- # 处理每个诊断信息
88
- for diag in sorted_diagnostics:
89
- severity = severity_map.get(diag["severity"], "Unknown")
90
- start = diag["range"]["start"]
91
- line = LSPRegistry.get_line_at_position(file_path, start["line"]).strip()
92
-
93
- output.extend([
94
- f"\n{severity} at line {start['line'] + 1}, column {start['character'] + 1}:",
95
- f"Message: {diag['message']}",
96
- f"Code: {line}",
97
- "-" * 60
98
- ])
99
-
100
- # 处理相关附加信息
101
- if diag.get("relatedInformation"):
102
- output.append("Related information:")
103
- for info in diag["relatedInformation"]:
104
- info_line = LSPRegistry.get_line_at_position(
105
- info["location"]["uri"],
106
- info["location"]["range"]["start"]["line"]
107
- ).strip()
108
- output.extend([
109
- f" - {info['message']}",
110
- f" at {info['location']['uri']}:{info['location']['range']['start']['line'] + 1}",
111
- f" {info_line}"
112
- ])
113
-
114
- return {
115
- "success": True,
116
- "stdout": "\n".join(output),
117
- "stderr": ""
118
- }
119
-
120
- except Exception as e:
121
- return {
122
- "success": False,
123
- "stderr": f"Error getting diagnostics: {str(e)}",
124
- "stdout": ""
125
- }
133
+ except Exception as e:
134
+ return {
135
+ "success": False,
136
+ "stderr": f"Error getting diagnostics: {str(e)}",
137
+ "stdout": ""
138
+ }
139
+ finally:
140
+ # 确保关闭LSP连接
141
+ if lsp:
142
+ lsp.shutdown()
126
143
  finally:
127
- # 确保关闭LSP连接
128
- if lsp:
129
- lsp.shutdown()
144
+ # 恢复原始目录
145
+ os.chdir(original_dir)
@@ -47,7 +47,7 @@ class MethodologyTool:
47
47
  """Ensure the methodology file exists"""
48
48
  if not os.path.exists(self.methodology_file):
49
49
  try:
50
- with open(self.methodology_file, 'w', encoding='utf-8') as f:
50
+ with open(self.methodology_file, 'w', encoding='utf-8', errors="ignore") as f:
51
51
  yaml.safe_dump({}, f, allow_unicode=True)
52
52
  except Exception as e:
53
53
  PrettyOutput.print(f"创建方法论文件失败:{str(e)}", OutputType.ERROR)
@@ -55,7 +55,7 @@ class MethodologyTool:
55
55
  def _load_methodologies(self) -> Dict:
56
56
  """Load all methodologies"""
57
57
  try:
58
- with open(self.methodology_file, 'r', encoding='utf-8') as f:
58
+ with open(self.methodology_file, 'r', encoding='utf-8', errors="ignore") as f:
59
59
  return yaml.safe_load(f) or {}
60
60
  except Exception as e:
61
61
  PrettyOutput.print(f"加载方法论失败: {str(e)}", OutputType.ERROR)
@@ -64,7 +64,7 @@ class MethodologyTool:
64
64
  def _save_methodologies(self, methodologies: Dict):
65
65
  """Save all methodologies"""
66
66
  try:
67
- with open(self.methodology_file, 'w', encoding='utf-8') as f:
67
+ with open(self.methodology_file, 'w', encoding='utf-8', errors="ignore") as f:
68
68
  yaml.safe_dump(methodologies, f, allow_unicode=True)
69
69
  except Exception as e:
70
70
  PrettyOutput.print(f"保存方法论失败: {str(e)}", OutputType.ERROR)
@@ -49,7 +49,7 @@ class ReadCodeTool:
49
49
  }
50
50
 
51
51
  # 读取文件内容
52
- with open(abs_path, 'r', encoding='utf-8') as f:
52
+ with open(abs_path, 'r', encoding='utf-8', errors="ignore") as f:
53
53
  lines = f.readlines()
54
54
 
55
55
  total_lines = len(lines)
@@ -84,7 +84,7 @@ class ReadCodeTool:
84
84
  f"{numbered_content}\n"
85
85
  f"{'='*80}\n"
86
86
  )
87
-
87
+ spinner.text = f"文件读取完成: {abs_path}"
88
88
  spinner.ok("✅")
89
89
  return {
90
90
  "success": True,
@@ -168,22 +168,20 @@ class SearchTool:
168
168
  # Process each batch
169
169
  batch_results = []
170
170
  for i, batch in enumerate(batches, 1):
171
- PrettyOutput.print(f"正在处理批次 {i}/{len(batches)}...", OutputType.PROGRESS)
172
-
173
- prompt = f"""Please analyze these search results to answer the question: {question}
171
+ prompt = f"""请根据以下搜索结果回答以下问题:{question}
174
172
 
175
- Search results content (Batch {i}/{len(batches)}):
173
+ 搜索结果内容 ( {i} 批/{len(batches)}):
176
174
  {'-' * 40}
177
175
  {''.join(batch)}
178
176
  {'-' * 40}
179
177
 
180
- Please extract key information related to the question. Focus on:
181
- 1. Relevant facts and details
182
- 2. Maintaining objectivity
183
- 3. Citing sources when appropriate
184
- 4. Noting any uncertainties
178
+ 请提取与问题相关的关键信息。重点关注:
179
+ 1. 相关事实和细节
180
+ 2. 保持客观性
181
+ 3. 在适当的时候引用来源
182
+ 4. 注明任何不确定性
185
183
 
186
- Format your response as a clear summary of findings from this batch."""
184
+ 请将您的回答格式化为对本批次搜索结果的清晰总结。"""
187
185
 
188
186
  response = self.model.chat_until_success(prompt)
189
187
  batch_results.append(response)
@@ -196,27 +194,27 @@ Format your response as a clear summary of findings from this batch."""
196
194
  batch_findings = '\n\n'.join(f'Batch {i+1}:\n{result}' for i, result in enumerate(batch_results))
197
195
  separator = '-' * 40
198
196
 
199
- synthesis_prompt = f"""Please provide a comprehensive answer to the original question by synthesizing the findings from multiple batches of search results.
197
+ synthesis_prompt = f"""请通过综合多个批次的搜索结果,为原始问题提供一个全面的回答。
200
198
 
201
- Original Question: {question}
199
+ 原始问题: {question}
202
200
 
203
- Findings from each batch:
201
+ 各批次的发现:
204
202
  {separator}
205
203
  {batch_findings}
206
204
  {separator}
207
205
 
208
- Please synthesize a final answer that:
209
- 1. Combines key insights from all batches
210
- 2. Resolves any contradictions between sources
211
- 3. Maintains clear source attribution
212
- 4. Acknowledges any remaining uncertainties
213
- 5. Provides a coherent and complete response to the original question"""
206
+ 请综合出一个最终答案,要求:
207
+ 1. 整合所有批次的关键见解
208
+ 2. 解决不同来源之间的矛盾
209
+ 3. 保持清晰的来源归属
210
+ 4. 承认任何剩余的不确定性
211
+ 5. 提供对原始问题的连贯完整的回答"""
214
212
 
215
213
  final_response = self.model.chat_until_success(synthesis_prompt)
216
214
  return final_response
217
215
 
218
216
  except Exception as e:
219
- return f"Information extraction failed: {str(e)}"
217
+ return f"信息提取失败:{str(e)}"
220
218
 
221
219
  def execute(self, args: Dict) -> Dict[str, Any]:
222
220
  """Execute search and extract information"""
@@ -84,7 +84,7 @@ class ToolGenerator:
84
84
  tools_dir.mkdir(parents=True, exist_ok=True)
85
85
  tool_file = tools_dir / f"{tool_name}.py"
86
86
 
87
- with open(tool_file, "w") as f:
87
+ with open(tool_file, "w", errors="ignore") as f:
88
88
  f.write(implementation)
89
89
  spinner.text = "工具保存完成"
90
90
  spinner.ok("✅")
@@ -0,0 +1,331 @@
1
+ from typing import Dict, Any, List, Optional
2
+ import os
3
+ import logging
4
+ from yaspin import yaspin
5
+
6
+ from jarvis.jarvis_utils.output import OutputType, PrettyOutput
7
+ from jarvis.jarvis_treesitter import (
8
+ CodeDatabase,
9
+ SymbolType,
10
+ setup_default_grammars,
11
+ DEFAULT_GRAMMAR_DIR
12
+ )
13
+
14
+ # 配置日志
15
+ logger = logging.getLogger(__name__)
16
+
17
+ class TreesitterAnalyzer:
18
+ """Tree-sitter 代码分析工具,用于快速查找代码中的符号定义、引用和调用关系"""
19
+
20
+ name = "treesitter_analyzer"
21
+ description = "使用 Tree-sitter 分析代码,查找符号定义、引用和调用关系"
22
+ parameters = {
23
+ "type": "object",
24
+ "properties": {
25
+ "action": {
26
+ "type": "string",
27
+ "enum": ["find_symbol", "find_references", "find_callers"],
28
+ "description": "分析操作类型: find_symbol(查找符号), find_references(查找引用), find_callers(查找调用者)"
29
+ },
30
+ "symbol_name": {
31
+ "type": "string",
32
+ "description": "要查找的符号名称,如函数名、类名、变量名等"
33
+ },
34
+ "directory": {
35
+ "type": "string",
36
+ "description": "要索引的代码目录,默认为当前目录",
37
+ "default": "."
38
+ },
39
+ "extensions": {
40
+ "type": "array",
41
+ "items": {"type": "string"},
42
+ "description": "要索引的文件扩展名列表,如 [\".py\", \".c\"],不指定则索引所有支持的文件类型"
43
+ },
44
+ "max_results": {
45
+ "type": "integer",
46
+ "description": "最大返回结果数量",
47
+ "default": 20
48
+ }
49
+ },
50
+ "required": ["action", "symbol_name", "directory"]
51
+ }
52
+
53
+ def __init__(self):
54
+ """初始化 Tree-sitter 分析器工具"""
55
+ # 确保语法文件目录存在
56
+ os.makedirs(DEFAULT_GRAMMAR_DIR, exist_ok=True)
57
+
58
+ # 创建代码数据库实例
59
+ self.db = None
60
+
61
+ def _get_database(self) -> CodeDatabase:
62
+ """获取 Tree-sitter 代码数据库实例,如果不存在则创建"""
63
+ if self.db is None:
64
+ self.db = CodeDatabase()
65
+ return self.db
66
+
67
+ def _index_directory(self, directory: str, extensions: Optional[List[str]] = None) -> Dict[str, Any]:
68
+ """索引指定目录下的代码文件"""
69
+ try:
70
+ db = self._get_database()
71
+ indexed_files = []
72
+ skipped_files = []
73
+
74
+ with yaspin(text=f"正在索引目录: {directory}...", color="cyan") as spinner:
75
+ for root, _, files in os.walk(directory):
76
+ for file in files:
77
+ # 检查文件扩展名
78
+ if extensions and not any(file.endswith(ext) for ext in extensions):
79
+ continue
80
+
81
+ file_path = os.path.join(root, file)
82
+ try:
83
+ db.index_file(file_path)
84
+ indexed_files.append(file_path)
85
+ except Exception as e:
86
+ skipped_files.append((file_path, str(e)))
87
+
88
+ spinner.text = f"索引完成: {len(indexed_files)} 个文件"
89
+ spinner.ok("✅")
90
+
91
+ return {
92
+ "success": True,
93
+ "indexed_files": indexed_files,
94
+ "skipped_files": skipped_files,
95
+ "index_summary": f"已成功索引 {len(indexed_files)} 个文件,跳过 {len(skipped_files)} 个文件"
96
+ }
97
+
98
+ except Exception as e:
99
+ logger.error(f"索引目录失败: {str(e)}")
100
+ return {
101
+ "success": False,
102
+ "stderr": f"索引目录失败: {str(e)}"
103
+ }
104
+
105
+ def _find_symbol(self, symbol_name: str, directory: str, max_results: int = 20) -> Dict[str, Any]:
106
+ """查找代码中的符号定义"""
107
+ try:
108
+ db = self._get_database()
109
+ symbols = db.find_symbol(symbol_name)
110
+
111
+ if not symbols:
112
+ return {
113
+ "success": True,
114
+ "stdout": f"未找到名为 '{symbol_name}' 的符号",
115
+ "symbols": []
116
+ }
117
+
118
+ # 限制结果数量
119
+ symbols = symbols[:max_results]
120
+
121
+ # 构建结果
122
+ result_list = []
123
+ for symbol in symbols:
124
+ result_list.append({
125
+ "name": symbol.name,
126
+ "type": symbol.type.value,
127
+ "file": symbol.location.file_path,
128
+ "line": symbol.location.start_line,
129
+ "column": symbol.location.start_column
130
+ })
131
+
132
+ # 构建输出文本
133
+ output_text = f"找到 {len(symbols)} 个名为 '{symbol_name}' 的符号:\n\n"
134
+ for i, symbol in enumerate(symbols, 1):
135
+ output_text += (f"{i}. {symbol.type.value}: {symbol.name}\n"
136
+ f" 位置: {symbol.location.file_path}:{symbol.location.start_line}:{symbol.location.start_column}\n\n")
137
+
138
+ return {
139
+ "success": True,
140
+ "stdout": output_text,
141
+ "symbols": result_list
142
+ }
143
+
144
+ except Exception as e:
145
+ logger.error(f"查找符号失败: {str(e)}")
146
+ return {
147
+ "success": False,
148
+ "stderr": f"查找符号失败: {str(e)}"
149
+ }
150
+
151
+ def _find_references(self, symbol_name: str, directory: str, max_results: int = 20) -> Dict[str, Any]:
152
+ """查找代码中符号的引用"""
153
+ try:
154
+ db = self._get_database()
155
+ symbols = db.find_symbol(symbol_name)
156
+
157
+ if not symbols:
158
+ return {
159
+ "success": True,
160
+ "stdout": f"未找到名为 '{symbol_name}' 的符号",
161
+ "references": []
162
+ }
163
+
164
+ # 获取第一个匹配符号的所有引用
165
+ references = db.find_references(symbols[0])
166
+
167
+ # 限制结果数量
168
+ references = references[:max_results]
169
+
170
+ # 构建结果
171
+ result_list = []
172
+ for ref in references:
173
+ result_list.append({
174
+ "file": ref.location.file_path,
175
+ "line": ref.location.start_line,
176
+ "column": ref.location.start_column
177
+ })
178
+
179
+ # 构建输出文本
180
+ output_text = f"找到 {len(references)} 处对 '{symbol_name}' 的引用:\n\n"
181
+ for i, ref in enumerate(references, 1):
182
+ output_text += f"{i}. {ref.location.file_path}:{ref.location.start_line}:{ref.location.start_column}\n"
183
+
184
+ return {
185
+ "success": True,
186
+ "stdout": output_text,
187
+ "symbol": {
188
+ "name": symbols[0].name,
189
+ "type": symbols[0].type.value,
190
+ "file": symbols[0].location.file_path,
191
+ "line": symbols[0].location.start_line,
192
+ "column": symbols[0].location.start_column
193
+ },
194
+ "references": result_list
195
+ }
196
+
197
+ except Exception as e:
198
+ logger.error(f"查找引用失败: {str(e)}")
199
+ return {
200
+ "success": False,
201
+ "stderr": f"查找引用失败: {str(e)}"
202
+ }
203
+
204
+ def _find_callers(self, symbol_name: str, directory: str, max_results: int = 20) -> Dict[str, Any]:
205
+ """查找代码中调用指定函数的位置"""
206
+ try:
207
+ db = self._get_database()
208
+ symbols = db.find_symbol(symbol_name)
209
+
210
+ if not symbols:
211
+ return {
212
+ "success": True,
213
+ "stdout": f"未找到名为 '{symbol_name}' 的函数",
214
+ "callers": []
215
+ }
216
+
217
+ # 筛选出函数类型的符号
218
+ function_symbols = [s for s in symbols if s.type == SymbolType.FUNCTION]
219
+ if not function_symbols:
220
+ return {
221
+ "success": True,
222
+ "stdout": f"'{symbol_name}' 不是一个函数",
223
+ "callers": []
224
+ }
225
+
226
+ # 获取第一个函数符号的所有调用者
227
+ callers = db.find_callers(function_symbols[0])
228
+
229
+ # 限制结果数量
230
+ callers = callers[:max_results]
231
+
232
+ # 构建结果
233
+ result_list = []
234
+ for caller in callers:
235
+ result_list.append({
236
+ "file": caller.location.file_path,
237
+ "line": caller.location.start_line,
238
+ "column": caller.location.start_column
239
+ })
240
+
241
+ # 构建输出文本
242
+ output_text = f"找到 {len(callers)} 处对函数 '{symbol_name}' 的调用:\n\n"
243
+ for i, caller in enumerate(callers, 1):
244
+ output_text += f"{i}. {caller.location.file_path}:{caller.location.start_line}:{caller.location.start_column}\n"
245
+
246
+ return {
247
+ "success": True,
248
+ "stdout": output_text,
249
+ "function": {
250
+ "name": function_symbols[0].name,
251
+ "file": function_symbols[0].location.file_path,
252
+ "line": function_symbols[0].location.start_line,
253
+ "column": function_symbols[0].location.start_column
254
+ },
255
+ "callers": result_list
256
+ }
257
+
258
+ except Exception as e:
259
+ logger.error(f"查找调用者失败: {str(e)}")
260
+ return {
261
+ "success": False,
262
+ "stderr": f"查找调用者失败: {str(e)}"
263
+ }
264
+
265
+ def execute(self, args: Dict) -> Dict[str, Any]:
266
+ """执行 Tree-sitter 代码分析
267
+
268
+ 参数:
269
+ args: 包含操作参数的字典
270
+
271
+ 返回:
272
+ Dict[str, Any]: 操作结果
273
+ """
274
+ try:
275
+ action = args.get("action")
276
+ symbol_name = args.get("symbol_name")
277
+ directory = args.get("directory", ".")
278
+ extensions = args.get("extensions", None)
279
+ max_results = args.get("max_results", 20)
280
+
281
+ # 确保 symbol_name 参数存在
282
+ if not symbol_name:
283
+ return {
284
+ "success": False,
285
+ "stdout": "",
286
+ "stderr": "缺少必要参数: symbol_name"
287
+ }
288
+
289
+ # 确保语法目录存在
290
+ os.makedirs(DEFAULT_GRAMMAR_DIR, exist_ok=True)
291
+
292
+ # 先自动索引目录
293
+ with yaspin(text="正在索引目录...") as spinner:
294
+ index_result = self._index_directory(directory, extensions)
295
+ if not index_result.get("success", False):
296
+ spinner.fail("✗")
297
+ return index_result
298
+ spinner.ok("✓")
299
+
300
+ # 根据不同的操作执行相应的函数
301
+ result = None
302
+ if action == "find_symbol":
303
+ result = self._find_symbol(symbol_name, directory, max_results)
304
+ elif action == "find_references":
305
+ result = self._find_references(symbol_name, directory, max_results)
306
+ elif action == "find_callers":
307
+ result = self._find_callers(symbol_name, directory, max_results)
308
+ else:
309
+ return {
310
+ "success": False,
311
+ "stdout": "",
312
+ "stderr": f"不支持的操作: {action}"
313
+ }
314
+
315
+ # 将索引信息添加到结果中
316
+ if result:
317
+ if "stdout" in result:
318
+ result["stdout"] = f"{index_result.get('index_summary', '')}\n\n{result['stdout']}"
319
+ else:
320
+ result["stdout"] = index_result.get('index_summary', '')
321
+
322
+ return result
323
+
324
+ except Exception as e:
325
+ logger.error(f"Tree-sitter 分析失败: {str(e)}")
326
+ return {
327
+ "success": False,
328
+ "stdout": "",
329
+ "stderr": f"Tree-sitter 分析失败: {str(e)}"
330
+ }
331
+