jarvis-ai-assistant 0.1.96__py3-none-any.whl → 0.1.97__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 +4 -7
- jarvis/jarvis_coder/main.py +17 -22
- jarvis/jarvis_coder/patch_handler.py +141 -441
- jarvis/jarvis_coder/plan_generator.py +64 -36
- jarvis/jarvis_platform/main.py +1 -1
- jarvis/jarvis_rag/main.py +1 -1
- jarvis/jarvis_smart_shell/main.py +15 -15
- 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.97.dist-info}/METADATA +1 -1
- jarvis_ai_assistant-0.1.97.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.97.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.96.dist-info → jarvis_ai_assistant-0.1.97.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.96.dist-info → jarvis_ai_assistant-0.1.97.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.1.96.dist-info → jarvis_ai_assistant-0.1.97.dist-info}/top_level.txt +0 -0
|
@@ -6,26 +6,15 @@ 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
|
-
|
|
16
|
+
|
|
17
|
+
def _extract_patches(self, response: str) -> List[Patch]:
|
|
29
18
|
"""从响应中提取补丁
|
|
30
19
|
|
|
31
20
|
Args:
|
|
@@ -34,414 +23,144 @@ int add(int a, int b) {
|
|
|
34
23
|
Returns:
|
|
35
24
|
List[Tuple[str, str, str]]: 补丁列表,每个补丁是 (格式, 文件路径, 补丁内容) 的元组
|
|
36
25
|
"""
|
|
37
|
-
patches = []
|
|
38
|
-
|
|
39
26
|
# 修改后的正则表达式匹配三种补丁格式
|
|
40
|
-
fmt_pattern = r'<
|
|
41
|
-
|
|
42
|
-
for
|
|
43
|
-
|
|
44
|
-
|
|
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))
|
|
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
|
|
77
32
|
|
|
78
|
-
return patches
|
|
79
|
-
|
|
80
|
-
def _extract_code(self, response: str) -> Dict[str, List[str]]:
|
|
81
|
-
"""从响应中提取代码
|
|
82
|
-
|
|
83
|
-
Args:
|
|
84
|
-
response: 模型响应内容
|
|
85
|
-
|
|
86
|
-
Returns:
|
|
87
|
-
Dict[str, List[str]]: 代码字典,key为文件路径,value为代码片段列表
|
|
88
|
-
"""
|
|
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
33
|
|
|
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, ""
|
|
203
|
-
|
|
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
|
-
|
|
226
|
-
def _confirm_and_apply_changes(self, file_path: str, temp_map: dict, modified_files: set) -> bool:
|
|
34
|
+
def _confirm_and_apply_changes(self, file_path: str) -> bool:
|
|
227
35
|
"""确认并应用修改"""
|
|
228
|
-
os.system(f"git diff {file_path}")
|
|
36
|
+
os.system(f"git diff --cached {file_path}")
|
|
229
37
|
confirm = input(f"\n是否接受 {file_path} 的修改?(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
|
# 回退修改
|
|
42
|
+
os.system(f"git reset {file_path}")
|
|
245
43
|
os.system(f"git checkout -- {file_path}")
|
|
246
44
|
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]
|
|
250
45
|
return False
|
|
251
46
|
|
|
252
|
-
|
|
47
|
+
|
|
48
|
+
def apply_file_patch(self, file_path: str, patches: List[Patch]) -> bool:
|
|
49
|
+
"""应用文件补丁"""
|
|
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"应用第 {i+1}/{len(patches)} 个补丁:删除文件 {file_path}", OutputType.INFO)
|
|
58
|
+
file_content = ""
|
|
59
|
+
os.system(f"git rm {file_path}")
|
|
60
|
+
PrettyOutput.print(f"应用第 {i+1}/{len(patches)} 个补丁成功", OutputType.SUCCESS)
|
|
61
|
+
elif patch.old_code == "":
|
|
62
|
+
PrettyOutput.print(f"应用第 {i+1}/{len(patches)} 个补丁:替换文件 {file_path} 内容:\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"应用第 {i+1}/{len(patches)} 个补丁成功", OutputType.SUCCESS)
|
|
67
|
+
else:
|
|
68
|
+
PrettyOutput.print(f"应用第 {i+1}/{len(patches)} 个补丁:文件原始内容:\n{patch.old_code}\n替换为:\n{patch.new_code}", OutputType.INFO)
|
|
69
|
+
if file_content.find(patch.old_code) == -1:
|
|
70
|
+
PrettyOutput.print(f"文件 {file_path} 中不存在 {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"应用第 {i+1}/{len(patches)} 个补丁成功", OutputType.SUCCESS)
|
|
79
|
+
return True
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def retry_comfirm(self) -> Tuple[str, str]:# 恢复用户选择逻辑
|
|
83
|
+
choice = input("\n请选择操作: (1) 重试 (2) 跳过 (3) 完全中止 [1]: ") or "1"
|
|
84
|
+
if choice == "2":
|
|
85
|
+
return "skip", ""
|
|
86
|
+
if choice == "3":
|
|
87
|
+
return "break", ""
|
|
88
|
+
return "continue", get_multiline_input("请输入补充说明和要求:")
|
|
89
|
+
|
|
90
|
+
def apply_patch(self, feature: str, raw_plan: str, structed_plan: Dict[str, str]) -> Tuple[bool, str]:
|
|
253
91
|
"""应用补丁(主入口)"""
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
|
|
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
|
-
|
|
96
|
+
if os.path.exists(file_path):
|
|
97
|
+
content = open(file_path, "r", encoding="utf-8").read()
|
|
98
|
+
else:
|
|
99
|
+
content = "<文件不存在,需要创建>"
|
|
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"为{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("补丁生成失败", OutputType.WARNING)
|
|
146
|
+
act, msg = self.retry_comfirm()
|
|
147
|
+
if act == "break":
|
|
148
|
+
PrettyOutput.print("终止补丁应用", OutputType.WARNING)
|
|
149
|
+
return False, msg
|
|
150
|
+
if act == "skip":
|
|
346
151
|
PrettyOutput.print(f"跳过文件 {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
|
-
|
|
400
|
-
错误信息:
|
|
401
|
-
{error_msg}
|
|
402
|
-
|
|
403
|
-
原始需求:
|
|
404
|
-
{feature}
|
|
405
161
|
|
|
406
|
-
用户补充信息:
|
|
407
|
-
{additional_info}
|
|
408
162
|
|
|
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
|
-
"""监控补丁应用结果
|
|
426
|
-
|
|
427
|
-
Args:
|
|
428
|
-
success: 是否成功
|
|
429
|
-
error_msg: 错误信息
|
|
430
|
-
|
|
431
|
-
Returns:
|
|
432
|
-
bool: 是否继续尝试
|
|
433
|
-
"""
|
|
434
|
-
if success:
|
|
435
|
-
PrettyOutput.print("补丁应用成功", OutputType.SUCCESS)
|
|
436
|
-
return False
|
|
437
|
-
|
|
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:
|
|
163
|
+
def handle_patch_application(self, feature: str, raw_plan: str, structed_plan: Dict[str,str]) -> bool:
|
|
445
164
|
"""处理补丁应用流程
|
|
446
165
|
|
|
447
166
|
Args:
|
|
@@ -452,41 +171,22 @@ CONFIRM_DELETE
|
|
|
452
171
|
Returns:
|
|
453
172
|
bool: 是否成功应用补丁
|
|
454
173
|
"""
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
PrettyOutput.print("
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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 # 回到外层循环重新开始补丁生成流程
|
|
174
|
+
PrettyOutput.print("\n将要应用以下修改方案:", OutputType.INFO)
|
|
175
|
+
for file_path, patches_code in structed_plan.items():
|
|
176
|
+
PrettyOutput.print(f"\n文件: {file_path}", OutputType.INFO)
|
|
177
|
+
PrettyOutput.print(f"修改方案: \n{patches_code}", OutputType.INFO)
|
|
178
|
+
# 3. 应用补丁
|
|
179
|
+
success, error_msg = self.apply_patch(feature, raw_plan, structed_plan)
|
|
180
|
+
if not success:
|
|
181
|
+
os.system("git reset --hard")
|
|
182
|
+
return False
|
|
183
|
+
# 6. 应用成功,让用户确认修改
|
|
184
|
+
PrettyOutput.print("\n补丁已应用,请检查修改效果。", OutputType.SUCCESS)
|
|
185
|
+
confirm = input("\n是否保留这些修改?(y/n) [y]: ").lower() or "y"
|
|
186
|
+
if confirm != "y":
|
|
187
|
+
PrettyOutput.print("用户取消修改,正在回退", OutputType.WARNING)
|
|
188
|
+
os.system("git reset --hard") # 回退所有修改
|
|
189
|
+
return False
|
|
190
|
+
else:
|
|
191
|
+
return True
|
|
192
|
+
|