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.
- jarvis/__init__.py +1 -1
- jarvis/agent.py +138 -117
- jarvis/jarvis_code_agent/code_agent.py +234 -0
- jarvis/{jarvis_coder → jarvis_code_agent}/file_select.py +16 -17
- jarvis/jarvis_code_agent/patch.py +118 -0
- jarvis/jarvis_code_agent/relevant_files.py +66 -0
- jarvis/jarvis_codebase/main.py +878 -0
- jarvis/jarvis_platform/main.py +5 -3
- jarvis/jarvis_rag/main.py +818 -0
- jarvis/jarvis_smart_shell/main.py +2 -2
- jarvis/models/ai8.py +3 -1
- jarvis/models/kimi.py +36 -30
- jarvis/models/ollama.py +17 -11
- jarvis/models/openai.py +15 -12
- jarvis/models/oyi.py +24 -7
- jarvis/models/registry.py +1 -25
- jarvis/tools/__init__.py +0 -6
- jarvis/tools/ask_codebase.py +99 -0
- jarvis/tools/ask_user.py +1 -9
- jarvis/tools/chdir.py +1 -1
- jarvis/tools/code_review.py +163 -0
- jarvis/tools/create_code_sub_agent.py +19 -45
- jarvis/tools/create_code_test_agent.py +115 -0
- jarvis/tools/create_ctags_agent.py +176 -0
- jarvis/tools/create_sub_agent.py +2 -2
- jarvis/tools/execute_shell.py +2 -2
- jarvis/tools/file_operation.py +2 -2
- jarvis/tools/find_in_codebase.py +108 -0
- jarvis/tools/git_commiter.py +68 -0
- jarvis/tools/methodology.py +3 -3
- jarvis/tools/rag.py +141 -0
- jarvis/tools/read_code.py +147 -0
- jarvis/tools/read_webpage.py +1 -1
- jarvis/tools/registry.py +47 -31
- jarvis/tools/search.py +8 -6
- jarvis/tools/select_code_files.py +4 -4
- jarvis/utils.py +374 -84
- {jarvis_ai_assistant-0.1.102.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/METADATA +52 -2
- jarvis_ai_assistant-0.1.103.dist-info/RECORD +51 -0
- jarvis_ai_assistant-0.1.103.dist-info/entry_points.txt +11 -0
- jarvis/jarvis_code_agent/main.py +0 -200
- jarvis/jarvis_coder/git_utils.py +0 -123
- jarvis/jarvis_coder/patch_handler.py +0 -340
- jarvis/jarvis_github/main.py +0 -232
- jarvis/tools/execute_code_modification.py +0 -70
- jarvis/tools/find_files.py +0 -119
- jarvis/tools/generate_tool.py +0 -174
- jarvis/tools/thinker.py +0 -151
- jarvis_ai_assistant-0.1.102.dist-info/RECORD +0 -46
- jarvis_ai_assistant-0.1.102.dist-info/entry_points.txt +0 -6
- /jarvis/{jarvis_coder → jarvis_codebase}/__init__.py +0 -0
- /jarvis/{jarvis_github → jarvis_rag}/__init__.py +0 -0
- {jarvis_ai_assistant-0.1.102.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.102.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/WHEEL +0 -0
- {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
|
-
#
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
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("
|
|
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("
|
|
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 []
|