jarvis-ai-assistant 0.1.102__py3-none-any.whl → 0.1.103__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 (55) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/agent.py +138 -117
  3. jarvis/jarvis_code_agent/code_agent.py +234 -0
  4. jarvis/{jarvis_coder → jarvis_code_agent}/file_select.py +16 -17
  5. jarvis/jarvis_code_agent/patch.py +118 -0
  6. jarvis/jarvis_code_agent/relevant_files.py +66 -0
  7. jarvis/jarvis_codebase/main.py +878 -0
  8. jarvis/jarvis_platform/main.py +5 -3
  9. jarvis/jarvis_rag/main.py +818 -0
  10. jarvis/jarvis_smart_shell/main.py +2 -2
  11. jarvis/models/ai8.py +3 -1
  12. jarvis/models/kimi.py +36 -30
  13. jarvis/models/ollama.py +17 -11
  14. jarvis/models/openai.py +15 -12
  15. jarvis/models/oyi.py +24 -7
  16. jarvis/models/registry.py +1 -25
  17. jarvis/tools/__init__.py +0 -6
  18. jarvis/tools/ask_codebase.py +99 -0
  19. jarvis/tools/ask_user.py +1 -9
  20. jarvis/tools/chdir.py +1 -1
  21. jarvis/tools/code_review.py +163 -0
  22. jarvis/tools/create_code_sub_agent.py +19 -45
  23. jarvis/tools/create_code_test_agent.py +115 -0
  24. jarvis/tools/create_ctags_agent.py +176 -0
  25. jarvis/tools/create_sub_agent.py +2 -2
  26. jarvis/tools/execute_shell.py +2 -2
  27. jarvis/tools/file_operation.py +2 -2
  28. jarvis/tools/find_in_codebase.py +108 -0
  29. jarvis/tools/git_commiter.py +68 -0
  30. jarvis/tools/methodology.py +3 -3
  31. jarvis/tools/rag.py +141 -0
  32. jarvis/tools/read_code.py +147 -0
  33. jarvis/tools/read_webpage.py +1 -1
  34. jarvis/tools/registry.py +47 -31
  35. jarvis/tools/search.py +8 -6
  36. jarvis/tools/select_code_files.py +4 -4
  37. jarvis/utils.py +374 -84
  38. {jarvis_ai_assistant-0.1.102.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/METADATA +52 -2
  39. jarvis_ai_assistant-0.1.103.dist-info/RECORD +51 -0
  40. jarvis_ai_assistant-0.1.103.dist-info/entry_points.txt +11 -0
  41. jarvis/jarvis_code_agent/main.py +0 -200
  42. jarvis/jarvis_coder/git_utils.py +0 -123
  43. jarvis/jarvis_coder/patch_handler.py +0 -340
  44. jarvis/jarvis_github/main.py +0 -232
  45. jarvis/tools/execute_code_modification.py +0 -70
  46. jarvis/tools/find_files.py +0 -119
  47. jarvis/tools/generate_tool.py +0 -174
  48. jarvis/tools/thinker.py +0 -151
  49. jarvis_ai_assistant-0.1.102.dist-info/RECORD +0 -46
  50. jarvis_ai_assistant-0.1.102.dist-info/entry_points.txt +0 -6
  51. /jarvis/{jarvis_coder → jarvis_codebase}/__init__.py +0 -0
  52. /jarvis/{jarvis_github → jarvis_rag}/__init__.py +0 -0
  53. {jarvis_ai_assistant-0.1.102.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/LICENSE +0 -0
  54. {jarvis_ai_assistant-0.1.102.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/WHEEL +0 -0
  55. {jarvis_ai_assistant-0.1.102.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,3 @@
1
-
2
1
  import os
3
2
  import re
4
3
  from typing import Dict, List
@@ -64,25 +63,23 @@ def _get_file_completer(root_dir: str) -> Completer:
64
63
  self.root_dir = root_dir
65
64
 
66
65
  def get_completions(self, document, complete_event):
67
- # Get the text of the current input
68
66
  text = document.text_before_cursor
69
67
 
70
- # If the input is empty, return all files in the root directory
71
68
  if not text:
72
69
  for path in self._list_files(""):
73
70
  yield Completion(path, start_position=0)
74
71
  return
75
72
 
76
- # Get the current directory and partial file name
77
- current_dir = os.path.dirname(text)
78
- file_prefix = os.path.basename(text)
79
-
80
- # List matching files
81
- search_dir = os.path.join(self.root_dir, current_dir) if current_dir else self.root_dir
82
- if os.path.isdir(search_dir):
83
- for path in self._list_files(current_dir):
84
- if path.startswith(text):
85
- yield Completion(path, start_position=-len(text))
73
+ # Generate fuzzy matching pattern
74
+ pattern = '.*'.join(map(re.escape, text))
75
+ try:
76
+ regex = re.compile(pattern, re.IGNORECASE)
77
+ except re.error:
78
+ return
79
+
80
+ for path in self._list_files(""):
81
+ if regex.search(path):
82
+ yield Completion(path, start_position=-len(text))
86
83
 
87
84
  def _list_files(self, current_dir: str) -> List[str]:
88
85
  """List all files in the specified directory (recursively)"""
@@ -93,7 +90,6 @@ def _get_file_completer(root_dir: str) -> Completer:
93
90
  for filename in filenames:
94
91
  full_path = os.path.join(root, filename)
95
92
  rel_path = os.path.relpath(full_path, self.root_dir)
96
- # Ignore .git directory and other hidden files
97
93
  if not any(part.startswith('.') for part in rel_path.split(os.sep)):
98
94
  files.append(rel_path)
99
95
 
@@ -133,10 +129,13 @@ def select_files(related_files: List[str], root_dir: str) -> List[str]:
133
129
  """Let the user select and supplement related files"""
134
130
  PrettyOutput.section("Related files", OutputType.INFO)
135
131
 
132
+ output = ""
136
133
  # Display found files
137
134
  selected_files = list(related_files) # Default select all
138
135
  for i, file in enumerate(related_files, 1):
139
- PrettyOutput.print(f"[{i}] {file}", OutputType.INFO)
136
+ output += f"[{i}] {file}\n"
137
+
138
+ PrettyOutput.print(output, OutputType.INFO)
140
139
 
141
140
  # Ask the user if they need to adjust the file list
142
141
  user_input = get_single_line_input("Do you need to adjust the file list? (y/n) [n]").strip().lower() or 'n'
@@ -160,7 +159,7 @@ def select_files(related_files: List[str], root_dir: str) -> List[str]:
160
159
  )
161
160
 
162
161
  while True:
163
- PrettyOutput.print("\nPlease enter the file path to supplement (support Tab completion and *? wildcard, input empty line to end):", OutputType.INFO)
162
+ PrettyOutput.print("Please enter the file path to supplement (support Tab completion and *? wildcard, input empty line to end):", OutputType.INFO)
164
163
  try:
165
164
  file_path = session.prompt(">>> ").strip()
166
165
  except KeyboardInterrupt:
@@ -177,7 +176,7 @@ def select_files(related_files: List[str], root_dir: str) -> List[str]:
177
176
  continue
178
177
 
179
178
  # Display matching files
180
- PrettyOutput.print("\nFound the following matching files:", OutputType.INFO)
179
+ PrettyOutput.print("Found the following matching files:", OutputType.INFO)
181
180
  for i, path in enumerate(matches, 1):
182
181
  PrettyOutput.print(f"[{i}] {path}", OutputType.INFO)
183
182
 
@@ -0,0 +1,118 @@
1
+ import re
2
+ from typing import Dict, Any, List, Tuple
3
+ import os
4
+ from jarvis.tools.git_commiter import GitCommitTool
5
+ from jarvis.tools.read_code import ReadCodeTool
6
+ from jarvis.utils import OutputType, PrettyOutput, has_uncommitted_changes, make_choice_input, user_confirm
7
+
8
+
9
+ def _parse_patch(patch_str: str) -> Dict[str, List[Dict[str, Any]]]:
10
+ """Parse patches from string with format:
11
+ <PATCH>
12
+ > /path/to/file start_line,end_line
13
+ content_line1
14
+ content_line2
15
+ ...
16
+ </PATCH>
17
+ """
18
+ result = {}
19
+ patches = re.findall(r"<PATCH>(.*?)</PATCH>", patch_str, re.DOTALL)
20
+
21
+ for patch in patches:
22
+ lines = patch.strip().split('\n')
23
+ if not lines:
24
+ continue
25
+
26
+ # Parse file path and line range
27
+ file_info = lines[0].strip()
28
+ if not file_info.startswith('>'):
29
+ continue
30
+
31
+ # Extract file path and line range
32
+ match = re.match(r'>\s*([^\s]+)\s+(\d+),(\d+)', file_info)
33
+ if not match:
34
+ continue
35
+
36
+ filepath = match.group(1).strip()
37
+ start_line = int(match.group(2))
38
+ end_line = int(match.group(3))
39
+
40
+ # Get content lines (skip the first line with file info)
41
+ content = '\n'.join(lines[1:])
42
+
43
+ if filepath not in result:
44
+ result[filepath] = []
45
+
46
+ # Store in result dictionary
47
+ result[filepath].append({
48
+ 'start_line': start_line,
49
+ 'end_line': end_line,
50
+ 'content': content
51
+ })
52
+
53
+ # Sort patches by start line in reverse order to apply from bottom to top
54
+ for filepath in result:
55
+ result[filepath].sort(key=lambda x: x['start_line'], reverse=True)
56
+
57
+ return result
58
+
59
+
60
+ def apply_patch(output_str: str)->str:
61
+ """Apply patches to files"""
62
+ patches = _parse_patch(output_str)
63
+ if not patches:
64
+ return ""
65
+
66
+ for filepath, patch_info in patches.items():
67
+ try:
68
+ # Check if file exists
69
+ if not os.path.exists(filepath):
70
+ PrettyOutput.print(f"File not found: {filepath}", OutputType.WARNING)
71
+ continue
72
+
73
+ # Read original file content
74
+ lines = open(filepath, 'r', encoding='utf-8').readlines()
75
+
76
+ # Apply patch
77
+ for patch in patch_info:
78
+ start_line = patch['start_line']
79
+ end_line = patch['end_line']
80
+ new_content = patch['content'].splitlines(keepends=True)
81
+
82
+ if new_content and new_content[-1] and new_content[-1][-1] != '\n':
83
+ new_content[-1] += '\n'
84
+ # Validate line numbers
85
+ if start_line < 0 or end_line > len(lines) + 1 or start_line > end_line:
86
+ PrettyOutput.print(f"Invalid line range [{start_line}, {end_line}) for file: {filepath}", OutputType.WARNING)
87
+ continue
88
+
89
+ # Create new content
90
+ lines[start_line:end_line] = new_content
91
+
92
+ # Write back to file
93
+ open(filepath, 'w', encoding='utf-8').writelines(lines)
94
+
95
+ PrettyOutput.print(f"Applied patch to {filepath} successfully\n", OutputType.SUCCESS)
96
+
97
+ except Exception as e:
98
+ PrettyOutput.print(f"Error applying patch to {filepath}: {str(e)}", OutputType.ERROR)
99
+ continue
100
+
101
+ if has_uncommitted_changes():
102
+ handle_commit_workflow()
103
+ return ""
104
+
105
+ def handle_commit_workflow()->bool:
106
+ """Handle the git commit workflow and return the commit details.
107
+
108
+ Returns:
109
+ tuple[bool, str, str]: (continue_execution, commit_id, commit_message)
110
+ """
111
+ if not user_confirm("Do you want to commit the code?", default=True):
112
+ os.system("git reset HEAD")
113
+ os.system("git checkout -- .")
114
+ return False
115
+
116
+ git_commiter = GitCommitTool()
117
+ commit_result = git_commiter.execute({})
118
+ return commit_result["success"]
@@ -0,0 +1,66 @@
1
+
2
+
3
+
4
+ import os
5
+ import re
6
+ from typing import List
7
+
8
+ import yaml
9
+ from jarvis.agent import Agent
10
+ from jarvis.jarvis_code_agent.file_select import select_files
11
+ from jarvis.jarvis_codebase.main import CodeBase
12
+ from jarvis.models.registry import PlatformRegistry
13
+ from jarvis.tools.registry import ToolRegistry
14
+ from jarvis.utils import OutputType, PrettyOutput, is_disable_codebase
15
+
16
+
17
+ def find_relevant_files(user_input: str, root_dir: str) -> List[str]:
18
+ try:
19
+ files_from_codebase = []
20
+ if not is_disable_codebase():
21
+ PrettyOutput.print("Find files from codebase...", OutputType.INFO)
22
+ codebase = CodeBase(root_dir)
23
+ files_from_codebase = codebase.search_similar(user_input)
24
+
25
+ PrettyOutput.print("Find files by agent...", OutputType.INFO)
26
+ find_file_tool_registry = ToolRegistry()
27
+ find_file_tool_registry.use_tools(["read_code", "execute_shell"])
28
+ find_file_agent = Agent(
29
+ system_prompt="""You are a file agent, you are responsible for finding files related to the user's requirement.
30
+ You can use `read_code` tool to read the code and analyze the code, and `execute_shell` tool to execute shell command(such as `grep/find/ls/git/ctags`) to find files.
31
+
32
+ IMPORTANT:
33
+ - Only provide the file path, do not provide any other information.
34
+ - If you can't find the file, please provide empty list.
35
+ - Don't modify the code, just find related files.
36
+ """,
37
+ name="FindFileAgent",
38
+ is_sub_agent=True,
39
+ tool_registry=find_file_tool_registry,
40
+ platform=PlatformRegistry().get_normal_platform(),
41
+ auto_complete=True,
42
+ summary_prompt="""Please provide the file path as this format(yaml list), if you can't find the file, please provide empty list:
43
+ <FILE_PATH>
44
+ - file_path1
45
+ - file_path2
46
+ </FILE_PATH>
47
+ """)
48
+ prompt = f"Find files related about '{user_input}'\n"
49
+ if files_from_codebase:
50
+ prompt += f"\n\nFiles maybe related: {files_from_codebase}\n\n Please read above files first"
51
+ output = find_file_agent.run(prompt)
52
+
53
+ rsp_from_agent = re.findall(r'<FILE_PATH>(.*?)</FILE_PATH>', output, re.DOTALL)
54
+ files_from_agent = []
55
+ if rsp_from_agent:
56
+ try:
57
+ files_from_agent = yaml.safe_load(rsp_from_agent[0])
58
+ except Exception as e:
59
+ files_from_agent = []
60
+ else:
61
+ files_from_agent = []
62
+
63
+ selected_files = select_files(files_from_agent, os.getcwd())
64
+ return selected_files
65
+ except Exception as e:
66
+ return []