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.

Files changed (67) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +19 -21
  3. jarvis/jarvis_code_agent/code_agent.py +205 -119
  4. jarvis/jarvis_code_agent/file_select.py +6 -105
  5. jarvis/jarvis_code_agent/patch.py +192 -259
  6. jarvis/jarvis_codebase/main.py +6 -2
  7. jarvis/jarvis_dev/main.py +6 -4
  8. jarvis/jarvis_git_squash/__init__.py +0 -0
  9. jarvis/jarvis_git_squash/main.py +81 -0
  10. jarvis/jarvis_lsp/cpp.py +1 -1
  11. jarvis/jarvis_lsp/go.py +1 -1
  12. jarvis/jarvis_lsp/registry.py +2 -2
  13. jarvis/jarvis_lsp/rust.py +1 -1
  14. jarvis/jarvis_multi_agent/__init__.py +1 -1
  15. jarvis/jarvis_platform/ai8.py +2 -1
  16. jarvis/jarvis_platform/base.py +20 -25
  17. jarvis/jarvis_platform/kimi.py +2 -3
  18. jarvis/jarvis_platform/ollama.py +3 -1
  19. jarvis/jarvis_platform/openai.py +1 -1
  20. jarvis/jarvis_platform/oyi.py +2 -1
  21. jarvis/jarvis_platform/registry.py +2 -1
  22. jarvis/jarvis_platform_manager/main.py +4 -6
  23. jarvis/jarvis_platform_manager/openai_test.py +0 -1
  24. jarvis/jarvis_rag/main.py +5 -2
  25. jarvis/jarvis_smart_shell/main.py +9 -4
  26. jarvis/jarvis_tools/ask_codebase.py +12 -7
  27. jarvis/jarvis_tools/ask_user.py +3 -2
  28. jarvis/jarvis_tools/base.py +21 -7
  29. jarvis/jarvis_tools/chdir.py +25 -1
  30. jarvis/jarvis_tools/code_review.py +13 -14
  31. jarvis/jarvis_tools/create_code_agent.py +4 -7
  32. jarvis/jarvis_tools/create_sub_agent.py +2 -2
  33. jarvis/jarvis_tools/execute_shell.py +3 -1
  34. jarvis/jarvis_tools/execute_shell_script.py +58 -0
  35. jarvis/jarvis_tools/file_operation.py +3 -2
  36. jarvis/jarvis_tools/git_commiter.py +26 -17
  37. jarvis/jarvis_tools/lsp_find_definition.py +1 -1
  38. jarvis/jarvis_tools/lsp_find_references.py +1 -1
  39. jarvis/jarvis_tools/lsp_get_diagnostics.py +19 -11
  40. jarvis/jarvis_tools/lsp_get_document_symbols.py +1 -1
  41. jarvis/jarvis_tools/lsp_prepare_rename.py +1 -1
  42. jarvis/jarvis_tools/lsp_validate_edit.py +1 -1
  43. jarvis/jarvis_tools/methodology.py +4 -1
  44. jarvis/jarvis_tools/rag.py +22 -15
  45. jarvis/jarvis_tools/read_code.py +4 -3
  46. jarvis/jarvis_tools/read_webpage.py +2 -1
  47. jarvis/jarvis_tools/registry.py +4 -1
  48. jarvis/jarvis_tools/{search.py → search_web.py} +5 -3
  49. jarvis/jarvis_tools/select_code_files.py +1 -1
  50. jarvis/jarvis_utils/__init__.py +19 -941
  51. jarvis/jarvis_utils/config.py +138 -0
  52. jarvis/jarvis_utils/embedding.py +201 -0
  53. jarvis/jarvis_utils/git_utils.py +120 -0
  54. jarvis/jarvis_utils/globals.py +82 -0
  55. jarvis/jarvis_utils/input.py +161 -0
  56. jarvis/jarvis_utils/methodology.py +128 -0
  57. jarvis/jarvis_utils/output.py +235 -0
  58. jarvis/jarvis_utils/utils.py +150 -0
  59. jarvis_ai_assistant-0.1.125.dist-info/METADATA +291 -0
  60. jarvis_ai_assistant-0.1.125.dist-info/RECORD +75 -0
  61. {jarvis_ai_assistant-0.1.123.dist-info → jarvis_ai_assistant-0.1.125.dist-info}/WHEEL +1 -1
  62. {jarvis_ai_assistant-0.1.123.dist-info → jarvis_ai_assistant-0.1.125.dist-info}/entry_points.txt +1 -0
  63. jarvis/jarvis_code_agent/relevant_files.py +0 -117
  64. jarvis_ai_assistant-0.1.123.dist-info/METADATA +0 -461
  65. jarvis_ai_assistant-0.1.123.dist-info/RECORD +0 -65
  66. {jarvis_ai_assistant-0.1.123.dist-info → jarvis_ai_assistant-0.1.125.dist-info}/LICENSE +0 -0
  67. {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, 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.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
- When making changes, you MUST:
26
- 1. Explain each modification BEFORE the <PATCH> block using:
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
- config/settings.yaml [1]
100
- TODO: Add configuration
33
+ File: [file_path]
34
+ Reason: [change_reason]
35
+ [contextual_code_snippet]
101
36
  </PATCH>
102
-
103
- # ======== DELETE ========
104
- # GOOD: Empty content for deletion
105
- # DELETE in src/old.py: Lines 10-12
106
- # Reason: Remove deprecated function
107
- <PATCH>
108
- src/old.py [10,12]
109
- </PATCH>
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/old.py [10,12]
114
- # Remove these lines
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
- def _parse_patch(patch_str: str) -> Dict[str, List[Dict[str, Any]]]:
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
- for patch in patches:
128
- # 分割首行和内容
129
- parts = patch.split('\n', 1)
130
- if len(parts) < 1:
131
- continue
132
- header_line = parts[0].strip()
133
- content = parts[1] if len(parts) > 1 else ''
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
- for filepath, patch_list in patches.items():
173
- for patch in patch_list:
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
- if has_uncommitted_changes():
181
- diff = get_diff()
182
- if handle_commit_workflow(diff):
183
- ret += "Successfully applied the patch\n"
184
- # Get modified line ranges
185
- modified_ranges = get_modified_line_ranges()
186
- modified_code = ReadCodeTool().execute({"files": [{"path": filepath, "start_line": start, "end_line": end} for filepath, (start, end) in modified_ranges.items()]})
187
- if modified_code["success"]:
188
- ret += "New code:\n"
189
- 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 += "✅ 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
- ret += "User rejected the patch\nThis is your patch preview:\n"
192
- ret += diff
193
- user_input = get_multiline_input("你可以继续输入(输入空行重试,Ctrl+C退出): ")
194
- if user_input:
195
- ret += "\n" + user_input
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
- ret = ""
198
-
199
- return ret # Ensure a string is always returned
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
- """使用更安全的subprocess代替os.system"""
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', 'HEAD'],
149
+ ['git', 'diff', '--cached'],
208
150
  capture_output=True,
209
151
  text=True,
210
152
  check=True
211
153
  )
212
- return result.stdout
213
- finally:
214
- subprocess.run(['git', 'reset', 'HEAD'], check=True)
215
-
216
- 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:
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
- import subprocess
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
- def handle_new_file(filepath: str, patch: Dict[str, Any]):
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
- with open(filepath, 'r+', encoding='utf-8') as f:
279
- lines = f.readlines()
280
-
281
- new_lines = validate_and_apply_changes(
282
- lines,
283
- patch['start'],
284
- patch['end'],
285
- patch['content']
286
- )
287
-
288
- f.seek(0)
289
- f.writelines(new_lines)
290
- f.truncate()
291
-
292
- PrettyOutput.print(f"成功更新 {filepath}", OutputType.SUCCESS)
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
- PrettyOutput.print(f"操作失败: {str(e)}", OutputType.ERROR)
296
-
297
- def validate_and_apply_changes(
298
- lines: List[str],
299
- start: int,
300
- end: int,
301
- content: str
302
- ) -> List[str]:
303
-
304
- new_content = content.splitlines(keepends=True)
305
-
306
- # 插入操作处理
307
- if start == end:
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:]
@@ -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, init_env
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", "search", "rag", "execute_shell"])
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", "search", "rag", "execute_shell"])
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", "search", "rag", "ask_codebase", "lsp_get_document_symbols", "execute_shell"])
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 PrettyOutput, OutputType
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 PrettyOutput, OutputType
7
+ from jarvis.jarvis_utils.output import OutputType, PrettyOutput
8
8
 
9
9
  class GoLSP(BaseLSP):
10
10
  """Go LSP implementation using gopls."""