jarvis-ai-assistant 0.1.101__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 +140 -140
- 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 +32 -29
- jarvis/jarvis_platform/main.py +5 -3
- jarvis/jarvis_rag/main.py +11 -15
- jarvis/jarvis_smart_shell/main.py +2 -2
- jarvis/models/ai8.py +1 -0
- jarvis/models/kimi.py +36 -30
- jarvis/models/ollama.py +17 -11
- jarvis/models/openai.py +15 -12
- jarvis/models/oyi.py +22 -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 +6 -3
- jarvis/tools/read_code.py +147 -0
- jarvis/tools/read_webpage.py +1 -1
- jarvis/tools/registry.py +92 -68
- jarvis/tools/search.py +8 -6
- jarvis/tools/select_code_files.py +4 -4
- jarvis/utils.py +270 -95
- {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/METADATA +9 -5
- jarvis_ai_assistant-0.1.103.dist-info/RECORD +51 -0
- {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/entry_points.txt +4 -2
- jarvis/jarvis_code_agent/main.py +0 -202
- jarvis/jarvis_coder/__init__.py +0 -0
- jarvis/jarvis_coder/git_utils.py +0 -123
- jarvis/jarvis_coder/main.py +0 -241
- jarvis/jarvis_coder/patch_handler.py +0 -340
- jarvis/jarvis_coder/plan_generator.py +0 -145
- 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.101.dist-info/RECORD +0 -51
- {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/top_level.txt +0 -0
|
@@ -1,340 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
import os
|
|
3
|
-
from typing import List, Tuple, Dict
|
|
4
|
-
import yaml
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
from jarvis.jarvis_coder.git_utils import generate_commit_message, init_git_repo, save_edit_record
|
|
8
|
-
from jarvis.models.base import BasePlatform
|
|
9
|
-
from jarvis.models.registry import PlatformRegistry
|
|
10
|
-
from jarvis.utils import OutputType, PrettyOutput, get_multiline_input, get_single_line_input, while_success
|
|
11
|
-
|
|
12
|
-
class Patch:
|
|
13
|
-
def __init__(self, start: int, end: int, new_code: str):
|
|
14
|
-
self.start = start # Line number where patch starts (inclusive)
|
|
15
|
-
self.end = end # Line number where patch ends (exclusive)
|
|
16
|
-
self.new_code = new_code # New code to insert/replace
|
|
17
|
-
|
|
18
|
-
class PatchHandler:
|
|
19
|
-
def __init__(self):
|
|
20
|
-
self.prompt_file = Path.home() / ".jarvis-coder-patch-prompt"
|
|
21
|
-
self.additional_info = self._load_additional_info()
|
|
22
|
-
self.root_dir = init_git_repo(os.getcwd())
|
|
23
|
-
self.record_dir = os.path.join(self.root_dir, ".jarvis-coder", "record")
|
|
24
|
-
if not os.path.exists(self.record_dir):
|
|
25
|
-
os.makedirs(self.record_dir)
|
|
26
|
-
def _load_additional_info(self) -> str:
|
|
27
|
-
"""Load saved additional info from prompt file"""
|
|
28
|
-
if not self.prompt_file.exists():
|
|
29
|
-
return ""
|
|
30
|
-
try:
|
|
31
|
-
with open(self.prompt_file, 'r') as f:
|
|
32
|
-
data = yaml.safe_load(f)
|
|
33
|
-
return data.get('additional_info', '') if data else ''
|
|
34
|
-
except Exception as e:
|
|
35
|
-
PrettyOutput.print(f"Failed to load additional info: {e}", OutputType.WARNING)
|
|
36
|
-
return ""
|
|
37
|
-
|
|
38
|
-
def _save_additional_info(self, info: str):
|
|
39
|
-
"""Save additional info to prompt file"""
|
|
40
|
-
try:
|
|
41
|
-
with open(self.prompt_file, 'w') as f:
|
|
42
|
-
yaml.dump({'additional_info': info}, f)
|
|
43
|
-
except Exception as e:
|
|
44
|
-
PrettyOutput.print(f"Failed to save additional info: {e}", OutputType.WARNING)
|
|
45
|
-
|
|
46
|
-
def _extract_patches(self, response: str) -> List[Patch]:
|
|
47
|
-
"""Extract patches from response with hexadecimal line numbers
|
|
48
|
-
|
|
49
|
-
Args:
|
|
50
|
-
response: Model response content
|
|
51
|
-
|
|
52
|
-
Returns:
|
|
53
|
-
List[Patch]: List of patches, each containing the line range and new code
|
|
54
|
-
"""
|
|
55
|
-
fmt_pattern = r'<PATCH>\n\[([0-9a-f]+),([0-9a-f]+)\)\n(.*?\n)</PATCH>'
|
|
56
|
-
ret = []
|
|
57
|
-
for m in re.finditer(fmt_pattern, response, re.DOTALL):
|
|
58
|
-
start = int(m.group(1), 16) # Convert hex to decimal
|
|
59
|
-
end = int(m.group(2), 16)
|
|
60
|
-
new_code = m.group(3)
|
|
61
|
-
ret.append(Patch(start, end, new_code))
|
|
62
|
-
return ret
|
|
63
|
-
|
|
64
|
-
def _confirm_and_apply_changes(self, file_path: str) -> bool:
|
|
65
|
-
"""Confirm and apply changes"""
|
|
66
|
-
os.system(f"git diff --cached {file_path}")
|
|
67
|
-
confirm = get_single_line_input(f"Accept {file_path} changes? (y/n) [y]").lower() or "y"
|
|
68
|
-
if confirm == "y":
|
|
69
|
-
return True
|
|
70
|
-
else:
|
|
71
|
-
# Rollback changes
|
|
72
|
-
os.system(f"git reset {file_path}")
|
|
73
|
-
os.system(f"git checkout -- {file_path}")
|
|
74
|
-
PrettyOutput.print(f"Changes to {file_path} have been rolled back", OutputType.WARNING)
|
|
75
|
-
return False
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def _finalize_changes(self) -> None:
|
|
80
|
-
"""Complete changes and commit"""
|
|
81
|
-
PrettyOutput.print("Modification confirmed, committing...", OutputType.INFO)
|
|
82
|
-
|
|
83
|
-
# Add only modified files under git control
|
|
84
|
-
os.system("git add -u")
|
|
85
|
-
|
|
86
|
-
# Then get git diff
|
|
87
|
-
git_diff = os.popen("git diff --cached").read()
|
|
88
|
-
|
|
89
|
-
# Automatically generate commit information, pass in feature
|
|
90
|
-
commit_message = generate_commit_message(git_diff)
|
|
91
|
-
|
|
92
|
-
# Display and confirm commit information
|
|
93
|
-
PrettyOutput.print(f"Automatically generated commit information: {commit_message}", OutputType.INFO)
|
|
94
|
-
user_confirm = get_single_line_input("Use this commit information? (y/n) [y]").lower() or "y"
|
|
95
|
-
|
|
96
|
-
if user_confirm.lower() != "y":
|
|
97
|
-
commit_message = get_single_line_input("Please enter a new commit information")
|
|
98
|
-
|
|
99
|
-
# No need to git add again, it has already been added
|
|
100
|
-
os.system(f"git commit -m '{commit_message}'")
|
|
101
|
-
save_edit_record(self.record_dir, commit_message, git_diff)
|
|
102
|
-
|
|
103
|
-
def _revert_changes(self) -> None:
|
|
104
|
-
"""Revert all changes"""
|
|
105
|
-
PrettyOutput.print("Modification cancelled, reverting changes", OutputType.INFO)
|
|
106
|
-
os.system(f"git reset --hard")
|
|
107
|
-
os.system(f"git clean -df")
|
|
108
|
-
|
|
109
|
-
def _check_patches_overlap(self, patches: List[Patch]) -> bool:
|
|
110
|
-
"""Check if any patches overlap with each other
|
|
111
|
-
|
|
112
|
-
Args:
|
|
113
|
-
patches: List of patches to check
|
|
114
|
-
|
|
115
|
-
Returns:
|
|
116
|
-
bool: True if patches overlap, False otherwise
|
|
117
|
-
"""
|
|
118
|
-
if not patches:
|
|
119
|
-
return False
|
|
120
|
-
|
|
121
|
-
# Sort patches by start line
|
|
122
|
-
sorted_patches = sorted(patches, key=lambda x: x.start)
|
|
123
|
-
|
|
124
|
-
# Check for overlaps
|
|
125
|
-
for i in range(len(sorted_patches) - 1):
|
|
126
|
-
current = sorted_patches[i]
|
|
127
|
-
next_patch = sorted_patches[i + 1]
|
|
128
|
-
|
|
129
|
-
if current.end > next_patch.start:
|
|
130
|
-
PrettyOutput.print(
|
|
131
|
-
f"Overlapping patches detected: [{current.start:04x},{current.end:04x}) and [{next_patch.start:04x},{next_patch.end:04x})",
|
|
132
|
-
OutputType.WARNING
|
|
133
|
-
)
|
|
134
|
-
return True
|
|
135
|
-
|
|
136
|
-
return False
|
|
137
|
-
|
|
138
|
-
def apply_file_patch(self, file_path: str, patches: List[Patch]) -> bool:
|
|
139
|
-
"""Apply file patches using line numbers"""
|
|
140
|
-
if not os.path.exists(file_path):
|
|
141
|
-
base_dir = os.path.dirname(file_path)
|
|
142
|
-
os.makedirs(base_dir, exist_ok=True)
|
|
143
|
-
open(file_path, "w", encoding="utf-8").close()
|
|
144
|
-
|
|
145
|
-
# Check for overlapping patches
|
|
146
|
-
if self._check_patches_overlap(patches):
|
|
147
|
-
PrettyOutput.print("Cannot apply overlapping patches", OutputType.ERROR)
|
|
148
|
-
os.system(f"git reset {file_path}")
|
|
149
|
-
os.system(f"git checkout -- {file_path}")
|
|
150
|
-
return False
|
|
151
|
-
|
|
152
|
-
with open(file_path, "r", encoding="utf-8") as f:
|
|
153
|
-
lines = f.readlines()
|
|
154
|
-
|
|
155
|
-
# Sort patches by start line in reverse order to apply from bottom to top
|
|
156
|
-
patches.sort(key=lambda x: x.start, reverse=True)
|
|
157
|
-
|
|
158
|
-
for i, patch in enumerate(patches):
|
|
159
|
-
PrettyOutput.print(f"Applying patch {i+1}/{len(patches)} at lines [{patch.start},{patch.end})", OutputType.INFO)
|
|
160
|
-
|
|
161
|
-
if patch.start > len(lines):
|
|
162
|
-
PrettyOutput.print(f"Invalid patch: start line {patch.start} exceeds file length {len(lines)}", OutputType.WARNING)
|
|
163
|
-
os.system(f"git reset {file_path}")
|
|
164
|
-
os.system(f"git checkout -- {file_path}")
|
|
165
|
-
return False
|
|
166
|
-
|
|
167
|
-
if patch.new_code:
|
|
168
|
-
new_lines = patch.new_code.splitlines(keepends=True)
|
|
169
|
-
lines[patch.start:patch.end] = new_lines
|
|
170
|
-
else:
|
|
171
|
-
del lines[patch.start:patch.end]
|
|
172
|
-
|
|
173
|
-
# Write modified content back to file
|
|
174
|
-
with open(file_path, "w", encoding="utf-8") as f:
|
|
175
|
-
f.writelines(lines)
|
|
176
|
-
|
|
177
|
-
os.system(f"git add {file_path}")
|
|
178
|
-
PrettyOutput.print(f"Successfully applied all patches to {file_path}", OutputType.SUCCESS)
|
|
179
|
-
return True
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
def retry_comfirm(self) -> Tuple[str, str]:
|
|
183
|
-
choice = get_single_line_input("\nPlease choose an action: (1) Retry (2) Skip (3) Completely stop [1]: ") or "1"
|
|
184
|
-
if choice == "2":
|
|
185
|
-
return "skip", ""
|
|
186
|
-
if choice == "3":
|
|
187
|
-
return "break", ""
|
|
188
|
-
|
|
189
|
-
feedback = get_multiline_input("Please enter additional information and requirements:")
|
|
190
|
-
if feedback:
|
|
191
|
-
save_prompt = get_single_line_input("Would you like to save this as general feedback for future patches? (y/n) [n]: ").lower() or "n"
|
|
192
|
-
if save_prompt == "y":
|
|
193
|
-
self._save_additional_info(feedback)
|
|
194
|
-
PrettyOutput.print("Feedback saved for future use", OutputType.SUCCESS)
|
|
195
|
-
|
|
196
|
-
return "continue", feedback
|
|
197
|
-
|
|
198
|
-
def apply_patch(self, feature: str, structed_plan: Dict[str, str]) -> Tuple[bool, str]:
|
|
199
|
-
"""Apply patch (main entry)"""
|
|
200
|
-
feedback = ""
|
|
201
|
-
for file_path, current_plan in structed_plan.items():
|
|
202
|
-
additional_info = self.additional_info # Initialize with saved info
|
|
203
|
-
while True:
|
|
204
|
-
if os.path.exists(file_path):
|
|
205
|
-
# Read file and add line numbers
|
|
206
|
-
lines = []
|
|
207
|
-
with open(file_path, "r", encoding="utf-8") as f:
|
|
208
|
-
for i, line in enumerate(f):
|
|
209
|
-
lines.append(f"{i:04x}{line}") # Changed from i+1 to i for 0-based indexing
|
|
210
|
-
content = "".join(lines)
|
|
211
|
-
else:
|
|
212
|
-
content = "<File does not exist, need to create>"
|
|
213
|
-
|
|
214
|
-
prompt = """You are a senior software development expert who can generate code patches based on the complete modification plan, current original code file path, code content (with 4-digit hexadecimal line numbers), and current file's modification plan. The output format should be as follows:
|
|
215
|
-
|
|
216
|
-
<PATCH>
|
|
217
|
-
[start,end)
|
|
218
|
-
new_code
|
|
219
|
-
</PATCH>
|
|
220
|
-
|
|
221
|
-
Example:
|
|
222
|
-
<PATCH>
|
|
223
|
-
[000c,000c)
|
|
224
|
-
def new_function():
|
|
225
|
-
pass
|
|
226
|
-
</PATCH>
|
|
227
|
-
|
|
228
|
-
means:
|
|
229
|
-
Insert code BEFORE line 12:
|
|
230
|
-
```
|
|
231
|
-
def new_function():
|
|
232
|
-
pass
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
Example 2:
|
|
236
|
-
<PATCH>
|
|
237
|
-
[0004,000b)
|
|
238
|
-
aa
|
|
239
|
-
bb
|
|
240
|
-
cc
|
|
241
|
-
</PATCH>
|
|
242
|
-
|
|
243
|
-
means:
|
|
244
|
-
Replace lines [4,11) with:
|
|
245
|
-
```
|
|
246
|
-
aa
|
|
247
|
-
bb
|
|
248
|
-
cc
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
Rules:
|
|
252
|
-
1. start and end are hexadecimal line numbers (e.g., 000a)
|
|
253
|
-
2. The patch will replace lines [start,end) with new_code (including start, excluding end)
|
|
254
|
-
3. If start equals end, new_code will be inserted BEFORE that line
|
|
255
|
-
4. If new_code is empty, lines [start,end) will be deleted
|
|
256
|
-
5. Multiple patches can be generated
|
|
257
|
-
6. Each line in the input file starts with its 4-digit hexadecimal line number (0-based)
|
|
258
|
-
7. Your new_code should NOT include line numbers
|
|
259
|
-
8. CRITICAL: Patches MUST NOT overlap - ensure each line is modified by at most one patch
|
|
260
|
-
9. Generate patches from bottom to top of the file
|
|
261
|
-
10. Ensure new_code maintains correct indentation and formatting
|
|
262
|
-
11. Each patch should modify no more than 20 lines
|
|
263
|
-
12. Include sufficient context in new_code to maintain code consistency
|
|
264
|
-
13. `[` and `)` must be included in the line range
|
|
265
|
-
14. Line numbers start from 0
|
|
266
|
-
15. Example of INVALID overlapping patches:
|
|
267
|
-
<PATCH>
|
|
268
|
-
[0001,0005)
|
|
269
|
-
code1
|
|
270
|
-
</PATCH>
|
|
271
|
-
<PATCH>
|
|
272
|
-
[0003,0007) # This overlaps with the previous patch
|
|
273
|
-
code2
|
|
274
|
-
</PATCH>
|
|
275
|
-
"""
|
|
276
|
-
|
|
277
|
-
prompt += f"""# Original requirement: {feature}
|
|
278
|
-
# Current file path: {file_path}
|
|
279
|
-
# Current file content:
|
|
280
|
-
<CONTENT>
|
|
281
|
-
{content}
|
|
282
|
-
</CONTENT>
|
|
283
|
-
# Current file modification plan:
|
|
284
|
-
{current_plan}
|
|
285
|
-
{ "# Additional information: " + additional_info if additional_info else "" }
|
|
286
|
-
"""
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
PrettyOutput.print(f"Generating formatted patches for {file_path}...", OutputType.PROGRESS)
|
|
290
|
-
response = PlatformRegistry.get_global_platform_registry().get_codegen_platform().chat_until_success(prompt)
|
|
291
|
-
patches = self._extract_patches(response)
|
|
292
|
-
|
|
293
|
-
if not patches or not self.apply_file_patch(file_path, patches) or not self._confirm_and_apply_changes(file_path):
|
|
294
|
-
os.system(f"git reset {file_path}")
|
|
295
|
-
os.system(f"git checkout -- {file_path}")
|
|
296
|
-
PrettyOutput.print("Patch generation failed", OutputType.WARNING)
|
|
297
|
-
act, msg = self.retry_comfirm()
|
|
298
|
-
if act == "break":
|
|
299
|
-
PrettyOutput.print("Terminate patch application", OutputType.WARNING)
|
|
300
|
-
additional_info = get_multiline_input("Please enter your additional information or suggestions (press Enter to cancel):")
|
|
301
|
-
return False, additional_info
|
|
302
|
-
if act == "skip":
|
|
303
|
-
PrettyOutput.print(f"Skip file {file_path}", OutputType.WARNING)
|
|
304
|
-
feedback += f"Skip file {file_path}\n"
|
|
305
|
-
feedback += "Reason: " + get_multiline_input("Please enter your reason:") + "\n"
|
|
306
|
-
break
|
|
307
|
-
else:
|
|
308
|
-
additional_info += msg + "\n"
|
|
309
|
-
continue
|
|
310
|
-
else:
|
|
311
|
-
self._finalize_changes()
|
|
312
|
-
break
|
|
313
|
-
|
|
314
|
-
return True, feedback
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
def handle_patch_application(self, feature: str, structed_plan: Dict[str,str]) -> Tuple[bool, str]:
|
|
319
|
-
"""Process patch application process
|
|
320
|
-
|
|
321
|
-
Args:
|
|
322
|
-
related_files: Related files list
|
|
323
|
-
feature: Feature description
|
|
324
|
-
modification_plan: Modification plan
|
|
325
|
-
|
|
326
|
-
Returns:
|
|
327
|
-
bool: Whether patch application is successful
|
|
328
|
-
"""
|
|
329
|
-
PrettyOutput.print("\nThe following modification plan will be applied:", OutputType.INFO)
|
|
330
|
-
for file_path, patches_code in structed_plan.items():
|
|
331
|
-
PrettyOutput.print(f"\nFile: {file_path}", OutputType.INFO)
|
|
332
|
-
PrettyOutput.print(f"Modification plan: \n{patches_code}", OutputType.INFO)
|
|
333
|
-
# 3. Apply patches
|
|
334
|
-
success, additional_info = self.apply_patch(feature, structed_plan)
|
|
335
|
-
if not success:
|
|
336
|
-
os.system("git reset --hard")
|
|
337
|
-
return False, additional_info
|
|
338
|
-
# 6. Apply successfully, let user confirm changes
|
|
339
|
-
PrettyOutput.print("\nPatches applied, please check the modification effect.", OutputType.SUCCESS)
|
|
340
|
-
return True, "Modification applied successfully"
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
from typing import Dict, List, Tuple
|
|
3
|
-
from jarvis.models.registry import PlatformRegistry
|
|
4
|
-
from jarvis.utils import PrettyOutput, OutputType, get_multiline_input, get_single_line_input, is_long_context
|
|
5
|
-
|
|
6
|
-
class PlanGenerator:
|
|
7
|
-
"""Modification plan generator"""
|
|
8
|
-
|
|
9
|
-
def _build_prompt(self, feature: str, related_files: List[Dict], additional_info: str) -> str:
|
|
10
|
-
"""Build prompt
|
|
11
|
-
|
|
12
|
-
Args:
|
|
13
|
-
feature: Feature description
|
|
14
|
-
related_files: Related files list
|
|
15
|
-
additional_info: User supplement information
|
|
16
|
-
|
|
17
|
-
Returns:
|
|
18
|
-
str: Complete prompt
|
|
19
|
-
"""
|
|
20
|
-
prompt = "You are a code modification expert who can generate modification plans based on requirements and relevant code snippets. I need your help to analyze how to implement the following feature:\n\n"
|
|
21
|
-
prompt += f"{feature}\n\n"
|
|
22
|
-
|
|
23
|
-
prompt += "Here are the relevant code file snippets:\n\n"
|
|
24
|
-
|
|
25
|
-
for file in related_files:
|
|
26
|
-
prompt += f"File: {file['file_path']}\n"
|
|
27
|
-
for part in file["parts"]:
|
|
28
|
-
prompt += f"<PART>\n{part}\n</PART>\n"
|
|
29
|
-
|
|
30
|
-
prompt += "\nPlease provide detailed modifications needed to implement this feature. Include:\n"
|
|
31
|
-
prompt += "1. Which files need to be modified\n"
|
|
32
|
-
prompt += "2. How to modify each file, no explanation needed\n"
|
|
33
|
-
prompt += "3. Don't assume other files or code exist, only generate modification plans based on provided file contents and description\n"
|
|
34
|
-
prompt += "4. Don't implement features outside the requirement\n"
|
|
35
|
-
prompt += "5. Output only one modification plan per file (can be multiple lines)\n"
|
|
36
|
-
prompt += "6. Output format as follows:\n"
|
|
37
|
-
prompt += "<PLAN>\n"
|
|
38
|
-
prompt += "> path/to/file1\n"
|
|
39
|
-
prompt += "modification plan\n"
|
|
40
|
-
prompt += "</PLAN>\n"
|
|
41
|
-
prompt += "<PLAN>\n"
|
|
42
|
-
prompt += "> path/to/file2\n"
|
|
43
|
-
prompt += "modification plan\n"
|
|
44
|
-
prompt += "</PLAN>\n"
|
|
45
|
-
if additional_info:
|
|
46
|
-
prompt += f"# Additional information:\n{additional_info}\n"
|
|
47
|
-
|
|
48
|
-
return prompt
|
|
49
|
-
|
|
50
|
-
|
|
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]:
|
|
87
|
-
"""Generate modification plan
|
|
88
|
-
|
|
89
|
-
Args:
|
|
90
|
-
feature: Feature description
|
|
91
|
-
related_files: Related files list
|
|
92
|
-
|
|
93
|
-
Returns:
|
|
94
|
-
Tuple[str, Dict[str,str]]: Modification plan, return None if user cancels
|
|
95
|
-
"""
|
|
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
|
-
|
|
104
|
-
while True:
|
|
105
|
-
prompt = self._build_prompt(feature, file_info, additional_info)
|
|
106
|
-
# Build prompt
|
|
107
|
-
PrettyOutput.print("Start generating modification plan...", OutputType.PROGRESS)
|
|
108
|
-
|
|
109
|
-
# Get modification plan
|
|
110
|
-
raw_plan = PlatformRegistry.get_global_platform_registry().get_thinking_platform().chat_until_success(prompt)
|
|
111
|
-
structed_plan = self._extract_code(raw_plan)
|
|
112
|
-
if not structed_plan:
|
|
113
|
-
PrettyOutput.print("Modification plan generation failed, please try again", OutputType.ERROR)
|
|
114
|
-
tmp = get_multiline_input("Please enter your additional information or suggestions (press Enter to cancel):")
|
|
115
|
-
if tmp == "__interrupt__" or prompt == "":
|
|
116
|
-
return {}
|
|
117
|
-
additional_info += tmp + "\n"
|
|
118
|
-
continue
|
|
119
|
-
user_input = get_single_line_input("Do you agree with this modification plan? (y/n) [y]").strip().lower() or 'y'
|
|
120
|
-
if user_input == 'y' or user_input == '':
|
|
121
|
-
return structed_plan
|
|
122
|
-
elif user_input == 'n':
|
|
123
|
-
# Get user feedback
|
|
124
|
-
tmp = get_multiline_input("Please enter your additional information or suggestions (press Enter to cancel):")
|
|
125
|
-
if prompt == "__interrupt__" or prompt == "":
|
|
126
|
-
return {}
|
|
127
|
-
additional_info += tmp + "\n"
|
|
128
|
-
continue
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def _extract_code(self, response: str) -> Dict[str, str]:
|
|
132
|
-
"""Extract code from response
|
|
133
|
-
|
|
134
|
-
Args:
|
|
135
|
-
response: Model response content
|
|
136
|
-
|
|
137
|
-
Returns:
|
|
138
|
-
Dict[str, List[str]]: Code dictionary, key is file path, value is code snippet list
|
|
139
|
-
"""
|
|
140
|
-
code_dict = {}
|
|
141
|
-
for match in re.finditer(r'<PLAN>\n> (.+?)\n(.*?)\n</PLAN>', response, re.DOTALL):
|
|
142
|
-
file_path = match.group(1)
|
|
143
|
-
code_part = match.group(2)
|
|
144
|
-
code_dict[file_path] = code_part
|
|
145
|
-
return code_dict
|
|
@@ -1,70 +0,0 @@
|
|
|
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, additional_info = 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
|
-
"stdout": "Changes have been rolled back",
|
|
55
|
-
"stderr": additional_info
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
"success": True,
|
|
60
|
-
"stdout": "Code modifications have been successfully applied and committed",
|
|
61
|
-
"stderr": additional_info
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
except Exception as e:
|
|
65
|
-
PrettyOutput.print(str(e), OutputType.ERROR)
|
|
66
|
-
return {
|
|
67
|
-
"success": False,
|
|
68
|
-
"stdout": "",
|
|
69
|
-
"stderr": f"Failed to execute code modifications: {str(e)}"
|
|
70
|
-
}
|
jarvis/tools/find_files.py
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
from typing import Dict, Any
|
|
2
|
-
|
|
3
|
-
from jarvis.agent import Agent
|
|
4
|
-
from jarvis.tools.registry import ToolRegistry
|
|
5
|
-
from jarvis.utils import OutputType, PrettyOutput
|
|
6
|
-
|
|
7
|
-
find_files_system_prompt = """You are a Find Files Agent specialized in searching and identifying relevant code files in a codebase. Your task is to find files that are most likely related to the given requirements or problems.
|
|
8
|
-
|
|
9
|
-
SEARCH WORKFLOW:
|
|
10
|
-
1. Understand Search Requirements
|
|
11
|
-
- Analyze the search query thoroughly
|
|
12
|
-
- Identify key technical terms and concepts
|
|
13
|
-
- Break down complex requirements into searchable terms
|
|
14
|
-
|
|
15
|
-
2. Execute Search Strategy
|
|
16
|
-
- Use shell commands to search systematically:
|
|
17
|
-
* Search for key terms:
|
|
18
|
-
<TOOL_CALL>
|
|
19
|
-
name: execute_shell
|
|
20
|
-
arguments:
|
|
21
|
-
command: grep -r "pattern" .
|
|
22
|
-
</TOOL_CALL>
|
|
23
|
-
* Find files by name patterns:
|
|
24
|
-
<TOOL_CALL>
|
|
25
|
-
name: execute_shell
|
|
26
|
-
arguments:
|
|
27
|
-
command: find . -name "pattern"
|
|
28
|
-
</TOOL_CALL>
|
|
29
|
-
* Examine file contents:
|
|
30
|
-
<TOOL_CALL>
|
|
31
|
-
name: execute_shell
|
|
32
|
-
arguments:
|
|
33
|
-
command: grep -A 5 -B 5 "pattern" file.py
|
|
34
|
-
</TOOL_CALL>
|
|
35
|
-
|
|
36
|
-
3. Analyze Results
|
|
37
|
-
- Review each potential file
|
|
38
|
-
- Check file relevance
|
|
39
|
-
- Examine file relationships
|
|
40
|
-
- Consider file dependencies
|
|
41
|
-
|
|
42
|
-
4. Generate File List
|
|
43
|
-
- List all relevant files
|
|
44
|
-
- Sort by relevance
|
|
45
|
-
- Include brief explanation for each file
|
|
46
|
-
- Format output as YAML
|
|
47
|
-
|
|
48
|
-
OUTPUT FORMAT:
|
|
49
|
-
files:
|
|
50
|
-
- path: path/to/file1
|
|
51
|
-
relevance: "Brief explanation of why this file is relevant"
|
|
52
|
-
- path: path/to/file2
|
|
53
|
-
relevance: "Brief explanation of why this file is relevant"
|
|
54
|
-
|
|
55
|
-
SEARCH BEST PRACTICES:
|
|
56
|
-
- Use multiple search terms
|
|
57
|
-
- Consider file naming conventions
|
|
58
|
-
- Check both file names and contents
|
|
59
|
-
- Look for related files (imports, dependencies)
|
|
60
|
-
- Use grep with context (-A, -B options)
|
|
61
|
-
- Search in specific directories when appropriate
|
|
62
|
-
- Exclude irrelevant directories (like .git, __pycache__)
|
|
63
|
-
|
|
64
|
-
IMPORTANT:
|
|
65
|
-
1. Focus on finding the most relevant files
|
|
66
|
-
2. Avoid listing irrelevant files
|
|
67
|
-
3. Explain relevance clearly but concisely
|
|
68
|
-
4. Consider both direct and indirect relevance
|
|
69
|
-
5. Use file content to confirm relevance
|
|
70
|
-
"""
|
|
71
|
-
|
|
72
|
-
class FindFilesTool:
|
|
73
|
-
name = "find_files"
|
|
74
|
-
description = "Search and identify relevant code files in the codebase based on requirements or problems"
|
|
75
|
-
parameters = {
|
|
76
|
-
"type": "object",
|
|
77
|
-
"properties": {
|
|
78
|
-
"query": {
|
|
79
|
-
"type": "string",
|
|
80
|
-
"description": "The search query or requirement description"
|
|
81
|
-
}
|
|
82
|
-
},
|
|
83
|
-
"required": ["query"]
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
def execute(self, args: Dict) -> Dict[str, Any]:
|
|
87
|
-
"""Execute file search task"""
|
|
88
|
-
try:
|
|
89
|
-
query = args["query"]
|
|
90
|
-
|
|
91
|
-
PrettyOutput.print(f"Creating Find Files agent to search for: {query}", OutputType.INFO)
|
|
92
|
-
|
|
93
|
-
tool_registry = ToolRegistry()
|
|
94
|
-
tool_registry.use_tools(["ask_user", "execute_shell", "file_operation"])
|
|
95
|
-
|
|
96
|
-
# Create find files agent
|
|
97
|
-
find_agent = Agent(
|
|
98
|
-
system_prompt=find_files_system_prompt,
|
|
99
|
-
name="Find Files Agent",
|
|
100
|
-
is_sub_agent=True,
|
|
101
|
-
tool_registry=tool_registry
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
# Execute search
|
|
105
|
-
result = find_agent.run(query)
|
|
106
|
-
|
|
107
|
-
return {
|
|
108
|
-
"success": True,
|
|
109
|
-
"stdout": result,
|
|
110
|
-
"stderr": ""
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
except Exception as e:
|
|
114
|
-
PrettyOutput.print(str(e), OutputType.ERROR)
|
|
115
|
-
return {
|
|
116
|
-
"success": False,
|
|
117
|
-
"stdout": "",
|
|
118
|
-
"stderr": f"Failed to execute file search: {str(e)}"
|
|
119
|
-
}
|