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
@@ -74,11 +74,11 @@ class ShellTool:
74
74
  tee_command = f"script -q -c '{escaped_command}' {output_file}"
75
75
 
76
76
  # Execute command and capture return code
77
- return_code = os.system(tee_command)
77
+ os.system(tee_command)
78
78
 
79
79
  # Read and process output file
80
80
  try:
81
- with open(output_file, 'r', encoding='utf-8', errors='replace') as f:
81
+ with open(output_file, 'r', encoding='utf-8', errors='ignore') as f:
82
82
  output = f.read()
83
83
  # Remove header and footer added by script command
84
84
  if output:
@@ -33,7 +33,7 @@ class ShellScriptTool:
33
33
  # Create temporary script file
34
34
  script_path = os.path.join(tempfile.gettempdir(), f"jarvis_script_{os.getpid()}.sh")
35
35
  try:
36
- with open(script_path, 'w', encoding='utf-8') as f:
36
+ with open(script_path, 'w', encoding='utf-8', errors="ignore") as f:
37
37
  f.write(script_content)
38
38
  # Use execute_shell to run the script
39
39
  from jarvis.jarvis_tools.execute_shell import ShellTool
@@ -55,7 +55,7 @@ class FileOperationTool:
55
55
  "stderr": "File too large (>10MB)"
56
56
  }
57
57
 
58
- with open(abs_path, 'r', encoding='utf-8') as f:
58
+ with open(abs_path, 'r', encoding='utf-8', errors="ignore") as f:
59
59
  lines = f.readlines()
60
60
 
61
61
 
@@ -90,7 +90,7 @@ class FileOperationTool:
90
90
  elif operation == "write":
91
91
  with yaspin(text=f"正在写入文件: {abs_path}...", color="cyan") as spinner:
92
92
  os.makedirs(os.path.dirname(os.path.abspath(abs_path)), exist_ok=True)
93
- with open(abs_path, 'w', encoding='utf-8') as f:
93
+ with open(abs_path, 'w', encoding='utf-8', errors="ignore") as f:
94
94
  f.write(content)
95
95
  spinner.text = f"文件写入完成: {abs_path}"
96
96
  spinner.ok("✅")
@@ -8,8 +8,9 @@ from yaspin import yaspin
8
8
  from jarvis.jarvis_platform.registry import PlatformRegistry
9
9
  import sys
10
10
  import argparse
11
+ import os
11
12
 
12
- from jarvis.jarvis_utils.git_utils import has_uncommitted_changes
13
+ from jarvis.jarvis_utils.git_utils import find_git_root, has_uncommitted_changes
13
14
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
14
15
  from jarvis.jarvis_utils.utils import init_env
15
16
 
@@ -24,6 +25,11 @@ class GitCommitTool:
24
25
  "type": "string",
25
26
  "description": "提交信息的语言",
26
27
  "default": "Chinese"
28
+ },
29
+ "root_dir": {
30
+ "type": "string",
31
+ "description": "Git仓库的根目录路径(可选)",
32
+ "default": "."
27
33
  }
28
34
  },
29
35
  "required": []
@@ -51,82 +57,95 @@ class GitCommitTool:
51
57
  def execute(self, args: Dict) -> Dict[str, Any]:
52
58
  """Execute automatic commit process with support for multi-line messages and special characters"""
53
59
  try:
54
- if not has_uncommitted_changes():
55
- PrettyOutput.print("没有未提交的更改", OutputType.SUCCESS)
56
- return {"success": True, "stdout": "No changes to commit", "stderr": ""}
60
+ root_dir = args.get("root_dir", ".")
57
61
 
58
- with yaspin(text="正在初始化提交流程...", color="cyan") as spinner:
59
- # 添加文件
60
- spinner.text = "正在添加文件到提交..."
61
- subprocess.Popen(
62
- ["git", "add", "."],
63
- stdout=subprocess.DEVNULL,
64
- stderr=subprocess.DEVNULL
65
- ).wait()
66
- spinner.write("✅ 添加文件到提交")
67
-
68
- # 获取差异
69
- spinner.text = "正在获取代码差异..."
70
- process = subprocess.Popen(
71
- ["git", "diff", "--cached", "--exit-code"],
72
- stdout=subprocess.PIPE,
73
- stderr=subprocess.PIPE
74
- )
75
- diff = process.communicate()[0].decode()
76
- spinner.write("✅ 获取差异")
62
+ # Store current directory
63
+ original_dir = os.getcwd()
64
+
65
+ try:
66
+ # Change to root_dir
67
+ os.chdir(root_dir)
77
68
 
78
- # 生成提交信息
79
- spinner.text = "正在生成提交消息..."
80
- prompt = f'''根据以下规则生成提交信息:
81
- 提交信息应使用{args.get('lang', '中文')}书写
82
- # 必需结构
83
- 必须使用以下格式:
84
- <COMMIT_MESSAGE>
85
- <类型>(<范围>): <主题>
86
- 使用祈使语气描述变更内容
87
- </COMMIT_MESSAGE>
88
- # 格式规则
89
- 1. 类型: fix, feat, docs, style, refactor, test, chore
90
- 2. 范围表示模块 (例如: auth, database)
91
- 3. 主题行 <= 72个字符,不以句号结尾
92
- 4. 正文使用现在时态解释每个变更的内容和原因
93
- 5. 不要遗漏任何变更
94
- # 分析材料
95
- {diff}
96
- '''
97
- platform = PlatformRegistry().get_codegen_platform()
98
- commit_message = platform.chat_until_success(prompt)
99
- commit_message = self._extract_commit_message(commit_message)
100
- spinner.write("✅ 生成提交消息")
69
+ find_git_root()
70
+ if not has_uncommitted_changes():
71
+ PrettyOutput.print("没有未提交的更改", OutputType.SUCCESS)
72
+ return {"success": True, "stdout": "No changes to commit", "stderr": ""}
101
73
 
102
- # 执行提交
103
- spinner.text = "正在准备提交..."
104
- with tempfile.NamedTemporaryFile(mode='w', delete=True) as tmp_file:
105
- tmp_file.write(commit_message)
106
- tmp_file.flush()
107
- spinner.text = "正在执行提交..."
108
- commit_cmd = ["git", "commit", "-F", tmp_file.name]
74
+ with yaspin(text="正在初始化提交流程...", color="cyan") as spinner:
75
+ # 添加文件
76
+ spinner.text = "正在添加文件到提交..."
109
77
  subprocess.Popen(
110
- commit_cmd,
78
+ ["git", "add", "."],
111
79
  stdout=subprocess.DEVNULL,
112
80
  stderr=subprocess.DEVNULL
113
81
  ).wait()
114
- spinner.write("✅ 提交")
82
+ spinner.write("✅ 添加文件到提交")
83
+
84
+ # 获取差异
85
+ spinner.text = "正在获取代码差异..."
86
+ process = subprocess.Popen(
87
+ ["git", "diff", "--cached", "--exit-code"],
88
+ stdout=subprocess.PIPE,
89
+ stderr=subprocess.PIPE
90
+ )
91
+ diff = process.communicate()[0].decode()
92
+ spinner.write("✅ 获取差异")
93
+
94
+ # 生成提交信息
95
+ spinner.text = "正在生成提交消息..."
96
+ prompt = f'''根据以下规则生成提交信息:
97
+ 提交信息应使用{args.get('lang', '中文')}书写
98
+ # 必需结构
99
+ 必须使用以下格式:
100
+ <COMMIT_MESSAGE>
101
+ <类型>(<范围>): <主题>
102
+ 使用祈使语气描述变更内容
103
+ </COMMIT_MESSAGE>
104
+ # 格式规则
105
+ 1. 类型: fix, feat, docs, style, refactor, test, chore
106
+ 2. 范围表示模块 (例如: auth, database)
107
+ 3. 主题行 <= 72个字符,不以句号结尾
108
+ 4. 正文使用现在时态解释每个变更的内容和原因
109
+ 5. 不要遗漏任何变更
110
+ # 分析材料
111
+ {diff}
112
+ '''
113
+ platform = PlatformRegistry().get_codegen_platform()
114
+ commit_message = platform.chat_until_success(prompt)
115
+ commit_message = self._extract_commit_message(commit_message)
116
+ spinner.write("✅ 生成提交消息")
117
+
118
+ # 执行提交
119
+ spinner.text = "正在准备提交..."
120
+ with tempfile.NamedTemporaryFile(mode='w', delete=True) as tmp_file:
121
+ tmp_file.write(commit_message)
122
+ tmp_file.flush()
123
+ spinner.text = "正在执行提交..."
124
+ commit_cmd = ["git", "commit", "-F", tmp_file.name]
125
+ subprocess.Popen(
126
+ commit_cmd,
127
+ stdout=subprocess.DEVNULL,
128
+ stderr=subprocess.DEVNULL
129
+ ).wait()
130
+ spinner.write("✅ 提交")
115
131
 
116
- commit_hash = self._get_last_commit_hash()
117
- spinner.text = "完成提交"
118
- spinner.ok("✅")
132
+ commit_hash = self._get_last_commit_hash()
133
+ spinner.text = "完成提交"
134
+ spinner.ok("✅")
119
135
 
120
- PrettyOutput.print(f"提交哈希: {commit_hash}\n提交消息: {commit_message}", OutputType.SUCCESS)
136
+ PrettyOutput.print(f"提交哈希: {commit_hash}\n提交消息: {commit_message}", OutputType.SUCCESS)
121
137
 
122
- return {
123
- "success": True,
124
- "stdout": yaml.safe_dump({
125
- "commit_hash": commit_hash,
126
- "commit_message": commit_message
127
- }),
128
- "stderr": ""
129
- }
138
+ return {
139
+ "success": True,
140
+ "stdout": yaml.safe_dump({
141
+ "commit_hash": commit_hash,
142
+ "commit_message": commit_message
143
+ }),
144
+ "stderr": ""
145
+ }
146
+ finally:
147
+ # Always restore original directory
148
+ os.chdir(original_dir)
130
149
 
131
150
  except Exception as e:
132
151
  return {
@@ -139,9 +158,10 @@ def main():
139
158
  init_env()
140
159
  parser = argparse.ArgumentParser(description='Git commit tool')
141
160
  parser.add_argument('--lang', type=str, default='Chinese', help='Language for commit messages')
161
+ parser.add_argument('--root-dir', type=str, default='.', help='Root directory of the Git repository')
142
162
  args = parser.parse_args()
143
163
  tool = GitCommitTool()
144
- tool.execute({"lang": args.lang if hasattr(args, 'lang') else 'Chinese'})
164
+ tool.execute({"lang": args.lang if hasattr(args, 'lang') else 'Chinese', "root_dir": args.root_dir})
145
165
 
146
166
  if __name__ == "__main__":
147
167
  sys.exit(main())
@@ -11,7 +11,12 @@ class LSPFindDefinitionTool:
11
11
  "file_path": "包含符号的文件路径",
12
12
  "line": "符号所在的行号(从0开始)",
13
13
  "character": "符号在行中的字符位置",
14
- "language": f"文件的编程语言({', '.join(LSPRegistry.get_global_lsp_registry().get_supported_languages())})"
14
+ "language": f"文件的编程语言({', '.join(LSPRegistry.get_global_lsp_registry().get_supported_languages())})",
15
+ "root_dir": {
16
+ "type": "string",
17
+ "description": "LSP操作的根目录路径(可选)",
18
+ "default": "."
19
+ }
15
20
  }
16
21
 
17
22
  @staticmethod
@@ -26,6 +31,7 @@ class LSPFindDefinitionTool:
26
31
  line = args.get("line", None)
27
32
  character = args.get("character", None)
28
33
  language = args.get("language", "")
34
+ root_dir = args.get("root_dir", ".")
29
35
 
30
36
  # Validate inputs
31
37
  if not all([file_path, line is not None, character is not None, language]):
@@ -52,83 +58,93 @@ class LSPFindDefinitionTool:
52
58
  "stdout": ""
53
59
  }
54
60
 
55
- # Get LSP instance
56
- registry = LSPRegistry.get_global_lsp_registry()
57
- lsp = registry.create_lsp(language)
61
+ # Store current directory
62
+ original_dir = os.getcwd()
58
63
 
59
- if not lsp:
60
- return {
61
- "success": False,
62
- "stderr": f"No LSP support for language: {language}",
63
- "stdout": ""
64
- }
65
-
66
64
  try:
67
- # Initialize LSP
68
- if not lsp.initialize(os.path.abspath(os.getcwd())):
65
+ # Change to root_dir
66
+ os.chdir(root_dir)
67
+
68
+ # Get LSP instance
69
+ registry = LSPRegistry.get_global_lsp_registry()
70
+ lsp = registry.create_lsp(language)
71
+
72
+ if not lsp:
69
73
  return {
70
74
  "success": False,
71
- "stderr": "LSP initialization failed",
75
+ "stderr": f"No LSP support for language: {language}",
72
76
  "stdout": ""
73
77
  }
74
78
 
75
- # Get symbol at position
76
- symbol = LSPRegistry.get_text_at_position(file_path, line, character)
77
- if not symbol:
78
- return {
79
- "success": False,
80
- "stderr": f"No symbol found at position {line}:{character}",
81
- "stdout": ""
82
- }
79
+ try:
80
+ # Initialize LSP
81
+ if not lsp.initialize(os.path.abspath(os.getcwd())):
82
+ return {
83
+ "success": False,
84
+ "stderr": "LSP initialization failed",
85
+ "stdout": ""
86
+ }
87
+
88
+ # Get symbol at position
89
+ symbol = LSPRegistry.get_text_at_position(file_path, line, character)
90
+ if not symbol:
91
+ return {
92
+ "success": False,
93
+ "stderr": f"No symbol found at position {line}:{character}",
94
+ "stdout": ""
95
+ }
96
+
97
+ # Find definition
98
+ defn = lsp.find_definition(file_path, (line, character))
99
+
100
+ if not defn:
101
+ return {
102
+ "success": True,
103
+ "stdout": f"No definition found for '{symbol}'",
104
+ "stderr": ""
105
+ }
106
+
107
+ # Format output
108
+ def_line = defn["range"]["start"]["line"]
109
+ def_char = defn["range"]["start"]["character"]
110
+ context = LSPRegistry.get_line_at_position(defn["uri"], def_line).strip()
111
+
112
+ output = [
113
+ f"Definition of '{symbol}':",
114
+ f"File: {defn['uri']}",
115
+ f"Line {def_line + 1}, Col {def_char + 1}: {context}"
116
+ ]
117
+
118
+ # Get a few lines of context around the definition
119
+ try:
120
+ with open(defn["uri"], 'r', errors="ignore") as f:
121
+ lines = f.readlines()
122
+ start = max(0, def_line - 2)
123
+ end = min(len(lines), def_line + 3)
124
+
125
+ if start < def_line:
126
+ output.append("\nContext:")
127
+ for i in range(start, end):
128
+ prefix = ">" if i == def_line else " "
129
+ output.append(f"{prefix} {i+1:4d} | {lines[i].rstrip()}")
130
+ except Exception:
131
+ pass
83
132
 
84
- # Find definition
85
- defn = lsp.find_definition(file_path, (line, character))
86
-
87
- if not defn:
88
133
  return {
89
134
  "success": True,
90
- "stdout": f"No definition found for '{symbol}'",
135
+ "stdout": "\n".join(output),
91
136
  "stderr": ""
92
137
  }
93
138
 
94
- # Format output
95
- def_line = defn["range"]["start"]["line"]
96
- def_char = defn["range"]["start"]["character"]
97
- context = LSPRegistry.get_line_at_position(defn["uri"], def_line).strip()
98
-
99
- output = [
100
- f"Definition of '{symbol}':",
101
- f"File: {defn['uri']}",
102
- f"Line {def_line + 1}, Col {def_char + 1}: {context}"
103
- ]
104
-
105
- # Get a few lines of context around the definition
106
- try:
107
- with open(defn["uri"], 'r') as f:
108
- lines = f.readlines()
109
- start = max(0, def_line - 2)
110
- end = min(len(lines), def_line + 3)
111
-
112
- if start < def_line:
113
- output.append("\nContext:")
114
- for i in range(start, end):
115
- prefix = ">" if i == def_line else " "
116
- output.append(f"{prefix} {i+1:4d} | {lines[i].rstrip()}")
117
- except Exception:
118
- pass
119
-
120
- return {
121
- "success": True,
122
- "stdout": "\n".join(output),
123
- "stderr": ""
124
- }
125
-
126
- except Exception as e:
127
- return {
128
- "success": False,
129
- "stderr": f"Error finding definition: {str(e)}",
130
- "stdout": ""
131
- }
139
+ except Exception as e:
140
+ return {
141
+ "success": False,
142
+ "stderr": f"Error finding definition: {str(e)}",
143
+ "stdout": ""
144
+ }
145
+ finally:
146
+ if lsp:
147
+ lsp.shutdown()
132
148
  finally:
133
- if lsp:
134
- lsp.shutdown()
149
+ # Always restore original directory
150
+ os.chdir(original_dir)
@@ -11,7 +11,12 @@ class LSPFindReferencesTool:
11
11
  "file_path": "Path to the file containing the symbol",
12
12
  "line": "Line number (0-based) of the symbol",
13
13
  "character": "Character position in the line",
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
@@ -26,6 +31,7 @@ class LSPFindReferencesTool:
26
31
  line = args.get("line", None)
27
32
  character = args.get("character", None)
28
33
  language = args.get("language", "")
34
+ root_dir = args.get("root_dir", ".")
29
35
 
30
36
  # Validate inputs
31
37
  if not all([file_path, line is not None, character is not None, language]):
@@ -52,60 +58,70 @@ class LSPFindReferencesTool:
52
58
  "stdout": ""
53
59
  }
54
60
 
55
- # Get LSP instance
56
- registry = LSPRegistry.get_global_lsp_registry()
57
- lsp = registry.create_lsp(language)
61
+ # Store current directory
62
+ original_dir = os.getcwd()
58
63
 
59
- if not lsp:
60
- return {
61
- "success": False,
62
- "stderr": f"No LSP support for language: {language}",
63
- "stdout": ""
64
- }
65
-
66
64
  try:
67
- # Initialize LSP
68
- if not lsp.initialize(os.path.abspath(os.getcwd())):
65
+ # Change to root_dir
66
+ os.chdir(root_dir)
67
+
68
+ # Get LSP instance
69
+ registry = LSPRegistry.get_global_lsp_registry()
70
+ lsp = registry.create_lsp(language)
71
+
72
+ if not lsp:
69
73
  return {
70
74
  "success": False,
71
- "stderr": "LSP initialization failed",
75
+ "stderr": f"No LSP support for language: {language}",
72
76
  "stdout": ""
73
77
  }
74
78
 
75
- # Get symbol at position
76
- symbol = LSPRegistry.get_text_at_position(file_path, line, character)
77
- if not symbol:
79
+ try:
80
+ # Initialize LSP
81
+ if not lsp.initialize(os.path.abspath(os.getcwd())):
82
+ return {
83
+ "success": False,
84
+ "stderr": "LSP initialization failed",
85
+ "stdout": ""
86
+ }
87
+
88
+ # Get symbol at position
89
+ symbol = LSPRegistry.get_text_at_position(file_path, line, character)
90
+ if not symbol:
91
+ return {
92
+ "success": False,
93
+ "stderr": f"No symbol found at position {line}:{character}",
94
+ "stdout": ""
95
+ }
96
+
97
+ # Find references
98
+ refs = lsp.find_references(file_path, (line, character))
99
+
100
+ # Format output
101
+ output = [f"References to '{symbol}':\n"]
102
+ for ref in refs:
103
+ ref_line = ref["range"]["start"]["line"]
104
+ ref_char = ref["range"]["start"]["character"]
105
+ context = LSPRegistry.get_line_at_position(ref["uri"], ref_line).strip()
106
+ output.append(f"File: {ref['uri']}")
107
+ output.append(f"Line {ref_line + 1}, Col {ref_char + 1}: {context}")
108
+ output.append("-" * 40)
109
+
110
+ return {
111
+ "success": True,
112
+ "stdout": "\n".join(output) if len(refs) > 0 else f"No references found for '{symbol}'",
113
+ "stderr": ""
114
+ }
115
+
116
+ except Exception as e:
78
117
  return {
79
118
  "success": False,
80
- "stderr": f"No symbol found at position {line}:{character}",
119
+ "stderr": f"Error finding references: {str(e)}",
81
120
  "stdout": ""
82
121
  }
83
-
84
- # Find references
85
- refs = lsp.find_references(file_path, (line, character))
86
-
87
- # Format output
88
- output = [f"References to '{symbol}':\n"]
89
- for ref in refs:
90
- ref_line = ref["range"]["start"]["line"]
91
- ref_char = ref["range"]["start"]["character"]
92
- context = LSPRegistry.get_line_at_position(ref["uri"], ref_line).strip()
93
- output.append(f"File: {ref['uri']}")
94
- output.append(f"Line {ref_line + 1}, Col {ref_char + 1}: {context}")
95
- output.append("-" * 40)
96
-
97
- return {
98
- "success": True,
99
- "stdout": "\n".join(output) if len(refs) > 0 else f"No references found for '{symbol}'",
100
- "stderr": ""
101
- }
102
-
103
- except Exception as e:
104
- return {
105
- "success": False,
106
- "stderr": f"Error finding references: {str(e)}",
107
- "stdout": ""
108
- }
122
+ finally:
123
+ if lsp:
124
+ lsp.shutdown()
109
125
  finally:
110
- if lsp:
111
- lsp.shutdown()
126
+ # Always restore original directory
127
+ os.chdir(original_dir)