auto-coder 1.0.0__py3-none-any.whl → 2.0.1__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 (574) hide show
  1. auto_coder-2.0.1.dist-info/LICENSE +158 -0
  2. auto_coder-2.0.1.dist-info/METADATA +558 -0
  3. auto_coder-2.0.1.dist-info/RECORD +795 -0
  4. {auto_coder-1.0.0.dist-info → auto_coder-2.0.1.dist-info}/WHEEL +1 -1
  5. {auto_coder-1.0.0.dist-info → auto_coder-2.0.1.dist-info}/entry_points.txt +3 -3
  6. autocoder/__init__.py +31 -0
  7. autocoder/agent/auto_filegroup.py +32 -13
  8. autocoder/agent/auto_learn_from_commit.py +9 -1
  9. autocoder/agent/base_agentic/__init__.py +3 -0
  10. autocoder/agent/base_agentic/agent_hub.py +1 -1
  11. autocoder/agent/base_agentic/base_agent.py +235 -136
  12. autocoder/agent/base_agentic/default_tools.py +119 -118
  13. autocoder/agent/base_agentic/test_base_agent.py +1 -1
  14. autocoder/agent/base_agentic/tool_registry.py +32 -20
  15. autocoder/agent/base_agentic/tools/read_file_tool_resolver.py +24 -3
  16. autocoder/agent/base_agentic/tools/write_to_file_tool_resolver.py +24 -11
  17. autocoder/agent/base_agentic/types.py +42 -0
  18. autocoder/agent/entry_command_agent/chat.py +77 -73
  19. autocoder/auto_coder.py +31 -40
  20. autocoder/auto_coder_rag.py +11 -1084
  21. autocoder/auto_coder_runner.py +962 -2345
  22. autocoder/auto_coder_terminal.py +26 -0
  23. autocoder/auto_coder_terminal_v3.py +190 -0
  24. autocoder/chat/conf_command.py +224 -124
  25. autocoder/chat/models_command.py +361 -299
  26. autocoder/chat/rules_command.py +79 -31
  27. autocoder/chat_auto_coder.py +988 -398
  28. autocoder/chat_auto_coder_lang.py +23 -732
  29. autocoder/commands/auto_command.py +25 -8
  30. autocoder/commands/auto_web.py +1 -1
  31. autocoder/commands/tools.py +44 -44
  32. autocoder/common/__init__.py +150 -128
  33. autocoder/common/ac_style_command_parser/__init__.py +39 -2
  34. autocoder/common/ac_style_command_parser/config.py +422 -0
  35. autocoder/common/ac_style_command_parser/parser.py +292 -78
  36. autocoder/common/ac_style_command_parser/test_parser.py +241 -16
  37. autocoder/common/ac_style_command_parser/test_typed_parser.py +342 -0
  38. autocoder/common/ac_style_command_parser/typed_parser.py +653 -0
  39. autocoder/common/action_yml_file_manager.py +25 -13
  40. autocoder/common/agent_events/__init__.py +52 -0
  41. autocoder/common/agent_events/agent_event_emitter.py +193 -0
  42. autocoder/common/agent_events/event_factory.py +177 -0
  43. autocoder/common/agent_events/examples.py +307 -0
  44. autocoder/common/agent_events/types.py +113 -0
  45. autocoder/common/agent_events/utils.py +68 -0
  46. autocoder/common/agent_hooks/__init__.py +44 -0
  47. autocoder/common/agent_hooks/examples.py +582 -0
  48. autocoder/common/agent_hooks/hook_executor.py +217 -0
  49. autocoder/common/agent_hooks/hook_manager.py +288 -0
  50. autocoder/common/agent_hooks/types.py +133 -0
  51. autocoder/common/agent_hooks/utils.py +99 -0
  52. autocoder/common/agent_query_queue/queue_executor.py +324 -0
  53. autocoder/common/agent_query_queue/queue_manager.py +325 -0
  54. autocoder/common/agents/__init__.py +11 -0
  55. autocoder/common/agents/agent_manager.py +323 -0
  56. autocoder/common/agents/agent_parser.py +189 -0
  57. autocoder/common/agents/example_usage.py +344 -0
  58. autocoder/common/agents/integration_example.py +330 -0
  59. autocoder/common/agents/test_agent_parser.py +545 -0
  60. autocoder/common/async_utils.py +101 -0
  61. autocoder/common/auto_coder_lang.py +23 -972
  62. autocoder/common/autocoderargs_parser/__init__.py +14 -0
  63. autocoder/common/autocoderargs_parser/parser.py +184 -0
  64. autocoder/common/autocoderargs_parser/tests/__init__.py +1 -0
  65. autocoder/common/autocoderargs_parser/tests/test_args_parser.py +235 -0
  66. autocoder/common/autocoderargs_parser/tests/test_token_parser.py +195 -0
  67. autocoder/common/autocoderargs_parser/token_parser.py +290 -0
  68. autocoder/common/buildin_tokenizer.py +2 -4
  69. autocoder/common/code_auto_generate.py +149 -74
  70. autocoder/common/code_auto_generate_diff.py +163 -70
  71. autocoder/common/code_auto_generate_editblock.py +179 -89
  72. autocoder/common/code_auto_generate_strict_diff.py +167 -72
  73. autocoder/common/code_auto_merge_editblock.py +13 -6
  74. autocoder/common/code_modification_ranker.py +1 -1
  75. autocoder/common/command_completer.py +3 -3
  76. autocoder/common/command_file_manager/manager.py +183 -47
  77. autocoder/common/command_file_manager/test_command_file_manager.py +507 -0
  78. autocoder/common/command_templates.py +1 -1
  79. autocoder/common/conf_utils.py +2 -4
  80. autocoder/common/conversations/config.py +11 -3
  81. autocoder/common/conversations/get_conversation_manager.py +100 -2
  82. autocoder/common/conversations/llm_stats_models.py +264 -0
  83. autocoder/common/conversations/manager.py +112 -28
  84. autocoder/common/conversations/models.py +16 -2
  85. autocoder/common/conversations/storage/index_manager.py +134 -10
  86. autocoder/common/core_config/__init__.py +63 -0
  87. autocoder/common/core_config/agentic_mode_manager.py +109 -0
  88. autocoder/common/core_config/base_manager.py +123 -0
  89. autocoder/common/core_config/compatibility.py +151 -0
  90. autocoder/common/core_config/config_manager.py +156 -0
  91. autocoder/common/core_config/conversation_manager.py +31 -0
  92. autocoder/common/core_config/exclude_manager.py +72 -0
  93. autocoder/common/core_config/file_manager.py +177 -0
  94. autocoder/common/core_config/human_as_model_manager.py +129 -0
  95. autocoder/common/core_config/lib_manager.py +54 -0
  96. autocoder/common/core_config/main_manager.py +81 -0
  97. autocoder/common/core_config/mode_manager.py +126 -0
  98. autocoder/common/core_config/models.py +70 -0
  99. autocoder/common/core_config/test_memory_manager.py +1056 -0
  100. autocoder/common/env_manager.py +282 -0
  101. autocoder/common/env_manager_usage_example.py +211 -0
  102. autocoder/common/file_checkpoint/conversation_checkpoint.py +19 -19
  103. autocoder/common/file_checkpoint/manager.py +264 -48
  104. autocoder/common/file_checkpoint/test_backup.py +1 -18
  105. autocoder/common/file_checkpoint/test_manager.py +270 -1
  106. autocoder/common/file_checkpoint/test_store.py +1 -17
  107. autocoder/common/file_handler/__init__.py +23 -0
  108. autocoder/common/file_handler/active_context_handler.py +159 -0
  109. autocoder/common/file_handler/add_files_handler.py +409 -0
  110. autocoder/common/file_handler/chat_handler.py +180 -0
  111. autocoder/common/file_handler/coding_handler.py +409 -0
  112. autocoder/common/file_handler/commit_handler.py +200 -0
  113. autocoder/common/file_handler/lib_handler.py +156 -0
  114. autocoder/common/file_handler/list_files_handler.py +111 -0
  115. autocoder/common/file_handler/mcp_handler.py +268 -0
  116. autocoder/common/file_handler/models_handler.py +493 -0
  117. autocoder/common/file_handler/remove_files_handler.py +172 -0
  118. autocoder/common/git_utils.py +44 -8
  119. autocoder/common/global_cancel.py +15 -6
  120. autocoder/common/ignorefiles/test_ignore_file_utils.py +1 -1
  121. autocoder/common/international/__init__.py +31 -0
  122. autocoder/common/international/demo_international.py +92 -0
  123. autocoder/common/international/message_manager.py +157 -0
  124. autocoder/common/international/messages/__init__.py +56 -0
  125. autocoder/common/international/messages/async_command_messages.py +507 -0
  126. autocoder/common/international/messages/auto_coder_messages.py +2208 -0
  127. autocoder/common/international/messages/chat_auto_coder_messages.py +1547 -0
  128. autocoder/common/international/messages/command_help_messages.py +986 -0
  129. autocoder/common/international/messages/conversation_command_messages.py +191 -0
  130. autocoder/common/international/messages/git_helper_plugin_messages.py +159 -0
  131. autocoder/common/international/messages/queue_command_messages.py +751 -0
  132. autocoder/common/international/messages/rules_command_messages.py +77 -0
  133. autocoder/common/international/messages/sdk_messages.py +1707 -0
  134. autocoder/common/international/messages/token_helper_plugin_messages.py +361 -0
  135. autocoder/common/international/messages/tool_display_messages.py +1212 -0
  136. autocoder/common/international/messages/workflow_exception_messages.py +473 -0
  137. autocoder/common/international/test_international.py +612 -0
  138. autocoder/common/linter_core/__init__.py +28 -0
  139. autocoder/common/linter_core/base_linter.py +61 -0
  140. autocoder/common/linter_core/config_loader.py +271 -0
  141. autocoder/common/linter_core/formatters/__init__.py +0 -0
  142. autocoder/common/linter_core/formatters/base_formatter.py +38 -0
  143. autocoder/common/linter_core/formatters/raw_formatter.py +17 -0
  144. autocoder/common/linter_core/linter.py +166 -0
  145. autocoder/common/linter_core/linter_factory.py +216 -0
  146. autocoder/common/linter_core/linter_manager.py +333 -0
  147. autocoder/common/linter_core/linters/__init__.py +9 -0
  148. autocoder/common/linter_core/linters/java_linter.py +342 -0
  149. autocoder/common/linter_core/linters/python_linter.py +115 -0
  150. autocoder/common/linter_core/linters/typescript_linter.py +119 -0
  151. autocoder/common/linter_core/models/__init__.py +7 -0
  152. autocoder/common/linter_core/models/lint_result.py +91 -0
  153. autocoder/common/linter_core/models.py +33 -0
  154. autocoder/common/linter_core/tests/__init__.py +3 -0
  155. autocoder/common/linter_core/tests/test_config_loader.py +323 -0
  156. autocoder/common/linter_core/tests/test_config_loading.py +308 -0
  157. autocoder/common/linter_core/tests/test_factory_manager.py +234 -0
  158. autocoder/common/linter_core/tests/test_formatters.py +147 -0
  159. autocoder/common/linter_core/tests/test_integration.py +317 -0
  160. autocoder/common/linter_core/tests/test_java_linter.py +496 -0
  161. autocoder/common/linter_core/tests/test_linters.py +265 -0
  162. autocoder/common/linter_core/tests/test_models.py +81 -0
  163. autocoder/common/linter_core/tests/verify_config_loading.py +296 -0
  164. autocoder/common/linter_core/tests/verify_fixes.py +183 -0
  165. autocoder/common/llm_friendly_package/__init__.py +31 -0
  166. autocoder/common/llm_friendly_package/base_manager.py +102 -0
  167. autocoder/common/llm_friendly_package/docs_manager.py +121 -0
  168. autocoder/common/llm_friendly_package/library_manager.py +171 -0
  169. autocoder/common/{llm_friendly_package.py → llm_friendly_package/main_manager.py} +204 -231
  170. autocoder/common/llm_friendly_package/models.py +40 -0
  171. autocoder/common/llm_friendly_package/test_llm_friendly_package.py +536 -0
  172. autocoder/common/llms/__init__.py +15 -0
  173. autocoder/common/llms/demo_error_handling.py +85 -0
  174. autocoder/common/llms/factory.py +142 -0
  175. autocoder/common/llms/manager.py +264 -0
  176. autocoder/common/llms/pricing.py +121 -0
  177. autocoder/common/llms/registry.py +316 -0
  178. autocoder/common/llms/schema.py +77 -0
  179. autocoder/common/llms/simple_demo.py +45 -0
  180. autocoder/common/llms/test_quick_model.py +116 -0
  181. autocoder/common/llms/test_remove_functionality.py +182 -0
  182. autocoder/common/llms/tests/__init__.py +1 -0
  183. autocoder/common/llms/tests/test_manager.py +330 -0
  184. autocoder/common/llms/tests/test_registry.py +364 -0
  185. autocoder/common/mcp_tools/__init__.py +62 -0
  186. autocoder/common/{mcp_tools.py → mcp_tools/executor.py} +49 -40
  187. autocoder/common/{mcp_hub.py → mcp_tools/hub.py} +42 -68
  188. autocoder/common/{mcp_server_install.py → mcp_tools/installer.py} +16 -28
  189. autocoder/common/{mcp_server.py → mcp_tools/server.py} +176 -48
  190. autocoder/common/mcp_tools/test_keyboard_interrupt.py +93 -0
  191. autocoder/common/mcp_tools/test_mcp_tools.py +391 -0
  192. autocoder/common/{mcp_server_types.py → mcp_tools/types.py} +121 -48
  193. autocoder/common/mcp_tools/verify_functionality.py +202 -0
  194. autocoder/common/model_speed_tester.py +32 -26
  195. autocoder/common/priority_directory_finder/__init__.py +142 -0
  196. autocoder/common/priority_directory_finder/examples.py +230 -0
  197. autocoder/common/priority_directory_finder/finder.py +283 -0
  198. autocoder/common/priority_directory_finder/models.py +236 -0
  199. autocoder/common/priority_directory_finder/test_priority_directory_finder.py +431 -0
  200. autocoder/common/project_scanner/__init__.py +18 -0
  201. autocoder/common/project_scanner/compat.py +77 -0
  202. autocoder/common/project_scanner/scanner.py +436 -0
  203. autocoder/common/project_tracker/__init__.py +27 -0
  204. autocoder/common/project_tracker/api.py +228 -0
  205. autocoder/common/project_tracker/demo.py +272 -0
  206. autocoder/common/project_tracker/tracker.py +487 -0
  207. autocoder/common/project_tracker/types.py +53 -0
  208. autocoder/common/pruner/__init__.py +67 -0
  209. autocoder/common/pruner/agentic_conversation_pruner.py +651 -102
  210. autocoder/common/pruner/conversation_message_ids_api.py +386 -0
  211. autocoder/common/pruner/conversation_message_ids_manager.py +347 -0
  212. autocoder/common/pruner/conversation_message_ids_pruner.py +473 -0
  213. autocoder/common/pruner/conversation_normalizer.py +347 -0
  214. autocoder/common/pruner/conversation_pruner.py +26 -6
  215. autocoder/common/pruner/test_agentic_conversation_pruner.py +554 -112
  216. autocoder/common/pruner/test_conversation_normalizer.py +502 -0
  217. autocoder/common/pruner/test_tool_content_detector.py +324 -0
  218. autocoder/common/pruner/tool_content_detector.py +227 -0
  219. autocoder/common/pruner/tools/__init__.py +18 -0
  220. autocoder/common/pruner/tools/query_message_ids.py +264 -0
  221. autocoder/common/pruner/tools/test_agentic_pruning_logic.py +432 -0
  222. autocoder/common/pruner/tools/test_message_ids_pruning_only.py +192 -0
  223. autocoder/common/pull_requests/__init__.py +9 -1
  224. autocoder/common/pull_requests/utils.py +122 -1
  225. autocoder/common/rag_manager/rag_manager.py +36 -40
  226. autocoder/common/rulefiles/__init__.py +53 -1
  227. autocoder/common/rulefiles/api.py +250 -0
  228. autocoder/common/rulefiles/core/__init__.py +14 -0
  229. autocoder/common/rulefiles/core/manager.py +241 -0
  230. autocoder/common/rulefiles/core/selector.py +805 -0
  231. autocoder/common/rulefiles/models/__init__.py +20 -0
  232. autocoder/common/rulefiles/models/index.py +16 -0
  233. autocoder/common/rulefiles/models/init_rule.py +18 -0
  234. autocoder/common/rulefiles/models/rule_file.py +18 -0
  235. autocoder/common/rulefiles/models/rule_relevance.py +14 -0
  236. autocoder/common/rulefiles/models/summary.py +16 -0
  237. autocoder/common/rulefiles/test_rulefiles.py +776 -0
  238. autocoder/common/rulefiles/utils/__init__.py +34 -0
  239. autocoder/common/rulefiles/utils/monitor.py +86 -0
  240. autocoder/common/rulefiles/utils/parser.py +230 -0
  241. autocoder/common/save_formatted_log.py +67 -10
  242. autocoder/common/search_replace.py +8 -1
  243. autocoder/common/search_replace_patch/__init__.py +24 -0
  244. autocoder/common/search_replace_patch/base.py +115 -0
  245. autocoder/common/search_replace_patch/manager.py +248 -0
  246. autocoder/common/search_replace_patch/patch_replacer.py +304 -0
  247. autocoder/common/search_replace_patch/similarity_replacer.py +306 -0
  248. autocoder/common/search_replace_patch/string_replacer.py +181 -0
  249. autocoder/common/search_replace_patch/tests/__init__.py +3 -0
  250. autocoder/common/search_replace_patch/tests/run_tests.py +126 -0
  251. autocoder/common/search_replace_patch/tests/test_base.py +188 -0
  252. autocoder/common/search_replace_patch/tests/test_empty_line_insert.py +233 -0
  253. autocoder/common/search_replace_patch/tests/test_integration.py +389 -0
  254. autocoder/common/search_replace_patch/tests/test_manager.py +351 -0
  255. autocoder/common/search_replace_patch/tests/test_patch_replacer.py +316 -0
  256. autocoder/common/search_replace_patch/tests/test_regex_replacer.py +306 -0
  257. autocoder/common/search_replace_patch/tests/test_similarity_replacer.py +384 -0
  258. autocoder/common/shell_commands/__init__.py +197 -0
  259. autocoder/common/shell_commands/background_process_notifier.py +346 -0
  260. autocoder/common/shell_commands/command_executor.py +1127 -0
  261. autocoder/common/shell_commands/error_recovery.py +541 -0
  262. autocoder/common/shell_commands/exceptions.py +120 -0
  263. autocoder/common/shell_commands/interactive_executor.py +476 -0
  264. autocoder/common/shell_commands/interactive_pexpect_process.py +623 -0
  265. autocoder/common/shell_commands/interactive_process.py +744 -0
  266. autocoder/common/shell_commands/interactive_session_manager.py +1014 -0
  267. autocoder/common/shell_commands/monitoring.py +529 -0
  268. autocoder/common/shell_commands/process_cleanup.py +386 -0
  269. autocoder/common/shell_commands/process_manager.py +606 -0
  270. autocoder/common/shell_commands/test_interactive_pexpect_process.py +281 -0
  271. autocoder/common/shell_commands/tests/__init__.py +6 -0
  272. autocoder/common/shell_commands/tests/conftest.py +118 -0
  273. autocoder/common/shell_commands/tests/test_background_process_notifier.py +703 -0
  274. autocoder/common/shell_commands/tests/test_command_executor.py +448 -0
  275. autocoder/common/shell_commands/tests/test_error_recovery.py +305 -0
  276. autocoder/common/shell_commands/tests/test_exceptions.py +299 -0
  277. autocoder/common/shell_commands/tests/test_execute_batch.py +588 -0
  278. autocoder/common/shell_commands/tests/test_indented_batch_commands.py +244 -0
  279. autocoder/common/shell_commands/tests/test_integration.py +664 -0
  280. autocoder/common/shell_commands/tests/test_monitoring.py +546 -0
  281. autocoder/common/shell_commands/tests/test_performance.py +632 -0
  282. autocoder/common/shell_commands/tests/test_process_cleanup.py +397 -0
  283. autocoder/common/shell_commands/tests/test_process_manager.py +606 -0
  284. autocoder/common/shell_commands/tests/test_timeout_config.py +343 -0
  285. autocoder/common/shell_commands/tests/test_timeout_manager.py +520 -0
  286. autocoder/common/shell_commands/timeout_config.py +315 -0
  287. autocoder/common/shell_commands/timeout_manager.py +352 -0
  288. autocoder/common/terminal_paste/__init__.py +14 -0
  289. autocoder/common/terminal_paste/demo.py +145 -0
  290. autocoder/common/terminal_paste/demo_paste_functionality.py +95 -0
  291. autocoder/common/terminal_paste/paste_handler.py +200 -0
  292. autocoder/common/terminal_paste/paste_manager.py +118 -0
  293. autocoder/common/terminal_paste/tests/__init__.py +1 -0
  294. autocoder/common/terminal_paste/tests/test_paste_handler.py +182 -0
  295. autocoder/common/terminal_paste/tests/test_paste_manager.py +126 -0
  296. autocoder/common/terminal_paste/utils.py +163 -0
  297. autocoder/common/test_autocoder_args.py +232 -0
  298. autocoder/common/test_env_manager.py +173 -0
  299. autocoder/common/test_env_manager_integration.py +159 -0
  300. autocoder/common/text_similarity/__init__.py +9 -0
  301. autocoder/common/text_similarity/demo.py +216 -0
  302. autocoder/common/text_similarity/examples.py +266 -0
  303. autocoder/common/text_similarity/test_text_similarity.py +306 -0
  304. autocoder/common/text_similarity/text_similarity.py +194 -0
  305. autocoder/common/text_similarity/utils.py +125 -0
  306. autocoder/common/todos/__init__.py +61 -0
  307. autocoder/common/todos/cache/__init__.py +16 -0
  308. autocoder/common/todos/cache/base_cache.py +89 -0
  309. autocoder/common/todos/cache/cache_manager.py +228 -0
  310. autocoder/common/todos/cache/memory_cache.py +225 -0
  311. autocoder/common/todos/config.py +155 -0
  312. autocoder/common/todos/exceptions.py +35 -0
  313. autocoder/common/todos/get_todo_manager.py +161 -0
  314. autocoder/common/todos/manager.py +537 -0
  315. autocoder/common/todos/models.py +239 -0
  316. autocoder/common/todos/storage/__init__.py +14 -0
  317. autocoder/common/todos/storage/base_storage.py +76 -0
  318. autocoder/common/todos/storage/file_storage.py +278 -0
  319. autocoder/common/tokens/counter.py +24 -2
  320. autocoder/common/tools_manager/__init__.py +17 -0
  321. autocoder/common/tools_manager/examples.py +162 -0
  322. autocoder/common/tools_manager/manager.py +385 -0
  323. autocoder/common/tools_manager/models.py +39 -0
  324. autocoder/common/tools_manager/test_tools_manager.py +303 -0
  325. autocoder/common/tools_manager/utils.py +191 -0
  326. autocoder/common/v2/agent/agentic_callbacks.py +270 -0
  327. autocoder/common/v2/agent/agentic_edit.py +2699 -1856
  328. autocoder/common/v2/agent/agentic_edit_change_manager.py +474 -0
  329. autocoder/common/v2/agent/agentic_edit_tools/__init__.py +35 -1
  330. autocoder/common/v2/agent/agentic_edit_tools/ac_mod_list_tool_resolver.py +279 -0
  331. autocoder/common/v2/agent/agentic_edit_tools/ac_mod_write_tool_resolver.py +10 -1
  332. autocoder/common/v2/agent/agentic_edit_tools/background_task_tool_resolver.py +1167 -0
  333. autocoder/common/v2/agent/agentic_edit_tools/base_tool_resolver.py +2 -2
  334. autocoder/common/v2/agent/agentic_edit_tools/conversation_message_ids_read_tool_resolver.py +214 -0
  335. autocoder/common/v2/agent/agentic_edit_tools/conversation_message_ids_write_tool_resolver.py +299 -0
  336. autocoder/common/v2/agent/agentic_edit_tools/count_tokens_tool_resolver.py +290 -0
  337. autocoder/common/v2/agent/agentic_edit_tools/execute_command_tool_resolver.py +564 -29
  338. autocoder/common/v2/agent/agentic_edit_tools/execute_workflow_tool_resolver.py +485 -0
  339. autocoder/common/v2/agent/agentic_edit_tools/extract_to_text_tool_resolver.py +225 -0
  340. autocoder/common/v2/agent/agentic_edit_tools/lint_report.py +79 -0
  341. autocoder/common/v2/agent/agentic_edit_tools/linter_config_models.py +343 -0
  342. autocoder/common/v2/agent/agentic_edit_tools/linter_enabled_tool_resolver.py +189 -0
  343. autocoder/common/v2/agent/agentic_edit_tools/list_files_tool_resolver.py +169 -101
  344. autocoder/common/v2/agent/agentic_edit_tools/load_extra_document_tool_resolver.py +356 -0
  345. autocoder/common/v2/agent/agentic_edit_tools/read_file_tool_resolver.py +243 -50
  346. autocoder/common/v2/agent/agentic_edit_tools/replace_in_file_tool_resolver.py +667 -147
  347. autocoder/common/v2/agent/agentic_edit_tools/run_named_subagents_tool_resolver.py +691 -0
  348. autocoder/common/v2/agent/agentic_edit_tools/search_files_tool_resolver.py +410 -86
  349. autocoder/common/v2/agent/agentic_edit_tools/session_interactive_tool_resolver.py +115 -0
  350. autocoder/common/v2/agent/agentic_edit_tools/session_start_tool_resolver.py +190 -0
  351. autocoder/common/v2/agent/agentic_edit_tools/session_stop_tool_resolver.py +76 -0
  352. autocoder/common/v2/agent/agentic_edit_tools/test_write_to_file_tool_resolver.py +207 -192
  353. autocoder/common/v2/agent/agentic_edit_tools/todo_read_tool_resolver.py +80 -63
  354. autocoder/common/v2/agent/agentic_edit_tools/todo_write_tool_resolver.py +237 -233
  355. autocoder/common/v2/agent/agentic_edit_tools/use_mcp_tool_resolver.py +2 -2
  356. autocoder/common/v2/agent/agentic_edit_tools/web_crawl_tool_resolver.py +557 -0
  357. autocoder/common/v2/agent/agentic_edit_tools/web_search_tool_resolver.py +600 -0
  358. autocoder/common/v2/agent/agentic_edit_tools/write_to_file_tool_resolver.py +56 -121
  359. autocoder/common/v2/agent/agentic_edit_types.py +343 -9
  360. autocoder/common/v2/agent/runner/__init__.py +3 -3
  361. autocoder/common/v2/agent/runner/base_runner.py +12 -26
  362. autocoder/common/v2/agent/runner/{event_runner.py → file_based_event_runner.py} +3 -2
  363. autocoder/common/v2/agent/runner/sdk_runner.py +150 -8
  364. autocoder/common/v2/agent/runner/terminal_runner.py +170 -57
  365. autocoder/common/v2/agent/runner/tool_display.py +557 -159
  366. autocoder/common/v2/agent/test_agentic_callbacks.py +265 -0
  367. autocoder/common/v2/agent/test_agentic_edit.py +194 -0
  368. autocoder/common/v2/agent/tool_caller/__init__.py +24 -0
  369. autocoder/common/v2/agent/tool_caller/default_tool_resolver_map.py +135 -0
  370. autocoder/common/v2/agent/tool_caller/integration_test.py +172 -0
  371. autocoder/common/v2/agent/tool_caller/plugins/__init__.py +14 -0
  372. autocoder/common/v2/agent/tool_caller/plugins/base_plugin.py +126 -0
  373. autocoder/common/v2/agent/tool_caller/plugins/examples/__init__.py +13 -0
  374. autocoder/common/v2/agent/tool_caller/plugins/examples/logging_plugin.py +164 -0
  375. autocoder/common/v2/agent/tool_caller/plugins/examples/security_filter_plugin.py +198 -0
  376. autocoder/common/v2/agent/tool_caller/plugins/plugin_interface.py +141 -0
  377. autocoder/common/v2/agent/tool_caller/test_tool_caller.py +278 -0
  378. autocoder/common/v2/agent/tool_caller/tool_call_plugin_manager.py +331 -0
  379. autocoder/common/v2/agent/tool_caller/tool_caller.py +337 -0
  380. autocoder/common/v2/agent/tool_caller/usage_example.py +193 -0
  381. autocoder/common/v2/code_agentic_editblock_manager.py +4 -4
  382. autocoder/common/v2/code_auto_generate.py +136 -78
  383. autocoder/common/v2/code_auto_generate_diff.py +135 -79
  384. autocoder/common/v2/code_auto_generate_editblock.py +174 -99
  385. autocoder/common/v2/code_auto_generate_strict_diff.py +151 -71
  386. autocoder/common/v2/code_auto_merge.py +1 -1
  387. autocoder/common/v2/code_auto_merge_editblock.py +13 -1
  388. autocoder/common/v2/code_diff_manager.py +3 -3
  389. autocoder/common/v2/code_editblock_manager.py +4 -14
  390. autocoder/common/v2/code_manager.py +1 -1
  391. autocoder/common/v2/code_strict_diff_manager.py +2 -2
  392. autocoder/common/wrap_llm_hint/__init__.py +10 -0
  393. autocoder/common/wrap_llm_hint/test_wrap_llm_hint.py +1067 -0
  394. autocoder/common/wrap_llm_hint/utils.py +432 -0
  395. autocoder/common/wrap_llm_hint/wrap_llm_hint.py +323 -0
  396. autocoder/completer/__init__.py +8 -0
  397. autocoder/completer/command_completer_v2.py +1094 -0
  398. autocoder/default_project/__init__.py +501 -0
  399. autocoder/dispacher/__init__.py +4 -12
  400. autocoder/dispacher/actions/action.py +400 -129
  401. autocoder/dispacher/actions/plugins/action_regex_project.py +2 -2
  402. autocoder/index/entry.py +117 -125
  403. autocoder/{agent → index/filter}/agentic_filter.py +322 -333
  404. autocoder/index/filter/normal_filter.py +5 -11
  405. autocoder/index/filter/quick_filter.py +1 -1
  406. autocoder/index/index.py +36 -9
  407. autocoder/index/tests/__init__.py +1 -0
  408. autocoder/index/tests/run_tests.py +195 -0
  409. autocoder/index/tests/test_entry.py +303 -0
  410. autocoder/index/tests/test_index_manager.py +314 -0
  411. autocoder/index/tests/test_module_integration.py +300 -0
  412. autocoder/index/tests/test_symbols_utils.py +183 -0
  413. autocoder/inner/__init__.py +4 -0
  414. autocoder/inner/agentic.py +923 -0
  415. autocoder/inner/async_command_handler.py +992 -0
  416. autocoder/inner/conversation_command_handlers.py +623 -0
  417. autocoder/inner/merge_command_handler.py +213 -0
  418. autocoder/inner/queue_command_handler.py +684 -0
  419. autocoder/models.py +95 -266
  420. autocoder/plugins/git_helper_plugin.py +31 -29
  421. autocoder/plugins/token_helper_plugin.py +65 -46
  422. autocoder/pyproject/__init__.py +32 -29
  423. autocoder/rag/agentic_rag.py +215 -75
  424. autocoder/rag/cache/simple_cache.py +1 -2
  425. autocoder/rag/loaders/image_loader.py +1 -1
  426. autocoder/rag/long_context_rag.py +42 -26
  427. autocoder/rag/qa_conversation_strategy.py +1 -1
  428. autocoder/rag/terminal/__init__.py +17 -0
  429. autocoder/rag/terminal/args.py +581 -0
  430. autocoder/rag/terminal/bootstrap.py +61 -0
  431. autocoder/rag/terminal/command_handlers.py +653 -0
  432. autocoder/rag/terminal/formatters/__init__.py +20 -0
  433. autocoder/rag/terminal/formatters/base.py +70 -0
  434. autocoder/rag/terminal/formatters/json_format.py +66 -0
  435. autocoder/rag/terminal/formatters/stream_json.py +95 -0
  436. autocoder/rag/terminal/formatters/text.py +28 -0
  437. autocoder/rag/terminal/init.py +120 -0
  438. autocoder/rag/terminal/utils.py +106 -0
  439. autocoder/rag/test_agentic_rag.py +389 -0
  440. autocoder/rag/test_doc_filter.py +3 -3
  441. autocoder/rag/test_long_context_rag.py +1 -1
  442. autocoder/rag/test_token_limiter.py +517 -10
  443. autocoder/rag/token_counter.py +3 -0
  444. autocoder/rag/token_limiter.py +19 -15
  445. autocoder/rag/tools/__init__.py +26 -2
  446. autocoder/rag/tools/bochaai_example.py +343 -0
  447. autocoder/rag/tools/bochaai_sdk.py +541 -0
  448. autocoder/rag/tools/metaso_example.py +268 -0
  449. autocoder/rag/tools/metaso_sdk.py +417 -0
  450. autocoder/rag/tools/recall_tool.py +28 -7
  451. autocoder/rag/tools/run_integration_tests.py +204 -0
  452. autocoder/rag/tools/test_all_providers.py +318 -0
  453. autocoder/rag/tools/test_bochaai_integration.py +482 -0
  454. autocoder/rag/tools/test_final_integration.py +215 -0
  455. autocoder/rag/tools/test_metaso_integration.py +424 -0
  456. autocoder/rag/tools/test_metaso_real.py +171 -0
  457. autocoder/rag/tools/test_web_crawl_tool.py +639 -0
  458. autocoder/rag/tools/test_web_search_tool.py +509 -0
  459. autocoder/rag/tools/todo_read_tool.py +202 -0
  460. autocoder/rag/tools/todo_write_tool.py +412 -0
  461. autocoder/rag/tools/web_crawl_tool.py +634 -0
  462. autocoder/rag/tools/web_search_tool.py +558 -0
  463. autocoder/rag/tools/web_tools_example.py +119 -0
  464. autocoder/rag/types.py +16 -0
  465. autocoder/rag/variable_holder.py +4 -2
  466. autocoder/rags.py +86 -79
  467. autocoder/regexproject/__init__.py +23 -21
  468. autocoder/sdk/__init__.py +46 -190
  469. autocoder/sdk/api.py +370 -0
  470. autocoder/sdk/async_runner/__init__.py +26 -0
  471. autocoder/sdk/async_runner/async_executor.py +650 -0
  472. autocoder/sdk/async_runner/async_handler.py +356 -0
  473. autocoder/sdk/async_runner/markdown_processor.py +595 -0
  474. autocoder/sdk/async_runner/task_metadata.py +284 -0
  475. autocoder/sdk/async_runner/worktree_manager.py +438 -0
  476. autocoder/sdk/cli/__init__.py +2 -5
  477. autocoder/sdk/cli/formatters.py +28 -204
  478. autocoder/sdk/cli/handlers.py +77 -44
  479. autocoder/sdk/cli/main.py +154 -171
  480. autocoder/sdk/cli/options.py +95 -22
  481. autocoder/sdk/constants.py +139 -51
  482. autocoder/sdk/core/auto_coder_core.py +484 -109
  483. autocoder/sdk/core/bridge.py +297 -115
  484. autocoder/sdk/exceptions.py +18 -12
  485. autocoder/sdk/formatters/__init__.py +19 -0
  486. autocoder/sdk/formatters/input.py +64 -0
  487. autocoder/sdk/formatters/output.py +247 -0
  488. autocoder/sdk/formatters/stream.py +54 -0
  489. autocoder/sdk/models/__init__.py +6 -5
  490. autocoder/sdk/models/options.py +55 -18
  491. autocoder/sdk/utils/formatters.py +27 -195
  492. autocoder/suffixproject/__init__.py +28 -25
  493. autocoder/terminal/__init__.py +14 -0
  494. autocoder/terminal/app.py +454 -0
  495. autocoder/terminal/args.py +32 -0
  496. autocoder/terminal/bootstrap.py +178 -0
  497. autocoder/terminal/command_processor.py +521 -0
  498. autocoder/terminal/command_registry.py +57 -0
  499. autocoder/terminal/help.py +97 -0
  500. autocoder/terminal/tasks/__init__.py +5 -0
  501. autocoder/terminal/tasks/background.py +77 -0
  502. autocoder/terminal/tasks/task_event.py +70 -0
  503. autocoder/terminal/ui/__init__.py +13 -0
  504. autocoder/terminal/ui/completer.py +268 -0
  505. autocoder/terminal/ui/keybindings.py +75 -0
  506. autocoder/terminal/ui/session.py +41 -0
  507. autocoder/terminal/ui/toolbar.py +64 -0
  508. autocoder/terminal/utils/__init__.py +13 -0
  509. autocoder/terminal/utils/errors.py +18 -0
  510. autocoder/terminal/utils/paths.py +19 -0
  511. autocoder/terminal/utils/shell.py +43 -0
  512. autocoder/terminal_v3/__init__.py +10 -0
  513. autocoder/terminal_v3/app.py +201 -0
  514. autocoder/terminal_v3/handlers/__init__.py +5 -0
  515. autocoder/terminal_v3/handlers/command_handler.py +131 -0
  516. autocoder/terminal_v3/models/__init__.py +6 -0
  517. autocoder/terminal_v3/models/conversation_buffer.py +214 -0
  518. autocoder/terminal_v3/models/message.py +50 -0
  519. autocoder/terminal_v3/models/tool_display.py +247 -0
  520. autocoder/terminal_v3/ui/__init__.py +7 -0
  521. autocoder/terminal_v3/ui/keybindings.py +56 -0
  522. autocoder/terminal_v3/ui/layout.py +141 -0
  523. autocoder/terminal_v3/ui/styles.py +43 -0
  524. autocoder/tsproject/__init__.py +23 -23
  525. autocoder/utils/auto_coder_utils/chat_stream_out.py +1 -1
  526. autocoder/utils/llms.py +88 -80
  527. autocoder/utils/math_utils.py +101 -0
  528. autocoder/utils/model_provider_selector.py +16 -4
  529. autocoder/utils/operate_config_api.py +33 -5
  530. autocoder/utils/thread_utils.py +2 -2
  531. autocoder/version.py +4 -2
  532. autocoder/workflow_agents/__init__.py +84 -0
  533. autocoder/workflow_agents/agent.py +143 -0
  534. autocoder/workflow_agents/exceptions.py +573 -0
  535. autocoder/workflow_agents/executor.py +665 -0
  536. autocoder/workflow_agents/loader.py +749 -0
  537. autocoder/workflow_agents/runner.py +267 -0
  538. autocoder/workflow_agents/types.py +173 -0
  539. autocoder/workflow_agents/utils.py +434 -0
  540. autocoder/workflow_agents/workflow_manager.py +211 -0
  541. auto_coder-1.0.0.dist-info/METADATA +0 -396
  542. auto_coder-1.0.0.dist-info/RECORD +0 -442
  543. auto_coder-1.0.0.dist-info/licenses/LICENSE +0 -201
  544. autocoder/auto_coder_server.py +0 -672
  545. autocoder/benchmark.py +0 -138
  546. autocoder/common/ac_style_command_parser/example.py +0 -7
  547. autocoder/common/cleaner.py +0 -31
  548. autocoder/common/command_completer_v2.py +0 -615
  549. autocoder/common/context_pruner.py +0 -477
  550. autocoder/common/conversation_pruner.py +0 -132
  551. autocoder/common/directory_cache/__init__.py +0 -1
  552. autocoder/common/directory_cache/cache.py +0 -192
  553. autocoder/common/directory_cache/test_cache.py +0 -190
  554. autocoder/common/file_checkpoint/examples.py +0 -217
  555. autocoder/common/llm_friendly_package_example.py +0 -138
  556. autocoder/common/llm_friendly_package_test.py +0 -63
  557. autocoder/common/pull_requests/test_module.py +0 -1
  558. autocoder/common/rulefiles/autocoderrules_utils.py +0 -484
  559. autocoder/common/text.py +0 -30
  560. autocoder/common/v2/agent/agentic_edit_tools/list_package_info_tool_resolver.py +0 -42
  561. autocoder/common/v2/agent/agentic_edit_tools/test_execute_command_tool_resolver.py +0 -70
  562. autocoder/common/v2/agent/agentic_edit_tools/test_search_files_tool_resolver.py +0 -163
  563. autocoder/common/v2/agent/agentic_tool_display.py +0 -183
  564. autocoder/plugins/dynamic_completion_example.py +0 -148
  565. autocoder/plugins/sample_plugin.py +0 -160
  566. autocoder/sdk/cli/__main__.py +0 -26
  567. autocoder/sdk/cli/completion_wrapper.py +0 -38
  568. autocoder/sdk/cli/install_completion.py +0 -301
  569. autocoder/sdk/models/messages.py +0 -209
  570. autocoder/sdk/session/__init__.py +0 -32
  571. autocoder/sdk/session/session.py +0 -106
  572. autocoder/sdk/session/session_manager.py +0 -56
  573. {auto_coder-1.0.0.dist-info → auto_coder-2.0.1.dist-info}/top_level.txt +0 -0
  574. /autocoder/{sdk/example.py → common/agent_query_queue/__init__.py} +0 -0
@@ -1,127 +1,651 @@
1
1
  import os
2
- import re
3
2
  from typing import Dict, Any, Optional, List, Tuple
4
3
  import typing
5
4
  from autocoder.common import AutoCoderArgs
6
- from autocoder.common.v2.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
5
+ from autocoder.common.v2.agent.agentic_edit_tools.linter_enabled_tool_resolver import LinterEnabledToolResolver
7
6
  from autocoder.common.v2.agent.agentic_edit_types import ReplaceInFileTool, ToolResult
8
7
  from autocoder.common.file_checkpoint.models import FileChange as CheckpointFileChange
9
- from autocoder.common.file_checkpoint.manager import FileChangeManager as CheckpointFileChangeManager
10
- from autocoder.linters.models import IssueSeverity, FileLintResult
11
8
  from loguru import logger
12
9
  from autocoder.common.auto_coder_lang import get_message_with_format
10
+ from autocoder.common.text_similarity import TextSimilarity
11
+ from autocoder.common.search_replace_patch import SearchReplaceManager
12
+ from autocoder.common.wrap_llm_hint.utils import add_hint_to_text
13
+ import pydantic
14
+ from autocoder.common import files as FileUtils
15
+
13
16
  if typing.TYPE_CHECKING:
14
17
  from autocoder.common.v2.agent.agentic_edit import AgenticEdit
15
18
 
16
- class ReplaceInFileToolResolver(BaseToolResolver):
19
+ class PathAndCode(pydantic.BaseModel):
20
+ path: str
21
+ content: str
22
+
23
+ class ReplacementFailureBlockAnalysis(pydantic.BaseModel):
24
+ """Structured analysis for a single SEARCH block when replacement fails."""
25
+ search_preview: str
26
+ similarity: float
27
+ start_line: int
28
+ end_line: int
29
+ best_window_preview: str
30
+ hints: List[str] = []
31
+
32
+
33
+ class ReplacementFailureReport(pydantic.BaseModel):
34
+ """Structured, extensible failure feedback for replacement operations."""
35
+ reason: str = "replacement_failed"
36
+ file_path: Optional[str] = None
37
+ used_strategy: Optional[str] = None
38
+ tried_strategies: List[str] = []
39
+ suggestions: List[str] = []
40
+ blocks: List[ReplacementFailureBlockAnalysis] = []
41
+
42
+ class ReplaceInFileToolResolver(LinterEnabledToolResolver):
17
43
  def __init__(self, agent: Optional['AgenticEdit'], tool: ReplaceInFileTool, args: AutoCoderArgs):
18
44
  super().__init__(agent, tool, args)
19
45
  self.tool: ReplaceInFileTool = tool # For type hinting
20
46
  self.args = args
21
- self.shadow_manager = self.agent.shadow_manager if self.agent else None
22
- self.shadow_linter = self.agent.shadow_linter if self.agent else None
47
+
48
+ # 初始化智能替换管理器
49
+ self.search_replace_manager = SearchReplaceManager()
50
+
51
+ # Get fence parameters from tool
52
+ self.fence_0 = getattr(tool, 'fence_0', '```')
53
+ self.fence_1 = getattr(tool, 'fence_1', '```')
23
54
 
24
- def parse_diff(self, diff_content: str) -> List[Tuple[str, str]]:
25
- """
26
- Parses the diff content into a list of (search_block, replace_block) tuples.
55
+ # Markers used in SEARCH/REPLACE blocks
56
+ self.SEARCH_MARKER: str = "<<<<<<< SEARCH" # exact literal
57
+ self.DIVIDER_MARKER: str = "======="
58
+ self.REPLACE_MARKER: str = ">>>>>>> REPLACE"
59
+
60
+
61
+ def parse_search_replace_blocks(self, diff_content: str) -> List[Tuple[str, str]]:
62
+ """Parse diff content using configured markers into (search, replace) tuples.
63
+
64
+ Preserves original newlines within each block.
27
65
  """
28
- blocks = []
66
+ blocks: List[Tuple[str, str]] = []
29
67
  lines = diff_content.splitlines(keepends=True)
30
68
  i = 0
31
69
  n = len(lines)
32
70
 
33
71
  while i < n:
34
72
  line = lines[i]
35
- if line.strip() == "<<<<<<< SEARCH":
73
+ if line.strip() == self.SEARCH_MARKER:
36
74
  i += 1
37
- search_lines = []
38
- # Accumulate search block
39
- while i < n and lines[i].strip() != "=======":
75
+ search_lines: List[str] = []
76
+ while i < n and lines[i].strip() != self.DIVIDER_MARKER:
40
77
  search_lines.append(lines[i])
41
78
  i += 1
42
79
  if i >= n:
43
80
  logger.warning("Unterminated SEARCH block found in diff content.")
44
81
  break
45
- i += 1 # skip '======='
46
- replace_lines = []
47
- # Accumulate replace block
48
- while i < n and lines[i].strip() != ">>>>>>> REPLACE":
82
+ i += 1 # skip divider
83
+
84
+ replace_lines: List[str] = []
85
+ while i < n and lines[i].strip() != self.REPLACE_MARKER:
49
86
  replace_lines.append(lines[i])
50
87
  i += 1
51
88
  if i >= n:
52
89
  logger.warning("Unterminated REPLACE block found in diff content.")
53
90
  break
54
- i += 1 # skip '>>>>>>> REPLACE'
91
+ i += 1 # skip replace marker
55
92
 
56
- search_block = ''.join(search_lines)
57
- replace_block = ''.join(replace_lines)
58
- blocks.append((search_block, replace_block))
93
+ blocks.append((''.join(search_lines), ''.join(replace_lines)))
59
94
  else:
60
95
  i += 1
61
96
 
62
97
  if not blocks and diff_content.strip():
63
- logger.warning(f"Could not parse any SEARCH/REPLACE blocks from diff: {diff_content}")
98
+ logger.warning(
99
+ f"Could not parse any SEARCH/REPLACE blocks from diff (using markers): {diff_content}"
100
+ )
64
101
  return blocks
65
102
 
66
- def _filter_lint_issues(self, lint_result:FileLintResult, levels: List[IssueSeverity] = [IssueSeverity.ERROR, IssueSeverity.WARNING]):
103
+ def _find_line_numbers(self, content: str, text_block: str) -> Tuple[int, int]:
67
104
  """
68
- 过滤 lint 结果,只保留指定级别的问题
105
+ Find the line numbers for a given text block in the content.
69
106
 
70
- 参数:
71
- lint_result: 单个文件的 lint 结果对象
72
- levels: 要保留的问题级别列表,默认保留 ERROR WARNING 级别
107
+ Args:
108
+ content: The full file content
109
+ text_block: The text block to find
73
110
 
74
- 返回:
75
- 过滤后的 lint 结果对象(原对象的副本)
111
+ Returns:
112
+ Tuple of (start_line, end_line) numbers (1-indexed)
113
+ """
114
+ block_lines = text_block.splitlines()
115
+ block_start_idx = content.find(text_block)
116
+ if block_start_idx == -1:
117
+ return (-1, -1)
118
+ lines_before = content[:block_start_idx].count('\n')
119
+ start_line = lines_before + 1
120
+ lines_in_block = len(block_lines)
121
+ end_line = start_line + lines_in_block - 1
122
+ return (start_line, end_line)
123
+
124
+ def _intelligent_replace(self, content: str, search_blocks: List[Tuple[str, str]]) -> Tuple[bool, str, List[str]]:
76
125
  """
77
- if not lint_result or not lint_result.issues:
78
- return lint_result
79
-
80
- # 创建一个新的 issues 列表,只包含指定级别的问题
81
- filtered_issues = []
82
- for issue in lint_result.issues:
83
- if issue.severity in levels:
84
- filtered_issues.append(issue)
85
-
86
- # 更新 lint_result 的副本
87
- filtered_result = lint_result
88
- filtered_result.issues = filtered_issues
89
-
90
- # 更新计数
91
- filtered_result.error_count = sum(1 for issue in filtered_result.issues if issue.severity == IssueSeverity.ERROR)
92
- filtered_result.warning_count = sum(1 for issue in filtered_result.issues if issue.severity == IssueSeverity.WARNING)
93
- filtered_result.info_count = sum(1 for issue in filtered_result.issues if issue.severity == IssueSeverity.INFO)
94
-
95
- return filtered_result
96
-
97
- def _format_lint_issues(self, lint_result:FileLintResult):
126
+ 使用智能替换策略进行文本替换(兼容旧签名)。
127
+
128
+ 新的结构化失败报告通过 _apply_replacements_with_fallback 提供;
129
+ 本方法保持返回 (success, new_content, errors) 以兼容旧调用点。
98
130
  """
99
- lint 结果格式化为可读的文本格式
100
-
101
- 参数:
102
- lint_result: 单个文件的 lint 结果对象
103
-
104
- 返回:
105
- str: 格式化的问题描述
131
+ success, new_content, errors, _ = self._apply_replacements_with_fallback(content, search_blocks)
132
+ return success, new_content, errors
133
+
134
+ def _apply_replacements_with_fallback(
135
+ self,
136
+ content: str,
137
+ search_blocks: List[Tuple[str, str]],
138
+ file_path: Optional[str] = None,
139
+ ) -> Tuple[bool, str, List[str], Optional[ReplacementFailureReport]]:
140
+ """Apply replacements using the advanced fallback strategy.
141
+
142
+ Returns (success, new_content, error_messages, failure_report).
106
143
  """
107
- formatted_issues = []
144
+ if not self.search_replace_manager:
145
+ return False, content, ["Advanced text replacement system is not available"], None
146
+
147
+ try:
148
+ result = self.search_replace_manager.replace_with_fallback(content, search_blocks)
149
+
150
+ if result.success:
151
+ logger.info(
152
+ f"Intelligent replacement succeeded using {result.metadata.get('used_strategy', 'unknown')} strategy"
153
+ )
154
+ return True, (result.new_content or content), [], None
155
+
156
+ # Build structured, extensible feedback
157
+ failure_report = self._build_failure_report(content, search_blocks, result, file_path)
158
+ error_message = self._format_failure_message(failure_report)
159
+ logger.warning(f"Intelligent replacement failed: {error_message}")
160
+ return False, content, [error_message], failure_report
161
+
162
+ except Exception as e:
163
+ logger.error(f"Error in intelligent replacement: {e}")
164
+ return False, content, [f"System error during text replacement: {str(e)}"], None
165
+
166
+ def _build_failure_report(
167
+ self,
168
+ content: str,
169
+ search_blocks: List[Tuple[str, str]],
170
+ result: Any,
171
+ file_path: Optional[str] = None,
172
+ ) -> ReplacementFailureReport:
173
+ blocks_analysis: List[ReplacementFailureBlockAnalysis] = []
174
+ for search_text, _ in search_blocks:
175
+ analysis = self._analyze_search_block_similarity(content, search_text)
176
+ blocks_analysis.append(analysis)
177
+
178
+ return ReplacementFailureReport(
179
+ file_path=file_path,
180
+ used_strategy=result.metadata.get("used_strategy"),
181
+ tried_strategies=result.metadata.get("tried_strategies", []),
182
+ suggestions=self._generate_common_suggestions(content),
183
+ blocks=blocks_analysis,
184
+ )
185
+
186
+ def _format_failure_message(self, report: ReplacementFailureReport) -> str:
187
+ """Format a human-friendly failure message while keeping details in the report.
108
188
 
109
- for issue in lint_result.issues:
110
- severity = "错误" if issue.severity.value == 3 else "警告" if issue.severity.value == 2 else "信息"
111
- line_info = f"第{issue.position.line}行"
112
- if issue.position.column:
113
- line_info += f", 第{issue.position.column}列"
189
+ Args:
190
+ report: ReplacementFailureReport containing structured failure information
114
191
 
115
- formatted_issues.append(
116
- f" - [{severity}] {line_info}: {issue.message} (规则: {issue.code})"
192
+ Returns:
193
+ str: A formatted error message with detailed analysis and suggestions
194
+
195
+ Example output for single file with file_path:
196
+ ```
197
+ Text replacement failed in file 'src/example.py' after trying multiple strategies.
198
+ ---
199
+ [[Block 1 Analysis:
200
+ 🔍 Search text: 'def old_function():'
201
+ 📏 Best match similarity: 75.0%
202
+ 📍 Best match lines: 42-45
203
+ 📝 Actual content: 'def old_function_name():'
204
+ ⚠️ Moderate similarity — check whitespace, indentation, or line endings
205
+
206
+ 📋 Strategies attempted: string, similarity, patch
207
+
208
+ 💡 Suggested actions:
209
+ • File uses LF (\\n) — ensure SEARCH blocks match exact line endings
210
+ • Use read_file tool to examine exact content around the target location
211
+ • Consider searching for a smaller, more unique fragment first]]
212
+ ```
213
+
214
+ Example output without file_path:
215
+ ```
216
+ Text replacement failed after trying multiple strategies.
217
+ ---
218
+ [[Block 1 Analysis:
219
+ 🔍 Search text: 'import os'
220
+ 📏 Best match similarity: 0.0%
221
+ 📍 Best match lines: 1-0
222
+ 📝 Actual content: ''
223
+ ❌ No similar content found — verify the target location and file
224
+
225
+ 📋 Strategies attempted: string, similarity, patch
226
+
227
+ 💡 Suggested actions:
228
+ • Use read_file tool to examine exact content around the target location
229
+ • Consider searching for a smaller, more unique fragment first]]
230
+ ```
231
+
232
+ Example output with multiple blocks:
233
+ ```
234
+ Text replacement failed in file 'utils.py' after trying multiple strategies.
235
+ ---
236
+ [[Block 1 Analysis:
237
+ 🔍 Search text: 'class Helper:'
238
+ 📏 Best match similarity: 85.0%
239
+ 📍 Best match lines: 10-12
240
+ 📝 Actual content: 'class HelperClass:'
241
+ ✅ High similarity — try using the exact content above as SEARCH block
242
+
243
+ Block 2 Analysis:
244
+ 🔍 Search text: 'def process():'
245
+ 📏 Best match similarity: 60.0%
246
+ 📍 Best match lines: 25-27
247
+ 📝 Actual content: 'def process_data():'
248
+ ⚠️ Moderate similarity — check whitespace, indentation, or line endings
249
+
250
+ 📋 Strategies attempted: string, similarity, patch
251
+
252
+ 💡 Suggested actions:
253
+ • File uses LF (\\n) — ensure SEARCH blocks match exact line endings
254
+ • Use read_file tool to examine exact content around the target location
255
+ • Consider searching for a smaller, more unique fragment first]]
256
+ ```
257
+ """
258
+ if report.file_path:
259
+ base_error = f"Text replacement failed in file '{report.file_path}' after trying multiple strategies."
260
+ else:
261
+ base_error = "Text replacement failed after trying multiple strategies."
262
+
263
+ analysis_parts: List[str] = []
264
+ for idx, blk in enumerate(report.blocks, 1):
265
+ analysis = [
266
+ f"🔍 Search text: {repr(blk.search_preview)}",
267
+ f"📏 Best match similarity: {blk.similarity:.1%}",
268
+ ]
269
+ if blk.start_line != -1:
270
+ analysis.append(f"📍 Best match lines: {blk.start_line}-{blk.end_line}")
271
+ analysis.append(f"📝 Actual content: {repr(blk.best_window_preview)}")
272
+ if blk.hints:
273
+ analysis.extend(blk.hints)
274
+ analysis_parts.append(f"Block {idx} Analysis:\n" + "\n".join(analysis))
275
+
276
+ if report.tried_strategies:
277
+ analysis_parts.append(
278
+ f"📋 Strategies attempted: {', '.join(report.tried_strategies)}"
117
279
  )
118
-
119
- return "\n".join(formatted_issues)
280
+ if report.suggestions:
281
+ analysis_parts.append("💡 Suggested actions:\n" + "\n".join(report.suggestions))
282
+
283
+ detailed_analysis = "\n\n".join(analysis_parts)
284
+ return add_hint_to_text(base_error, detailed_analysis)
285
+
286
+ def _analyze_search_block_similarity(
287
+ self, content: str, search_text: str
288
+ ) -> ReplacementFailureBlockAnalysis:
289
+ """Analyze how well a SEARCH block matches the file content and produce hints."""
290
+ try:
291
+ similarity_finder = TextSimilarity(search_text, content)
292
+ similarity, best_window = similarity_finder.get_best_matching_window()
293
+ start_line, end_line = self._find_line_numbers(content, best_window)
294
+
295
+ hints: List[str] = []
296
+ if similarity > 0.8:
297
+ hints.append("✅ High similarity — try using the exact content above as SEARCH block")
298
+ elif similarity > 0.5:
299
+ hints.append(
300
+ "⚠️ Moderate similarity — check whitespace, indentation, or line endings"
301
+ )
302
+ else:
303
+ hints.append(
304
+ "❌ No similar content found — verify the target location and file"
305
+ )
306
+
307
+ return ReplacementFailureBlockAnalysis(
308
+ search_preview=(search_text[:50] + "..." if len(search_text) > 50 else search_text),
309
+ similarity=float(similarity),
310
+ start_line=start_line,
311
+ end_line=end_line,
312
+ best_window_preview=(
313
+ best_window[:100] + "..." if len(best_window) > 100 else best_window
314
+ ),
315
+ hints=hints,
316
+ )
317
+ except Exception as e:
318
+ return ReplacementFailureBlockAnalysis(
319
+ search_preview=(search_text[:50] + "..." if len(search_text) > 50 else search_text),
320
+ similarity=0.0,
321
+ start_line=-1,
322
+ end_line=-1,
323
+ best_window_preview=f"<analysis error: {str(e)}>",
324
+ hints=["❌ Error analyzing block"],
325
+ )
326
+
327
+ def _generate_common_suggestions(self, content: str) -> List[str]:
328
+ """Generate general suggestions to help the LLM fix mismatches."""
329
+ suggestions: List[str] = []
330
+
331
+ has_crlf = '\r\n' in content
332
+ has_lf = '\n' in content and '\r\n' not in content
333
+ if has_crlf or has_lf:
334
+ line_ending = "CRLF (\\r\\n)" if has_crlf else "LF (\\n)"
335
+ suggestions.append(
336
+ f"• File uses {line_ending} — ensure SEARCH blocks match exact line endings"
337
+ )
338
+
339
+ content_lines = content.splitlines()
340
+ has_trailing_spaces = any(
341
+ line.endswith(' ') or line.endswith('\t') for line in content_lines
342
+ )
343
+ if has_trailing_spaces:
344
+ suggestions.append(
345
+ "• File contains trailing whitespace — include exact spacing in SEARCH blocks"
346
+ )
347
+
348
+ suggestions.append(
349
+ "• Use read_file tool to examine exact content around the target location"
350
+ )
351
+ suggestions.append(
352
+ "• Consider searching for a smaller, more unique fragment first"
353
+ )
354
+
355
+ return suggestions
356
+
357
+
120
358
 
359
+
360
+ def parse_whole_text(self, text: str) -> List[PathAndCode]:
361
+ '''
362
+ 从文本中抽取如下格式代码(two_line_mode):
363
+
364
+ ```python
365
+ ##File: /project/path/src/autocoder/index/index.py
366
+ <<<<<<< SEARCH
367
+ =======
368
+ >>>>>>> REPLACE
369
+ ```
370
+
371
+ 或者 (one_line_mode)
372
+
373
+ ```python:/project/path/src/autocoder/index/index.py
374
+ <<<<<<< SEARCH
375
+ =======
376
+ >>>>>>> REPLACE
377
+ ```
378
+
379
+ '''
380
+ HEAD = self.SEARCH_MARKER
381
+ DIVIDER = self.DIVIDER_MARKER
382
+ UPDATED = self.REPLACE_MARKER
383
+ lines = text.split("\n")
384
+ lines_len = len(lines)
385
+ start_marker_count = 0
386
+ block = []
387
+ path_and_code_list = []
388
+ # two_line_mode or one_line_mode
389
+ current_editblock_mode = "two_line_mode"
390
+ current_editblock_path = None
391
+
392
+ def guard(index):
393
+ return index + 1 < lines_len
394
+
395
+ def start_marker(line, index):
396
+ nonlocal current_editblock_mode
397
+ nonlocal current_editblock_path
398
+ if (
399
+ line.startswith(self.fence_0)
400
+ and guard(index)
401
+ and ":" in line
402
+ and lines[index + 1].startswith(HEAD)
403
+ ):
404
+
405
+ current_editblock_mode = "one_line_mode"
406
+ current_editblock_path = line.split(":", 1)[1].strip()
407
+ return True
408
+
409
+ if (
410
+ line.startswith(self.fence_0)
411
+ and guard(index)
412
+ and lines[index + 1].startswith("##File:")
413
+ ):
414
+ current_editblock_mode = "two_line_mode"
415
+ current_editblock_path = None
416
+ return True
417
+
418
+ return False
419
+
420
+ def end_marker(line, index):
421
+ return line.startswith(self.fence_1) and UPDATED in lines[index - 1]
422
+
423
+ for index, line in enumerate(lines):
424
+ if start_marker(line, index) and start_marker_count == 0:
425
+ start_marker_count += 1
426
+ elif end_marker(line, index) and start_marker_count == 1:
427
+ start_marker_count -= 1
428
+ if block:
429
+ if current_editblock_mode == "two_line_mode":
430
+ path = block[0].split(":", 1)[1].strip()
431
+ content = "\n".join(block[1:])
432
+ else:
433
+ path = current_editblock_path
434
+ content = "\n".join(block)
435
+ block = []
436
+ path_and_code_list.append(
437
+ PathAndCode(path=path, content=content))
438
+ elif start_marker_count > 0:
439
+ block.append(line)
440
+
441
+ return path_and_code_list
442
+
443
+ def get_edits(self, content: str):
444
+ edits = self.parse_whole_text(content)
445
+ HEAD = self.SEARCH_MARKER
446
+ DIVIDER = self.DIVIDER_MARKER
447
+ UPDATED = self.REPLACE_MARKER
448
+ result = []
449
+ for edit in edits:
450
+ heads = []
451
+ updates = []
452
+ c = edit.content
453
+ in_head = False
454
+ in_updated = False
455
+ # 使用 splitlines(keepends=True) 来保留换行符信息
456
+ lines = c.splitlines(keepends=True)
457
+ for line in lines:
458
+ if line.strip() == HEAD:
459
+ in_head = True
460
+ continue
461
+ if line.strip() == DIVIDER:
462
+ in_head = False
463
+ in_updated = True
464
+ continue
465
+ if line.strip() == UPDATED:
466
+ in_head = False
467
+ in_updated = False
468
+ continue
469
+ if in_head:
470
+ heads.append(line)
471
+ if in_updated:
472
+ updates.append(line)
473
+
474
+ # 直接拼接,保留原始的换行符
475
+ head_content = "".join(heads)
476
+ update_content = "".join(updates)
477
+
478
+ # 去掉可能的末尾换行符以避免重复
479
+ if head_content.endswith('\n'):
480
+ head_content = head_content[:-1]
481
+ if update_content.endswith('\n'):
482
+ update_content = update_content[:-1]
483
+
484
+ result.append((edit.path, head_content, update_content))
485
+ return result
121
486
 
487
+ def replace_in_multiple_files(self, diff_content: str, source_dir: str, abs_project_dir: str) -> ToolResult:
488
+ """Replace content in multiple files when path is '*'.
489
+
490
+ Enhanced with structured failure feedback in result.content when any block fails.
491
+ """
492
+ try:
493
+ # 使用新的解析方法解析多文件格式
494
+ codes = self.get_edits(diff_content)
495
+ if not codes:
496
+ return ToolResult(success=False, message="No valid edit blocks found in diff content")
497
+
498
+ file_content_mapping: Dict[str, str] = {}
499
+ failed_blocks: List[Tuple[str, str, str]] = []
500
+ errors: List[str] = []
501
+ failed_details_by_file: Dict[str, Any] = {}
502
+
503
+ # 按文件分组处理块
504
+ file_blocks_map: Dict[str, List[Tuple[str, str]]] = {}
505
+ for block in codes:
506
+ file_path, head, update = block
507
+ abs_file_path = os.path.abspath(os.path.join(source_dir, file_path))
508
+
509
+ # Security check
510
+ if not abs_file_path.startswith(abs_project_dir):
511
+ errors.append(f"Access denied to file: {file_path}")
512
+ continue
513
+
514
+ if file_path not in file_blocks_map:
515
+ file_blocks_map[file_path] = []
516
+ file_blocks_map[file_path].append((head, update))
517
+
518
+ # 对每个文件使用智能替换策略
519
+ for file_path, blocks in file_blocks_map.items():
520
+ abs_file_path = os.path.abspath(os.path.join(source_dir, file_path))
521
+
522
+ if not os.path.exists(abs_file_path):
523
+ # New file - 对于新文件,直接使用所有更新块的内容
524
+ new_content = "\n".join([update for head, update in blocks if update])
525
+ file_content_mapping[file_path] = new_content
526
+ else:
527
+ # 读取现有文件内容
528
+ existing_content = FileUtils.read_file(abs_file_path)
529
+
530
+ # 使用智能替换策略(与 normal 方法保持一致)
531
+ logger.info(f"Using intelligent replacement for file: {file_path}")
532
+ success, new_content, intelligent_errors = self._intelligent_replace(existing_content, blocks)
533
+
534
+ if success:
535
+ file_content_mapping[file_path] = new_content
536
+ logger.info(f"Intelligent replacement succeeded for {len(blocks)} blocks in {file_path}")
537
+ else:
538
+ logger.warning(f"Intelligent replacement failed for {file_path}: {intelligent_errors}")
539
+ # Build structured details for each failed file
540
+ try:
541
+ # reuse new failure report via internal method
542
+ _, _, _, failure_report = self._apply_replacements_with_fallback(existing_content, blocks, file_path)
543
+ if failure_report is not None:
544
+ failed_details_by_file[file_path] = failure_report.dict()
545
+ except Exception:
546
+ pass
547
+ errors.extend([f"{file_path}: {err}" for err in intelligent_errors])
548
+ failed_blocks.extend([(file_path, head, update) for head, update in blocks])
549
+
550
+ if failed_blocks:
551
+ total_blocks = sum(len(blocks) for blocks in file_blocks_map.values())
552
+ failed_count = len(failed_blocks)
553
+ message = (
554
+ f"Failed to apply {failed_count}/{total_blocks} blocks across multiple files. "
555
+ f"See content.failed_files for details."
556
+ )
557
+ content_details: Dict[str, Any] = {
558
+ "failed_files": failed_details_by_file,
559
+ "total_blocks": total_blocks,
560
+ "failed_blocks": failed_count,
561
+ "errors": errors,
562
+ }
563
+ return ToolResult(success=False, message=message, content=content_details)
564
+
565
+ # Apply changes to files
566
+ changed_files = []
567
+ for file_path, new_content in file_content_mapping.items():
568
+ abs_file_path = os.path.abspath(os.path.join(source_dir, file_path))
569
+ os.makedirs(os.path.dirname(abs_file_path), exist_ok=True)
570
+
571
+ # Handle checkpoint manager if available
572
+ if self.agent and self.agent.checkpoint_manager:
573
+ changes = {
574
+ file_path: CheckpointFileChange(
575
+ file_path=file_path,
576
+ content=new_content,
577
+ is_deletion=False,
578
+ is_new=not os.path.exists(abs_file_path)
579
+ )
580
+ }
581
+ change_group_id = self.args.event_file
582
+
583
+ conversation_id = self.agent.conversation_config.conversation_id if self.agent else None
584
+ logger.debug(f"多文件对话检查点调试 - conversation_config存在: {self.agent.conversation_config is not None}, conversation_id: {conversation_id}")
585
+
586
+ if conversation_id:
587
+ first_message_id, last_message_id = self.agent.get_conversation_message_range()
588
+ logger.debug(f"多文件获取消息范围 - first_message_id: {first_message_id}, last_message_id: {last_message_id}")
589
+
590
+ self.agent.checkpoint_manager.apply_changes_with_conversation(
591
+ changes=changes,
592
+ conversation_id=conversation_id,
593
+ first_message_id=first_message_id,
594
+ last_message_id=last_message_id,
595
+ change_group_id=change_group_id,
596
+ metadata={"event_file": self.args.event_file}
597
+ )
598
+ logger.debug(f"多文件已调用 apply_changes_with_conversation")
599
+ else:
600
+ logger.warning(f"多文件conversation_id 为 None,跳过对话检查点保存")
601
+ else:
602
+ with open(abs_file_path, 'w', encoding='utf-8') as f:
603
+ f.write(new_content)
604
+
605
+ changed_files.append(file_path)
606
+
607
+ # Record file change for AgenticEdit
608
+ if self.agent:
609
+ rel_path = os.path.relpath(abs_file_path, abs_project_dir)
610
+ self.agent.record_file_change(rel_path, "modified", content=new_content)
611
+
612
+ # 计算统计信息(与 normal 方法保持一致)
613
+ total_blocks = sum(len(blocks) for blocks in file_blocks_map.values())
614
+ applied_blocks = total_blocks - len(failed_blocks)
615
+
616
+ # 构建成功消息(与 normal 方法保持一致)
617
+ if errors:
618
+ success_message = f"Successfully applied {applied_blocks}/{total_blocks} blocks across {len(changed_files)} files: {', '.join(changed_files)}. Warnings: {'; '.join(errors)}"
619
+ else:
620
+ success_message = f"Successfully applied {applied_blocks}/{total_blocks} blocks across {len(changed_files)} files: {', '.join(changed_files)}"
621
+
622
+ # Run linter check if enabled for multiple files
623
+ result = ToolResult(success=True, message=success_message)
624
+ if self.linter_config and self.linter_config.enabled and self.linter_config.check_after_modification:
625
+ # Collect all modified files for linting
626
+ files_to_lint = []
627
+ for file_path in changed_files:
628
+ if self.should_lint(file_path):
629
+ abs_path = os.path.abspath(os.path.join(source_dir, file_path))
630
+ files_to_lint.append(abs_path)
631
+
632
+ if files_to_lint:
633
+ logger.info(f"Running linter check on {len(files_to_lint)} modified files")
634
+ lint_report = self.lint_files(files_to_lint)
635
+ if lint_report:
636
+ result = self.handle_lint_results(result, lint_report)
637
+
638
+ return result
639
+
640
+ except Exception as e:
641
+ logger.error(f"Error in multiple file replacement: {str(e)}")
642
+ return ToolResult(success=False, message=f"Error processing multiple file replacement: {str(e)}")
122
643
 
123
644
  def replace_in_file_normal(self, file_path: str, diff_content: str, source_dir: str, abs_project_dir: str, abs_file_path: str) -> ToolResult:
124
- """Replace content in file directly without using shadow manager"""
645
+ """Replace content in file directly without using shadow manager.
646
+
647
+ Adds structured failure details to result.content when no blocks applied.
648
+ """
125
649
  try:
126
650
  # Read original content
127
651
  if not os.path.exists(abs_file_path):
@@ -132,7 +656,7 @@ class ReplaceInFileToolResolver(BaseToolResolver):
132
656
  with open(abs_file_path, 'r', encoding='utf-8', errors='replace') as f:
133
657
  original_content = f.read()
134
658
 
135
- parsed_blocks = self.parse_diff(diff_content)
659
+ parsed_blocks = self.parse_search_replace_blocks(diff_content)
136
660
  if not parsed_blocks:
137
661
  return ToolResult(success=False, message=get_message_with_format("replace_in_file.no_valid_blocks"))
138
662
 
@@ -140,24 +664,27 @@ class ReplaceInFileToolResolver(BaseToolResolver):
140
664
  applied_count = 0
141
665
  errors = []
142
666
 
143
- # Apply blocks sequentially
144
- for i, (search_block, replace_block) in enumerate(parsed_blocks):
145
- start_index = current_content.find(search_block)
146
-
147
- if start_index != -1:
148
- current_content = current_content[:start_index] + replace_block + current_content[start_index + len(search_block):]
149
- applied_count += 1
150
- logger.info(f"Applied SEARCH/REPLACE block {i+1} in file {file_path}")
151
- else:
152
- error_message = f"SEARCH block {i+1} not found in the current file content. Content to search:\n---\n{search_block}\n---"
153
- logger.warning(error_message)
154
- context_start = max(0, original_content.find(search_block[:20]) - 100)
155
- context_end = min(len(original_content), context_start + 200 + len(search_block[:20]))
156
- logger.warning(f"Approximate context in file:\n---\n{original_content[context_start:context_end]}\n---")
157
- errors.append(error_message)
667
+ # 使用智能替换
668
+ logger.info("Using intelligent replacement with multiple strategies")
669
+ success, new_content, intelligent_errors = self._intelligent_replace(current_content, parsed_blocks)
670
+
671
+ if success:
672
+ current_content = new_content
673
+ applied_count = len(parsed_blocks)
674
+ logger.info(f"Intelligent replacement succeeded for all {applied_count} blocks")
675
+ else:
676
+ logger.warning(f"Intelligent replacement failed: {intelligent_errors}")
677
+ errors.extend(intelligent_errors)
158
678
 
159
679
  if applied_count == 0 and errors:
160
- return ToolResult(success=False, message=get_message_with_format("replace_in_file.apply_failed", errors="\n".join(errors)))
680
+ # Provide structured report
681
+ _, _, _, failure_report = self._apply_replacements_with_fallback(original_content, parsed_blocks, file_path)
682
+ content_details = failure_report.dict() if failure_report else None
683
+ return ToolResult(
684
+ success=False,
685
+ message=get_message_with_format("replace_in_file.apply_failed", errors="\n\n".join(errors)),
686
+ content=content_details,
687
+ )
161
688
 
162
689
  # Write the modified content back to file
163
690
  if self.agent and self.agent.checkpoint_manager:
@@ -166,93 +693,86 @@ class ReplaceInFileToolResolver(BaseToolResolver):
166
693
  file_path=file_path,
167
694
  content=current_content,
168
695
  is_deletion=False,
169
- is_new=True
696
+ is_new=False
170
697
  )
171
698
  }
172
699
  change_group_id = self.args.event_file
173
700
 
174
- self.agent.checkpoint_manager.apply_changes_with_conversation(
175
- changes=changes,
176
- conversations=self.agent.current_conversations,
177
- change_group_id=change_group_id,
178
- metadata={"event_file": self.args.event_file}
179
- )
701
+ conversation_id = self.agent.conversation_config.conversation_id if self.agent else None
702
+ logger.debug(f"对话检查点调试 - conversation_config存在: {self.agent.conversation_config is not None}, conversation_id: {conversation_id}")
703
+
704
+ if conversation_id:
705
+ first_message_id, last_message_id = self.agent.get_conversation_message_range()
706
+ logger.debug(f"获取消息范围 - first_message_id: {first_message_id}, last_message_id: {last_message_id}")
707
+
708
+ self.agent.checkpoint_manager.apply_changes_with_conversation(
709
+ changes=changes,
710
+ conversation_id=conversation_id,
711
+ first_message_id=first_message_id,
712
+ last_message_id=last_message_id,
713
+ change_group_id=change_group_id,
714
+ metadata={"event_file": self.args.event_file}
715
+ )
716
+ logger.debug(f"已调用 apply_changes_with_conversation")
717
+ else:
718
+ logger.warning(f"conversation_id 为 None,跳过对话检查点保存")
180
719
  else:
181
720
  with open(abs_file_path, 'w', encoding='utf-8') as f:
182
721
  f.write(current_content)
183
722
 
184
723
  logger.info(f"Successfully applied {applied_count}/{len(parsed_blocks)} changes to file: {file_path}")
185
724
 
186
- # 新增:执行代码质量检查
187
- lint_results = None
188
- lint_message = ""
189
- formatted_issues = ""
190
- has_lint_issues = False
191
-
192
- # 检查是否启用了Lint功能
193
- enable_lint = self.args.enable_auto_fix_lint
194
- logger.info(f"检查Lint功能状态: enable_lint={enable_lint}")
195
-
196
- if enable_lint:
197
- try:
198
- if self.agent.linter:
199
- lint_results = self.agent.linter.lint_file(file_path)
200
- if lint_results and lint_results.issues:
201
- # 过滤 lint 结果,只保留 ERROR 和 WARNING 级别的问题
202
- filtered_results = self._filter_lint_issues(lint_results)
203
- if filtered_results.issues:
204
- has_lint_issues = True
205
- # 格式化 lint 问题
206
- formatted_issues = self._format_lint_issues(filtered_results)
207
- lint_message = f"\n\n代码质量检查发现 {len(filtered_results.issues)} 个问题"
208
- except Exception as e:
209
- logger.error(f"Lint 检查失败: {str(e)}")
210
- lint_message = "\n\n尝试进行代码质量检查时出错。"
211
- else:
212
- logger.info("代码质量检查已禁用")
213
-
214
- # 构建包含 lint 结果的返回消息
725
+ # 构建成功消息
215
726
  if errors:
216
- message = get_message_with_format("replace_in_file.apply_success_with_warnings",
217
- applied=applied_count,
218
- total=len(parsed_blocks),
219
- file_path=file_path,
220
- errors="\n".join(errors))
727
+ final_message = get_message_with_format("replace_in_file.apply_success_with_warnings",
728
+ applied=applied_count,
729
+ total=len(parsed_blocks),
730
+ file_path=file_path,
731
+ errors="\n".join(errors))
221
732
  else:
222
- message = get_message_with_format("replace_in_file.apply_success",
223
- applied=applied_count,
224
- total=len(parsed_blocks),
225
- file_path=file_path)
733
+ final_message = get_message_with_format("replace_in_file.apply_success",
734
+ applied=applied_count,
735
+ total=len(parsed_blocks),
736
+ file_path=file_path)
226
737
 
227
738
  # 变更跟踪,回调AgenticEdit
228
739
  if self.agent:
229
740
  rel_path = os.path.relpath(abs_file_path, abs_project_dir)
230
741
  self.agent.record_file_change(rel_path, "modified", diff=diff_content, content=current_content)
231
-
232
- # 附加 lint 结果到返回内容
233
- result_content = {
234
- "content": current_content,
235
- }
236
-
237
- # 只有在启用Lint时才添加Lint结果
238
- if enable_lint:
239
- message = message + "\n" + lint_message
240
- result_content["lint_results"] = {
241
- "has_issues": has_lint_issues,
242
- "issues": formatted_issues if has_lint_issues else None
243
- }
244
742
 
245
- return ToolResult(success=True, message=message, content=result_content)
743
+ # Run linter check if enabled
744
+ result = ToolResult(success=True, message=final_message)
745
+ if self.should_lint(file_path) and self.linter_config and self.linter_config.check_after_modification:
746
+ logger.info(f"Running linter check on modified file: {file_path}")
747
+ lint_report = self.lint_files([abs_file_path])
748
+ if lint_report:
749
+ result = self.handle_lint_results(result, lint_report)
750
+
751
+ return result
246
752
  except Exception as e:
247
753
  logger.error(f"Error writing replaced content to file '{file_path}': {str(e)}")
248
754
  return ToolResult(success=False, message=get_message_with_format("replace_in_file.write_error", error=str(e)))
249
755
 
250
756
  def resolve(self) -> ToolResult:
251
757
  """Resolve the replace in file tool by calling the appropriate implementation"""
758
+ # Check if we are in plan mode
759
+ if self.args.agentic_mode == "plan":
760
+ return ToolResult(
761
+ success=False,
762
+ message="Currently in plan mode, modification tools are disabled. "
763
+ )
764
+
252
765
  file_path = self.tool.path
253
- diff_content = self.tool.diff
766
+ diff_content = self.tool.diff.strip()
254
767
  source_dir = self.args.source_dir or "."
255
768
  abs_project_dir = os.path.abspath(source_dir)
769
+
770
+ # Check if this is multiple file mode (path="*")
771
+ if file_path == "*":
772
+ logger.info("Multiple file replacement mode detected")
773
+ return self.replace_in_multiple_files(diff_content, source_dir, abs_project_dir)
774
+
775
+ # Single file mode
256
776
  abs_file_path = os.path.abspath(os.path.join(source_dir, file_path))
257
777
 
258
778
  # Security check