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,156 @@
1
+ """
2
+ 文件变更管理模块的数据模型
3
+
4
+ 定义了表示文件变更、变更记录和操作结果的数据类。
5
+ """
6
+
7
+ import os
8
+ import uuid
9
+ from dataclasses import dataclass, field
10
+ from datetime import datetime
11
+ from typing import Dict, List, Optional, Union
12
+
13
+
14
+ @dataclass
15
+ class FileChange:
16
+ """表示单个文件的变更信息"""
17
+ file_path: str
18
+ content: str
19
+ is_new: bool = False
20
+ is_deletion: bool = False
21
+
22
+ @classmethod
23
+ def from_dict(cls, data: Dict) -> 'FileChange':
24
+ """从字典创建FileChange对象"""
25
+ return cls(
26
+ file_path=data['file_path'],
27
+ content=data['content'],
28
+ is_new=data.get('is_new', False),
29
+ is_deletion=data.get('is_deletion', False)
30
+ )
31
+
32
+ def to_dict(self) -> Dict:
33
+ """将FileChange对象转换为字典"""
34
+ return {
35
+ 'file_path': self.file_path,
36
+ 'content': self.content,
37
+ 'is_new': self.is_new,
38
+ 'is_deletion': self.is_deletion
39
+ }
40
+
41
+
42
+ @dataclass
43
+ class ChangeRecord:
44
+ """表示一条变更记录,包含变更的元数据和详细信息"""
45
+ change_id: str
46
+ timestamp: float
47
+ file_path: str
48
+ backup_id: Optional[str]
49
+ is_new: bool = False
50
+ is_deletion: bool = False
51
+ group_id: Optional[str] = None
52
+
53
+ @classmethod
54
+ def create(cls, file_path: str, backup_id: Optional[str],
55
+ is_new: bool = False, is_deletion: bool = False,
56
+ group_id: Optional[str] = None) -> 'ChangeRecord':
57
+ """创建一个新的变更记录"""
58
+ return cls(
59
+ change_id=str(uuid.uuid4()),
60
+ timestamp=datetime.now().timestamp(),
61
+ file_path=file_path,
62
+ backup_id=backup_id,
63
+ is_new=is_new,
64
+ is_deletion=is_deletion,
65
+ group_id=group_id
66
+ )
67
+
68
+ @classmethod
69
+ def from_dict(cls, data: Dict) -> 'ChangeRecord':
70
+ """从字典创建ChangeRecord对象"""
71
+ return cls(
72
+ change_id=data['change_id'],
73
+ timestamp=data['timestamp'],
74
+ file_path=data['file_path'],
75
+ backup_id=data.get('backup_id'),
76
+ is_new=data.get('is_new', False),
77
+ is_deletion=data.get('is_deletion', False),
78
+ group_id=data.get('group_id')
79
+ )
80
+
81
+ def to_dict(self) -> Dict:
82
+ """将ChangeRecord对象转换为字典"""
83
+ return {
84
+ 'change_id': self.change_id,
85
+ 'timestamp': self.timestamp,
86
+ 'file_path': self.file_path,
87
+ 'backup_id': self.backup_id,
88
+ 'is_new': self.is_new,
89
+ 'is_deletion': self.is_deletion,
90
+ 'group_id': self.group_id
91
+ }
92
+
93
+
94
+ @dataclass
95
+ class DiffResult:
96
+ """表示文件差异比较的结果"""
97
+ file_path: str
98
+ old_content: Optional[str]
99
+ new_content: str
100
+ is_new: bool = False
101
+ is_deletion: bool = False
102
+
103
+ def get_diff_summary(self) -> str:
104
+ """获取差异摘要"""
105
+ if self.is_new:
106
+ return f"新文件: {self.file_path}"
107
+ elif self.is_deletion:
108
+ return f"删除文件: {self.file_path}"
109
+ else:
110
+ old_lines = 0 if self.old_content is None else len(self.old_content.splitlines())
111
+ new_lines = len(self.new_content.splitlines())
112
+ return f"修改文件: {self.file_path} (原始行数: {old_lines}, 新行数: {new_lines})"
113
+
114
+
115
+ @dataclass
116
+ class ApplyResult:
117
+ """表示变更应用的结果"""
118
+ success: bool
119
+ change_ids: List[str] = field(default_factory=list)
120
+ errors: Dict[str, str] = field(default_factory=dict)
121
+
122
+ @property
123
+ def has_errors(self) -> bool:
124
+ """是否有错误"""
125
+ return len(self.errors) > 0
126
+
127
+ def add_error(self, file_path: str, error_message: str) -> None:
128
+ """添加错误信息"""
129
+ self.errors[file_path] = error_message
130
+ self.success = False
131
+
132
+ def add_change_id(self, change_id: str) -> None:
133
+ """添加成功应用的变更ID"""
134
+ self.change_ids.append(change_id)
135
+
136
+
137
+ @dataclass
138
+ class UndoResult:
139
+ """表示变更撤销的结果"""
140
+ success: bool
141
+ restored_files: List[str] = field(default_factory=list)
142
+ errors: Dict[str, str] = field(default_factory=dict)
143
+
144
+ @property
145
+ def has_errors(self) -> bool:
146
+ """是否有错误"""
147
+ return len(self.errors) > 0
148
+
149
+ def add_error(self, file_path: str, error_message: str) -> None:
150
+ """添加错误信息"""
151
+ self.errors[file_path] = error_message
152
+ self.success = False
153
+
154
+ def add_restored_file(self, file_path: str) -> None:
155
+ """添加成功恢复的文件"""
156
+ self.restored_files.append(file_path)
@@ -0,0 +1,383 @@
1
+ """
2
+ 文件变更存储器
3
+
4
+ 负责存储和管理文件变更历史记录,支持按组、按时间等方式查询变更记录。
5
+ """
6
+
7
+ import os
8
+ import json
9
+ import sqlite3
10
+ import logging
11
+ import threading
12
+ from typing import Dict, List, Optional, Tuple, Any
13
+ from datetime import datetime
14
+
15
+ from autocoder.common.file_checkpoint.models import ChangeRecord
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class FileChangeStore:
21
+ """负责存储和管理文件变更历史记录"""
22
+
23
+ def __init__(self, store_dir: Optional[str] = None, max_history: int = 50):
24
+ """
25
+ 初始化变更存储
26
+
27
+ Args:
28
+ store_dir: 存储目录,如果为None则使用默认目录
29
+ max_history: 最大保存的历史版本数量
30
+ """
31
+ if store_dir is None:
32
+ # 默认存储目录为项目根目录下的.auto-coder/checkpoint
33
+ store_dir = os.path.join(os.getcwd(), ".auto-coder", "checkpoint")
34
+
35
+ self.store_dir = store_dir
36
+ self.max_history = max_history
37
+ self.db_file = os.path.join(store_dir, "changes.db")
38
+ self.lock = threading.RLock()
39
+
40
+ # 确保存储目录存在
41
+ os.makedirs(store_dir, exist_ok=True)
42
+
43
+ # 初始化数据库
44
+ self._init_db()
45
+
46
+ def save_change(self, change_record: ChangeRecord) -> str:
47
+ """
48
+ 保存一条变更记录
49
+
50
+ Args:
51
+ change_record: 变更记录对象
52
+
53
+ Returns:
54
+ str: 变更记录ID
55
+ """
56
+ with self.lock:
57
+ try:
58
+ # 将变更记录保存到数据库
59
+ conn = self._get_db_connection()
60
+ cursor = conn.cursor()
61
+
62
+ cursor.execute(
63
+ """
64
+ INSERT INTO changes
65
+ (change_id, timestamp, file_path, backup_id, is_new, is_deletion, group_id, data)
66
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
67
+ """,
68
+ (
69
+ change_record.change_id,
70
+ change_record.timestamp,
71
+ change_record.file_path,
72
+ change_record.backup_id,
73
+ int(change_record.is_new),
74
+ int(change_record.is_deletion),
75
+ change_record.group_id,
76
+ json.dumps(change_record.to_dict())
77
+ )
78
+ )
79
+
80
+ conn.commit()
81
+
82
+ # 保存变更记录到JSON文件
83
+ self._save_change_to_file(change_record)
84
+
85
+ # 清理过旧的历史记录
86
+ self._clean_old_history()
87
+
88
+ logger.debug(f"已保存变更记录 {change_record.change_id}")
89
+ return change_record.change_id
90
+
91
+ except Exception as e:
92
+ logger.error(f"保存变更记录失败: {str(e)}")
93
+ raise
94
+
95
+ def get_change(self, change_id: str) -> Optional[ChangeRecord]:
96
+ """
97
+ 获取指定ID的变更记录
98
+
99
+ Args:
100
+ change_id: 变更记录ID
101
+
102
+ Returns:
103
+ ChangeRecord: 变更记录对象,如果不存在则返回None
104
+ """
105
+ with self.lock:
106
+ try:
107
+ conn = self._get_db_connection()
108
+ cursor = conn.cursor()
109
+
110
+ cursor.execute(
111
+ "SELECT data FROM changes WHERE change_id = ?",
112
+ (change_id,)
113
+ )
114
+
115
+ row = cursor.fetchone()
116
+ if row is None:
117
+ return None
118
+
119
+ data = json.loads(row[0])
120
+ return ChangeRecord.from_dict(data)
121
+
122
+ except Exception as e:
123
+ logger.error(f"获取变更记录 {change_id} 失败: {str(e)}")
124
+ return None
125
+
126
+ def get_changes_by_group(self, group_id: str) -> List[ChangeRecord]:
127
+ """
128
+ 获取指定组的所有变更记录
129
+
130
+ Args:
131
+ group_id: 变更组ID
132
+
133
+ Returns:
134
+ List[ChangeRecord]: 变更记录列表
135
+ """
136
+ with self.lock:
137
+ try:
138
+ conn = self._get_db_connection()
139
+ cursor = conn.cursor()
140
+
141
+ cursor.execute(
142
+ "SELECT data FROM changes WHERE group_id = ? ORDER BY timestamp DESC",
143
+ (group_id,)
144
+ )
145
+
146
+ changes = []
147
+ for row in cursor.fetchall():
148
+ data = json.loads(row[0])
149
+ changes.append(ChangeRecord.from_dict(data))
150
+
151
+ return changes
152
+
153
+ except Exception as e:
154
+ logger.error(f"获取变更组 {group_id} 的记录失败: {str(e)}")
155
+ return []
156
+
157
+ def get_latest_changes(self, limit: int = 10) -> List[ChangeRecord]:
158
+ """
159
+ 获取最近的变更记录
160
+
161
+ Args:
162
+ limit: 返回的记录数量限制
163
+
164
+ Returns:
165
+ List[ChangeRecord]: 变更记录列表
166
+ """
167
+ with self.lock:
168
+ try:
169
+ conn = self._get_db_connection()
170
+ cursor = conn.cursor()
171
+
172
+ cursor.execute(
173
+ "SELECT data FROM changes ORDER BY timestamp DESC LIMIT ?",
174
+ (limit,)
175
+ )
176
+
177
+ changes = []
178
+ for row in cursor.fetchall():
179
+ data = json.loads(row[0])
180
+ changes.append(ChangeRecord.from_dict(data))
181
+
182
+ return changes
183
+
184
+ except Exception as e:
185
+ logger.error(f"获取最近变更记录失败: {str(e)}")
186
+ return []
187
+
188
+ def get_changes_by_file(self, file_path: str, limit: int = 10) -> List[ChangeRecord]:
189
+ """
190
+ 获取指定文件的变更记录
191
+
192
+ Args:
193
+ file_path: 文件路径
194
+ limit: 返回的记录数量限制
195
+
196
+ Returns:
197
+ List[ChangeRecord]: 变更记录列表
198
+ """
199
+ with self.lock:
200
+ try:
201
+ conn = self._get_db_connection()
202
+ cursor = conn.cursor()
203
+
204
+ cursor.execute(
205
+ "SELECT data FROM changes WHERE file_path = ? ORDER BY timestamp DESC LIMIT ?",
206
+ (file_path, limit)
207
+ )
208
+
209
+ changes = []
210
+ for row in cursor.fetchall():
211
+ data = json.loads(row[0])
212
+ changes.append(ChangeRecord.from_dict(data))
213
+
214
+ return changes
215
+
216
+ except Exception as e:
217
+ logger.error(f"获取文件 {file_path} 的变更记录失败: {str(e)}")
218
+ return []
219
+
220
+ def delete_change(self, change_id: str) -> bool:
221
+ """
222
+ 删除指定的变更记录
223
+
224
+ Args:
225
+ change_id: 变更记录ID
226
+
227
+ Returns:
228
+ bool: 删除是否成功
229
+ """
230
+ with self.lock:
231
+ try:
232
+ # 获取变更记录
233
+ change = self.get_change(change_id)
234
+ if change is None:
235
+ return False
236
+
237
+ # 删除数据库中的记录
238
+ conn = self._get_db_connection()
239
+ cursor = conn.cursor()
240
+
241
+ cursor.execute(
242
+ "DELETE FROM changes WHERE change_id = ?",
243
+ (change_id,)
244
+ )
245
+
246
+ conn.commit()
247
+
248
+ # 删除JSON文件
249
+ json_file = os.path.join(self.store_dir, f"{change_id}.json")
250
+ if os.path.exists(json_file):
251
+ os.remove(json_file)
252
+
253
+ logger.debug(f"已删除变更记录 {change_id}")
254
+ return True
255
+
256
+ except Exception as e:
257
+ logger.error(f"删除变更记录 {change_id} 失败: {str(e)}")
258
+ return False
259
+
260
+ def get_change_groups(self, limit: int = 10) -> List[Tuple[str, float, int]]:
261
+ """
262
+ 获取变更组列表
263
+
264
+ Args:
265
+ limit: 返回的组数量限制
266
+
267
+ Returns:
268
+ List[Tuple[str, float, int]]: 变更组ID、最新时间戳和变更数量的列表
269
+ """
270
+ with self.lock:
271
+ try:
272
+ conn = self._get_db_connection()
273
+ cursor = conn.cursor()
274
+
275
+ cursor.execute(
276
+ """
277
+ SELECT group_id, MAX(timestamp) as latest_time, COUNT(*) as count
278
+ FROM changes
279
+ WHERE group_id IS NOT NULL
280
+ GROUP BY group_id
281
+ ORDER BY latest_time DESC
282
+ LIMIT ?
283
+ """,
284
+ (limit,)
285
+ )
286
+
287
+ groups = []
288
+ for row in cursor.fetchall():
289
+ groups.append((row[0], row[1], row[2]))
290
+
291
+ return groups
292
+
293
+ except Exception as e:
294
+ logger.error(f"获取变更组列表失败: {str(e)}")
295
+ return []
296
+
297
+ def _init_db(self) -> None:
298
+ """初始化数据库"""
299
+ try:
300
+ conn = self._get_db_connection()
301
+ cursor = conn.cursor()
302
+
303
+ # 创建变更记录表
304
+ cursor.execute(
305
+ """
306
+ CREATE TABLE IF NOT EXISTS changes (
307
+ change_id TEXT PRIMARY KEY,
308
+ timestamp REAL NOT NULL,
309
+ file_path TEXT NOT NULL,
310
+ backup_id TEXT,
311
+ is_new INTEGER NOT NULL DEFAULT 0,
312
+ is_deletion INTEGER NOT NULL DEFAULT 0,
313
+ group_id TEXT,
314
+ data TEXT NOT NULL
315
+ )
316
+ """
317
+ )
318
+
319
+ # 创建索引
320
+ cursor.execute(
321
+ "CREATE INDEX IF NOT EXISTS idx_changes_timestamp ON changes (timestamp)"
322
+ )
323
+ cursor.execute(
324
+ "CREATE INDEX IF NOT EXISTS idx_changes_file_path ON changes (file_path)"
325
+ )
326
+ cursor.execute(
327
+ "CREATE INDEX IF NOT EXISTS idx_changes_group_id ON changes (group_id)"
328
+ )
329
+
330
+ conn.commit()
331
+
332
+ except Exception as e:
333
+ logger.error(f"初始化数据库失败: {str(e)}")
334
+ raise
335
+
336
+ def _get_db_connection(self) -> sqlite3.Connection:
337
+ """获取数据库连接"""
338
+ conn = sqlite3.connect(self.db_file)
339
+ conn.row_factory = sqlite3.Row
340
+ return conn
341
+
342
+ def _save_change_to_file(self, change_record: ChangeRecord) -> None:
343
+ """将变更记录保存到JSON文件"""
344
+ try:
345
+ json_file = os.path.join(self.store_dir, f"{change_record.change_id}.json")
346
+
347
+ with open(json_file, 'w', encoding='utf-8') as f:
348
+ json.dump(change_record.to_dict(), f, indent=2)
349
+
350
+ except Exception as e:
351
+ logger.error(f"保存变更记录到文件失败: {str(e)}")
352
+
353
+ def _clean_old_history(self) -> None:
354
+ """清理过旧的历史记录"""
355
+ try:
356
+ conn = self._get_db_connection()
357
+ cursor = conn.cursor()
358
+
359
+ # 获取记录总数
360
+ cursor.execute("SELECT COUNT(*) FROM changes")
361
+ total_count = cursor.fetchone()[0]
362
+
363
+ # 如果记录数超过最大限制,删除最旧的记录
364
+ if total_count > self.max_history:
365
+ # 计算需要删除的记录数
366
+ delete_count = total_count - self.max_history
367
+
368
+ # 获取要删除的记录ID
369
+ cursor.execute(
370
+ "SELECT change_id FROM changes ORDER BY timestamp ASC LIMIT ?",
371
+ (delete_count,)
372
+ )
373
+
374
+ change_ids = [row[0] for row in cursor.fetchall()]
375
+
376
+ # 删除记录
377
+ for change_id in change_ids:
378
+ self.delete_change(change_id)
379
+
380
+ logger.debug(f"已清理 {len(change_ids)} 条过旧的变更记录")
381
+
382
+ except Exception as e:
383
+ logger.error(f"清理过旧的历史记录失败: {str(e)}")