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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: auto-coder
3
- Version: 0.1.379
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=QMVJ6Ndva0H0aRvWBJXkESwPMywZGBmC3chIHzbsCVc,25
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=FDWheOy2NVG1RDeG2lhMlj0cFjG4UpqCkWPRMEM7Atc,3755
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=XmCOaWgGo3RemU1ubYOoNr40eaRqGHCwX7GS2vt3voo,5935
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=KCyr9BzhGbsujG5z_4d06vk6Xn2IBrHPkflWFfe-CNI,116725
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=sX00xzczfmyW6yPG3nMm0xO8p-WARQTiD4jcoUiTxsg,3844
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=vs6cyN77q4HeiBvFbVBzMpL9H75wwskJk5WXjgIPw64,18963
241
- autocoder/linters/reactjs_linter.py,sha256=LDCXmAI15LUq8nNPyYko3oryzCc2Su05ob63jEeDdIs,20815
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/vue_linter.py,sha256=ZyvoxT0kSizFh_UkR7UZYO5DV9edbvDQZaibEF9W95I,20905
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.379.dist-info/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
333
- auto_coder-0.1.379.dist-info/METADATA,sha256=eBl5fXsRRMsTm7rMpOuBV2wuhbYHzoRBRRXAWSzA1sc,2775
334
- auto_coder-0.1.379.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
335
- auto_coder-0.1.379.dist-info/entry_points.txt,sha256=0nzHtHH4pNcM7xq4EBA2toS28Qelrvcbrr59GqD_0Ak,350
336
- auto_coder-0.1.379.dist-info/top_level.txt,sha256=Jqc0_uJSw2GwoFQAa9iJxYns-2mWla-9ok_Y3Gcznjk,10
337
- auto_coder-0.1.379.dist-info/RECORD,,
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
- printer.print_str_in_terminal(f"Command requires approval: {command}")
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
- printer.print_str_in_terminal(f"Executing command: {command} in {os.path.abspath(source_dir)}")
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 = False) -> ListCommandsResult:
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 resolve(self) -> ToolResult:
23
- printer = Printer()
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
- printer.print_str_in_terminal(f"Command requires approval: {command}")
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
- printer.print_str_in_terminal(f"Executing command: {command} in {os.path.abspath(source_dir)}")
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:\n{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=output)
100
+ return ToolResult(success=True, message="Command executed successfully.", content=final_output)
63
101
  else:
64
- error_message = f"Command failed with return code {exit_code}.\nOutput:\n{output}"
65
- return ToolResult(success=False, message=error_message, content={"output": output, "returncode": exit_code})
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': 'error' if message.get('severity', 1) == 2 else 'warning',
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))
@@ -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': 'error' if message.get('severity', 1) == 2 else 'warning',
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.379"
2
+ __version__ = "0.1.381"