auto-coder 0.1.363__py3-none-any.whl → 0.1.365__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 auto-coder might be problematic. Click here for more details.

Files changed (39) hide show
  1. {auto_coder-0.1.363.dist-info → auto_coder-0.1.365.dist-info}/METADATA +2 -2
  2. {auto_coder-0.1.363.dist-info → auto_coder-0.1.365.dist-info}/RECORD +39 -23
  3. autocoder/agent/base_agentic/tools/execute_command_tool_resolver.py +1 -1
  4. autocoder/auto_coder.py +46 -2
  5. autocoder/auto_coder_runner.py +2 -0
  6. autocoder/common/__init__.py +5 -0
  7. autocoder/common/file_checkpoint/__init__.py +21 -0
  8. autocoder/common/file_checkpoint/backup.py +264 -0
  9. autocoder/common/file_checkpoint/conversation_checkpoint.py +182 -0
  10. autocoder/common/file_checkpoint/examples.py +217 -0
  11. autocoder/common/file_checkpoint/manager.py +611 -0
  12. autocoder/common/file_checkpoint/models.py +156 -0
  13. autocoder/common/file_checkpoint/store.py +383 -0
  14. autocoder/common/file_checkpoint/test_backup.py +242 -0
  15. autocoder/common/file_checkpoint/test_manager.py +570 -0
  16. autocoder/common/file_checkpoint/test_models.py +360 -0
  17. autocoder/common/file_checkpoint/test_store.py +327 -0
  18. autocoder/common/file_checkpoint/test_utils.py +297 -0
  19. autocoder/common/file_checkpoint/utils.py +119 -0
  20. autocoder/common/rulefiles/autocoderrules_utils.py +114 -55
  21. autocoder/common/save_formatted_log.py +76 -5
  22. autocoder/common/utils_code_auto_generate.py +2 -1
  23. autocoder/common/v2/agent/agentic_edit.py +545 -225
  24. autocoder/common/v2/agent/agentic_edit_tools/list_files_tool_resolver.py +83 -43
  25. autocoder/common/v2/agent/agentic_edit_tools/read_file_tool_resolver.py +116 -29
  26. autocoder/common/v2/agent/agentic_edit_tools/replace_in_file_tool_resolver.py +179 -48
  27. autocoder/common/v2/agent/agentic_edit_tools/search_files_tool_resolver.py +101 -56
  28. autocoder/common/v2/agent/agentic_edit_tools/test_write_to_file_tool_resolver.py +322 -0
  29. autocoder/common/v2/agent/agentic_edit_tools/write_to_file_tool_resolver.py +173 -132
  30. autocoder/common/v2/agent/agentic_edit_types.py +4 -0
  31. autocoder/compilers/normal_compiler.py +64 -0
  32. autocoder/events/event_manager_singleton.py +133 -4
  33. autocoder/linters/normal_linter.py +373 -0
  34. autocoder/linters/python_linter.py +4 -2
  35. autocoder/version.py +1 -1
  36. {auto_coder-0.1.363.dist-info → auto_coder-0.1.365.dist-info}/LICENSE +0 -0
  37. {auto_coder-0.1.363.dist-info → auto_coder-0.1.365.dist-info}/WHEEL +0 -0
  38. {auto_coder-0.1.363.dist-info → auto_coder-0.1.365.dist-info}/entry_points.txt +0 -0
  39. {auto_coder-0.1.363.dist-info → auto_coder-0.1.365.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,297 @@
1
+ import pytest
2
+ import os
3
+ import tempfile
4
+ import shutil
5
+ import json
6
+ from pathlib import Path
7
+ from unittest.mock import patch, MagicMock
8
+
9
+ from autocoder.common.file_checkpoint.utils import (
10
+ apply_shadow_changes, undo_last_changes, get_change_history
11
+ )
12
+ from autocoder.common.file_checkpoint.models import FileChange
13
+ from autocoder.common.file_checkpoint.manager import FileChangeManager
14
+
15
+ @pytest.fixture
16
+ def temp_test_dir():
17
+ """提供一个临时的测试目录"""
18
+ temp_dir = tempfile.mkdtemp()
19
+ yield temp_dir
20
+ shutil.rmtree(temp_dir)
21
+
22
+ @pytest.fixture
23
+ def sample_file(temp_test_dir):
24
+ """创建一个用于测试的样例文件"""
25
+ file_path = os.path.join(temp_test_dir, "sample.txt")
26
+ with open(file_path, 'w', encoding='utf-8') as f:
27
+ f.write("这是一个测试文件的内容")
28
+ return file_path
29
+
30
+ class TestApplyShadowChanges:
31
+ """apply_shadow_changes函数的单元测试"""
32
+
33
+ def test_apply_new_file(self, temp_test_dir):
34
+ """测试应用新文件变更"""
35
+ # 准备影子系统变更
36
+ changes = {
37
+ "new_file.txt": {
38
+ "content": "这是新文件内容"
39
+ }
40
+ }
41
+
42
+ # 应用变更
43
+ result = apply_shadow_changes(temp_test_dir, changes)
44
+
45
+ # 检查结果
46
+ assert result['success'] is True
47
+ assert len(result['change_ids']) == 1
48
+ assert not result['errors']
49
+ assert "new_file.txt" in result['changed_files']
50
+
51
+ # 检查文件是否被创建
52
+ new_file_path = os.path.join(temp_test_dir, "new_file.txt")
53
+ assert os.path.exists(new_file_path)
54
+
55
+ # 检查文件内容
56
+ with open(new_file_path, 'r', encoding='utf-8') as f:
57
+ content = f.read()
58
+ assert content == "这是新文件内容"
59
+
60
+ def test_apply_modify_file(self, temp_test_dir, sample_file):
61
+ """测试应用修改文件变更"""
62
+ # 获取相对路径
63
+ rel_path = os.path.relpath(sample_file, temp_test_dir)
64
+
65
+ # 准备影子系统变更
66
+ changes = {
67
+ rel_path: {
68
+ "content": "修改后的内容"
69
+ }
70
+ }
71
+
72
+ # 应用变更
73
+ result = apply_shadow_changes(temp_test_dir, changes)
74
+
75
+ # 检查结果
76
+ assert result['success'] is True
77
+ assert len(result['change_ids']) == 1
78
+ assert not result['errors']
79
+ assert rel_path in result['changed_files']
80
+
81
+ # 检查文件内容
82
+ with open(sample_file, 'r', encoding='utf-8') as f:
83
+ content = f.read()
84
+ assert content == "修改后的内容"
85
+
86
+ def test_apply_delete_file(self, temp_test_dir, sample_file):
87
+ """测试应用删除文件变更"""
88
+ # 获取相对路径
89
+ rel_path = os.path.relpath(sample_file, temp_test_dir)
90
+
91
+ # 准备影子系统变更
92
+ changes = {
93
+ rel_path: {
94
+ "content": "",
95
+ "is_deletion": True
96
+ }
97
+ }
98
+
99
+ # 应用变更
100
+ result = apply_shadow_changes(temp_test_dir, changes)
101
+
102
+ # 检查结果
103
+ assert result['success'] is True
104
+ assert len(result['change_ids']) == 1
105
+ assert not result['errors']
106
+ assert rel_path in result['changed_files']
107
+
108
+ # 检查文件是否被删除
109
+ assert not os.path.exists(sample_file)
110
+
111
+ def test_apply_multiple_changes(self, temp_test_dir, sample_file):
112
+ """测试应用多个文件变更"""
113
+ # 获取相对路径
114
+ rel_path = os.path.relpath(sample_file, temp_test_dir)
115
+
116
+ # 准备影子系统变更
117
+ changes = {
118
+ rel_path: {
119
+ "content": "修改后的内容"
120
+ },
121
+ "new_file.txt": {
122
+ "content": "新文件内容"
123
+ }
124
+ }
125
+
126
+ # 应用变更
127
+ result = apply_shadow_changes(temp_test_dir, changes)
128
+
129
+ # 检查结果
130
+ assert result['success'] is True
131
+ assert len(result['change_ids']) == 2
132
+ assert not result['errors']
133
+ assert rel_path in result['changed_files']
134
+ assert "new_file.txt" in result['changed_files']
135
+
136
+ # 检查文件内容
137
+ with open(sample_file, 'r', encoding='utf-8') as f:
138
+ content = f.read()
139
+ assert content == "修改后的内容"
140
+
141
+ new_file_path = os.path.join(temp_test_dir, "new_file.txt")
142
+ assert os.path.exists(new_file_path)
143
+ with open(new_file_path, 'r', encoding='utf-8') as f:
144
+ content = f.read()
145
+ assert content == "新文件内容"
146
+
147
+
148
+ class TestUndoLastChanges:
149
+ """undo_last_changes函数的单元测试"""
150
+
151
+ def test_undo_single_change(self, temp_test_dir, sample_file):
152
+ """测试撤销单个变更"""
153
+ # 获取相对路径
154
+ rel_path = os.path.relpath(sample_file, temp_test_dir)
155
+
156
+ # 先应用变更
157
+ changes = {
158
+ rel_path: {
159
+ "content": "修改后的内容"
160
+ }
161
+ }
162
+ apply_result = apply_shadow_changes(temp_test_dir, changes)
163
+ assert apply_result['success'] is True
164
+
165
+ # 检查文件已被修改
166
+ with open(sample_file, 'r', encoding='utf-8') as f:
167
+ content = f.read()
168
+ assert content == "修改后的内容"
169
+
170
+ # 撤销变更
171
+ undo_result = undo_last_changes(temp_test_dir)
172
+
173
+ # 检查撤销结果
174
+ assert undo_result['success'] is True
175
+ assert len(undo_result['restored_files']) == 1
176
+ assert rel_path in undo_result['restored_files']
177
+
178
+ # 检查文件内容是否被恢复
179
+ with open(sample_file, 'r', encoding='utf-8') as f:
180
+ content = f.read()
181
+ assert content == "这是一个测试文件的内容"
182
+
183
+ def test_undo_multiple_steps(self, temp_test_dir, sample_file):
184
+ """测试撤销多个步骤"""
185
+ # 获取相对路径
186
+ rel_path = os.path.relpath(sample_file, temp_test_dir)
187
+
188
+ # 应用第一个变更
189
+ changes1 = {
190
+ rel_path: {
191
+ "content": "第一次修改"
192
+ }
193
+ }
194
+ apply_result1 = apply_shadow_changes(temp_test_dir, changes1)
195
+ assert apply_result1['success'] is True
196
+
197
+ # 应用第二个变更
198
+ changes2 = {
199
+ rel_path: {
200
+ "content": "第二次修改"
201
+ }
202
+ }
203
+ apply_result2 = apply_shadow_changes(temp_test_dir, changes2)
204
+ assert apply_result2['success'] is True
205
+
206
+ # 检查文件内容
207
+ with open(sample_file, 'r', encoding='utf-8') as f:
208
+ content = f.read()
209
+ assert content == "第二次修改"
210
+
211
+ # 撤销两个步骤
212
+ undo_result = undo_last_changes(temp_test_dir, steps=2)
213
+
214
+ # 检查撤销结果
215
+ assert undo_result['success'] is True
216
+ assert len(undo_result['restored_files']) == 2
217
+
218
+ # 检查文件内容是否被恢复到原始状态
219
+ with open(sample_file, 'r', encoding='utf-8') as f:
220
+ content = f.read()
221
+ assert content == "这是一个测试文件的内容"
222
+
223
+ def test_undo_with_error(self, temp_test_dir):
224
+ """测试撤销出错的情况"""
225
+ # 模拟FileChangeManager的undo_last_change方法返回错误
226
+ with patch.object(FileChangeManager, 'undo_last_change') as mock_undo:
227
+ from autocoder.common.file_checkpoint.models import UndoResult
228
+ error_result = UndoResult(success=False)
229
+ error_result.add_error("test.txt", "文件不存在")
230
+ mock_undo.return_value = error_result
231
+
232
+ # 尝试撤销变更
233
+ undo_result = undo_last_changes(temp_test_dir)
234
+
235
+ # 检查撤销结果
236
+ assert undo_result['success'] is False
237
+ assert "test.txt" in undo_result['errors']
238
+ assert undo_result['errors']["test.txt"] == "文件不存在"
239
+
240
+
241
+ class TestGetChangeHistory:
242
+ """get_change_history函数的单元测试"""
243
+
244
+ def test_get_history(self, temp_test_dir, sample_file):
245
+ """测试获取变更历史"""
246
+ # 获取相对路径
247
+ rel_path = os.path.relpath(sample_file, temp_test_dir)
248
+
249
+ # 应用两个变更
250
+ changes1 = {
251
+ rel_path: {
252
+ "content": "第一次修改"
253
+ }
254
+ }
255
+ changes2 = {
256
+ "new_file.txt": {
257
+ "content": "新文件内容"
258
+ }
259
+ }
260
+
261
+ apply_shadow_changes(temp_test_dir, changes1)
262
+ apply_shadow_changes(temp_test_dir, changes2)
263
+
264
+ # 获取变更历史
265
+ history = get_change_history(temp_test_dir)
266
+
267
+ # 检查历史记录
268
+ assert len(history) >= 2
269
+
270
+ # 检查第一条记录(最新的,是new_file.txt)
271
+ assert history[0]['file_path'] == "new_file.txt"
272
+ assert history[0]['is_new'] is True
273
+
274
+ # 检查第二条记录(较早的,是修改的sample_file)
275
+ assert history[1]['file_path'] == rel_path
276
+ assert history[1]['is_new'] is False
277
+
278
+ def test_get_history_with_limit(self, temp_test_dir):
279
+ """测试限制历史记录数量"""
280
+ # 使用模拟的FileChangeManager
281
+ with patch.object(FileChangeManager, 'get_change_history') as mock_get_history:
282
+ # 创建模拟的返回值
283
+ from autocoder.common.file_checkpoint.models import ChangeRecord
284
+ mock_records = [
285
+ ChangeRecord.create(file_path=f"file{i}.txt", backup_id=f"backup{i}")
286
+ for i in range(5)
287
+ ]
288
+ mock_get_history.return_value = mock_records
289
+
290
+ # 请求限制为3条记录
291
+ history = get_change_history(temp_test_dir, limit=3)
292
+
293
+ # 检查参数调用
294
+ mock_get_history.assert_called_once_with(3)
295
+
296
+ # 检查结果
297
+ assert len(history) == 5 # 模拟返回了5条记录
@@ -0,0 +1,119 @@
1
+ """
2
+ 文件变更管理模块的工具函数
3
+
4
+ 提供一些常用的工具函数,简化文件变更管理模块的使用。
5
+ """
6
+
7
+ import os
8
+ import json
9
+ from typing import Dict, List, Optional, Any
10
+
11
+ from autocoder.common.file_checkpoint.models import FileChange
12
+ from autocoder.common.file_checkpoint.manager import FileChangeManager
13
+
14
+
15
+ def apply_shadow_changes(source_dir: str, changes: Dict[str, Any]) -> Dict[str, Any]:
16
+ """
17
+ 将影子系统的变更应用到用户项目中
18
+
19
+ Args:
20
+ source_dir: 用户项目的根目录
21
+ changes: 影子系统的变更字典,格式为 {file_path: {content: str}}
22
+
23
+ Returns:
24
+ Dict: 应用结果,包含成功和失败的信息
25
+ """
26
+ # 创建文件变更管理器
27
+ manager = FileChangeManager(source_dir)
28
+
29
+ # 将影子系统的变更转换为 FileChange 对象
30
+ file_changes = {}
31
+ for file_path, change in changes.items():
32
+ # 确定文件是否是新文件
33
+ abs_file_path = os.path.join(source_dir, file_path)
34
+ is_new = not os.path.exists(abs_file_path)
35
+
36
+ file_changes[file_path] = FileChange(
37
+ file_path=file_path,
38
+ content=change.get('content', ''),
39
+ is_new=is_new,
40
+ is_deletion=change.get('is_deletion', False)
41
+ )
42
+
43
+ # 应用变更
44
+ result = manager.apply_changes(file_changes)
45
+
46
+ # 返回结果
47
+ return {
48
+ 'success': result.success,
49
+ 'change_ids': result.change_ids,
50
+ 'errors': result.errors,
51
+ 'changed_files': list(file_changes.keys())
52
+ }
53
+
54
+
55
+ def undo_last_changes(source_dir: str, steps: int = 1) -> Dict[str, Any]:
56
+ """
57
+ 撤销最近的变更
58
+
59
+ Args:
60
+ source_dir: 用户项目的根目录
61
+ steps: 要撤销的步骤数
62
+
63
+ Returns:
64
+ Dict: 撤销结果,包含成功和失败的信息
65
+ """
66
+ # 创建文件变更管理器
67
+ manager = FileChangeManager(source_dir)
68
+
69
+ # 执行多步撤销
70
+ all_restored_files = []
71
+ all_errors = {}
72
+ success = True
73
+
74
+ for _ in range(steps):
75
+ result = manager.undo_last_change()
76
+ if not result.success:
77
+ success = False
78
+ all_errors.update(result.errors)
79
+ break
80
+
81
+ all_restored_files.extend(result.restored_files)
82
+
83
+ # 返回结果
84
+ return {
85
+ 'success': success,
86
+ 'restored_files': all_restored_files,
87
+ 'errors': all_errors
88
+ }
89
+
90
+
91
+ def get_change_history(source_dir: str, limit: int = 10) -> List[Dict[str, Any]]:
92
+ """
93
+ 获取变更历史记录
94
+
95
+ Args:
96
+ source_dir: 用户项目的根目录
97
+ limit: 返回的历史记录数量限制
98
+
99
+ Returns:
100
+ List[Dict]: 变更记录列表
101
+ """
102
+ # 创建文件变更管理器
103
+ manager = FileChangeManager(source_dir)
104
+
105
+ # 获取变更历史
106
+ changes = manager.get_change_history(limit)
107
+
108
+ # 转换为字典列表
109
+ return [
110
+ {
111
+ 'change_id': change.change_id,
112
+ 'timestamp': change.timestamp,
113
+ 'file_path': change.file_path,
114
+ 'is_new': change.is_new,
115
+ 'is_deletion': change.is_deletion,
116
+ 'group_id': change.group_id
117
+ }
118
+ for change in changes
119
+ ]
@@ -10,6 +10,7 @@ import byzerllm # Added import
10
10
  from pydantic import BaseModel, Field
11
11
  from typing import List, Dict, Optional, Any # Added Any
12
12
  from autocoder.common import AutoCoderArgs
13
+ import concurrent.futures # 添加线程池导入
13
14
 
14
15
  # 尝试导入 FileMonitor
15
16
  try:
@@ -25,7 +26,7 @@ class RuleFile(BaseModel):
25
26
  """规则文件的Pydantic模型"""
26
27
  description: str = Field(default="", description="规则的描述")
27
28
  globs: List[str] = Field(default_factory=list, description="文件匹配模式列表")
28
- always_apply: bool = Field(default=False, alias="alwaysApply", description="是否总是应用规则")
29
+ always_apply: bool = Field(default=False, description="是否总是应用规则")
29
30
  content: str = Field(default="", description="规则文件的正文内容")
30
31
  file_path: str = Field(default="", description="规则文件的路径")
31
32
 
@@ -206,15 +207,14 @@ class AutocoderRulesManager:
206
207
  except Exception as e:
207
208
  logger.warning(f"解析规则文件YAML头部时出错: {e}")
208
209
 
209
- # 创建并返回Pydantic模型
210
+ # 创建并返回Pydantic模型
210
211
  rule = RuleFile(
211
212
  description=metadata.get('description', ''),
212
213
  globs=metadata.get('globs', []),
213
214
  always_apply=metadata.get('alwaysApply', False),
214
215
  content=markdown_content.strip(),
215
216
  file_path=file_path
216
- )
217
-
217
+ )
218
218
  return rule
219
219
 
220
220
  except Exception as e:
@@ -337,10 +337,49 @@ class RuleSelector:
337
337
  "rule": rule,
338
338
  "context": context
339
339
  }
340
+
341
+ def _evaluate_rule(self, rule: RuleFile, context: str) -> tuple[RuleFile, bool, Optional[str]]:
342
+ """
343
+ 评估单个规则是否适用于当前上下文。
344
+
345
+ Args:
346
+ rule: 要评估的规则
347
+ context: 上下文信息
348
+
349
+ Returns:
350
+ tuple: (规则, 是否选中, 理由)
351
+ """
352
+ # 如果规则设置为总是应用,直接返回选中
353
+ if rule.always_apply:
354
+ return (rule, True, "规则设置为总是应用")
355
+
356
+ # 如果没有LLM,无法评估non-always规则
357
+ if self.llm is None:
358
+ return (rule, False, "未提供LLM,无法评估non-always规则")
359
+
360
+ try:
361
+ prompt = self._build_selection_prompt.prompt(rule=rule, context=context)
362
+ logger.debug(f"为规则 '{os.path.basename(rule.file_path)}' 生成的判断 Prompt (片段): {prompt[:200]}...")
363
+
364
+ result = None
365
+ try:
366
+ # 使用with_return_type方法获取结构化结果
367
+ result = self._build_selection_prompt.with_llm(self.llm).with_return_type(RuleRelevance).run(rule=rule, context=context)
368
+ if result and result.is_relevant:
369
+ return (rule, True, result.reason)
370
+ else:
371
+ return (rule, False, result.reason if result else "未提供理由")
372
+ except Exception as e:
373
+ logger.warning(f"LLM 未能为规则 '{os.path.basename(rule.file_path)}' 提供有效响应: {e}")
374
+ return (rule, False, f"LLM评估出错: {str(e)}")
375
+
376
+ except Exception as e:
377
+ logger.error(f"评估规则 '{os.path.basename(rule.file_path)}' 时出错: {e}", exc_info=True)
378
+ return (rule, False, f"评估过程出错: {str(e)}")
340
379
 
341
- def select_rules(self, context: str, rules: List[RuleFile]) -> List[RuleFile]:
380
+ def select_rules(self, context: str) -> List[RuleFile]:
342
381
  """
343
- 选择适用于当前上下文的规则。
382
+ 选择适用于当前上下文的规则。使用线程池并发评估规则。
344
383
 
345
384
  Args:
346
385
  context: 可选的字典,包含用于规则选择的上下文信息 (例如,用户指令、目标文件等)。
@@ -348,56 +387,47 @@ class RuleSelector:
348
387
  Returns:
349
388
  List[RuleFile]: 选定的规则列表。
350
389
  """
390
+ rules = get_parsed_rules()
351
391
  selected_rules: List[RuleFile] = []
352
392
  logger.info(f"开始选择规则,总规则数: {len(rules)}")
353
-
393
+
394
+ # 预先分类处理always_apply规则
395
+ always_apply_rules = []
396
+ need_llm_rules = []
397
+
354
398
  for rule in rules:
355
399
  if rule.always_apply:
356
- selected_rules.append(rule)
357
- logger.debug(f"规则 '{os.path.basename(rule.file_path)}' (AlwaysApply=True) 已自动选择。")
358
- continue
359
-
360
- if self.llm is None:
361
- logger.debug(f"规则 '{os.path.basename(rule.file_path)}' (AlwaysApply=False) 已跳过,因为未提供 LLM。")
362
- continue
363
-
364
- # 对于 alwaysApply=False 的规则,使用 LLM 判断
365
- try:
366
- prompt = self._build_selection_prompt.prompt(rule=rule, context=context)
367
- logger.debug(f"为规则 '{os.path.basename(rule.file_path)}' 生成的判断 Prompt (片段): {prompt[:200]}...")
368
-
369
- # **** 实际LLM调用 ****
370
- # 确保 self.llm 实例已正确初始化并可用
371
- if self.llm: # Check if llm is not None
372
- result = None
373
- try:
374
- # 使用with_return_type方法获取结构化结果
375
- result = self._build_selection_prompt.with_llm(self.llm).with_return_type(RuleRelevance).run(rule=rule, context=context)
376
- if result and result.is_relevant:
377
- selected_rules.append(rule)
378
- logger.info(f"规则 '{os.path.basename(rule.file_path)}' (AlwaysApply=False) 已被 LLM 选择,原因: {result.reason}")
379
- else:
380
- logger.debug(f"规则 '{os.path.basename(rule.file_path)}' (AlwaysApply=False) 未被 LLM 选择,原因: {result.reason if result else '未提供'}")
381
- except Exception as e:
382
- logger.warning(f"LLM 未能为规则 '{os.path.basename(rule.file_path)}' 提供有效响应。")
383
- # 根据需要决定是否跳过或默认不选
384
- continue # 跳过此规则
385
- else: # Handle case where self.llm is None after the initial check
386
- logger.warning(f"LLM instance became None unexpectedly for rule '{os.path.basename(rule.file_path)}'.")
387
- continue
388
-
389
- # **** 模拟LLM调用 (用于测试/开发) ****
390
- # 注释掉模拟部分,使用上面的实际调用
391
- # simulated_response = "yes" if "always" in rule.description.lower() or "index" in rule.description.lower() else "no"
392
- # logger.warning(f"模拟LLM判断规则 '{os.path.basename(rule.file_path)}': {simulated_response}")
393
- # response_text = simulated_response
394
- # **** 结束模拟 ****
395
-
396
- except Exception as e:
397
- logger.error(f"使用 LLM 判断规则 '{os.path.basename(rule.file_path)}' 时出错: {e}", exc_info=True)
398
- # 根据策略决定是否包含出错的规则,这里选择跳过
399
- continue
400
-
400
+ always_apply_rules.append(rule)
401
+ elif self.llm is not None:
402
+ need_llm_rules.append(rule)
403
+
404
+ # 添加always_apply规则
405
+ for rule in always_apply_rules:
406
+ selected_rules.append(rule)
407
+ logger.debug(f"规则 '{os.path.basename(rule.file_path)}' (AlwaysApply=True) 已自动选择。")
408
+
409
+ # 如果没有需要LLM评估的规则,直接返回结果
410
+ if not need_llm_rules:
411
+ logger.info(f"规则选择完成,选中规则数: {len(selected_rules)}")
412
+ return selected_rules
413
+
414
+ # 使用线程池并发评估规则
415
+ with concurrent.futures.ThreadPoolExecutor() as executor:
416
+ # 提交所有评估任务
417
+ future_to_rule = {
418
+ executor.submit(self._evaluate_rule, rule, context): rule
419
+ for rule in need_llm_rules
420
+ }
421
+
422
+ # 收集评估结果
423
+ for future in concurrent.futures.as_completed(future_to_rule):
424
+ rule, is_selected, reason = future.result()
425
+ if is_selected:
426
+ selected_rules.append(rule)
427
+ logger.info(f"规则 '{os.path.basename(rule.file_path)}' (AlwaysApply=False) 已被 LLM 选择,原因: {reason}")
428
+ else:
429
+ logger.debug(f"规则 '{os.path.basename(rule.file_path)}' (AlwaysApply=False) 未被 LLM 选择,原因: {reason}")
430
+
401
431
  logger.info(f"规则选择完成,选中规则数: {len(selected_rules)}")
402
432
  return selected_rules
403
433
 
@@ -417,9 +447,38 @@ class RuleSelector:
417
447
  # 保持 file_path 作为 key
418
448
  return {rule.file_path: rule.content for rule in selected_rules}
419
449
 
420
- def auto_select_rules(context: str, rules: List[RuleFile], llm: Optional[byzerllm.ByzerLLM] = None,args:Optional[AutoCoderArgs] = None) -> List[RuleFile]:
450
+ def auto_select_rules(context: str, llm: Optional[byzerllm.ByzerLLM] = None,args:Optional[AutoCoderArgs] = None) -> List[Dict[str, str]]:
421
451
  """
422
452
  根据LLM的判断和规则元数据选择适用的规则。
423
453
  """
424
- selector = RuleSelector(llm=llm, args=args)
425
- return selector.select_rules(context=context, rules=rules)
454
+ selector = RuleSelector(llm=llm, args=args)
455
+ return selector.get_selected_rules_content(context=context)
456
+
457
+ def get_required_and_index_rules() -> Dict[str, str]:
458
+ """
459
+ 获取所有必须应用的规则文件(always_apply=True)和Index.md文件。
460
+
461
+ Args:
462
+ project_root: 可选的项目根目录路径,用于初始化规则管理器。
463
+
464
+ Returns:
465
+ Dict[str, str]: 包含必须应用的规则和Index.md文件的{file_path: content}字典。
466
+ """
467
+ # 获取所有解析后的规则文件
468
+ parsed_rules = get_parsed_rules()
469
+ result: Dict[str, str] = {}
470
+ logger.info(f"获取所有解析后的规则文件完成,总数: {len(parsed_rules)}")
471
+
472
+ for rule in parsed_rules:
473
+ # 检查是否是always_apply=True的规则
474
+ if rule.always_apply:
475
+ result[rule.file_path] = rule.content
476
+ logger.info(f"添加必须应用的规则: {os.path.basename(rule.file_path)}")
477
+
478
+ # 检查是否是Index.md文件
479
+ if os.path.basename(rule.file_path).lower() == "index.md":
480
+ result[rule.file_path] = rule.content
481
+ logger.info(f"添加Index.md文件: {rule.file_path}")
482
+
483
+ logger.info(f"获取必须应用的规则和Index.md文件完成,总数: {len(result)}")
484
+ return result