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.

Files changed (70) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +134 -136
  3. jarvis/jarvis_code_agent/code_agent.py +198 -52
  4. jarvis/jarvis_code_agent/file_select.py +6 -19
  5. jarvis/jarvis_code_agent/patch.py +183 -312
  6. jarvis/jarvis_code_agent/shell_input_handler.py +22 -0
  7. jarvis/jarvis_codebase/main.py +89 -86
  8. jarvis/jarvis_dev/main.py +695 -715
  9. jarvis/jarvis_git_squash/__init__.py +0 -0
  10. jarvis/jarvis_git_squash/main.py +81 -0
  11. jarvis/jarvis_lsp/base.py +0 -12
  12. jarvis/jarvis_lsp/cpp.py +1 -10
  13. jarvis/jarvis_lsp/go.py +1 -10
  14. jarvis/jarvis_lsp/python.py +0 -28
  15. jarvis/jarvis_lsp/registry.py +2 -3
  16. jarvis/jarvis_lsp/rust.py +1 -10
  17. jarvis/jarvis_multi_agent/__init__.py +53 -53
  18. jarvis/jarvis_platform/ai8.py +2 -1
  19. jarvis/jarvis_platform/base.py +19 -24
  20. jarvis/jarvis_platform/kimi.py +2 -3
  21. jarvis/jarvis_platform/ollama.py +3 -1
  22. jarvis/jarvis_platform/openai.py +1 -1
  23. jarvis/jarvis_platform/oyi.py +2 -1
  24. jarvis/jarvis_platform/registry.py +2 -1
  25. jarvis/jarvis_platform_manager/main.py +4 -6
  26. jarvis/jarvis_platform_manager/openai_test.py +0 -1
  27. jarvis/jarvis_rag/main.py +5 -2
  28. jarvis/jarvis_smart_shell/main.py +9 -4
  29. jarvis/jarvis_tools/ask_codebase.py +18 -13
  30. jarvis/jarvis_tools/ask_user.py +5 -4
  31. jarvis/jarvis_tools/base.py +22 -8
  32. jarvis/jarvis_tools/chdir.py +8 -9
  33. jarvis/jarvis_tools/code_review.py +19 -20
  34. jarvis/jarvis_tools/create_code_agent.py +6 -6
  35. jarvis/jarvis_tools/create_sub_agent.py +9 -9
  36. jarvis/jarvis_tools/execute_shell.py +55 -20
  37. jarvis/jarvis_tools/execute_shell_script.py +7 -7
  38. jarvis/jarvis_tools/file_operation.py +39 -10
  39. jarvis/jarvis_tools/git_commiter.py +20 -17
  40. jarvis/jarvis_tools/lsp_find_definition.py +8 -8
  41. jarvis/jarvis_tools/lsp_find_references.py +1 -1
  42. jarvis/jarvis_tools/lsp_get_diagnostics.py +19 -11
  43. jarvis/jarvis_tools/lsp_get_document_symbols.py +1 -1
  44. jarvis/jarvis_tools/lsp_prepare_rename.py +8 -8
  45. jarvis/jarvis_tools/methodology.py +10 -7
  46. jarvis/jarvis_tools/rag.py +27 -20
  47. jarvis/jarvis_tools/read_webpage.py +4 -3
  48. jarvis/jarvis_tools/registry.py +143 -140
  49. jarvis/jarvis_tools/{search.py → search_web.py} +10 -7
  50. jarvis/jarvis_tools/select_code_files.py +4 -4
  51. jarvis/jarvis_tools/tool_generator.py +33 -34
  52. jarvis/jarvis_utils/__init__.py +19 -982
  53. jarvis/jarvis_utils/config.py +138 -0
  54. jarvis/jarvis_utils/embedding.py +201 -0
  55. jarvis/jarvis_utils/git_utils.py +120 -0
  56. jarvis/jarvis_utils/globals.py +82 -0
  57. jarvis/jarvis_utils/input.py +161 -0
  58. jarvis/jarvis_utils/methodology.py +128 -0
  59. jarvis/jarvis_utils/output.py +235 -0
  60. jarvis/jarvis_utils/utils.py +150 -0
  61. jarvis_ai_assistant-0.1.126.dist-info/METADATA +305 -0
  62. jarvis_ai_assistant-0.1.126.dist-info/RECORD +74 -0
  63. {jarvis_ai_assistant-0.1.124.dist-info → jarvis_ai_assistant-0.1.126.dist-info}/WHEEL +1 -1
  64. {jarvis_ai_assistant-0.1.124.dist-info → jarvis_ai_assistant-0.1.126.dist-info}/entry_points.txt +1 -0
  65. jarvis/jarvis_tools/lsp_validate_edit.py +0 -141
  66. jarvis/jarvis_tools/read_code.py +0 -191
  67. jarvis_ai_assistant-0.1.124.dist-info/METADATA +0 -460
  68. jarvis_ai_assistant-0.1.124.dist-info/RECORD +0 -65
  69. {jarvis_ai_assistant-0.1.124.dist-info → jarvis_ai_assistant-0.1.126.dist-info}/LICENSE +0 -0
  70. {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, List, Tuple
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.read_code import ReadCodeTool
7
- from jarvis.jarvis_utils import OutputType, PrettyOutput, get_multiline_input, has_uncommitted_changes, user_confirm
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
- # 🛠️ Code Patch Specification
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
- [FILE] [RANGE]
33
- [CONTENT]
33
+ File: [文件路径]
34
+ Reason: [修改原因]
35
+ [上下文代码片段]
34
36
  </PATCH>
35
37
  --------------------------------
36
-
37
- Explain:
38
- - [OPERATION]: The operation to be performed, including:
39
- - INSERT: Insert code before the specified line, [RANGE] should be [m,m)
40
- - REPLACE: Replace code in the specified range, [RANGE] should be [m,n] n>=m
41
- - DELETE: Delete code in the specified range, [RANGE] should be [m,n] n>=m
42
- - NEW_FILE: Create a new file, [RANGE] should be [1,1)
43
- - [FILE]: The path of the file to be modified
44
- - [RANGE]: The range of the lines to be modified, [m,n] includes both m and n, [m,n) includes m but excludes n
45
- - [CONTENT]: The content of the code to be modified, if the operation is delete, the [CONTENT] is empty
46
-
47
- Critical Rules:
48
- - NEVER include unchanged code in patch content
49
- - ONLY show lines that are being modified/added
50
- - Maintain original line breaks around modified sections
51
- - Preserve surrounding comments unless explicitly modifying them
52
- - Verify line number range is correct
53
- - Verify indentation is correct
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
- def _parse_patch(patch_str: str) -> Dict[str, List[Dict[str, Any]]]:
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
- for patch in patches:
68
- parts = patch.split('\n', 1)
69
- if len(parts) < 1:
70
- continue
71
- header_line = parts[0].strip()
72
- content = parts[1] if len(parts) > 1 else ''
73
-
74
- if content and not content.endswith('\n'):
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
- for filepath, patch_list in patches.items():
123
- for i, patch in enumerate(patch_list):
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
- if has_uncommitted_changes():
135
- diff = get_diff()
136
- if handle_commit_workflow(diff):
137
- ret += "Successfully applied the patch\n"
138
- # Get modified line ranges
139
- modified_ranges = get_modified_line_ranges()
140
- modified_code = ReadCodeTool().execute({"files": [{"path": filepath, "start_line": start, "end_line": end} for filepath, (start, end) in modified_ranges.items()]})
141
- if modified_code["success"]:
142
- ret += "New code:\n"
143
- ret += modified_code["stdout"]
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
- ret += "User rejected the patch\nThis is your patch preview:\n"
146
- ret += diff
147
- user_input = get_multiline_input("你可以继续输入(输入空行重试,Ctrl+C退出): ")
148
- if user_input:
149
- ret += "\n" + user_input
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
- ret = ""
152
-
153
- return ret # Ensure a string is always returned
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
- """使用更安全的subprocess代替os.system"""
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', 'HEAD'],
154
+ ['git', 'diff', '--cached'],
162
155
  capture_output=True,
163
156
  text=True,
164
157
  check=True
165
158
  )
166
- return result.stdout
167
- finally:
168
- subprocess.run(['git', 'reset', 'HEAD'], check=True)
169
-
170
- def revert_change():
171
- import subprocess
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
- def handle_new_file(filepath: str, patch: Dict[str, Any]):
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
- with open(filepath, 'r+', encoding='utf-8') as f:
236
- lines = f.readlines()
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
- Returns:
289
- str: Prompt with file contents prepended if files are found
290
- """
291
- prompt = user_input
292
- files = []
293
-
294
- file_refs = re.findall(r"'([^']+)'", user_input)
295
- for ref in file_refs:
296
- # Handle file:start,end or file:start:end format
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
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
+