auto-coder 0.1.362__py3-none-any.whl → 0.1.363__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of auto-coder might be problematic. Click here for more details.

Files changed (46) hide show
  1. {auto_coder-0.1.362.dist-info → auto_coder-0.1.363.dist-info}/METADATA +1 -1
  2. {auto_coder-0.1.362.dist-info → auto_coder-0.1.363.dist-info}/RECORD +46 -18
  3. autocoder/agent/base_agentic/__init__.py +0 -0
  4. autocoder/agent/base_agentic/agent_hub.py +169 -0
  5. autocoder/agent/base_agentic/agentic_lang.py +112 -0
  6. autocoder/agent/base_agentic/agentic_tool_display.py +180 -0
  7. autocoder/agent/base_agentic/base_agent.py +1582 -0
  8. autocoder/agent/base_agentic/default_tools.py +683 -0
  9. autocoder/agent/base_agentic/test_base_agent.py +82 -0
  10. autocoder/agent/base_agentic/tool_registry.py +425 -0
  11. autocoder/agent/base_agentic/tools/__init__.py +12 -0
  12. autocoder/agent/base_agentic/tools/ask_followup_question_tool_resolver.py +72 -0
  13. autocoder/agent/base_agentic/tools/attempt_completion_tool_resolver.py +37 -0
  14. autocoder/agent/base_agentic/tools/base_tool_resolver.py +35 -0
  15. autocoder/agent/base_agentic/tools/example_tool_resolver.py +46 -0
  16. autocoder/agent/base_agentic/tools/execute_command_tool_resolver.py +72 -0
  17. autocoder/agent/base_agentic/tools/list_files_tool_resolver.py +110 -0
  18. autocoder/agent/base_agentic/tools/plan_mode_respond_tool_resolver.py +35 -0
  19. autocoder/agent/base_agentic/tools/read_file_tool_resolver.py +54 -0
  20. autocoder/agent/base_agentic/tools/replace_in_file_tool_resolver.py +156 -0
  21. autocoder/agent/base_agentic/tools/search_files_tool_resolver.py +134 -0
  22. autocoder/agent/base_agentic/tools/talk_to_group_tool_resolver.py +96 -0
  23. autocoder/agent/base_agentic/tools/talk_to_tool_resolver.py +79 -0
  24. autocoder/agent/base_agentic/tools/use_mcp_tool_resolver.py +44 -0
  25. autocoder/agent/base_agentic/tools/write_to_file_tool_resolver.py +58 -0
  26. autocoder/agent/base_agentic/types.py +189 -0
  27. autocoder/agent/base_agentic/utils.py +100 -0
  28. autocoder/auto_coder_runner.py +4 -4
  29. autocoder/chat/conf_command.py +11 -10
  30. autocoder/common/rulefiles/autocoderrules_utils.py +24 -0
  31. autocoder/common/save_formatted_log.py +1 -1
  32. autocoder/common/v2/agent/agentic_edit.py +21 -19
  33. autocoder/common/v2/agent/agentic_edit_tools/replace_in_file_tool_resolver.py +73 -1
  34. autocoder/common/v2/agent/agentic_edit_tools/write_to_file_tool_resolver.py +132 -4
  35. autocoder/common/v2/agent/agentic_edit_types.py +1 -2
  36. autocoder/common/v2/agent/agentic_tool_display.py +2 -3
  37. autocoder/rag/long_context_rag.py +424 -397
  38. autocoder/rag/test_doc_filter.py +393 -0
  39. autocoder/rag/test_long_context_rag.py +473 -0
  40. autocoder/rag/test_token_limiter.py +342 -0
  41. autocoder/shadows/shadow_manager.py +1 -3
  42. autocoder/version.py +1 -1
  43. {auto_coder-0.1.362.dist-info → auto_coder-0.1.363.dist-info}/LICENSE +0 -0
  44. {auto_coder-0.1.362.dist-info → auto_coder-0.1.363.dist-info}/WHEEL +0 -0
  45. {auto_coder-0.1.362.dist-info → auto_coder-0.1.363.dist-info}/entry_points.txt +0 -0
  46. {auto_coder-0.1.362.dist-info → auto_coder-0.1.363.dist-info}/top_level.txt +0 -0
@@ -72,7 +72,6 @@ from autocoder.common.v2.agent.agentic_edit_types import (AgenticEditRequest, To
72
72
  AskFollowupQuestionTool, UseMcpTool, AttemptCompletionTool
73
73
  )
74
74
 
75
-
76
75
  # Map Pydantic Tool Models to their Resolver Classes
77
76
  TOOL_RESOLVER_MAP: Dict[Type[BaseTool], Type[BaseToolResolver]] = {
78
77
  ExecuteCommandTool: ExecuteCommandToolResolver,
@@ -86,7 +85,7 @@ TOOL_RESOLVER_MAP: Dict[Type[BaseTool], Type[BaseToolResolver]] = {
86
85
  AskFollowupQuestionTool: AskFollowupQuestionToolResolver,
87
86
  AttemptCompletionTool: AttemptCompletionToolResolver, # Will stop the loop anyway
88
87
  PlanModeRespondTool: PlanModeRespondToolResolver,
89
- UseMcpTool: UseMcpToolResolver,
88
+ UseMcpTool: UseMcpToolResolver
90
89
  }
91
90
 
92
91
 
@@ -233,7 +232,7 @@ class AgenticEdit:
233
232
  # Tools
234
233
 
235
234
  ## execute_command
236
- Description: Request to execute a CLI command on the system. Use this when you need to perform system operations or run specific commands to accomplish any step in the user's task. You must tailor your command to the user's system and provide a clear explanation of what the command does. For command chaining, use the appropriate chaining syntax for the user's shell. Prefer to execute complex CLI commands over creating executable scripts, as they are more flexible and easier to run. Commands will be executed in the current working directory: ${cwd.toPosix()}
235
+ Description: Request to execute a CLI command on the system. Use this when you need to perform system operations or run specific commands to accomplish any step in the user's task. You must tailor your command to the user's system and provide a clear explanation of what the command does. For command chaining, use the appropriate chaining syntax for the user's shell. Prefer to execute complex CLI commands over creating executable scripts, as they are more flexible and easier to run. Commands will be executed in the current working directory: {{current_project}}
237
236
  Parameters:
238
237
  - command: (required) The CLI command to execute. This should be valid for the current operating system. Ensure the command is properly formatted and does not contain any harmful instructions.
239
238
  - requires_approval: (required) A boolean indicating whether this command requires explicit user approval before execution in case the user has auto-approve mode enabled. Set to 'true' for potentially impactful operations like installing/uninstalling packages, deleting/overwriting files, system configuration changes, network operations, or any commands that could have unintended side effects. Set to 'false' for safe operations like reading files/directories, running development servers, building projects, and other non-destructive operations.
@@ -255,7 +254,7 @@ class AgenticEdit:
255
254
  ## read_file
256
255
  Description: Request to read the contents of a file at the specified path. Use this when you need to examine the contents of an existing file you do not know the contents of, for example to analyze code, review text files, or extract information from configuration files. Automatically extracts raw text from PDF and DOCX files. May not be suitable for other types of binary files, as it returns the raw content as a string.
257
256
  Parameters:
258
- - path: (required) The path of the file to read (relative to the current working directory ${cwd.toPosix()})
257
+ - path: (required) The path of the file to read (relative to the current working directory {{ current_project }})
259
258
  Usage:
260
259
  <read_file>
261
260
  <path>File path here</path>
@@ -264,7 +263,7 @@ class AgenticEdit:
264
263
  ## write_to_file
265
264
  Description: Request to write content to a file at the specified path. If the file exists, it will be overwritten with the provided content. If the file doesn't exist, it will be created. This tool will automatically create any directories needed to write the file.
266
265
  Parameters:
267
- - path: (required) The path of the file to write to (relative to the current working directory ${cwd.toPosix()})
266
+ - path: (required) The path of the file to write to (relative to the current working directory {{ current_project }})
268
267
  - content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified.
269
268
  Usage:
270
269
  <write_to_file>
@@ -277,15 +276,15 @@ class AgenticEdit:
277
276
  ## replace_in_file
278
277
  Description: Request to replace sections of content in an existing file using SEARCH/REPLACE blocks that define exact changes to specific parts of the file. This tool should be used when you need to make targeted changes to specific parts of a file.
279
278
  Parameters:
280
- - path: (required) The path of the file to modify (relative to the current working directory ${cwd.toPosix()})
279
+ - path: (required) The path of the file to modify (relative to the current working directory {{ current_project }})
281
280
  - diff: (required) One or more SEARCH/REPLACE blocks following this exact format:
282
- \`\`\`
281
+ ```
283
282
  <<<<<<< SEARCH
284
283
  [exact content to find]
285
284
  =======
286
285
  [new content to replace with]
287
286
  >>>>>>> REPLACE
288
- \`\`\`
287
+ ```
289
288
  Critical rules:
290
289
  1. SEARCH content must match the associated file section to find EXACTLY:
291
290
  * Match character-for-character including whitespace, indentation, line endings
@@ -313,7 +312,7 @@ class AgenticEdit:
313
312
  ## search_files
314
313
  Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context.
315
314
  Parameters:
316
- - path: (required) The path of the directory to search in (relative to the current working directory ${cwd.toPosix()}). This directory will be recursively searched.
315
+ - path: (required) The path of the directory to search in (relative to the current working directory {{ current_project }}). This directory will be recursively searched.
317
316
  - regex: (required) The regular expression pattern to search for. Uses Rust regex syntax.
318
317
  - file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*).
319
318
  Usage:
@@ -326,7 +325,7 @@ class AgenticEdit:
326
325
  ## list_files
327
326
  Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not.
328
327
  Parameters:
329
- - path: (required) The path of the directory to list contents for (relative to the current working directory ${cwd.toPosix()})
328
+ - path: (required) The path of the directory to list contents for (relative to the current working directory {{ current_project }})
330
329
  - recursive: (optional) Whether to list files recursively. Use true for recursive listing, false or omit for top-level only.
331
330
  Usage:
332
331
  <list_files>
@@ -337,7 +336,7 @@ class AgenticEdit:
337
336
  ## list_code_definition_names
338
337
  Description: Request to list definition names (classes, functions, methods, etc.) used in source code files at the top level of the specified directory. This tool provides insights into the codebase structure and important constructs, encapsulating high-level concepts and relationships that are crucial for understanding the overall architecture.
339
338
  Parameters:
340
- - path: (required) The path of the directory (relative to the current working directory ${cwd.toPosix()}) to list top level source code definitions for.
339
+ - path: (required) The path of the directory (relative to the current working directory {{ current_project }}) to list top level source code definitions for.
341
340
  Usage:
342
341
  <list_code_definition_names>
343
342
  <path>Directory path here</path>
@@ -636,7 +635,7 @@ class AgenticEdit:
636
635
  - Your current working directory is: {{current_project}}
637
636
  - You cannot \`cd\` into a different directory to complete a task. You are stuck operating from '{{ current_project }}', so be sure to pass in the correct 'path' parameter when using tools that require a path.
638
637
  - Do not use the ~ character or $HOME to refer to the home directory.
639
- - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '${cwd.toPosix()}', and if so prepend with \`cd\`'ing into that directory && then executing the command (as one command since you are stuck operating from '${cwd.toPosix()}'). For example, if you needed to run \`npm install\` in a project outside of '${cwd.toPosix()}', you would need to prepend with a \`cd\` i.e. pseudocode for this would be \`cd (path to project) && (command, in this case npm install)\`.
638
+ - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '{{ current_project }}', and if so prepend with \`cd\`'ing into that directory && then executing the command (as one command since you are stuck operating from '{{ current_project }}'). For example, if you needed to run \`npm install\` in a project outside of '{{ current_project }}', you would need to prepend with a \`cd\` i.e. pseudocode for this would be \`cd (path to project) && (command, in this case npm install)\`.
640
639
  - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using replace_in_file to make informed changes.
641
640
  - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when creating files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser.
642
641
  - Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
@@ -813,17 +812,18 @@ class AgenticEdit:
813
812
 
814
813
  assistant_buffer = ""
815
814
  logger.info("Initializing stream chat with LLM")
815
+
816
+ ## 实际请求大模型
816
817
  llm_response_gen = stream_chat_with_continue(
817
818
  llm=self.llm,
818
819
  conversations=conversations,
819
820
  llm_config={}, # Placeholder for future LLM configs
820
821
  args=self.args
821
822
  )
822
-
823
- meta_holder = byzerllm.MetaHolder()
823
+
824
824
  logger.info("Starting to parse LLM response stream")
825
825
  parsed_events = self.stream_and_parse_llm_response(
826
- llm_response_gen, meta_holder)
826
+ llm_response_gen)
827
827
 
828
828
  event_count = 0
829
829
  for event in parsed_events:
@@ -942,8 +942,9 @@ class AgenticEdit:
942
942
  # logger.error("Stopping analyze loop due to parsing error.")
943
943
  # return
944
944
 
945
- logger.info("Yielding token usage event")
946
- yield TokenUsageEvent(usage=meta_holder.meta)
945
+ elif isinstance(event, TokenUsageEvent):
946
+ logger.info("Yielding token usage event")
947
+ yield event
947
948
 
948
949
  if not tool_executed:
949
950
  # No tool executed in this LLM response cycle
@@ -973,7 +974,7 @@ class AgenticEdit:
973
974
  logger.info(f"AgenticEdit analyze loop finished after {iteration_count} iterations.")
974
975
 
975
976
  def stream_and_parse_llm_response(
976
- self, generator: Generator[Tuple[str, Any], None, None], meta_holder: byzerllm.MetaHolder
977
+ self, generator: Generator[Tuple[str, Any], None, None]
977
978
  ) -> Generator[Union[LLMOutputEvent, LLMThinkingEvent, ToolCallEvent, ErrorEvent], None, None]:
978
979
  """
979
980
  Streamingly parses the LLM response generator, distinguishing between
@@ -1050,7 +1051,8 @@ class AgenticEdit:
1050
1051
  logger.exception(
1051
1052
  f"Failed to parse tool XML for <{tool_tag}>: {e}\nXML:\n{tool_xml}")
1052
1053
  return None
1053
-
1054
+
1055
+ meta_holder = byzerllm.MetaHolder()
1054
1056
  for content_chunk, metadata in generator:
1055
1057
  global_cancel.check_and_raise(token=self.args.event_file)
1056
1058
  meta_holder.meta = metadata
@@ -14,7 +14,9 @@ class ReplaceInFileToolResolver(BaseToolResolver):
14
14
  def __init__(self, agent: Optional['AgenticEdit'], tool: ReplaceInFileTool, args: AutoCoderArgs):
15
15
  super().__init__(agent, tool, args)
16
16
  self.tool: ReplaceInFileTool = tool # For type hinting
17
+ self.args = args
17
18
  self.shadow_manager = self.agent.shadow_manager if self.agent else None
19
+ self.shadow_linter = self.agent.shadow_linter if self.agent else None
18
20
 
19
21
  def parse_diff(self, diff_content: str) -> List[Tuple[str, str]]:
20
22
  """
@@ -58,6 +60,30 @@ class ReplaceInFileToolResolver(BaseToolResolver):
58
60
  logger.warning(f"Could not parse any SEARCH/REPLACE blocks from diff: {diff_content}")
59
61
  return blocks
60
62
 
63
+ def _format_lint_issues(self, lint_result):
64
+ """
65
+ 将 lint 结果格式化为可读的文本格式
66
+
67
+ 参数:
68
+ lint_result: 单个文件的 lint 结果对象
69
+
70
+ 返回:
71
+ str: 格式化的问题描述
72
+ """
73
+ formatted_issues = []
74
+
75
+ for issue in lint_result.issues:
76
+ severity = "错误" if issue.severity.value == 3 else "警告" if issue.severity.value == 2 else "信息"
77
+ line_info = f"第{issue.position.line}行"
78
+ if issue.position.column:
79
+ line_info += f", 第{issue.position.column}列"
80
+
81
+ formatted_issues.append(
82
+ f" - [{severity}] {line_info}: {issue.message} (规则: {issue.code})"
83
+ )
84
+
85
+ return "\n".join(formatted_issues)
86
+
61
87
  def resolve(self) -> ToolResult:
62
88
  file_path = self.tool.path
63
89
  diff_content = self.tool.diff
@@ -130,6 +156,36 @@ class ReplaceInFileToolResolver(BaseToolResolver):
130
156
  f.write(current_content)
131
157
  logger.info(f"Successfully applied {applied_count}/{len(parsed_blocks)} changes to file: {file_path}")
132
158
 
159
+ # 新增:执行代码质量检查
160
+ lint_results = None
161
+ lint_message = ""
162
+ formatted_issues = ""
163
+ has_lint_issues = False
164
+
165
+ # 检查是否启用了Lint功能
166
+ enable_lint = self.args.enable_auto_fix_lint
167
+
168
+ if enable_lint:
169
+ try:
170
+ if self.shadow_linter and self.shadow_manager:
171
+ # 对修改后的文件进行 lint 检查
172
+ shadow_path = target_path # 已经是影子路径
173
+ lint_results = self.shadow_linter.lint_shadow_file(shadow_path)
174
+
175
+ if lint_results and lint_results.issues:
176
+ has_lint_issues = True
177
+ # 格式化 lint 问题
178
+ formatted_issues = self._format_lint_issues(lint_results)
179
+ lint_message = f"\n\n代码质量检查发现 {len(lint_results.issues)} 个问题:\n{formatted_issues}"
180
+ else:
181
+ lint_message = "\n\n代码质量检查通过,未发现问题。"
182
+ except Exception as e:
183
+ logger.error(f"Lint 检查失败: {str(e)}")
184
+ lint_message = "\n\n尝试进行代码质量检查时出错。"
185
+ else:
186
+ logger.info("代码质量检查已禁用")
187
+
188
+ # 构建包含 lint 结果的返回消息
133
189
  if errors:
134
190
  message = get_message_with_format("replace_in_file.apply_success_with_warnings",
135
191
  applied=applied_count,
@@ -141,13 +197,29 @@ class ReplaceInFileToolResolver(BaseToolResolver):
141
197
  applied=applied_count,
142
198
  total=len(parsed_blocks),
143
199
  file_path=file_path)
200
+
201
+ # 将 lint 消息添加到结果中,如果启用了Lint
202
+ if enable_lint:
203
+ message += lint_message
144
204
 
145
205
  # 变更跟踪,回调AgenticEdit
146
206
  if self.agent:
147
207
  rel_path = os.path.relpath(abs_file_path, abs_project_dir)
148
208
  self.agent.record_file_change(rel_path, "modified", diff=diff_content, content=current_content)
149
209
 
150
- return ToolResult(success=True, message=message, content=current_content)
210
+ # 附加 lint 结果到返回内容
211
+ result_content = {
212
+ "content": current_content,
213
+ }
214
+
215
+ # 只有在启用Lint时才添加Lint结果
216
+ if enable_lint:
217
+ result_content["lint_results"] = {
218
+ "has_issues": has_lint_issues,
219
+ "issues": formatted_issues if has_lint_issues else None
220
+ }
221
+
222
+ return ToolResult(success=True, message=message, content=result_content)
151
223
  except Exception as e:
152
224
  logger.error(f"Error writing replaced content to file '{file_path}': {str(e)}")
153
225
  return ToolResult(success=False, message=get_message_with_format("replace_in_file.write_error", error=str(e)))
@@ -4,6 +4,7 @@ from autocoder.common.v2.agent.agentic_edit_types import WriteToFileTool, ToolRe
4
4
  from autocoder.common.v2.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
5
5
  from loguru import logger
6
6
  from autocoder.common import AutoCoderArgs
7
+ from autocoder.common.auto_coder_lang import get_message_with_format
7
8
  import typing
8
9
 
9
10
  if typing.TYPE_CHECKING:
@@ -13,7 +14,33 @@ class WriteToFileToolResolver(BaseToolResolver):
13
14
  def __init__(self, agent: Optional['AgenticEdit'], tool: WriteToFileTool, args: AutoCoderArgs):
14
15
  super().__init__(agent, tool, args)
15
16
  self.tool: WriteToFileTool = tool # For type hinting
17
+ self.args = args
16
18
  self.shadow_manager = self.agent.shadow_manager if self.agent else None
19
+ self.shadow_linter = self.agent.shadow_linter if self.agent else None
20
+
21
+ def _format_lint_issues(self, lint_result):
22
+ """
23
+ 将 lint 结果格式化为可读的文本格式
24
+
25
+ 参数:
26
+ lint_result: 单个文件的 lint 结果对象
27
+
28
+ 返回:
29
+ str: 格式化的问题描述
30
+ """
31
+ formatted_issues = []
32
+
33
+ for issue in lint_result.issues:
34
+ severity = "错误" if issue.severity.value == 3 else "警告" if issue.severity.value == 2 else "信息"
35
+ line_info = f"第{issue.position.line}行"
36
+ if issue.position.column:
37
+ line_info += f", 第{issue.position.column}列"
38
+
39
+ formatted_issues.append(
40
+ f" - [{severity}] {line_info}: {issue.message} (规则: {issue.code})"
41
+ )
42
+
43
+ return "\n".join(formatted_issues)
17
44
 
18
45
  def resolve(self) -> ToolResult:
19
46
  file_path = self.tool.path
@@ -39,8 +66,56 @@ class WriteToFileToolResolver(BaseToolResolver):
39
66
  if self.agent:
40
67
  rel_path = os.path.relpath(abs_file_path, abs_project_dir)
41
68
  self.agent.record_file_change(rel_path, "added", diff=None, content=content)
42
-
43
- return ToolResult(success=True, message=f"Successfully wrote to file (shadow): {file_path}", content=content)
69
+
70
+ # 新增:执行代码质量检查
71
+ lint_results = None
72
+ lint_message = ""
73
+ formatted_issues = ""
74
+ has_lint_issues = False
75
+
76
+ # 检查是否启用了Lint功能
77
+ enable_lint = self.args.enable_auto_fix_lint
78
+
79
+ if enable_lint:
80
+ try:
81
+ if self.shadow_linter and self.shadow_manager:
82
+ # 对新创建的文件进行 lint 检查
83
+ shadow_path = self.shadow_manager.to_shadow_path(abs_file_path)
84
+ lint_results = self.shadow_linter.lint_shadow_file(shadow_path)
85
+
86
+ if lint_results and lint_results.issues:
87
+ has_lint_issues = True
88
+ # 格式化 lint 问题
89
+ formatted_issues = self._format_lint_issues(lint_results)
90
+ lint_message = f"\n\n代码质量检查发现 {len(lint_results.issues)} 个问题:\n{formatted_issues}"
91
+ else:
92
+ lint_message = "\n\n代码质量检查通过,未发现问题。"
93
+ except Exception as e:
94
+ logger.error(f"Lint 检查失败: {str(e)}")
95
+ lint_message = "\n\n尝试进行代码质量检查时出错。"
96
+ else:
97
+ logger.info("代码质量检查已禁用")
98
+
99
+ # 构建包含 lint 结果的返回消息
100
+ message = f"Successfully wrote to file (shadow): {file_path}"
101
+
102
+ # 将 lint 消息添加到结果中,如果启用了Lint
103
+ if enable_lint:
104
+ message += lint_message
105
+
106
+ # 附加 lint 结果到返回内容
107
+ result_content = {
108
+ "content": content,
109
+ }
110
+
111
+ # 只有在启用Lint时才添加Lint结果
112
+ if enable_lint:
113
+ result_content["lint_results"] = {
114
+ "has_issues": has_lint_issues,
115
+ "issues": formatted_issues if has_lint_issues else None
116
+ }
117
+
118
+ return ToolResult(success=True, message=message, content=result_content)
44
119
  else:
45
120
  # No shadow manager fallback to original file
46
121
  os.makedirs(os.path.dirname(abs_file_path), exist_ok=True)
@@ -51,8 +126,61 @@ class WriteToFileToolResolver(BaseToolResolver):
51
126
  if self.agent:
52
127
  rel_path = os.path.relpath(abs_file_path, abs_project_dir)
53
128
  self.agent.record_file_change(rel_path, "added", diff=None, content=content)
54
-
55
- return ToolResult(success=True, message=f"Successfully wrote to file: {file_path}", content=content)
129
+
130
+ # 新增:执行代码质量检查
131
+ lint_results = None
132
+ lint_message = ""
133
+ formatted_issues = ""
134
+ has_lint_issues = False
135
+
136
+ # 检查是否启用了Lint功能
137
+ enable_lint = self.args.enable_auto_fix_lint
138
+
139
+ if enable_lint:
140
+ try:
141
+ if self.shadow_linter and self.shadow_manager:
142
+ # 对新创建的文件进行 lint 检查
143
+ # 由于没有shadow系统,需要先创建shadow文件
144
+ shadow_path = self.shadow_manager.to_shadow_path(abs_file_path)
145
+ os.makedirs(os.path.dirname(shadow_path), exist_ok=True)
146
+ with open(shadow_path, 'w', encoding='utf-8') as f:
147
+ f.write(content)
148
+
149
+ lint_results = self.shadow_linter.lint_shadow_file(shadow_path)
150
+
151
+ if lint_results and lint_results.issues:
152
+ has_lint_issues = True
153
+ # 格式化 lint 问题
154
+ formatted_issues = self._format_lint_issues(lint_results)
155
+ lint_message = f"\n\n代码质量检查发现 {len(lint_results.issues)} 个问题:\n{formatted_issues}"
156
+ else:
157
+ lint_message = "\n\n代码质量检查通过,未发现问题。"
158
+ except Exception as e:
159
+ logger.error(f"Lint 检查失败: {str(e)}")
160
+ lint_message = "\n\n尝试进行代码质量检查时出错。"
161
+ else:
162
+ logger.info("代码质量检查已禁用")
163
+
164
+ # 构建包含 lint 结果的返回消息
165
+ message = f"Successfully wrote to file: {file_path}"
166
+
167
+ # 将 lint 消息添加到结果中,如果启用了Lint
168
+ if enable_lint:
169
+ message += lint_message
170
+
171
+ # 附加 lint 结果到返回内容
172
+ result_content = {
173
+ "content": content,
174
+ }
175
+
176
+ # 只有在启用Lint时才添加Lint结果
177
+ if enable_lint:
178
+ result_content["lint_results"] = {
179
+ "has_issues": has_lint_issues,
180
+ "issues": formatted_issues if has_lint_issues else None
181
+ }
182
+
183
+ return ToolResult(success=True, message=message, content=result_content)
56
184
  except Exception as e:
57
185
  logger.error(f"Error writing to file '{file_path}': {str(e)}")
58
186
  return ToolResult(success=False, message=f"An error occurred while writing to the file: {str(e)}")
@@ -2,7 +2,6 @@ from pydantic import BaseModel
2
2
  from typing import List, Dict, Any, Callable, Optional, Type
3
3
  from pydantic import SkipValidation
4
4
 
5
-
6
5
  # Result class used by Tool Resolvers
7
6
  class ToolResult(BaseModel):
8
7
  success: bool
@@ -115,7 +114,7 @@ TOOL_MODEL_MAP: Dict[str, Type[BaseTool]] = {
115
114
  "attempt_completion": AttemptCompletionTool,
116
115
  "plan_mode_respond": PlanModeRespondTool,
117
116
  "use_mcp_tool": UseMcpTool,
118
- "list_package_info": ListPackageInfoTool,
117
+ "list_package_info": ListPackageInfoTool,
119
118
  }
120
119
 
121
120
  class FileChangeEntry(BaseModel):
@@ -93,8 +93,7 @@ TOOL_DISPLAY_MESSAGES: Dict[Type[BaseTool], Dict[str, str]] = {
93
93
  "[dim]工具:[/dim] [blue]{{ tool_name }}[/]\n"
94
94
  "[dim]参数:[/dim] {{ arguments_snippet }}{{ ellipsis }}"
95
95
  )
96
- }
97
- # AttemptCompletionTool is handled separately in the display loop
96
+ }
98
97
  }
99
98
 
100
99
  def get_tool_display_message(tool: BaseTool) -> str:
@@ -172,7 +171,7 @@ def get_tool_display_message(tool: BaseTool) -> str:
172
171
  "tool_name": tool.tool_name,
173
172
  "arguments_snippet": snippet,
174
173
  "ellipsis": '...' if len(args_str) > 100 else ''
175
- }
174
+ }
176
175
  else:
177
176
  # Generic context for tools not specifically handled above
178
177
  context = tool.model_dump()