jarvis-ai-assistant 0.1.98__py3-none-any.whl → 0.1.99__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 (40) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/agent.py +199 -157
  3. jarvis/jarvis_code_agent/__init__.py +0 -0
  4. jarvis/jarvis_code_agent/main.py +203 -0
  5. jarvis/jarvis_codebase/main.py +412 -284
  6. jarvis/jarvis_coder/file_select.py +209 -0
  7. jarvis/jarvis_coder/git_utils.py +64 -2
  8. jarvis/jarvis_coder/main.py +11 -389
  9. jarvis/jarvis_coder/patch_handler.py +84 -14
  10. jarvis/jarvis_coder/plan_generator.py +49 -7
  11. jarvis/jarvis_rag/main.py +9 -9
  12. jarvis/jarvis_smart_shell/main.py +5 -7
  13. jarvis/models/base.py +6 -1
  14. jarvis/models/ollama.py +2 -2
  15. jarvis/models/registry.py +3 -6
  16. jarvis/tools/ask_user.py +6 -6
  17. jarvis/tools/codebase_qa.py +5 -7
  18. jarvis/tools/create_code_sub_agent.py +55 -0
  19. jarvis/tools/{sub_agent.py → create_sub_agent.py} +4 -1
  20. jarvis/tools/execute_code_modification.py +72 -0
  21. jarvis/tools/{file_ops.py → file_operation.py} +13 -14
  22. jarvis/tools/find_related_files.py +86 -0
  23. jarvis/tools/methodology.py +25 -25
  24. jarvis/tools/rag.py +32 -32
  25. jarvis/tools/registry.py +72 -36
  26. jarvis/tools/search.py +1 -1
  27. jarvis/tools/select_code_files.py +64 -0
  28. jarvis/utils.py +153 -49
  29. {jarvis_ai_assistant-0.1.98.dist-info → jarvis_ai_assistant-0.1.99.dist-info}/METADATA +1 -1
  30. jarvis_ai_assistant-0.1.99.dist-info/RECORD +52 -0
  31. {jarvis_ai_assistant-0.1.98.dist-info → jarvis_ai_assistant-0.1.99.dist-info}/entry_points.txt +2 -1
  32. jarvis/main.py +0 -155
  33. jarvis/tools/coder.py +0 -69
  34. jarvis_ai_assistant-0.1.98.dist-info/RECORD +0 -47
  35. /jarvis/tools/{shell.py → execute_shell.py} +0 -0
  36. /jarvis/tools/{generator.py → generate_tool.py} +0 -0
  37. /jarvis/tools/{webpage.py → read_webpage.py} +0 -0
  38. {jarvis_ai_assistant-0.1.98.dist-info → jarvis_ai_assistant-0.1.99.dist-info}/LICENSE +0 -0
  39. {jarvis_ai_assistant-0.1.98.dist-info → jarvis_ai_assistant-0.1.99.dist-info}/WHEEL +0 -0
  40. {jarvis_ai_assistant-0.1.98.dist-info → jarvis_ai_assistant-0.1.99.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,7 @@
1
1
  import re
2
2
  from typing import Dict, List, Tuple
3
3
  from jarvis.models.registry import PlatformRegistry
4
- from jarvis.utils import PrettyOutput, OutputType, get_multiline_input
4
+ from jarvis.utils import PrettyOutput, OutputType, get_multiline_input, get_single_line_input, is_long_context
5
5
 
6
6
  class PlanGenerator:
7
7
  """Modification plan generator"""
@@ -48,7 +48,42 @@ class PlanGenerator:
48
48
  return prompt
49
49
 
50
50
 
51
- def generate_plan(self, feature: str, related_files: List[Dict]) -> Tuple[str, Dict[str,str]]:
51
+ @staticmethod
52
+ def get_key_code(files: List[str], feature: str)->List[Dict[str, List[str]]]:
53
+ """Extract relevant key code snippets from files"""
54
+ ret = []
55
+ for file in files:
56
+ PrettyOutput.print(f"Analyzing file: {file}", OutputType.INFO)
57
+ model = PlatformRegistry.get_global_platform_registry().get_codegen_platform()
58
+ model.set_suppress_output(True)
59
+ file_path = file
60
+ content = open(file_path, "r", encoding="utf-8").read()
61
+
62
+ try:
63
+ prompt = f"""You are a code analysis expert who can extract relevant snippets from code.
64
+ Please return in the following format:
65
+ <PART>
66
+ content
67
+ </PART>
68
+
69
+ Multiple snippets can be returned. If the file content is not relevant to the requirement, return empty.
70
+
71
+ Requirement: {feature}
72
+ File path: {file_path}
73
+ Code content:
74
+ {content}
75
+ """
76
+
77
+ # 调用大模型进行分析
78
+ response = model.chat_until_success(prompt)
79
+
80
+ parts = re.findall(r'<PART>\n(.*?)\n</PART>', response, re.DOTALL)
81
+ ret.append({"file_path": file, "parts": parts})
82
+ except Exception as e:
83
+ PrettyOutput.print(f"Failed to analyze file: {str(e)}", OutputType.ERROR)
84
+ return ret
85
+
86
+ def generate_plan(self, feature: str, related_files: List[str]) -> Dict[str,str]:
52
87
  """Generate modification plan
53
88
 
54
89
  Args:
@@ -59,8 +94,15 @@ class PlanGenerator:
59
94
  Tuple[str, Dict[str,str]]: Modification plan, return None if user cancels
60
95
  """
61
96
  additional_info = ""
97
+ file_info = []
98
+ if is_long_context(related_files):
99
+ file_info = PlanGenerator.get_key_code(related_files, feature)
100
+ else:
101
+ for file in related_files:
102
+ file_info.append({"file_path": file, "parts": [open(file, "r", encoding="utf-8").read()]})
103
+
62
104
  while True:
63
- prompt = self._build_prompt(feature, related_files, additional_info)
105
+ prompt = self._build_prompt(feature, file_info, additional_info)
64
106
  # Build prompt
65
107
  PrettyOutput.print("Start generating modification plan...", OutputType.PROGRESS)
66
108
 
@@ -71,17 +113,17 @@ class PlanGenerator:
71
113
  PrettyOutput.print("Modification plan generation failed, please try again", OutputType.ERROR)
72
114
  tmp = get_multiline_input("Please enter your additional information or suggestions (press Enter to cancel):")
73
115
  if tmp == "__interrupt__" or prompt == "":
74
- return "", {}
116
+ return {}
75
117
  additional_info += tmp + "\n"
76
118
  continue
77
- user_input = input("\nDo you agree with this modification plan? (y/n) [y]: ").strip().lower() or 'y'
119
+ user_input = get_single_line_input("Do you agree with this modification plan? (y/n) [y]").strip().lower() or 'y'
78
120
  if user_input == 'y' or user_input == '':
79
- return raw_plan, structed_plan
121
+ return structed_plan
80
122
  elif user_input == 'n':
81
123
  # Get user feedback
82
124
  tmp = get_multiline_input("Please enter your additional information or suggestions (press Enter to cancel):")
83
125
  if prompt == "__interrupt__" or prompt == "":
84
- return "", {}
126
+ return {}
85
127
  additional_info += tmp + "\n"
86
128
  continue
87
129
 
jarvis/jarvis_rag/main.py CHANGED
@@ -90,7 +90,7 @@ class TextFileProcessor(FileProcessor):
90
90
  continue
91
91
 
92
92
  if not detected_encoding:
93
- raise UnicodeDecodeError(f"Failed to decode file with supported encodings: {file_path}")
93
+ raise UnicodeDecodeError(f"Failed to decode file with supported encodings: {file_path}") # type: ignore
94
94
 
95
95
  # Use the detected encoding to read the file
96
96
  with open(file_path, 'r', encoding=detected_encoding, errors='replace') as f:
@@ -114,7 +114,7 @@ class PDFProcessor(FileProcessor):
114
114
  @staticmethod
115
115
  def extract_text(file_path: str) -> str:
116
116
  text_parts = []
117
- with fitz.open(file_path) as doc:
117
+ with fitz.open(file_path) as doc: # type: ignore
118
118
  for page in doc:
119
119
  text_parts.append(page.get_text())
120
120
  return "\n".join(text_parts)
@@ -248,7 +248,7 @@ class RAGTool:
248
248
 
249
249
  # Create a flat index to store original vectors, for reconstruction
250
250
  self.flat_index = faiss.IndexFlatIP(self.vector_dim)
251
- self.flat_index.add(vectors)
251
+ self.flat_index.add(vectors) # type: ignore
252
252
 
253
253
  # Create an IVF index for fast search
254
254
  nlist = max(4, int(vectors.shape[0] / 1000)) # 每1000个向量一个聚类中心
@@ -256,8 +256,8 @@ class RAGTool:
256
256
  self.index = faiss.IndexIVFFlat(quantizer, self.vector_dim, nlist, faiss.METRIC_INNER_PRODUCT)
257
257
 
258
258
  # Train and add vectors
259
- self.index.train(vectors)
260
- self.index.add(vectors)
259
+ self.index.train(vectors) # type: ignore
260
+ self.index.add(vectors) # type: ignore
261
261
  # Set the number of clusters to probe during search
262
262
  self.index.nprobe = min(nlist, 10)
263
263
 
@@ -341,7 +341,7 @@ class RAGTool:
341
341
  except Exception as e:
342
342
  PrettyOutput.print(f"Failed to get vector representation: {str(e)}",
343
343
  output_type=OutputType.ERROR)
344
- return np.zeros((len(texts), self.vector_dim), dtype=np.float32)
344
+ return np.zeros((len(texts), self.vector_dim), dtype=np.float32) # type: ignore
345
345
 
346
346
  def _process_document_batch(self, documents: List[Document]) -> List[np.ndarray]:
347
347
  """Process a batch of documents vectorization
@@ -361,7 +361,7 @@ Content: {doc.content}
361
361
  """
362
362
  texts.append(combined_text)
363
363
 
364
- return self._get_embedding_batch(texts)
364
+ return self._get_embedding_batch(texts) # type: ignore
365
365
 
366
366
  def _process_file(self, file_path: str) -> List[Document]:
367
367
  """Process a single file"""
@@ -516,7 +516,7 @@ Content: {doc.content}
516
516
  if d.metadata['file_path'] == doc.metadata['file_path']), None)
517
517
  if doc_idx is not None:
518
518
  # Reconstruct vectors from flat index
519
- vector = np.zeros((1, self.vector_dim), dtype=np.float32)
519
+ vector = np.zeros((1, self.vector_dim), dtype=np.float32) # type: ignore
520
520
  self.flat_index.reconstruct(doc_idx, vector.ravel())
521
521
  unchanged_vectors.append(vector)
522
522
 
@@ -585,7 +585,7 @@ Content: {doc.content}
585
585
 
586
586
  # Initial search more results for MMR
587
587
  initial_k = min(top_k * 2, len(self.documents))
588
- distances, indices = self.index.search(query_vector, initial_k)
588
+ distances, indices = self.index.search(query_vector, initial_k) # type: ignore
589
589
 
590
590
  # Get valid results
591
591
  valid_indices = indices[0][indices[0] != -1]
@@ -4,24 +4,23 @@ import os
4
4
  import sys
5
5
  import readline
6
6
  from typing import Optional
7
- from yaspin import yaspin
8
- from yaspin.spinners import Spinners
7
+ from yaspin import yaspin # type: ignore
8
+ from yaspin.spinners import Spinners # type: ignore
9
9
 
10
10
  from jarvis.models.registry import PlatformRegistry
11
- from jarvis.utils import PrettyOutput, OutputType, load_env_from_file
11
+ from jarvis.utils import PrettyOutput, OutputType, get_single_line_input, load_env_from_file
12
12
 
13
13
  def execute_command(command: str) -> None:
14
14
  """Show command and allow user to edit, then execute, Ctrl+C to cancel"""
15
15
  try:
16
- print("\nGenerated command (can be edited, press Enter to execute, Ctrl+C to cancel):")
17
16
  # Pre-fill input line
18
17
  readline.set_startup_hook(lambda: readline.insert_text(command))
19
18
  try:
20
- edited_command = input("> ")
19
+ edited_command = get_single_line_input("Generated command (can be edited, press Enter to execute, Ctrl+C to cancel)")
21
20
  if edited_command.strip(): # Ensure command is not empty
22
21
  os.system(edited_command)
23
22
  except KeyboardInterrupt:
24
- print("\nExecution cancelled")
23
+ PrettyOutput.print("Execution cancelled", OutputType.INFO)
25
24
  finally:
26
25
  readline.set_startup_hook() # Clear pre-filled
27
26
  except Exception as e:
@@ -38,7 +37,6 @@ def process_request(request: str) -> Optional[str]:
38
37
  """
39
38
  try:
40
39
  # Get language model instance
41
- PlatformRegistry.suppress_output = True
42
40
  model = PlatformRegistry.get_global_platform_registry().get_normal_platform()
43
41
  model.set_suppress_output(True)
44
42
 
jarvis/models/base.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from abc import ABC, abstractmethod
2
+ import re
2
3
  from typing import Dict, List, Tuple
3
4
 
4
5
  from jarvis.utils import OutputType, PrettyOutput, while_success, while_true
@@ -26,7 +27,11 @@ class BasePlatform(ABC):
26
27
  raise NotImplementedError("chat is not implemented")
27
28
 
28
29
  def chat_until_success(self, message: str) -> str:
29
- return while_true(lambda: while_success(lambda: self.chat(message), 5), 5)
30
+ def _chat():
31
+ response = self.chat(message)
32
+ response = re.sub(r'<think>(.*?)</think>', '', response, flags=re.DOTALL)
33
+ return response
34
+ return while_true(lambda: while_success(lambda: _chat(), 5), 5)
30
35
 
31
36
  @abstractmethod
32
37
  def upload_files(self, file_list: List[str]) -> List[Dict]:
jarvis/models/ollama.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import requests
2
2
  from typing import List, Dict, Tuple
3
3
  from jarvis.models.base import BasePlatform
4
- from jarvis.utils import OutputType, PrettyOutput
4
+ from jarvis.utils import OutputType, PrettyOutput, get_single_line_input
5
5
  import os
6
6
  import json
7
7
 
@@ -136,7 +136,7 @@ if __name__ == "__main__":
136
136
  ollama = OllamaPlatform()
137
137
  while True:
138
138
  try:
139
- message = input("\nInput question (Ctrl+C to exit): ")
139
+ message = get_single_line_input("\nInput question (Ctrl+C to exit)")
140
140
  ollama.chat_until_success(message)
141
141
  except KeyboardInterrupt:
142
142
  print("\nGoodbye!")
jarvis/models/registry.py CHANGED
@@ -3,8 +3,8 @@ import inspect
3
3
  import os
4
4
  import sys
5
5
  from typing import Dict, Type, Optional, List
6
- from .base import BasePlatform
7
- from ..utils import PrettyOutput, OutputType
6
+ from jarvis.models.base import BasePlatform
7
+ from jarvis.utils import PrettyOutput, OutputType
8
8
 
9
9
  REQUIRED_METHODS = [
10
10
  ('chat', ['message']), # 方法名和参数列表
@@ -23,7 +23,6 @@ class PlatformRegistry:
23
23
 
24
24
  global_platform_name = "kimi"
25
25
  global_platform_registry = None
26
- suppress_output = False
27
26
 
28
27
  @staticmethod
29
28
  def get_platform_dir() -> str:
@@ -117,7 +116,7 @@ class PlatformRegistry:
117
116
  module = importlib.import_module(module_name)
118
117
 
119
118
  # 遍历模块中的所有类
120
- for name, obj in inspect.getmembers(module):
119
+ for _, obj in inspect.getmembers(module):
121
120
  # 检查是否是BasePlatform的子类,但不是BasePlatform本身
122
121
  if (inspect.isclass(obj) and
123
122
  issubclass(obj, BasePlatform) and
@@ -126,8 +125,6 @@ class PlatformRegistry:
126
125
  # 检查平台实现
127
126
  if not PlatformRegistry.check_platform_implementation(obj):
128
127
  continue
129
- if not PlatformRegistry.suppress_output:
130
- PrettyOutput.print(f"Load platform from {os.path.join(directory, filename)}: {obj.platform_name}", OutputType.SUCCESS) # type: ignore
131
128
  platforms[obj.platform_name] = obj # type: ignore
132
129
  break
133
130
  except Exception as e:
jarvis/tools/ask_user.py CHANGED
@@ -18,22 +18,22 @@ class AskUserTool:
18
18
 
19
19
 
20
20
  def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
21
- """执行询问用户操作
21
+ """Execute the operation of asking the user
22
22
 
23
23
  Args:
24
- args: 包含问题的字典
24
+ args: A dictionary containing the question
25
25
 
26
26
  Returns:
27
- Dict: 包含用户响应的字典
27
+ Dict: A dictionary containing the user's response
28
28
  """
29
29
  try:
30
30
  question = args["question"]
31
31
 
32
- # 显示问题
33
- PrettyOutput.print("\n问题:", OutputType.SYSTEM)
32
+ # Display the question
33
+ PrettyOutput.print("\nQuestion:", OutputType.SYSTEM)
34
34
  PrettyOutput.print(question, OutputType.SYSTEM)
35
35
 
36
- # 获取用户输入
36
+ # Get user input
37
37
  user_response = get_multiline_input("Please enter your answer (input empty line to end)")
38
38
 
39
39
  if user_response == "__interrupt__":
@@ -4,7 +4,7 @@ from jarvis.jarvis_codebase.main import CodeBase
4
4
  from jarvis.utils import find_git_root, PrettyOutput, OutputType
5
5
 
6
6
  class CodebaseQATool:
7
- """代码库问答工具,用于回答关于代码库的问题"""
7
+ """Codebase QA Tool"""
8
8
 
9
9
  name = "codebase_qa"
10
10
  description = "Answer questions about the codebase, can query and understand code functionality, structure, and implementation details"
@@ -29,7 +29,7 @@ class CodebaseQATool:
29
29
  }
30
30
 
31
31
  def execute(self, params: Dict[str, Any]) -> Dict[str, Any]:
32
- """执行代码问答"""
32
+ """Execute codebase QA"""
33
33
  try:
34
34
  dir = params.get("dir")
35
35
  question = params["question"]
@@ -42,7 +42,7 @@ class CodebaseQATool:
42
42
  return {
43
43
  "success": False,
44
44
  "stdout": "",
45
- "stderr": "错误:当前目录不在Git仓库中",
45
+ "stderr": "Error: Current directory is not in a Git repository",
46
46
  "error": "NotInGitRepository"
47
47
  }
48
48
 
@@ -62,13 +62,11 @@ class CodebaseQATool:
62
62
  }
63
63
 
64
64
  except Exception as e:
65
- PrettyOutput.print(f"代码问答出错: {str(e)}", output_type=OutputType.ERROR)
65
+ PrettyOutput.print(f"Codebase QA error: {str(e)}", output_type=OutputType.ERROR)
66
66
  return {
67
67
  "success": False,
68
68
  "stdout": "",
69
- "stderr": f"执行代码问答时发生错误: {str(e)}",
69
+ "stderr": f"Error executing codebase QA: {str(e)}",
70
70
  "error": str(type(e).__name__)
71
71
  }
72
72
 
73
- def register():
74
- return CodebaseQATool()
@@ -0,0 +1,55 @@
1
+ from typing import Dict, Any
2
+
3
+ from jarvis.agent import Agent
4
+ from jarvis.utils import OutputType, PrettyOutput
5
+ from jarvis.jarvis_code_agent.main import system_prompt
6
+
7
+
8
+ class CodeSubAgentTool:
9
+ name = "create_code_sub_agent"
10
+ description = "Create a sub-agent to handle specific code development subtasks"
11
+ parameters = {
12
+ "type": "object",
13
+ "properties": {
14
+ "name": {
15
+ "type": "string",
16
+ "description": "The name of the sub-agent"
17
+ },
18
+ "subtask": {
19
+ "type": "string",
20
+ "description": "The specific code development subtask to complete"
21
+ },
22
+ },
23
+ "required": ["subtask", "name"]
24
+ }
25
+
26
+ def execute(self, args: Dict) -> Dict[str, Any]:
27
+ """Execute code development subtask"""
28
+ try:
29
+ subtask = args["subtask"]
30
+ name = args["name"]
31
+
32
+ PrettyOutput.print(f"Creating code sub-agent {name} for subtask: {subtask}", OutputType.INFO)
33
+
34
+ # Create sub-agent
35
+ sub_agent = Agent(
36
+ system_prompt=system_prompt,
37
+ name=name,
38
+ is_sub_agent=True
39
+ )
40
+
41
+ # Execute subtask
42
+ result = sub_agent.run(subtask)
43
+
44
+ return {
45
+ "success": True,
46
+ "stdout": f"Code Development Subtask Results:\n\n{result}",
47
+ "stderr": ""
48
+ }
49
+
50
+ except Exception as e:
51
+ PrettyOutput.print(str(e), OutputType.ERROR)
52
+ return {
53
+ "success": False,
54
+ "error": f"Failed to execute code development subtask: {str(e)}"
55
+ }
@@ -1,7 +1,7 @@
1
1
  from typing import Dict, Any
2
2
 
3
3
 
4
- from jarvis.agent import Agent
4
+ from jarvis.agent import Agent, origin_agent_system_prompt
5
5
  from jarvis.utils import OutputType, PrettyOutput
6
6
 
7
7
 
@@ -58,8 +58,11 @@ class SubAgentTool:
58
58
  if goal:
59
59
  task_description += f"\n\nCompletion goal:\n{goal}"
60
60
 
61
+
62
+
61
63
  # Create sub-agent
62
64
  sub_agent = Agent(
65
+ system_prompt=origin_agent_system_prompt,
63
66
  name=agent_name,
64
67
  is_sub_agent=True
65
68
  )
@@ -0,0 +1,72 @@
1
+ from typing import Dict, Any
2
+
3
+ from jarvis.agent import Agent
4
+ from jarvis.utils import OutputType, PrettyOutput
5
+ from jarvis.jarvis_coder.patch_handler import PatchHandler
6
+
7
+
8
+ class CodeModifyTool:
9
+ name = "execute_code_modification"
10
+ description = "Execute code modifications according to the provided plan"
11
+ parameters = {
12
+ "type": "object",
13
+ "properties": {
14
+ "task": {
15
+ "type": "string",
16
+ "description": "The code modification task description"
17
+ },
18
+ "structured_plan": {
19
+ "type": "object",
20
+ "description": "Dictionary mapping file paths to their modification plans. Example: {'path/to/file.py': 'Add function foo() to handle...'}",
21
+ "additionalProperties": {
22
+ "type": "string",
23
+ "description": "Modification plan for a specific file"
24
+ },
25
+ "examples": [{
26
+ "src/file1.py": "Add error handling to process_data()",
27
+ "src/file2.py": "Update API endpoint URL in get_data()"
28
+ }]
29
+ }
30
+ },
31
+ "required": ["task", "raw_plan", "structured_plan"]
32
+ }
33
+
34
+ def execute(self, args: Dict) -> Dict[str, Any]:
35
+ """Execute code modifications using PatchHandler"""
36
+ try:
37
+ task = args["task"]
38
+ structured_plan = args["structured_plan"]
39
+
40
+ PrettyOutput.print("Executing code modifications...", OutputType.INFO)
41
+
42
+ # Create patch handler instance
43
+ patch_handler = PatchHandler()
44
+
45
+ # Apply patches and handle the process
46
+ success = patch_handler.handle_patch_application(
47
+ feature=task,
48
+ structed_plan=structured_plan
49
+ )
50
+
51
+ if not success:
52
+ return {
53
+ "success": False,
54
+ "error": "Code modification was cancelled or failed",
55
+ "stdout": "Changes have been rolled back",
56
+ "stderr": ""
57
+ }
58
+
59
+ return {
60
+ "success": True,
61
+ "stdout": "Code modifications have been successfully applied and committed",
62
+ "stderr": ""
63
+ }
64
+
65
+ except Exception as e:
66
+ PrettyOutput.print(str(e), OutputType.ERROR)
67
+ return {
68
+ "success": False,
69
+ "error": f"Failed to execute code modifications: {str(e)}",
70
+ "stdout": "",
71
+ "stderr": str(e)
72
+ }
@@ -1,6 +1,5 @@
1
- from typing import Dict, Any, Protocol
1
+ from typing import Dict, Any
2
2
  import os
3
- from enum import Enum
4
3
 
5
4
  from jarvis.utils import OutputType, PrettyOutput
6
5
 
@@ -36,15 +35,15 @@ class FileOperationTool:
36
35
 
37
36
 
38
37
  def execute(self, args: Dict) -> Dict[str, Any]:
39
- """执行文件操作"""
38
+ """Execute file operations"""
40
39
  try:
41
40
  operation = args["operation"]
42
41
  filepath = args["filepath"]
43
42
  encoding = args.get("encoding", "utf-8")
44
43
 
45
- # 记录操作和完整路径
44
+ # Record the operation and the full path
46
45
  abs_path = os.path.abspath(filepath)
47
- PrettyOutput.print(f"文件操作: {operation} - {abs_path}", OutputType.INFO)
46
+ PrettyOutput.print(f"File operation: {operation} - {abs_path}", OutputType.INFO)
48
47
 
49
48
  if operation == "exists":
50
49
  exists = os.path.exists(filepath)
@@ -61,15 +60,15 @@ class FileOperationTool:
61
60
  "error": f"文件不存在: {filepath}"
62
61
  }
63
62
 
64
- # 检查文件大小
63
+ # Check file size
65
64
  if os.path.getsize(filepath) > 10 * 1024 * 1024: # 10MB
66
65
  return {
67
66
  "success": False,
68
- "error": "文件过大 (>10MB)"
67
+ "error": "File too large (>10MB)"
69
68
  }
70
69
 
71
- with open(filepath, 'r', encoding=encoding) as f:
72
- content = f.read()
70
+ content = open(filepath, 'r', encoding=encoding).read()
71
+ PrettyOutput.print(content, OutputType.INFO)
73
72
  return {
74
73
  "success": True,
75
74
  "stdout": content,
@@ -80,10 +79,10 @@ class FileOperationTool:
80
79
  if not args.get("content"):
81
80
  return {
82
81
  "success": False,
83
- "error": "写入/追加操作需要提供content参数"
82
+ "error": "Write/append operation requires providing the content parameter"
84
83
  }
85
84
 
86
- # 创建目录(如果不存在)
85
+ # Create directory (if it doesn't exist)
87
86
  os.makedirs(os.path.dirname(os.path.abspath(filepath)), exist_ok=True)
88
87
 
89
88
  mode = 'a' if operation == "append" else 'w'
@@ -92,19 +91,19 @@ class FileOperationTool:
92
91
 
93
92
  return {
94
93
  "success": True,
95
- "stdout": f"成功{operation}内容到 {filepath}",
94
+ "stdout": f"Successfully {operation} content to {filepath}",
96
95
  "stderr": ""
97
96
  }
98
97
 
99
98
  else:
100
99
  return {
101
100
  "success": False,
102
- "error": f"未知操作: {operation}"
101
+ "error": f"Unknown operation: {operation}"
103
102
  }
104
103
 
105
104
  except Exception as e:
106
105
  PrettyOutput.print(str(e), OutputType.ERROR)
107
106
  return {
108
107
  "success": False,
109
- "error": f"文件操作失败: {str(e)}"
108
+ "error": f"File operation failed: {str(e)}"
110
109
  }
@@ -0,0 +1,86 @@
1
+ import os
2
+ from typing import Any, Dict
3
+ from jarvis.jarvis_codebase.main import CodeBase
4
+ from jarvis.utils import find_git_root, PrettyOutput, OutputType
5
+
6
+ class CodebaseSearchTool:
7
+ """Codebase Search Tool"""
8
+
9
+ name = "find_related_files"
10
+ description = "Find code files related to the given requirement by searching the codebase using NLP-based semantic search. IMPORTANT: Requires complete sentence descriptions - keywords or phrases alone will not yield comprehensive results."
11
+ parameters = {
12
+ "type": "object",
13
+ "properties": {
14
+ "dir": {
15
+ "type": "string",
16
+ "description": "Project root directory"
17
+ },
18
+ "query": {
19
+ "type": "string",
20
+ "description": """Requirement description to find related code files. Must be a complete sentence that clearly describes what you're looking for.
21
+
22
+ Good examples:
23
+ - 'Need to modify the user authentication process to add password validation'
24
+ - 'Want to update the database connection configuration to support multiple databases'
25
+ - 'Looking for the code that handles file upload functionality in the system'
26
+
27
+ Bad examples (will not work well):
28
+ - 'user auth'
29
+ - 'database config'
30
+ - 'upload'""",
31
+ "examples": [
32
+ "Need to modify the error handling in the authentication process",
33
+ "Want to update how the system processes user uploads",
34
+ "Looking for the code that manages database connections"
35
+ ]
36
+ },
37
+ "top_k": {
38
+ "type": "integer",
39
+ "description": "Number of most relevant files to return",
40
+ "default": 5
41
+ }
42
+ },
43
+ "required": ["query"]
44
+ }
45
+
46
+ def execute(self, params: Dict[str, Any]) -> Dict[str, Any]:
47
+ """Execute codebase search"""
48
+ try:
49
+ dir = params.get("dir")
50
+ query = params["query"]
51
+ top_k = params.get("top_k", 5)
52
+
53
+ # Find the root directory of the codebase
54
+ current_dir = os.getcwd()
55
+ root_dir = find_git_root(dir or current_dir)
56
+ if not root_dir:
57
+ return {
58
+ "success": False,
59
+ "stdout": "",
60
+ "stderr": "Error: Current directory is not in a Git repository",
61
+ "error": "NotInGitRepository"
62
+ }
63
+
64
+ os.chdir(root_dir)
65
+ codebase = CodeBase(root_dir)
66
+ # Generate index
67
+
68
+ codebase.generate_codebase()
69
+ # Execute search
70
+ response = codebase.search_similar(query, top_k)
71
+ os.chdir(current_dir)
72
+ return {
73
+ "success": True,
74
+ "stdout": str(response),
75
+ "stderr": "",
76
+ "error": None
77
+ }
78
+
79
+ except Exception as e:
80
+ PrettyOutput.print(f"Codebase QA error: {str(e)}", output_type=OutputType.ERROR)
81
+ return {
82
+ "success": False,
83
+ "stdout": "",
84
+ "stderr": f"Error executing codebase QA: {str(e)}",
85
+ "error": str(type(e).__name__)
86
+ }