jarvis-ai-assistant 0.1.96__py3-none-any.whl → 0.1.98__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 -144
- jarvis/jarvis_codebase/main.py +87 -54
- jarvis/jarvis_coder/git_utils.py +22 -25
- jarvis/jarvis_coder/main.py +166 -171
- jarvis/jarvis_coder/patch_handler.py +153 -453
- jarvis/jarvis_coder/plan_generator.py +76 -48
- jarvis/jarvis_platform/main.py +39 -39
- jarvis/jarvis_rag/main.py +182 -182
- jarvis/jarvis_smart_shell/main.py +34 -34
- jarvis/main.py +24 -24
- jarvis/models/ai8.py +22 -22
- jarvis/models/base.py +17 -13
- jarvis/models/kimi.py +31 -31
- jarvis/models/ollama.py +28 -28
- jarvis/models/openai.py +22 -24
- jarvis/models/oyi.py +25 -25
- jarvis/models/registry.py +33 -34
- jarvis/tools/ask_user.py +5 -5
- jarvis/tools/base.py +2 -2
- jarvis/tools/chdir.py +9 -9
- jarvis/tools/codebase_qa.py +4 -4
- jarvis/tools/coder.py +4 -4
- jarvis/tools/file_ops.py +1 -1
- jarvis/tools/generator.py +23 -23
- jarvis/tools/methodology.py +4 -4
- jarvis/tools/rag.py +4 -4
- jarvis/tools/registry.py +38 -38
- jarvis/tools/search.py +42 -42
- jarvis/tools/shell.py +13 -13
- jarvis/tools/sub_agent.py +16 -16
- jarvis/tools/thinker.py +41 -41
- jarvis/tools/webpage.py +17 -17
- jarvis/utils.py +59 -60
- {jarvis_ai_assistant-0.1.96.dist-info → jarvis_ai_assistant-0.1.98.dist-info}/METADATA +1 -1
- jarvis_ai_assistant-0.1.98.dist-info/RECORD +47 -0
- jarvis_ai_assistant-0.1.96.dist-info/RECORD +0 -47
- {jarvis_ai_assistant-0.1.96.dist-info → jarvis_ai_assistant-0.1.98.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.96.dist-info → jarvis_ai_assistant-0.1.98.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.96.dist-info → jarvis_ai_assistant-0.1.98.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.1.96.dist-info → jarvis_ai_assistant-0.1.98.dist-info}/top_level.txt +0 -0
|
@@ -6,487 +6,187 @@ from jarvis.models.base import BasePlatform
|
|
|
6
6
|
from jarvis.models.registry import PlatformRegistry
|
|
7
7
|
from jarvis.utils import OutputType, PrettyOutput, get_multiline_input, while_success
|
|
8
8
|
|
|
9
|
+
class Patch:
|
|
10
|
+
def __init__(self, old_code: str, new_code: str):
|
|
11
|
+
self.old_code = old_code
|
|
12
|
+
self.new_code = new_code
|
|
13
|
+
|
|
9
14
|
class PatchHandler:
|
|
10
|
-
def __init__(self):
|
|
11
|
-
self.code_part_model = PlatformRegistry.get_global_platform_registry().get_codegen_platform()
|
|
12
|
-
self.code_part_model.set_system_message("""你是一个资深程序开发专家,你可以根据代码文件片段,需求和修改计划生成修改代码,供程序自动应用。生成代码的格式如下,可以生成多个修改片段:
|
|
13
|
-
<CODE>
|
|
14
|
-
> path/to/file
|
|
15
|
-
code
|
|
16
|
-
</CODE>
|
|
17
|
-
|
|
18
|
-
如:
|
|
19
|
-
<CODE>
|
|
20
|
-
> /src/a.cpp
|
|
21
|
-
// add function
|
|
22
|
-
// existing code
|
|
23
|
-
int add(int a, int b) {
|
|
24
|
-
return a + b;
|
|
25
|
-
}
|
|
26
|
-
</CODE>""")
|
|
27
15
|
|
|
28
|
-
def _extract_patches(self, response: str) -> List[Tuple[str, str, str]]:
|
|
29
|
-
"""从响应中提取补丁
|
|
30
|
-
|
|
31
|
-
Args:
|
|
32
|
-
response: 模型响应内容
|
|
33
|
-
|
|
34
|
-
Returns:
|
|
35
|
-
List[Tuple[str, str, str]]: 补丁列表,每个补丁是 (格式, 文件路径, 补丁内容) 的元组
|
|
36
|
-
"""
|
|
37
|
-
patches = []
|
|
38
|
-
|
|
39
|
-
# 修改后的正则表达式匹配三种补丁格式
|
|
40
|
-
fmt_pattern = r'<PATCH_FMT(\d?)>\n?(.*?)\n?</PATCH_FMT\d?>\n?'
|
|
41
|
-
|
|
42
|
-
for match in re.finditer(fmt_pattern, response, re.DOTALL):
|
|
43
|
-
fmt_type = match.group(1) or "1" # 默认FMT1
|
|
44
|
-
patch_content = match.group(2)
|
|
45
|
-
|
|
46
|
-
# 提取文件路径和内容
|
|
47
|
-
lines = patch_content.split('\n')
|
|
48
|
-
if not lines:
|
|
49
|
-
continue
|
|
50
|
-
|
|
51
|
-
file_path_match = re.search(r'>\s*(.*)', lines[0])
|
|
52
|
-
if not file_path_match:
|
|
53
|
-
continue
|
|
54
|
-
|
|
55
|
-
file_path = file_path_match.group(1)
|
|
56
|
-
|
|
57
|
-
# 处理不同格式
|
|
58
|
-
if fmt_type == "3":
|
|
59
|
-
# FMT3格式:文件删除
|
|
60
|
-
if len(lines) < 2 or "CONFIRM_DELETE" not in lines[1]:
|
|
61
|
-
continue
|
|
62
|
-
patches.append(("FMT3", file_path, "CONFIRM_DELETE"))
|
|
63
|
-
elif fmt_type == "1":
|
|
64
|
-
# FMT1格式:新旧内容分隔
|
|
65
|
-
parts = '\n'.join(lines[1:]).split('@@@@@@')
|
|
66
|
-
if len(parts) != 2:
|
|
67
|
-
continue
|
|
68
|
-
old_content = parts[0]
|
|
69
|
-
new_content = parts[1]
|
|
70
|
-
patches.append(("FMT1", file_path, f"{old_content}\n@@@@@@\n{new_content}"))
|
|
71
|
-
elif fmt_type == "2":
|
|
72
|
-
# FMT2格式:全文件替换
|
|
73
|
-
if not lines[1:]: # 新增内容校验
|
|
74
|
-
continue
|
|
75
|
-
full_content = '\n'.join(lines[1:])
|
|
76
|
-
patches.append(("FMT2", file_path, full_content))
|
|
77
16
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def _extract_code(self, response: str) -> Dict[str, List[str]]:
|
|
81
|
-
"""从响应中提取代码
|
|
17
|
+
def _extract_patches(self, response: str) -> List[Patch]:
|
|
18
|
+
"""Extract patches from response
|
|
82
19
|
|
|
83
20
|
Args:
|
|
84
|
-
response:
|
|
21
|
+
response: Model response content
|
|
85
22
|
|
|
86
23
|
Returns:
|
|
87
|
-
|
|
24
|
+
List[Tuple[str, str, str]]: Patch list, each patch is a tuple of (format, file path, patch content)
|
|
88
25
|
"""
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
code_dict[file_path].append(code_list)
|
|
96
|
-
return code_dict
|
|
97
|
-
|
|
98
|
-
def make_code_raw_patch(self, related_files: List[Dict], modification_plan: str) -> Dict[str, List[str]]:
|
|
99
|
-
"""生成修改方案"""
|
|
100
|
-
prompt = f"# 修改方案:\n{modification_plan}\n# 相关文件:"
|
|
101
|
-
# 添加文件内容到提示
|
|
102
|
-
for i, file in enumerate(related_files):
|
|
103
|
-
if file["parts"]:
|
|
104
|
-
prompt += f"""\n{i}. {file["file_path"]}\n"""
|
|
105
|
-
prompt += f"""文件内容片段:\n"""
|
|
106
|
-
for i, p in enumerate(file["parts"]):
|
|
107
|
-
prompt += f"<PART{i}>\n"
|
|
108
|
-
prompt += f'{p}\n'
|
|
109
|
-
prompt += f"</PART{i}>\n"
|
|
110
|
-
|
|
111
|
-
# 调用模型生成代码片段
|
|
112
|
-
response = while_success(lambda: self.code_part_model.chat(prompt), 5)
|
|
113
|
-
return self._extract_code(response)
|
|
114
|
-
|
|
115
|
-
def make_file_formatted_patch(self, file: str, plan: str, code_list: List[str], model: BasePlatform) -> List[Tuple[str, str, str]]:
|
|
116
|
-
"""生成文件补丁"""
|
|
117
|
-
if os.path.exists(file):
|
|
118
|
-
content = open(file, "r", encoding="utf-8").read()
|
|
119
|
-
else:
|
|
120
|
-
content = "<文件不存在,需要创建>"
|
|
121
|
-
prompt = f"""\n# 完整修改方案:{plan}\n# 当前修改文件路径:\n{file}\n# 当前修改文件内容:\n<CONTENT>{content}\n</CONTENT>\n# 生成的补丁:"""
|
|
122
|
-
for code in code_list:
|
|
123
|
-
prompt += f"\n<CODE>\n{code}\n</CODE>"
|
|
124
|
-
PrettyOutput.print(f"为{file}生成格式化补丁...", OutputType.PROGRESS)
|
|
125
|
-
response = while_success(lambda: model.chat(prompt), 5)
|
|
126
|
-
return self._extract_patches(response)
|
|
127
|
-
|
|
128
|
-
def _handle_fmt3_delete(self, patch_file_path: str, patch_content: str, temp_map: dict, modified_files: set) -> Tuple[bool, str]:
|
|
129
|
-
"""处理FMT3文件删除补丁"""
|
|
130
|
-
if not os.path.exists(patch_file_path):
|
|
131
|
-
return False, f"文件不存在无法删除: {patch_file_path}"
|
|
132
|
-
|
|
133
|
-
# 安全检查
|
|
134
|
-
if patch_content != "CONFIRM_DELETE":
|
|
135
|
-
return False, f"删除确认标记缺失: {patch_file_path}"
|
|
136
|
-
|
|
137
|
-
# 执行删除
|
|
138
|
-
os.remove(patch_file_path)
|
|
139
|
-
os.system(f"git rm {patch_file_path}")
|
|
140
|
-
PrettyOutput.print(f"成功删除文件: {patch_file_path}", OutputType.SUCCESS)
|
|
141
|
-
if patch_file_path in temp_map:
|
|
142
|
-
del temp_map[patch_file_path]
|
|
143
|
-
modified_files.add(patch_file_path)
|
|
144
|
-
return True, ""
|
|
145
|
-
|
|
146
|
-
def _handle_fmt2_full_replace(self, patch_file_path: str, patch_content: str,
|
|
147
|
-
temp_map: dict, modified_files: set) -> Tuple[bool, str]:
|
|
148
|
-
"""处理FMT2全文件替换补丁"""
|
|
149
|
-
if not os.path.isabs(patch_file_path):
|
|
150
|
-
patch_file_path = os.path.abspath(patch_file_path)
|
|
151
|
-
|
|
152
|
-
if patch_file_path not in temp_map:
|
|
153
|
-
# 新建文件
|
|
154
|
-
try:
|
|
155
|
-
os.makedirs(os.path.dirname(patch_file_path), exist_ok=True)
|
|
156
|
-
with open(patch_file_path, "w", encoding="utf-8") as f:
|
|
157
|
-
f.write(patch_content)
|
|
158
|
-
temp_map[patch_file_path] = patch_content
|
|
159
|
-
modified_files.add(patch_file_path)
|
|
160
|
-
return True, ""
|
|
161
|
-
except Exception as e:
|
|
162
|
-
return False, f"创建新文件失败 {patch_file_path}: {str(e)}"
|
|
163
|
-
|
|
164
|
-
# 替换现有文件
|
|
165
|
-
temp_map[patch_file_path] = patch_content
|
|
166
|
-
modified_files.add(patch_file_path)
|
|
167
|
-
return True, ""
|
|
168
|
-
|
|
169
|
-
def _handle_fmt1_diff(self, patch_file_path: str, old_content: str, new_content: str,
|
|
170
|
-
temp_map: dict, modified_files: set) -> Tuple[bool, str]:
|
|
171
|
-
"""处理FMT1差异补丁"""
|
|
172
|
-
if patch_file_path not in temp_map and not old_content:
|
|
173
|
-
# 处理新文件创建
|
|
174
|
-
try:
|
|
175
|
-
dir_path = os.path.dirname(patch_file_path)
|
|
176
|
-
if dir_path and not os.path.exists(dir_path):
|
|
177
|
-
os.makedirs(dir_path, exist_ok=True)
|
|
178
|
-
with open(patch_file_path, "w", encoding="utf-8") as f:
|
|
179
|
-
f.write(new_content)
|
|
180
|
-
os.system(f"git add {patch_file_path}")
|
|
181
|
-
PrettyOutput.print(f"成功创建并添加文件: {patch_file_path}", OutputType.SUCCESS)
|
|
182
|
-
modified_files.add(patch_file_path)
|
|
183
|
-
temp_map[patch_file_path] = new_content
|
|
184
|
-
return True, ""
|
|
185
|
-
except Exception as e:
|
|
186
|
-
return False, f"创建新文件失败 {patch_file_path}: {str(e)}"
|
|
187
|
-
|
|
188
|
-
if patch_file_path not in temp_map:
|
|
189
|
-
return False, f"文件不存在: {patch_file_path}:{temp_map.keys()}"
|
|
190
|
-
|
|
191
|
-
current_content = temp_map[patch_file_path]
|
|
192
|
-
if old_content and old_content not in current_content:
|
|
193
|
-
return False, (
|
|
194
|
-
f"补丁应用失败: {patch_file_path}\n"
|
|
195
|
-
f"原因: 未找到要替换的代码\n"
|
|
196
|
-
f"期望找到的代码:\n{old_content}\n"
|
|
197
|
-
f"实际文件内容:\n{current_content[:200]}..."
|
|
198
|
-
)
|
|
199
|
-
|
|
200
|
-
temp_map[patch_file_path] = current_content.replace(old_content, new_content)
|
|
201
|
-
modified_files.add(patch_file_path)
|
|
202
|
-
return True, ""
|
|
26
|
+
# 修改后的正则表达式匹配三种补丁格式
|
|
27
|
+
fmt_pattern = r'<PATCH>\n>>>>>> SEARCH\n(.*?)\n?(={5,})\n(.*?)\n?<<<<<< REPLACE\n</PATCH>'
|
|
28
|
+
ret = []
|
|
29
|
+
for m in re.finditer(fmt_pattern, response, re.DOTALL):
|
|
30
|
+
ret.append(Patch(m.group(1), m.group(3)))
|
|
31
|
+
return ret
|
|
203
32
|
|
|
204
|
-
def _apply_single_patch(self, fmt: str, patch_file_path: str, patch_content: str,
|
|
205
|
-
temp_map: dict, modified_files: set) -> Tuple[bool, str]:
|
|
206
|
-
"""应用单个补丁"""
|
|
207
|
-
try:
|
|
208
|
-
if fmt == "FMT3":
|
|
209
|
-
return self._handle_fmt3_delete(patch_file_path, patch_content, temp_map, modified_files)
|
|
210
|
-
|
|
211
|
-
elif fmt == "FMT2":
|
|
212
|
-
return self._handle_fmt2_full_replace(patch_file_path, patch_content, temp_map, modified_files)
|
|
213
|
-
|
|
214
|
-
elif fmt == "FMT1":
|
|
215
|
-
parts = patch_content.split("@@@@@@")
|
|
216
|
-
if len(parts) != 2:
|
|
217
|
-
return False, f"补丁格式错误: {patch_file_path},缺少分隔符"
|
|
218
|
-
old_content = parts[0]
|
|
219
|
-
new_content = parts[1]
|
|
220
|
-
return self._handle_fmt1_diff(patch_file_path, old_content, new_content, temp_map, modified_files)
|
|
221
|
-
|
|
222
|
-
return False, f"未知的补丁格式: {fmt}"
|
|
223
|
-
except Exception as e:
|
|
224
|
-
return False, f"处理补丁时发生错误: {str(e)}"
|
|
225
33
|
|
|
226
|
-
def _confirm_and_apply_changes(self, file_path: str
|
|
227
|
-
"""
|
|
228
|
-
os.system(f"git diff {file_path}")
|
|
229
|
-
confirm = input(f"\
|
|
34
|
+
def _confirm_and_apply_changes(self, file_path: str) -> bool:
|
|
35
|
+
"""Confirm and apply changes"""
|
|
36
|
+
os.system(f"git diff --cached {file_path}")
|
|
37
|
+
confirm = input(f"\nAccept {file_path} changes? (y/n) [y]: ").lower() or "y"
|
|
230
38
|
if confirm == "y":
|
|
231
|
-
|
|
232
|
-
try:
|
|
233
|
-
dir_path = os.path.dirname(file_path)
|
|
234
|
-
if dir_path and not os.path.exists(dir_path):
|
|
235
|
-
os.makedirs(dir_path, exist_ok=True)
|
|
236
|
-
with open(file_path, "w", encoding="utf-8") as f:
|
|
237
|
-
f.write(temp_map[file_path])
|
|
238
|
-
PrettyOutput.print(f"成功修改文件: {file_path}", OutputType.SUCCESS)
|
|
239
|
-
return True
|
|
240
|
-
except Exception as e:
|
|
241
|
-
PrettyOutput.print(f"写入文件失败 {file_path}: {str(e)}", OutputType.WARNING)
|
|
242
|
-
return False
|
|
39
|
+
return True
|
|
243
40
|
else:
|
|
244
|
-
#
|
|
41
|
+
# Rollback changes
|
|
42
|
+
os.system(f"git reset {file_path}")
|
|
245
43
|
os.system(f"git checkout -- {file_path}")
|
|
246
|
-
PrettyOutput.print(f"
|
|
247
|
-
modified_files.discard(file_path)
|
|
248
|
-
if file_path in temp_map:
|
|
249
|
-
del temp_map[file_path]
|
|
44
|
+
PrettyOutput.print(f"Changes to {file_path} have been rolled back", OutputType.WARNING)
|
|
250
45
|
return False
|
|
251
46
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
注意事项:
|
|
301
|
-
1. 必须包含CONFIRM_DELETE确认标记
|
|
302
|
-
""")
|
|
303
|
-
|
|
304
|
-
|
|
47
|
+
|
|
48
|
+
def apply_file_patch(self, file_path: str, patches: List[Patch]) -> bool:
|
|
49
|
+
"""Apply file patch"""
|
|
50
|
+
if not os.path.exists(file_path):
|
|
51
|
+
base_dir = os.path.dirname(file_path)
|
|
52
|
+
os.makedirs(base_dir, exist_ok=True)
|
|
53
|
+
open(file_path, "w", encoding="utf-8").close()
|
|
54
|
+
file_content = open(file_path, "r", encoding="utf-8").read()
|
|
55
|
+
for i, patch in enumerate(patches):
|
|
56
|
+
if patch.old_code == "" and patch.new_code == "":
|
|
57
|
+
PrettyOutput.print(f"Apply patch {i+1}/{len(patches)}: Delete file {file_path}", OutputType.INFO)
|
|
58
|
+
file_content = ""
|
|
59
|
+
os.system(f"git rm {file_path}")
|
|
60
|
+
PrettyOutput.print(f"Apply patch {i+1}/{len(patches)} successfully", OutputType.SUCCESS)
|
|
61
|
+
elif patch.old_code == "":
|
|
62
|
+
PrettyOutput.print(f"Apply patch {i+1}/{len(patches)}: Replace file {file_path} content: \n{patch.new_code}", OutputType.INFO)
|
|
63
|
+
file_content = patch.new_code
|
|
64
|
+
open(file_path, "w", encoding="utf-8").write(patch.new_code)
|
|
65
|
+
os.system(f"git add {file_path}")
|
|
66
|
+
PrettyOutput.print(f"Apply patch {i+1}/{len(patches)} successfully", OutputType.SUCCESS)
|
|
67
|
+
else:
|
|
68
|
+
PrettyOutput.print(f"Apply patch {i+1}/{len(patches)}: File original content: \n{patch.old_code}\nReplace with: \n{patch.new_code}", OutputType.INFO)
|
|
69
|
+
if file_content.find(patch.old_code) == -1:
|
|
70
|
+
PrettyOutput.print(f"File {file_path} does not contain {patch.old_code}", OutputType.WARNING)
|
|
71
|
+
os.system(f"git reset {file_path}")
|
|
72
|
+
os.system(f"git checkout -- {file_path}")
|
|
73
|
+
return False
|
|
74
|
+
else:
|
|
75
|
+
file_content = file_content.replace(patch.old_code, patch.new_code, 1)
|
|
76
|
+
open(file_path, "w", encoding="utf-8").write(file_content)
|
|
77
|
+
os.system(f"git add {file_path}")
|
|
78
|
+
PrettyOutput.print(f"Apply patch {i+1}/{len(patches)} successfully", OutputType.SUCCESS)
|
|
79
|
+
return True
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def retry_comfirm(self) -> Tuple[str, str]:# Restore user selection logic
|
|
83
|
+
choice = input("\nPlease choose an action: (1) Retry (2) Skip (3) Completely stop [1]: ") or "1"
|
|
84
|
+
if choice == "2":
|
|
85
|
+
return "skip", ""
|
|
86
|
+
if choice == "3":
|
|
87
|
+
return "break", ""
|
|
88
|
+
return "continue", get_multiline_input("Please enter additional information and requirements:")
|
|
89
|
+
|
|
90
|
+
def apply_patch(self, feature: str, raw_plan: str, structed_plan: Dict[str, str]) -> Tuple[bool, str]:
|
|
91
|
+
"""Apply patch (main entry)"""
|
|
92
|
+
for file_path, current_plan in structed_plan.items():
|
|
93
|
+
additional_info = ""
|
|
305
94
|
while True:
|
|
306
|
-
try:
|
|
307
|
-
if retry_count == 0: # 首次调用生成格式化补丁
|
|
308
|
-
patches = self.make_file_formatted_patch(
|
|
309
|
-
file_path, plan, original_code_list, model
|
|
310
|
-
)
|
|
311
|
-
else: # 重试时直接生成新补丁
|
|
312
|
-
# 构建重试提示
|
|
313
|
-
original_code_str = '\n'.join(original_code_list)
|
|
314
|
-
retry_prompt = f"""根据以下信息重新生成补丁:
|
|
315
|
-
错误信息:{error_details}
|
|
316
|
-
用户补充:{additional_info}
|
|
317
|
-
原始代码片段:
|
|
318
|
-
{original_code_str}
|
|
319
|
-
请生成新的补丁,特别注意代码匹配准确性"""
|
|
320
|
-
|
|
321
|
-
response = while_success(lambda: model.chat(retry_prompt), 5)
|
|
322
|
-
patches = self._extract_patches(response)
|
|
323
|
-
|
|
324
|
-
except Exception as e:
|
|
325
|
-
error_info.append(f"生成补丁失败: {str(e)}")
|
|
326
|
-
return False, "\n".join(error_info)
|
|
327
|
-
|
|
328
|
-
# 处理当前文件的所有补丁
|
|
329
|
-
file_success = True
|
|
330
|
-
current_errors = []
|
|
331
|
-
for fmt, patch_file_path, patch_content in patches:
|
|
332
|
-
success, msg = self._apply_single_patch(fmt, patch_file_path, patch_content, temp_map, modified_files)
|
|
333
|
-
if not success:
|
|
334
|
-
current_errors.append(msg)
|
|
335
|
-
file_success = False
|
|
336
95
|
|
|
337
|
-
if
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
96
|
+
if os.path.exists(file_path):
|
|
97
|
+
content = open(file_path, "r", encoding="utf-8").read()
|
|
98
|
+
else:
|
|
99
|
+
content = "<File does not exist, need to create>"
|
|
100
|
+
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, and current file's modification plan. The output format should be as follows:
|
|
101
|
+
<PATCH>
|
|
102
|
+
>>>>>> SEARCH
|
|
103
|
+
old_code
|
|
104
|
+
======
|
|
105
|
+
new_code
|
|
106
|
+
<<<<<< REPLACE
|
|
107
|
+
</PATCH>
|
|
108
|
+
Rules:
|
|
109
|
+
1. When old_code is empty, it means replace everything from start to end
|
|
110
|
+
2. When new_code is empty, it means delete old_code
|
|
111
|
+
3. When both old_code and new_code are empty, it means delete the file
|
|
112
|
+
Notes:
|
|
113
|
+
1. Multiple patches can be generated
|
|
114
|
+
2. old_code will be replaced with new_code, pay attention to context continuity
|
|
115
|
+
3. Avoid breaking existing code logic when generating patches, e.g., don't insert function definitions inside existing function bodies
|
|
116
|
+
4. Include sufficient context to avoid ambiguity
|
|
117
|
+
5. Patches will be merged using file_content.replace(patch.old_code, patch.new_code, 1), so old_code and new_code need to match exactly, including empty lines, line breaks, whitespace, tabs, and comments
|
|
118
|
+
6. Ensure generated code has correct format (syntax, indentation, line breaks)
|
|
119
|
+
7. Ensure new_code's indentation and format matches old_code
|
|
120
|
+
8. Ensure code is inserted in appropriate locations, e.g., code using variables should be after declarations/definitions
|
|
121
|
+
9. Provide at least 3 lines of context before and after modified code for location
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
"""
|
|
125
|
+
prompt += f"""# Original requirement: {feature}
|
|
126
|
+
# Complete modification plan: {raw_plan}
|
|
127
|
+
# Current file path: {file_path}
|
|
128
|
+
# Current file content:
|
|
129
|
+
<CONTENT>
|
|
130
|
+
{content}
|
|
131
|
+
</CONTENT>
|
|
132
|
+
# Current file modification plan:
|
|
133
|
+
{current_plan}
|
|
134
|
+
{ "# Additional information: " + additional_info if additional_info else "" }
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
PrettyOutput.print(f"Generating formatted patches for {file_path}...", OutputType.PROGRESS)
|
|
139
|
+
response = PlatformRegistry.get_global_platform_registry().get_codegen_platform().chat_until_success(prompt)
|
|
140
|
+
patches = self._extract_patches(response)
|
|
141
|
+
|
|
142
|
+
if not patches or not self.apply_file_patch(file_path, patches) or not self._confirm_and_apply_changes(file_path):
|
|
143
|
+
os.system(f"git reset {file_path}")
|
|
144
|
+
os.system(f"git checkout -- {file_path}")
|
|
145
|
+
PrettyOutput.print("Patch generation failed", OutputType.WARNING)
|
|
146
|
+
act, msg = self.retry_comfirm()
|
|
147
|
+
if act == "break":
|
|
148
|
+
PrettyOutput.print("Terminate patch application", OutputType.WARNING)
|
|
149
|
+
return False, msg
|
|
150
|
+
if act == "skip":
|
|
151
|
+
PrettyOutput.print(f"Skip file {file_path}", OutputType.WARNING)
|
|
347
152
|
break
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
# 处理补充信息
|
|
352
|
-
if choice == "2":
|
|
353
|
-
additional_info = get_multiline_input("请输入补充说明和要求:")
|
|
354
|
-
retry_count += 1
|
|
355
|
-
continue # 直接进入下一次循环生成新补丁
|
|
356
|
-
|
|
357
|
-
# 选择1直接重试
|
|
358
|
-
retry_count += 1
|
|
359
|
-
continue
|
|
153
|
+
else:
|
|
154
|
+
additional_info += msg + "\n"
|
|
155
|
+
continue
|
|
360
156
|
else:
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
# 所有文件处理完成后写入实际文件
|
|
364
|
-
for file_path in modified_files:
|
|
365
|
-
try:
|
|
366
|
-
dir_path = os.path.dirname(file_path)
|
|
367
|
-
if dir_path and not os.path.exists(dir_path):
|
|
368
|
-
os.makedirs(dir_path, exist_ok=True)
|
|
369
|
-
|
|
370
|
-
with open(file_path, "w", encoding="utf-8") as f:
|
|
371
|
-
f.write(temp_map[file_path])
|
|
372
|
-
|
|
373
|
-
PrettyOutput.print(f"成功修改文件: {file_path}", OutputType.SUCCESS)
|
|
374
|
-
|
|
375
|
-
except Exception as e:
|
|
376
|
-
error_info.append(f"写入文件失败 {file_path}: {str(e)}")
|
|
377
|
-
return False, "\n".join(error_info)
|
|
157
|
+
break
|
|
378
158
|
|
|
379
159
|
return True, ""
|
|
380
160
|
|
|
381
|
-
def handle_patch_feedback(self, error_msg: str, feature: str) -> Dict[str, List[str]]:
|
|
382
|
-
"""处理补丁应用失败的反馈
|
|
383
|
-
|
|
384
|
-
Args:
|
|
385
|
-
error_msg: 错误信息
|
|
386
|
-
feature: 功能描述
|
|
387
|
-
|
|
388
|
-
Returns:
|
|
389
|
-
List[Tuple[str, str, str]]: 新的补丁列表
|
|
390
|
-
"""
|
|
391
|
-
PrettyOutput.print("补丁应用失败,尝试重新生成", OutputType.WARNING)
|
|
392
|
-
|
|
393
|
-
# 获取用户补充信息
|
|
394
|
-
additional_info = input("\n请输入补充信息(直接回车跳过):")
|
|
395
|
-
PrettyOutput.print(f"开始重新生成补丁", OutputType.INFO)
|
|
396
|
-
|
|
397
|
-
# 构建重试提示
|
|
398
|
-
retry_prompt = f"""补丁应用失败,请根据以下信息重新生成补丁:
|
|
399
161
|
|
|
400
|
-
错误信息:
|
|
401
|
-
{error_msg}
|
|
402
162
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
用户补充信息:
|
|
407
|
-
{additional_info}
|
|
408
|
-
|
|
409
|
-
请重新生成补丁,确保:
|
|
410
|
-
1. 代码匹配完全准确
|
|
411
|
-
2. 保持正确的缩进和格式
|
|
412
|
-
3. 避免之前的错误
|
|
413
|
-
"""
|
|
414
|
-
response = while_success(lambda: self.code_part_model.chat(retry_prompt), 5)
|
|
415
|
-
|
|
416
|
-
try:
|
|
417
|
-
patches = self._extract_code(response)
|
|
418
|
-
return patches
|
|
419
|
-
|
|
420
|
-
except Exception as e:
|
|
421
|
-
PrettyOutput.print(f"解析patch失败: {str(e)}", OutputType.WARNING)
|
|
422
|
-
return {}
|
|
423
|
-
|
|
424
|
-
def monitor_patch_result(self, success: bool, error_msg: str) -> bool:
|
|
425
|
-
"""监控补丁应用结果
|
|
163
|
+
def handle_patch_application(self, feature: str, raw_plan: str, structed_plan: Dict[str,str]) -> bool:
|
|
164
|
+
"""Process patch application process
|
|
426
165
|
|
|
427
166
|
Args:
|
|
428
|
-
|
|
429
|
-
|
|
167
|
+
related_files: Related files list
|
|
168
|
+
feature: Feature description
|
|
169
|
+
modification_plan: Modification plan
|
|
430
170
|
|
|
431
171
|
Returns:
|
|
432
|
-
bool:
|
|
172
|
+
bool: Whether patch application is successful
|
|
433
173
|
"""
|
|
434
|
-
|
|
435
|
-
|
|
174
|
+
PrettyOutput.print("\nThe following modification plan will be applied:", OutputType.INFO)
|
|
175
|
+
for file_path, patches_code in structed_plan.items():
|
|
176
|
+
PrettyOutput.print(f"\nFile: {file_path}", OutputType.INFO)
|
|
177
|
+
PrettyOutput.print(f"Modification plan: \n{patches_code}", OutputType.INFO)
|
|
178
|
+
# 3. Apply patches
|
|
179
|
+
success, error_msg = self.apply_patch(feature, raw_plan, structed_plan)
|
|
180
|
+
if not success:
|
|
181
|
+
os.system("git reset --hard")
|
|
436
182
|
return False
|
|
183
|
+
# 6. Apply successfully, let user confirm changes
|
|
184
|
+
PrettyOutput.print("\nPatches applied, please check the modification effect.", OutputType.SUCCESS)
|
|
185
|
+
confirm = input("\nKeep these changes? (y/n) [y]: ").lower() or "y"
|
|
186
|
+
if confirm != "y":
|
|
187
|
+
PrettyOutput.print("User cancelled changes, rolling back", OutputType.WARNING)
|
|
188
|
+
os.system("git reset --hard") # Rollback all changes
|
|
189
|
+
return False
|
|
190
|
+
else:
|
|
191
|
+
return True
|
|
437
192
|
|
|
438
|
-
PrettyOutput.print(f"补丁应用失败: {error_msg}", OutputType.WARNING)
|
|
439
|
-
|
|
440
|
-
# 询问是否继续尝试
|
|
441
|
-
retry = input("\n是否重新尝试?(y/n) [y]: ").lower() or "y"
|
|
442
|
-
return retry == "y"
|
|
443
|
-
|
|
444
|
-
def handle_patch_application(self, related_files: List[Dict], feature: str, modification_plan: str) -> bool:
|
|
445
|
-
"""处理补丁应用流程
|
|
446
|
-
|
|
447
|
-
Args:
|
|
448
|
-
related_files: 相关文件列表
|
|
449
|
-
feature: 功能描述
|
|
450
|
-
modification_plan: 修改方案
|
|
451
|
-
|
|
452
|
-
Returns:
|
|
453
|
-
bool: 是否成功应用补丁
|
|
454
|
-
"""
|
|
455
|
-
|
|
456
|
-
while True:
|
|
457
|
-
PrettyOutput.print("开始生成补丁...", OutputType.PROGRESS)
|
|
458
|
-
patches = self.make_code_raw_patch(related_files, modification_plan)
|
|
459
|
-
while True: # 在当前尝试中循环,直到成功或用户放弃
|
|
460
|
-
# 1. 生成补丁
|
|
461
|
-
if patches:
|
|
462
|
-
# 2. 显示补丁内容
|
|
463
|
-
PrettyOutput.print("\n将要应用以下补丁:", OutputType.INFO)
|
|
464
|
-
for file_path, patches_code in patches.items():
|
|
465
|
-
PrettyOutput.print(f"\n文件: {file_path}", OutputType.INFO)
|
|
466
|
-
for i, code_part in enumerate(patches_code):
|
|
467
|
-
PrettyOutput.print(f"片段{i}: \n{code_part}", OutputType.INFO)
|
|
468
|
-
# 3. 应用补丁
|
|
469
|
-
success, error_msg = self.apply_patch(related_files, modification_plan, patches)
|
|
470
|
-
if not success:
|
|
471
|
-
# 4. 如果应用失败,询问是否重试
|
|
472
|
-
should_retry = self.monitor_patch_result(success, error_msg)
|
|
473
|
-
if not should_retry:
|
|
474
|
-
return False # 用户选择不重试,直接返回失败
|
|
475
|
-
|
|
476
|
-
# 5. 处理失败反馈
|
|
477
|
-
patches = self.handle_patch_feedback(error_msg, modification_plan)
|
|
478
|
-
continue
|
|
479
|
-
# 6. 应用成功,让用户确认修改
|
|
480
|
-
PrettyOutput.print("\n补丁已应用,请检查修改效果。", OutputType.SUCCESS)
|
|
481
|
-
confirm = input("\n是否保留这些修改?(y/n) [y]: ").lower() or "y"
|
|
482
|
-
if confirm != "y":
|
|
483
|
-
PrettyOutput.print("用户取消修改,正在回退", OutputType.WARNING)
|
|
484
|
-
os.system("git reset --hard") # 回退所有修改
|
|
485
|
-
user_feed = get_multiline_input("请输入补充修改需求(直接回车结束): ").strip()
|
|
486
|
-
if not user_feed or user_feed == "__interrupt__":
|
|
487
|
-
return True
|
|
488
|
-
|
|
489
|
-
response = while_success(lambda: self.code_part_model.chat(user_feed), 5)
|
|
490
|
-
patches = self._extract_code(response)
|
|
491
|
-
|
|
492
|
-
continue # 回到外层循环重新开始补丁生成流程
|