auto-coder 0.1.361__py3-none-any.whl → 0.1.363__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 (57) hide show
  1. {auto_coder-0.1.361.dist-info → auto_coder-0.1.363.dist-info}/METADATA +2 -1
  2. {auto_coder-0.1.361.dist-info → auto_coder-0.1.363.dist-info}/RECORD +57 -29
  3. autocoder/agent/auto_learn.py +249 -262
  4. autocoder/agent/base_agentic/__init__.py +0 -0
  5. autocoder/agent/base_agentic/agent_hub.py +169 -0
  6. autocoder/agent/base_agentic/agentic_lang.py +112 -0
  7. autocoder/agent/base_agentic/agentic_tool_display.py +180 -0
  8. autocoder/agent/base_agentic/base_agent.py +1582 -0
  9. autocoder/agent/base_agentic/default_tools.py +683 -0
  10. autocoder/agent/base_agentic/test_base_agent.py +82 -0
  11. autocoder/agent/base_agentic/tool_registry.py +425 -0
  12. autocoder/agent/base_agentic/tools/__init__.py +12 -0
  13. autocoder/agent/base_agentic/tools/ask_followup_question_tool_resolver.py +72 -0
  14. autocoder/agent/base_agentic/tools/attempt_completion_tool_resolver.py +37 -0
  15. autocoder/agent/base_agentic/tools/base_tool_resolver.py +35 -0
  16. autocoder/agent/base_agentic/tools/example_tool_resolver.py +46 -0
  17. autocoder/agent/base_agentic/tools/execute_command_tool_resolver.py +72 -0
  18. autocoder/agent/base_agentic/tools/list_files_tool_resolver.py +110 -0
  19. autocoder/agent/base_agentic/tools/plan_mode_respond_tool_resolver.py +35 -0
  20. autocoder/agent/base_agentic/tools/read_file_tool_resolver.py +54 -0
  21. autocoder/agent/base_agentic/tools/replace_in_file_tool_resolver.py +156 -0
  22. autocoder/agent/base_agentic/tools/search_files_tool_resolver.py +134 -0
  23. autocoder/agent/base_agentic/tools/talk_to_group_tool_resolver.py +96 -0
  24. autocoder/agent/base_agentic/tools/talk_to_tool_resolver.py +79 -0
  25. autocoder/agent/base_agentic/tools/use_mcp_tool_resolver.py +44 -0
  26. autocoder/agent/base_agentic/tools/write_to_file_tool_resolver.py +58 -0
  27. autocoder/agent/base_agentic/types.py +189 -0
  28. autocoder/agent/base_agentic/utils.py +100 -0
  29. autocoder/auto_coder.py +1 -1
  30. autocoder/auto_coder_runner.py +36 -14
  31. autocoder/chat/conf_command.py +11 -10
  32. autocoder/commands/auto_command.py +227 -159
  33. autocoder/common/__init__.py +2 -2
  34. autocoder/common/ignorefiles/ignore_file_utils.py +12 -8
  35. autocoder/common/result_manager.py +10 -2
  36. autocoder/common/rulefiles/autocoderrules_utils.py +169 -0
  37. autocoder/common/save_formatted_log.py +1 -1
  38. autocoder/common/v2/agent/agentic_edit.py +53 -41
  39. autocoder/common/v2/agent/agentic_edit_tools/read_file_tool_resolver.py +15 -12
  40. autocoder/common/v2/agent/agentic_edit_tools/replace_in_file_tool_resolver.py +73 -1
  41. autocoder/common/v2/agent/agentic_edit_tools/write_to_file_tool_resolver.py +132 -4
  42. autocoder/common/v2/agent/agentic_edit_types.py +1 -2
  43. autocoder/common/v2/agent/agentic_tool_display.py +2 -3
  44. autocoder/common/v2/code_auto_generate_editblock.py +3 -1
  45. autocoder/index/index.py +14 -8
  46. autocoder/privacy/model_filter.py +297 -35
  47. autocoder/rag/long_context_rag.py +424 -397
  48. autocoder/rag/test_doc_filter.py +393 -0
  49. autocoder/rag/test_long_context_rag.py +473 -0
  50. autocoder/rag/test_token_limiter.py +342 -0
  51. autocoder/shadows/shadow_manager.py +1 -3
  52. autocoder/utils/_markitdown.py +22 -3
  53. autocoder/version.py +1 -1
  54. {auto_coder-0.1.361.dist-info → auto_coder-0.1.363.dist-info}/LICENSE +0 -0
  55. {auto_coder-0.1.361.dist-info → auto_coder-0.1.363.dist-info}/WHEEL +0 -0
  56. {auto_coder-0.1.361.dist-info → auto_coder-0.1.363.dist-info}/entry_points.txt +0 -0
  57. {auto_coder-0.1.361.dist-info → auto_coder-0.1.363.dist-info}/top_level.txt +0 -0
@@ -1,93 +1,355 @@
1
1
  import re
2
2
  import yaml
3
3
  from pathlib import Path
4
- from typing import Dict, List, Optional
4
+ from typing import Dict, List, Optional, Tuple, Set, Literal, Union, Any
5
+ from enum import Enum
6
+ import pathspec
7
+ import os
8
+ from dataclasses import dataclass
9
+
5
10
  from autocoder.common import AutoCoderArgs
6
11
  from autocoder.utils import llms as llm_utils
7
12
 
13
+ # 尝试导入 FileMonitor
14
+ try:
15
+ from autocoder.common.file_monitor.monitor import FileMonitor, Change
16
+ except ImportError:
17
+ # 如果导入失败,提供一个空的实现
18
+ print("警告: 无法导入 FileMonitor,过滤器文件变更监控将不可用")
19
+ FileMonitor = None
20
+ Change = None
21
+
22
+
23
+ class Permission(str, Enum):
24
+ """权限枚举类型"""
25
+ ALLOW = "ALLOW" # 显式允许访问
26
+ DENY = "DENY" # 完全禁止访问
27
+ DENY_READ = "DENY_READ" # 禁止读取但允许写入
28
+ DENY_WRITE = "DENY_WRITE" # 禁止写入但允许读取
29
+
30
+
31
+ class AccessOperation(str, Enum):
32
+ """访问操作类型"""
33
+ READ = "READ"
34
+ WRITE = "WRITE"
35
+
36
+
37
+ @dataclass
38
+ class LineRange:
39
+ """行范围定义"""
40
+ start: int
41
+ end: int
42
+
43
+ def contains(self, line_number: int) -> bool:
44
+ """检查指定行号是否在范围内"""
45
+ return self.start <= line_number <= self.end
46
+
47
+
48
+ @dataclass
49
+ class AccessRule:
50
+ """访问规则"""
51
+ pattern: str
52
+ permission: Permission
53
+ line_ranges: List[LineRange] = None
54
+
55
+ @classmethod
56
+ def from_dict(cls, rule_dict: Dict[str, Any]) -> 'AccessRule':
57
+ """从字典创建规则"""
58
+ pattern = rule_dict.get('pattern')
59
+ permission = Permission(rule_dict.get('permission', 'DENY'))
60
+
61
+ line_ranges = None
62
+ ranges_data = rule_dict.get('line_ranges')
63
+ if ranges_data:
64
+ line_ranges = []
65
+ for range_dict in ranges_data:
66
+ start = range_dict.get('start', 1)
67
+ end = range_dict.get('end', float('inf'))
68
+ line_ranges.append(LineRange(start, end))
69
+
70
+ return cls(pattern=pattern, permission=permission, line_ranges=line_ranges)
71
+
8
72
 
9
73
  class ModelPathFilter:
10
74
  def __init__(self,
11
75
  model_name: str,
12
76
  args: AutoCoderArgs,
13
- default_forbidden: List[str] = None):
77
+ default_rules: List[Dict[str, Any]] = None):
14
78
  """
15
79
  模型路径过滤器
16
80
  :param model_name: 当前使用的模型名称
17
81
  :param args: 自动编码器参数
18
- :param default_forbidden: 默认禁止路径规则
82
+ :param default_rules: 默认访问规则
19
83
  """
20
84
  self.model_name = model_name
85
+ self.args = args
21
86
  if args.model_filter_path:
22
- self.config_path = Path(args.model_filter_path)
87
+ # 如果是相对路径,转换为绝对路径
88
+ path = Path(args.model_filter_path)
89
+ if not path.is_absolute():
90
+ path = Path(args.source_dir) / path
91
+ self.config_path = path
23
92
  else:
24
- self.config_path = Path(args.source_dir, ".model_filters.yml")
25
- self.default_forbidden = default_forbidden or []
26
- self._rules_cache: Dict[str, List[re.Pattern]] = {}
93
+ # 检查项目根目录
94
+ root_config = Path(args.source_dir) / ".model_filters.yml"
95
+ if root_config.exists():
96
+ self.config_path = root_config
97
+ else:
98
+ # 检查.auto-coder目录
99
+ auto_coder_config = Path(args.source_dir) / ".auto-coder" / ".model_filters.yml"
100
+ self.config_path = auto_coder_config
101
+
102
+ self.default_rules = default_rules or []
103
+ self._rules_cache: Dict[str, List[AccessRule]] = {}
104
+ self._path_specs: Dict[str, pathspec.PathSpec] = {}
105
+ self._file_monitor = None
27
106
  self._load_rules()
107
+ self._setup_file_monitor()
28
108
 
29
109
  def _load_rules(self):
30
- """加载并编译正则规则"""
110
+ """加载并编译路径过滤规则"""
31
111
  if not self.config_path.exists():
32
112
  return
33
113
 
34
114
  with open(self.config_path, 'r', encoding="utf-8") as f:
35
- config = yaml.safe_load(f)
115
+ config = yaml.safe_load(f) or {}
116
+
117
+ # 处理模型特定规则
118
+ model_config = config.get('model_filters', {}).get(self.model_name, {})
119
+ model_rules = model_config.get('rules', [])
120
+
121
+ # 处理默认规则
122
+ config_default_rules = config.get('default_rules', [])
123
+ all_rules = []
124
+
125
+ # 将所有规则转换为 AccessRule 对象
126
+ for rule_dict in model_rules + config_default_rules + self.default_rules:
127
+ if isinstance(rule_dict, str):
128
+ # 兼容旧版本的简单字符串格式
129
+ rule = AccessRule(pattern=rule_dict, permission=Permission.DENY)
130
+ else:
131
+ rule = AccessRule.from_dict(rule_dict)
132
+ all_rules.append(rule)
133
+
134
+ # 缓存规则
135
+ self._rules_cache[self.model_name] = all_rules
136
+
137
+ # 创建路径匹配器
138
+ patterns = [rule.pattern for rule in all_rules]
139
+ self._path_specs[self.model_name] = pathspec.PathSpec.from_lines('gitwildmatch', patterns)
36
140
 
37
- model_rules = config.get('model_filters', {}).get(self.model_name, {})
38
- all_rules = model_rules.get('forbidden_paths', []) + self.default_forbidden
141
+ def _setup_file_monitor(self):
142
+ """设置文件监控,当过滤器文件变化时重新加载规则"""
143
+ if FileMonitor is None or not self.config_path.exists():
144
+ return
145
+
146
+ try:
147
+ # 获取或创建 FileMonitor 实例
148
+ root_dir = os.path.dirname(str(self.config_path))
149
+ self._file_monitor = FileMonitor(root_dir=root_dir)
150
+
151
+ # 注册过滤器文件的回调
152
+ self._file_monitor.register(str(self.config_path), self._on_filter_file_changed)
153
+ except Exception as e:
154
+ print(f"设置过滤器文件监控时出错: {e}")
39
155
 
40
- # 预编译正则表达式
41
- self._rules_cache[self.model_name] = [
42
- re.compile(rule) for rule in all_rules
43
- ]
156
+ def _on_filter_file_changed(self, change_type: Change, changed_path: str):
157
+ """当过滤器文件发生变化时的回调函数"""
158
+ if os.path.abspath(changed_path) == os.path.abspath(str(self.config_path)):
159
+ print(f"检测到过滤器文件变化 ({change_type.name}): {changed_path}")
160
+ self.reload_rules()
161
+ print(f"已重新加载模型 {self.model_name} 的过滤规则")
44
162
 
45
- def is_accessible(self, file_path: str) -> bool:
163
+ def _normalize_path(self, file_path: str) -> str:
164
+ """标准化文件路径"""
165
+ if not file_path:
166
+ return ""
167
+ # 转换为相对路径
168
+ rel_path = os.path.relpath(file_path, self.args.source_dir)
169
+ # 统一路径分隔符
170
+ return rel_path.replace(os.sep, '/')
171
+
172
+ def _get_applicable_rules(self, file_path: str) -> List[AccessRule]:
173
+ """获取适用于指定文件的所有规则"""
174
+ if not file_path:
175
+ return []
176
+
177
+ normalized_path = self._normalize_path(file_path)
178
+ path_spec = self._path_specs.get(self.model_name)
179
+ rules = self._rules_cache.get(self.model_name, [])
180
+
181
+ if not path_spec or not rules:
182
+ return []
183
+
184
+ # 找出所有匹配此路径的规则
185
+ matching_rules = []
186
+ for rule in rules:
187
+ # 使用PathSpec检查单个匹配
188
+ single_spec = pathspec.PathSpec.from_lines('gitwildmatch', [rule.pattern])
189
+ if single_spec.match_file(normalized_path):
190
+ matching_rules.append(rule)
191
+
192
+ return matching_rules
193
+
194
+ def is_accessible(self,
195
+ file_path: str,
196
+ operation: Union[AccessOperation, str] = AccessOperation.READ,
197
+ line_number: int = None) -> bool:
46
198
  """
47
- 检查文件路径是否符合访问规则
199
+ 检查文件路径在指定操作和行号下是否可访问
200
+ :param file_path: 文件路径
201
+ :param operation: 访问操作类型(READ/WRITE)
202
+ :param line_number: 可选的行号
48
203
  :return: True表示允许访问,False表示禁止
49
204
  """
50
- # 优先使用模型专属规则
51
- patterns = self._rules_cache.get(self.model_name, [])
52
-
53
- # 回退到默认规则
54
- if not patterns and self.default_forbidden:
55
- patterns = [re.compile(rule) for rule in self.default_forbidden]
56
-
57
- # 如果路径为空或None,直接返回True
58
205
  if not file_path:
59
206
  return True
207
+
208
+ # 确保operation是AccessOperation类型
209
+ if isinstance(operation, str):
210
+ operation = AccessOperation(operation)
211
+
212
+ # 获取所有适用规则
213
+ applicable_rules = self._get_applicable_rules(file_path)
214
+ if not applicable_rules:
215
+ return True
216
+
217
+ # 按优先级处理规则 (ALLOW > DENY_specific > DENY)
218
+ for rule in applicable_rules:
219
+ # 检查行范围限制
220
+ if rule.line_ranges and line_number:
221
+ # 如果规则有行范围限制但当前行不在范围内,跳过此规则
222
+ if not any(r.contains(line_number) for r in rule.line_ranges):
223
+ continue
224
+
225
+ # 检查权限
226
+ if rule.permission == Permission.ALLOW:
227
+ return True
228
+ elif rule.permission == Permission.DENY:
229
+ return False
230
+ elif rule.permission == Permission.DENY_READ and operation == AccessOperation.READ:
231
+ return False
232
+ elif rule.permission == Permission.DENY_WRITE and operation == AccessOperation.WRITE:
233
+ return False
234
+
235
+ # 如果没有规则明确禁止,默认允许访问
236
+ return True
237
+
238
+ def is_readable(self, file_path: str, line_number: int = None) -> bool:
239
+ """检查文件是否可读取"""
240
+ return self.is_accessible(file_path, AccessOperation.READ, line_number)
241
+
242
+ def is_writable(self, file_path: str, line_number: int = None) -> bool:
243
+ """检查文件是否可写入"""
244
+ return self.is_accessible(file_path, AccessOperation.WRITE, line_number)
60
245
 
61
- return not any(pattern.search(file_path) for pattern in patterns)
246
+ def get_accessible_line_ranges(self,
247
+ file_path: str,
248
+ operation: Union[AccessOperation, str] = AccessOperation.READ) -> List[LineRange]:
249
+ """
250
+ 获取文件在指定操作下的可访问行范围
251
+ :return: 可访问的行范围列表
252
+ """
253
+ if not file_path:
254
+ return [LineRange(1, float('inf'))] # 默认完全可访问
255
+
256
+ # 确保operation是AccessOperation类型
257
+ if isinstance(operation, str):
258
+ operation = AccessOperation(operation)
259
+
260
+ # 获取所有适用规则
261
+ applicable_rules = self._get_applicable_rules(file_path)
262
+ if not applicable_rules:
263
+ return [LineRange(1, float('inf'))] # 默认完全可访问
264
+
265
+ # 检查是否有ALLOW规则
266
+ for rule in applicable_rules:
267
+ if rule.permission == Permission.ALLOW:
268
+ return [LineRange(1, float('inf'))] # 显式允许则完全可访问
269
+
270
+ # 检查是否有完全DENY规则
271
+ for rule in applicable_rules:
272
+ if rule.permission == Permission.DENY:
273
+ return [] # 完全禁止则无可访问行
274
+ elif ((rule.permission == Permission.DENY_READ and operation == AccessOperation.READ) or
275
+ (rule.permission == Permission.DENY_WRITE and operation == AccessOperation.WRITE)):
276
+ if not rule.line_ranges:
277
+ return [] # 如果没有指定行范围限制,则完全禁止指定操作
278
+
279
+ # 收集所有限制的行范围
280
+ restricted_ranges = []
281
+ for rule in applicable_rules:
282
+ if ((rule.permission == Permission.DENY_READ and operation == AccessOperation.READ) or
283
+ (rule.permission == Permission.DENY_WRITE and operation == AccessOperation.WRITE)):
284
+ if rule.line_ranges:
285
+ restricted_ranges.extend(rule.line_ranges)
286
+
287
+ # 如果没有限制范围,则全部可访问
288
+ if not restricted_ranges:
289
+ return [LineRange(1, float('inf'))]
290
+
291
+ # 计算可访问范围 (补集)
292
+ # 简化处理: 目前仅返回第一个受限区域前的行和最后一个受限区域后的行
293
+ sorted_ranges = sorted(restricted_ranges, key=lambda r: r.start)
294
+ accessible = []
295
+
296
+ # 第一个受限区域前的行
297
+ if sorted_ranges[0].start > 1:
298
+ accessible.append(LineRange(1, sorted_ranges[0].start - 1))
299
+
300
+ # 相邻受限区域之间的行
301
+ for i in range(len(sorted_ranges) - 1):
302
+ if sorted_ranges[i].end + 1 < sorted_ranges[i+1].start:
303
+ accessible.append(LineRange(sorted_ranges[i].end + 1, sorted_ranges[i+1].start - 1))
304
+
305
+ # 最后一个受限区域后的行
306
+ if sorted_ranges[-1].end < float('inf'):
307
+ accessible.append(LineRange(sorted_ranges[-1].end + 1, float('inf')))
308
+
309
+ return accessible
62
310
 
63
- def add_temp_rule(self, rule: str):
311
+ def add_temp_rule(self, pattern: str, permission: Union[Permission, str] = Permission.DENY):
64
312
  """
65
313
  添加临时规则
66
- :param rule: 正则表达式规则
314
+ :param pattern: gitignore格式的匹配模式
315
+ :param permission: 权限类型
67
316
  """
68
- patterns = self._rules_cache.get(self.model_name, [])
69
- patterns.append(re.compile(rule))
70
- self._rules_cache[self.model_name] = patterns
317
+ # 确保permission是Permission类型
318
+ if isinstance(permission, str):
319
+ permission = Permission(permission)
320
+
321
+ # 创建新规则
322
+ new_rule = AccessRule(pattern=pattern, permission=permission)
323
+
324
+ # 添加到规则缓存
325
+ rules = self._rules_cache.get(self.model_name, [])
326
+ rules.append(new_rule)
327
+ self._rules_cache[self.model_name] = rules
328
+
329
+ # 更新路径匹配器
330
+ patterns = [rule.pattern for rule in rules]
331
+ self._path_specs[self.model_name] = pathspec.PathSpec.from_lines('gitwildmatch', patterns)
71
332
 
72
333
  def reload_rules(self):
73
334
  """重新加载规则配置"""
74
335
  self._rules_cache.clear()
336
+ self._path_specs.clear()
75
337
  self._load_rules()
76
338
 
77
339
  def has_rules(self):
78
340
  """检查是否存在规则"""
79
- return bool(self._rules_cache.get(self.model_name, []))
341
+ return self.model_name in self._rules_cache and bool(self._rules_cache[self.model_name])
80
342
 
81
343
  @classmethod
82
344
  def from_model_object(cls,
83
345
  llm_obj,
84
346
  args: AutoCoderArgs,
85
- default_forbidden: Optional[List[str]] = None):
347
+ default_rules: Optional[List[Dict[str, Any]]] = None):
86
348
  """
87
349
  从LLM对象创建过滤器
88
350
  :param llm_obj: ByzerLLM实例或类似对象
89
351
  :param args: 自动编码器参数
90
- :param default_forbidden: 默认禁止路径规则
352
+ :param default_rules: 默认访问规则
91
353
  """
92
354
  model_name = ",".join(llm_utils.get_llm_names(llm_obj))
93
355
  if not model_name:
@@ -96,5 +358,5 @@ class ModelPathFilter:
96
358
  return cls(
97
359
  model_name=model_name,
98
360
  args=args,
99
- default_forbidden=default_forbidden
361
+ default_rules=default_rules
100
362
  )