jarvis-ai-assistant 0.1.123__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 +19 -21
- jarvis/jarvis_code_agent/code_agent.py +205 -119
- jarvis/jarvis_code_agent/file_select.py +6 -105
- jarvis/jarvis_code_agent/patch.py +192 -259
- 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 +20 -25
- 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 +25 -1
- jarvis/jarvis_tools/code_review.py +13 -14
- jarvis/jarvis_tools/create_code_agent.py +4 -7
- jarvis/jarvis_tools/create_sub_agent.py +2 -2
- jarvis/jarvis_tools/execute_shell.py +3 -1
- jarvis/jarvis_tools/execute_shell_script.py +58 -0
- jarvis/jarvis_tools/file_operation.py +3 -2
- jarvis/jarvis_tools/git_commiter.py +26 -17
- 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 -3
- jarvis/jarvis_tools/select_code_files.py +1 -1
- jarvis/jarvis_utils/__init__.py +19 -941
- 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.123.dist-info → jarvis_ai_assistant-0.1.125.dist-info}/WHEEL +1 -1
- {jarvis_ai_assistant-0.1.123.dist-info → jarvis_ai_assistant-0.1.125.dist-info}/entry_points.txt +1 -0
- jarvis/jarvis_code_agent/relevant_files.py +0 -117
- jarvis_ai_assistant-0.1.123.dist-info/METADATA +0 -461
- jarvis_ai_assistant-0.1.123.dist-info/RECORD +0 -65
- {jarvis_ai_assistant-0.1.123.dist-info → jarvis_ai_assistant-0.1.125.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.123.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,144 +26,51 @@ class PatchOutputHandler(OutputHandler):
|
|
|
21
26
|
|
|
22
27
|
def prompt(self) -> str:
|
|
23
28
|
return """
|
|
24
|
-
# 🛠️ Code Patch Specification
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
# [OPERATION] on [FILE]: Lines X-Y
|
|
28
|
-
# Reason: [CLEAR EXPLANATION]
|
|
29
|
-
2. Maintain original code style and compatibility:
|
|
30
|
-
- Preserve existing indentation levels
|
|
31
|
-
- Keep surrounding empty lines
|
|
32
|
-
- Match variable naming conventions
|
|
33
|
-
- Maintain API compatibility
|
|
34
|
-
3. Follow the exact patch format below
|
|
35
|
-
4. Use separate <PATCH> blocks for different files
|
|
36
|
-
5. Include ONLY modified lines in content
|
|
37
|
-
|
|
38
|
-
<PATCH>
|
|
39
|
-
File path [Range]
|
|
40
|
-
Code content
|
|
41
|
-
</PATCH>
|
|
42
|
-
|
|
43
|
-
Critical Rules:
|
|
44
|
-
- NEVER include unchanged code in patch content
|
|
45
|
-
- ONLY show lines that are being modified/added
|
|
46
|
-
- Maintain original line breaks around modified sections
|
|
47
|
-
- Preserve surrounding comments unless explicitly modifying them
|
|
48
|
-
|
|
49
|
-
Examples:
|
|
50
|
-
# ======== REPLACE ========
|
|
51
|
-
# GOOD: Only modified lines
|
|
52
|
-
# REPLACE in src/app.py: Lines 5-8
|
|
53
|
-
# Reason: Update calculation formula
|
|
54
|
-
<PATCH>
|
|
55
|
-
src/app.py [5,8]
|
|
56
|
-
result = (base_value * 1.15) + tax
|
|
57
|
-
logger.debug("New calculation applied")
|
|
58
|
-
</PATCH>
|
|
59
|
-
|
|
60
|
-
# BAD: Includes unchanged lines
|
|
61
|
-
<PATCH>
|
|
62
|
-
src/app.py [5,8]
|
|
63
|
-
def calculate():
|
|
64
|
-
# Original comment (should not be included)
|
|
65
|
-
result = (base_value * 1.15) + tax
|
|
66
|
-
return result # Original line
|
|
67
|
-
</PATCH>
|
|
68
|
-
|
|
69
|
-
# ======== INSERT ========
|
|
70
|
-
# GOOD: Insert single line
|
|
71
|
-
# INSERT in utils/logger.py: Before line 3
|
|
72
|
-
# Reason: Add initialization check
|
|
73
|
-
<PATCH>
|
|
74
|
-
utils/logger.py [3]
|
|
75
|
-
if not _initialized: initialize()
|
|
76
|
-
</PATCH>
|
|
77
|
-
|
|
78
|
-
# BAD: Extra empty lines
|
|
79
|
-
<PATCH>
|
|
80
|
-
utils/logger.py [3]
|
|
81
|
-
|
|
82
|
-
if not _initialized:
|
|
83
|
-
initialize()
|
|
84
|
-
|
|
85
|
-
</PATCH>
|
|
86
|
-
|
|
87
|
-
# ======== NEW FILE ========
|
|
88
|
-
# GOOD: Complete minimal content
|
|
89
|
-
# NEW FILE in config/settings.yaml: Create new config
|
|
90
|
-
<PATCH>
|
|
91
|
-
config/settings.yaml [1]
|
|
92
|
-
database:
|
|
93
|
-
host: localhost
|
|
94
|
-
port: 5432
|
|
95
|
-
</PATCH>
|
|
96
|
-
|
|
97
|
-
# BAD: Placeholder content
|
|
29
|
+
# 🛠️ Contextual Code Patch Specification
|
|
30
|
+
Use <PATCH> blocks to specify code changes:
|
|
31
|
+
--------------------------------
|
|
98
32
|
<PATCH>
|
|
99
|
-
|
|
100
|
-
|
|
33
|
+
File: [file_path]
|
|
34
|
+
Reason: [change_reason]
|
|
35
|
+
[contextual_code_snippet]
|
|
101
36
|
</PATCH>
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
# BAD: Comment in delete operation
|
|
37
|
+
--------------------------------
|
|
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:
|
|
112
45
|
<PATCH>
|
|
113
|
-
src/
|
|
114
|
-
|
|
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
|
|
115
56
|
</PATCH>
|
|
116
57
|
"""
|
|
117
58
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
"""解析补丁格式"""
|
|
59
|
+
def _parse_patch(patch_str: str) -> Dict[str, str]:
|
|
60
|
+
"""解析新的上下文补丁格式"""
|
|
121
61
|
result = {}
|
|
122
|
-
header_pattern = re.compile(
|
|
123
|
-
r'^\s*"?(.+?)"?\s*\[(\d+)(?:,(\d+))?\]\s*$' # Match file path and line number
|
|
124
|
-
)
|
|
125
62
|
patches = re.findall(r'<PATCH>\n?(.*?)\n?</PATCH>', patch_str, re.DOTALL)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
# 仅在内容非空时添加换行符
|
|
136
|
-
if content and not content.endswith('\n'):
|
|
137
|
-
content += '\n'
|
|
138
|
-
|
|
139
|
-
# 解析文件路径和行号
|
|
140
|
-
header_match = header_pattern.match(header_line)
|
|
141
|
-
if not header_match:
|
|
142
|
-
continue
|
|
143
|
-
|
|
144
|
-
filepath = header_match.group(1)
|
|
145
|
-
start = int(header_match.group(2)) # 保持1-based行号
|
|
146
|
-
end = int(header_match.group(3)) + 1 if header_match.group(3) else start
|
|
147
|
-
|
|
148
|
-
# 存储参数
|
|
149
|
-
if filepath not in result:
|
|
150
|
-
result[filepath] = []
|
|
151
|
-
result[filepath].append({
|
|
152
|
-
'filepath': filepath,
|
|
153
|
-
'start': start,
|
|
154
|
-
'end': end,
|
|
155
|
-
'content': content # 保留原始内容(可能为空)
|
|
156
|
-
})
|
|
157
|
-
for filepath in result.keys():
|
|
158
|
-
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
|
|
159
72
|
return result
|
|
160
73
|
|
|
161
|
-
|
|
162
74
|
def apply_patch(output_str: str) -> str:
|
|
163
75
|
"""Apply patches to files"""
|
|
164
76
|
try:
|
|
@@ -166,158 +78,179 @@ def apply_patch(output_str: str) -> str:
|
|
|
166
78
|
except Exception as e:
|
|
167
79
|
PrettyOutput.print(f"解析补丁失败: {str(e)}", OutputType.ERROR)
|
|
168
80
|
return ""
|
|
169
|
-
|
|
170
|
-
ret = ""
|
|
171
81
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
try:
|
|
175
|
-
handle_code_operation(filepath, patch)
|
|
176
|
-
PrettyOutput.print(f"成功处理 操作", OutputType.SUCCESS)
|
|
177
|
-
except Exception as e:
|
|
178
|
-
PrettyOutput.print(f"操作失败: {str(e)}", OutputType.ERROR)
|
|
82
|
+
# 获取当前提交hash作为起始点
|
|
83
|
+
start_hash = get_latest_commit_hash()
|
|
179
84
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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)"
|
|
190
111
|
else:
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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)
|
|
196
131
|
else:
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
+
# 修改后的获取差异函数
|
|
201
143
|
def get_diff() -> str:
|
|
202
|
-
"""
|
|
144
|
+
"""使用git获取暂存区差异"""
|
|
203
145
|
import subprocess
|
|
204
146
|
try:
|
|
205
147
|
subprocess.run(['git', 'add', '.'], check=True)
|
|
206
148
|
result = subprocess.run(
|
|
207
|
-
['git', 'diff', '
|
|
149
|
+
['git', 'diff', '--cached'],
|
|
208
150
|
capture_output=True,
|
|
209
151
|
text=True,
|
|
210
152
|
check=True
|
|
211
153
|
)
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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:
|
|
217
160
|
"""Handle the git commit workflow and return the commit details.
|
|
218
161
|
|
|
219
162
|
Returns:
|
|
220
163
|
tuple[bool, str, str]: (continue_execution, commit_id, commit_message)
|
|
221
164
|
"""
|
|
222
|
-
if not user_confirm("是否要提交代码?", default=True):
|
|
223
|
-
|
|
224
|
-
subprocess.run(['git', 'reset', 'HEAD'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
225
|
-
subprocess.run(['git', 'checkout', '--', '.'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
226
|
-
subprocess.run(['git', 'clean', '-fd'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
165
|
+
if is_confirm_before_apply_patch() and not user_confirm("是否要提交代码?", default=True):
|
|
166
|
+
revert_change()
|
|
227
167
|
return False
|
|
228
|
-
|
|
229
168
|
git_commiter = GitCommitTool()
|
|
230
169
|
commit_result = git_commiter.execute({})
|
|
231
170
|
return commit_result["success"]
|
|
232
171
|
|
|
233
|
-
def get_modified_line_ranges() -> Dict[str, Tuple[int, int]]:
|
|
234
|
-
"""Get modified line ranges from git diff for all changed files.
|
|
235
|
-
|
|
236
|
-
Returns:
|
|
237
|
-
Dictionary mapping file paths to tuple with (start_line, end_line) ranges
|
|
238
|
-
for modified sections. Line numbers are 1-based.
|
|
239
|
-
"""
|
|
240
|
-
# Get git diff for all files
|
|
241
|
-
diff_output = os.popen("git show").read()
|
|
242
|
-
|
|
243
|
-
# Parse the diff to get modified files and their line ranges
|
|
244
|
-
result = {}
|
|
245
|
-
current_file = None
|
|
246
|
-
|
|
247
|
-
for line in diff_output.splitlines():
|
|
248
|
-
# Match lines like "+++ b/path/to/file"
|
|
249
|
-
file_match = re.match(r"^\+\+\+ b/(.*)", line)
|
|
250
|
-
if file_match:
|
|
251
|
-
current_file = file_match.group(1)
|
|
252
|
-
continue
|
|
253
|
-
|
|
254
|
-
# Match lines like "@@ -100,5 +100,7 @@" where the + part shows new lines
|
|
255
|
-
range_match = re.match(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@", line)
|
|
256
|
-
if range_match and current_file:
|
|
257
|
-
start_line = int(range_match.group(1)) # Keep as 1-based
|
|
258
|
-
line_count = int(range_match.group(2)) if range_match.group(2) else 1
|
|
259
|
-
end_line = start_line + line_count - 1
|
|
260
|
-
result[current_file] = (start_line, end_line)
|
|
261
|
-
|
|
262
|
-
return result
|
|
263
172
|
# New handler functions below ▼▼▼
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
"""统一参数格式处理新文件"""
|
|
267
|
-
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
|
268
|
-
with open(filepath, 'w', encoding='utf-8') as f:
|
|
269
|
-
f.write(patch['content'])
|
|
270
|
-
|
|
271
|
-
def handle_code_operation(filepath: str, patch: Dict[str, Any]):
|
|
272
|
-
"""处理紧凑格式补丁"""
|
|
173
|
+
def handle_code_operation(filepath: str, patch_content: str) -> str:
|
|
174
|
+
"""处理基于上下文的代码片段"""
|
|
273
175
|
try:
|
|
274
|
-
# 新建文件时强制覆盖
|
|
275
|
-
os.makedirs(os.path.dirname(filepath) or '.', exist_ok=True)
|
|
276
176
|
if not os.path.exists(filepath):
|
|
177
|
+
# 新建文件
|
|
178
|
+
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
|
277
179
|
open(filepath, 'w', encoding='utf-8').close()
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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']}"
|
|
183
|
+
|
|
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.
|
|
193
|
+
|
|
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
|
|
199
|
+
|
|
200
|
+
Output Format:
|
|
201
|
+
<MERGED_CODE>
|
|
202
|
+
[merged_code]
|
|
203
|
+
</MERGED_CODE>
|
|
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 ""
|
|
294
242
|
except Exception as e:
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
lines
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
if start < 1 or start > len(lines)+1:
|
|
309
|
-
raise ValueError(f"无效插入位置: {start}")
|
|
310
|
-
# 在指定位置前插入
|
|
311
|
-
return lines[:start-1] + new_content + lines[start-1:]
|
|
312
|
-
|
|
313
|
-
# 范围替换/删除操作
|
|
314
|
-
if start > end:
|
|
315
|
-
raise ValueError(f"起始行{start}不能大于结束行{end}")
|
|
316
|
-
|
|
317
|
-
max_line = len(lines)
|
|
318
|
-
# 自动修正行号范围
|
|
319
|
-
start = max(1, min(start, max_line+1))
|
|
320
|
-
end = max(start, min(end, max_line+1))
|
|
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
|
|
321
256
|
|
|
322
|
-
# 执行替换
|
|
323
|
-
return lines[:start-1] + new_content + lines[end-1:]
|
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
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import argparse
|
|
3
|
+
from typing import Dict, Any
|
|
4
|
+
from jarvis.jarvis_tools.git_commiter import GitCommitTool
|
|
5
|
+
import subprocess
|
|
6
|
+
|
|
7
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
8
|
+
from jarvis.jarvis_utils.utils import init_env, user_confirm
|
|
9
|
+
class GitSquashTool:
|
|
10
|
+
name = "git_squash_agent"
|
|
11
|
+
description = "Squash commits interactively using a base commit hash"
|
|
12
|
+
|
|
13
|
+
def _confirm_squash(self) -> bool:
|
|
14
|
+
"""Prompt user for confirmation to squash commits"""
|
|
15
|
+
return user_confirm("是否确认压缩提交?", default=True)
|
|
16
|
+
|
|
17
|
+
def _reset_to_commit(self, commit_hash: str) -> bool:
|
|
18
|
+
"""Perform soft reset to specified commit hash"""
|
|
19
|
+
try:
|
|
20
|
+
subprocess.Popen(
|
|
21
|
+
["git", "reset", "--soft", commit_hash],
|
|
22
|
+
stdout=subprocess.DEVNULL,
|
|
23
|
+
stderr=subprocess.DEVNULL
|
|
24
|
+
).wait()
|
|
25
|
+
return True
|
|
26
|
+
except Exception:
|
|
27
|
+
return False
|
|
28
|
+
|
|
29
|
+
def execute(self, args: Dict) -> Dict[str, Any]:
|
|
30
|
+
"""Execute the squash operation"""
|
|
31
|
+
try:
|
|
32
|
+
if not self._confirm_squash():
|
|
33
|
+
return {
|
|
34
|
+
"success": False,
|
|
35
|
+
"stdout": "Operation cancelled",
|
|
36
|
+
"stderr": ""
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if not self._reset_to_commit(args['commit_hash']):
|
|
40
|
+
return {
|
|
41
|
+
"success": False,
|
|
42
|
+
"stdout": "",
|
|
43
|
+
"stderr": "Failed to reset to specified commit"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# Use existing GitCommitTool for new commit
|
|
47
|
+
commit_tool = GitCommitTool()
|
|
48
|
+
result = commit_tool.execute({"lang": args.get('lang', 'Chinese')})
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
"success": result['success'],
|
|
52
|
+
"stdout": result['stdout'],
|
|
53
|
+
"stderr": result['stderr']
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
except Exception as e:
|
|
57
|
+
return {
|
|
58
|
+
"success": False,
|
|
59
|
+
"stdout": "",
|
|
60
|
+
"stderr": f"Squash failed: {str(e)}"
|
|
61
|
+
}
|
|
62
|
+
def main():
|
|
63
|
+
init_env()
|
|
64
|
+
parser = argparse.ArgumentParser(description='Git squash tool')
|
|
65
|
+
parser.add_argument('commit_hash', type=str, help='Base commit hash to squash from')
|
|
66
|
+
parser.add_argument('--lang', type=str, default='Chinese', help='Language for commit messages')
|
|
67
|
+
args = parser.parse_args()
|
|
68
|
+
|
|
69
|
+
tool = GitSquashTool()
|
|
70
|
+
result = tool.execute({
|
|
71
|
+
'commit_hash': args.commit_hash,
|
|
72
|
+
'lang': args.lang
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
if not result['success']:
|
|
76
|
+
PrettyOutput.print(result['stderr'], OutputType.ERROR)
|
|
77
|
+
sys.exit(1)
|
|
78
|
+
|
|
79
|
+
PrettyOutput.print(result['stdout'], OutputType.SUCCESS)
|
|
80
|
+
if __name__ == "__main__":
|
|
81
|
+
sys.exit(main())
|
jarvis/jarvis_lsp/cpp.py
CHANGED
|
@@ -4,7 +4,7 @@ import subprocess
|
|
|
4
4
|
from typing import List, Dict, Optional, Tuple, Any
|
|
5
5
|
import json
|
|
6
6
|
from jarvis.jarvis_lsp.base import BaseLSP
|
|
7
|
-
from jarvis.jarvis_utils import
|
|
7
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
8
8
|
|
|
9
9
|
class CPPLSP(BaseLSP):
|
|
10
10
|
"""C++ LSP implementation using clangd."""
|
jarvis/jarvis_lsp/go.py
CHANGED
|
@@ -4,7 +4,7 @@ import subprocess
|
|
|
4
4
|
from typing import List, Dict, Optional, Tuple, Any
|
|
5
5
|
import json
|
|
6
6
|
from jarvis.jarvis_lsp.base import BaseLSP
|
|
7
|
-
from jarvis.jarvis_utils import
|
|
7
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
8
8
|
|
|
9
9
|
class GoLSP(BaseLSP):
|
|
10
10
|
"""Go LSP implementation using gopls."""
|