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.

Files changed (41) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/agent.py +138 -144
  3. jarvis/jarvis_codebase/main.py +87 -54
  4. jarvis/jarvis_coder/git_utils.py +22 -25
  5. jarvis/jarvis_coder/main.py +166 -171
  6. jarvis/jarvis_coder/patch_handler.py +153 -453
  7. jarvis/jarvis_coder/plan_generator.py +76 -48
  8. jarvis/jarvis_platform/main.py +39 -39
  9. jarvis/jarvis_rag/main.py +182 -182
  10. jarvis/jarvis_smart_shell/main.py +34 -34
  11. jarvis/main.py +24 -24
  12. jarvis/models/ai8.py +22 -22
  13. jarvis/models/base.py +17 -13
  14. jarvis/models/kimi.py +31 -31
  15. jarvis/models/ollama.py +28 -28
  16. jarvis/models/openai.py +22 -24
  17. jarvis/models/oyi.py +25 -25
  18. jarvis/models/registry.py +33 -34
  19. jarvis/tools/ask_user.py +5 -5
  20. jarvis/tools/base.py +2 -2
  21. jarvis/tools/chdir.py +9 -9
  22. jarvis/tools/codebase_qa.py +4 -4
  23. jarvis/tools/coder.py +4 -4
  24. jarvis/tools/file_ops.py +1 -1
  25. jarvis/tools/generator.py +23 -23
  26. jarvis/tools/methodology.py +4 -4
  27. jarvis/tools/rag.py +4 -4
  28. jarvis/tools/registry.py +38 -38
  29. jarvis/tools/search.py +42 -42
  30. jarvis/tools/shell.py +13 -13
  31. jarvis/tools/sub_agent.py +16 -16
  32. jarvis/tools/thinker.py +41 -41
  33. jarvis/tools/webpage.py +17 -17
  34. jarvis/utils.py +59 -60
  35. {jarvis_ai_assistant-0.1.96.dist-info → jarvis_ai_assistant-0.1.98.dist-info}/METADATA +1 -1
  36. jarvis_ai_assistant-0.1.98.dist-info/RECORD +47 -0
  37. jarvis_ai_assistant-0.1.96.dist-info/RECORD +0 -47
  38. {jarvis_ai_assistant-0.1.96.dist-info → jarvis_ai_assistant-0.1.98.dist-info}/LICENSE +0 -0
  39. {jarvis_ai_assistant-0.1.96.dist-info → jarvis_ai_assistant-0.1.98.dist-info}/WHEEL +0 -0
  40. {jarvis_ai_assistant-0.1.96.dist-info → jarvis_ai_assistant-0.1.98.dist-info}/entry_points.txt +0 -0
  41. {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
- return patches
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
- Dict[str, List[str]]: 代码字典,key为文件路径,value为代码片段列表
24
+ List[Tuple[str, str, str]]: Patch list, each patch is a tuple of (format, file path, patch content)
88
25
  """
89
- code_dict = {}
90
- for match in re.finditer(r'<CODE>\n> (.+?)\n(.*)\n</CODE>', response, re.DOTALL):
91
- file_path = match.group(1)
92
- code_list = match.group(2)
93
- if file_path not in code_dict:
94
- code_dict[file_path] = []
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, temp_map: dict, modified_files: set) -> bool:
227
- """确认并应用修改"""
228
- os.system(f"git diff {file_path}")
229
- confirm = input(f"\n是否接受 {file_path} 的修改?(y/n) [y]: ").lower() or "y"
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"已回退 {file_path} 的修改", OutputType.WARNING)
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
- def apply_patch(self, related_files: List[Dict], plan: str, patches_code: Dict[str, List[str]]) -> Tuple[bool, str]:
253
- """应用补丁(主入口)"""
254
- error_info = []
255
- modified_files = set()
256
- file_map = {file["file_path"]: file["file_content"] for file in related_files}
257
- temp_map = file_map.copy()
258
-
259
- for file_path, code_list in patches_code.items():
260
- retry_count = 0
261
- original_code_list = code_list.copy()
262
- additional_info = ""
263
- error_details = ""
264
- model = PlatformRegistry.get_global_platform_registry().get_codegen_platform()
265
- model.set_system_message("""你是一个资深程序开发专家,你可以根据修改方案,要修改的代码文件,要修改代码的代码片段,生成给出代码片段的规范的补丁片段供程序自动应用。
266
- 补丁片段格式说明:
267
- 可选三种格式:
268
- 1. 差异模式(适合局部修改):
269
- <PATCH_FMT1>
270
- > path/to/file
271
- old_content
272
- @@@@@@
273
- new_content
274
- </PATCH_FMT1>
275
-
276
- 注意事项:
277
- a、仅输出补丁内容,不要输出任何其他内容
278
- b、如果在大段代码中有零星修改,生成多个补丁
279
- c、要替换的内容,一定要与文件内容完全一致(**包括缩进与空白**),不要有任何多余或者缺失的内容
280
- d、务必保留原始文件的缩进和格式
281
- e、给出的代码是修改的一部分,不用关注除本文件以外的修改
282
-
283
- 2. 全文件模式(适合新建或完全重写):
284
- <PATCH_FMT2>
285
- > path/to/file
286
- def new_function():
287
- print("new code")
288
- </PATCH_FMT2>
289
-
290
- 注意事项:
291
- a、仅输出补丁内容,不要输出任何其他内容
292
- b、给出的代码是修改的一部分,不用关注除本文件以外的修改
293
-
294
- 3. 删除文件模式:
295
- <PATCH_FMT3>
296
- > path/to/file_to_delete
297
- CONFIRM_DELETE
298
- </PATCH_FMT3>
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 not file_success or not self._confirm_and_apply_changes(file_path, temp_map, modified_files):
338
- # 显示错误信息并询问用户操作
339
- PrettyOutput.print(f"\n文件 {file_path} 补丁应用失败:", OutputType.WARNING)
340
- PrettyOutput.print("\n".join(current_errors), OutputType.WARNING)
341
-
342
- # 恢复用户选择逻辑
343
- choice = input("\n请选择操作: (1) 重试 (2) 补充信息后重试 (3) 跳过 (4) 完全中止 [1]: ") or "1"
344
-
345
- if choice == "3":
346
- PrettyOutput.print(f"跳过文件 {file_path}", OutputType.WARNING)
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
- if choice == "4":
349
- return False, "用户中止补丁应用"
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
- continue
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
- {feature}
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
- success: 是否成功
429
- error_msg: 错误信息
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
- if success:
435
- PrettyOutput.print("补丁应用成功", OutputType.SUCCESS)
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 # 回到外层循环重新开始补丁生成流程