jarvis-ai-assistant 0.1.102__py3-none-any.whl → 0.1.104__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 +19 -22
  5. jarvis/jarvis_code_agent/patch.py +120 -0
  6. jarvis/jarvis_code_agent/relevant_files.py +97 -0
  7. jarvis/jarvis_codebase/main.py +871 -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 +96 -0
  19. jarvis/tools/ask_user.py +1 -9
  20. jarvis/tools/chdir.py +2 -37
  21. jarvis/tools/code_review.py +210 -0
  22. jarvis/tools/create_code_test_agent.py +115 -0
  23. jarvis/tools/create_ctags_agent.py +164 -0
  24. jarvis/tools/create_sub_agent.py +2 -2
  25. jarvis/tools/execute_shell.py +2 -2
  26. jarvis/tools/file_operation.py +2 -2
  27. jarvis/tools/find_in_codebase.py +78 -0
  28. jarvis/tools/git_commiter.py +68 -0
  29. jarvis/tools/methodology.py +3 -3
  30. jarvis/tools/rag.py +141 -0
  31. jarvis/tools/read_code.py +116 -0
  32. jarvis/tools/read_webpage.py +1 -1
  33. jarvis/tools/registry.py +47 -31
  34. jarvis/tools/search.py +8 -6
  35. jarvis/tools/select_code_files.py +4 -4
  36. jarvis/utils.py +375 -85
  37. {jarvis_ai_assistant-0.1.102.dist-info → jarvis_ai_assistant-0.1.104.dist-info}/METADATA +107 -32
  38. jarvis_ai_assistant-0.1.104.dist-info/RECORD +50 -0
  39. jarvis_ai_assistant-0.1.104.dist-info/entry_points.txt +11 -0
  40. jarvis/jarvis_code_agent/main.py +0 -200
  41. jarvis/jarvis_coder/git_utils.py +0 -123
  42. jarvis/jarvis_coder/patch_handler.py +0 -340
  43. jarvis/jarvis_github/main.py +0 -232
  44. jarvis/tools/create_code_sub_agent.py +0 -56
  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.104.dist-info}/LICENSE +0 -0
  54. {jarvis_ai_assistant-0.1.102.dist-info → jarvis_ai_assistant-0.1.104.dist-info}/WHEEL +0 -0
  55. {jarvis_ai_assistant-0.1.102.dist-info → jarvis_ai_assistant-0.1.104.dist-info}/top_level.txt +0 -0
@@ -1,10 +1,9 @@
1
-
2
1
  import os
3
2
  import re
4
3
  from typing import Dict, List
5
4
  from prompt_toolkit import PromptSession
6
5
  from prompt_toolkit.completion import WordCompleter, Completer, Completion
7
- from jarvis.utils import OutputType, PrettyOutput, get_single_line_input
6
+ from jarvis.utils import OutputType, PrettyOutput, get_single_line_input, user_confirm
8
7
 
9
8
 
10
9
  def _parse_file_selection(input_str: str, max_index: int) -> List[int]:
@@ -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,14 +129,16 @@ 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, lang="markdown")
140
139
 
141
140
  # Ask the user if they need to adjust the file list
142
- user_input = get_single_line_input("Do you need to adjust the file list? (y/n) [n]").strip().lower() or 'n'
143
- if user_input == 'y':
141
+ if user_confirm("Do you need to adjust the file list?", False):
144
142
  # Let the user select files
145
143
  numbers = get_single_line_input("Please enter the file numbers to include (support: 1,3-6 format, press Enter to keep the current selection)").strip()
146
144
  if numbers:
@@ -151,8 +149,7 @@ def select_files(related_files: List[str], root_dir: str) -> List[str]:
151
149
  PrettyOutput.print("No valid files selected, keep the current selection", OutputType.WARNING)
152
150
 
153
151
  # Ask if they need to supplement files
154
- user_input = get_single_line_input("Do you need to supplement other files? (y/n) [n]").strip().lower() or 'n'
155
- if user_input == 'y':
152
+ if user_confirm("Do you need to supplement other files?", False):
156
153
  # Create file completion session
157
154
  session = PromptSession(
158
155
  completer=_get_file_completer(root_dir),
@@ -160,7 +157,7 @@ def select_files(related_files: List[str], root_dir: str) -> List[str]:
160
157
  )
161
158
 
162
159
  while True:
163
- PrettyOutput.print("\nPlease enter the file path to supplement (support Tab completion and *? wildcard, input empty line to end):", OutputType.INFO)
160
+ PrettyOutput.print("Please enter the file path to supplement (support Tab completion and *? wildcard, input empty line to end):", OutputType.INFO)
164
161
  try:
165
162
  file_path = session.prompt(">>> ").strip()
166
163
  except KeyboardInterrupt:
@@ -177,7 +174,7 @@ def select_files(related_files: List[str], root_dir: str) -> List[str]:
177
174
  continue
178
175
 
179
176
  # Display matching files
180
- PrettyOutput.print("\nFound the following matching files:", OutputType.INFO)
177
+ PrettyOutput.print("Found the following matching files:", OutputType.INFO)
181
178
  for i, path in enumerate(matches, 1):
182
179
  PrettyOutput.print(f"[{i}] {path}", OutputType.INFO)
183
180
 
@@ -0,0 +1,120 @@
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
+ diff = os.popen("git diff HEAD").read()
112
+ PrettyOutput.print(diff, OutputType.CODE, lang="diff")
113
+ if not user_confirm("Do you want to commit the code?", default=True):
114
+ os.system("git reset HEAD")
115
+ os.system("git checkout -- .")
116
+ return False
117
+
118
+ git_commiter = GitCommitTool()
119
+ commit_result = git_commiter.execute({})
120
+ return commit_result["success"]
@@ -0,0 +1,97 @@
1
+ import os
2
+ import re
3
+ from typing import List
4
+
5
+ import yaml
6
+ from jarvis.agent import Agent
7
+ from jarvis.jarvis_code_agent.file_select import select_files
8
+ from jarvis.jarvis_codebase.main import CodeBase
9
+ from jarvis.models.registry import PlatformRegistry
10
+ from jarvis.tools.registry import ToolRegistry
11
+ from jarvis.utils import OutputType, PrettyOutput, is_disable_codebase
12
+
13
+
14
+ def find_relevant_files(user_input: str, root_dir: str) -> List[str]:
15
+ try:
16
+ files_from_codebase = []
17
+ if not is_disable_codebase():
18
+ PrettyOutput.print("Find files from codebase...", OutputType.INFO)
19
+ codebase = CodeBase(root_dir)
20
+ files_from_codebase = codebase.search_similar(user_input)
21
+
22
+ PrettyOutput.print("Find files by agent...", OutputType.INFO)
23
+ find_file_tool_registry = ToolRegistry()
24
+ find_file_tool_registry.use_tools(["read_code", "execute_shell"])
25
+ find_file_agent = Agent(
26
+ system_prompt="""You are a file agent, you are responsible for finding files related to the user's requirement.
27
+
28
+ SEARCH STRATEGY:
29
+ 1. First Pass - Quick Search:
30
+ - Use `execute_shell` with git grep/find to locate potential files
31
+ - Search for key terms, function names, and relevant patterns
32
+ - Example: execute_shell("git grep -l 'search_term'")
33
+
34
+ 2. Content Analysis:
35
+ - For each potential file, analyze its content
36
+ - Follow the file reading guidelines for large files
37
+ - Look for:
38
+ * Direct matches to requirement terms
39
+ * Related functionality
40
+ * Imported/referenced files
41
+ * Test files for modified code
42
+
43
+ FILE READING GUIDELINES:
44
+ 1. For Large Files (>200 lines):
45
+ - Do NOT read the entire file at once
46
+ - First use grep/ctags to locate relevant sections
47
+ - Then read specific sections with context
48
+ - Example:
49
+ * execute_shell("grep -n 'function_name' path/to/file")
50
+ * read_code("path/to/file", start_line=found_line-10, end_line=found_line+20)
51
+
52
+ 2. For Small Files:
53
+ - Can read entire file directly
54
+
55
+ IMPORTANT RULES:
56
+ - Only return files that are DIRECTLY related to the requirement
57
+ - Exclude false positives and loosely related files
58
+ - If a file only contains imports/references, don't include it
59
+ - Include both implementation and test files when relevant
60
+ - If unsure about a file, use grep/read_code to verify relevance
61
+ - Return empty list if no truly relevant files are found
62
+ - Do NOT modify any code, only find files
63
+
64
+ OUTPUT FORMAT:
65
+ - Only provide file paths in the specified YAML format
66
+ - No additional explanations or comments
67
+ """,
68
+ name="FindFileAgent",
69
+ is_sub_agent=True,
70
+ tool_registry=find_file_tool_registry,
71
+ platform=PlatformRegistry().get_normal_platform(),
72
+ auto_complete=True,
73
+ summary_prompt="""Please provide the file paths as YAML list:
74
+ <FILE_PATH>
75
+ - file_path1
76
+ - file_path2
77
+ </FILE_PATH>
78
+ """)
79
+ prompt = f"Find files related about '{user_input}'\n"
80
+ if files_from_codebase:
81
+ prompt += f"\n\nFiles maybe related: {files_from_codebase}\n\n Please read above files first"
82
+ output = find_file_agent.run(prompt)
83
+
84
+ rsp_from_agent = re.findall(r'<FILE_PATH>(.*?)</FILE_PATH>', output, re.DOTALL)
85
+ files_from_agent = []
86
+ if rsp_from_agent:
87
+ try:
88
+ files_from_agent = yaml.safe_load(rsp_from_agent[0])
89
+ except Exception as e:
90
+ files_from_agent = []
91
+ else:
92
+ files_from_agent = []
93
+
94
+ selected_files = select_files(files_from_agent, os.getcwd())
95
+ return selected_files
96
+ except Exception as e:
97
+ return []