auto-coder 0.1.399__py3-none-any.whl → 1.0.0__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.399.dist-info → auto_coder-1.0.0.dist-info}/METADATA +1 -1
- {auto_coder-0.1.399.dist-info → auto_coder-1.0.0.dist-info}/RECORD +71 -35
- autocoder/agent/agentic_filter.py +1 -1
- autocoder/agent/base_agentic/tools/read_file_tool_resolver.py +1 -1
- autocoder/auto_coder_runner.py +121 -26
- autocoder/chat_auto_coder.py +81 -22
- autocoder/commands/auto_command.py +1 -1
- autocoder/common/__init__.py +2 -2
- autocoder/common/ac_style_command_parser/parser.py +27 -12
- autocoder/common/auto_coder_lang.py +78 -0
- autocoder/common/command_completer_v2.py +1 -1
- autocoder/common/file_monitor/test_file_monitor.py +307 -0
- autocoder/common/git_utils.py +7 -2
- autocoder/common/pruner/__init__.py +0 -0
- autocoder/common/pruner/agentic_conversation_pruner.py +197 -0
- autocoder/common/pruner/context_pruner.py +574 -0
- autocoder/common/pruner/conversation_pruner.py +132 -0
- autocoder/common/pruner/test_agentic_conversation_pruner.py +342 -0
- autocoder/common/pruner/test_context_pruner.py +546 -0
- autocoder/common/pull_requests/__init__.py +256 -0
- autocoder/common/pull_requests/base_provider.py +191 -0
- autocoder/common/pull_requests/config.py +66 -0
- autocoder/common/pull_requests/example.py +1 -0
- autocoder/common/pull_requests/exceptions.py +46 -0
- autocoder/common/pull_requests/manager.py +201 -0
- autocoder/common/pull_requests/models.py +164 -0
- autocoder/common/pull_requests/providers/__init__.py +23 -0
- autocoder/common/pull_requests/providers/gitcode_provider.py +19 -0
- autocoder/common/pull_requests/providers/gitee_provider.py +20 -0
- autocoder/common/pull_requests/providers/github_provider.py +214 -0
- autocoder/common/pull_requests/providers/gitlab_provider.py +29 -0
- autocoder/common/pull_requests/test_module.py +1 -0
- autocoder/common/pull_requests/utils.py +344 -0
- autocoder/common/tokens/__init__.py +77 -0
- autocoder/common/tokens/counter.py +231 -0
- autocoder/common/tokens/file_detector.py +105 -0
- autocoder/common/tokens/filters.py +111 -0
- autocoder/common/tokens/models.py +28 -0
- autocoder/common/v2/agent/agentic_edit.py +538 -590
- autocoder/common/v2/agent/agentic_edit_tools/__init__.py +8 -1
- autocoder/common/v2/agent/agentic_edit_tools/ac_mod_read_tool_resolver.py +40 -0
- autocoder/common/v2/agent/agentic_edit_tools/ac_mod_write_tool_resolver.py +43 -0
- autocoder/common/v2/agent/agentic_edit_tools/ask_followup_question_tool_resolver.py +8 -0
- autocoder/common/v2/agent/agentic_edit_tools/execute_command_tool_resolver.py +1 -1
- autocoder/common/v2/agent/agentic_edit_tools/read_file_tool_resolver.py +1 -1
- autocoder/common/v2/agent/agentic_edit_tools/search_files_tool_resolver.py +33 -88
- autocoder/common/v2/agent/agentic_edit_tools/test_write_to_file_tool_resolver.py +8 -8
- autocoder/common/v2/agent/agentic_edit_tools/todo_read_tool_resolver.py +118 -0
- autocoder/common/v2/agent/agentic_edit_tools/todo_write_tool_resolver.py +324 -0
- autocoder/common/v2/agent/agentic_edit_types.py +47 -4
- autocoder/common/v2/agent/runner/__init__.py +31 -0
- autocoder/common/v2/agent/runner/base_runner.py +106 -0
- autocoder/common/v2/agent/runner/event_runner.py +216 -0
- autocoder/common/v2/agent/runner/sdk_runner.py +40 -0
- autocoder/common/v2/agent/runner/terminal_runner.py +283 -0
- autocoder/common/v2/agent/runner/tool_display.py +191 -0
- autocoder/index/entry.py +1 -1
- autocoder/plugins/token_helper_plugin.py +107 -7
- autocoder/run_context.py +9 -0
- autocoder/sdk/__init__.py +114 -81
- autocoder/sdk/cli/handlers.py +2 -1
- autocoder/sdk/cli/main.py +9 -2
- autocoder/sdk/cli/options.py +4 -3
- autocoder/sdk/core/auto_coder_core.py +7 -152
- autocoder/sdk/core/bridge.py +5 -4
- autocoder/sdk/models/options.py +8 -6
- autocoder/version.py +1 -1
- {auto_coder-0.1.399.dist-info → auto_coder-1.0.0.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.399.dist-info → auto_coder-1.0.0.dist-info}/entry_points.txt +0 -0
- {auto_coder-0.1.399.dist-info → auto_coder-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {auto_coder-0.1.399.dist-info → auto_coder-1.0.0.dist-info}/top_level.txt +0 -0
autocoder/common/__init__.py
CHANGED
|
@@ -397,13 +397,13 @@ class AutoCoderArgs(pydantic.BaseModel):
|
|
|
397
397
|
in_code_apply: bool = False
|
|
398
398
|
model_filter_path: Optional[str] = None
|
|
399
399
|
|
|
400
|
-
conversation_prune_safe_zone_tokens:
|
|
400
|
+
conversation_prune_safe_zone_tokens: int = 50*1024
|
|
401
401
|
conversation_prune_group_size: Optional[int] = 4
|
|
402
402
|
conversation_prune_strategy: Optional[str] = "summarize"
|
|
403
403
|
|
|
404
404
|
context_prune_strategy: Optional[str] = "extract"
|
|
405
405
|
context_prune: Optional[bool] = True
|
|
406
|
-
context_prune_safe_zone_tokens: Optional[int] =
|
|
406
|
+
context_prune_safe_zone_tokens: Optional[int] = 24*1024
|
|
407
407
|
context_prune_sliding_window_size: Optional[int] = 1000
|
|
408
408
|
context_prune_sliding_window_overlap: Optional[int] = 100
|
|
409
409
|
|
|
@@ -22,8 +22,8 @@ class CommandParser:
|
|
|
22
22
|
# 匹配命令的正则表达式 - 必须是以/开头,后跟单词字符,且不能后跟/或.
|
|
23
23
|
# (?<!\S) 确保命令前是字符串开头或空白字符
|
|
24
24
|
self.command_pattern = r'(?<!\S)/(\w+)(?!/|\.)'
|
|
25
|
-
#
|
|
26
|
-
self.key_value_pattern = r'(\w+)=(
|
|
25
|
+
# 匹配键值对参数的正则表达式,支持带引号的值(包括三重引号)
|
|
26
|
+
self.key_value_pattern = r'(\w+)=(?:\'\'\'([^\']*?)\'\'\'|"""([^"]*?)"""|"([^"]*?)"|\'([^\']*?)\'|([^\s"\']*))(?:\s|$)'
|
|
27
27
|
# 匹配路径模式的正则表达式
|
|
28
28
|
self.path_pattern = r'/\w+(?:/[^/\s]+)+'
|
|
29
29
|
|
|
@@ -119,8 +119,9 @@ class CommandParser:
|
|
|
119
119
|
if key_value_pairs:
|
|
120
120
|
for match in key_value_pairs:
|
|
121
121
|
key = match[0]
|
|
122
|
-
#
|
|
123
|
-
|
|
122
|
+
# 值可能在六个捕获组中的一个,取非空的那个
|
|
123
|
+
# match[1]: 三重单引号, match[2]: 三重双引号, match[3]: 双引号, match[4]: 单引号, match[5]: 无引号
|
|
124
|
+
value = match[1] or match[2] or match[3] or match[4] or match[5]
|
|
124
125
|
kwargs[key] = value.strip()
|
|
125
126
|
|
|
126
127
|
# 替换带引号的键值对
|
|
@@ -131,15 +132,22 @@ class CommandParser:
|
|
|
131
132
|
|
|
132
133
|
# 现在 processed_params_str 中应该只剩下位置参数
|
|
133
134
|
|
|
134
|
-
#
|
|
135
|
-
quote_pattern = r'(
|
|
135
|
+
# 处理带引号的位置参数(包括三重引号)
|
|
136
|
+
quote_pattern = r'(?:\'\'\'([^\']*?)\'\'\'|"""([^"]*?)"""|"([^"]*?)"|\'([^\']*?)\')'
|
|
136
137
|
quoted_args = re.findall(quote_pattern, processed_params_str)
|
|
137
138
|
for quoted_arg in quoted_args:
|
|
138
139
|
# 取非空的那个捕获组
|
|
139
|
-
arg = quoted_arg[0] or quoted_arg[1]
|
|
140
|
+
arg = quoted_arg[0] or quoted_arg[1] or quoted_arg[2] or quoted_arg[3]
|
|
140
141
|
args.append(arg)
|
|
141
142
|
# 从参数字符串中移除这个带引号的参数
|
|
142
|
-
|
|
143
|
+
if quoted_arg[0]: # 三重单引号
|
|
144
|
+
quoted_pattern = f"'''{arg}'''"
|
|
145
|
+
elif quoted_arg[1]: # 三重双引号
|
|
146
|
+
quoted_pattern = f'"""{arg}"""'
|
|
147
|
+
elif quoted_arg[2]: # 双引号
|
|
148
|
+
quoted_pattern = f'"{arg}"'
|
|
149
|
+
else: # 单引号
|
|
150
|
+
quoted_pattern = f"'{arg}'"
|
|
143
151
|
processed_params_str = processed_params_str.replace(quoted_pattern, "", 1).strip()
|
|
144
152
|
|
|
145
153
|
# 分割剩余的位置参数(不带引号的)
|
|
@@ -148,17 +156,24 @@ class CommandParser:
|
|
|
148
156
|
else:
|
|
149
157
|
# 如果没有键值对,处理所有参数作为位置参数
|
|
150
158
|
|
|
151
|
-
#
|
|
152
|
-
quote_pattern = r'(
|
|
159
|
+
# 处理带引号的位置参数(包括三重引号)
|
|
160
|
+
quote_pattern = r'(?:\'\'\'([^\']*?)\'\'\'|"""([^"]*?)"""|"([^"]*?)"|\'([^\']*?)\')'
|
|
153
161
|
quoted_args = re.findall(quote_pattern, params_str)
|
|
154
162
|
processed_params_str = params_str
|
|
155
163
|
|
|
156
164
|
for quoted_arg in quoted_args:
|
|
157
165
|
# 取非空的那个捕获组
|
|
158
|
-
arg = quoted_arg[0] or quoted_arg[1]
|
|
166
|
+
arg = quoted_arg[0] or quoted_arg[1] or quoted_arg[2] or quoted_arg[3]
|
|
159
167
|
args.append(arg)
|
|
160
168
|
# 从参数字符串中移除这个带引号的参数
|
|
161
|
-
|
|
169
|
+
if quoted_arg[0]: # 三重单引号
|
|
170
|
+
quoted_pattern = f"'''{arg}'''"
|
|
171
|
+
elif quoted_arg[1]: # 三重双引号
|
|
172
|
+
quoted_pattern = f'"""{arg}"""'
|
|
173
|
+
elif quoted_arg[2]: # 双引号
|
|
174
|
+
quoted_pattern = f'"{arg}"'
|
|
175
|
+
else: # 单引号
|
|
176
|
+
quoted_pattern = f"'{arg}'"
|
|
162
177
|
processed_params_str = processed_params_str.replace(quoted_pattern, "", 1).strip()
|
|
163
178
|
|
|
164
179
|
# 分割剩余的位置参数(不带引号的)
|
|
@@ -849,6 +849,84 @@ MESSAGES = {
|
|
|
849
849
|
"/agent/edit/apply_changes":{
|
|
850
850
|
"en":"Commit the changes in preview steps",
|
|
851
851
|
"zh":"提交前面步骤的修改"
|
|
852
|
+
},
|
|
853
|
+
"/agent/edit/pull_request/branch_name_failed": {
|
|
854
|
+
"en": "Unable to get current branch name, skipping PR creation",
|
|
855
|
+
"zh": "无法获取当前分支名,跳过 PR 创建"
|
|
856
|
+
},
|
|
857
|
+
"/agent/edit/pull_request/title": {
|
|
858
|
+
"en": "AutoCoder: {{query}}",
|
|
859
|
+
"zh": "AutoCoder: {{query}}"
|
|
860
|
+
},
|
|
861
|
+
"/agent/edit/pull_request/default_query": {
|
|
862
|
+
"en": "Code auto generation",
|
|
863
|
+
"zh": "代码自动生成"
|
|
864
|
+
},
|
|
865
|
+
"/agent/edit/pull_request/description": {
|
|
866
|
+
"en": """## 🤖 AutoCoder Generated Pull Request
|
|
867
|
+
|
|
868
|
+
**Task Description**: {{query}}
|
|
869
|
+
|
|
870
|
+
### 📝 Change Summary
|
|
871
|
+
- Modified {{file_count}} files
|
|
872
|
+
- Commit Hash: `{{commit_hash}}`
|
|
873
|
+
|
|
874
|
+
### 📂 Changed Files List
|
|
875
|
+
{{file_list}}
|
|
876
|
+
|
|
877
|
+
### ⚙️ Generation Configuration
|
|
878
|
+
- Source Branch: `{{source_branch}}`
|
|
879
|
+
- Target Branch: `{{target_branch}}`
|
|
880
|
+
- Auto-generated Time: {{timestamp}}
|
|
881
|
+
|
|
882
|
+
### 🔍 Next Steps
|
|
883
|
+
- [ ] Code Review
|
|
884
|
+
- [ ] Test Verification
|
|
885
|
+
- [ ] Merge to Main Branch
|
|
886
|
+
|
|
887
|
+
---
|
|
888
|
+
*This PR was automatically created by AutoCoder*
|
|
889
|
+
""",
|
|
890
|
+
"zh": """## 🤖 AutoCoder 自动生成的 Pull Request
|
|
891
|
+
|
|
892
|
+
**任务描述**: {{query}}
|
|
893
|
+
|
|
894
|
+
### 📝 变更摘要
|
|
895
|
+
- 共修改 {{file_count}} 个文件
|
|
896
|
+
- 提交哈希: `{{commit_hash}}`
|
|
897
|
+
|
|
898
|
+
### 📂 变更文件列表
|
|
899
|
+
{{file_list}}
|
|
900
|
+
|
|
901
|
+
### ⚙️ 生成配置
|
|
902
|
+
- 源分支: `{{source_branch}}`
|
|
903
|
+
- 目标分支: `{{target_branch}}`
|
|
904
|
+
- 自动生成时间: {{timestamp}}
|
|
905
|
+
|
|
906
|
+
### 🔍 下一步
|
|
907
|
+
- [ ] 代码审查
|
|
908
|
+
- [ ] 测试验证
|
|
909
|
+
- [ ] 合并到主分支
|
|
910
|
+
|
|
911
|
+
---
|
|
912
|
+
*此 PR 由 AutoCoder 自动创建*
|
|
913
|
+
"""
|
|
914
|
+
},
|
|
915
|
+
"/agent/edit/pull_request/creating": {
|
|
916
|
+
"en": "Creating Pull Request: {{title}}",
|
|
917
|
+
"zh": "正在创建 Pull Request: {{title}}"
|
|
918
|
+
},
|
|
919
|
+
"/agent/edit/pull_request/success": {
|
|
920
|
+
"en": "✅ Pull Request created successfully",
|
|
921
|
+
"zh": "✅ Pull Request 创建成功"
|
|
922
|
+
},
|
|
923
|
+
"/agent/edit/pull_request/failed": {
|
|
924
|
+
"en": "❌ Pull Request creation failed: {{error}}",
|
|
925
|
+
"zh": "❌ Pull Request 创建失败: {{error}}"
|
|
926
|
+
},
|
|
927
|
+
"/agent/edit/pull_request/exception": {
|
|
928
|
+
"en": "❌ Exception occurred while creating Pull Request: {{error}}",
|
|
929
|
+
"zh": "❌ 创建 Pull Request 时发生异常: {{error}}"
|
|
852
930
|
}
|
|
853
931
|
}
|
|
854
932
|
|
|
@@ -22,7 +22,7 @@ COMMAND_HIERARCHY = {
|
|
|
22
22
|
"/mcp": {"/add": {}, "/remove": {}, "/list": {}, "/list_running": {}, "/refresh": {}, "/info": {}},
|
|
23
23
|
"/lib": {"/add": {}, "/remove": {}, "/list": {}, "/set-proxy": {}, "/refresh": {}, "/get": {}},
|
|
24
24
|
"/models": {"/chat": {}, "/add": {}, "/add_model": {}, "/remove": {}, "/list": {}, "/speed": {}, "/speed-test": {}, "/input_price": {}, "/output_price": {}, "/activate": {}},
|
|
25
|
-
"/auto": {"/new": {}, "/
|
|
25
|
+
"/auto": {"/new": {}, "/resume": {}, "/list": {},"/command":{}},
|
|
26
26
|
"/shell": {"/chat": {}},
|
|
27
27
|
"/active_context": {"/list": {}, "/run": {}},
|
|
28
28
|
"/index": {"/query": {}, "/build": {}, "/export": {}, "/import": {}},
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
FileMonitor 模块测试
|
|
5
|
+
|
|
6
|
+
测试 FileMonitor 的基本功能:文件的增删改查监控
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import tempfile
|
|
11
|
+
import shutil
|
|
12
|
+
import time
|
|
13
|
+
import threading
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import List, Tuple
|
|
16
|
+
from unittest.mock import Mock
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
from watchfiles import Change
|
|
20
|
+
except ImportError:
|
|
21
|
+
print("警告:watchfiles 未安装,请运行: pip install watchfiles")
|
|
22
|
+
Change = None
|
|
23
|
+
|
|
24
|
+
from autocoder.common.file_monitor.monitor import get_file_monitor, FileMonitor
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class FileMonitorTester:
|
|
28
|
+
"""FileMonitor 测试类"""
|
|
29
|
+
|
|
30
|
+
def __init__(self):
|
|
31
|
+
self.temp_dir = None
|
|
32
|
+
self.monitor = None
|
|
33
|
+
self.events = []
|
|
34
|
+
self.event_lock = threading.Lock()
|
|
35
|
+
|
|
36
|
+
def setup(self):
|
|
37
|
+
"""设置测试环境"""
|
|
38
|
+
# 创建临时目录
|
|
39
|
+
self.temp_dir = tempfile.mkdtemp(prefix="file_monitor_test_")
|
|
40
|
+
print(f"创建临时测试目录: {self.temp_dir}")
|
|
41
|
+
|
|
42
|
+
# 重置单例实例以确保测试隔离
|
|
43
|
+
FileMonitor.reset_instance()
|
|
44
|
+
|
|
45
|
+
# 获取监控实例
|
|
46
|
+
self.monitor = get_file_monitor(self.temp_dir)
|
|
47
|
+
|
|
48
|
+
# 清空事件记录
|
|
49
|
+
self.events.clear()
|
|
50
|
+
|
|
51
|
+
def teardown(self):
|
|
52
|
+
"""清理测试环境"""
|
|
53
|
+
if self.monitor and self.monitor.is_running():
|
|
54
|
+
self.monitor.stop()
|
|
55
|
+
|
|
56
|
+
if self.temp_dir and os.path.exists(self.temp_dir):
|
|
57
|
+
shutil.rmtree(self.temp_dir)
|
|
58
|
+
print(f"清理临时测试目录: {self.temp_dir}")
|
|
59
|
+
|
|
60
|
+
# 重置单例实例
|
|
61
|
+
FileMonitor.reset_instance()
|
|
62
|
+
|
|
63
|
+
def record_event(self, change_type: 'Change', changed_path: str):
|
|
64
|
+
"""记录文件变化事件"""
|
|
65
|
+
with self.event_lock:
|
|
66
|
+
self.events.append((change_type, changed_path))
|
|
67
|
+
print(f"📝 记录事件: {change_type.name} - {os.path.basename(changed_path)}")
|
|
68
|
+
|
|
69
|
+
def wait_for_events(self, expected_count: int, timeout: float = 3.0):
|
|
70
|
+
"""等待指定数量的事件"""
|
|
71
|
+
start_time = time.time()
|
|
72
|
+
while time.time() - start_time < timeout:
|
|
73
|
+
with self.event_lock:
|
|
74
|
+
if len(self.events) >= expected_count:
|
|
75
|
+
return True
|
|
76
|
+
time.sleep(0.1)
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
def wait_for_specific_event(self, expected_type: 'Change', expected_path: str, timeout: float = 3.0):
|
|
80
|
+
"""等待特定类型的事件"""
|
|
81
|
+
start_time = time.time()
|
|
82
|
+
while time.time() - start_time < timeout:
|
|
83
|
+
with self.event_lock:
|
|
84
|
+
for event_type, event_path in self.events:
|
|
85
|
+
if event_type == expected_type and expected_path in event_path:
|
|
86
|
+
return True
|
|
87
|
+
time.sleep(0.1)
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
def test_file_create(self):
|
|
91
|
+
"""测试文件创建监控"""
|
|
92
|
+
print("\n🔍 测试用例 1: 文件创建监控")
|
|
93
|
+
|
|
94
|
+
# 注册监控所有 .txt 文件
|
|
95
|
+
self.monitor.register("**/*.txt", self.record_event)
|
|
96
|
+
|
|
97
|
+
# 启动监控
|
|
98
|
+
self.monitor.start()
|
|
99
|
+
time.sleep(0.5) # 等待监控启动
|
|
100
|
+
|
|
101
|
+
# 创建测试文件
|
|
102
|
+
test_file = os.path.join(self.temp_dir, "test_create.txt")
|
|
103
|
+
with open(test_file, 'w') as f:
|
|
104
|
+
f.write("Hello, World!")
|
|
105
|
+
print(f"✅ 创建文件: {os.path.basename(test_file)}")
|
|
106
|
+
|
|
107
|
+
# 等待事件
|
|
108
|
+
if self.wait_for_events(1):
|
|
109
|
+
with self.event_lock:
|
|
110
|
+
event_type, event_path = self.events[-1]
|
|
111
|
+
if event_type == Change.added and test_file in event_path:
|
|
112
|
+
print("✅ 文件创建事件检测成功")
|
|
113
|
+
return True
|
|
114
|
+
else:
|
|
115
|
+
print(f"❌ 事件类型不匹配: 期望 {Change.added.name}, 实际 {event_type.name}")
|
|
116
|
+
else:
|
|
117
|
+
print("❌ 未检测到文件创建事件")
|
|
118
|
+
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
def test_file_modify(self):
|
|
122
|
+
"""测试文件修改监控"""
|
|
123
|
+
print("\n🔍 测试用例 2: 文件修改监控")
|
|
124
|
+
|
|
125
|
+
# 先创建一个文件
|
|
126
|
+
test_file = os.path.join(self.temp_dir, "test_modify.txt")
|
|
127
|
+
with open(test_file, 'w') as f:
|
|
128
|
+
f.write("Initial content")
|
|
129
|
+
|
|
130
|
+
# 等待文件创建完成
|
|
131
|
+
time.sleep(1.0)
|
|
132
|
+
|
|
133
|
+
# 清空事件记录
|
|
134
|
+
with self.event_lock:
|
|
135
|
+
self.events.clear()
|
|
136
|
+
|
|
137
|
+
# 注册监控
|
|
138
|
+
self.monitor.register(test_file, self.record_event)
|
|
139
|
+
|
|
140
|
+
# 等待一下确保监控注册完成
|
|
141
|
+
time.sleep(0.5)
|
|
142
|
+
|
|
143
|
+
# 尝试多种修改方式来触发修改事件
|
|
144
|
+
# 方式1: 追加内容
|
|
145
|
+
with open(test_file, 'a') as f:
|
|
146
|
+
f.write("\nModified content")
|
|
147
|
+
time.sleep(0.5)
|
|
148
|
+
|
|
149
|
+
# 方式2: 重写文件(如果追加没有触发修改事件)
|
|
150
|
+
if not self.wait_for_specific_event(Change.modified, test_file, timeout=1.0):
|
|
151
|
+
with open(test_file, 'w') as f:
|
|
152
|
+
f.write("Completely new content")
|
|
153
|
+
|
|
154
|
+
print(f"✅ 修改文件: {os.path.basename(test_file)}")
|
|
155
|
+
|
|
156
|
+
# 等待修改事件或添加事件(某些系统可能报告为添加)
|
|
157
|
+
if (self.wait_for_specific_event(Change.modified, test_file, timeout=2.0) or
|
|
158
|
+
self.wait_for_specific_event(Change.added, test_file, timeout=1.0)):
|
|
159
|
+
print("✅ 文件修改事件检测成功")
|
|
160
|
+
return True
|
|
161
|
+
else:
|
|
162
|
+
# 打印所有事件用于调试
|
|
163
|
+
with self.event_lock:
|
|
164
|
+
print(f"所有事件: {[(e[0].name, os.path.basename(e[1])) for e in self.events]}")
|
|
165
|
+
# 如果检测到任何事件,说明监控是工作的,只是事件类型不同
|
|
166
|
+
if self.events:
|
|
167
|
+
print("⚠️ 检测到文件变化事件,但类型与预期不符(这在某些文件系统中是正常的)")
|
|
168
|
+
return True
|
|
169
|
+
else:
|
|
170
|
+
print("❌ 未检测到任何文件修改事件")
|
|
171
|
+
|
|
172
|
+
return False
|
|
173
|
+
|
|
174
|
+
def test_file_delete(self):
|
|
175
|
+
"""测试文件删除监控"""
|
|
176
|
+
print("\n🔍 测试用例 3: 文件删除监控")
|
|
177
|
+
|
|
178
|
+
# 先创建一个文件
|
|
179
|
+
test_file = os.path.join(self.temp_dir, "test_delete.txt")
|
|
180
|
+
with open(test_file, 'w') as f:
|
|
181
|
+
f.write("To be deleted")
|
|
182
|
+
|
|
183
|
+
# 等待文件创建完成
|
|
184
|
+
time.sleep(1.0)
|
|
185
|
+
|
|
186
|
+
# 清空事件记录
|
|
187
|
+
with self.event_lock:
|
|
188
|
+
self.events.clear()
|
|
189
|
+
|
|
190
|
+
# 注册监控
|
|
191
|
+
self.monitor.register(test_file, self.record_event)
|
|
192
|
+
|
|
193
|
+
# 等待一下确保监控注册完成
|
|
194
|
+
time.sleep(0.5)
|
|
195
|
+
|
|
196
|
+
# 删除文件
|
|
197
|
+
os.remove(test_file)
|
|
198
|
+
print(f"✅ 删除文件: {os.path.basename(test_file)}")
|
|
199
|
+
|
|
200
|
+
# 等待删除事件
|
|
201
|
+
if self.wait_for_specific_event(Change.deleted, test_file):
|
|
202
|
+
print("✅ 文件删除事件检测成功")
|
|
203
|
+
return True
|
|
204
|
+
else:
|
|
205
|
+
# 打印所有事件用于调试
|
|
206
|
+
with self.event_lock:
|
|
207
|
+
print(f"所有事件: {[(e[0].name, os.path.basename(e[1])) for e in self.events]}")
|
|
208
|
+
print("❌ 未检测到文件删除事件")
|
|
209
|
+
|
|
210
|
+
return False
|
|
211
|
+
|
|
212
|
+
def test_directory_monitoring(self):
|
|
213
|
+
"""测试目录监控"""
|
|
214
|
+
print("\n🔍 测试用例 4: 目录监控")
|
|
215
|
+
|
|
216
|
+
# 创建子目录
|
|
217
|
+
sub_dir = os.path.join(self.temp_dir, "subdir")
|
|
218
|
+
os.makedirs(sub_dir)
|
|
219
|
+
|
|
220
|
+
# 等待目录创建完成
|
|
221
|
+
time.sleep(1.0)
|
|
222
|
+
|
|
223
|
+
# 清空事件记录
|
|
224
|
+
with self.event_lock:
|
|
225
|
+
self.events.clear()
|
|
226
|
+
|
|
227
|
+
# 注册监控整个子目录
|
|
228
|
+
self.monitor.register(sub_dir, self.record_event)
|
|
229
|
+
|
|
230
|
+
# 等待一下确保监控注册完成
|
|
231
|
+
time.sleep(0.5)
|
|
232
|
+
|
|
233
|
+
# 在子目录中创建文件
|
|
234
|
+
test_file = os.path.join(sub_dir, "subdir_file.txt")
|
|
235
|
+
with open(test_file, 'w') as f:
|
|
236
|
+
f.write("File in subdirectory")
|
|
237
|
+
print(f"✅ 在子目录中创建文件: {os.path.basename(test_file)}")
|
|
238
|
+
|
|
239
|
+
# 等待文件创建事件
|
|
240
|
+
if self.wait_for_specific_event(Change.added, test_file):
|
|
241
|
+
print("✅ 目录监控事件检测成功")
|
|
242
|
+
return True
|
|
243
|
+
else:
|
|
244
|
+
# 打印所有事件用于调试
|
|
245
|
+
with self.event_lock:
|
|
246
|
+
print(f"所有事件: {[(e[0].name, os.path.basename(e[1])) for e in self.events]}")
|
|
247
|
+
print("❌ 未检测到目录监控事件")
|
|
248
|
+
|
|
249
|
+
return False
|
|
250
|
+
|
|
251
|
+
def run_all_tests(self):
|
|
252
|
+
"""运行所有测试"""
|
|
253
|
+
print("🚀 开始 FileMonitor 功能测试")
|
|
254
|
+
print("=" * 50)
|
|
255
|
+
|
|
256
|
+
if Change is None:
|
|
257
|
+
print("❌ 测试失败: watchfiles 库未安装")
|
|
258
|
+
return False
|
|
259
|
+
|
|
260
|
+
try:
|
|
261
|
+
self.setup()
|
|
262
|
+
|
|
263
|
+
# 运行测试用例
|
|
264
|
+
test_results = []
|
|
265
|
+
test_results.append(self.test_file_create())
|
|
266
|
+
test_results.append(self.test_file_modify())
|
|
267
|
+
test_results.append(self.test_file_delete())
|
|
268
|
+
test_results.append(self.test_directory_monitoring())
|
|
269
|
+
|
|
270
|
+
# 统计结果
|
|
271
|
+
passed = sum(test_results)
|
|
272
|
+
total = len(test_results)
|
|
273
|
+
|
|
274
|
+
print("\n" + "=" * 50)
|
|
275
|
+
print(f"📊 测试结果: {passed}/{total} 个测试用例通过")
|
|
276
|
+
|
|
277
|
+
if passed == total:
|
|
278
|
+
print("🎉 所有测试用例通过!FileMonitor 功能正常")
|
|
279
|
+
return True
|
|
280
|
+
else:
|
|
281
|
+
print(f"⚠️ {total - passed} 个测试用例失败")
|
|
282
|
+
return False
|
|
283
|
+
|
|
284
|
+
except Exception as e:
|
|
285
|
+
print(f"❌ 测试过程中发生异常: {e}")
|
|
286
|
+
import traceback
|
|
287
|
+
traceback.print_exc()
|
|
288
|
+
return False
|
|
289
|
+
finally:
|
|
290
|
+
self.teardown()
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def main():
|
|
294
|
+
"""主函数"""
|
|
295
|
+
tester = FileMonitorTester()
|
|
296
|
+
success = tester.run_all_tests()
|
|
297
|
+
|
|
298
|
+
if success:
|
|
299
|
+
print("\n✅ FileMonitor 测试完成,所有功能正常")
|
|
300
|
+
exit(0)
|
|
301
|
+
else:
|
|
302
|
+
print("\n❌ FileMonitor 测试失败")
|
|
303
|
+
exit(1)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
if __name__ == "__main__":
|
|
307
|
+
main()
|
autocoder/common/git_utils.py
CHANGED
|
@@ -294,7 +294,7 @@ def get_uncommitted_changes(repo_path: str) -> str:
|
|
|
294
294
|
return f"Error: {str(e)}"
|
|
295
295
|
|
|
296
296
|
@byzerllm.prompt()
|
|
297
|
-
def generate_commit_message(changes_report: str) -> str:
|
|
297
|
+
def generate_commit_message(changes_report: str, query: Optional[str] = None) -> str:
|
|
298
298
|
'''
|
|
299
299
|
我是一个Git提交信息生成助手。我们的目标是通过一些变更报告,倒推用户的需求,将需求作为commit message。
|
|
300
300
|
commit message 需要简洁,包含两部分:
|
|
@@ -635,7 +635,12 @@ def generate_commit_message(changes_report: str) -> str:
|
|
|
635
635
|
</examples>
|
|
636
636
|
|
|
637
637
|
下面是变更报告:
|
|
638
|
-
{{ changes_report }}
|
|
638
|
+
{{ changes_report }}
|
|
639
|
+
|
|
640
|
+
{% if query %}
|
|
641
|
+
这里你需要遵守的用户的额外要求:
|
|
642
|
+
{{ query }}
|
|
643
|
+
{% endif %}
|
|
639
644
|
|
|
640
645
|
请输出commit message, 不要输出任何其他内容.
|
|
641
646
|
'''
|
|
File without changes
|