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.
- 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 +19 -22
- jarvis/jarvis_code_agent/patch.py +120 -0
- jarvis/jarvis_code_agent/relevant_files.py +97 -0
- jarvis/jarvis_codebase/main.py +871 -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 +96 -0
- jarvis/tools/ask_user.py +1 -9
- jarvis/tools/chdir.py +2 -37
- jarvis/tools/code_review.py +210 -0
- jarvis/tools/create_code_test_agent.py +115 -0
- jarvis/tools/create_ctags_agent.py +164 -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 +78 -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 +116 -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 +375 -85
- {jarvis_ai_assistant-0.1.102.dist-info → jarvis_ai_assistant-0.1.104.dist-info}/METADATA +107 -32
- jarvis_ai_assistant-0.1.104.dist-info/RECORD +50 -0
- jarvis_ai_assistant-0.1.104.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/create_code_sub_agent.py +0 -56
- 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.104.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.102.dist-info → jarvis_ai_assistant-0.1.104.dist-info}/WHEEL +0 -0
- {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
|
-
#
|
|
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,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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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("
|
|
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("
|
|
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 []
|