auto-coder 0.1.379__py3-none-any.whl → 0.1.381__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.379.dist-info → auto_coder-0.1.381.dist-info}/METADATA +2 -1
- {auto_coder-0.1.379.dist-info → auto_coder-0.1.381.dist-info}/RECORD +18 -14
- autocoder/agent/base_agentic/tools/execute_command_tool_resolver.py +2 -3
- autocoder/common/command_file_manager/manager.py +1 -1
- autocoder/common/v2/agent/agentic_edit.py +2 -36
- autocoder/common/v2/agent/agentic_edit_tools/execute_command_tool_resolver.py +48 -8
- autocoder/common/v2/agent/agentic_edit_tools/test_execute_command_tool_resolver.py +70 -0
- autocoder/linters/python_linter.py +2 -22
- autocoder/linters/reactjs_linter.py +8 -1
- autocoder/linters/test_python_linter.py +166 -0
- autocoder/linters/test_reactjs_linter.py +183 -0
- autocoder/linters/test_vue_linter.py +217 -0
- autocoder/linters/vue_linter.py +9 -1
- autocoder/version.py +1 -1
- {auto_coder-0.1.379.dist-info → auto_coder-0.1.381.dist-info}/LICENSE +0 -0
- {auto_coder-0.1.379.dist-info → auto_coder-0.1.381.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.379.dist-info → auto_coder-0.1.381.dist-info}/entry_points.txt +0 -0
- {auto_coder-0.1.379.dist-info → auto_coder-0.1.381.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: auto-coder
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.381
|
|
4
4
|
Summary: AutoCoder: AutoCoder
|
|
5
5
|
Author: allwefantasy
|
|
6
6
|
Classifier: Programming Language :: Python :: 3.10
|
|
@@ -43,6 +43,7 @@ Requires-Dist: python-docx
|
|
|
43
43
|
Requires-Dist: docx2txt
|
|
44
44
|
Requires-Dist: pdf2image
|
|
45
45
|
Requires-Dist: docx2pdf
|
|
46
|
+
Requires-Dist: pypdf
|
|
46
47
|
Requires-Dist: pyperclip
|
|
47
48
|
Requires-Dist: colorama
|
|
48
49
|
Requires-Dist: pylint
|
|
@@ -11,7 +11,7 @@ autocoder/command_parser.py,sha256=fx1g9E6GaM273lGTcJqaFQ-hoksS_Ik2glBMnVltPCE,1
|
|
|
11
11
|
autocoder/lang.py,sha256=PFtATuOhHRnfpqHQkXr6p4C893JvpsgwTMif3l-GEi0,14321
|
|
12
12
|
autocoder/models.py,sha256=pD5u6gcMKRwWaLxeVin18g25k-ERyeHOFsRpOgO_Ae0,13788
|
|
13
13
|
autocoder/run_context.py,sha256=IUfSO6_gp2Wt1blFWAmOpN0b0nDrTTk4LmtCYUBIoro,1643
|
|
14
|
-
autocoder/version.py,sha256=
|
|
14
|
+
autocoder/version.py,sha256=EynnXiflVuvMfUc6x15mObliP7AT9Dp2I42Rg5rcOMI,25
|
|
15
15
|
autocoder/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
autocoder/agent/agentic_filter.py,sha256=zlInIRhawKIYTJjCiJBWqPCOV5UtMbh5VnvszfTy2vo,39824
|
|
17
17
|
autocoder/agent/auto_demand_organizer.py,sha256=URAq0gSEiHeV_W4zwhOI_83kHz0Ryfj1gcfh5jwCv_w,6501
|
|
@@ -40,7 +40,7 @@ autocoder/agent/base_agentic/tools/ask_followup_question_tool_resolver.py,sha256
|
|
|
40
40
|
autocoder/agent/base_agentic/tools/attempt_completion_tool_resolver.py,sha256=9a_qPihfm45Q22kmxmFwYrZrtNsgtEyBePL8D4SXfes,1487
|
|
41
41
|
autocoder/agent/base_agentic/tools/base_tool_resolver.py,sha256=Y0PmRPhBLLdZpr2DNV0W5goGfAqylM5m1bHxa3T6vAY,1009
|
|
42
42
|
autocoder/agent/base_agentic/tools/example_tool_resolver.py,sha256=8WBIqEH_76S0iBHBM3RhLkk14UpJ28lRAw6dNUT-0no,1388
|
|
43
|
-
autocoder/agent/base_agentic/tools/execute_command_tool_resolver.py,sha256=
|
|
43
|
+
autocoder/agent/base_agentic/tools/execute_command_tool_resolver.py,sha256=UL7eQz7Im4BhpglIxUDmEBZ9xwM_hhk1J9iY228uvPU,3691
|
|
44
44
|
autocoder/agent/base_agentic/tools/list_files_tool_resolver.py,sha256=SCAYAiqmgwuCLMwRwroUQFfVS_hbcuAoG9-jIJxiB1c,8061
|
|
45
45
|
autocoder/agent/base_agentic/tools/plan_mode_respond_tool_resolver.py,sha256=hiLdMRknR_Aljd7Ic2bOWZKs11aFHdhkRTKTlP3IIgw,1461
|
|
46
46
|
autocoder/agent/base_agentic/tools/read_file_tool_resolver.py,sha256=egcxrXv32stvhB3QawE2k62-9W5U3TNuY3qoTyKKhs0,5076
|
|
@@ -124,7 +124,7 @@ autocoder/common/types.py,sha256=Cw_4RH-rGmAgQE-Ck69maMAMqlPCDA4Yj37QmuUY0mQ,713
|
|
|
124
124
|
autocoder/common/utils_code_auto_generate.py,sha256=sqtLmVxRveRwqLwJ8UlDgwngwmh2AAo3vgl-I-ALcDQ,4597
|
|
125
125
|
autocoder/common/command_file_manager/__init__.py,sha256=ardiG_iuWhurfbN6RO4wij2U7Dmafnxn2vgWYYxKqDM,970
|
|
126
126
|
autocoder/common/command_file_manager/examples.py,sha256=kWNVK_iDvQn5NtA8D791Oxkj8xP5DWSD72PcMYIgwBQ,4067
|
|
127
|
-
autocoder/common/command_file_manager/manager.py,sha256=
|
|
127
|
+
autocoder/common/command_file_manager/manager.py,sha256=TrwtAbtvaxnjOBdaHMBnAOffe1_pvL1xkpibsfGUB68,5934
|
|
128
128
|
autocoder/common/command_file_manager/models.py,sha256=cDYRSQGK5O4nFI0GGHQvHcPeNKUxKePZQCgy7Mfw3kU,3471
|
|
129
129
|
autocoder/common/command_file_manager/utils.py,sha256=OA9OhB1VnkvnFGof9p6jGfIVx31usUcqucXZCkqNm4I,3488
|
|
130
130
|
autocoder/common/conversations/__init__.py,sha256=xGZeOFrDsgg2fkPK1zmvYBDhAyX66FtgOcZaxhYKJXU,1338
|
|
@@ -172,14 +172,14 @@ autocoder/common/v2/code_editblock_manager.py,sha256=DMwJw-FAM6VyaBQV3p4xespHpgZ
|
|
|
172
172
|
autocoder/common/v2/code_manager.py,sha256=C403bS-f6urixwitlKHcml-J03hci-UyNwHJOqBiY6Q,9182
|
|
173
173
|
autocoder/common/v2/code_strict_diff_manager.py,sha256=Bys7tFAq4G03R1zUZuxrszBTvP4QB96jIw2y5BDLyRM,9424
|
|
174
174
|
autocoder/common/v2/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
175
|
-
autocoder/common/v2/agent/agentic_edit.py,sha256=
|
|
175
|
+
autocoder/common/v2/agent/agentic_edit.py,sha256=aPYUhDyLPALIHzULCHnVSoDc6x2vcrAFXBknxc8k_lg,112210
|
|
176
176
|
autocoder/common/v2/agent/agentic_edit_types.py,sha256=nEcZc2MOZ_fQLaJX-YDha_x9Iim22ao4tykYM2iIy4k,4908
|
|
177
177
|
autocoder/common/v2/agent/agentic_tool_display.py,sha256=-a-JTQLc4q03E_rdIILKMI0B6DHN-5gcGlrqq-mBYK4,7239
|
|
178
178
|
autocoder/common/v2/agent/agentic_edit_tools/__init__.py,sha256=RbPZZcZg_VnGssL577GxSyFrYrxQ_LopJ4G_-mY3z_Q,1337
|
|
179
179
|
autocoder/common/v2/agent/agentic_edit_tools/ask_followup_question_tool_resolver.py,sha256=-HFXo3RR6CH8xXjDaE2mYV4XasTLAmvXe6WutL7qbwA,3208
|
|
180
180
|
autocoder/common/v2/agent/agentic_edit_tools/attempt_completion_tool_resolver.py,sha256=82ZGKeRBSDKeead_XVBW4FxpiE-5dS7tBOk_3RZ6B5s,1511
|
|
181
181
|
autocoder/common/v2/agent/agentic_edit_tools/base_tool_resolver.py,sha256=Zid2m1uZd-2wVFGc_n_KAViXZyNjbdLSpI5n7ut1RUQ,1036
|
|
182
|
-
autocoder/common/v2/agent/agentic_edit_tools/execute_command_tool_resolver.py,sha256=
|
|
182
|
+
autocoder/common/v2/agent/agentic_edit_tools/execute_command_tool_resolver.py,sha256=8dCjSvpo09QG4Jj-kh5iOTQtikp87W0Z6q_6XyezQDo,5439
|
|
183
183
|
autocoder/common/v2/agent/agentic_edit_tools/list_code_definition_names_tool_resolver.py,sha256=8QoMsADUDWliqiDt_dpguz31403syB8eeW0Pcw-qfb8,3842
|
|
184
184
|
autocoder/common/v2/agent/agentic_edit_tools/list_files_tool_resolver.py,sha256=1tJX9RYRU0zRjKZMzFlZzKm-4R32CzRnZ0UQJj4pxAk,8074
|
|
185
185
|
autocoder/common/v2/agent/agentic_edit_tools/list_package_info_tool_resolver.py,sha256=dIdV12VuczHpHuHgx2B1j_3BZYc9PL0jfHCuBk9ryk8,2005
|
|
@@ -187,6 +187,7 @@ autocoder/common/v2/agent/agentic_edit_tools/plan_mode_respond_tool_resolver.py,
|
|
|
187
187
|
autocoder/common/v2/agent/agentic_edit_tools/read_file_tool_resolver.py,sha256=6JhjM3VXV3BGelh1dNcdr-M5FoVPoqLkls1-y8ND8_c,6721
|
|
188
188
|
autocoder/common/v2/agent/agentic_edit_tools/replace_in_file_tool_resolver.py,sha256=ZTdImTBK7KTYH98JVUioBNtIz-dqSfhkmNEBhajt7hk,12686
|
|
189
189
|
autocoder/common/v2/agent/agentic_edit_tools/search_files_tool_resolver.py,sha256=VtDRDFJ2qzACy2jpD1ZNhywgCT3_iYUmGoUhHHmMX3o,9581
|
|
190
|
+
autocoder/common/v2/agent/agentic_edit_tools/test_execute_command_tool_resolver.py,sha256=cG4TBqJX0RP1w67xElt_KH8XzgAhSUbIhuOQFvSnDDE,2864
|
|
190
191
|
autocoder/common/v2/agent/agentic_edit_tools/test_search_files_tool_resolver.py,sha256=9eBo3WLkrr77iNotwIwVmH1ZL3UY0JQgLpdAIc9wTTM,6127
|
|
191
192
|
autocoder/common/v2/agent/agentic_edit_tools/test_write_to_file_tool_resolver.py,sha256=ZWRPsJny_My4UMzovrB8J2_x5N0rEW-xx3DVI-kDRFI,15870
|
|
192
193
|
autocoder/common/v2/agent/agentic_edit_tools/use_mcp_tool_resolver.py,sha256=wM2Xy4bcnD0TSLEmcM8rvvyyWenN5_KQnJMO6hJ8lTE,1716
|
|
@@ -237,10 +238,13 @@ autocoder/linters/base_linter.py,sha256=1_0DPESnSyF3ZcQhoFkBYJylT5w-B61Rx-3A9uhu
|
|
|
237
238
|
autocoder/linters/linter_factory.py,sha256=BgGeXPdli7BgiN9BifWoosyn9BGeJnRwSqX0G1R8qvU,10471
|
|
238
239
|
autocoder/linters/models.py,sha256=GBdayu_p50KBxoRms4X68zrDK-OsKDEKKjo926FevwE,9838
|
|
239
240
|
autocoder/linters/normal_linter.py,sha256=ezToVW33psvBXsGhE7y1ng7ucf7yT_1YuIULns6TXYM,13011
|
|
240
|
-
autocoder/linters/python_linter.py,sha256=
|
|
241
|
-
autocoder/linters/reactjs_linter.py,sha256=
|
|
241
|
+
autocoder/linters/python_linter.py,sha256=VFuKoHo9FTo_2YyscCghDgb1RfnpdiHGAYBB6img9Cs,18165
|
|
242
|
+
autocoder/linters/reactjs_linter.py,sha256=MvJVYgNJi0WxiEveexwqhSfN0FcwHxJ_wVjNx8H_rT4,21106
|
|
242
243
|
autocoder/linters/shadow_linter.py,sha256=SKgRNVnTavNUviFC9osYMz18nGWCVOPOHx9LavEbnmc,15047
|
|
243
|
-
autocoder/linters/
|
|
244
|
+
autocoder/linters/test_python_linter.py,sha256=zYPliyjm1s0IJmlIadyWQedsLNOGbaz7Q68_LmREO_w,6288
|
|
245
|
+
autocoder/linters/test_reactjs_linter.py,sha256=72VnejikldyEH5PdvJzrgtcxhnlGkVZTe3hWRGnw4mA,7099
|
|
246
|
+
autocoder/linters/test_vue_linter.py,sha256=m031brPZdK66Yi-hvlPvL0RYyQxvY43lHu_ATVxrvQs,7592
|
|
247
|
+
autocoder/linters/vue_linter.py,sha256=MjF-SgSbgOucyv0hnp2r56CIfe8hqDibs0NKnvUUvLw,21225
|
|
244
248
|
autocoder/memory/__init__.py,sha256=5FeGvsesRViYL4BkFiHw9SdlyHeWlqALpTyqOpfnBRw,179
|
|
245
249
|
autocoder/memory/active_context_manager.py,sha256=nqWD4lBLNcskXDRERhPpqnmn_i1V7_CTfQSN3xAX6b8,32297
|
|
246
250
|
autocoder/memory/active_package.py,sha256=NHLLnncFSfFcOFLWILwJLuEVd4nOoL0mqzFev6QHgzU,25480
|
|
@@ -329,9 +333,9 @@ autocoder/utils/types.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
329
333
|
autocoder/utils/auto_coder_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
330
334
|
autocoder/utils/auto_coder_utils/chat_stream_out.py,sha256=t902pKxQ5xM7zgIHiAOsTPLwxhE6VuvXAqPy751S7fg,14096
|
|
331
335
|
autocoder/utils/chat_auto_coder_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
332
|
-
auto_coder-0.1.
|
|
333
|
-
auto_coder-0.1.
|
|
334
|
-
auto_coder-0.1.
|
|
335
|
-
auto_coder-0.1.
|
|
336
|
-
auto_coder-0.1.
|
|
337
|
-
auto_coder-0.1.
|
|
336
|
+
auto_coder-0.1.381.dist-info/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
|
|
337
|
+
auto_coder-0.1.381.dist-info/METADATA,sha256=y_fLq5hUkZd9xWAXVyRm2wll1aWIHLRbPw-yIFGeQ40,2796
|
|
338
|
+
auto_coder-0.1.381.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
339
|
+
auto_coder-0.1.381.dist-info/entry_points.txt,sha256=0nzHtHH4pNcM7xq4EBA2toS28Qelrvcbrr59GqD_0Ak,350
|
|
340
|
+
auto_coder-0.1.381.dist-info/top_level.txt,sha256=Jqc0_uJSw2GwoFQAa9iJxYns-2mWla-9ok_Y3Gcznjk,10
|
|
341
|
+
auto_coder-0.1.381.dist-info/RECORD,,
|
|
@@ -19,7 +19,6 @@ class ExecuteCommandToolResolver(BaseToolResolver):
|
|
|
19
19
|
self.tool: ExecuteCommandTool = tool # For type hinting
|
|
20
20
|
|
|
21
21
|
def resolve(self) -> ToolResult:
|
|
22
|
-
printer = Printer()
|
|
23
22
|
command = self.tool.command
|
|
24
23
|
requires_approval = self.tool.requires_approval
|
|
25
24
|
source_dir = self.args.source_dir or "."
|
|
@@ -35,11 +34,11 @@ class ExecuteCommandToolResolver(BaseToolResolver):
|
|
|
35
34
|
# Approval mechanism (simplified)
|
|
36
35
|
if requires_approval:
|
|
37
36
|
# In a real scenario, this would involve user interaction
|
|
38
|
-
|
|
37
|
+
logger.info(f"Command requires approval: {command}")
|
|
39
38
|
# For now, let's assume approval is granted in non-interactive mode or handled elsewhere
|
|
40
39
|
pass
|
|
41
40
|
|
|
42
|
-
|
|
41
|
+
logger.info(f"Executing command: {command} in {os.path.abspath(source_dir)}")
|
|
43
42
|
try:
|
|
44
43
|
# 使用封装的run_cmd方法执行命令
|
|
45
44
|
if get_run_context().is_web():
|
|
@@ -37,7 +37,7 @@ class CommandManager:
|
|
|
37
37
|
os.makedirs(self.commands_dir, exist_ok=True)
|
|
38
38
|
logger.info(f"已创建命令目录: {self.commands_dir}")
|
|
39
39
|
|
|
40
|
-
def list_command_files(self, recursive: bool =
|
|
40
|
+
def list_command_files(self, recursive: bool = True) -> ListCommandsResult:
|
|
41
41
|
"""
|
|
42
42
|
列出命令目录中的所有命令文件
|
|
43
43
|
|
|
@@ -391,20 +391,7 @@ class AgenticEdit:
|
|
|
391
391
|
Your final result description here
|
|
392
392
|
</result>
|
|
393
393
|
<command>Command to demonstrate result (optional)</command>
|
|
394
|
-
</attempt_completion>
|
|
395
|
-
|
|
396
|
-
## plan_mode_respond
|
|
397
|
-
Description: Respond to the user's inquiry in an effort to plan a solution to the user's task. This tool should be used when you need to provide a response to a question or statement from the user about how you plan to accomplish the task. This tool is only available in PLAN MODE. The environment_details will specify the current mode, if it is not PLAN MODE then you should not use this tool. Depending on the user's message, you may ask questions to get clarification about the user's request, architect a solution to the task, and to brainstorm ideas with the user. For example, if the user's task is to create a website, you may start by asking some clarifying questions, then present a detailed plan for how you will accomplish the task given the context, and perhaps engage in a back and forth to finalize the details before the user switches you to ACT MODE to implement the solution.
|
|
398
|
-
Parameters:
|
|
399
|
-
- response: (required) The response to provide to the user. Do not try to use tools in this parameter, this is simply a chat response. (You MUST use the response parameter, do not simply place the response text directly within <plan_mode_respond> tags.)
|
|
400
|
-
- options: (optional) An array of 2-5 options for the user to choose from. Each option should be a string describing a possible choice or path forward in the planning process. This can help guide the discussion and make it easier for the user to provide input on key decisions. You may not always need to provide options, but it may be helpful in many cases where it can save the user from having to type out a response manually. Do NOT present an option to toggle to Act mode, as this will be something you need to direct the user to do manually themselves.
|
|
401
|
-
Usage:
|
|
402
|
-
<plan_mode_respond>
|
|
403
|
-
<response>Your response here</response>
|
|
404
|
-
<options>
|
|
405
|
-
Array of options here (optional), e.g. ["Option 1", "Option 2", "Option 3"]
|
|
406
|
-
</options>
|
|
407
|
-
</plan_mode_respond>
|
|
394
|
+
</attempt_completion>
|
|
408
395
|
|
|
409
396
|
## mcp_tool
|
|
410
397
|
Description: Request to execute a tool via the Model Context Protocol (MCP) server. Use this when you need to execute a tool that is not natively supported by the agentic edit tools.
|
|
@@ -596,28 +583,7 @@ class AgenticEdit:
|
|
|
596
583
|
|
|
597
584
|
By thoughtfully selecting between write_to_file and replace_in_file, you can make your file editing process smoother, safer, and more efficient.
|
|
598
585
|
|
|
599
|
-
====
|
|
600
|
-
|
|
601
|
-
ACT MODE V.S. PLAN MODE
|
|
602
|
-
|
|
603
|
-
In each user message, the environment_details will specify the current mode. There are two modes:
|
|
604
|
-
|
|
605
|
-
- ACT MODE: In this mode, you have access to all tools EXCEPT the plan_mode_respond tool.
|
|
606
|
-
- In ACT MODE, you use tools to accomplish the user's task. Once you've completed the user's task, you use the attempt_completion tool to present the result of the task to the user.
|
|
607
|
-
- PLAN MODE: In this special mode, you have access to the plan_mode_respond tool.
|
|
608
|
-
- In PLAN MODE, the goal is to gather information and get context to create a detailed plan for accomplishing the task, which the user will review and approve before they switch you to ACT MODE to implement the solution.
|
|
609
|
-
- In PLAN MODE, when you need to converse with the user or present a plan, you should use the plan_mode_respond tool to deliver your response directly, rather than using <thinking> tags to analyze when to respond. Do not talk about using plan_mode_respond - just use it directly to share your thoughts and provide helpful answers.
|
|
610
|
-
|
|
611
|
-
## What is PLAN MODE?
|
|
612
|
-
|
|
613
|
-
- While you are usually in ACT MODE, the user may switch to PLAN MODE in order to have a back and forth with you to plan how to best accomplish the task.
|
|
614
|
-
- When starting in PLAN MODE, depending on the user's request, you may need to do some information gathering e.g. using read_file or search_files to get more context about the task. You may also ask the user clarifying questions to get a better understanding of the task. You may return mermaid diagrams to visually display your understanding.
|
|
615
|
-
- Once you've gained more context about the user's request, you should architect a detailed plan for how you will accomplish the task. Returning mermaid diagrams may be helpful here as well.
|
|
616
|
-
- Then you might ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and plan the best way to accomplish it.
|
|
617
|
-
- If at any point a mermaid diagram would make your plan clearer to help the user quickly see the structure, you are encouraged to include a Mermaid code block in the response. (Note: if you use colors in your mermaid diagrams, be sure to use high contrast colors so the text is readable.)
|
|
618
|
-
- Finally once it seems like you've reached a good plan, ask the user to switch you back to ACT MODE to implement the solution.
|
|
619
|
-
|
|
620
|
-
====
|
|
586
|
+
====
|
|
621
587
|
|
|
622
588
|
PACKAGE CONTEXT INFORMATION
|
|
623
589
|
|
|
@@ -8,6 +8,9 @@ from autocoder.common import shells
|
|
|
8
8
|
from autocoder.common.printer import Printer
|
|
9
9
|
from loguru import logger
|
|
10
10
|
import typing
|
|
11
|
+
from autocoder.common.context_pruner import PruneContext
|
|
12
|
+
from autocoder.rag.token_counter import count_tokens
|
|
13
|
+
from autocoder.common import SourceCode
|
|
11
14
|
from autocoder.common import AutoCoderArgs
|
|
12
15
|
from autocoder.events.event_manager_singleton import get_event_manager
|
|
13
16
|
from autocoder.run_context import get_run_context
|
|
@@ -18,9 +21,42 @@ class ExecuteCommandToolResolver(BaseToolResolver):
|
|
|
18
21
|
def __init__(self, agent: Optional['AgenticEdit'], tool: ExecuteCommandTool, args: AutoCoderArgs):
|
|
19
22
|
super().__init__(agent, tool, args)
|
|
20
23
|
self.tool: ExecuteCommandTool = tool # For type hinting
|
|
24
|
+
self.context_pruner = PruneContext(
|
|
25
|
+
max_tokens=self.args.context_prune_safe_zone_tokens,
|
|
26
|
+
args=self.args,
|
|
27
|
+
llm=self.agent.context_prune_llm
|
|
28
|
+
)
|
|
21
29
|
|
|
22
|
-
def
|
|
23
|
-
|
|
30
|
+
def _prune_file_content(self, content: str, file_path: str) -> str:
|
|
31
|
+
"""对文件内容进行剪枝处理"""
|
|
32
|
+
if not self.context_pruner:
|
|
33
|
+
return content
|
|
34
|
+
|
|
35
|
+
# 计算 token 数量
|
|
36
|
+
tokens = count_tokens(content)
|
|
37
|
+
if tokens <= self.args.context_prune_safe_zone_tokens:
|
|
38
|
+
return content
|
|
39
|
+
|
|
40
|
+
# 创建 SourceCode 对象
|
|
41
|
+
source_code = SourceCode(
|
|
42
|
+
module_name=file_path,
|
|
43
|
+
source_code=content,
|
|
44
|
+
tokens=tokens
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# 使用 context_pruner 进行剪枝
|
|
48
|
+
pruned_sources = self.context_pruner.handle_overflow(
|
|
49
|
+
file_sources=[source_code],
|
|
50
|
+
conversations=self.agent.current_conversations if self.agent else [],
|
|
51
|
+
strategy=self.args.context_prune_strategy
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if not pruned_sources:
|
|
55
|
+
return content
|
|
56
|
+
|
|
57
|
+
return pruned_sources[0].source_code
|
|
58
|
+
|
|
59
|
+
def resolve(self) -> ToolResult:
|
|
24
60
|
command = self.tool.command
|
|
25
61
|
requires_approval = self.tool.requires_approval
|
|
26
62
|
source_dir = self.args.source_dir or "."
|
|
@@ -36,11 +72,11 @@ class ExecuteCommandToolResolver(BaseToolResolver):
|
|
|
36
72
|
# Approval mechanism (simplified)
|
|
37
73
|
if requires_approval:
|
|
38
74
|
# In a real scenario, this would involve user interaction
|
|
39
|
-
|
|
75
|
+
logger.info(f"Command requires approval: {command}")
|
|
40
76
|
# For now, let's assume approval is granted in non-interactive mode or handled elsewhere
|
|
41
77
|
pass
|
|
42
78
|
|
|
43
|
-
|
|
79
|
+
logger.info(f"Executing command: {command} in {os.path.abspath(source_dir)}")
|
|
44
80
|
try:
|
|
45
81
|
# 使用封装的run_cmd方法执行命令
|
|
46
82
|
if get_run_context().is_web():
|
|
@@ -56,13 +92,17 @@ class ExecuteCommandToolResolver(BaseToolResolver):
|
|
|
56
92
|
logger.info(f"Command executed: {command}")
|
|
57
93
|
logger.info(f"Return Code: {exit_code}")
|
|
58
94
|
if output:
|
|
59
|
-
logger.info(f"Output
|
|
95
|
+
logger.info(f"Original Output (length: {len(output)} chars)") # Avoid logging potentially huge output directly
|
|
96
|
+
|
|
97
|
+
final_output = self._prune_file_content(output, "command_output")
|
|
60
98
|
|
|
61
99
|
if exit_code == 0:
|
|
62
|
-
return ToolResult(success=True, message="Command executed successfully.", content=
|
|
100
|
+
return ToolResult(success=True, message="Command executed successfully.", content=final_output)
|
|
63
101
|
else:
|
|
64
|
-
|
|
65
|
-
|
|
102
|
+
# For the human-readable error message, we might prefer the original full output.
|
|
103
|
+
# For the agent-consumable content, we provide the (potentially pruned) final_output.
|
|
104
|
+
error_message_for_human = f"Command failed with return code {exit_code}.\nOutput:\n{output}"
|
|
105
|
+
return ToolResult(success=False, message=error_message_for_human, content={"output": final_output, "returncode": exit_code})
|
|
66
106
|
|
|
67
107
|
except FileNotFoundError:
|
|
68
108
|
return ToolResult(success=False, message=f"Error: The command '{command.split()[0]}' was not found. Please ensure it is installed and in the system's PATH.")
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from unittest.mock import MagicMock, patch
|
|
3
|
+
from autocoder.common.v2.agent.agentic_edit_tools \
|
|
4
|
+
.execute_command_tool_resolver import ExecuteCommandToolResolver
|
|
5
|
+
from autocoder.common.v2.agent.agentic_edit_types import ExecuteCommandTool
|
|
6
|
+
from autocoder.common import AutoCoderArgs
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestExecuteCommandToolResolver:
|
|
10
|
+
@pytest.fixture
|
|
11
|
+
def mock_agent(self):
|
|
12
|
+
agent = MagicMock()
|
|
13
|
+
agent.args = AutoCoderArgs()
|
|
14
|
+
agent.args.context_prune_safe_zone_tokens = 1000
|
|
15
|
+
agent.context_prune_llm = None
|
|
16
|
+
agent.current_conversations = []
|
|
17
|
+
return agent
|
|
18
|
+
|
|
19
|
+
@pytest.fixture
|
|
20
|
+
def mock_tool(self):
|
|
21
|
+
return ExecuteCommandTool(
|
|
22
|
+
command="echo 'test output'",
|
|
23
|
+
requires_approval=False
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_command_execution_with_token_count(self, mock_agent, mock_tool):
|
|
28
|
+
"""测试命令执行并验证token统计功能"""
|
|
29
|
+
resolver = ExecuteCommandToolResolver(mock_agent, mock_tool, mock_agent.args)
|
|
30
|
+
|
|
31
|
+
# 模拟命令执行返回大量输出
|
|
32
|
+
with patch("autocoder.common.run_cmd.run_cmd_subprocess") as mock_run_cmd:
|
|
33
|
+
mock_run_cmd.return_value = (0, "test output " * 500) # 生成大量输出
|
|
34
|
+
|
|
35
|
+
result = resolver.resolve()
|
|
36
|
+
|
|
37
|
+
assert result.success
|
|
38
|
+
assert "test output" in result.content
|
|
39
|
+
|
|
40
|
+
# 验证token统计功能
|
|
41
|
+
# 这里需要修改ExecuteCommandToolResolver来添加token统计功能
|
|
42
|
+
# 测试将失败,符合TDD的红色阶段
|
|
43
|
+
|
|
44
|
+
def test_output_pruning_when_exceeds_token_limit(self, mock_agent, mock_tool):
|
|
45
|
+
"""测试当输出超过token限制时的裁剪功能"""
|
|
46
|
+
resolver = ExecuteCommandToolResolver(mock_agent, mock_tool, mock_agent.args)
|
|
47
|
+
|
|
48
|
+
# 模拟命令执行返回大量输出
|
|
49
|
+
with patch("autocoder.common.run_cmd.run_cmd_subprocess") as mock_run_cmd:
|
|
50
|
+
mock_run_cmd.return_value = (0, "test output " * 500) # 生成大量输出
|
|
51
|
+
|
|
52
|
+
result = resolver.resolve()
|
|
53
|
+
|
|
54
|
+
assert result.success
|
|
55
|
+
# 验证输出是否被裁剪
|
|
56
|
+
# 这里需要修改ExecuteCommandToolResolver来添加裁剪功能
|
|
57
|
+
# 测试将失败,符合TDD的红色阶段
|
|
58
|
+
|
|
59
|
+
def test_command_execution_failure(self, mock_agent, mock_tool):
|
|
60
|
+
"""测试命令执行失败的情况"""
|
|
61
|
+
resolver = ExecuteCommandToolResolver(mock_agent, mock_tool, mock_agent.args)
|
|
62
|
+
|
|
63
|
+
# 模拟命令执行失败
|
|
64
|
+
with patch("autocoder.common.run_cmd.run_cmd_subprocess") as mock_run_cmd:
|
|
65
|
+
mock_run_cmd.return_value = (1, "command failed")
|
|
66
|
+
|
|
67
|
+
result = resolver.resolve()
|
|
68
|
+
|
|
69
|
+
assert not result.success
|
|
70
|
+
assert "command failed" in result.message
|
|
@@ -332,17 +332,7 @@ class PythonLinter(BaseLinter):
|
|
|
332
332
|
result['warning_count'] += pylint_result['warning_count']
|
|
333
333
|
except Exception as e:
|
|
334
334
|
if self.verbose:
|
|
335
|
-
print(f"Error running pylint: {str(e)}")
|
|
336
|
-
|
|
337
|
-
# Run flake8
|
|
338
|
-
try:
|
|
339
|
-
flake8_result = self._run_flake8(file_path)
|
|
340
|
-
result['issues'].extend(flake8_result['issues'])
|
|
341
|
-
result['error_count'] += flake8_result['error_count']
|
|
342
|
-
result['warning_count'] += flake8_result['warning_count']
|
|
343
|
-
except Exception as e:
|
|
344
|
-
if self.verbose:
|
|
345
|
-
print(f"Error running flake8: {str(e)}")
|
|
335
|
+
print(f"Error running pylint: {str(e)}")
|
|
346
336
|
|
|
347
337
|
# Mark as successful
|
|
348
338
|
result['success'] = True
|
|
@@ -400,17 +390,7 @@ class PythonLinter(BaseLinter):
|
|
|
400
390
|
except Exception as e:
|
|
401
391
|
if self.verbose:
|
|
402
392
|
print(f"Error running pylint: {str(e)}")
|
|
403
|
-
|
|
404
|
-
# Run flake8
|
|
405
|
-
try:
|
|
406
|
-
flake8_result = self._run_flake8(project_path)
|
|
407
|
-
result['issues'].extend(flake8_result['issues'])
|
|
408
|
-
result['error_count'] += flake8_result['error_count']
|
|
409
|
-
result['warning_count'] += flake8_result['warning_count']
|
|
410
|
-
except Exception as e:
|
|
411
|
-
if self.verbose:
|
|
412
|
-
print(f"Error running flake8: {str(e)}")
|
|
413
|
-
|
|
393
|
+
|
|
414
394
|
# Mark as successful
|
|
415
395
|
result['success'] = True
|
|
416
396
|
|
|
@@ -243,11 +243,18 @@ class ReactJSLinter(BaseLinter):
|
|
|
243
243
|
|
|
244
244
|
# Process individual messages
|
|
245
245
|
for message in file_result.get('messages', []):
|
|
246
|
+
severity = "info"
|
|
247
|
+
if message.get('severity', 1) == 2:
|
|
248
|
+
severity = "error"
|
|
249
|
+
elif message.get('severity', 1) == 1:
|
|
250
|
+
severity = "warning"
|
|
251
|
+
elif message.get('severity', 1) == 0:
|
|
252
|
+
severity = "info"
|
|
246
253
|
issue = {
|
|
247
254
|
'file': file_rel_path,
|
|
248
255
|
'line': message.get('line', 0),
|
|
249
256
|
'column': message.get('column', 0),
|
|
250
|
-
'severity':
|
|
257
|
+
'severity': severity,
|
|
251
258
|
'message': message.get('message', ''),
|
|
252
259
|
'rule': message.get('ruleId', 'unknown')
|
|
253
260
|
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pytest
|
|
3
|
+
import json
|
|
4
|
+
from unittest.mock import patch, MagicMock
|
|
5
|
+
from autocoder.linters.python_linter import PythonLinter
|
|
6
|
+
|
|
7
|
+
class TestPythonLinter:
|
|
8
|
+
"""测试PythonLinter类"""
|
|
9
|
+
|
|
10
|
+
def setup_method(self):
|
|
11
|
+
"""每个测试方法前的设置"""
|
|
12
|
+
self.linter = PythonLinter(verbose=True)
|
|
13
|
+
|
|
14
|
+
def test_lint_file_nonexistent_file(self):
|
|
15
|
+
"""测试不存在的文件"""
|
|
16
|
+
result = self.linter.lint_file("nonexistent_file.py")
|
|
17
|
+
|
|
18
|
+
assert result['success'] == False
|
|
19
|
+
assert "不存在" in result.get('error', '') or "does not exist" in result.get('error', '')
|
|
20
|
+
|
|
21
|
+
def test_lint_file_unsupported_file(self):
|
|
22
|
+
"""测试不支持的文件类型"""
|
|
23
|
+
# 创建一个临时文件
|
|
24
|
+
with open("temp.txt", "w") as f:
|
|
25
|
+
f.write("这不是Python文件")
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
result = self.linter.lint_file("temp.txt")
|
|
29
|
+
|
|
30
|
+
assert result['success'] == False
|
|
31
|
+
assert "不支持" in result.get('error', '') or "Unsupported" in result.get('error', '')
|
|
32
|
+
finally:
|
|
33
|
+
# 清理
|
|
34
|
+
if os.path.exists("temp.txt"):
|
|
35
|
+
os.remove("temp.txt")
|
|
36
|
+
|
|
37
|
+
@patch.object(PythonLinter, '_check_dependencies')
|
|
38
|
+
@patch.object(PythonLinter, '_install_dependencies_if_needed')
|
|
39
|
+
def test_lint_file_missing_dependencies(self, mock_install, mock_check):
|
|
40
|
+
"""测试缺少依赖项的情况"""
|
|
41
|
+
# 模拟依赖检查失败
|
|
42
|
+
mock_check.return_value = False
|
|
43
|
+
# 模拟安装依赖失败
|
|
44
|
+
mock_install.return_value = False
|
|
45
|
+
|
|
46
|
+
# 创建一个临时Python文件
|
|
47
|
+
with open("temp.py", "w") as f:
|
|
48
|
+
f.write("print('Hello, World!')")
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
result = self.linter.lint_file("temp.py")
|
|
52
|
+
|
|
53
|
+
assert result['success'] == False
|
|
54
|
+
assert "依赖" in result.get('error', '') or "dependencies" in result.get('error', '')
|
|
55
|
+
finally:
|
|
56
|
+
# 清理
|
|
57
|
+
if os.path.exists("temp.py"):
|
|
58
|
+
os.remove("temp.py")
|
|
59
|
+
|
|
60
|
+
@patch.object(PythonLinter, '_check_dependencies')
|
|
61
|
+
@patch.object(PythonLinter, '_run_pylint')
|
|
62
|
+
def test_lint_file_successful(self, mock_pylint, mock_check):
|
|
63
|
+
"""测试成功的lint过程"""
|
|
64
|
+
# 模拟依赖检查成功
|
|
65
|
+
mock_check.return_value = True
|
|
66
|
+
|
|
67
|
+
# 模拟pylint结果
|
|
68
|
+
mock_pylint.return_value = {
|
|
69
|
+
'error_count': 1,
|
|
70
|
+
'warning_count': 2,
|
|
71
|
+
'issues': [
|
|
72
|
+
{
|
|
73
|
+
'file': 'test.py',
|
|
74
|
+
'line': 10,
|
|
75
|
+
'column': 5,
|
|
76
|
+
'severity': 'error',
|
|
77
|
+
'message': '未定义变量',
|
|
78
|
+
'rule': 'undefined-variable',
|
|
79
|
+
'tool': 'pylint'
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
'file': 'test.py',
|
|
83
|
+
'line': 15,
|
|
84
|
+
'column': 1,
|
|
85
|
+
'severity': 'warning',
|
|
86
|
+
'message': '不必要的空行',
|
|
87
|
+
'rule': 'trailing-whitespace',
|
|
88
|
+
'tool': 'pylint'
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# 创建一个临时Python文件
|
|
94
|
+
with open("test.py", "w") as f:
|
|
95
|
+
f.write("print('Hello, World!')")
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
result = self.linter.lint_file("test.py")
|
|
99
|
+
|
|
100
|
+
assert result['success'] == True
|
|
101
|
+
assert result['language'] == 'python'
|
|
102
|
+
assert result['files_analyzed'] == 1
|
|
103
|
+
assert result['error_count'] == 1
|
|
104
|
+
assert result['warning_count'] == 2 # 只有pylint的警告
|
|
105
|
+
assert len(result['issues']) == 2 # 总共2个问题
|
|
106
|
+
finally:
|
|
107
|
+
# 清理
|
|
108
|
+
if os.path.exists("test.py"):
|
|
109
|
+
os.remove("test.py")
|
|
110
|
+
|
|
111
|
+
# python -m pytest src/autocoder/linters/test_python_linter.py::TestPythonLinter::test_lint_real_file -v -s
|
|
112
|
+
@pytest.mark.integration
|
|
113
|
+
def test_lint_real_file(self):
|
|
114
|
+
"""测试对实际文件的lint功能"""
|
|
115
|
+
# 实际存在的Python文件路径
|
|
116
|
+
file_path = "/Users/allwefantasy/projects/auto-coder/src/autocoder/common/v2/agent/agentic_edit_tools/read_file_tool_resolver.py"
|
|
117
|
+
|
|
118
|
+
# 检查文件是否存在,如果不存在则跳过测试
|
|
119
|
+
if not os.path.exists(file_path):
|
|
120
|
+
pytest.skip(f"测试文件 {file_path} 不存在")
|
|
121
|
+
|
|
122
|
+
# 执行lint操作
|
|
123
|
+
result = self.linter.lint_file(file_path)
|
|
124
|
+
|
|
125
|
+
# 基本断言
|
|
126
|
+
assert result['success'] == True
|
|
127
|
+
assert result['language'] == 'python'
|
|
128
|
+
assert result['files_analyzed'] == 1
|
|
129
|
+
|
|
130
|
+
# 打印完整的lint结果
|
|
131
|
+
print("\n==== Lint结果概述 ====")
|
|
132
|
+
print(f"文件: {file_path}")
|
|
133
|
+
print(f"错误数: {result['error_count']}")
|
|
134
|
+
print(f"警告数: {result['warning_count']}")
|
|
135
|
+
print(f"问题总数: {len(result['issues'])}")
|
|
136
|
+
|
|
137
|
+
print("\n==== 详细问题列表 ====")
|
|
138
|
+
if not result['issues']:
|
|
139
|
+
print("没有发现问题!")
|
|
140
|
+
else:
|
|
141
|
+
for i, issue in enumerate(result['issues'], 1):
|
|
142
|
+
print(f"\n问题 #{i}:")
|
|
143
|
+
print(f" 文件: {issue['file']}")
|
|
144
|
+
print(f" 位置: 第{issue['line']}行, 第{issue['column']}列")
|
|
145
|
+
print(f" 严重性: {issue['severity']}")
|
|
146
|
+
print(f" 信息: {issue['message']}")
|
|
147
|
+
print(f" 规则: {issue['rule']}")
|
|
148
|
+
print(f" 工具: {issue['tool']}")
|
|
149
|
+
|
|
150
|
+
# 可选:打印完整的JSON格式结果
|
|
151
|
+
print("\n==== 完整JSON结果 ====")
|
|
152
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
153
|
+
|
|
154
|
+
# 验证结果中包含issues字段,且为列表类型
|
|
155
|
+
assert 'issues' in result
|
|
156
|
+
assert isinstance(result['issues'], list)
|
|
157
|
+
|
|
158
|
+
# 如果有issues,验证其结构
|
|
159
|
+
if result['issues']:
|
|
160
|
+
issue = result['issues'][0]
|
|
161
|
+
assert 'file' in issue
|
|
162
|
+
assert 'line' in issue
|
|
163
|
+
assert 'severity' in issue
|
|
164
|
+
assert 'message' in issue
|
|
165
|
+
assert 'rule' in issue
|
|
166
|
+
assert 'tool' in issue
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pytest
|
|
3
|
+
import json
|
|
4
|
+
from unittest.mock import patch, MagicMock
|
|
5
|
+
from autocoder.linters.reactjs_linter import ReactJSLinter
|
|
6
|
+
|
|
7
|
+
# python -m pytest src/autocoder/linters/test_reactjs_linter.py::TestReactJSLinter::test_lint_real_file -v -s
|
|
8
|
+
class TestReactJSLinter:
|
|
9
|
+
"""测试ReactJSLinter类"""
|
|
10
|
+
|
|
11
|
+
def setup_method(self):
|
|
12
|
+
"""每个测试方法前的设置"""
|
|
13
|
+
self.linter = ReactJSLinter(verbose=True)
|
|
14
|
+
|
|
15
|
+
def test_lint_file_nonexistent_file(self):
|
|
16
|
+
"""测试不存在的文件"""
|
|
17
|
+
result = self.linter.lint_file("nonexistent_file.jsx")
|
|
18
|
+
|
|
19
|
+
assert result['success'] == False
|
|
20
|
+
assert "不存在" in result.get('error', '') or "does not exist" in result.get('error', '')
|
|
21
|
+
|
|
22
|
+
def test_lint_file_unsupported_file(self):
|
|
23
|
+
"""测试不支持的文件类型"""
|
|
24
|
+
# 创建一个临时文件
|
|
25
|
+
with open("temp.txt", "w") as f:
|
|
26
|
+
f.write("这不是React文件")
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
result = self.linter.lint_file("temp.txt")
|
|
30
|
+
|
|
31
|
+
assert result['success'] == False
|
|
32
|
+
assert "不支持" in result.get('error', '') or "Unsupported" in result.get('error', '')
|
|
33
|
+
finally:
|
|
34
|
+
# 清理
|
|
35
|
+
if os.path.exists("temp.txt"):
|
|
36
|
+
os.remove("temp.txt")
|
|
37
|
+
|
|
38
|
+
@patch.object(ReactJSLinter, '_check_dependencies')
|
|
39
|
+
def test_lint_file_missing_dependencies(self, mock_check):
|
|
40
|
+
"""测试缺少依赖项的情况"""
|
|
41
|
+
# 模拟依赖检查失败
|
|
42
|
+
mock_check.return_value = False
|
|
43
|
+
|
|
44
|
+
# 创建一个临时React文件
|
|
45
|
+
with open("temp.jsx", "w") as f:
|
|
46
|
+
f.write("import React from 'react';\nfunction App() { return <div>Hello</div>; }")
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
result = self.linter.lint_file("temp.jsx")
|
|
50
|
+
|
|
51
|
+
assert result['success'] == False
|
|
52
|
+
assert "依赖" in result.get('error', '') or "dependencies" in result.get('error', '')
|
|
53
|
+
finally:
|
|
54
|
+
# 清理
|
|
55
|
+
if os.path.exists("temp.jsx"):
|
|
56
|
+
os.remove("temp.jsx")
|
|
57
|
+
|
|
58
|
+
@patch.object(ReactJSLinter, '_detect_file_type')
|
|
59
|
+
def test_lint_file_not_react_file(self, mock_detect):
|
|
60
|
+
"""测试非React文件"""
|
|
61
|
+
# 模拟文件类型检测失败
|
|
62
|
+
mock_detect.return_value = False
|
|
63
|
+
|
|
64
|
+
# 创建一个临时JS文件
|
|
65
|
+
with open("temp.js", "w") as f:
|
|
66
|
+
f.write("function hello() { console.log('Hello'); }")
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
result = self.linter.lint_file("temp.js")
|
|
70
|
+
|
|
71
|
+
assert result['success'] == False
|
|
72
|
+
assert "Not a React file" in result.get('error', '')
|
|
73
|
+
finally:
|
|
74
|
+
# 清理
|
|
75
|
+
if os.path.exists("temp.js"):
|
|
76
|
+
os.remove("temp.js")
|
|
77
|
+
|
|
78
|
+
@patch.object(ReactJSLinter, '_check_dependencies')
|
|
79
|
+
@patch.object(ReactJSLinter, '_detect_file_type')
|
|
80
|
+
@patch.object(ReactJSLinter, '_install_eslint_if_needed')
|
|
81
|
+
@patch('subprocess.run')
|
|
82
|
+
def test_lint_file_successful(self, mock_run, mock_install, mock_detect, mock_check):
|
|
83
|
+
"""测试成功的lint过程"""
|
|
84
|
+
# 模拟依赖检查成功
|
|
85
|
+
mock_check.return_value = True
|
|
86
|
+
# 模拟文件类型检测成功
|
|
87
|
+
mock_detect.return_value = True
|
|
88
|
+
# 模拟安装ESLint成功
|
|
89
|
+
mock_install.return_value = True
|
|
90
|
+
|
|
91
|
+
# 模拟subprocess.run的结果
|
|
92
|
+
mock_process = MagicMock()
|
|
93
|
+
mock_process.stdout = json.dumps([
|
|
94
|
+
{
|
|
95
|
+
'filePath': os.path.abspath('test.jsx'),
|
|
96
|
+
'errorCount': 1,
|
|
97
|
+
'warningCount': 2,
|
|
98
|
+
'messages': [
|
|
99
|
+
{
|
|
100
|
+
'line': 10,
|
|
101
|
+
'column': 5,
|
|
102
|
+
'severity': 2,
|
|
103
|
+
'message': '未使用的变量',
|
|
104
|
+
'ruleId': 'no-unused-vars'
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
'line': 15,
|
|
108
|
+
'column': 1,
|
|
109
|
+
'severity': 1,
|
|
110
|
+
'message': '缺少分号',
|
|
111
|
+
'ruleId': 'semi'
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
'line': 20,
|
|
115
|
+
'column': 10,
|
|
116
|
+
'severity': 1,
|
|
117
|
+
'message': '建议使用解构赋值',
|
|
118
|
+
'ruleId': 'prefer-destructuring'
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
])
|
|
123
|
+
mock_run.return_value = mock_process
|
|
124
|
+
|
|
125
|
+
# 创建一个临时React文件
|
|
126
|
+
with open("test.jsx", "w") as f:
|
|
127
|
+
f.write("import React from 'react';\nfunction App() { return <div>Hello</div>; }")
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
result = self.linter.lint_file("test.jsx")
|
|
131
|
+
|
|
132
|
+
assert result['success'] == True
|
|
133
|
+
assert result['framework'] == 'reactjs'
|
|
134
|
+
assert result['files_analyzed'] == 1
|
|
135
|
+
assert result['error_count'] == 1
|
|
136
|
+
assert result['warning_count'] == 2
|
|
137
|
+
assert len(result['issues']) == 3
|
|
138
|
+
finally:
|
|
139
|
+
# 清理
|
|
140
|
+
if os.path.exists("test.jsx"):
|
|
141
|
+
os.remove("test.jsx")
|
|
142
|
+
|
|
143
|
+
# python -m pytest src/autocoder/linters/test_reactjs_linter.py::TestReactJSLinter::test_lint_real_file -v -s
|
|
144
|
+
@pytest.mark.integration
|
|
145
|
+
def test_lint_real_file(self):
|
|
146
|
+
"""测试对实际文件的lint功能"""
|
|
147
|
+
# 检查是否有React示例文件可用
|
|
148
|
+
file_path = "/Users/allwefantasy/projects/auto-coder.web/frontend/src/components/Sidebar/ChatPanel.tsx"
|
|
149
|
+
|
|
150
|
+
# 执行lint操作
|
|
151
|
+
result = self.linter.lint_file(file_path)
|
|
152
|
+
|
|
153
|
+
# 基本断言
|
|
154
|
+
assert 'success' in result
|
|
155
|
+
assert 'framework' in result
|
|
156
|
+
assert 'files_analyzed' in result
|
|
157
|
+
|
|
158
|
+
# 打印完整的lint结果
|
|
159
|
+
print("\n==== Lint结果概述 ====")
|
|
160
|
+
print(f"文件: {file_path}")
|
|
161
|
+
print(f"成功: {result.get('success', False)}")
|
|
162
|
+
if 'error' in result:
|
|
163
|
+
print(f"错误: {result['error']}")
|
|
164
|
+
else:
|
|
165
|
+
print(f"错误数: {result.get('error_count', 0)}")
|
|
166
|
+
print(f"警告数: {result.get('warning_count', 0)}")
|
|
167
|
+
print(f"问题总数: {len(result.get('issues', []))}")
|
|
168
|
+
|
|
169
|
+
print("\n==== 详细问题列表 ====")
|
|
170
|
+
if not result.get('issues', []):
|
|
171
|
+
print("没有发现问题或执行失败!")
|
|
172
|
+
else:
|
|
173
|
+
for i, issue in enumerate(result['issues'], 1):
|
|
174
|
+
print(f"\n问题 #{i}:")
|
|
175
|
+
print(f" 文件: {issue['file']}")
|
|
176
|
+
print(f" 位置: 第{issue['line']}行, 第{issue['column']}列")
|
|
177
|
+
print(f" 严重性: {issue['severity']}")
|
|
178
|
+
print(f" 信息: {issue['message']}")
|
|
179
|
+
print(f" 规则: {issue['rule']}")
|
|
180
|
+
|
|
181
|
+
# 可选:打印完整的JSON格式结果
|
|
182
|
+
print("\n==== 完整JSON结果 ====")
|
|
183
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pytest
|
|
3
|
+
import json
|
|
4
|
+
from unittest.mock import patch, MagicMock
|
|
5
|
+
from autocoder.linters.vue_linter import VueLinter
|
|
6
|
+
|
|
7
|
+
class TestVueLinter:
|
|
8
|
+
"""测试VueLinter类"""
|
|
9
|
+
|
|
10
|
+
def setup_method(self):
|
|
11
|
+
"""每个测试方法前的设置"""
|
|
12
|
+
self.linter = VueLinter(verbose=True)
|
|
13
|
+
|
|
14
|
+
def test_lint_file_nonexistent_file(self):
|
|
15
|
+
"""测试不存在的文件"""
|
|
16
|
+
result = self.linter.lint_file("nonexistent_file.vue")
|
|
17
|
+
|
|
18
|
+
assert result['success'] == False
|
|
19
|
+
assert "不存在" in result.get('error', '') or "does not exist" in result.get('error', '')
|
|
20
|
+
|
|
21
|
+
def test_lint_file_unsupported_file(self):
|
|
22
|
+
"""测试不支持的文件类型"""
|
|
23
|
+
# 创建一个临时文件
|
|
24
|
+
with open("temp.txt", "w") as f:
|
|
25
|
+
f.write("这不是Vue文件")
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
result = self.linter.lint_file("temp.txt")
|
|
29
|
+
|
|
30
|
+
assert result['success'] == False
|
|
31
|
+
assert "不支持" in result.get('error', '') or "Unsupported" in result.get('error', '')
|
|
32
|
+
finally:
|
|
33
|
+
# 清理
|
|
34
|
+
if os.path.exists("temp.txt"):
|
|
35
|
+
os.remove("temp.txt")
|
|
36
|
+
|
|
37
|
+
@patch.object(VueLinter, '_check_dependencies')
|
|
38
|
+
def test_lint_file_missing_dependencies(self, mock_check):
|
|
39
|
+
"""测试缺少依赖项的情况"""
|
|
40
|
+
# 模拟依赖检查失败
|
|
41
|
+
mock_check.return_value = False
|
|
42
|
+
|
|
43
|
+
# 创建一个临时Vue文件
|
|
44
|
+
with open("temp.vue", "w") as f:
|
|
45
|
+
f.write("<template><div>Hello</div></template>")
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
result = self.linter.lint_file("temp.vue")
|
|
49
|
+
|
|
50
|
+
assert result['success'] == False
|
|
51
|
+
assert "依赖" in result.get('error', '') or "dependencies" in result.get('error', '')
|
|
52
|
+
finally:
|
|
53
|
+
# 清理
|
|
54
|
+
if os.path.exists("temp.vue"):
|
|
55
|
+
os.remove("temp.vue")
|
|
56
|
+
|
|
57
|
+
@patch.object(VueLinter, '_detect_file_type')
|
|
58
|
+
def test_lint_file_not_vue_file(self, mock_detect):
|
|
59
|
+
"""测试非Vue文件"""
|
|
60
|
+
# 模拟文件类型检测失败
|
|
61
|
+
mock_detect.return_value = False
|
|
62
|
+
|
|
63
|
+
# 创建一个临时JS文件
|
|
64
|
+
with open("temp.js", "w") as f:
|
|
65
|
+
f.write("function hello() { console.log('Hello'); }")
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
result = self.linter.lint_file("temp.js")
|
|
69
|
+
|
|
70
|
+
assert result['success'] == False
|
|
71
|
+
assert "Not a Vue file" in result.get('error', '')
|
|
72
|
+
finally:
|
|
73
|
+
# 清理
|
|
74
|
+
if os.path.exists("temp.js"):
|
|
75
|
+
os.remove("temp.js")
|
|
76
|
+
|
|
77
|
+
@patch.object(VueLinter, '_check_dependencies')
|
|
78
|
+
@patch.object(VueLinter, '_detect_file_type')
|
|
79
|
+
@patch.object(VueLinter, '_install_eslint_if_needed')
|
|
80
|
+
@patch('subprocess.run')
|
|
81
|
+
def test_lint_file_successful(self, mock_run, mock_install, mock_detect, mock_check):
|
|
82
|
+
"""测试成功的lint过程"""
|
|
83
|
+
# 模拟依赖检查成功
|
|
84
|
+
mock_check.return_value = True
|
|
85
|
+
# 模拟文件类型检测成功
|
|
86
|
+
mock_detect.return_value = True
|
|
87
|
+
# 模拟安装ESLint成功
|
|
88
|
+
mock_install.return_value = True
|
|
89
|
+
|
|
90
|
+
# 模拟subprocess.run的结果
|
|
91
|
+
mock_process = MagicMock()
|
|
92
|
+
mock_process.stdout = json.dumps([
|
|
93
|
+
{
|
|
94
|
+
'filePath': os.path.abspath('test.vue'),
|
|
95
|
+
'errorCount': 1,
|
|
96
|
+
'warningCount': 2,
|
|
97
|
+
'messages': [
|
|
98
|
+
{
|
|
99
|
+
'line': 10,
|
|
100
|
+
'column': 5,
|
|
101
|
+
'severity': 2,
|
|
102
|
+
'message': '组件名称必须是多词的',
|
|
103
|
+
'ruleId': 'vue/multi-word-component-names'
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
'line': 15,
|
|
107
|
+
'column': 1,
|
|
108
|
+
'severity': 1,
|
|
109
|
+
'message': 'props应该被详细定义',
|
|
110
|
+
'ruleId': 'vue/require-prop-types'
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
'line': 20,
|
|
114
|
+
'column': 10,
|
|
115
|
+
'severity': 1,
|
|
116
|
+
'message': '缺少key属性',
|
|
117
|
+
'ruleId': 'vue/require-v-for-key'
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
])
|
|
122
|
+
mock_run.return_value = mock_process
|
|
123
|
+
|
|
124
|
+
# 创建一个临时Vue文件
|
|
125
|
+
with open("test.vue", "w") as f:
|
|
126
|
+
f.write("<template><div>Hello</div></template>")
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
result = self.linter.lint_file("test.vue")
|
|
130
|
+
|
|
131
|
+
assert result['success'] == True
|
|
132
|
+
assert result['framework'] == 'vue'
|
|
133
|
+
assert result['files_analyzed'] == 1
|
|
134
|
+
assert result['error_count'] == 1
|
|
135
|
+
assert result['warning_count'] == 2
|
|
136
|
+
assert len(result['issues']) == 3
|
|
137
|
+
finally:
|
|
138
|
+
# 清理
|
|
139
|
+
if os.path.exists("test.vue"):
|
|
140
|
+
os.remove("test.vue")
|
|
141
|
+
|
|
142
|
+
# python -m pytest src/autocoder/linters/test_vue_linter.py::TestVueLinter::test_lint_real_file -v -s
|
|
143
|
+
@pytest.mark.integration
|
|
144
|
+
def test_lint_real_file(self):
|
|
145
|
+
"""测试对实际文件的lint功能"""
|
|
146
|
+
# 检查是否有Vue示例文件可用
|
|
147
|
+
test_dir = os.path.dirname(os.path.abspath(__file__))
|
|
148
|
+
file_path = os.path.join(test_dir, "test_samples", "vue_component.vue")
|
|
149
|
+
|
|
150
|
+
if not os.path.exists(file_path):
|
|
151
|
+
# 如果没有测试示例文件,创建一个
|
|
152
|
+
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
|
153
|
+
with open(file_path, "w") as f:
|
|
154
|
+
f.write("""
|
|
155
|
+
<template>
|
|
156
|
+
<div class="test-component">
|
|
157
|
+
<h1>Hello, {{ name }}!</h1>
|
|
158
|
+
<p>This is a test Vue component.</p>
|
|
159
|
+
</div>
|
|
160
|
+
</template>
|
|
161
|
+
|
|
162
|
+
<script>
|
|
163
|
+
export default {
|
|
164
|
+
name: 'TestComponent',
|
|
165
|
+
props: {
|
|
166
|
+
name: {
|
|
167
|
+
type: String,
|
|
168
|
+
required: true,
|
|
169
|
+
default: 'World'
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
</script>
|
|
174
|
+
|
|
175
|
+
<style scoped>
|
|
176
|
+
.test-component {
|
|
177
|
+
padding: 20px;
|
|
178
|
+
border: 1px solid #ccc;
|
|
179
|
+
border-radius: 5px;
|
|
180
|
+
}
|
|
181
|
+
</style>
|
|
182
|
+
""")
|
|
183
|
+
|
|
184
|
+
# 执行lint操作
|
|
185
|
+
result = self.linter.lint_file(file_path)
|
|
186
|
+
|
|
187
|
+
# 基本断言
|
|
188
|
+
assert 'success' in result
|
|
189
|
+
assert 'framework' in result
|
|
190
|
+
assert 'files_analyzed' in result
|
|
191
|
+
|
|
192
|
+
# 打印完整的lint结果
|
|
193
|
+
print("\n==== Lint结果概述 ====")
|
|
194
|
+
print(f"文件: {file_path}")
|
|
195
|
+
print(f"成功: {result.get('success', False)}")
|
|
196
|
+
if 'error' in result:
|
|
197
|
+
print(f"错误: {result['error']}")
|
|
198
|
+
else:
|
|
199
|
+
print(f"错误数: {result.get('error_count', 0)}")
|
|
200
|
+
print(f"警告数: {result.get('warning_count', 0)}")
|
|
201
|
+
print(f"问题总数: {len(result.get('issues', []))}")
|
|
202
|
+
|
|
203
|
+
print("\n==== 详细问题列表 ====")
|
|
204
|
+
if not result.get('issues', []):
|
|
205
|
+
print("没有发现问题或执行失败!")
|
|
206
|
+
else:
|
|
207
|
+
for i, issue in enumerate(result['issues'], 1):
|
|
208
|
+
print(f"\n问题 #{i}:")
|
|
209
|
+
print(f" 文件: {issue['file']}")
|
|
210
|
+
print(f" 位置: 第{issue['line']}行, 第{issue['column']}列")
|
|
211
|
+
print(f" 严重性: {issue['severity']}")
|
|
212
|
+
print(f" 信息: {issue['message']}")
|
|
213
|
+
print(f" 规则: {issue['rule']}")
|
|
214
|
+
|
|
215
|
+
# 可选:打印完整的JSON格式结果
|
|
216
|
+
print("\n==== 完整JSON结果 ====")
|
|
217
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
autocoder/linters/vue_linter.py
CHANGED
|
@@ -320,13 +320,21 @@ class VueLinter(BaseLinter):
|
|
|
320
320
|
total_errors += file_result.get('errorCount', 0)
|
|
321
321
|
total_warnings += file_result.get('warningCount', 0)
|
|
322
322
|
|
|
323
|
+
severity = "info"
|
|
324
|
+
if message.get('severity', 1) == 2:
|
|
325
|
+
severity = "error"
|
|
326
|
+
elif message.get('severity', 1) == 1:
|
|
327
|
+
severity = "warning"
|
|
328
|
+
elif message.get('severity', 1) == 0:
|
|
329
|
+
severity = "info"
|
|
330
|
+
|
|
323
331
|
# Process individual messages
|
|
324
332
|
for message in file_result.get('messages', []):
|
|
325
333
|
issue = {
|
|
326
334
|
'file': file_rel_path,
|
|
327
335
|
'line': message.get('line', 0),
|
|
328
336
|
'column': message.get('column', 0),
|
|
329
|
-
'severity':
|
|
337
|
+
'severity': severity,
|
|
330
338
|
'message': message.get('message', ''),
|
|
331
339
|
'rule': message.get('ruleId', 'unknown')
|
|
332
340
|
}
|
autocoder/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
|
|
2
|
-
__version__ = "0.1.
|
|
2
|
+
__version__ = "0.1.381"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|