jarvis-ai-assistant 0.1.124__py3-none-any.whl → 0.1.126__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 +134 -136
- jarvis/jarvis_code_agent/code_agent.py +198 -52
- jarvis/jarvis_code_agent/file_select.py +6 -19
- jarvis/jarvis_code_agent/patch.py +183 -312
- jarvis/jarvis_code_agent/shell_input_handler.py +22 -0
- jarvis/jarvis_codebase/main.py +89 -86
- jarvis/jarvis_dev/main.py +695 -715
- jarvis/jarvis_git_squash/__init__.py +0 -0
- jarvis/jarvis_git_squash/main.py +81 -0
- jarvis/jarvis_lsp/base.py +0 -12
- jarvis/jarvis_lsp/cpp.py +1 -10
- jarvis/jarvis_lsp/go.py +1 -10
- jarvis/jarvis_lsp/python.py +0 -28
- jarvis/jarvis_lsp/registry.py +2 -3
- jarvis/jarvis_lsp/rust.py +1 -10
- jarvis/jarvis_multi_agent/__init__.py +53 -53
- jarvis/jarvis_platform/ai8.py +2 -1
- jarvis/jarvis_platform/base.py +19 -24
- jarvis/jarvis_platform/kimi.py +2 -3
- jarvis/jarvis_platform/ollama.py +3 -1
- jarvis/jarvis_platform/openai.py +1 -1
- jarvis/jarvis_platform/oyi.py +2 -1
- jarvis/jarvis_platform/registry.py +2 -1
- jarvis/jarvis_platform_manager/main.py +4 -6
- jarvis/jarvis_platform_manager/openai_test.py +0 -1
- jarvis/jarvis_rag/main.py +5 -2
- jarvis/jarvis_smart_shell/main.py +9 -4
- jarvis/jarvis_tools/ask_codebase.py +18 -13
- jarvis/jarvis_tools/ask_user.py +5 -4
- jarvis/jarvis_tools/base.py +22 -8
- jarvis/jarvis_tools/chdir.py +8 -9
- jarvis/jarvis_tools/code_review.py +19 -20
- jarvis/jarvis_tools/create_code_agent.py +6 -6
- jarvis/jarvis_tools/create_sub_agent.py +9 -9
- jarvis/jarvis_tools/execute_shell.py +55 -20
- jarvis/jarvis_tools/execute_shell_script.py +7 -7
- jarvis/jarvis_tools/file_operation.py +39 -10
- jarvis/jarvis_tools/git_commiter.py +20 -17
- jarvis/jarvis_tools/lsp_find_definition.py +8 -8
- jarvis/jarvis_tools/lsp_find_references.py +1 -1
- jarvis/jarvis_tools/lsp_get_diagnostics.py +19 -11
- jarvis/jarvis_tools/lsp_get_document_symbols.py +1 -1
- jarvis/jarvis_tools/lsp_prepare_rename.py +8 -8
- jarvis/jarvis_tools/methodology.py +10 -7
- jarvis/jarvis_tools/rag.py +27 -20
- jarvis/jarvis_tools/read_webpage.py +4 -3
- jarvis/jarvis_tools/registry.py +143 -140
- jarvis/jarvis_tools/{search.py → search_web.py} +10 -7
- jarvis/jarvis_tools/select_code_files.py +4 -4
- jarvis/jarvis_tools/tool_generator.py +33 -34
- jarvis/jarvis_utils/__init__.py +19 -982
- jarvis/jarvis_utils/config.py +138 -0
- jarvis/jarvis_utils/embedding.py +201 -0
- jarvis/jarvis_utils/git_utils.py +120 -0
- jarvis/jarvis_utils/globals.py +82 -0
- jarvis/jarvis_utils/input.py +161 -0
- jarvis/jarvis_utils/methodology.py +128 -0
- jarvis/jarvis_utils/output.py +235 -0
- jarvis/jarvis_utils/utils.py +150 -0
- jarvis_ai_assistant-0.1.126.dist-info/METADATA +305 -0
- jarvis_ai_assistant-0.1.126.dist-info/RECORD +74 -0
- {jarvis_ai_assistant-0.1.124.dist-info → jarvis_ai_assistant-0.1.126.dist-info}/WHEEL +1 -1
- {jarvis_ai_assistant-0.1.124.dist-info → jarvis_ai_assistant-0.1.126.dist-info}/entry_points.txt +1 -0
- jarvis/jarvis_tools/lsp_validate_edit.py +0 -141
- jarvis/jarvis_tools/read_code.py +0 -191
- jarvis_ai_assistant-0.1.124.dist-info/METADATA +0 -460
- jarvis_ai_assistant-0.1.124.dist-info/RECORD +0 -65
- {jarvis_ai_assistant-0.1.124.dist-info → jarvis_ai_assistant-0.1.126.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.124.dist-info → jarvis_ai_assistant-0.1.126.dist-info}/top_level.txt +0 -0
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import re
|
|
2
|
-
from typing import Dict, Any,
|
|
2
|
+
from typing import Dict, Any, Tuple
|
|
3
3
|
import os
|
|
4
|
+
|
|
4
5
|
from jarvis.jarvis_agent.output_handler import OutputHandler
|
|
6
|
+
from jarvis.jarvis_platform.registry import PlatformRegistry
|
|
5
7
|
from jarvis.jarvis_tools.git_commiter import GitCommitTool
|
|
6
|
-
from jarvis.jarvis_tools.
|
|
7
|
-
from jarvis.
|
|
8
|
-
|
|
8
|
+
from jarvis.jarvis_tools.execute_shell_script import ShellScriptTool
|
|
9
|
+
from jarvis.jarvis_tools.file_operation import FileOperationTool
|
|
10
|
+
from jarvis.jarvis_utils.config import is_confirm_before_apply_patch
|
|
11
|
+
from jarvis.jarvis_utils.git_utils import get_commits_between, get_latest_commit_hash
|
|
12
|
+
from jarvis.jarvis_utils.input import get_multiline_input
|
|
13
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
14
|
+
from jarvis.jarvis_utils.utils import user_confirm
|
|
9
15
|
|
|
10
16
|
class PatchOutputHandler(OutputHandler):
|
|
11
17
|
def name(self) -> str:
|
|
12
18
|
return "PATCH"
|
|
13
|
-
|
|
14
19
|
def handle(self, response: str) -> Tuple[bool, Any]:
|
|
15
20
|
return False, apply_patch(response)
|
|
16
21
|
|
|
@@ -21,94 +26,51 @@ class PatchOutputHandler(OutputHandler):
|
|
|
21
26
|
|
|
22
27
|
def prompt(self) -> str:
|
|
23
28
|
return """
|
|
24
|
-
# 🛠️
|
|
25
|
-
|
|
26
|
-
You can output multiple patches, each patch is a <PATCH> block.
|
|
29
|
+
# 🛠️ 上下文代码补丁规范
|
|
30
|
+
使用<PATCH>块来指定代码更改:
|
|
27
31
|
--------------------------------
|
|
28
|
-
# [OPERATION] on [FILE]
|
|
29
|
-
# Start Line: [START_LINE], End Line: [END_LINE] [include/exclude], I can verify the line number range is correct
|
|
30
|
-
# Reason: [CLEAR EXPLANATION]
|
|
31
32
|
<PATCH>
|
|
32
|
-
|
|
33
|
-
[
|
|
33
|
+
File: [文件路径]
|
|
34
|
+
Reason: [修改原因]
|
|
35
|
+
[上下文代码片段]
|
|
34
36
|
</PATCH>
|
|
35
37
|
--------------------------------
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
38
|
+
规则:
|
|
39
|
+
1. 代码片段必须包含足够的上下文(前后各3行)
|
|
40
|
+
2. 我可以看到完整代码,所以只需显示修改的代码部分
|
|
41
|
+
3. 保留原始缩进和格式
|
|
42
|
+
4. 对于新文件,提供完整代码
|
|
43
|
+
5. 修改现有文件时,保留周围未更改的代码
|
|
44
|
+
示例:
|
|
45
|
+
<PATCH>
|
|
46
|
+
File: src/utils/math.py
|
|
47
|
+
Reason: 修复除零处理
|
|
48
|
+
def safe_divide(a, b):
|
|
49
|
+
# 添加参数验证
|
|
50
|
+
if b == 0:
|
|
51
|
+
raise ValueError("除数不能为零")
|
|
52
|
+
return a / b
|
|
53
|
+
# 现有代码 ...
|
|
54
|
+
def add(a, b):
|
|
55
|
+
return a + b
|
|
56
|
+
</PATCH>
|
|
54
57
|
"""
|
|
55
58
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
"""解析补丁格式"""
|
|
59
|
+
def _parse_patch(patch_str: str) -> Dict[str, str]:
|
|
60
|
+
"""解析新的上下文补丁格式"""
|
|
59
61
|
result = {}
|
|
60
|
-
# 更新正则表达式以更好地处理文件路径和范围
|
|
61
|
-
header_pattern = re.compile(
|
|
62
|
-
r'^\s*"?([^\n\r\[]+)"?\s*\[(\d+)(?:,(\d+))?([\]\)])\s*$', # 匹配文件路径和行号
|
|
63
|
-
re.ASCII
|
|
64
|
-
)
|
|
65
62
|
patches = re.findall(r'<PATCH>\n?(.*?)\n?</PATCH>', patch_str, re.DOTALL)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
content += '\n'
|
|
76
|
-
|
|
77
|
-
# 解析文件路径和行号
|
|
78
|
-
header_match = header_pattern.match(header_line)
|
|
79
|
-
if not header_match:
|
|
80
|
-
PrettyOutput.print(f"无法解析补丁头: {header_line}", OutputType.WARNING)
|
|
81
|
-
continue
|
|
82
|
-
|
|
83
|
-
filepath = header_match.group(1).strip()
|
|
84
|
-
|
|
85
|
-
try:
|
|
86
|
-
start = int(header_match.group(2)) # 保持1-based行号
|
|
87
|
-
end = int(header_match.group(3)) if header_match.group(3) else start
|
|
88
|
-
range_type = header_match.group(4) # ] 或 ) 表示范围类型
|
|
89
|
-
except (ValueError, IndexError) as e:
|
|
90
|
-
PrettyOutput.print(f"解析行号失败: {str(e)}", OutputType.WARNING)
|
|
91
|
-
continue
|
|
92
|
-
|
|
93
|
-
# 根据范围类型调整结束行号
|
|
94
|
-
if range_type == ')': # 对于 [m,n) 格式,不包括第n行
|
|
95
|
-
end = end
|
|
96
|
-
else: # 对于 [m,n] 格式,包括第n行
|
|
97
|
-
end = end + 1
|
|
98
|
-
|
|
99
|
-
if filepath not in result:
|
|
100
|
-
result[filepath] = []
|
|
101
|
-
result[filepath].append({
|
|
102
|
-
'filepath': filepath,
|
|
103
|
-
'start': start,
|
|
104
|
-
'end': end,
|
|
105
|
-
'content': content
|
|
106
|
-
})
|
|
107
|
-
for filepath in result.keys():
|
|
108
|
-
result[filepath] = sorted(result[filepath], key=lambda x: x['start'], reverse=True)
|
|
63
|
+
if patches:
|
|
64
|
+
for patch in patches:
|
|
65
|
+
first_line = patch.splitlines()[0]
|
|
66
|
+
sm = re.match(r'^File:\s*(.+)$', first_line)
|
|
67
|
+
if not sm:
|
|
68
|
+
PrettyOutput.print("无效的补丁格式", OutputType.WARNING)
|
|
69
|
+
continue
|
|
70
|
+
filepath = sm.group(1).strip()
|
|
71
|
+
result[filepath] = patch
|
|
109
72
|
return result
|
|
110
73
|
|
|
111
|
-
|
|
112
74
|
def apply_patch(output_str: str) -> str:
|
|
113
75
|
"""Apply patches to files"""
|
|
114
76
|
try:
|
|
@@ -116,262 +78,171 @@ def apply_patch(output_str: str) -> str:
|
|
|
116
78
|
except Exception as e:
|
|
117
79
|
PrettyOutput.print(f"解析补丁失败: {str(e)}", OutputType.ERROR)
|
|
118
80
|
return ""
|
|
119
|
-
|
|
120
|
-
ret = ""
|
|
121
81
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
try:
|
|
125
|
-
err = handle_code_operation(filepath, patch)
|
|
126
|
-
if err:
|
|
127
|
-
PrettyOutput.print(err, OutputType.WARNING)
|
|
128
|
-
revert_change()
|
|
129
|
-
return err
|
|
130
|
-
PrettyOutput.print(f"成功为文件{filepath}应用补丁{i+1}/{len(patch_list)}", OutputType.SUCCESS)
|
|
131
|
-
except Exception as e:
|
|
132
|
-
PrettyOutput.print(f"操作失败: {str(e)}", OutputType.ERROR)
|
|
82
|
+
# 获取当前提交hash作为起始点
|
|
83
|
+
start_hash = get_latest_commit_hash()
|
|
133
84
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
85
|
+
# 按文件逐个处理
|
|
86
|
+
for filepath, patch_content in patches.items():
|
|
87
|
+
try:
|
|
88
|
+
handle_code_operation(filepath, patch_content)
|
|
89
|
+
PrettyOutput.print(f"文件 {filepath} 处理完成", OutputType.SUCCESS)
|
|
90
|
+
except Exception as e:
|
|
91
|
+
revert_file(filepath) # 回滚单个文件
|
|
92
|
+
PrettyOutput.print(f"文件 {filepath} 处理失败: {str(e)}", OutputType.ERROR)
|
|
93
|
+
|
|
94
|
+
final_ret = ""
|
|
95
|
+
diff = get_diff()
|
|
96
|
+
if diff:
|
|
97
|
+
PrettyOutput.print(diff, OutputType.CODE, lang="diff")
|
|
98
|
+
if handle_commit_workflow():
|
|
99
|
+
# 获取提交信息
|
|
100
|
+
end_hash = get_latest_commit_hash()
|
|
101
|
+
commits = get_commits_between(start_hash, end_hash)
|
|
102
|
+
|
|
103
|
+
# 添加提交信息到final_ret
|
|
104
|
+
if commits:
|
|
105
|
+
final_ret += "✅ 补丁已应用\n"
|
|
106
|
+
final_ret += "提交信息:\n"
|
|
107
|
+
for commit_hash, commit_message in commits:
|
|
108
|
+
final_ret += f"- {commit_hash[:7]}: {commit_message}\n"
|
|
109
|
+
|
|
110
|
+
final_ret += f"应用补丁后的代码:\n{diff}"
|
|
111
|
+
|
|
112
|
+
else:
|
|
113
|
+
final_ret += "✅ 补丁已应用(没有新的提交)"
|
|
144
114
|
else:
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
115
|
+
final_ret += "❌ 我不想提交代码\n"
|
|
116
|
+
final_ret += "之前的代码:\n"
|
|
117
|
+
final_ret += diff
|
|
118
|
+
else:
|
|
119
|
+
final_ret += "❌ 没有要提交的更改\n"
|
|
120
|
+
# 用户确认最终结果
|
|
121
|
+
PrettyOutput.print(final_ret, OutputType.USER)
|
|
122
|
+
if not is_confirm_before_apply_patch() or user_confirm("是否使用此回复?", default=True):
|
|
123
|
+
return final_ret
|
|
124
|
+
return get_multiline_input("请输入自定义回复")
|
|
125
|
+
def revert_file(filepath: str):
|
|
126
|
+
"""增强版git恢复,处理新文件"""
|
|
127
|
+
import subprocess
|
|
128
|
+
try:
|
|
129
|
+
# 检查文件是否在版本控制中
|
|
130
|
+
result = subprocess.run(
|
|
131
|
+
['git', 'ls-files', '--error-unmatch', filepath],
|
|
132
|
+
stderr=subprocess.PIPE
|
|
133
|
+
)
|
|
134
|
+
if result.returncode == 0:
|
|
135
|
+
subprocess.run(['git', 'checkout', 'HEAD', '--', filepath], check=True)
|
|
150
136
|
else:
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
137
|
+
if os.path.exists(filepath):
|
|
138
|
+
os.remove(filepath)
|
|
139
|
+
subprocess.run(['git', 'clean', '-f', '--', filepath], check=True)
|
|
140
|
+
except subprocess.CalledProcessError as e:
|
|
141
|
+
PrettyOutput.print(f"恢复文件失败: {str(e)}", OutputType.ERROR)
|
|
142
|
+
# 修改后的恢复函数
|
|
143
|
+
def revert_change():
|
|
144
|
+
import subprocess
|
|
145
|
+
subprocess.run(['git', 'reset', '--hard', 'HEAD'], check=True)
|
|
146
|
+
subprocess.run(['git', 'clean', '-fd'], check=True)
|
|
147
|
+
# 修改后的获取差异函数
|
|
155
148
|
def get_diff() -> str:
|
|
156
|
-
"""
|
|
149
|
+
"""使用git获取暂存区差异"""
|
|
157
150
|
import subprocess
|
|
158
151
|
try:
|
|
159
152
|
subprocess.run(['git', 'add', '.'], check=True)
|
|
160
153
|
result = subprocess.run(
|
|
161
|
-
['git', 'diff', '
|
|
154
|
+
['git', 'diff', '--cached'],
|
|
162
155
|
capture_output=True,
|
|
163
156
|
text=True,
|
|
164
157
|
check=True
|
|
165
158
|
)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
subprocess.run(['git', 'reset', 'HEAD'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
173
|
-
subprocess.run(['git', 'checkout', '--', '.'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
174
|
-
subprocess.run(['git', 'clean', '-fd'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
175
|
-
|
|
176
|
-
def handle_commit_workflow(diff:str)->bool:
|
|
159
|
+
ret = result.stdout
|
|
160
|
+
subprocess.run(['git', "reset", "--soft", "HEAD"], check=True)
|
|
161
|
+
return ret
|
|
162
|
+
except subprocess.CalledProcessError as e:
|
|
163
|
+
return f"获取差异失败: {str(e)}"
|
|
164
|
+
def handle_commit_workflow()->bool:
|
|
177
165
|
"""Handle the git commit workflow and return the commit details.
|
|
178
166
|
|
|
179
167
|
Returns:
|
|
180
168
|
tuple[bool, str, str]: (continue_execution, commit_id, commit_message)
|
|
181
169
|
"""
|
|
182
|
-
if not user_confirm("是否要提交代码?", default=True):
|
|
170
|
+
if is_confirm_before_apply_patch() and not user_confirm("是否要提交代码?", default=True):
|
|
183
171
|
revert_change()
|
|
184
172
|
return False
|
|
185
|
-
|
|
186
173
|
git_commiter = GitCommitTool()
|
|
187
174
|
commit_result = git_commiter.execute({})
|
|
188
175
|
return commit_result["success"]
|
|
189
176
|
|
|
190
|
-
def get_modified_line_ranges() -> Dict[str, Tuple[int, int]]:
|
|
191
|
-
"""Get modified line ranges from git diff for all changed files.
|
|
192
|
-
|
|
193
|
-
Returns:
|
|
194
|
-
Dictionary mapping file paths to tuple with (start_line, end_line) ranges
|
|
195
|
-
for modified sections. Line numbers are 1-based.
|
|
196
|
-
"""
|
|
197
|
-
# Get git diff for all files
|
|
198
|
-
diff_output = os.popen("git show").read()
|
|
199
|
-
|
|
200
|
-
# Parse the diff to get modified files and their line ranges
|
|
201
|
-
result = {}
|
|
202
|
-
current_file = None
|
|
203
|
-
|
|
204
|
-
for line in diff_output.splitlines():
|
|
205
|
-
# Match lines like "+++ b/path/to/file"
|
|
206
|
-
file_match = re.match(r"^\+\+\+ b/(.*)", line)
|
|
207
|
-
if file_match:
|
|
208
|
-
current_file = file_match.group(1)
|
|
209
|
-
continue
|
|
210
|
-
|
|
211
|
-
# Match lines like "@@ -100,5 +100,7 @@" where the + part shows new lines
|
|
212
|
-
range_match = re.match(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@", line)
|
|
213
|
-
if range_match and current_file:
|
|
214
|
-
start_line = int(range_match.group(1)) # Keep as 1-based
|
|
215
|
-
line_count = int(range_match.group(2)) if range_match.group(2) else 1
|
|
216
|
-
end_line = start_line + line_count - 1
|
|
217
|
-
result[current_file] = (start_line, end_line)
|
|
218
|
-
|
|
219
|
-
return result
|
|
220
177
|
# New handler functions below ▼▼▼
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
"""统一参数格式处理新文件"""
|
|
224
|
-
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
|
225
|
-
with open(filepath, 'w', encoding='utf-8') as f:
|
|
226
|
-
f.write(patch['content'])
|
|
227
|
-
|
|
228
|
-
def handle_code_operation(filepath: str, patch: Dict[str, Any]) -> str:
|
|
229
|
-
"""处理紧凑格式补丁"""
|
|
178
|
+
def handle_code_operation(filepath: str, patch_content: str) -> str:
|
|
179
|
+
"""处理基于上下文的代码片段"""
|
|
230
180
|
try:
|
|
231
|
-
# 新建文件时强制覆盖
|
|
232
|
-
os.makedirs(os.path.dirname(filepath) or '.', exist_ok=True)
|
|
233
181
|
if not os.path.exists(filepath):
|
|
182
|
+
# 新建文件
|
|
183
|
+
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
|
234
184
|
open(filepath, 'w', encoding='utf-8').close()
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
new_lines = validate_and_apply_changes(
|
|
239
|
-
lines,
|
|
240
|
-
patch['start'],
|
|
241
|
-
patch['end'],
|
|
242
|
-
patch['content']
|
|
243
|
-
)
|
|
244
|
-
|
|
245
|
-
f.seek(0)
|
|
246
|
-
f.writelines(new_lines)
|
|
247
|
-
f.truncate()
|
|
248
|
-
PrettyOutput.print(f"成功更新 {filepath}", OutputType.SUCCESS)
|
|
249
|
-
return ""
|
|
250
|
-
except Exception as e:
|
|
251
|
-
error_msg = f"Failed to handle code operation: {str(e)}"
|
|
252
|
-
PrettyOutput.print(error_msg, OutputType.ERROR)
|
|
253
|
-
return error_msg
|
|
254
|
-
def validate_and_apply_changes(
|
|
255
|
-
lines: List[str],
|
|
256
|
-
start: int,
|
|
257
|
-
end: int,
|
|
258
|
-
content: str
|
|
259
|
-
) -> List[str]:
|
|
260
|
-
new_content = content.splitlines(keepends=True)
|
|
261
|
-
|
|
262
|
-
# 插入操作处理
|
|
263
|
-
if start == end:
|
|
264
|
-
if start < 1 or start > len(lines)+1:
|
|
265
|
-
raise ValueError(f"无效插入位置: {start}")
|
|
266
|
-
return lines[:start-1] + new_content + lines[start-1:]
|
|
267
|
-
|
|
268
|
-
# 范围替换/删除操作
|
|
269
|
-
if start > end:
|
|
270
|
-
raise ValueError(f"起始行{start}不能大于结束行{end}")
|
|
271
|
-
|
|
272
|
-
max_line = len(lines)
|
|
273
|
-
# 自动修正行号范围
|
|
274
|
-
start = max(1, min(start, max_line+1))
|
|
275
|
-
end = max(start, min(end, max_line+1))
|
|
276
|
-
|
|
277
|
-
# 执行替换
|
|
278
|
-
return lines[:start-1] + new_content + lines[end-1:]
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
def file_input_handler(user_input: str, agent: Any) -> str:
|
|
282
|
-
"""Handle file input with optional line ranges.
|
|
283
|
-
|
|
284
|
-
Args:
|
|
285
|
-
user_input: User input string containing file references
|
|
286
|
-
agent: Agent instance (unused in current implementation)
|
|
185
|
+
old_file_content = FileOperationTool().execute({"operation": "read", "files": [{"path": filepath}]})
|
|
186
|
+
if not old_file_content["success"]:
|
|
187
|
+
return f"文件读取失败: {old_file_content['stderr']}"
|
|
287
188
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
# Handle special values and Python-style negative indices
|
|
309
|
-
try:
|
|
310
|
-
with open(file_path, 'r', encoding='utf-8') as f:
|
|
311
|
-
total_lines = len(f.readlines())
|
|
312
|
-
except FileNotFoundError:
|
|
313
|
-
PrettyOutput.print(f"文件不存在: {file_path}", OutputType.WARNING)
|
|
314
|
-
continue
|
|
315
|
-
# Process start line
|
|
316
|
-
if raw_start == 0: # 0表示整个文件
|
|
317
|
-
start_line = 1
|
|
318
|
-
end_line = total_lines
|
|
319
|
-
else:
|
|
320
|
-
start_line = raw_start if raw_start > 0 else total_lines + raw_start + 1
|
|
321
|
-
|
|
322
|
-
# Process end line
|
|
323
|
-
if raw_end == 0: # 0表示整个文件(如果start也是0)
|
|
324
|
-
end_line = total_lines
|
|
325
|
-
else:
|
|
326
|
-
end_line = raw_end if raw_end > 0 else total_lines + raw_end + 1
|
|
327
|
-
|
|
328
|
-
# Auto-correct ranges
|
|
329
|
-
start_line = max(1, min(start_line, total_lines))
|
|
330
|
-
end_line = max(start_line, min(end_line, total_lines))
|
|
331
|
-
|
|
332
|
-
# Final validation
|
|
333
|
-
if start_line < 1 or end_line > total_lines or start_line > end_line:
|
|
334
|
-
raise ValueError
|
|
335
|
-
|
|
336
|
-
except:
|
|
337
|
-
continue
|
|
338
|
-
|
|
339
|
-
# Add file if it exists
|
|
340
|
-
if os.path.isfile(file_path):
|
|
341
|
-
files.append({
|
|
342
|
-
"path": file_path,
|
|
343
|
-
"start_line": start_line,
|
|
344
|
-
"end_line": end_line
|
|
345
|
-
})
|
|
346
|
-
else:
|
|
347
|
-
# Handle simple file path
|
|
348
|
-
if os.path.isfile(ref):
|
|
349
|
-
files.append({
|
|
350
|
-
"path": ref,
|
|
351
|
-
"start_line": 1, # 1-based
|
|
352
|
-
"end_line": -1
|
|
353
|
-
})
|
|
354
|
-
|
|
355
|
-
# Read and process files if any were found
|
|
356
|
-
if files:
|
|
357
|
-
result = ReadCodeTool().execute({"files": files})
|
|
358
|
-
if result["success"]:
|
|
359
|
-
return result["stdout"] + "\n" + prompt
|
|
360
|
-
|
|
361
|
-
return prompt + """
|
|
362
|
-
==================================================================
|
|
363
|
-
Patch Line Number Range Rules:
|
|
364
|
-
- INSERT: [m,m)
|
|
365
|
-
- REPLACE: [m,n] n>=m
|
|
366
|
-
- DELETE: [m,n] n>=m
|
|
367
|
-
- NEW_FILE: [1,1)
|
|
368
|
-
|
|
369
|
-
Critical Rules:
|
|
370
|
-
- NEVER include unchanged code in patch content
|
|
371
|
-
- ONLY show lines that are being modified/added
|
|
372
|
-
- Maintain original line breaks around modified sections
|
|
373
|
-
- Preserve surrounding comments unless explicitly modifying them
|
|
374
|
-
- Verify line number range is correct
|
|
375
|
-
- Verify indentation is correct
|
|
376
|
-
==================================================================
|
|
189
|
+
prompt = f"""
|
|
190
|
+
你是一个代码审查员,请审查以下代码并将其与上下文合并。
|
|
191
|
+
原始代码:
|
|
192
|
+
{old_file_content["stdout"]}
|
|
193
|
+
补丁内容:
|
|
194
|
+
{patch_content}
|
|
195
|
+
"""
|
|
196
|
+
prompt += f"""
|
|
197
|
+
请将代码与上下文合并并返回完整的合并代码。
|
|
198
|
+
|
|
199
|
+
要求:
|
|
200
|
+
1. 严格保留原始代码的格式、空行和缩进
|
|
201
|
+
2. 仅在<MERGED_CODE>块中包含实际代码内容,包括空行和缩进
|
|
202
|
+
3. 绝对不要使用markdown代码块(```)或反引号,除非修改的是markdown文件
|
|
203
|
+
4. 除了合并后的代码,不要输出任何其他文本
|
|
204
|
+
|
|
205
|
+
输出格式:
|
|
206
|
+
<MERGED_CODE>
|
|
207
|
+
[merged_code]
|
|
208
|
+
</MERGED_CODE>
|
|
377
209
|
"""
|
|
210
|
+
model = PlatformRegistry().get_codegen_platform()
|
|
211
|
+
model.set_suppress_output(False)
|
|
212
|
+
count = 5
|
|
213
|
+
start_line = -1
|
|
214
|
+
end_line = -1
|
|
215
|
+
response = []
|
|
216
|
+
while count > 0:
|
|
217
|
+
count -= 1
|
|
218
|
+
response.extend(model.chat_until_success(prompt).splitlines())
|
|
219
|
+
try:
|
|
220
|
+
start_line = response.index("<MERGED_CODE>") + 1
|
|
221
|
+
except:
|
|
222
|
+
pass
|
|
223
|
+
try:
|
|
224
|
+
end_line = response.index("</MERGED_CODE>")
|
|
225
|
+
except:
|
|
226
|
+
pass
|
|
227
|
+
if start_line == -1:
|
|
228
|
+
PrettyOutput.print(f"❌ 为文件 {filepath} 应用补丁失败", OutputType.WARNING)
|
|
229
|
+
return f"代码合并失败"
|
|
230
|
+
if end_line == -1:
|
|
231
|
+
last_line = response[-1]
|
|
232
|
+
prompt = f"""
|
|
233
|
+
继续从最后一行开始(不要包含<MERGED_CODE>标签,完成后输出</MERGED_CODE>标签):
|
|
234
|
+
{last_line}
|
|
235
|
+
"""
|
|
236
|
+
response.pop() # 删除最后一行
|
|
237
|
+
continue
|
|
238
|
+
if end_line < start_line:
|
|
239
|
+
PrettyOutput.print(f"❌ 为文件 {filepath} 应用补丁失败", OutputType.WARNING)
|
|
240
|
+
return f"代码合并失败"
|
|
241
|
+
break
|
|
242
|
+
# 写入合并后的代码
|
|
243
|
+
with open(filepath, 'w', encoding='utf-8') as f:
|
|
244
|
+
f.write("\n".join(response[start_line:end_line])+"\n")
|
|
245
|
+
PrettyOutput.print(f"✅ 为文件 {filepath} 应用补丁成功", OutputType.SUCCESS)
|
|
246
|
+
return ""
|
|
247
|
+
except Exception as e:
|
|
248
|
+
return f"文件操作失败: {str(e)}"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
from typing import Any, Tuple
|
|
4
|
+
|
|
5
|
+
from jarvis.jarvis_tools.execute_shell_script import ShellScriptTool
|
|
6
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
7
|
+
from jarvis.jarvis_utils.utils import user_confirm
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def shell_input_handler(user_input: str, agent: Any) -> Tuple[str, bool]:
|
|
11
|
+
lines = user_input.splitlines()
|
|
12
|
+
cmdline = [line for line in lines if line.startswith("!")]
|
|
13
|
+
if len(cmdline) == 0:
|
|
14
|
+
return user_input, False
|
|
15
|
+
else:
|
|
16
|
+
script = '\n'.join([c[1:] for c in cmdline])
|
|
17
|
+
PrettyOutput.print(script, OutputType.CODE, lang="bash")
|
|
18
|
+
if user_confirm(f"是否要执行以上shell脚本?", default=True):
|
|
19
|
+
ShellScriptTool().execute({"script_content": script})
|
|
20
|
+
return "", True
|
|
21
|
+
return user_input, False
|
|
22
|
+
|