jarvis-ai-assistant 0.1.161__py3-none-any.whl → 0.1.163__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/jarvis_agent/__init__.py +24 -6
- jarvis/jarvis_agent/jarvis.py +1 -2
- jarvis/jarvis_code_agent/code_agent.py +126 -159
- jarvis/jarvis_dev/main.py +17 -4
- jarvis/jarvis_git_details/main.py +2 -1
- jarvis/jarvis_git_utils/git_commiter.py +2 -3
- jarvis/jarvis_multi_agent/__init__.py +2 -3
- jarvis/jarvis_platform/base.py +0 -2
- jarvis/jarvis_platform/yuanbao.py +1 -0
- jarvis/jarvis_tools/edit_file.py +279 -0
- jarvis/jarvis_tools/file_operation.py +0 -2
- jarvis/jarvis_tools/read_code.py +0 -2
- jarvis/jarvis_tools/rewrite_file.py +183 -0
- jarvis/jarvis_utils/git_utils.py +144 -0
- jarvis/jarvis_utils/globals.py +0 -29
- jarvis/jarvis_utils/methodology.py +15 -9
- {jarvis_ai_assistant-0.1.161.dist-info → jarvis_ai_assistant-0.1.163.dist-info}/METADATA +3 -1
- {jarvis_ai_assistant-0.1.161.dist-info → jarvis_ai_assistant-0.1.163.dist-info}/RECORD +23 -22
- {jarvis_ai_assistant-0.1.161.dist-info → jarvis_ai_assistant-0.1.163.dist-info}/WHEEL +1 -1
- jarvis/jarvis_agent/patch.py +0 -617
- {jarvis_ai_assistant-0.1.161.dist-info → jarvis_ai_assistant-0.1.163.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.1.161.dist-info → jarvis_ai_assistant-0.1.163.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.161.dist-info → jarvis_ai_assistant-0.1.163.dist-info}/top_level.txt +0 -0
jarvis/jarvis_agent/patch.py
DELETED
|
@@ -1,617 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
from typing import Dict, Any, Tuple
|
|
3
|
-
import os
|
|
4
|
-
|
|
5
|
-
from yaspin import yaspin # type: ignore
|
|
6
|
-
|
|
7
|
-
from jarvis.jarvis_agent.output_handler import OutputHandler
|
|
8
|
-
from jarvis.jarvis_platform.base import BasePlatform
|
|
9
|
-
from jarvis.jarvis_platform.registry import PlatformRegistry
|
|
10
|
-
from jarvis.jarvis_git_utils.git_commiter import GitCommitTool
|
|
11
|
-
from jarvis.jarvis_tools.file_operation import FileOperationTool
|
|
12
|
-
from jarvis.jarvis_utils.config import is_confirm_before_apply_patch
|
|
13
|
-
from jarvis.jarvis_utils.git_utils import get_commits_between, get_latest_commit_hash
|
|
14
|
-
from jarvis.jarvis_utils.globals import add_read_file_record, has_read_file
|
|
15
|
-
from jarvis.jarvis_utils.input import get_multiline_input
|
|
16
|
-
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
17
|
-
from jarvis.jarvis_utils.utils import is_context_overflow, get_file_line_count, user_confirm
|
|
18
|
-
from jarvis.jarvis_utils.tag import ot, ct
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class PatchOutputHandler(OutputHandler):
|
|
22
|
-
def name(self) -> str:
|
|
23
|
-
return "PATCH"
|
|
24
|
-
|
|
25
|
-
def handle(self, response: str, agent: Any) -> Tuple[bool, Any]:
|
|
26
|
-
return False, apply_patch(response, agent)
|
|
27
|
-
|
|
28
|
-
def can_handle(self, response: str) -> bool:
|
|
29
|
-
if _has_patch_block(response):
|
|
30
|
-
return True
|
|
31
|
-
return False
|
|
32
|
-
|
|
33
|
-
def prompt(self) -> str:
|
|
34
|
-
return f"""
|
|
35
|
-
# 代码补丁规范
|
|
36
|
-
|
|
37
|
-
## 重要提示
|
|
38
|
-
我可以看到完整的代码,所以不需要生成完整的代码,只需要提供修改的代码片段即可。请尽量精简补丁内容,只包含必要的上下文和修改部分。特别注意:不要提供完整文件内容,只提供需要修改的部分!
|
|
39
|
-
|
|
40
|
-
## 补丁格式定义
|
|
41
|
-
使用{ot("PATCH")}块来精确指定代码更改:
|
|
42
|
-
```
|
|
43
|
-
{ot("PATCH")}
|
|
44
|
-
File: [文件路径]
|
|
45
|
-
Reason: [修改原因]
|
|
46
|
-
[代码修改说明,不用输出完整的代码,仅输出修改的片段即可]
|
|
47
|
-
{ct("PATCH")}
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
## 核心原则
|
|
51
|
-
1. **精准修改**:只显示需要修改的代码部分,不需要展示整个文件内容
|
|
52
|
-
2. **最小补丁原则**:始终生成最小范围的补丁,只包含必要的上下文和实际修改
|
|
53
|
-
3. **格式严格保持**:
|
|
54
|
-
- 严格保持原始代码的缩进方式(空格或制表符)
|
|
55
|
-
- 保持原始代码的空行数量和位置
|
|
56
|
-
- 保持原始代码的行尾空格处理方式
|
|
57
|
-
- 不改变原始代码的换行风格
|
|
58
|
-
4. **新旧区分**:
|
|
59
|
-
- 对于新文件:提供完整的代码内容
|
|
60
|
-
- 对于现有文件:只提供修改部分,不要提供整个文件
|
|
61
|
-
5. **理由说明**:每个补丁必须包含清晰的修改理由,解释为什么需要此更改
|
|
62
|
-
|
|
63
|
-
## 格式兼容性要求
|
|
64
|
-
1. **缩进一致性**:
|
|
65
|
-
- 如果原代码使用4个空格缩进,补丁也必须使用4个空格缩进
|
|
66
|
-
- 如果原代码使用制表符缩进,补丁也必须使用制表符缩进
|
|
67
|
-
2. **空行保留**:
|
|
68
|
-
- 如果原代码在函数之间有两个空行,补丁也必须保留这两个空行
|
|
69
|
-
- 如果原代码在类方法之间有一个空行,补丁也必须保留这一个空行
|
|
70
|
-
3. **行尾处理**:
|
|
71
|
-
- 如果原代码行尾没有空格,补丁也不应添加行尾空格
|
|
72
|
-
- 如果原代码使用特定的行尾注释风格,补丁也应保持该风格
|
|
73
|
-
|
|
74
|
-
## 补丁示例
|
|
75
|
-
```
|
|
76
|
-
{ot("PATCH")}
|
|
77
|
-
File: src/utils/math.py
|
|
78
|
-
Reason: 修复除零错误,增加参数验证以提高函数健壮性
|
|
79
|
-
def safe_divide(a, b):
|
|
80
|
-
# 添加参数验证
|
|
81
|
-
if b == 0:
|
|
82
|
-
raise ValueError("除数不能为零")
|
|
83
|
-
return a / b
|
|
84
|
-
# 现有代码 ...
|
|
85
|
-
def add(a, b):
|
|
86
|
-
return a + b
|
|
87
|
-
{ct("PATCH")}
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
## 最佳实践
|
|
91
|
-
- 每个补丁专注于单一职责的修改
|
|
92
|
-
- 避免包含过多无关代码
|
|
93
|
-
- 确保修改理由清晰明确,便于理解变更目的
|
|
94
|
-
- 保持代码风格一致性,遵循项目现有的编码规范
|
|
95
|
-
- 在修改前仔细分析原代码的格式风格,确保补丁与之完全兼容
|
|
96
|
-
- 绝不提供完整文件内容,除非是新建文件
|
|
97
|
-
- 每个文件的修改是独立的,不能出现“参照xxx文件的修改”这样的描述
|
|
98
|
-
- 不要出现未实现的代码,如:TODO
|
|
99
|
-
"""
|
|
100
|
-
|
|
101
|
-
def _has_patch_block(patch_str: str) -> bool:
|
|
102
|
-
"""判断是否存在补丁块"""
|
|
103
|
-
return re.search(ot("PATCH")+r'\n?(.*?)\n?' +
|
|
104
|
-
ct("PATCH"), patch_str, re.DOTALL) is not None
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def _parse_patch(patch_str: str) -> Tuple[Dict[str, str], str]:
|
|
108
|
-
"""解析新的上下文补丁格式"""
|
|
109
|
-
result = {}
|
|
110
|
-
patches = re.findall(ot("PATCH")+r'\n?(.*?)\n?' +
|
|
111
|
-
ct("PATCH"), patch_str, re.DOTALL)
|
|
112
|
-
if patches:
|
|
113
|
-
for patch in patches:
|
|
114
|
-
first_line = patch.splitlines()[0]
|
|
115
|
-
sm = re.match(r'^File:\s*(.+)$', first_line)
|
|
116
|
-
if not sm:
|
|
117
|
-
return ({}, f"""无效的补丁格式,正确格式应该为:
|
|
118
|
-
{ot("PATCH")}
|
|
119
|
-
File: [文件路径]
|
|
120
|
-
Reason: [修改原因]
|
|
121
|
-
[代码修改说明,不用输出完整的代码,仅输出修改的片段即可]
|
|
122
|
-
{ct("PATCH")}""")
|
|
123
|
-
filepath = os.path.abspath(sm.group(1).strip())
|
|
124
|
-
if filepath not in result:
|
|
125
|
-
result[filepath] = patch
|
|
126
|
-
else:
|
|
127
|
-
result[filepath] += "\n\n" + patch
|
|
128
|
-
return result, ""
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def apply_patch(output_str: str, agent: Any) -> str:
|
|
132
|
-
"""Apply patches to files"""
|
|
133
|
-
with yaspin(text="正在应用补丁...", color="cyan") as spinner:
|
|
134
|
-
try:
|
|
135
|
-
patches, error_msg = _parse_patch(output_str)
|
|
136
|
-
if error_msg:
|
|
137
|
-
spinner.text = "补丁格式错误"
|
|
138
|
-
spinner.fail("❌")
|
|
139
|
-
return error_msg
|
|
140
|
-
except Exception as e:
|
|
141
|
-
spinner.text = "解析补丁失败"
|
|
142
|
-
spinner.fail("❌")
|
|
143
|
-
return f"解析补丁失败: {str(e)}"
|
|
144
|
-
|
|
145
|
-
# 获取当前提交hash作为起始点
|
|
146
|
-
spinner.text = "开始获取当前提交hash..."
|
|
147
|
-
start_hash = get_latest_commit_hash()
|
|
148
|
-
spinner.write("✅ 当前提交hash获取完成")
|
|
149
|
-
|
|
150
|
-
not_read_file = [
|
|
151
|
-
f for f in patches.keys()
|
|
152
|
-
if not has_read_file(f)
|
|
153
|
-
and os.path.exists(f)
|
|
154
|
-
and os.path.getsize(f) > 0
|
|
155
|
-
]
|
|
156
|
-
if not_read_file:
|
|
157
|
-
spinner.text=f"以下文件未读取: {not_read_file},应用补丁存在风险,将先读取文件后再生成补丁"
|
|
158
|
-
spinner.fail("❌")
|
|
159
|
-
return f"以下文件未读取: {not_read_file},应用补丁存在风险,请先读取文件后再生成补丁"
|
|
160
|
-
|
|
161
|
-
# 检查是否有文件在Git仓库外
|
|
162
|
-
in_git_repo = True
|
|
163
|
-
for filepath in patches.keys():
|
|
164
|
-
if not _is_file_in_git_repo(filepath):
|
|
165
|
-
in_git_repo = False
|
|
166
|
-
break
|
|
167
|
-
|
|
168
|
-
# 按文件逐个处理
|
|
169
|
-
for filepath, patch_content in patches.items():
|
|
170
|
-
try:
|
|
171
|
-
spinner.text = f"正在处理文件: {filepath}"
|
|
172
|
-
if not os.path.exists(filepath):
|
|
173
|
-
# 新建文件
|
|
174
|
-
spinner.text = "文件不存在,正在创建文件..."
|
|
175
|
-
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
|
176
|
-
open(filepath, 'w', encoding='utf-8').close()
|
|
177
|
-
spinner.write("✅ 文件创建完成")
|
|
178
|
-
add_read_file_record(filepath)
|
|
179
|
-
with spinner.hidden():
|
|
180
|
-
while not handle_code_operation(filepath, patch_content):
|
|
181
|
-
if user_confirm("补丁应用失败,是否重试?", default=True):
|
|
182
|
-
pass
|
|
183
|
-
else:
|
|
184
|
-
raise Exception("补丁应用失败")
|
|
185
|
-
spinner.write(f"✅ 文件 {filepath} 处理完成")
|
|
186
|
-
except Exception as e:
|
|
187
|
-
spinner.text = f"文件 {filepath} 处理失败: {str(e)}, 回滚文件"
|
|
188
|
-
revert_file(filepath) # 回滚单个文件
|
|
189
|
-
spinner.write(f"✅ 文件 {filepath} 回滚完成")
|
|
190
|
-
|
|
191
|
-
final_ret = ""
|
|
192
|
-
if in_git_repo:
|
|
193
|
-
diff = get_diff()
|
|
194
|
-
if diff:
|
|
195
|
-
PrettyOutput.print(diff, OutputType.CODE, lang="diff")
|
|
196
|
-
with spinner.hidden():
|
|
197
|
-
commited = handle_commit_workflow()
|
|
198
|
-
if commited:
|
|
199
|
-
# 获取提交信息
|
|
200
|
-
end_hash = get_latest_commit_hash()
|
|
201
|
-
commits = get_commits_between(start_hash, end_hash)
|
|
202
|
-
|
|
203
|
-
# 添加提交信息到final_ret
|
|
204
|
-
if commits:
|
|
205
|
-
final_ret += "✅ 补丁已应用\n"
|
|
206
|
-
final_ret += "# 提交信息:\n"
|
|
207
|
-
for commit_hash, commit_message in commits:
|
|
208
|
-
final_ret += f"- {commit_hash[:7]}: {commit_message}\n"
|
|
209
|
-
|
|
210
|
-
final_ret += f"# 应用补丁:\n```diff\n{diff}\n```"
|
|
211
|
-
|
|
212
|
-
# 修改后的提示逻辑
|
|
213
|
-
addon_prompt = f"如果用户的需求未完成,请继续生成补丁,如果已经完成,请终止,不要输出新的 {ot('PATCH')},不要实现任何超出用户需求外的内容\n"
|
|
214
|
-
addon_prompt += "如果有任何信息不明确,调用工具获取信息\n"
|
|
215
|
-
addon_prompt += "每次响应必须且只能包含一个操作\n"
|
|
216
|
-
|
|
217
|
-
agent.set_addon_prompt(addon_prompt)
|
|
218
|
-
|
|
219
|
-
else:
|
|
220
|
-
final_ret += "✅ 补丁已应用(没有新的提交)"
|
|
221
|
-
else:
|
|
222
|
-
final_ret += "❌ 补丁应用被拒绝\n"
|
|
223
|
-
final_ret += f"# 补丁预览:\n```diff\n{diff}\n```"
|
|
224
|
-
else:
|
|
225
|
-
commited = False
|
|
226
|
-
final_ret += "❌ 没有要提交的更改\n"
|
|
227
|
-
else:
|
|
228
|
-
# 对于Git仓库外的文件,直接返回成功
|
|
229
|
-
final_ret += "✅ 补丁已应用(文件不在Git仓库中)"
|
|
230
|
-
commited = True
|
|
231
|
-
# 用户确认最终结果
|
|
232
|
-
with spinner.hidden():
|
|
233
|
-
if commited:
|
|
234
|
-
return final_ret
|
|
235
|
-
PrettyOutput.print(final_ret, OutputType.USER, lang="markdown")
|
|
236
|
-
if not is_confirm_before_apply_patch() or user_confirm("是否使用此回复?", default=True):
|
|
237
|
-
return final_ret
|
|
238
|
-
custom_reply = get_multiline_input("请输入自定义回复")
|
|
239
|
-
if not custom_reply.strip(): # 如果自定义回复为空,返回空字符串
|
|
240
|
-
return ""
|
|
241
|
-
agent.set_addon_prompt(custom_reply)
|
|
242
|
-
return final_ret
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
def _is_file_in_git_repo(filepath: str) -> bool:
|
|
246
|
-
"""检查文件是否在当前Git仓库中"""
|
|
247
|
-
import subprocess
|
|
248
|
-
try:
|
|
249
|
-
# 获取Git仓库根目录
|
|
250
|
-
repo_root = subprocess.run(
|
|
251
|
-
['git', 'rev-parse', '--show-toplevel'],
|
|
252
|
-
capture_output=True,
|
|
253
|
-
text=True
|
|
254
|
-
).stdout.strip()
|
|
255
|
-
|
|
256
|
-
# 检查文件路径是否在仓库根目录下
|
|
257
|
-
return os.path.abspath(filepath).startswith(os.path.abspath(repo_root))
|
|
258
|
-
except:
|
|
259
|
-
return False
|
|
260
|
-
|
|
261
|
-
def revert_file(filepath: str):
|
|
262
|
-
"""增强版git恢复,处理新文件"""
|
|
263
|
-
import subprocess
|
|
264
|
-
try:
|
|
265
|
-
# 检查文件是否在版本控制中
|
|
266
|
-
result = subprocess.run(
|
|
267
|
-
['git', 'ls-files', '--error-unmatch', filepath],
|
|
268
|
-
stderr=subprocess.PIPE,
|
|
269
|
-
text=False # 禁用自动文本解码
|
|
270
|
-
)
|
|
271
|
-
if result.returncode == 0:
|
|
272
|
-
subprocess.run(['git', 'checkout', 'HEAD',
|
|
273
|
-
'--', filepath], check=True)
|
|
274
|
-
else:
|
|
275
|
-
if os.path.exists(filepath):
|
|
276
|
-
os.remove(filepath)
|
|
277
|
-
subprocess.run(['git', 'clean', '-f', '--', filepath], check=True)
|
|
278
|
-
except subprocess.CalledProcessError as e:
|
|
279
|
-
error_msg = e.stderr.decode('utf-8', errors='replace') if e.stderr else str(e)
|
|
280
|
-
PrettyOutput.print(f"恢复文件失败: {error_msg}", OutputType.ERROR)
|
|
281
|
-
# 修改后的恢复函数
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
def revert_change():
|
|
285
|
-
"""恢复所有未提交的修改到HEAD状态"""
|
|
286
|
-
import subprocess
|
|
287
|
-
try:
|
|
288
|
-
# 检查是否为空仓库
|
|
289
|
-
head_check = subprocess.run(
|
|
290
|
-
['git', 'rev-parse', '--verify', 'HEAD'],
|
|
291
|
-
stderr=subprocess.PIPE,
|
|
292
|
-
stdout=subprocess.PIPE
|
|
293
|
-
)
|
|
294
|
-
if head_check.returncode == 0:
|
|
295
|
-
subprocess.run(['git', 'reset', '--hard', 'HEAD'], check=True)
|
|
296
|
-
subprocess.run(['git', 'clean', '-fd'], check=True)
|
|
297
|
-
except subprocess.CalledProcessError as e:
|
|
298
|
-
return f"恢复更改失败: {str(e)}"
|
|
299
|
-
# 修改后的获取差异函数
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
def get_diff() -> str:
|
|
303
|
-
"""使用git获取暂存区差异"""
|
|
304
|
-
import subprocess
|
|
305
|
-
|
|
306
|
-
# 初始化状态
|
|
307
|
-
need_reset = False
|
|
308
|
-
|
|
309
|
-
try:
|
|
310
|
-
# 暂存所有修改
|
|
311
|
-
subprocess.run(['git', 'add', '.'], check=True)
|
|
312
|
-
need_reset = True
|
|
313
|
-
|
|
314
|
-
# 获取差异
|
|
315
|
-
result = subprocess.run(
|
|
316
|
-
['git', 'diff', '--cached'],
|
|
317
|
-
capture_output=True,
|
|
318
|
-
text=False,
|
|
319
|
-
check=True
|
|
320
|
-
)
|
|
321
|
-
|
|
322
|
-
# 解码输出
|
|
323
|
-
try:
|
|
324
|
-
ret = result.stdout.decode('utf-8')
|
|
325
|
-
except UnicodeDecodeError:
|
|
326
|
-
ret = result.stdout.decode('utf-8', errors='replace')
|
|
327
|
-
|
|
328
|
-
# 重置暂存区
|
|
329
|
-
subprocess.run(['git', "reset", "--mixed"], check=False)
|
|
330
|
-
return ret
|
|
331
|
-
|
|
332
|
-
except subprocess.CalledProcessError as e:
|
|
333
|
-
if need_reset:
|
|
334
|
-
subprocess.run(['git', "reset", "--mixed"], check=False)
|
|
335
|
-
return f"获取差异失败: {str(e)}"
|
|
336
|
-
except Exception as e:
|
|
337
|
-
if need_reset:
|
|
338
|
-
subprocess.run(['git', "reset", "--mixed"], check=False)
|
|
339
|
-
return f"发生意外错误: {str(e)}"
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
def handle_commit_workflow() -> bool:
|
|
343
|
-
"""Handle the git commit workflow and return the commit details.
|
|
344
|
-
|
|
345
|
-
Returns:
|
|
346
|
-
bool: 提交是否成功
|
|
347
|
-
"""
|
|
348
|
-
if is_confirm_before_apply_patch() and not user_confirm("是否要提交代码?", default=True):
|
|
349
|
-
revert_change()
|
|
350
|
-
return False
|
|
351
|
-
|
|
352
|
-
import subprocess
|
|
353
|
-
try:
|
|
354
|
-
# 获取当前分支的提交总数
|
|
355
|
-
commit_count = subprocess.run(
|
|
356
|
-
['git', 'rev-list', '--count', 'HEAD'],
|
|
357
|
-
capture_output=True,
|
|
358
|
-
text=True
|
|
359
|
-
)
|
|
360
|
-
if commit_count.returncode != 0:
|
|
361
|
-
return False
|
|
362
|
-
|
|
363
|
-
commit_count = int(commit_count.stdout.strip())
|
|
364
|
-
|
|
365
|
-
# 暂存所有修改
|
|
366
|
-
subprocess.run(['git', 'add', '.'], check=True)
|
|
367
|
-
|
|
368
|
-
# 提交变更
|
|
369
|
-
subprocess.run(
|
|
370
|
-
['git', 'commit', '-m', f'CheckPoint #{commit_count + 1}'],
|
|
371
|
-
check=True
|
|
372
|
-
)
|
|
373
|
-
return True
|
|
374
|
-
except subprocess.CalledProcessError as e:
|
|
375
|
-
return False
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
def handle_code_operation(filepath: str, patch_content: str) -> bool:
|
|
379
|
-
"""处理代码操作"""
|
|
380
|
-
if get_file_line_count(filepath) < 5:
|
|
381
|
-
return handle_small_code_operation(filepath, patch_content)
|
|
382
|
-
else:
|
|
383
|
-
retry_count = 5
|
|
384
|
-
while retry_count > 0:
|
|
385
|
-
retry_count -= 1
|
|
386
|
-
if handle_large_code_operation(filepath, patch_content, PlatformRegistry().get_normal_platform() if retry_count > 2 else PlatformRegistry().get_thinking_platform()):
|
|
387
|
-
return True
|
|
388
|
-
return handle_small_code_operation(filepath, patch_content)
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
def handle_small_code_operation(filepath: str, patch_content: str) -> bool:
|
|
392
|
-
"""处理基于上下文的代码片段"""
|
|
393
|
-
with yaspin(text=f"正在修改文件 {filepath}...", color="cyan") as spinner:
|
|
394
|
-
try:
|
|
395
|
-
model = PlatformRegistry().get_normal_platform()
|
|
396
|
-
file_content = FileOperationTool().execute({"operation":"read", "files":[{"path":filepath}]})["stdout"]
|
|
397
|
-
|
|
398
|
-
model.set_suppress_output(False)
|
|
399
|
-
|
|
400
|
-
prompt = f"""
|
|
401
|
-
# 代码合并专家指南
|
|
402
|
-
|
|
403
|
-
## 任务描述
|
|
404
|
-
你是一位精确的代码审查与合并专家,需要将补丁内容与原始代码智能合并。
|
|
405
|
-
|
|
406
|
-
### 补丁内容
|
|
407
|
-
```
|
|
408
|
-
{patch_content}
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
## 合并要求
|
|
412
|
-
1. **精确性**:严格按照补丁的意图修改代码
|
|
413
|
-
2. **完整性**:确保所有需要的更改都被应用
|
|
414
|
-
3. **一致性**:严格保留原始代码的格式、空行和缩进风格
|
|
415
|
-
4. **上下文保留**:保持未修改部分的代码完全不变
|
|
416
|
-
|
|
417
|
-
## 输出格式规范
|
|
418
|
-
- 仅在{ot("MERGED_CODE")}标签内输出合并后的完整代码
|
|
419
|
-
- 每次最多输出300行代码
|
|
420
|
-
- 不要使用markdown代码块(```)或反引号,除非修改的是markdown文件
|
|
421
|
-
- 除了合并后的代码,不要输出任何其他文本
|
|
422
|
-
- 所有代码输出完成后,输出{ot("!!!FINISHED!!!")}标记
|
|
423
|
-
|
|
424
|
-
## 输出模板
|
|
425
|
-
{ot("MERGED_CODE")}
|
|
426
|
-
[合并后的完整代码,包括所有空行和缩进]
|
|
427
|
-
{ct("MERGED_CODE")}
|
|
428
|
-
|
|
429
|
-
# 原始代码
|
|
430
|
-
{file_content}
|
|
431
|
-
"""
|
|
432
|
-
|
|
433
|
-
count = 30
|
|
434
|
-
start_line = -1
|
|
435
|
-
end_line = -1
|
|
436
|
-
code = []
|
|
437
|
-
finished = False
|
|
438
|
-
while count > 0:
|
|
439
|
-
count -= 1
|
|
440
|
-
with spinner.hidden():
|
|
441
|
-
response = model.chat_until_success(prompt).splitlines()
|
|
442
|
-
try:
|
|
443
|
-
start_line = response.index(ot("MERGED_CODE")) + 1
|
|
444
|
-
try:
|
|
445
|
-
end_line = response.index(ct("MERGED_CODE"))
|
|
446
|
-
code = response[start_line:end_line]
|
|
447
|
-
except:
|
|
448
|
-
pass
|
|
449
|
-
except:
|
|
450
|
-
pass
|
|
451
|
-
|
|
452
|
-
try:
|
|
453
|
-
response.index(ot("!!!FINISHED!!!"))
|
|
454
|
-
finished = True
|
|
455
|
-
break
|
|
456
|
-
except:
|
|
457
|
-
prompt += f"""
|
|
458
|
-
# 继续输出
|
|
459
|
-
|
|
460
|
-
## 说明
|
|
461
|
-
请继续输出接下来的300行代码
|
|
462
|
-
|
|
463
|
-
## 要求
|
|
464
|
-
- 严格保留原始代码的格式、空行和缩进
|
|
465
|
-
- 仅在{ot("MERGED_CODE")}块中包含实际代码内容
|
|
466
|
-
- 不要使用markdown代码块(```)或反引号
|
|
467
|
-
- 除了合并后的代码,不要输出任何其他文本
|
|
468
|
-
- 所有代码输出完成后,输出{ot("!!!FINISHED!!!")}标记
|
|
469
|
-
"""
|
|
470
|
-
pass
|
|
471
|
-
if not finished:
|
|
472
|
-
spinner.text = "生成代码失败"
|
|
473
|
-
spinner.fail("❌")
|
|
474
|
-
return False
|
|
475
|
-
# 写入合并后的代码
|
|
476
|
-
spinner.text = "写入合并后的代码..."
|
|
477
|
-
with open(filepath, 'w', encoding='utf-8', errors="ignore") as f:
|
|
478
|
-
f.write("\n".join(code)+"\n")
|
|
479
|
-
spinner.write("✅ 合并后的代码写入完成")
|
|
480
|
-
spinner.text = "代码修改完成"
|
|
481
|
-
spinner.ok("✅")
|
|
482
|
-
return True
|
|
483
|
-
except Exception as e:
|
|
484
|
-
spinner.text = "代码修改失败"
|
|
485
|
-
spinner.fail("❌")
|
|
486
|
-
return False
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
def handle_large_code_operation(filepath: str, patch_content: str, model: BasePlatform) -> bool:
|
|
491
|
-
"""处理大型代码文件的补丁操作,使用差异化补丁格式"""
|
|
492
|
-
with yaspin(text=f"正在处理文件 {filepath}...", color="cyan") as spinner:
|
|
493
|
-
try:
|
|
494
|
-
file_content = FileOperationTool().execute({"operation":"read", "files":[{"path":filepath}]})["stdout"]
|
|
495
|
-
need_upload_file = is_context_overflow(file_content)
|
|
496
|
-
upload_success = False
|
|
497
|
-
# 读取原始文件内容
|
|
498
|
-
with spinner.hidden():
|
|
499
|
-
if need_upload_file and model.upload_files([filepath]):
|
|
500
|
-
upload_success = True
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
model.set_suppress_output(False)
|
|
504
|
-
|
|
505
|
-
main_prompt = f"""
|
|
506
|
-
# 代码补丁生成专家指南
|
|
507
|
-
|
|
508
|
-
## 任务描述
|
|
509
|
-
你是一位精确的代码补丁生成专家,需要根据补丁描述生成精确的代码差异。
|
|
510
|
-
|
|
511
|
-
### 补丁内容
|
|
512
|
-
```
|
|
513
|
-
{patch_content}
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
## 补丁生成要求
|
|
517
|
-
1. **精确性**:严格按照补丁的意图修改代码
|
|
518
|
-
2. **格式一致性**:严格保持原始代码的格式风格
|
|
519
|
-
- 缩进方式(空格或制表符)必须与原代码保持一致
|
|
520
|
-
- 空行数量和位置必须与原代码风格匹配
|
|
521
|
-
- 行尾空格处理必须与原代码一致
|
|
522
|
-
3. **最小化修改**:只修改必要的代码部分,保持其他部分不变
|
|
523
|
-
4. **上下文完整性**:提供足够的上下文,确保补丁能准确应用
|
|
524
|
-
|
|
525
|
-
## 输出格式规范
|
|
526
|
-
- 使用{ot("DIFF")}块包围每个需要修改的代码段
|
|
527
|
-
- 每个{ot("DIFF")}块必须包含SEARCH部分和REPLACE部分
|
|
528
|
-
- SEARCH部分是需要查找的原始代码
|
|
529
|
-
- REPLACE部分是替换后的新代码
|
|
530
|
-
- 确保SEARCH部分能在原文件中**唯一匹配**
|
|
531
|
-
- 如果修改较大,可以使用多个{ot("DIFF")}块
|
|
532
|
-
|
|
533
|
-
## 输出模板
|
|
534
|
-
{ot("DIFF")}
|
|
535
|
-
>>>>>> SEARCH
|
|
536
|
-
[需要查找的原始代码,包含足够上下文,避免出现可匹配多处的情况]
|
|
537
|
-
{'='*5}
|
|
538
|
-
[替换后的新代码]
|
|
539
|
-
<<<<<< REPLACE
|
|
540
|
-
{ct("DIFF")}
|
|
541
|
-
|
|
542
|
-
{ot("DIFF")}
|
|
543
|
-
>>>>>> SEARCH
|
|
544
|
-
[另一处需要查找的原始代码,包含足够上下文,避免出现可匹配多处的情况]
|
|
545
|
-
{'='*5}
|
|
546
|
-
[另一处替换后的新代码]
|
|
547
|
-
<<<<<< REPLACE
|
|
548
|
-
{ct("DIFF")}
|
|
549
|
-
"""
|
|
550
|
-
|
|
551
|
-
for _ in range(3):
|
|
552
|
-
file_prompt = ""
|
|
553
|
-
if not need_upload_file:
|
|
554
|
-
file_prompt = f"""
|
|
555
|
-
# 原始代码
|
|
556
|
-
{file_content}
|
|
557
|
-
"""
|
|
558
|
-
with spinner.hidden():
|
|
559
|
-
response = model.chat_until_success(main_prompt + file_prompt)
|
|
560
|
-
else:
|
|
561
|
-
if upload_success:
|
|
562
|
-
with spinner.hidden():
|
|
563
|
-
response = model.chat_until_success(main_prompt)
|
|
564
|
-
else:
|
|
565
|
-
with spinner.hidden():
|
|
566
|
-
response = model.chat_big_content(file_content, main_prompt)
|
|
567
|
-
|
|
568
|
-
# 解析差异化补丁
|
|
569
|
-
diff_blocks = re.finditer(ot("DIFF")+r'\s*>{4,} SEARCH\n?(.*?)\n?={4,}\n?(.*?)\s*<{4,} REPLACE\n?'+ct("DIFF"),
|
|
570
|
-
response, re.DOTALL)
|
|
571
|
-
|
|
572
|
-
# 读取原始文件内容
|
|
573
|
-
with open(filepath, 'r', encoding='utf-8', errors="ignore") as f:
|
|
574
|
-
file_content = f.read()
|
|
575
|
-
|
|
576
|
-
# 应用所有差异化补丁
|
|
577
|
-
modified_content = file_content
|
|
578
|
-
patch_count = 0
|
|
579
|
-
success = True
|
|
580
|
-
for match in diff_blocks:
|
|
581
|
-
search_text = match.group(1).strip()
|
|
582
|
-
replace_text = match.group(2).strip()
|
|
583
|
-
patch_count += 1
|
|
584
|
-
# 检查搜索文本是否存在于文件中
|
|
585
|
-
if search_text in modified_content:
|
|
586
|
-
# 如果有多处,报错
|
|
587
|
-
if modified_content.count(search_text) > 1:
|
|
588
|
-
spinner.write(f"❌ 补丁 #{patch_count} 应用失败:找到多个匹配的代码段")
|
|
589
|
-
success = False
|
|
590
|
-
break
|
|
591
|
-
# 应用替换
|
|
592
|
-
modified_content = modified_content.replace(
|
|
593
|
-
search_text, replace_text)
|
|
594
|
-
spinner.write(f"✅ 补丁 #{patch_count} 应用成功")
|
|
595
|
-
else:
|
|
596
|
-
spinner.write(f"❌ 补丁 #{patch_count} 应用失败:无法找到匹配的代码段")
|
|
597
|
-
success = False
|
|
598
|
-
break
|
|
599
|
-
if not success:
|
|
600
|
-
revert_file(filepath)
|
|
601
|
-
continue
|
|
602
|
-
|
|
603
|
-
# 写入修改后的内容
|
|
604
|
-
with open(filepath, 'w', encoding='utf-8', errors="ignore") as f:
|
|
605
|
-
f.write(modified_content)
|
|
606
|
-
|
|
607
|
-
spinner.text = f"文件 {filepath} 修改完成,应用了 {patch_count} 个补丁"
|
|
608
|
-
spinner.ok("✅")
|
|
609
|
-
return True
|
|
610
|
-
spinner.text = f"文件 {filepath} 修改失败"
|
|
611
|
-
spinner.fail("❌")
|
|
612
|
-
return False
|
|
613
|
-
|
|
614
|
-
except Exception as e:
|
|
615
|
-
spinner.text = f"文件修改失败: {str(e)}"
|
|
616
|
-
spinner.fail("❌")
|
|
617
|
-
return False
|
{jarvis_ai_assistant-0.1.161.dist-info → jarvis_ai_assistant-0.1.163.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{jarvis_ai_assistant-0.1.161.dist-info → jarvis_ai_assistant-0.1.163.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{jarvis_ai_assistant-0.1.161.dist-info → jarvis_ai_assistant-0.1.163.dist-info}/top_level.txt
RENAMED
|
File without changes
|