jarvis-ai-assistant 0.1.120__py3-none-any.whl → 0.1.122__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.
Files changed (28) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +3 -3
  3. jarvis/jarvis_code_agent/code_agent.py +36 -58
  4. jarvis/jarvis_code_agent/file_select.py +3 -3
  5. jarvis/jarvis_code_agent/patch.py +155 -298
  6. jarvis/jarvis_code_agent/relevant_files.py +2 -4
  7. jarvis/jarvis_codebase/main.py +2 -2
  8. jarvis/jarvis_lsp/registry.py +7 -7
  9. jarvis/jarvis_platform/ai8.py +10 -10
  10. jarvis/jarvis_platform/kimi.py +3 -5
  11. jarvis/jarvis_platform/oyi.py +11 -11
  12. jarvis/jarvis_platform/registry.py +3 -3
  13. jarvis/jarvis_platform_manager/main.py +2 -2
  14. jarvis/jarvis_tools/ask_codebase.py +1 -1
  15. jarvis/jarvis_tools/code_review.py +1 -1
  16. jarvis/jarvis_tools/create_code_agent.py +1 -1
  17. jarvis/jarvis_tools/file_operation.py +0 -3
  18. jarvis/jarvis_tools/git_commiter.py +26 -7
  19. jarvis/jarvis_tools/rag.py +1 -1
  20. jarvis/jarvis_tools/read_code.py +1 -2
  21. jarvis/jarvis_utils/__init__.py +120 -47
  22. {jarvis_ai_assistant-0.1.120.dist-info → jarvis_ai_assistant-0.1.122.dist-info}/METADATA +1 -1
  23. {jarvis_ai_assistant-0.1.120.dist-info → jarvis_ai_assistant-0.1.122.dist-info}/RECORD +27 -28
  24. {jarvis_ai_assistant-0.1.120.dist-info → jarvis_ai_assistant-0.1.122.dist-info}/entry_points.txt +0 -1
  25. jarvis/jarvis_dev/main.py +0 -878
  26. {jarvis_ai_assistant-0.1.120.dist-info → jarvis_ai_assistant-0.1.122.dist-info}/LICENSE +0 -0
  27. {jarvis_ai_assistant-0.1.120.dist-info → jarvis_ai_assistant-0.1.122.dist-info}/WHEEL +0 -0
  28. {jarvis_ai_assistant-0.1.120.dist-info → jarvis_ai_assistant-0.1.122.dist-info}/top_level.txt +0 -0
@@ -21,246 +21,112 @@ class PatchOutputHandler(OutputHandler):
21
21
 
22
22
  def prompt(self) -> str:
23
23
  return """
24
- # 🔄 REPLACE: Modify existing code
25
- <REPLACE>
26
- File: path/to/file
27
- Lines: [start,end] or [start,end)
28
- [new content]
29
- ...
30
- </REPLACE>
31
-
32
- # ➕ INSERT: Add new code
33
- <INSERT>
34
- File: path/to/file
35
- Line: position
36
- [new content]
37
- ...
38
- </INSERT>
39
-
40
- # 🗑️ DELETE: Remove existing code
41
- <DELETE>
42
- File: path/to/file
43
- Lines: [start,end] or [start,end)
44
- </DELETE>
45
-
46
- # 🆕 NEW_FILE: Create new file
47
- <NEW_FILE>
48
- File: path/to/file
49
- [new content]
50
- ...
51
- </NEW_FILE>
52
-
53
- # ➡️ MOVE_FILE: Relocate a file
54
- <MOVE_FILE>
55
- File: path/to/source/file
56
- NewPath: path/to/destination/file
57
- </MOVE_FILE>
58
-
59
- # ❌ REMOVE_FILE: Delete entire file
60
- <REMOVE_FILE>
61
- File: path/to/file
62
- </REMOVE_FILE>
63
-
64
- # 📋 Formatting Rules
65
- 1. File Paths
66
- - Use relative paths from project root
67
- - Must be exact and case-sensitive
68
- - Example: src/module/file.py
69
-
70
- 2. Line Numbers
71
- - Format: [start,end] (inclusive) or [start,end) (right-exclusive)
72
- - 1-based line numbers
73
- - Single number for INSERT
74
- - Omit for NEW_FILE/REMOVE_FILE
75
-
76
- 3. Content
77
- - Maintain original indentation
78
- - Follow existing code style
79
-
80
- # 📌 Usage Examples
81
- ## REPLACE Example (Closed Interval)
82
- <REPLACE>
83
- File: src/utils.py
84
- Lines: [9,13]
24
+ # 🛠️ Simplified Patch Format
25
+ <PATCH>
26
+ File path [Operation parameters]
27
+ Code content
28
+ </PATCH>
29
+
30
+ Operation types:
31
+ - Replace: [Start line,End line] Replace line range (e.g. [5,8] replaces lines 5-8)
32
+ - Delete: [Start line,End line] Delete line range (e.g. [10,10] deletes line 10)
33
+ - Insert: [Line number] Insert before specified line (e.g. [3] inserts before line 3)
34
+ - New file: [1] Create new file
35
+
36
+ Examples:
37
+ # Replace operation
38
+ <PATCH>
39
+ src/app.py [5,8]
85
40
  def updated_function():
86
- # Replaces lines 9-13 inclusive
87
- return "new_implementation"
88
- </REPLACE>
41
+ print("Replaced lines 5-8")
42
+ return new_value * 2
43
+ </PATCH>
89
44
 
90
- ## REPLACE Example (Left-Closed Right-Open)
91
- <REPLACE>
92
- File: src/calculator.py
93
- Lines: [5,8)
94
- def new_calculation():
95
- # Replaces lines 5-7 (excludes line 8)
96
- return 42
97
- </REPLACE>
45
+ # Delete operation
46
+ <PATCH>
47
+ src/old.py [10,10]
48
+ </PATCH>
98
49
 
99
- ## INSERT Example
100
- <INSERT>
101
- File: src/main.py
102
- Line: 19
103
- # Inserted before line 19
104
- new_feature()
105
- </INSERT>
50
+ # Insert operation
51
+ <PATCH>
52
+ utils/logger.py [3]
53
+ print("Inserted before original line 3")
54
+ </PATCH>
106
55
 
107
- ## NEW_FILE Example
108
- <NEW_FILE>
109
- File: src/new_module.py
110
56
  # New file creation
111
- def feature_entry():
112
- pass
113
- </NEW_FILE>
114
-
115
- ## DELETE Example
116
- <DELETE>
117
- File: src/utils.py
118
- Lines: [9,13]
119
- </DELETE>
120
-
121
- ## MOVE_FILE Example
122
- <MOVE_FILE>
123
- File: src/old_dir/file.py
124
- NewPath: src/new_dir/file.py
125
- </MOVE_FILE>
126
-
127
- ## REMOVE_FILE Example
128
- <REMOVE_FILE>
129
- File: src/obsolete.py
130
- </REMOVE_FILE>
131
-
132
- # 🚨 Critical Requirements
133
- 1. One change per block
134
- 2. Use correct operation type
135
- 3. Match existing code style
136
- 4. Preserve indentation levels
137
- 5. Exact file paths required
138
- 6. Handle edge cases properly
139
- 7. Include error handling
140
- 8. Maintain code consistency
57
+ <PATCH>
58
+ config.yaml [1]
59
+ database:
60
+ host: localhost
61
+ port: 5432
62
+ </PATCH>
141
63
  """
142
64
 
143
65
 
144
66
  def _parse_patch(patch_str: str) -> Dict[str, List[Dict[str, Any]]]:
145
- """Parse patches from string with optimized format"""
67
+ """解析补丁格式"""
146
68
  result = {}
147
- patches = re.findall(r"<(REPLACE|INSERT|DELETE|NEW_FILE|REMOVE_FILE|MOVE_FILE)>(.*?)</\1>", patch_str, re.DOTALL)
69
+ header_pattern = re.compile(
70
+ r'^\s*"?(.+?)"?\s*\[(\d+)(?:,(\d+))?\]\s*$' # Match file path and line number
71
+ )
72
+ patches = re.findall(r'<PATCH>\n?(.*?)\n?</PATCH>', patch_str, re.DOTALL)
148
73
 
149
- for patch_type, patch in patches:
150
- lines = patch.strip().split('\n')
151
- if not lines:
74
+ for patch in patches:
75
+ # 分割首行和内容
76
+ parts = patch.split('\n', 1)
77
+ if len(parts) < 1:
152
78
  continue
79
+ header_line = parts[0].strip()
80
+ content = parts[1] if len(parts) > 1 else ''
81
+
82
+ # 仅在内容非空时添加换行符
83
+ if content and not content.endswith('\n'):
84
+ content += '\n'
153
85
 
154
- # Parse file path
155
- file_match = re.match(r"File:\s*([^\s]+)", lines[0])
156
- if not file_match:
86
+ # 解析文件路径和行号
87
+ header_match = header_pattern.match(header_line)
88
+ if not header_match:
157
89
  continue
158
- filepath = file_match.group(1).strip()
159
-
160
- # Initialize line numbers
161
- start_line = end_line = 0
162
-
163
- # Parse line numbers based on operation type
164
- if patch_type in ['REPLACE', 'DELETE']:
165
- # 增强正则表达式兼容性
166
- line_match = re.match(
167
- r"^Lines:\s*\[\s*(\d+)\s*,\s*(\d+)\s*([\]\)])\s*$", # 匹配行尾
168
- lines[1].strip(), # 去除前后空格
169
- re.IGNORECASE
170
- )
171
- if line_match:
172
- start_line = int(line_match.group(1))
173
- end_value = int(line_match.group(2))
174
- bracket_type = line_match.group(3).strip()
175
-
176
- # 根据括号类型处理区间
177
- if bracket_type == ')': # [m,n)
178
- end_line = end_value - 1
179
- else: # [m,n]
180
- end_line = end_value
181
-
182
- # 确保 end_line >= start_line
183
- end_line = max(end_line, start_line)
184
- else:
185
- PrettyOutput.print(f"无法解析行号格式: {lines[1]}", OutputType.WARNING)
186
- continue
187
- elif patch_type == 'INSERT':
188
- line_match = re.match(r"Line:\s*(\d+)", lines[1])
189
- if line_match:
190
- start_line = int(line_match.group(1)) # 1-based
191
- end_line = start_line
192
- elif patch_type == 'MOVE_FILE':
193
- new_path_match = re.match(r"NewPath:\s*([^\s]+)", lines[1])
194
- if new_path_match:
195
- new_path = new_path_match.group(1).strip()
196
- else:
197
- continue
198
-
199
- # Get content (after metadata)
200
- if patch_type in ['REPLACE', 'DELETE']:
201
- content_start = 2 # File + Lines
202
- elif patch_type == 'INSERT':
203
- content_start = 2 # File + Line
204
- elif patch_type == 'NEW_FILE':
205
- content_start = 1 # File
206
- elif patch_type == 'MOVE_FILE':
207
- content_start = 2 # File + NewPath
208
- elif patch_type == 'REMOVE_FILE':
209
- content_start = 1 # File
210
-
211
- content_lines = lines[content_start:]
212
- content = '\n'.join(content_lines).strip()
213
90
 
91
+ filepath = header_match.group(1)
92
+ start = int(header_match.group(2)) # 保持1-based行号
93
+ end = int(header_match.group(3)) + 1 if header_match.group(3) else start
94
+
95
+ # 存储参数
214
96
  if filepath not in result:
215
97
  result[filepath] = []
216
-
217
- # Handle MOVE_FILE specially
218
- if patch_type == 'MOVE_FILE':
219
- result[filepath].append({
220
- 'type': patch_type,
221
- 'new_path': new_path,
222
- 'content': content
223
- })
224
- else:
225
- result[filepath].append({
226
- 'type': patch_type,
227
- 'start_line': start_line,
228
- 'end_line': end_line,
229
- 'content': content
230
- })
231
-
232
- # Sort patches by start line in reverse order to apply from bottom to top
233
- for filepath in result:
234
- result[filepath].sort(key=lambda x: x.get('start_line', 0), reverse=True)
235
-
98
+ result[filepath].append({
99
+ 'filepath': filepath,
100
+ 'start': start,
101
+ 'end': end,
102
+ 'content': content # 保留原始内容(可能为空)
103
+ })
104
+ for filepath in result.keys():
105
+ result[filepath] = sorted(result[filepath], key=lambda x: x['start'], reverse=True)
236
106
  return result
237
107
 
238
108
 
239
109
  def apply_patch(output_str: str) -> str:
240
110
  """Apply patches to files"""
241
- patches = _parse_patch(output_str)
242
- ret = "" # Initialize return value
243
-
244
- for filepath, patch_info in patches.items():
245
- try:
246
- for patch in patch_info:
247
- patch_type = patch['type']
248
-
249
- if patch_type == 'MOVE_FILE':
250
- handle_move_file(filepath, patch)
251
- elif patch_type == 'NEW_FILE':
252
- handle_new_file(filepath, patch)
253
- elif patch_type == 'REMOVE_FILE':
254
- handle_remove_file(filepath)
255
- else:
256
- handle_code_operation(filepath, patch)
257
-
258
- except Exception as e:
259
- PrettyOutput.print(f"应用 {patch_type} 操作到 {filepath} 失败: {str(e)}", OutputType.ERROR)
260
- continue
111
+ try:
112
+ patches = _parse_patch(output_str)
113
+ except Exception as e:
114
+ PrettyOutput.print(f"解析补丁失败: {str(e)}", OutputType.ERROR)
115
+ return ""
261
116
 
117
+ ret = ""
118
+
119
+ for filepath, patch_list in patches.items():
120
+ for patch in patch_list:
121
+ try:
122
+ handle_code_operation(filepath, patch)
123
+ PrettyOutput.print(f"成功处理 操作", OutputType.SUCCESS)
124
+ except Exception as e:
125
+ PrettyOutput.print(f"操作失败: {str(e)}", OutputType.ERROR)
126
+
262
127
  if has_uncommitted_changes():
263
- if handle_commit_workflow():
128
+ diff = get_diff()
129
+ if handle_commit_workflow(diff):
264
130
  ret += "Successfully applied the patch\n"
265
131
  # Get modified line ranges
266
132
  modified_ranges = get_modified_line_ranges()
@@ -269,25 +135,37 @@ def apply_patch(output_str: str) -> str:
269
135
  ret += "New code:\n"
270
136
  ret += modified_code["stdout"]
271
137
  else:
272
- ret += "User rejected the patch"
273
- user_input = get_multiline_input("你可以继续输入: ")
138
+ ret += "User rejected the patch\nThis is your patch preview:\n"
139
+ ret += diff
140
+ user_input = get_multiline_input("你可以继续输入(输入空行重试,Ctrl+C退出): ")
274
141
  if user_input:
275
142
  ret += "\n" + user_input
276
143
  else:
277
- return ""
144
+ ret = ""
278
145
 
279
146
  return ret # Ensure a string is always returned
280
-
281
- def handle_commit_workflow()->bool:
147
+
148
+ def get_diff() -> str:
149
+ """使用更安全的subprocess代替os.system"""
150
+ import subprocess
151
+ try:
152
+ subprocess.run(['git', 'add', '.'], check=True)
153
+ result = subprocess.run(
154
+ ['git', 'diff', 'HEAD'],
155
+ capture_output=True,
156
+ text=True,
157
+ check=True
158
+ )
159
+ return result.stdout
160
+ finally:
161
+ subprocess.run(['git', 'reset', 'HEAD'], check=True)
162
+
163
+ def handle_commit_workflow(diff:str)->bool:
282
164
  """Handle the git commit workflow and return the commit details.
283
165
 
284
166
  Returns:
285
167
  tuple[bool, str, str]: (continue_execution, commit_id, commit_message)
286
168
  """
287
- os.system("git add .")
288
- diff = os.popen("git diff HEAD").read()
289
- os.system("git reset HEAD")
290
- PrettyOutput.print(diff, OutputType.CODE, lang="diff")
291
169
  if not user_confirm("是否要提交代码?", default=True):
292
170
  os.system("git reset HEAD")
293
171
  os.system("git checkout -- .")
@@ -303,7 +181,7 @@ def get_modified_line_ranges() -> Dict[str, Tuple[int, int]]:
303
181
 
304
182
  Returns:
305
183
  Dictionary mapping file paths to tuple with (start_line, end_line) ranges
306
- for modified sections. Line numbers are 0-based.
184
+ for modified sections. Line numbers are 1-based.
307
185
  """
308
186
  # Get git diff for all files
309
187
  diff_output = os.popen("git show").read()
@@ -322,91 +200,70 @@ def get_modified_line_ranges() -> Dict[str, Tuple[int, int]]:
322
200
  # Match lines like "@@ -100,5 +100,7 @@" where the + part shows new lines
323
201
  range_match = re.match(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@", line)
324
202
  if range_match and current_file:
325
- start_line = int(range_match.group(1)) - 1 # Convert to 0-based
203
+ start_line = int(range_match.group(1)) # Keep as 1-based
326
204
  line_count = int(range_match.group(2)) if range_match.group(2) else 1
327
- end_line = start_line + line_count
205
+ end_line = start_line + line_count - 1
328
206
  result[current_file] = (start_line, end_line)
329
207
 
330
208
  return result
331
-
332
209
  # New handler functions below ▼▼▼
333
210
 
334
- def handle_move_file(filepath: str, patch: Dict[str, Any]):
335
- """Handle file moving operation"""
336
- new_path = patch['new_path']
337
- os.makedirs(os.path.dirname(new_path), exist_ok=True)
338
- if os.path.exists(filepath):
339
- os.rename(filepath, new_path)
340
- PrettyOutput.print(f"成功移动文件 {filepath} -> {new_path}", OutputType.SUCCESS)
341
- else:
342
- PrettyOutput.print(f"源文件不存在: {filepath}", OutputType.WARNING)
343
-
344
211
  def handle_new_file(filepath: str, patch: Dict[str, Any]):
345
- """Handle new file creation"""
346
- new_content = patch.get('content', '').splitlines(keepends=True)
347
- if new_content and new_content[-1] and new_content[-1][-1] != '\n':
348
- new_content[-1] += '\n'
349
-
212
+ """统一参数格式处理新文件"""
350
213
  os.makedirs(os.path.dirname(filepath), exist_ok=True)
351
214
  with open(filepath, 'w', encoding='utf-8') as f:
352
- f.writelines(new_content)
353
- PrettyOutput.print(f"成功创建新文件 {filepath}", OutputType.SUCCESS)
354
-
355
- def handle_remove_file(filepath: str):
356
- """Handle file removal"""
357
- if os.path.exists(filepath):
358
- os.remove(filepath)
359
- PrettyOutput.print(f"成功删除文件 {filepath}", OutputType.SUCCESS)
360
- else:
361
- PrettyOutput.print(f"文件不存在: {filepath}", OutputType.WARNING)
215
+ f.write(patch['content'])
362
216
 
363
217
  def handle_code_operation(filepath: str, patch: Dict[str, Any]):
364
- """Handle code modification operations (REPLACE/INSERT/DELETE)"""
365
- patch_type = patch['type']
366
- start_line = patch.get('start_line', 0)
367
- end_line = patch.get('end_line', 0)
368
- new_content = patch.get('content', '').splitlines(keepends=True)
369
-
370
- if not new_content:
371
- new_content = ['']
372
-
373
- PrettyOutput.print(f"patch_type: {patch_type}\nstart_line: {start_line}\nend_line: {end_line}\nnew_content:\n{new_content}", OutputType.INFO)
374
-
375
- if new_content and new_content[-1] and new_content[-1][-1] != '\n':
376
- new_content[-1] += '\n'
377
-
378
- if not os.path.exists(filepath):
379
- PrettyOutput.print(f"文件不存在: {filepath}", OutputType.WARNING)
380
- return
218
+ """处理紧凑格式补丁"""
219
+ try:
220
+ # 新建文件时强制覆盖
221
+ os.makedirs(os.path.dirname(filepath), exist_ok=True)
222
+ if not os.path.exists(filepath):
223
+ open(filepath, 'w', encoding='utf-8').close()
224
+ with open(filepath, 'r+', encoding='utf-8') as f:
225
+ lines = f.readlines()
226
+
227
+ new_lines = validate_and_apply_changes(
228
+ lines,
229
+ patch['start'],
230
+ patch['end'],
231
+ patch['content']
232
+ )
233
+
234
+ f.seek(0)
235
+ f.writelines(new_lines)
236
+ f.truncate()
381
237
 
382
- with open(filepath, 'r+', encoding='utf-8') as f:
383
- lines = f.readlines()
384
- validate_and_apply_changes(patch_type, lines, start_line, end_line, new_content)
385
- f.seek(0)
386
- f.writelines(lines)
387
- f.truncate()
238
+ PrettyOutput.print(f"成功更新 {filepath}", OutputType.SUCCESS)
388
239
 
389
- PrettyOutput.print(f"成功对 {filepath} 执行 {patch_type} 操作", OutputType.SUCCESS)
240
+ except Exception as e:
241
+ PrettyOutput.print(f"操作失败: {str(e)}", OutputType.ERROR)
390
242
 
391
243
  def validate_and_apply_changes(
392
- patch_type: str,
393
244
  lines: List[str],
394
- start_line: int,
395
- end_line: int,
396
- new_content: List[str]
397
- ):
398
- """Validate and apply code changes to in-memory file content"""
399
- if patch_type in ['REPLACE', 'DELETE']:
400
- if start_line < 1 or end_line > len(lines) or start_line > end_line:
401
- raise ValueError(f"Invalid line range [{start_line}, {end_line}] (total lines: {len(lines)})")
402
-
403
- if patch_type == 'REPLACE':
404
- lines[start_line-1:end_line] = new_content
405
- else: # DELETE
406
- lines[start_line-1:end_line] = []
407
-
408
- elif patch_type == 'INSERT':
409
- if start_line < 1 or start_line > len(lines) + 1:
410
- raise ValueError(f"Invalid insertion position [{start_line}] (valid range: 1-{len(lines)+1})")
411
-
412
- lines[start_line-1:start_line-1] = new_content
245
+ start: int,
246
+ end: int,
247
+ content: str
248
+ ) -> List[str]:
249
+
250
+ new_content = content.splitlines(keepends=True)
251
+
252
+ # 插入操作处理
253
+ if start == end:
254
+ if start < 1 or start > len(lines)+1:
255
+ raise ValueError(f"无效插入位置: {start}")
256
+ # 在指定位置前插入
257
+ return lines[:start-1] + new_content + lines[start-1:]
258
+
259
+ # 范围替换/删除操作
260
+ if start > end:
261
+ raise ValueError(f"起始行{start}不能大于结束行{end}")
262
+
263
+ max_line = len(lines)
264
+ # 自动修正行号范围
265
+ start = max(1, min(start, max_line+1))
266
+ end = max(start, min(end, max_line+1))
267
+
268
+ # 执行替换
269
+ return lines[:start-1] + new_content + lines[end-1:]
@@ -60,14 +60,12 @@ Identify customization options:
60
60
  - "Should we create a new class?"
61
61
 
62
62
  # 🎨 Question Template
63
- ```
63
+ 3-5 specific questions about existing implementations:
64
64
  <QUESTION>
65
- [3-5 specific questions about existing implementations:
66
65
  1. System architecture question
67
66
  2. Implementation details question
68
- 3. Integration or extension question]
67
+ 3. Integration or extension question
69
68
  </QUESTION>
70
- ```
71
69
 
72
70
  # 🔎 Investigation Focus
73
71
  1. Current System
@@ -336,7 +336,7 @@ Content: {content}
336
336
  vectors = np.vstack(vectors)
337
337
  if len(vectors) != len(ids):
338
338
  PrettyOutput.print(f"向量数量不匹配: {len(vectors)} 个向量 vs {len(ids)} 个ID",
339
- output_type=OutputType.ERROR)
339
+ output_type=OutputType.WARNING)
340
340
  self.index = None
341
341
  return
342
342
 
@@ -441,7 +441,7 @@ Content: {content}
441
441
  output_lines.append("删除的文件:")
442
442
  output_lines.extend(f" {f}" for f in deleted_files)
443
443
 
444
- PrettyOutput.print("\n".join(output_lines), output_type=OutputType.WARNING)
444
+ PrettyOutput.print("\n".join(output_lines), output_type=OutputType.INFO)
445
445
 
446
446
  # If force is True, continue directly
447
447
  if not force:
@@ -59,8 +59,8 @@ class LSPRegistry:
59
59
 
60
60
  if missing_methods:
61
61
  PrettyOutput.print(
62
- f"LSP {lsp_class.__name__} is missing necessary methods: {', '.join(missing_methods)}",
63
- OutputType.ERROR
62
+ f"LSP {lsp_class.__name__} 缺少必要的方法: {', '.join(missing_methods)}",
63
+ OutputType.WARNING
64
64
  )
65
65
  return False
66
66
 
@@ -72,7 +72,7 @@ class LSPRegistry:
72
72
  lsp_servers = {}
73
73
 
74
74
  if not os.path.exists(directory):
75
- PrettyOutput.print(f"LSP 目录不存在: {directory}", OutputType.ERROR)
75
+ PrettyOutput.print(f"LSP 目录不存在: {directory}", OutputType.WARNING)
76
76
  return lsp_servers
77
77
 
78
78
  package_name = None
@@ -138,7 +138,7 @@ class LSPRegistry:
138
138
  def create_lsp(self, language: str) -> Optional[BaseLSP]:
139
139
  """Create LSP instance for specified language."""
140
140
  if language not in self.lsp_servers:
141
- PrettyOutput.print(f"没有找到 LSP 支持的语言: {language}", OutputType.ERROR)
141
+ PrettyOutput.print(f"没有找到 LSP 支持的语言: {language}", OutputType.WARNING)
142
142
  return None
143
143
 
144
144
  try:
@@ -186,11 +186,11 @@ def main():
186
186
  lsp = registry.create_lsp(args.language)
187
187
 
188
188
  if not lsp:
189
- PrettyOutput.print(f"没有 LSP 支持的语言: {args.language}", OutputType.ERROR)
189
+ PrettyOutput.print(f"没有 LSP 支持的语言: {args.language}", OutputType.WARNING)
190
190
  return 1
191
191
 
192
192
  if not lsp.initialize(os.path.dirname(os.path.abspath(args.file))):
193
- PrettyOutput.print("LSP 初始化失败", OutputType.ERROR)
193
+ PrettyOutput.print("LSP 初始化失败", OutputType.WARNING)
194
194
  return 1
195
195
 
196
196
  try:
@@ -208,7 +208,7 @@ def main():
208
208
 
209
209
  elif args.action in ('references', 'definition'):
210
210
  if args.line is None or args.character is None:
211
- PrettyOutput.print("需要行和字符位置用于 references/definition", OutputType.ERROR)
211
+ PrettyOutput.print("需要行和字符位置用于 references/definition", OutputType.WARNING)
212
212
  return 1
213
213
 
214
214
  if args.action == 'references':