auto-coder 0.1.363__py3-none-any.whl → 0.1.364__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.
- {auto_coder-0.1.363.dist-info → auto_coder-0.1.364.dist-info}/METADATA +2 -2
- {auto_coder-0.1.363.dist-info → auto_coder-0.1.364.dist-info}/RECORD +33 -18
- autocoder/agent/base_agentic/tools/execute_command_tool_resolver.py +1 -1
- autocoder/auto_coder_runner.py +2 -0
- autocoder/common/__init__.py +2 -0
- autocoder/common/file_checkpoint/__init__.py +21 -0
- autocoder/common/file_checkpoint/backup.py +264 -0
- autocoder/common/file_checkpoint/examples.py +217 -0
- autocoder/common/file_checkpoint/manager.py +404 -0
- autocoder/common/file_checkpoint/models.py +156 -0
- autocoder/common/file_checkpoint/store.py +383 -0
- autocoder/common/file_checkpoint/test_backup.py +242 -0
- autocoder/common/file_checkpoint/test_manager.py +570 -0
- autocoder/common/file_checkpoint/test_models.py +360 -0
- autocoder/common/file_checkpoint/test_store.py +327 -0
- autocoder/common/file_checkpoint/test_utils.py +297 -0
- autocoder/common/file_checkpoint/utils.py +119 -0
- autocoder/common/rulefiles/autocoderrules_utils.py +114 -55
- autocoder/common/save_formatted_log.py +76 -5
- autocoder/common/v2/agent/agentic_edit.py +318 -197
- autocoder/common/v2/agent/agentic_edit_tools/read_file_tool_resolver.py +2 -2
- autocoder/common/v2/agent/agentic_edit_tools/replace_in_file_tool_resolver.py +27 -4
- autocoder/common/v2/agent/agentic_edit_tools/test_write_to_file_tool_resolver.py +322 -0
- autocoder/common/v2/agent/agentic_edit_tools/write_to_file_tool_resolver.py +83 -61
- autocoder/compilers/normal_compiler.py +64 -0
- autocoder/events/event_manager_singleton.py +133 -4
- autocoder/linters/normal_linter.py +373 -0
- autocoder/linters/python_linter.py +4 -2
- autocoder/version.py +1 -1
- {auto_coder-0.1.363.dist-info → auto_coder-0.1.364.dist-info}/LICENSE +0 -0
- {auto_coder-0.1.363.dist-info → auto_coder-0.1.364.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.363.dist-info → auto_coder-0.1.364.dist-info}/entry_points.txt +0 -0
- {auto_coder-0.1.363.dist-info → auto_coder-0.1.364.dist-info}/top_level.txt +0 -0
|
@@ -6,6 +6,7 @@ from datetime import datetime
|
|
|
6
6
|
import glob
|
|
7
7
|
import json
|
|
8
8
|
import re
|
|
9
|
+
import time
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
import byzerllm
|
|
11
12
|
|
|
@@ -22,6 +23,10 @@ class EventManagerSingleton:
|
|
|
22
23
|
_default_instance: Optional[EventManager] = None
|
|
23
24
|
_instances: Dict[str, EventManager] = {}
|
|
24
25
|
_lock = threading.RLock() # 用于线程安全操作的锁
|
|
26
|
+
_cleanup_thread: Optional[threading.Thread] = None
|
|
27
|
+
_stop_cleanup = threading.Event()
|
|
28
|
+
_max_event_files = 100 # 最大保留的事件文件数量
|
|
29
|
+
_cleanup_interval = 60 # 清理线程执行间隔,单位秒
|
|
25
30
|
|
|
26
31
|
@classmethod
|
|
27
32
|
def get_instance(cls, event_file: Optional[str] = None) -> EventManager:
|
|
@@ -34,15 +39,26 @@ class EventManagerSingleton:
|
|
|
34
39
|
Returns:
|
|
35
40
|
EventManager: The appropriate EventManager instance
|
|
36
41
|
"""
|
|
42
|
+
# 确保清理线程已启动
|
|
43
|
+
cls._ensure_cleanup_thread_started()
|
|
44
|
+
|
|
37
45
|
if event_file is None:
|
|
38
46
|
# Use default instance logic
|
|
39
47
|
if cls._default_instance is None:
|
|
40
|
-
|
|
48
|
+
logger.info("Creating new default EventManager instance.")
|
|
49
|
+
event_store_path = os.path.join(".auto-coder", "events", "events.jsonl")
|
|
50
|
+
logger.debug(f"Default EventManager using event store: {event_store_path}")
|
|
51
|
+
cls._default_instance = EventManager(JsonlEventStore(event_store_path))
|
|
52
|
+
else:
|
|
53
|
+
logger.debug("Returning existing default EventManager instance.")
|
|
41
54
|
return cls._default_instance
|
|
42
55
|
|
|
43
56
|
# If event_file is provided, use it as a key to store/retrieve EventManager instances
|
|
44
57
|
if event_file not in cls._instances:
|
|
58
|
+
logger.info(f"Creating new EventManager instance for event file: {event_file}")
|
|
45
59
|
cls._instances[event_file] = EventManager(JsonlEventStore(event_file))
|
|
60
|
+
else:
|
|
61
|
+
logger.debug(f"Returning existing EventManager instance for event file: {event_file}")
|
|
46
62
|
|
|
47
63
|
return cls._instances[event_file]
|
|
48
64
|
|
|
@@ -52,7 +68,11 @@ class EventManagerSingleton:
|
|
|
52
68
|
重置单例实例。主要用于测试或需要更改事件文件时。
|
|
53
69
|
"""
|
|
54
70
|
with cls._lock:
|
|
55
|
-
cls._default_instance
|
|
71
|
+
if cls._default_instance is not None:
|
|
72
|
+
logger.info("Resetting default EventManager instance.")
|
|
73
|
+
cls._default_instance = None
|
|
74
|
+
else:
|
|
75
|
+
logger.debug("Default EventManager instance was already None. No reset needed.")
|
|
56
76
|
|
|
57
77
|
@classmethod
|
|
58
78
|
def set_default_event_file(cls, event_file: str) -> None:
|
|
@@ -68,6 +88,110 @@ class EventManagerSingleton:
|
|
|
68
88
|
cls._default_event_file = event_file
|
|
69
89
|
else:
|
|
70
90
|
logger.warning("尝试更改默认事件文件,但实例已存在。请先调用reset_instance()。")
|
|
91
|
+
|
|
92
|
+
@classmethod
|
|
93
|
+
def _ensure_cleanup_thread_started(cls) -> None:
|
|
94
|
+
"""
|
|
95
|
+
确保清理线程已启动。如果尚未启动,则启动线程。
|
|
96
|
+
"""
|
|
97
|
+
with cls._lock:
|
|
98
|
+
if cls._cleanup_thread is None or not cls._cleanup_thread.is_alive():
|
|
99
|
+
logger.info("启动事件文件清理线程")
|
|
100
|
+
cls._stop_cleanup.clear()
|
|
101
|
+
cls._cleanup_thread = threading.Thread(
|
|
102
|
+
target=cls._cleanup_event_files_thread,
|
|
103
|
+
daemon=True,
|
|
104
|
+
name="EventFilesCleanupThread"
|
|
105
|
+
)
|
|
106
|
+
cls._cleanup_thread.start()
|
|
107
|
+
|
|
108
|
+
@classmethod
|
|
109
|
+
def _cleanup_event_files_thread(cls) -> None:
|
|
110
|
+
"""
|
|
111
|
+
定时清理线程的主函数。定期执行清理操作,确保事件目录只保留最新的指定数量文件。
|
|
112
|
+
"""
|
|
113
|
+
logger.info(f"事件文件清理线程已启动,将保留最新的{cls._max_event_files}个文件")
|
|
114
|
+
while not cls._stop_cleanup.is_set():
|
|
115
|
+
try:
|
|
116
|
+
cls._cleanup_event_files()
|
|
117
|
+
# 等待指定时间或直到停止信号
|
|
118
|
+
cls._stop_cleanup.wait(cls._cleanup_interval)
|
|
119
|
+
except Exception as e:
|
|
120
|
+
logger.error(f"事件文件清理过程中发生错误: {e}")
|
|
121
|
+
# 出错后等待一段时间再重试
|
|
122
|
+
time.sleep(60)
|
|
123
|
+
|
|
124
|
+
@classmethod
|
|
125
|
+
def _cleanup_event_files(cls) -> None:
|
|
126
|
+
"""
|
|
127
|
+
清理事件文件,只保留最新的指定数量文件。
|
|
128
|
+
"""
|
|
129
|
+
logger.info("开始清理事件文件...")
|
|
130
|
+
|
|
131
|
+
# 确定事件文件所在目录
|
|
132
|
+
events_dir = os.path.join(os.getcwd(),".auto-coder", "events")
|
|
133
|
+
|
|
134
|
+
# 确保目录存在
|
|
135
|
+
if not os.path.exists(events_dir):
|
|
136
|
+
logger.warning(f"事件目录不存在: {events_dir}")
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
# 获取所有事件文件
|
|
140
|
+
event_file_pattern = os.path.join(events_dir, "*.jsonl")
|
|
141
|
+
event_files = glob.glob(event_file_pattern)
|
|
142
|
+
|
|
143
|
+
if not event_files:
|
|
144
|
+
logger.info(f"未找到任何事件文件: {event_file_pattern}")
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
# 排除默认的events.jsonl文件
|
|
148
|
+
event_files = [f for f in event_files if os.path.basename(f) != "events.jsonl"]
|
|
149
|
+
|
|
150
|
+
if len(event_files) <= cls._max_event_files:
|
|
151
|
+
logger.info(f"事件文件数量({len(event_files)})未超过最大保留数量({cls._max_event_files}),无需清理")
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
# 解析文件名中的时间戳,格式为 uuid_YYYYMMDD-HHMMSS.jsonl
|
|
155
|
+
def extract_timestamp(file_path):
|
|
156
|
+
file_name = os.path.basename(file_path)
|
|
157
|
+
match = re.search(r'_(\d{8}-\d{6})\.jsonl$', file_name)
|
|
158
|
+
if match:
|
|
159
|
+
timestamp_str = match.group(1)
|
|
160
|
+
try:
|
|
161
|
+
return datetime.strptime(timestamp_str, "%Y%m%d-%H%M%S")
|
|
162
|
+
except ValueError:
|
|
163
|
+
return datetime.fromtimestamp(0) # 默认最早时间
|
|
164
|
+
return datetime.fromtimestamp(0) # 默认最早时间
|
|
165
|
+
|
|
166
|
+
# 按时间戳从新到旧排序
|
|
167
|
+
sorted_files = sorted(event_files, key=extract_timestamp, reverse=True)
|
|
168
|
+
|
|
169
|
+
# 保留最新的文件,删除其余文件
|
|
170
|
+
files_to_keep = sorted_files[:cls._max_event_files]
|
|
171
|
+
files_to_delete = sorted_files[cls._max_event_files:]
|
|
172
|
+
|
|
173
|
+
for file_path in files_to_delete:
|
|
174
|
+
try:
|
|
175
|
+
os.remove(file_path)
|
|
176
|
+
logger.info(f"已删除旧事件文件: {file_path}")
|
|
177
|
+
except Exception as e:
|
|
178
|
+
logger.error(f"删除事件文件失败 {file_path}: {e}")
|
|
179
|
+
|
|
180
|
+
logger.info(f"事件文件清理完成,删除了{len(files_to_delete)}个旧文件,保留了{len(files_to_keep)}个最新文件")
|
|
181
|
+
|
|
182
|
+
@classmethod
|
|
183
|
+
def stop_cleanup_thread(cls) -> None:
|
|
184
|
+
"""
|
|
185
|
+
停止清理线程,通常在应用程序退出时调用。
|
|
186
|
+
"""
|
|
187
|
+
logger.info("正在停止事件文件清理线程...")
|
|
188
|
+
cls._stop_cleanup.set()
|
|
189
|
+
if cls._cleanup_thread and cls._cleanup_thread.is_alive():
|
|
190
|
+
cls._cleanup_thread.join(timeout=5)
|
|
191
|
+
if cls._cleanup_thread.is_alive():
|
|
192
|
+
logger.warning("清理线程未在指定时间内停止")
|
|
193
|
+
else:
|
|
194
|
+
logger.info("清理线程已成功停止")
|
|
71
195
|
|
|
72
196
|
def get_event_file_path(file_id:str,project_path: Optional[str] = None) -> str:
|
|
73
197
|
if project_path is None:
|
|
@@ -95,9 +219,12 @@ def gengerate_event_file_path(project_path: Optional[str] = None) -> Tuple[str,s
|
|
|
95
219
|
|
|
96
220
|
# 要使用绝对路径,否则会被认为产生两个event_manager
|
|
97
221
|
if project_path is None:
|
|
98
|
-
|
|
222
|
+
full_path = os.path.join(os.getcwd(),".auto-coder", "events", file_name)
|
|
99
223
|
else:
|
|
100
|
-
|
|
224
|
+
full_path = os.path.join(project_path, ".auto-coder", "events", file_name)
|
|
225
|
+
|
|
226
|
+
logger.info(f"Generated event file path: {full_path}, file_id: {file_id}")
|
|
227
|
+
return full_path, file_id
|
|
101
228
|
|
|
102
229
|
@byzerllm.prompt()
|
|
103
230
|
def _format_events_prompt(event_files: List[Dict]) -> str:
|
|
@@ -148,6 +275,7 @@ def to_events_prompt(limit: int = 5, project_path: Optional[str] = None) -> str:
|
|
|
148
275
|
Returns:
|
|
149
276
|
格式化后的事件提示文本
|
|
150
277
|
"""
|
|
278
|
+
logger.info(f"Generating events prompt with limit={limit}, project_path={project_path}")
|
|
151
279
|
# 确定事件文件所在目录
|
|
152
280
|
if project_path is None:
|
|
153
281
|
events_dir = os.path.join(".auto-coder", "events")
|
|
@@ -164,6 +292,7 @@ def to_events_prompt(limit: int = 5, project_path: Optional[str] = None) -> str:
|
|
|
164
292
|
event_files = glob.glob(event_file_pattern)
|
|
165
293
|
|
|
166
294
|
if not event_files:
|
|
295
|
+
logger.info(f"No event files found in pattern: {event_file_pattern}")
|
|
167
296
|
logger.warning(f"未找到任何事件文件: {event_file_pattern}")
|
|
168
297
|
return "未找到任何事件记录。"
|
|
169
298
|
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
"""
|
|
2
|
+
用于对文件进行代码检查的模块。
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import time
|
|
7
|
+
from typing import Dict, List, Any, Optional, Tuple
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
|
|
10
|
+
from autocoder.linters.linter_factory import LinterFactory
|
|
11
|
+
from autocoder.linters.models import (
|
|
12
|
+
LintIssue,
|
|
13
|
+
FileLintResult,
|
|
14
|
+
ProjectLintResult,
|
|
15
|
+
IssuePosition,
|
|
16
|
+
IssueSeverity
|
|
17
|
+
)
|
|
18
|
+
from loguru import logger as global_logger
|
|
19
|
+
|
|
20
|
+
class NormalLinter:
|
|
21
|
+
"""
|
|
22
|
+
用于对文件进行代码检查的类。
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, project_dir: str, verbose: bool = False):
|
|
26
|
+
"""
|
|
27
|
+
初始化。
|
|
28
|
+
|
|
29
|
+
参数:
|
|
30
|
+
project_dir (str): 项目根目录路径
|
|
31
|
+
verbose (bool): 是否启用详细输出
|
|
32
|
+
"""
|
|
33
|
+
self.project_dir = project_dir
|
|
34
|
+
self.verbose = verbose
|
|
35
|
+
self.logger = global_logger.bind(name="NormalLinter")
|
|
36
|
+
|
|
37
|
+
def lint_file(self, file_path: str, fix: bool = False) -> FileLintResult:
|
|
38
|
+
"""
|
|
39
|
+
对单个文件进行代码检查。
|
|
40
|
+
|
|
41
|
+
参数:
|
|
42
|
+
file_path (str): 文件的路径
|
|
43
|
+
fix (bool): 是否自动修复问题
|
|
44
|
+
|
|
45
|
+
返回:
|
|
46
|
+
FileLintResult: 代码检查结果
|
|
47
|
+
"""
|
|
48
|
+
try:
|
|
49
|
+
# 记录开始时间以计算执行时间
|
|
50
|
+
start_time = time.time()
|
|
51
|
+
|
|
52
|
+
# 对文件运行代码检查
|
|
53
|
+
raw_lint_result = LinterFactory.lint_file(file_path, fix=fix, verbose=self.verbose)
|
|
54
|
+
|
|
55
|
+
# 如果lint过程返回None,创建一个空的结果对象,而不是直接返回None
|
|
56
|
+
if raw_lint_result is None:
|
|
57
|
+
language = self._detect_language(file_path)
|
|
58
|
+
return FileLintResult(
|
|
59
|
+
file_path=file_path,
|
|
60
|
+
success=False,
|
|
61
|
+
language=language,
|
|
62
|
+
issues=[], # 空问题列表
|
|
63
|
+
error=None,
|
|
64
|
+
error_count=0,
|
|
65
|
+
warning_count=0,
|
|
66
|
+
info_count=0
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# 计算执行时间(毫秒)
|
|
70
|
+
execution_time_ms = int((time.time() - start_time) * 1000)
|
|
71
|
+
raw_lint_result['execution_time_ms'] = execution_time_ms
|
|
72
|
+
|
|
73
|
+
# 将原始结果转换为Pydantic模型
|
|
74
|
+
return self._convert_raw_lint_result(raw_lint_result, file_path)
|
|
75
|
+
except Exception as e:
|
|
76
|
+
if self.verbose:
|
|
77
|
+
print(f"检查 {file_path} 时出错: {str(e)}")
|
|
78
|
+
|
|
79
|
+
language = self._detect_language(file_path)
|
|
80
|
+
return FileLintResult(
|
|
81
|
+
file_path=file_path,
|
|
82
|
+
success=False,
|
|
83
|
+
language=language,
|
|
84
|
+
error=str(e),
|
|
85
|
+
issues=[], # 添加空问题列表
|
|
86
|
+
error_count=0,
|
|
87
|
+
warning_count=0,
|
|
88
|
+
info_count=0
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def lint_all_files(self, fix: bool = False) -> ProjectLintResult:
|
|
92
|
+
"""
|
|
93
|
+
对项目目录中的所有文件进行代码检查。
|
|
94
|
+
|
|
95
|
+
参数:
|
|
96
|
+
fix (bool): 是否自动修复问题
|
|
97
|
+
|
|
98
|
+
返回:
|
|
99
|
+
ProjectLintResult: 所有文件的汇总代码检查结果
|
|
100
|
+
"""
|
|
101
|
+
all_files = self._get_all_files()
|
|
102
|
+
file_results = {}
|
|
103
|
+
total_files = len(all_files)
|
|
104
|
+
files_with_issues = 0
|
|
105
|
+
total_issues = 0
|
|
106
|
+
total_errors = 0
|
|
107
|
+
total_warnings = 0
|
|
108
|
+
total_infos = 0
|
|
109
|
+
fixed_issues_count = 0
|
|
110
|
+
|
|
111
|
+
# 处理每个文件
|
|
112
|
+
for file_path in all_files:
|
|
113
|
+
self.logger.info(f"正在检查文件: {file_path}")
|
|
114
|
+
try:
|
|
115
|
+
file_result = self.lint_file(file_path, fix=fix)
|
|
116
|
+
self.logger.info(f"检查完成: {file_path}")
|
|
117
|
+
|
|
118
|
+
file_results[file_path] = file_result
|
|
119
|
+
|
|
120
|
+
# 更新统计数据
|
|
121
|
+
if file_result.success:
|
|
122
|
+
issue_count = len(file_result.issues)
|
|
123
|
+
if issue_count > 0:
|
|
124
|
+
files_with_issues += 1
|
|
125
|
+
total_issues += issue_count
|
|
126
|
+
total_errors += file_result.error_count
|
|
127
|
+
total_warnings += file_result.warning_count
|
|
128
|
+
total_infos += file_result.info_count
|
|
129
|
+
|
|
130
|
+
if file_result.fixed_issues_count:
|
|
131
|
+
fixed_issues_count += file_result.fixed_issues_count
|
|
132
|
+
except Exception as e:
|
|
133
|
+
if self.verbose:
|
|
134
|
+
import traceback
|
|
135
|
+
traceback.print_exc()
|
|
136
|
+
print(f"处理 {file_path} 时出错: {str(e)}")
|
|
137
|
+
|
|
138
|
+
language = self._detect_language(file_path)
|
|
139
|
+
file_results[file_path] = FileLintResult(
|
|
140
|
+
file_path=file_path,
|
|
141
|
+
success=False,
|
|
142
|
+
language=language,
|
|
143
|
+
error=str(e),
|
|
144
|
+
issues=[],
|
|
145
|
+
error_count=0,
|
|
146
|
+
warning_count=0,
|
|
147
|
+
info_count=0
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# 创建项目结果
|
|
151
|
+
return ProjectLintResult(
|
|
152
|
+
project_path=self.project_dir,
|
|
153
|
+
file_results=file_results,
|
|
154
|
+
total_files=total_files,
|
|
155
|
+
files_with_issues=files_with_issues,
|
|
156
|
+
total_issues=total_issues,
|
|
157
|
+
total_errors=total_errors,
|
|
158
|
+
total_warnings=total_warnings,
|
|
159
|
+
total_infos=total_infos,
|
|
160
|
+
fixed_issues_count=fixed_issues_count if fix else None
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
def lint_dir(self, directory: str, fix: bool = False) -> ProjectLintResult:
|
|
164
|
+
"""
|
|
165
|
+
对特定目录中的所有文件进行代码检查。
|
|
166
|
+
|
|
167
|
+
参数:
|
|
168
|
+
directory (str): 目录的路径
|
|
169
|
+
fix (bool): 是否自动修复问题
|
|
170
|
+
|
|
171
|
+
返回:
|
|
172
|
+
ProjectLintResult: 目录中所有文件的汇总代码检查结果
|
|
173
|
+
"""
|
|
174
|
+
files = self._get_files_in_dir(directory)
|
|
175
|
+
file_results = {}
|
|
176
|
+
total_files = len(files)
|
|
177
|
+
files_with_issues = 0
|
|
178
|
+
total_issues = 0
|
|
179
|
+
total_errors = 0
|
|
180
|
+
total_warnings = 0
|
|
181
|
+
total_infos = 0
|
|
182
|
+
fixed_issues_count = 0
|
|
183
|
+
|
|
184
|
+
for file_path in files:
|
|
185
|
+
try:
|
|
186
|
+
file_result = self.lint_file(file_path, fix=fix)
|
|
187
|
+
|
|
188
|
+
file_results[file_path] = file_result
|
|
189
|
+
|
|
190
|
+
# 更新统计数据
|
|
191
|
+
if file_result.success:
|
|
192
|
+
issue_count = len(file_result.issues)
|
|
193
|
+
if issue_count > 0:
|
|
194
|
+
files_with_issues += 1
|
|
195
|
+
total_issues += issue_count
|
|
196
|
+
total_errors += file_result.error_count
|
|
197
|
+
total_warnings += file_result.warning_count
|
|
198
|
+
total_infos += file_result.info_count
|
|
199
|
+
|
|
200
|
+
if file_result.fixed_issues_count:
|
|
201
|
+
fixed_issues_count += file_result.fixed_issues_count
|
|
202
|
+
except Exception as e:
|
|
203
|
+
if self.verbose:
|
|
204
|
+
print(f"处理 {file_path} 时出错: {str(e)}")
|
|
205
|
+
|
|
206
|
+
language = self._detect_language(file_path)
|
|
207
|
+
file_results[file_path] = FileLintResult(
|
|
208
|
+
file_path=file_path,
|
|
209
|
+
success=False,
|
|
210
|
+
language=language,
|
|
211
|
+
error=str(e),
|
|
212
|
+
issues=[],
|
|
213
|
+
error_count=0,
|
|
214
|
+
warning_count=0,
|
|
215
|
+
info_count=0
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
# 创建项目结果
|
|
219
|
+
return ProjectLintResult(
|
|
220
|
+
project_path=directory,
|
|
221
|
+
file_results=file_results,
|
|
222
|
+
total_files=total_files,
|
|
223
|
+
files_with_issues=files_with_issues,
|
|
224
|
+
total_issues=total_issues,
|
|
225
|
+
total_errors=total_errors,
|
|
226
|
+
total_warnings=total_warnings,
|
|
227
|
+
total_infos=total_infos,
|
|
228
|
+
fixed_issues_count=fixed_issues_count if fix else None
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
def _get_all_files(self) -> List[str]:
|
|
232
|
+
"""
|
|
233
|
+
获取项目目录中的所有文件。
|
|
234
|
+
|
|
235
|
+
返回:
|
|
236
|
+
List[str]: 文件的绝对路径列表
|
|
237
|
+
"""
|
|
238
|
+
all_files = []
|
|
239
|
+
|
|
240
|
+
for root, _, files in os.walk(self.project_dir):
|
|
241
|
+
for file in files:
|
|
242
|
+
# 跳过隐藏文件和目录
|
|
243
|
+
if file.startswith('.'):
|
|
244
|
+
continue
|
|
245
|
+
|
|
246
|
+
file_path = os.path.join(root, file)
|
|
247
|
+
all_files.append(file_path)
|
|
248
|
+
|
|
249
|
+
return all_files
|
|
250
|
+
|
|
251
|
+
def _get_files_in_dir(self, directory: str) -> List[str]:
|
|
252
|
+
"""
|
|
253
|
+
获取目录及其子目录中的所有文件。
|
|
254
|
+
|
|
255
|
+
参数:
|
|
256
|
+
directory (str): 目录路径
|
|
257
|
+
|
|
258
|
+
返回:
|
|
259
|
+
List[str]: 绝对文件路径列表
|
|
260
|
+
"""
|
|
261
|
+
all_files = []
|
|
262
|
+
|
|
263
|
+
for root, _, files in os.walk(directory):
|
|
264
|
+
for file in files:
|
|
265
|
+
# 跳过隐藏文件
|
|
266
|
+
if file.startswith('.'):
|
|
267
|
+
continue
|
|
268
|
+
|
|
269
|
+
file_path = os.path.join(root, file)
|
|
270
|
+
all_files.append(file_path)
|
|
271
|
+
|
|
272
|
+
return all_files
|
|
273
|
+
|
|
274
|
+
def _detect_language(self, file_path: str) -> str:
|
|
275
|
+
"""
|
|
276
|
+
根据文件扩展名检测文件的语言。
|
|
277
|
+
|
|
278
|
+
参数:
|
|
279
|
+
file_path (str): 文件路径
|
|
280
|
+
|
|
281
|
+
返回:
|
|
282
|
+
str: 检测到的语言或"unknown"
|
|
283
|
+
"""
|
|
284
|
+
try:
|
|
285
|
+
language = LinterFactory._detect_language_from_file(file_path)
|
|
286
|
+
# 确保返回值是字符串
|
|
287
|
+
return language if isinstance(language, str) else "unknown"
|
|
288
|
+
except ValueError:
|
|
289
|
+
# 如果语言检测失败,返回默认值
|
|
290
|
+
return "unknown"
|
|
291
|
+
except Exception:
|
|
292
|
+
# 捕获所有其他异常并返回默认值
|
|
293
|
+
return "unknown"
|
|
294
|
+
|
|
295
|
+
def _convert_raw_lint_result(self, raw_result: Dict[str, Any], file_path: str) -> FileLintResult:
|
|
296
|
+
"""
|
|
297
|
+
将原始的linter输出转换为FileLintResult Pydantic模型。
|
|
298
|
+
|
|
299
|
+
参数:
|
|
300
|
+
raw_result (Dict[str, Any]): Linter的原始输出
|
|
301
|
+
file_path (str): 文件的路径
|
|
302
|
+
|
|
303
|
+
返回:
|
|
304
|
+
FileLintResult: 标准化的lint结果模型
|
|
305
|
+
"""
|
|
306
|
+
# 提取语言信息
|
|
307
|
+
language = raw_result.get('language')
|
|
308
|
+
if not isinstance(language, str):
|
|
309
|
+
language = self._detect_language(file_path)
|
|
310
|
+
|
|
311
|
+
# 初始化计数器
|
|
312
|
+
error_count = 0
|
|
313
|
+
warning_count = 0
|
|
314
|
+
info_count = 0
|
|
315
|
+
|
|
316
|
+
# 处理问题
|
|
317
|
+
issues = []
|
|
318
|
+
raw_issues = raw_result.get('issues', [])
|
|
319
|
+
|
|
320
|
+
for raw_issue in raw_issues:
|
|
321
|
+
# 确定严重性
|
|
322
|
+
severity_str = raw_issue.get('severity', 'error').lower()
|
|
323
|
+
if severity_str in ('error', 'critical', 'fatal'):
|
|
324
|
+
severity = IssueSeverity.ERROR
|
|
325
|
+
error_count += 1
|
|
326
|
+
elif severity_str in ('warning', 'warn'):
|
|
327
|
+
severity = IssueSeverity.WARNING
|
|
328
|
+
warning_count += 1
|
|
329
|
+
else:
|
|
330
|
+
severity = IssueSeverity.INFO
|
|
331
|
+
info_count += 1
|
|
332
|
+
|
|
333
|
+
# 提取位置信息
|
|
334
|
+
line = raw_issue.get('line', 1)
|
|
335
|
+
column = raw_issue.get('column')
|
|
336
|
+
end_line = raw_issue.get('end_line')
|
|
337
|
+
end_column = raw_issue.get('end_column')
|
|
338
|
+
|
|
339
|
+
position = IssuePosition(
|
|
340
|
+
line=line,
|
|
341
|
+
column=column,
|
|
342
|
+
end_line=end_line,
|
|
343
|
+
end_column=end_column
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
# 创建问题
|
|
347
|
+
issue = LintIssue(
|
|
348
|
+
code=raw_issue.get('code', ''),
|
|
349
|
+
message=raw_issue.get('message', '未知问题'),
|
|
350
|
+
severity=severity,
|
|
351
|
+
position=position,
|
|
352
|
+
file_path=file_path,
|
|
353
|
+
rule_name=raw_issue.get('rule_name'),
|
|
354
|
+
source=raw_issue.get('source'),
|
|
355
|
+
fix_available=raw_issue.get('fix_available', False),
|
|
356
|
+
fix_description=raw_issue.get('fix_description')
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
issues.append(issue)
|
|
360
|
+
|
|
361
|
+
# 创建文件结果
|
|
362
|
+
return FileLintResult(
|
|
363
|
+
file_path=file_path,
|
|
364
|
+
success=raw_result.get('success', True),
|
|
365
|
+
language=language,
|
|
366
|
+
issues=issues,
|
|
367
|
+
error=raw_result.get('error'),
|
|
368
|
+
warning_count=warning_count,
|
|
369
|
+
error_count=error_count,
|
|
370
|
+
info_count=info_count,
|
|
371
|
+
execution_time_ms=raw_result.get('execution_time_ms'),
|
|
372
|
+
fixed_issues_count=raw_result.get('fixed_issues_count')
|
|
373
|
+
)
|
|
@@ -11,6 +11,7 @@ import tempfile
|
|
|
11
11
|
from typing import Dict, List, Any, Optional, Tuple
|
|
12
12
|
|
|
13
13
|
from autocoder.linters.base_linter import BaseLinter
|
|
14
|
+
from loguru import logger
|
|
14
15
|
|
|
15
16
|
class PythonLinter(BaseLinter):
|
|
16
17
|
"""
|
|
@@ -142,6 +143,8 @@ class PythonLinter(BaseLinter):
|
|
|
142
143
|
'warning_count': 0,
|
|
143
144
|
'issues': []
|
|
144
145
|
}
|
|
146
|
+
|
|
147
|
+
logger.info(f"Running pylint on {target}")
|
|
145
148
|
|
|
146
149
|
try:
|
|
147
150
|
# Create a temp file to store JSON output
|
|
@@ -152,8 +155,7 @@ class PythonLinter(BaseLinter):
|
|
|
152
155
|
cmd = [
|
|
153
156
|
sys.executable,
|
|
154
157
|
"-m",
|
|
155
|
-
"pylint",
|
|
156
|
-
"--disable=import-error",
|
|
158
|
+
"pylint",
|
|
157
159
|
"--output-format=json",
|
|
158
160
|
target
|
|
159
161
|
]
|
autocoder/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.1.
|
|
1
|
+
__version__ = "0.1.364"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|