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
@@ -18,7 +18,7 @@ class GitSquashTool:
18
18
  """Perform soft reset to specified commit hash"""
19
19
  try:
20
20
  subprocess.Popen(
21
- ["git", "reset", "--soft", commit_hash],
21
+ ["git", "reset", "--mixed", commit_hash],
22
22
  stdout=subprocess.DEVNULL,
23
23
  stderr=subprocess.DEVNULL
24
24
  ).wait()
jarvis/jarvis_lsp/base.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from abc import ABC, abstractmethod
2
- from typing import List, Dict, Optional, Tuple, Any
2
+ from typing import List, Dict, Optional, Tuple, Any, Union
3
3
 
4
4
  class BaseLSP(ABC):
5
5
  """Base class for Language Server Protocol integration.
@@ -11,7 +11,7 @@ class BaseLSP(ABC):
11
11
  4. Symbol analysis
12
12
  """
13
13
 
14
- language: str = "" # Language identifier, should be overridden by subclasses
14
+ language: Union[str, List[str]] = "" # Language identifier, should be overridden by subclasses
15
15
 
16
16
  @abstractmethod
17
17
  def initialize(self, workspace_path: str) -> bool:
@@ -67,17 +67,6 @@ class BaseLSP(ABC):
67
67
  """
68
68
  return None
69
69
 
70
- @abstractmethod
71
- def get_document_symbols(self, file_path: str) -> List[Dict[str, Any]]:
72
- """Get all symbols in document.
73
-
74
- Args:
75
- file_path: Path to the file
76
-
77
- Returns:
78
- List of symbols with their locations and types
79
- """
80
- return []
81
70
 
82
71
  @abstractmethod
83
72
  def get_diagnostics(self, file_path: str) -> List[Dict[str, Any]]:
@@ -112,19 +101,6 @@ class BaseLSP(ABC):
112
101
  """
113
102
  return []
114
103
 
115
- @abstractmethod
116
- def prepare_rename(self, file_path: str, position: Tuple[int, int]) -> Optional[Dict[str, Any]]:
117
- """Check if symbol at position can be renamed.
118
-
119
- Args:
120
- file_path: Path to the file
121
- position: Symbol position
122
-
123
- Returns:
124
- Range that would be renamed or None if rename not allowed
125
- """
126
- return None
127
-
128
104
 
129
105
  def shutdown(self):
130
106
  """Shutdown LSP server cleanly."""
jarvis/jarvis_lsp/cpp.py CHANGED
@@ -9,7 +9,7 @@ from jarvis.jarvis_utils.output import OutputType, PrettyOutput
9
9
  class CPPLSP(BaseLSP):
10
10
  """C++ LSP implementation using clangd."""
11
11
 
12
- language = "cpp"
12
+ language = ["cpp", "c"]
13
13
 
14
14
  @staticmethod
15
15
  def check() -> bool:
@@ -80,12 +80,7 @@ class CPPLSP(BaseLSP):
80
80
  "position": {"line": position[0], "character": position[1]}
81
81
  })
82
82
  return result[0] if result else None
83
-
84
- def get_document_symbols(self, file_path: str) -> List[Dict[str, Any]]:
85
- result = self._send_request("textDocument/documentSymbol", {
86
- "textDocument": {"uri": f"file://{file_path}"}
87
- })
88
- return result or [] # type: ignore
83
+
89
84
 
90
85
  def get_diagnostics(self, file_path: str) -> List[Dict[str, Any]]:
91
86
  # Send didOpen notification to trigger diagnostics
@@ -107,13 +102,6 @@ class CPPLSP(BaseLSP):
107
102
  pass
108
103
  return []
109
104
 
110
- def prepare_rename(self, file_path: str, position: Tuple[int, int]) -> Optional[Dict[str, Any]]:
111
- result = self._send_request("textDocument/prepareRename", {
112
- "textDocument": {"uri": f"file://{file_path}"},
113
- "position": {"line": position[0], "character": position[1]}
114
- })
115
- return result
116
-
117
105
 
118
106
  def shutdown(self):
119
107
  if self.clangd_process:
jarvis/jarvis_lsp/go.py CHANGED
@@ -87,12 +87,6 @@ class GoLSP(BaseLSP):
87
87
  })
88
88
  return result[0] if result else None
89
89
 
90
- def get_document_symbols(self, file_path: str) -> List[Dict[str, Any]]:
91
- result = self._send_request("textDocument/documentSymbol", {
92
- "textDocument": {"uri": f"file://{file_path}"}
93
- })
94
- return result or [] # type: ignore
95
-
96
90
  def get_diagnostics(self, file_path: str) -> List[Dict[str, Any]]:
97
91
  # Send didOpen notification to trigger diagnostics
98
92
  self._send_request("textDocument/didOpen", {
@@ -113,13 +107,6 @@ class GoLSP(BaseLSP):
113
107
  pass
114
108
  return []
115
109
 
116
- def prepare_rename(self, file_path: str, position: Tuple[int, int]) -> Optional[Dict[str, Any]]:
117
- result = self._send_request("textDocument/prepareRename", {
118
- "textDocument": {"uri": f"file://{file_path}"},
119
- "position": {"line": position[0], "character": position[1]}
120
- })
121
- return result
122
-
123
110
 
124
111
  def shutdown(self):
125
112
  if self.gopls_process:
@@ -18,7 +18,7 @@ class PythonLSP(BaseLSP):
18
18
  def _get_script(self, file_path: str):
19
19
  if file_path not in self.script_cache:
20
20
  try:
21
- with open(file_path, 'r') as f:
21
+ with open(file_path, 'r', errors="ignore") as f:
22
22
  content = f.read()
23
23
  self.script_cache[file_path] = jedi.Script(code=content, path=file_path)
24
24
  except Exception:
@@ -54,16 +54,6 @@ class PythonLSP(BaseLSP):
54
54
  }
55
55
  }
56
56
 
57
- def get_document_symbols(self, file_path: str) -> List[Dict[str, Any]]:
58
- script = self._get_script(file_path)
59
- if not script:
60
- return []
61
- try:
62
- names = script.get_names()
63
- return [self._location_to_dict(name) for name in names]
64
- except Exception:
65
- return []
66
-
67
57
  def get_diagnostics(self, file_path: str) -> List[Dict[str, Any]]:
68
58
  script = self._get_script(file_path)
69
59
  if not script:
@@ -82,24 +72,5 @@ class PythonLSP(BaseLSP):
82
72
  except Exception:
83
73
  return []
84
74
 
85
- def prepare_rename(self, file_path: str, position: Tuple[int, int]) -> Optional[Dict[str, Any]]:
86
- script = self._get_script(file_path)
87
- if not script:
88
- return None
89
- try:
90
- refs = script.get_references(line=position[0] + 1, column=position[1])
91
- if refs:
92
- ref = refs[0]
93
- return {
94
- "range": {
95
- "start": {"line": ref.line - 1, "character": ref.column},
96
- "end": {"line": ref.line - 1, "character": ref.column + len(ref.name)}
97
- }
98
- }
99
- except Exception:
100
- return None
101
- return None
102
-
103
-
104
75
  def shutdown(self):
105
76
  self.script_cache.clear()
@@ -11,9 +11,7 @@ REQUIRED_METHODS = [
11
11
  ('initialize', ['workspace_path']),
12
12
  ('find_references', ['file_path', 'position']),
13
13
  ('find_definition', ['file_path', 'position']),
14
- ('get_document_symbols', ['file_path']),
15
14
  ('get_diagnostics', ['file_path']),
16
- ('prepare_rename', ['file_path', 'position']),
17
15
  ('shutdown', [])
18
16
  ]
19
17
 
@@ -29,7 +27,7 @@ class LSPRegistry:
29
27
  if not os.path.exists(user_lsp_dir):
30
28
  try:
31
29
  os.makedirs(user_lsp_dir)
32
- with open(os.path.join(user_lsp_dir, "__init__.py"), "w") as f:
30
+ with open(os.path.join(user_lsp_dir, "__init__.py"), "w", errors="ignore") as f:
33
31
  pass
34
32
  except Exception as e:
35
33
  PrettyOutput.print(f"创建 LSP 目录失败: {str(e)}", OutputType.ERROR)
@@ -100,7 +98,11 @@ class LSPRegistry:
100
98
  if hasattr(obj, 'check'):
101
99
  if not obj.check(): # type: ignore
102
100
  continue
103
- lsp_servers[obj.language] = obj
101
+ if isinstance(obj.language, str):
102
+ lsp_servers[obj.language] = obj
103
+ elif isinstance(obj.language, list):
104
+ for lang in obj.language: # type: ignore
105
+ lsp_servers[lang] = obj
104
106
  break
105
107
  except Exception as e:
106
108
  PrettyOutput.print(f"加载 LSP {module_name} 失败: {str(e)}", OutputType.ERROR)
@@ -154,7 +156,7 @@ class LSPRegistry:
154
156
  @staticmethod
155
157
  def get_text_at_position(file_path: str, line: int, start_character: int) -> str:
156
158
  """Get text at position."""
157
- with open(file_path, 'r') as file:
159
+ with open(file_path, 'r', errors="ignore") as file:
158
160
  lines = file.readlines()
159
161
  symbol = re.search(r'\b\w+\b', lines[line][start_character:])
160
162
  return symbol.group() if symbol else ""
@@ -162,7 +164,7 @@ class LSPRegistry:
162
164
  @staticmethod
163
165
  def get_line_at_position(file_path: str, line: int) -> str:
164
166
  """Get line at position."""
165
- with open(file_path, 'r') as file:
167
+ with open(file_path, 'r', errors="ignore") as file:
166
168
  lines = file.readlines()
167
169
  return lines[line]
168
170
 
@@ -192,14 +194,8 @@ def main():
192
194
  PrettyOutput.print("LSP 初始化失败", OutputType.WARNING)
193
195
  return 1
194
196
 
195
- try:
196
- # Execute requested action
197
- if args.action == 'symbols':
198
- symbols = lsp.get_document_symbols(args.file)
199
- for symbol in symbols:
200
- print(f"Symbol {LSPRegistry.get_text_at_position(args.file, symbol['range']['start']['line'], symbol['range']['start']['character'])} at {symbol['range']['start']['line']}:{symbol['range']['start']['character']}: {symbol['uri']}")
201
-
202
- elif args.action == 'diagnostics':
197
+ try:
198
+ if args.action == 'diagnostics':
203
199
  diagnostics = lsp.get_diagnostics(args.file)
204
200
  for diag in diagnostics:
205
201
  severity = ['Error', 'Warning', 'Info', 'Hint'][diag['severity'] - 1]
jarvis/jarvis_lsp/rust.py CHANGED
@@ -89,11 +89,6 @@ class RustLSP(BaseLSP):
89
89
  })
90
90
  return result[0] if result else None
91
91
 
92
- def get_document_symbols(self, file_path: str) -> List[Dict[str, Any]]:
93
- result = self._send_request("textDocument/documentSymbol", {
94
- "textDocument": {"uri": f"file://{file_path}"}
95
- })
96
- return result or [] # type: ignore
97
92
 
98
93
  def get_diagnostics(self, file_path: str) -> List[Dict[str, Any]]:
99
94
  # Send didOpen notification to trigger diagnostics
@@ -115,13 +110,6 @@ class RustLSP(BaseLSP):
115
110
  pass
116
111
  return []
117
112
 
118
- def prepare_rename(self, file_path: str, position: Tuple[int, int]) -> Optional[Dict[str, Any]]:
119
- result = self._send_request("textDocument/prepareRename", {
120
- "textDocument": {"uri": f"file://{file_path}"},
121
- "position": {"line": position[0], "character": position[1]}
122
- })
123
- return result
124
-
125
113
 
126
114
  def shutdown(self):
127
115
  if self.analyzer_process:
@@ -5,8 +5,10 @@ import yaml
5
5
 
6
6
  from jarvis.jarvis_agent import Agent
7
7
  from jarvis.jarvis_agent.output_handler import OutputHandler
8
+ from jarvis.jarvis_tools.registry import ToolRegistry
8
9
  from jarvis.jarvis_utils.input import get_multiline_input
9
10
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
11
+ from jarvis.jarvis_utils.utils import init_env
10
12
 
11
13
 
12
14
  class MultiAgent(OutputHandler):
@@ -18,65 +20,63 @@ class MultiAgent(OutputHandler):
18
20
 
19
21
  def prompt(self) -> str:
20
22
  return f"""
21
- # 🤖 多智能体消息处理系统
22
- 您是多智能体系统的一部分,通过结构化消息进行通信。
23
-
24
- # 🎯 核心规则
25
- ## 关键操作规则
26
- - 每轮只能执行一个操作:
27
- - 要么使用一个工具(文件操作、询问用户等)
28
- - 要么发送一条消息给其他智能体
29
- - 切勿在同一轮中同时进行这两种操作
30
-
31
- ## 消息流控制
32
- - 发送消息后等待响应
33
- - 处理响应后再进行下一步操作
34
- - 切勿同时发送多条消息
35
- - 切勿将消息与工具调用混合使用
36
-
37
- # 📝 消息格式
23
+ # 多智能体协作系统
24
+
25
+ ## 身份与角色定位
26
+ - **核心职责**:作为多智能体系统的协调者,通过结构化消息实现高效协作
27
+ - **关键能力**:消息路由、任务分发、结果整合、流程协调
28
+ - **工作范围**:在多个专业智能体之间建立有效沟通渠道
29
+
30
+ ## 交互原则与策略
31
+ ### 消息处理规范
32
+ - **单一操作原则**:每轮只执行一个操作(工具调用或消息发送)
33
+ - **完整性原则**:确保消息包含所有必要信息,避免歧义
34
+ - **明确性原则**:清晰表达意图、需求和期望结果
35
+ - **上下文保留**:在消息中包含足够的背景信息
36
+
37
+ ### 消息格式标准
38
38
  ```
39
39
  <SEND_MESSAGE>
40
40
  to: 智能体名称 # 目标智能体名称
41
41
  content: |
42
- 消息内容 # 消息内容
43
- 可使用多行 # 如果需要
44
- 保持正确的缩进
42
+ # 消息主题
43
+
44
+ ## 背景信息
45
+ [提供必要的上下文和背景]
46
+
47
+ ## 具体需求
48
+ [明确表达期望完成的任务]
49
+
50
+ ## 相关资源
51
+ [列出相关文档、数据或工具]
52
+
53
+ ## 期望结果
54
+ [描述期望的输出格式和内容]
45
55
  </SEND_MESSAGE>
46
56
  ```
47
57
 
48
- # 🔄 操作顺序
49
- 1. 选择最重要的操作
50
- - 评估优先级
51
- - 选择一个操作
52
- - 执行该操作
53
-
54
- 2. 等待响应
55
- - 处理结果/响应
56
- - 计划下一步操作
57
- - 等待下一轮
58
-
59
- 3. 处理响应
60
- - 处理收到的消息
61
- - 需要时回复发送者
62
- - 根据响应继续任务
63
-
64
- # 👥 可用智能体
65
- {chr(10).join([f"- {c['name']}: {c.get('description', '')}" for c in self.agents_config])}
66
-
67
- # ❗ 重要规则
68
- 1. 每轮只能执行一个操作
69
- 2. 等待响应
70
- 3. 处理后再进行下一步
71
- 4. 回复消息
72
- 5. 需要时转发任务
73
-
74
- # 💡 提示
75
- - 第一个操作将被执行
76
- - 额外的操作将被忽略
77
- - 总是先处理响应
78
- - 需要时发送消息以继续任务
79
- - 处理并回复收到的消息
58
+ ## 协作流程规范
59
+ ### 任务分发流程
60
+ 1. **需求分析**:理解用户需求并确定最适合的智能体
61
+ 2. **任务分解**:将复杂任务分解为可管理的子任务
62
+ 3. **精准分发**:根据专长将任务分配给合适的智能体
63
+ 4. **结果整合**:收集各智能体的输出并整合为连贯结果
64
+
65
+ ### 消息流控制
66
+ 1. **单向流动**:发送消息后等待响应,避免消息风暴
67
+ 2. **优先级管理**:处理紧急消息优先,保持任务顺序
68
+ 3. **状态跟踪**:记录每个任务的当前状态和处理进度
69
+ 4. **异常处理**:优雅处理超时、错误和意外响应
70
+
71
+ ## 可用智能体资源
72
+ {chr(10).join([f"- **{c['name']}**: {c.get('description', '')}" for c in self.agents_config])}
73
+
74
+ ## 最佳实践指南
75
+ 1. **任务明确化**:每个消息专注于单一、明确的任务
76
+ 2. **信息充分性**:提供足够信息让接收者能独立完成任务
77
+ 3. **反馈循环**:建立清晰的反馈机制,及时调整方向
78
+ 4. **知识共享**:确保关键信息在相关智能体间共享
79
+ 5. **协作效率**:避免不必要的消息传递,减少协调开销
80
80
  """
81
81
 
82
82
  def can_handle(self, response: str) -> bool:
@@ -115,6 +115,15 @@ content: |
115
115
 
116
116
  def init_agents(self):
117
117
  for config in self.agents_config:
118
+ output_handler = config.get('output_handler', [])
119
+ if len(output_handler) == 0:
120
+ output_handler = [
121
+ ToolRegistry(),
122
+ self,
123
+ ]
124
+ else:
125
+ output_handler.append(self)
126
+ config['output_handler'] = output_handler
118
127
  agent = Agent(**config)
119
128
  self.agents[config['name']] = agent
120
129
 
@@ -146,6 +155,7 @@ def main():
146
155
  Returns:
147
156
  最终处理结果
148
157
  """
158
+ init_env()
149
159
  import argparse
150
160
  parser = argparse.ArgumentParser(description="多智能体系统启动器")
151
161
  parser.add_argument("--config", "-c", required=True, help="YAML配置文件路径")
@@ -153,7 +163,7 @@ def main():
153
163
  args = parser.parse_args()
154
164
 
155
165
  try:
156
- with open(args.config, 'r') as f:
166
+ with open(args.config, 'r', errors="ignore") as f:
157
167
  config_data = yaml.safe_load(f)
158
168
 
159
169
  # 获取agents配置
@@ -16,7 +16,6 @@ REQUIRED_METHODS = [
16
16
  ('set_model_name', ['model_name']),
17
17
  ('get_model_list', []),
18
18
  ('set_suppress_output', ['suppress']),
19
- ('upload_files', ['file_list'])
20
19
  ]
21
20
 
22
21
  class PlatformRegistry:
@@ -32,7 +31,7 @@ class PlatformRegistry:
32
31
  try:
33
32
  os.makedirs(user_platform_dir)
34
33
  # 创建 __init__.py 使其成为 Python 包
35
- with open(os.path.join(user_platform_dir, "__init__.py"), "w") as f:
34
+ with open(os.path.join(user_platform_dir, "__init__.py"), "w", errors="ignore") as f:
36
35
  pass
37
36
 
38
37
  pass
@@ -219,7 +219,7 @@ def service_command(args):
219
219
  timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
220
220
  log_file = os.path.join(logs_dir, f"conversation_{conversation_id}_{timestamp}.txt")
221
221
 
222
- with open(log_file, "w", encoding="utf-8") as f:
222
+ with open(log_file, "w", encoding="utf-8", errors="ignore") as f:
223
223
  f.write(f"Conversation ID: {conversation_id}\n")
224
224
  f.write(f"Timestamp: {timestamp}\n")
225
225
  f.write(f"Model: {model}\n\n")
@@ -464,7 +464,7 @@ def service_command(args):
464
464
  "response": full_response
465
465
  }
466
466
 
467
- with open(log_file, "w", encoding="utf-8") as f:
467
+ with open(log_file, "w", encoding="utf-8", errors="ignore") as f:
468
468
  json.dump(log_data, f, ensure_ascii=False, indent=2)
469
469
 
470
470
  PrettyOutput.print(f"Stream conversation logged to {log_file}", OutputType.INFO)
@@ -501,7 +501,7 @@ def service_command(args):
501
501
  "error": error_msg
502
502
  }
503
503
 
504
- with open(log_file, "w", encoding="utf-8") as f:
504
+ with open(log_file, "w", encoding="utf-8", errors="ignore") as f:
505
505
  json.dump(log_data, f, ensure_ascii=False, indent=2)
506
506
 
507
507
  PrettyOutput.print(f"Stream error logged to {log_file}", OutputType.ERROR)
jarvis/jarvis_rag/main.py CHANGED
@@ -96,7 +96,7 @@ class TextFileProcessor(FileProcessor):
96
96
  raise UnicodeDecodeError(f"Failed to decode file with supported encodings: {file_path}") # type: ignore
97
97
 
98
98
  # Use the detected encoding to read the file
99
- with open(file_path, 'r', encoding=detected_encoding, errors='replace') as f:
99
+ with open(file_path, 'r', encoding=detected_encoding, errors='ignore') as f:
100
100
  content = f.read()
101
101
 
102
102
  # Normalize Unicode characters
@@ -1,4 +1,5 @@
1
1
  from typing import Dict, Any
2
+ import os
2
3
 
3
4
  from yaspin import yaspin
4
5
  from jarvis.jarvis_codebase.main import CodeBase
@@ -22,6 +23,11 @@ class AskCodebaseTool:
22
23
  "type": "integer",
23
24
  "description": "要分析的最相关文件数量(可选)",
24
25
  "default": 20
26
+ },
27
+ "root_dir": {
28
+ "type": "string",
29
+ "description": "代码库根目录路径(可选)",
30
+ "default": "."
25
31
  }
26
32
  },
27
33
  "required": ["question"]
@@ -48,27 +54,39 @@ class AskCodebaseTool:
48
54
  try:
49
55
  question = args["question"]
50
56
  top_k = args.get("top_k", 20)
51
- # Create new CodeBase instance
52
- git_root = find_git_root()
53
- codebase = CodeBase(git_root)
54
-
55
- # Use ask_codebase method
57
+ root_dir = args.get("root_dir", ".")
56
58
 
57
- files, response = codebase.ask_codebase(question, top_k)
59
+ # Store current directory
60
+ original_dir = os.getcwd()
58
61
 
59
-
60
- # Print found files
61
- if files:
62
- output = "找到的相关文件:\n"
63
- for file in files:
64
- output += f"- {file['file']} ({file['reason']})\n"
65
- PrettyOutput.print(output, OutputType.INFO, lang="markdown")
66
-
67
- return {
68
- "success": True,
69
- "stdout": response,
70
- "stderr": ""
71
- }
62
+ try:
63
+ # Change to root_dir
64
+ os.chdir(root_dir)
65
+
66
+ # Create new CodeBase instance
67
+ git_root = find_git_root()
68
+ codebase = CodeBase(git_root)
69
+
70
+ # Use ask_codebase method
71
+ files, response = codebase.ask_codebase(question, top_k)
72
+
73
+ # Print found files
74
+ if files:
75
+ output = "找到的相关文件:\n"
76
+ for file in files:
77
+ output += f"- {file['file']} ({file['reason']})\n"
78
+ PrettyOutput.print(output, OutputType.SUCCESS, lang="markdown")
79
+
80
+ PrettyOutput.print(response, OutputType.SUCCESS, lang="markdown")
81
+
82
+ return {
83
+ "success": True,
84
+ "stdout": response,
85
+ "stderr": ""
86
+ }
87
+ finally:
88
+ # Always restore original directory
89
+ os.chdir(original_dir)
72
90
  except Exception as e:
73
91
  error_msg = f"分析代码库失败: {str(e)}"
74
92
  PrettyOutput.print(error_msg, OutputType.WARNING)
@@ -84,12 +102,14 @@ def main():
84
102
  parser = argparse.ArgumentParser(description='Ask questions about the codebase')
85
103
  parser.add_argument('question', help='Question about the codebase')
86
104
  parser.add_argument('--top-k', type=int, help='Number of files to analyze', default=20)
105
+ parser.add_argument('--root-dir', type=str, help='Root directory of the codebase', default=".")
87
106
 
88
107
  args = parser.parse_args()
89
108
  tool = AskCodebaseTool()
90
109
  result = tool.execute({
91
110
  "question": args.question,
92
- "top_k": args.top_k
111
+ "top_k": args.top_k,
112
+ "root_dir": args.root_dir
93
113
  })
94
114
 
95
115
  if result["success"]: