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
@@ -0,0 +1,1094 @@
1
+ import os
2
+ import shlex
3
+ from typing import Callable, Dict, Any, List, Iterable, Optional
4
+
5
+ import pydantic
6
+ from pydantic import BaseModel, SkipValidation
7
+ from prompt_toolkit.completion import Completer, Completion, CompleteEvent
8
+ from prompt_toolkit.document import Document
9
+
10
+ from autocoder.common import AutoCoderArgs
11
+
12
+ # Only need MemoryConfig now
13
+ from autocoder.common.command_completer import MemoryConfig
14
+ from autocoder.common.llms import LLMManager
15
+ from autocoder.common.command_file_manager import CommandManager
16
+
17
+ # Define command structure in a more structured way if needed,
18
+ # but primarily rely on handlers for logic.
19
+ COMMAND_HIERARCHY = {
20
+ "/add_files": {
21
+ "/group": {"/add": {}, "/drop": {}, "/reset": {}, "/set": {}},
22
+ "/refresh": {},
23
+ },
24
+ "/remove_files": {"/all": {}},
25
+ # Added list/get for clarity
26
+ "/conf": {"/drop": {}, "/export": {}, "/import": {}, "/get": {}},
27
+ "/coding": {"/apply": {}, "/next": {}},
28
+ "/chat": {
29
+ "/new": {},
30
+ "/save": {},
31
+ "/copy": {},
32
+ "/mcp": {},
33
+ "/rag": {},
34
+ "/review": {},
35
+ "/learn": {},
36
+ "/no_context": {},
37
+ },
38
+ "/mcp": {
39
+ "/add": {},
40
+ "/remove": {},
41
+ "/list": {},
42
+ "/list_running": {},
43
+ "/refresh": {},
44
+ "/info": {},
45
+ },
46
+ "/lib": {
47
+ "/add": {},
48
+ "/remove": {},
49
+ "/list": {},
50
+ "/set-proxy": {},
51
+ "/refresh": {},
52
+ "/get": {},
53
+ },
54
+ "/models": {
55
+ "/chat": {},
56
+ "/add_provider": {},
57
+ "/remove": {},
58
+ "/list": {},
59
+ "/check": {},
60
+ "/speed": {},
61
+ "/speed-test": {},
62
+ "/input_price": {},
63
+ "/output_price": {},
64
+ },
65
+ "/auto": {
66
+ "/new": {},
67
+ "/resume": {},
68
+ "/list": {},
69
+ "/rename": {},
70
+ "/command": {},
71
+ "/async": {
72
+ "/model": {},
73
+ "/list": {},
74
+ "/task": {},
75
+ "/kill": {},
76
+ "/effect": {},
77
+ "/name": {},
78
+ "/prefix": {},
79
+ "/libs": {},
80
+ },
81
+ "/queue": {
82
+ "/add": {},
83
+ "/list": {},
84
+ "/remove": {},
85
+ "/start": {},
86
+ "/stop": {},
87
+ "/stats": {},
88
+ "/clear": {},
89
+ "/status": {},
90
+ },
91
+ },
92
+ "/shell": {"/chat": {}},
93
+ "/active_context": {"/list": {}, "/run": {}},
94
+ "/index": {"/query": {}, "/build": {}, "/export": {}, "/import": {}},
95
+ "/exclude_files": {"/list": {}, "/drop": {}},
96
+ "/exclude_dirs": {}, # No specific subcommands shown in V1, treat as simple list
97
+ "/commit": {}, # No specific subcommands shown in V1
98
+ "/revert": {},
99
+ "/ask": {},
100
+ "/design": {"/svg": {}, "/sd": {}, "/logo": {}},
101
+ "/summon": {},
102
+ "/mode": {}, # Simple value completion
103
+ "/voice_input": {},
104
+ "/exit": {},
105
+ "/help": {},
106
+ "/list_files": {},
107
+ "/clear": {},
108
+ "/cls": {},
109
+ "/debug": {},
110
+ "/rules": {
111
+ "/list": {},
112
+ "/get": {},
113
+ "/remove": {},
114
+ "/analyze": {},
115
+ "/commit": {},
116
+ "/help": {},
117
+ "/init": {},
118
+ },
119
+ "/workflow": {},
120
+ }
121
+
122
+
123
+ class CommandCompleterV2(Completer):
124
+ """
125
+ A more extensible command completer using a handler-based approach.
126
+ """
127
+
128
+ def __init__(
129
+ self,
130
+ commands: List[str],
131
+ memory_model: MemoryConfig,
132
+ project_root: Optional[str] = None,
133
+ ):
134
+ self.base_commands = commands # Top-level commands starting with /
135
+ self.memory_model = memory_model
136
+
137
+ # Initialize project_scanner
138
+ from autocoder.common.project_scanner import ProjectScanner
139
+
140
+ self.project_root = project_root or os.getcwd()
141
+ self.scanner = ProjectScanner(
142
+ project_root=self.project_root,
143
+ default_exclude_dirs=[
144
+ ".git",
145
+ "node_modules",
146
+ "dist",
147
+ "build",
148
+ "__pycache__",
149
+ ".auto-coder",
150
+ ],
151
+ )
152
+
153
+ # Set extra exclude dirs if available
154
+ if hasattr(memory_model, "get_memory_func"):
155
+ memory = memory_model.get_memory_func()
156
+ extra_exclude_dirs = memory.get("exclude_dirs", [])
157
+ self.scanner.update_extra_exclude_dirs(extra_exclude_dirs)
158
+
159
+ # Data stores, initialized and refreshable
160
+ self.all_file_names: List[str] = []
161
+ self.all_files: List[str] = []
162
+ # Store relative paths with ./ prefix
163
+ self.all_file_rel_paths: List[str] = []
164
+ self.all_dir_names: List[str] = []
165
+ # Store directory relative paths with ./ prefix
166
+ self.all_dir_rel_paths: List[str] = []
167
+ self.all_files_with_dot: List[str] = []
168
+ # Use Any for SymbolItem structure from runner
169
+ self.symbol_list: List[Any] = []
170
+ self.current_file_names: List[str] = []
171
+ # Current files with ./ prefix
172
+ self.current_file_rel_paths: List[str] = []
173
+ self.config_keys = list(AutoCoderArgs.model_fields.keys())
174
+ self.group_names: List[str] = []
175
+ self.lib_names: List[str] = []
176
+ self.model_names: List[str] = [] # Assuming models can be fetched
177
+
178
+ self.refresh_files() # Initial data load
179
+ self._update_dynamic_data() # Load groups, libs etc.
180
+
181
+ # Initialize CommandManager and command files cache
182
+ commands_dir = os.path.join(self.project_root, ".autocodercommands")
183
+ self.command_manager = CommandManager(commands_dir)
184
+ self.command_files_cache: List[str] = []
185
+ self.command_files_cache_timestamp = 0
186
+ self._update_command_files_cache()
187
+
188
+ # Map command prefixes or patterns to handler methods
189
+ self.command_handlers: Dict[str, Callable] = {
190
+ "/": self._handle_base_command,
191
+ "/add_files": self._handle_add_files,
192
+ "/remove_files": self._handle_remove_files,
193
+ "/exclude_dirs": self._handle_exclude_dirs,
194
+ "/exclude_files": self._handle_exclude_files,
195
+ "/conf": self._handle_conf,
196
+ "/lib": self._handle_lib,
197
+ "/mcp": self._handle_mcp,
198
+ "/models": self._handle_models,
199
+ "/active_context": self._handle_active_context,
200
+ "/mode": self._handle_mode,
201
+ "/chat": self._handle_text_with_symbols,
202
+ "/coding": self._handle_text_with_symbols,
203
+ "/auto": self._handle_auto,
204
+ "/ask": self._handle_text_with_symbols, # Treat like chat for @/@@
205
+ "/summon": self._handle_text_with_symbols,
206
+ "/design": self._handle_design,
207
+ "/rules": self._handle_rules,
208
+ "/workflow": self._handle_workflow,
209
+ # Add handlers for other commands if they need specific logic beyond @/@@
210
+ # Default handler for plain text or commands not explicitly handled
211
+ "default": self._handle_text_with_symbols,
212
+ }
213
+
214
+ def _update_dynamic_data(self):
215
+ """Load or update data that changes during runtime (groups, libs, current files)."""
216
+ self.current_file_names = (
217
+ self.memory_model.get_memory_func()
218
+ .get("current_files", {})
219
+ .get("files", [])
220
+ )
221
+
222
+ # Generate relative paths with ./ prefix for current files
223
+ self.current_file_rel_paths = []
224
+ for f in self.current_file_names:
225
+ rel_path = os.path.relpath(f, self.project_root)
226
+ if not rel_path.startswith("."):
227
+ rel_path = f"./{rel_path}"
228
+ self.current_file_rel_paths.append(rel_path)
229
+
230
+ self.group_names = list(
231
+ self.memory_model.get_memory_func()
232
+ .get("current_files", {})
233
+ .get("groups", {})
234
+ .keys()
235
+ )
236
+ self.lib_names = list(
237
+ self.memory_model.get_memory_func().get("libs", {}).keys()
238
+ )
239
+ # In a real scenario, might fetch model names from models_module
240
+ try:
241
+ llm_manager = LLMManager()
242
+ self.model_names = list(llm_manager.get_all_models().keys())
243
+ except ImportError:
244
+ self.model_names = [] # Fallback if models module not available
245
+
246
+ # Update scanner's extra exclude dirs if changed
247
+ memory = self.memory_model.get_memory_func()
248
+ extra_exclude_dirs = memory.get("exclude_dirs", [])
249
+ self.scanner.update_extra_exclude_dirs(extra_exclude_dirs)
250
+
251
+ def refresh_files(self):
252
+ """Refresh file and symbol lists from the project scanner."""
253
+ self.all_file_names = self.scanner.get_all_file_names()
254
+ self.all_files = self.scanner.get_all_file_paths()
255
+ self.all_dir_names = self.scanner.get_all_dir_paths()
256
+ self.symbol_list = self.scanner.get_symbol_list()
257
+
258
+ # Generate all_files_with_dot (relative paths starting with ./)
259
+ self.all_files_with_dot = []
260
+ for f in self.all_files:
261
+ rel_path = os.path.relpath(f, self.project_root)
262
+ if not rel_path.startswith("."):
263
+ rel_path = f"./{rel_path}"
264
+ self.all_files_with_dot.append(rel_path)
265
+
266
+ # Generate relative paths with ./ prefix
267
+ self.all_file_rel_paths = []
268
+ for f in self.all_files:
269
+ rel_path = os.path.relpath(f, self.project_root)
270
+ if not rel_path.startswith("."):
271
+ rel_path = f"./{rel_path}"
272
+ self.all_file_rel_paths.append(rel_path)
273
+
274
+ self.all_dir_rel_paths = []
275
+ for d in self.all_dir_names:
276
+ rel_path = os.path.relpath(d, self.project_root)
277
+ if not rel_path.startswith("."):
278
+ rel_path = f"./{rel_path}"
279
+ self.all_dir_rel_paths.append(rel_path)
280
+
281
+ self._update_dynamic_data() # Also refresh dynamic data
282
+ self._update_command_files_cache() # Also refresh command files cache
283
+
284
+ def _update_command_files_cache(self):
285
+ """更新命令文件缓存"""
286
+ try:
287
+ # 检查目录是否存在
288
+ if not os.path.exists(self.command_manager.commands_dir):
289
+ self.command_files_cache = []
290
+ return
291
+
292
+ # 获取目录修改时间
293
+ dir_mtime = os.path.getmtime(self.command_manager.commands_dir)
294
+
295
+ # 如果缓存过期或为空,重新加载
296
+ if (
297
+ dir_mtime > self.command_files_cache_timestamp
298
+ or not self.command_files_cache
299
+ ):
300
+ result = self.command_manager.list_command_files(recursive=True)
301
+ if result.success:
302
+ self.command_files_cache = result.command_files
303
+ self.command_files_cache_timestamp = dir_mtime
304
+ else:
305
+ self.command_files_cache = []
306
+ except Exception:
307
+ # 如果出错,清空缓存
308
+ self.command_files_cache = []
309
+
310
+ # --- Main Completion Logic ---
311
+
312
+ def get_completions(
313
+ self, document: Document, complete_event: CompleteEvent
314
+ ) -> Iterable[Completion]:
315
+ text = document.text_before_cursor
316
+ word_before_cursor = document.get_word_before_cursor(WORD=True)
317
+
318
+ # Update dynamic data on each completion request
319
+ self._update_dynamic_data()
320
+
321
+ if not text.strip(): # Empty input
322
+ yield from self._handle_base_command(
323
+ document, complete_event, word_before_cursor, text
324
+ )
325
+ return
326
+
327
+ parts = text.split(maxsplit=1)
328
+ first_word = parts[0]
329
+
330
+ # 1. Handle Base Command Completion (e.g., typing "/")
331
+ if first_word.startswith("/") and len(parts) == 1 and not text.endswith(" "):
332
+ yield from self._handle_base_command(
333
+ document, complete_event, word_before_cursor, text
334
+ )
335
+
336
+ # 2. Dispatch to Specific Command Handlers
337
+ elif first_word in self.command_handlers:
338
+ handler = self.command_handlers[first_word]
339
+ yield from handler(document, complete_event, word_before_cursor, text)
340
+
341
+ # 3. Handle Special Prefixes within general text or unhandled commands
342
+ elif word_before_cursor.startswith("@") and not word_before_cursor.startswith(
343
+ "@@"
344
+ ):
345
+ yield from self._handle_at_completion(
346
+ document, complete_event, word_before_cursor, text
347
+ )
348
+ elif word_before_cursor.startswith("@@"):
349
+ yield from self._handle_double_at_completion(
350
+ document, complete_event, word_before_cursor, text
351
+ )
352
+ elif word_before_cursor.startswith("<"): # Potential tag completion
353
+ yield from self._handle_img_tag(
354
+ document, complete_event, word_before_cursor, text
355
+ )
356
+
357
+ # 4. Default Handler (for plain text or commands without specific handlers)
358
+ else:
359
+ handler = self.command_handlers.get("default")
360
+ if handler:
361
+ yield from handler(document, complete_event, word_before_cursor, text)
362
+
363
+ # --- Handler Methods ---
364
+
365
+ def _handle_base_command(
366
+ self, document: Document, complete_event: CompleteEvent, word: str, text: str
367
+ ) -> Iterable[Completion]:
368
+ """Handles completion for top-level commands starting with '/'."""
369
+ command_prefix = text.lstrip() # The word being typed
370
+ for cmd in self.base_commands:
371
+ if cmd.startswith(command_prefix):
372
+ yield Completion(cmd, start_position=-len(command_prefix))
373
+
374
+ def _handle_add_files(
375
+ self, document: Document, complete_event: CompleteEvent, word: str, text: str
376
+ ) -> Iterable[Completion]:
377
+ """Handles completions for /add_files command."""
378
+ args_text = text[len("/add_files") :].lstrip()
379
+ parts = args_text.split()
380
+ last_part = parts[-1] if parts and not text.endswith(" ") else ""
381
+
382
+ # Sub-command completion
383
+ if not args_text or (len(parts) == 1 and not text.endswith(" ")):
384
+ for sub_cmd in COMMAND_HIERARCHY["/add_files"]:
385
+ if sub_cmd.startswith(last_part):
386
+ yield Completion(sub_cmd, start_position=-len(last_part))
387
+
388
+ # File/Group completion based on context
389
+ if args_text.startswith("/group"):
390
+ group_args_text = args_text[len("/group") :].lstrip()
391
+ group_parts = group_args_text.split()
392
+ group_last_part = (
393
+ group_parts[-1] if group_parts and not text.endswith(" ") else ""
394
+ )
395
+
396
+ # Complete subcommands of /group
397
+ if not group_args_text or (
398
+ len(group_parts) == 1 and not text.endswith(" ")
399
+ ):
400
+ for group_sub_cmd in COMMAND_HIERARCHY["/add_files"]["/group"]:
401
+ if group_sub_cmd.startswith(group_last_part):
402
+ yield Completion(
403
+ group_sub_cmd, start_position=-len(group_last_part)
404
+ )
405
+
406
+ # Complete group names for /drop or direct use
407
+ elif (
408
+ group_parts
409
+ and group_parts[0] in ["/drop", "/set"]
410
+ or len(group_parts) >= 1
411
+ and not group_parts[0].startswith("/")
412
+ ):
413
+ current_word_for_group = group_last_part
414
+ # Handle comma-separated group names
415
+ if "," in current_word_for_group:
416
+ current_word_for_group = current_word_for_group.split(",")[-1]
417
+
418
+ yield from self._complete_items(
419
+ current_word_for_group, self.group_names
420
+ )
421
+
422
+ elif args_text.startswith("/refresh"):
423
+ pass # No further completion needed
424
+
425
+ # Default: File path completion
426
+ else:
427
+ yield from self._complete_file_paths(word, text)
428
+
429
+ def _handle_remove_files(
430
+ self, document: Document, complete_event: CompleteEvent, word: str, text: str
431
+ ) -> Iterable[Completion]:
432
+ """Handles completions for /remove_files command."""
433
+ # 'word' is document.get_word_before_cursor(WORD=True)
434
+
435
+ # Complete /all subcommand
436
+ if "/all".startswith(word):
437
+ yield Completion("/all", start_position=-len(word))
438
+
439
+ # Complete from current file paths with ./ prefix
440
+ yield from self._complete_items_with_in(word, self.current_file_rel_paths)
441
+
442
+ # Also complete from just the base filenames (but with full paths)
443
+ for rel_path in self.current_file_rel_paths:
444
+ basename = os.path.basename(rel_path)
445
+ if word in basename and word not in rel_path:
446
+ # Show full path when matching by basename
447
+ yield Completion(rel_path, start_position=-len(word))
448
+
449
+ def _handle_exclude_dirs(
450
+ self, document: Document, complete_event: CompleteEvent, word: str, text: str
451
+ ) -> Iterable[Completion]:
452
+ """Handles completions for /exclude_dirs command."""
453
+ args_text = text[len("/exclude_dirs") :].lstrip()
454
+ current_word = args_text.split(",")[-1].strip()
455
+ yield from self._complete_items(current_word, self.all_dir_names)
456
+
457
+ def _handle_exclude_files(
458
+ self, document: Document, complete_event: CompleteEvent, word: str, text: str
459
+ ) -> Iterable[Completion]:
460
+ """Handles completions for /exclude_files command."""
461
+ args_text = text[len("/exclude_files") :].lstrip()
462
+ parts = args_text.split()
463
+ last_part = parts[-1] if parts and not text.endswith(" ") else ""
464
+
465
+ if not args_text or (len(parts) == 1 and not text.endswith(" ")):
466
+ for sub_cmd in COMMAND_HIERARCHY["/exclude_files"]:
467
+ if sub_cmd.startswith(last_part):
468
+ yield Completion(sub_cmd, start_position=-len(last_part))
469
+
470
+ elif parts and parts[0] == "/drop":
471
+ current_word = last_part
472
+ yield from self._complete_items(
473
+ current_word,
474
+ self.memory_model.get_memory_func().get("exclude_files", []),
475
+ )
476
+ else:
477
+ # Suggest prefix for regex
478
+ if not last_part:
479
+ yield Completion("regex://", start_position=0)
480
+ elif "regex://".startswith(last_part):
481
+ yield Completion("regex://", start_position=-len(last_part))
482
+
483
+ def _handle_conf(
484
+ self, document: Document, complete_event: CompleteEvent, word: str, text: str
485
+ ) -> Iterable[Completion]:
486
+ """Handles completions for /conf command."""
487
+ args_text = text[len("/conf") :].lstrip()
488
+ parts = args_text.split()
489
+ last_part = parts[-1] if parts and not text.endswith(" ") else ""
490
+ # Complete subcommands like /drop, /export, /import, /list, /get
491
+ if not args_text or (
492
+ len(parts) == 1 and not text.endswith(" ") and ":" not in text
493
+ ):
494
+ for sub_cmd in COMMAND_HIERARCHY["/conf"]:
495
+ if sub_cmd.startswith(last_part):
496
+ yield Completion(sub_cmd, start_position=-len(last_part))
497
+ # Also complete config keys directly
498
+ yield from self._complete_config_keys(last_part, add_colon=False)
499
+
500
+ # Complete config keys after /drop or /get
501
+ elif parts and parts[0] in ["/drop", "/get"]:
502
+ yield from self._complete_config_keys(last_part, add_colon=False)
503
+
504
+ # Complete file paths after /export or /import
505
+ elif parts and parts[0] in ["/export", "/import"]:
506
+ # Use word here as it's likely the path
507
+ yield from self._complete_file_paths(word, text)
508
+
509
+ # Complete config keys for setting (key:value)
510
+ elif ":" not in last_part:
511
+ yield from self._complete_config_keys(last_part, add_colon=True)
512
+
513
+ # Complete values after colon
514
+ elif ":" in args_text:
515
+ key_part = args_text.split(":", 1)[0].strip()
516
+ value_part = args_text.split(":", 1)[1].strip() if ":" in args_text else ""
517
+ yield from self._complete_config_values(key_part, value_part)
518
+ # Example: Complete enum values or suggest file paths for path-like keys
519
+ pass # Placeholder for future value completions
520
+
521
+ def _complete_config_values(self, key: str, value: str) -> Iterable[Completion]:
522
+ """Helper to complete configuration values based on the key."""
523
+ start_pos = -len(value)
524
+
525
+ # Model name completion for keys containing "model"
526
+ if key.endswith("_model") or key == "model":
527
+ # Refresh model names if they can change dynamically
528
+ # self.refresh_model_names()
529
+ for model_name in self.model_names:
530
+ if model_name.startswith(value) or value == ":":
531
+ yield Completion(model_name, start_position=start_pos)
532
+ # If a model name matched, we might prioritize these completions.
533
+ # Consider returning here if model names are the only relevant values.
534
+
535
+ # Boolean value completion
536
+ field_info = AutoCoderArgs.model_fields.get(key)
537
+ if field_info and field_info.annotation == bool:
538
+ if "true".startswith(value):
539
+ yield Completion("true", start_position=start_pos)
540
+ if "false".startswith(value):
541
+ yield Completion("false", start_position=start_pos)
542
+ # If boolean matched, we might prioritize these completions.
543
+ # Consider returning here if boolean is the only relevant value type.
544
+
545
+ # Add more value completions based on key type or name here
546
+ # e.g., enums, file paths, specific string formats
547
+
548
+ def _handle_lib(
549
+ self, document: Document, complete_event: CompleteEvent, word: str, text: str
550
+ ) -> Iterable[Completion]:
551
+ """Handles completions for /lib command."""
552
+ args_text = text[len("/lib") :].lstrip()
553
+ parts = args_text.split()
554
+ last_part = parts[-1] if parts and not text.endswith(" ") else ""
555
+
556
+ # Complete subcommands
557
+ if not args_text or (len(parts) == 1 and not text.endswith(" ")):
558
+ for sub_cmd in COMMAND_HIERARCHY["/lib"]:
559
+ if sub_cmd.startswith(last_part):
560
+ yield Completion(sub_cmd, start_position=-len(last_part))
561
+
562
+ # Complete lib names for add/remove/get
563
+ elif parts and parts[0] in ["/add", "/remove", "/get"]:
564
+ yield from self._complete_items(last_part, self.lib_names)
565
+
566
+ # Complete proxy URL for set-proxy (less specific, maybe suggest http/https?)
567
+ elif parts and parts[0] == "/set-proxy":
568
+ if "http://".startswith(last_part):
569
+ yield Completion("http://", start_position=-len(last_part))
570
+ if "https://".startswith(last_part):
571
+ yield Completion("https://", start_position=-len(last_part))
572
+
573
+ def _handle_mcp(
574
+ self, document: Document, complete_event: CompleteEvent, word: str, text: str
575
+ ) -> Iterable[Completion]:
576
+ """Handles completions for /mcp command."""
577
+ args_text = text[len("/mcp") :].lstrip()
578
+ parts = args_text.split()
579
+ last_part = parts[-1] if parts and not text.endswith(" ") else ""
580
+
581
+ # Complete subcommands
582
+ if not args_text or (len(parts) == 1 and not text.endswith(" ")):
583
+ for sub_cmd in COMMAND_HIERARCHY["/mcp"]:
584
+ if sub_cmd.startswith(last_part):
585
+ yield Completion(sub_cmd, start_position=-len(last_part))
586
+ # Potentially complete server names after /remove, /refresh, /add if available
587
+
588
+ def _handle_models(
589
+ self, document: Document, complete_event: CompleteEvent, word: str, text: str
590
+ ) -> Iterable[Completion]:
591
+ """Handles completions for /models command."""
592
+ args_text = text[len("/models") :].lstrip()
593
+ parts = args_text.split()
594
+ last_part = parts[-1] if parts and not text.endswith(" ") else ""
595
+
596
+ # Complete subcommands
597
+ if not args_text or (len(parts) == 1 and not text.endswith(" ")):
598
+ for sub_cmd in COMMAND_HIERARCHY["/models"]:
599
+ if sub_cmd.startswith(last_part):
600
+ yield Completion(sub_cmd, start_position=-len(last_part))
601
+
602
+ # 如果没有子命令匹配,提示可以直接输入 provider/model_name
603
+ if not last_part.startswith("/"):
604
+ yield from self._complete_items(last_part, self.model_names)
605
+
606
+ # Complete model names for /remove/speed/input_price/output_price/chat
607
+ elif parts and parts[0] in [
608
+ "/remove",
609
+ "/speed",
610
+ "/input_price",
611
+ "/output_price",
612
+ "/chat",
613
+ ]:
614
+ yield from self._complete_items(last_part, self.model_names)
615
+
616
+ # Complete parameters for /add_provider (e.g., name=, base_url=)
617
+ elif parts and parts[0] == "/add_provider":
618
+ # Suggest common keys if the last part is empty or partially typed
619
+ common_keys = [
620
+ "name=",
621
+ "model_type=",
622
+ "model_name=",
623
+ "provider=",
624
+ "base_url=",
625
+ "api_key_path=",
626
+ "description=",
627
+ "is_reasoning=",
628
+ "input_price=",
629
+ "output_price=",
630
+ "context_window=",
631
+ "max_output_tokens=",
632
+ ]
633
+ yield from self._complete_items(last_part, common_keys)
634
+
635
+ elif parts and parts[0] == "/speed-test":
636
+ if "/long_context".startswith(last_part):
637
+ yield Completion("/long_context", start_position=-len(last_part))
638
+
639
+ # 如果第一个参数不是子命令,则是 provider/model_name 格式,补全模型名称
640
+ elif parts and not parts[0].startswith("/"):
641
+ # 用户正在输入 provider/model_name,补全第二个参数时不做提示
642
+ if len(parts) == 1 and not text.endswith(" "):
643
+ yield from self._complete_items(last_part, self.model_names)
644
+
645
+ def _handle_active_context(
646
+ self, document: Document, complete_event: CompleteEvent, word: str, text: str
647
+ ) -> Iterable[Completion]:
648
+ """Handles completions for /active_context command."""
649
+ args_text = text[len("/active_context") :].lstrip()
650
+ parts = args_text.split()
651
+ last_part = parts[-1] if parts and not text.endswith(" ") else ""
652
+
653
+ # Complete subcommands
654
+ if not args_text or (len(parts) == 1 and not text.endswith(" ")):
655
+ for sub_cmd in COMMAND_HIERARCHY["/active_context"]:
656
+ if sub_cmd.startswith(last_part):
657
+ yield Completion(sub_cmd, start_position=-len(last_part))
658
+
659
+ # Complete action file names for /run
660
+ elif parts and parts[0] == "/run":
661
+ # Assuming action files are in 'actions' dir and end with .yml
662
+ action_dir = "actions"
663
+ if os.path.isdir(action_dir):
664
+ try:
665
+ action_files = [
666
+ f for f in os.listdir(action_dir) if f.endswith(".yml")
667
+ ]
668
+ yield from self._complete_items(last_part, action_files)
669
+ except OSError:
670
+ pass # Ignore if cannot list dir
671
+
672
+ def _handle_mode(
673
+ self, document: Document, complete_event: CompleteEvent, word: str, text: str
674
+ ) -> Iterable[Completion]:
675
+ """Handles completions for /mode command."""
676
+ args_text = text[len("/mode") :].lstrip()
677
+ modes = ["normal", "auto_detect", "voice_input", "shell"]
678
+ yield from self._complete_items(args_text, modes)
679
+
680
+ def _handle_design(
681
+ self, document: Document, complete_event: CompleteEvent, word: str, text: str
682
+ ) -> Iterable[Completion]:
683
+ """Handles completions for /design command."""
684
+ args_text = text[len("/design") :].lstrip()
685
+ parts = args_text.split()
686
+ last_part = parts[-1] if parts and not text.endswith(" ") else ""
687
+
688
+ # Complete subcommands
689
+ if not args_text or (len(parts) == 1 and not text.endswith(" ")):
690
+ for sub_cmd in COMMAND_HIERARCHY["/design"]:
691
+ if sub_cmd.startswith(last_part):
692
+ yield Completion(sub_cmd, start_position=-len(last_part))
693
+
694
+ def _handle_auto(
695
+ self, document: Document, complete_event: CompleteEvent, word: str, text: str
696
+ ) -> Iterable[Completion]:
697
+ """Handles completions for /auto command."""
698
+ args_text = text[len("/auto") :].lstrip()
699
+ parts = args_text.split()
700
+ last_part = parts[-1] if parts and not text.endswith(" ") else ""
701
+
702
+ # Complete subcommands
703
+ if not args_text or (len(parts) == 1 and not text.endswith(" ")):
704
+ for sub_cmd in COMMAND_HIERARCHY["/auto"]:
705
+ if sub_cmd.startswith(last_part):
706
+ yield Completion(sub_cmd, start_position=-len(last_part))
707
+ return
708
+
709
+ # Handle /command subcommand
710
+ if args_text.startswith("/command"):
711
+ command_args_text = args_text[len("/command") :].lstrip()
712
+ # Update command files cache
713
+ self._update_command_files_cache()
714
+
715
+ # Complete command file paths
716
+ yield from self._complete_command_files(command_args_text)
717
+ return
718
+
719
+ # Handle @ and @@ symbols in other contexts
720
+ if word.startswith("@") and not word.startswith("@@"):
721
+ yield from self._handle_at_completion(document, complete_event, word, text)
722
+ elif word.startswith("@@"):
723
+ yield from self._handle_double_at_completion(
724
+ document, complete_event, word, text
725
+ )
726
+ elif word.startswith("<"):
727
+ yield from self._handle_img_tag(document, complete_event, word, text)
728
+
729
+ def _handle_text_with_symbols(
730
+ self, document: Document, complete_event: CompleteEvent, word: str, text: str
731
+ ) -> Iterable[Completion]:
732
+ """Handles general text input, including @, @@, <img> tags and command-specific subcommands."""
733
+ # Check for command-specific subcommands first
734
+ parts = text.split(maxsplit=1)
735
+ command = parts[0]
736
+ if command in COMMAND_HIERARCHY:
737
+ args_text = parts[1] if len(parts) > 1 else ""
738
+ sub_parts = args_text.split()
739
+ last_part = sub_parts[-1] if sub_parts and not text.endswith(" ") else ""
740
+
741
+ # Complete subcommands if applicable
742
+ if not args_text or (len(sub_parts) == 1 and not text.endswith(" ")):
743
+ if isinstance(COMMAND_HIERARCHY[command], dict):
744
+ for sub_cmd in COMMAND_HIERARCHY[command]:
745
+ if sub_cmd.startswith(last_part):
746
+ yield Completion(sub_cmd, start_position=-len(last_part))
747
+
748
+ # Now handle @, @@, <img> regardless of command (or if no command)
749
+ if word.startswith("@") and not word.startswith("@@"):
750
+ yield from self._handle_at_completion(document, complete_event, word, text)
751
+ elif word.startswith("@@"):
752
+ yield from self._handle_double_at_completion(
753
+ document, complete_event, word, text
754
+ )
755
+ elif word.startswith("<"): # Potential tag completion
756
+ yield from self._handle_img_tag(document, complete_event, word, text)
757
+
758
+ def _handle_workflow(
759
+ self, document: Document, complete_event: CompleteEvent, word: str, text: str
760
+ ) -> Iterable[Completion]:
761
+ """Handles completions for /workflow command."""
762
+ args_text = text[len("/workflow") :].lstrip()
763
+ parts = args_text.split()
764
+ last_part = parts[-1] if parts and not text.endswith(" ") else ""
765
+
766
+ # If no arguments or first argument, complete workflow names
767
+ if not args_text or (len(parts) == 1 and not text.endswith(" ")):
768
+ # List workflow files from .autocoderworkflow directory
769
+ workflow_dir = os.path.join(self.project_root, ".autocoderworkflow")
770
+ if os.path.isdir(workflow_dir):
771
+ try:
772
+ for f in os.listdir(workflow_dir):
773
+ if f.endswith(".yaml") or f.endswith(".yml"):
774
+ workflow_name = os.path.splitext(f)[0]
775
+ if workflow_name.startswith(last_part):
776
+ yield Completion(
777
+ workflow_name,
778
+ start_position=-len(last_part),
779
+ display=f"{workflow_name} (workflow)",
780
+ )
781
+ except OSError:
782
+ pass # Ignore errors listing directory
783
+
784
+ # If workflow name is specified, complete query= parameter
785
+ elif len(parts) >= 1 and not text.endswith(" "):
786
+ # Complete query= when typing "qu"
787
+ if "query=".startswith(last_part):
788
+ yield Completion("query=", start_position=-len(last_part))
789
+
790
+ # Support symbol completions within /workflow arguments
791
+ # Allow @ (file path) and @@ (symbols) just like in chat/coding
792
+ if word.startswith("@") and not word.startswith("@@"):
793
+ yield from self._handle_at_completion(document, complete_event, word, text)
794
+ elif word.startswith("@@"):
795
+ yield from self._handle_double_at_completion(
796
+ document, complete_event, word, text
797
+ )
798
+
799
+ def _handle_rules(
800
+ self, document: Document, complete_event: CompleteEvent, word: str, text: str
801
+ ) -> Iterable[Completion]:
802
+ """处理 /rules 命令的补全,支持子命令和规则文件路径。同时支持 @ 和 @@ 符号。"""
803
+ args_text = text[len("/rules") :].lstrip()
804
+ parts = args_text.split()
805
+ last_part = parts[-1] if parts and not text.endswith(" ") else ""
806
+
807
+ # 补全子命令
808
+ if not args_text or (
809
+ len(parts) == 1 and not text.endswith(" ") and parts[0].startswith("/")
810
+ ):
811
+ for sub_cmd in COMMAND_HIERARCHY["/rules"]:
812
+ if sub_cmd.startswith(last_part):
813
+ yield Completion(sub_cmd, start_position=-len(last_part))
814
+ return
815
+
816
+ # 根据子命令补全参数
817
+ if parts and parts[0] == "/list" or parts[0] == "/get" or parts[0] == "/remove":
818
+ # 获取规则文件或目录补全,可以是通配符
819
+ # 这里可以简单地提供文件路径补全
820
+ yield from self._complete_file_paths(last_part, text)
821
+ # 也可以添加常用通配符补全
822
+ common_patterns = ["*.md", "*.rules", "*.txt"]
823
+ for pattern in common_patterns:
824
+ if pattern.startswith(last_part):
825
+ yield Completion(pattern, start_position=-len(last_part))
826
+ return
827
+
828
+ # 对于 /commit 子命令,补全 /query
829
+ if parts and parts[0] == "/commit":
830
+ if "/query".startswith(last_part):
831
+ yield Completion("/query", start_position=-len(last_part))
832
+ return
833
+
834
+ # 支持 @ 和 @@ 符号的补全,不管当前命令是什么
835
+ if word.startswith("@") and not word.startswith("@@"):
836
+ yield from self._handle_at_completion(document, complete_event, word, text)
837
+ elif word.startswith("@@"):
838
+ yield from self._handle_double_at_completion(
839
+ document, complete_event, word, text
840
+ )
841
+
842
+ # --- Symbol/Tag Handlers ---
843
+
844
+ def _handle_at_completion(
845
+ self, document: Document, complete_event: CompleteEvent, word: str, text: str
846
+ ) -> Iterable[Completion]:
847
+ """Handles completion for single '@' (file paths)."""
848
+ name = word[1:]
849
+ yield from self._complete_file_paths(name, text, is_symbol=True)
850
+
851
+ def _handle_double_at_completion(
852
+ self, document: Document, complete_event: CompleteEvent, word: str, text: str
853
+ ) -> Iterable[Completion]:
854
+ """Handles completion for double '@@' (symbols)."""
855
+ name = word[2:]
856
+ yield from self._complete_symbols(name)
857
+
858
+ def _handle_img_tag(
859
+ self, document: Document, complete_event: CompleteEvent, word: str, text: str
860
+ ) -> Iterable[Completion]:
861
+ """Handles completion for <img> tags and paths within them."""
862
+ image_extensions = (
863
+ ".png",
864
+ ".jpg",
865
+ ".jpeg",
866
+ ".gif",
867
+ ".bmp",
868
+ ".tiff",
869
+ ".tif",
870
+ ".webp",
871
+ ".svg",
872
+ ".ico",
873
+ ".heic",
874
+ ".heif",
875
+ ".raw",
876
+ ".cr2",
877
+ ".nef",
878
+ ".arw",
879
+ ".dng",
880
+ ".orf",
881
+ ".rw2",
882
+ ".pef",
883
+ ".srw",
884
+ ".eps",
885
+ ".ai",
886
+ ".psd",
887
+ ".xcf",
888
+ )
889
+
890
+ # Basic tag completion
891
+ if "<img".startswith(word):
892
+ yield Completion("<img>", start_position=-len(word))
893
+ if "</img".startswith(word):
894
+ yield Completion("</img>", start_position=-len(word))
895
+
896
+ # Path completion inside <img> tag
897
+ # Find the last opening <img> tag that isn't closed yet
898
+ last_open_img = text.rfind("<img>")
899
+ last_close_img = text.rfind("</img>")
900
+
901
+ if last_open_img != -1 and (
902
+ last_close_img == -1 or last_close_img < last_open_img
903
+ ):
904
+ path_prefix = text[last_open_img + len("<img>") :]
905
+ current_path_word = document.get_word_before_cursor(
906
+ WORD=True
907
+ ) # Path part being typed
908
+
909
+ # Only complete if cursor is within the tag content
910
+ if document.cursor_position > last_open_img + len("<img>"):
911
+
912
+ search_dir = (
913
+ os.path.dirname(path_prefix)
914
+ if os.path.dirname(path_prefix)
915
+ else "."
916
+ )
917
+ file_basename = os.path.basename(current_path_word)
918
+
919
+ try:
920
+ if os.path.isdir(search_dir):
921
+ for item in os.listdir(search_dir):
922
+ full_path = os.path.join(search_dir, item)
923
+ # Suggest directories or image files matching the prefix
924
+ if item.startswith(file_basename):
925
+ if os.path.isdir(full_path):
926
+ relative_path = os.path.relpath(
927
+ full_path, "."
928
+ ) # Use relative path
929
+ yield Completion(
930
+ relative_path + os.sep,
931
+ start_position=-len(current_path_word),
932
+ display=item + "/",
933
+ )
934
+ elif item.lower().endswith(image_extensions):
935
+ relative_path = os.path.relpath(
936
+ full_path, "."
937
+ ) # Use relative path
938
+ yield Completion(
939
+ relative_path,
940
+ start_position=-len(current_path_word),
941
+ display=item,
942
+ )
943
+ except OSError:
944
+ pass # Ignore errors listing directories
945
+
946
+ # --- Helper Methods ---
947
+
948
+ def _complete_command_files(self, word: str) -> Iterable[Completion]:
949
+ """Complete command files from .autocodercommands directory."""
950
+ if word is None:
951
+ word = ""
952
+
953
+ start_pos = -len(word)
954
+
955
+ # Complete files that start with the word
956
+ for command_file in self.command_files_cache:
957
+ if command_file.startswith(word):
958
+ display_name = command_file
959
+ # Add visual indicator for command files
960
+ if command_file.endswith(".md"):
961
+ display_name = f"{command_file} (command)"
962
+ yield Completion(
963
+ command_file, start_position=start_pos, display=display_name
964
+ )
965
+ continue
966
+
967
+ # If no exact prefix matches, try partial matches
968
+ if word:
969
+ for command_file in self.command_files_cache:
970
+ # Match by basename
971
+ basename = os.path.basename(command_file)
972
+ if word in basename and not command_file.startswith(word):
973
+ display_name = f"{command_file} (command)"
974
+ yield Completion(
975
+ command_file, start_position=start_pos, display=display_name
976
+ )
977
+
978
+ def _complete_items_with_in(
979
+ self, word: str, items: Iterable[str]
980
+ ) -> Iterable[Completion]:
981
+ """Generic helper to complete a word from a list of items."""
982
+ for item in items:
983
+ if item and word in item:
984
+ yield Completion(item, start_position=-len(word))
985
+
986
+ def _complete_items(self, word: str, items: Iterable[str]) -> Iterable[Completion]:
987
+ """Generic helper to complete a word from a list of items."""
988
+ if word is None:
989
+ word = ""
990
+ for item in items:
991
+ if item and item.startswith(word):
992
+ yield Completion(item, start_position=-len(word))
993
+
994
+ def _complete_config_keys(
995
+ self, word: str, add_colon: bool = False
996
+ ) -> Iterable[Completion]:
997
+ """Helper to complete configuration keys."""
998
+ suffix = ":" if add_colon else ""
999
+ for key in self.config_keys:
1000
+ if key.startswith(word):
1001
+ yield Completion(key + suffix, start_position=-len(word))
1002
+
1003
+ def _complete_file_paths(
1004
+ self, name: str, text: str, is_symbol: bool = False
1005
+ ) -> Iterable[Completion]:
1006
+ """Helper to complete file paths (@ completion or general path)."""
1007
+ if name is None:
1008
+ name = ""
1009
+ start_pos = -len(name)
1010
+
1011
+ # Prioritize active files if triggered by @
1012
+ if is_symbol:
1013
+ for rel_path in self.current_file_rel_paths:
1014
+ if name in rel_path or name in os.path.basename(rel_path):
1015
+ yield Completion(
1016
+ rel_path,
1017
+ start_position=start_pos,
1018
+ display=f"{rel_path} (active)",
1019
+ )
1020
+
1021
+ # General file path completion (relative paths with dot)
1022
+ if name.startswith("."):
1023
+ yield from self._complete_items(name, self.all_files_with_dot)
1024
+ # Also complete directories starting with dot
1025
+ for rel_path in self.all_dir_rel_paths:
1026
+ if rel_path.startswith(name):
1027
+ yield Completion(
1028
+ rel_path + os.sep,
1029
+ start_position=start_pos,
1030
+ display=f"{rel_path}/ (dir)",
1031
+ )
1032
+ return # Don't mix with other completions if starting with .
1033
+
1034
+ # Complete directory names first (higher priority)
1035
+ for rel_path in self.all_dir_rel_paths:
1036
+ dir_basename = os.path.basename(rel_path)
1037
+
1038
+ # Match by basename or full path
1039
+ if name and (name in dir_basename or name in rel_path):
1040
+ # Always complete with full relative path
1041
+ yield Completion(
1042
+ rel_path + os.sep,
1043
+ start_position=start_pos,
1044
+ display=f"{rel_path}/ (dir)",
1045
+ )
1046
+ elif not name:
1047
+ # Show all directories when no filter
1048
+ yield Completion(
1049
+ rel_path + os.sep,
1050
+ start_position=start_pos,
1051
+ display=f"{rel_path}/ (dir)",
1052
+ )
1053
+
1054
+ # Complete file names with full paths
1055
+ for rel_path in self.all_file_rel_paths:
1056
+ file_basename = os.path.basename(rel_path)
1057
+
1058
+ # Match by basename or full path
1059
+ if name and (name in file_basename or name in rel_path):
1060
+ # Skip if already shown as active
1061
+ if rel_path not in self.current_file_rel_paths:
1062
+ yield Completion(
1063
+ rel_path, start_position=start_pos, display=rel_path
1064
+ )
1065
+
1066
+ def _complete_symbols(self, name: str) -> Iterable[Completion]:
1067
+ """Helper to complete symbols (@@ completion)."""
1068
+ if name is None:
1069
+ name = ""
1070
+ start_pos = -len(name)
1071
+ for symbol in self.symbol_list:
1072
+ # Assuming symbol has attributes symbol_name, file_name, symbol_type
1073
+ if name in symbol.symbol_name:
1074
+ file_name = symbol.file_name
1075
+ display_name = self._get_display_path(file_name)
1076
+ display_text = (
1077
+ f"{symbol.symbol_name} ({display_name}/{symbol.symbol_type})"
1078
+ )
1079
+ completion_text = (
1080
+ f"{symbol.symbol_name} ({display_name}/{symbol.symbol_type})"
1081
+ )
1082
+ yield Completion(
1083
+ completion_text, start_position=start_pos, display=display_text
1084
+ )
1085
+
1086
+ def _get_display_path(self, file_path: str, max_parts: int = 3) -> str:
1087
+ """Helper to create a display path. Now returns full relative path."""
1088
+ try:
1089
+ # Always return full relative path for clarity
1090
+ rel_path = os.path.relpath(file_path, self.project_root)
1091
+ return rel_path
1092
+ # Handle cases where paths are not relative (e.g., different drives on Windows)
1093
+ except ValueError:
1094
+ return file_path