jarvis-ai-assistant 0.1.124__py3-none-any.whl → 0.1.125__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 +18 -20
- jarvis/jarvis_code_agent/code_agent.py +195 -45
- jarvis/jarvis_code_agent/file_select.py +6 -19
- jarvis/jarvis_code_agent/patch.py +189 -310
- jarvis/jarvis_codebase/main.py +6 -2
- jarvis/jarvis_dev/main.py +6 -4
- jarvis/jarvis_git_squash/__init__.py +0 -0
- jarvis/jarvis_git_squash/main.py +81 -0
- jarvis/jarvis_lsp/cpp.py +1 -1
- jarvis/jarvis_lsp/go.py +1 -1
- jarvis/jarvis_lsp/registry.py +2 -2
- jarvis/jarvis_lsp/rust.py +1 -1
- jarvis/jarvis_multi_agent/__init__.py +1 -1
- 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 +12 -7
- jarvis/jarvis_tools/ask_user.py +3 -2
- jarvis/jarvis_tools/base.py +21 -7
- jarvis/jarvis_tools/chdir.py +0 -1
- jarvis/jarvis_tools/code_review.py +13 -14
- jarvis/jarvis_tools/create_code_agent.py +2 -2
- jarvis/jarvis_tools/create_sub_agent.py +2 -2
- jarvis/jarvis_tools/execute_shell.py +3 -1
- jarvis/jarvis_tools/execute_shell_script.py +4 -4
- jarvis/jarvis_tools/file_operation.py +3 -2
- jarvis/jarvis_tools/git_commiter.py +5 -2
- jarvis/jarvis_tools/lsp_find_definition.py +1 -1
- 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 +1 -1
- jarvis/jarvis_tools/lsp_validate_edit.py +1 -1
- jarvis/jarvis_tools/methodology.py +4 -1
- jarvis/jarvis_tools/rag.py +22 -15
- jarvis/jarvis_tools/read_code.py +4 -3
- jarvis/jarvis_tools/read_webpage.py +2 -1
- jarvis/jarvis_tools/registry.py +4 -1
- jarvis/jarvis_tools/{search.py → search_web.py} +5 -2
- jarvis/jarvis_tools/select_code_files.py +1 -1
- 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.125.dist-info/METADATA +291 -0
- jarvis_ai_assistant-0.1.125.dist-info/RECORD +75 -0
- {jarvis_ai_assistant-0.1.124.dist-info → jarvis_ai_assistant-0.1.125.dist-info}/entry_points.txt +1 -0
- 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.125.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.124.dist-info → jarvis_ai_assistant-0.1.125.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.124.dist-info → jarvis_ai_assistant-0.1.125.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.file_operation import FileOperationTool
|
|
9
|
+
from jarvis.jarvis_tools.execute_shell_script import ShellScriptTool
|
|
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
|
-
# 🛠️ Code Patch Specification
|
|
25
|
-
|
|
26
|
-
You can output multiple patches, each patch is a <PATCH> block.
|
|
29
|
+
# 🛠️ Contextual Code Patch Specification
|
|
30
|
+
Use <PATCH> blocks to specify code changes:
|
|
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: [file_path]
|
|
34
|
+
Reason: [change_reason]
|
|
35
|
+
[contextual_code_snippet]
|
|
34
36
|
</PATCH>
|
|
35
37
|
--------------------------------
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
38
|
+
Rules:
|
|
39
|
+
1. Code snippets must include sufficient context (3 lines before/after)
|
|
40
|
+
2. I can see full code, so only show modified code sections
|
|
41
|
+
3. Preserve original indentation and formatting
|
|
42
|
+
4. For new files, provide complete code
|
|
43
|
+
5. When modifying existing files, retain surrounding unchanged code
|
|
44
|
+
Example:
|
|
45
|
+
<PATCH>
|
|
46
|
+
File: src/utils/math.py
|
|
47
|
+
Reason: Fix zero division handling
|
|
48
|
+
def safe_divide(a, b):
|
|
49
|
+
# Add parameter validation
|
|
50
|
+
if b == 0:
|
|
51
|
+
raise ValueError("Divisor cannot be zero")
|
|
52
|
+
return a / b
|
|
53
|
+
# existing code ...
|
|
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,179 @@ 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 += "✅ The patches have been applied\n"
|
|
106
|
+
final_ret += "Commit History:\n"
|
|
107
|
+
for commit_hash, commit_message in commits:
|
|
108
|
+
final_ret += f"- {commit_hash[:7]}: {commit_message}\n"
|
|
109
|
+
else:
|
|
110
|
+
final_ret += "✅ The patches have been applied (no new commits)"
|
|
144
111
|
else:
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
112
|
+
final_ret += "❌ I don't want to commit the code"
|
|
113
|
+
else:
|
|
114
|
+
final_ret += "❌ There are no changes to commit"
|
|
115
|
+
# 用户确认最终结果
|
|
116
|
+
PrettyOutput.print(final_ret, OutputType.USER)
|
|
117
|
+
if not is_confirm_before_apply_patch() or user_confirm("是否使用此回复?", default=True):
|
|
118
|
+
return final_ret
|
|
119
|
+
return get_multiline_input("请输入自定义回复")
|
|
120
|
+
def revert_file(filepath: str):
|
|
121
|
+
"""增强版git恢复,处理新文件"""
|
|
122
|
+
import subprocess
|
|
123
|
+
try:
|
|
124
|
+
# 检查文件是否在版本控制中
|
|
125
|
+
result = subprocess.run(
|
|
126
|
+
['git', 'ls-files', '--error-unmatch', filepath],
|
|
127
|
+
stderr=subprocess.PIPE
|
|
128
|
+
)
|
|
129
|
+
if result.returncode == 0:
|
|
130
|
+
subprocess.run(['git', 'checkout', 'HEAD', '--', filepath], check=True)
|
|
150
131
|
else:
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
132
|
+
if os.path.exists(filepath):
|
|
133
|
+
os.remove(filepath)
|
|
134
|
+
subprocess.run(['git', 'clean', '-f', '--', filepath], check=True)
|
|
135
|
+
except subprocess.CalledProcessError as e:
|
|
136
|
+
PrettyOutput.print(f"恢复文件失败: {str(e)}", OutputType.ERROR)
|
|
137
|
+
# 修改后的恢复函数
|
|
138
|
+
def revert_change():
|
|
139
|
+
import subprocess
|
|
140
|
+
subprocess.run(['git', 'reset', '--hard', 'HEAD'], check=True)
|
|
141
|
+
subprocess.run(['git', 'clean', '-fd'], check=True)
|
|
142
|
+
# 修改后的获取差异函数
|
|
155
143
|
def get_diff() -> str:
|
|
156
|
-
"""
|
|
144
|
+
"""使用git获取暂存区差异"""
|
|
157
145
|
import subprocess
|
|
158
146
|
try:
|
|
159
147
|
subprocess.run(['git', 'add', '.'], check=True)
|
|
160
148
|
result = subprocess.run(
|
|
161
|
-
['git', 'diff', '
|
|
149
|
+
['git', 'diff', '--cached'],
|
|
162
150
|
capture_output=True,
|
|
163
151
|
text=True,
|
|
164
152
|
check=True
|
|
165
153
|
)
|
|
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:
|
|
154
|
+
ret = result.stdout
|
|
155
|
+
subprocess.run(['git', "reset", "--soft", "HEAD"], check=True)
|
|
156
|
+
return ret
|
|
157
|
+
except subprocess.CalledProcessError as e:
|
|
158
|
+
return f"获取差异失败: {str(e)}"
|
|
159
|
+
def handle_commit_workflow()->bool:
|
|
177
160
|
"""Handle the git commit workflow and return the commit details.
|
|
178
161
|
|
|
179
162
|
Returns:
|
|
180
163
|
tuple[bool, str, str]: (continue_execution, commit_id, commit_message)
|
|
181
164
|
"""
|
|
182
|
-
if not user_confirm("是否要提交代码?", default=True):
|
|
165
|
+
if is_confirm_before_apply_patch() and not user_confirm("是否要提交代码?", default=True):
|
|
183
166
|
revert_change()
|
|
184
167
|
return False
|
|
185
|
-
|
|
186
168
|
git_commiter = GitCommitTool()
|
|
187
169
|
commit_result = git_commiter.execute({})
|
|
188
170
|
return commit_result["success"]
|
|
189
171
|
|
|
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
172
|
# 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
|
-
"""处理紧凑格式补丁"""
|
|
173
|
+
def handle_code_operation(filepath: str, patch_content: str) -> str:
|
|
174
|
+
"""处理基于上下文的代码片段"""
|
|
230
175
|
try:
|
|
231
|
-
# 新建文件时强制覆盖
|
|
232
|
-
os.makedirs(os.path.dirname(filepath) or '.', exist_ok=True)
|
|
233
176
|
if not os.path.exists(filepath):
|
|
177
|
+
# 新建文件
|
|
178
|
+
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
|
234
179
|
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)
|
|
180
|
+
old_file_content = FileOperationTool().execute({"operation": "read", "files": [{"path": filepath}]})
|
|
181
|
+
if not old_file_content["success"]:
|
|
182
|
+
return f"文件读取失败: {old_file_content['stderr']}"
|
|
287
183
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
if ':' in ref:
|
|
298
|
-
file_path, line_range = ref.split(':', 1)
|
|
299
|
-
# Initialize with default values
|
|
300
|
-
start_line = 1 # 1-based
|
|
301
|
-
end_line = -1
|
|
302
|
-
|
|
303
|
-
# Process line range if specified
|
|
304
|
-
if ',' in line_range or ':' in line_range:
|
|
305
|
-
try:
|
|
306
|
-
raw_start, raw_end = map(int, re.split(r'[,:]', line_range))
|
|
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
|
|
184
|
+
prompt = f"""
|
|
185
|
+
You are a code reviewer, please review the following code and merge the code with the context.
|
|
186
|
+
Original Code:
|
|
187
|
+
{old_file_content["stdout"]}
|
|
188
|
+
Patch:
|
|
189
|
+
{patch_content}
|
|
190
|
+
"""
|
|
191
|
+
prompt += f"""
|
|
192
|
+
Please merge the code with the context and return the fully merged code.
|
|
335
193
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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)
|
|
194
|
+
Requirements:
|
|
195
|
+
1. Strictly preserve original code formatting and indentation
|
|
196
|
+
2. Only include actual code content in <MERGED_CODE> block
|
|
197
|
+
3. Absolutely NO markdown code blocks (```) or backticks
|
|
198
|
+
4. Maintain exact line numbers from original code except for changes
|
|
368
199
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
- Preserve surrounding comments unless explicitly modifying them
|
|
374
|
-
- Verify line number range is correct
|
|
375
|
-
- Verify indentation is correct
|
|
376
|
-
==================================================================
|
|
200
|
+
Output Format:
|
|
201
|
+
<MERGED_CODE>
|
|
202
|
+
[merged_code]
|
|
203
|
+
</MERGED_CODE>
|
|
377
204
|
"""
|
|
205
|
+
model = PlatformRegistry().get_codegen_platform()
|
|
206
|
+
model.set_suppress_output(False)
|
|
207
|
+
count = 5
|
|
208
|
+
start_line = -1
|
|
209
|
+
end_line = -1
|
|
210
|
+
response = []
|
|
211
|
+
while count > 0:
|
|
212
|
+
count -= 1
|
|
213
|
+
response.extend(model.chat_until_success(prompt).splitlines())
|
|
214
|
+
try:
|
|
215
|
+
start_line = response.index("<MERGED_CODE>") + 1
|
|
216
|
+
except:
|
|
217
|
+
pass
|
|
218
|
+
try:
|
|
219
|
+
end_line = response.index("</MERGED_CODE>")
|
|
220
|
+
except:
|
|
221
|
+
pass
|
|
222
|
+
if start_line == -1:
|
|
223
|
+
PrettyOutput.print(f"❌ 为文件 {filepath} 应用补丁失败", OutputType.WARNING)
|
|
224
|
+
return f"代码合并失败"
|
|
225
|
+
if end_line == -1:
|
|
226
|
+
last_line = response[-1]
|
|
227
|
+
prompt = f"""
|
|
228
|
+
continue with the last line:
|
|
229
|
+
{last_line}
|
|
230
|
+
"""
|
|
231
|
+
response.pop() # 删除最后一行
|
|
232
|
+
continue
|
|
233
|
+
if end_line < start_line:
|
|
234
|
+
PrettyOutput.print(f"❌ 为文件 {filepath} 应用补丁失败", OutputType.WARNING)
|
|
235
|
+
return f"代码合并失败"
|
|
236
|
+
break
|
|
237
|
+
# 写入合并后的代码
|
|
238
|
+
with open(filepath, 'w', encoding='utf-8') as f:
|
|
239
|
+
f.write("\n".join(response[start_line:end_line]))
|
|
240
|
+
PrettyOutput.print(f"✅ 为文件 {filepath} 应用补丁成功", OutputType.SUCCESS)
|
|
241
|
+
return ""
|
|
242
|
+
except Exception as e:
|
|
243
|
+
return f"文件操作失败: {str(e)}"
|
|
244
|
+
def shell_input_handler(user_input: str, agent: Any) -> Tuple[str, bool]:
|
|
245
|
+
lines = user_input.splitlines()
|
|
246
|
+
cmdline = [line for line in lines if line.startswith("!")]
|
|
247
|
+
if len(cmdline) == 0:
|
|
248
|
+
return user_input, False
|
|
249
|
+
else:
|
|
250
|
+
script = '\n'.join([c[1:] for c in cmdline])
|
|
251
|
+
PrettyOutput.print(script, OutputType.CODE, lang="bash")
|
|
252
|
+
if user_confirm(f"是否要执行以上shell脚本?", default=True):
|
|
253
|
+
ShellScriptTool().execute({"script_content": script})
|
|
254
|
+
return "", True
|
|
255
|
+
return user_input, False
|
|
256
|
+
|
jarvis/jarvis_codebase/main.py
CHANGED
|
@@ -7,14 +7,18 @@ from typing import List, Tuple, Optional, Dict
|
|
|
7
7
|
from jarvis.jarvis_platform.registry import PlatformRegistry
|
|
8
8
|
import concurrent.futures
|
|
9
9
|
from concurrent.futures import ThreadPoolExecutor
|
|
10
|
-
from jarvis.jarvis_utils import OutputType, PrettyOutput, find_git_root, get_context_token_count, get_embedding, get_file_md5, get_max_token_count, get_thread_count, load_embedding_model, user_confirm
|
|
11
|
-
from jarvis.jarvis_utils import init_env
|
|
12
10
|
import argparse
|
|
13
11
|
import pickle
|
|
14
12
|
import lzma # 添加 lzma 导入
|
|
15
13
|
from tqdm import tqdm
|
|
16
14
|
import re
|
|
17
15
|
|
|
16
|
+
from jarvis.jarvis_utils.config import get_max_token_count, get_thread_count
|
|
17
|
+
from jarvis.jarvis_utils.embedding import get_embedding, load_embedding_model, get_context_token_count
|
|
18
|
+
from jarvis.jarvis_utils.git_utils import find_git_root
|
|
19
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
20
|
+
from jarvis.jarvis_utils.utils import get_file_md5, init_env, user_confirm
|
|
21
|
+
|
|
18
22
|
class CodeBase:
|
|
19
23
|
def __init__(self, root_dir: str):
|
|
20
24
|
init_env()
|
jarvis/jarvis_dev/main.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
from jarvis.jarvis_platform.registry import PlatformRegistry
|
|
2
2
|
from jarvis.jarvis_multi_agent import MultiAgent, AgentConfig
|
|
3
3
|
from jarvis.jarvis_tools.registry import ToolRegistry
|
|
4
|
-
from jarvis.jarvis_utils import get_multiline_input
|
|
4
|
+
from jarvis.jarvis_utils.input import get_multiline_input
|
|
5
|
+
from jarvis.jarvis_utils.utils import init_env
|
|
6
|
+
|
|
5
7
|
|
|
6
8
|
# Define system prompts for each role
|
|
7
9
|
PM_PROMPT = """
|
|
@@ -830,13 +832,13 @@ def create_dev_team() -> MultiAgent:
|
|
|
830
832
|
"""Create a development team with multiple agents."""
|
|
831
833
|
|
|
832
834
|
PM_output_handler = ToolRegistry()
|
|
833
|
-
PM_output_handler.use_tools(["ask_user", "file_operation", "
|
|
835
|
+
PM_output_handler.use_tools(["ask_user", "file_operation", "search_web", "rag", "execute_shell"])
|
|
834
836
|
|
|
835
837
|
BA_output_handler = ToolRegistry()
|
|
836
|
-
BA_output_handler.use_tools(["ask_user", "file_operation", "
|
|
838
|
+
BA_output_handler.use_tools(["ask_user", "file_operation", "search_web", "rag", "execute_shell"])
|
|
837
839
|
|
|
838
840
|
SA_output_handler = ToolRegistry()
|
|
839
|
-
SA_output_handler.use_tools(["read_code", "file_operation", "
|
|
841
|
+
SA_output_handler.use_tools(["read_code", "file_operation", "search_web", "rag", "ask_codebase", "lsp_get_document_symbols", "execute_shell"])
|
|
840
842
|
|
|
841
843
|
TL_output_handler = ToolRegistry()
|
|
842
844
|
TL_output_handler.use_tools(["read_code", "file_operation", "ask_codebase", "lsp_get_diagnostics", "lsp_find_references", "lsp_find_definition", "execute_shell"])
|
|
File without changes
|