jarvis-ai-assistant 0.7.8__py3-none-any.whl → 1.0.2__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.
Files changed (279) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +567 -222
  3. jarvis/jarvis_agent/agent_manager.py +19 -12
  4. jarvis/jarvis_agent/builtin_input_handler.py +79 -11
  5. jarvis/jarvis_agent/config_editor.py +7 -2
  6. jarvis/jarvis_agent/event_bus.py +24 -13
  7. jarvis/jarvis_agent/events.py +19 -1
  8. jarvis/jarvis_agent/file_context_handler.py +67 -64
  9. jarvis/jarvis_agent/file_methodology_manager.py +38 -24
  10. jarvis/jarvis_agent/jarvis.py +186 -114
  11. jarvis/jarvis_agent/language_extractors/__init__.py +8 -1
  12. jarvis/jarvis_agent/language_extractors/c_extractor.py +7 -4
  13. jarvis/jarvis_agent/language_extractors/cpp_extractor.py +9 -4
  14. jarvis/jarvis_agent/language_extractors/go_extractor.py +7 -4
  15. jarvis/jarvis_agent/language_extractors/java_extractor.py +27 -20
  16. jarvis/jarvis_agent/language_extractors/javascript_extractor.py +22 -17
  17. jarvis/jarvis_agent/language_extractors/python_extractor.py +7 -4
  18. jarvis/jarvis_agent/language_extractors/rust_extractor.py +7 -4
  19. jarvis/jarvis_agent/language_extractors/typescript_extractor.py +22 -17
  20. jarvis/jarvis_agent/language_support_info.py +250 -219
  21. jarvis/jarvis_agent/main.py +19 -23
  22. jarvis/jarvis_agent/memory_manager.py +9 -6
  23. jarvis/jarvis_agent/methodology_share_manager.py +21 -15
  24. jarvis/jarvis_agent/output_handler.py +4 -2
  25. jarvis/jarvis_agent/prompt_builder.py +7 -6
  26. jarvis/jarvis_agent/prompt_manager.py +113 -8
  27. jarvis/jarvis_agent/prompts.py +317 -85
  28. jarvis/jarvis_agent/protocols.py +5 -2
  29. jarvis/jarvis_agent/run_loop.py +192 -32
  30. jarvis/jarvis_agent/session_manager.py +7 -3
  31. jarvis/jarvis_agent/share_manager.py +23 -13
  32. jarvis/jarvis_agent/shell_input_handler.py +12 -8
  33. jarvis/jarvis_agent/stdio_redirect.py +25 -26
  34. jarvis/jarvis_agent/task_analyzer.py +29 -23
  35. jarvis/jarvis_agent/task_list.py +869 -0
  36. jarvis/jarvis_agent/task_manager.py +26 -23
  37. jarvis/jarvis_agent/tool_executor.py +6 -5
  38. jarvis/jarvis_agent/tool_share_manager.py +24 -14
  39. jarvis/jarvis_agent/user_interaction.py +3 -3
  40. jarvis/jarvis_agent/utils.py +9 -1
  41. jarvis/jarvis_agent/web_bridge.py +37 -17
  42. jarvis/jarvis_agent/web_output_sink.py +5 -2
  43. jarvis/jarvis_agent/web_server.py +165 -36
  44. jarvis/jarvis_c2rust/__init__.py +1 -1
  45. jarvis/jarvis_c2rust/cli.py +260 -141
  46. jarvis/jarvis_c2rust/collector.py +37 -18
  47. jarvis/jarvis_c2rust/constants.py +60 -0
  48. jarvis/jarvis_c2rust/library_replacer.py +242 -1010
  49. jarvis/jarvis_c2rust/library_replacer_checkpoint.py +133 -0
  50. jarvis/jarvis_c2rust/library_replacer_llm.py +287 -0
  51. jarvis/jarvis_c2rust/library_replacer_loader.py +191 -0
  52. jarvis/jarvis_c2rust/library_replacer_output.py +134 -0
  53. jarvis/jarvis_c2rust/library_replacer_prompts.py +124 -0
  54. jarvis/jarvis_c2rust/library_replacer_utils.py +188 -0
  55. jarvis/jarvis_c2rust/llm_module_agent.py +98 -1044
  56. jarvis/jarvis_c2rust/llm_module_agent_apply.py +170 -0
  57. jarvis/jarvis_c2rust/llm_module_agent_executor.py +288 -0
  58. jarvis/jarvis_c2rust/llm_module_agent_loader.py +170 -0
  59. jarvis/jarvis_c2rust/llm_module_agent_prompts.py +268 -0
  60. jarvis/jarvis_c2rust/llm_module_agent_types.py +57 -0
  61. jarvis/jarvis_c2rust/llm_module_agent_utils.py +150 -0
  62. jarvis/jarvis_c2rust/llm_module_agent_validator.py +119 -0
  63. jarvis/jarvis_c2rust/loaders.py +28 -10
  64. jarvis/jarvis_c2rust/models.py +5 -2
  65. jarvis/jarvis_c2rust/optimizer.py +192 -1974
  66. jarvis/jarvis_c2rust/optimizer_build_fix.py +286 -0
  67. jarvis/jarvis_c2rust/optimizer_clippy.py +766 -0
  68. jarvis/jarvis_c2rust/optimizer_config.py +49 -0
  69. jarvis/jarvis_c2rust/optimizer_docs.py +183 -0
  70. jarvis/jarvis_c2rust/optimizer_options.py +48 -0
  71. jarvis/jarvis_c2rust/optimizer_progress.py +469 -0
  72. jarvis/jarvis_c2rust/optimizer_report.py +52 -0
  73. jarvis/jarvis_c2rust/optimizer_unsafe.py +309 -0
  74. jarvis/jarvis_c2rust/optimizer_utils.py +469 -0
  75. jarvis/jarvis_c2rust/optimizer_visibility.py +185 -0
  76. jarvis/jarvis_c2rust/scanner.py +229 -166
  77. jarvis/jarvis_c2rust/transpiler.py +531 -2732
  78. jarvis/jarvis_c2rust/transpiler_agents.py +503 -0
  79. jarvis/jarvis_c2rust/transpiler_build.py +1294 -0
  80. jarvis/jarvis_c2rust/transpiler_codegen.py +204 -0
  81. jarvis/jarvis_c2rust/transpiler_compile.py +146 -0
  82. jarvis/jarvis_c2rust/transpiler_config.py +178 -0
  83. jarvis/jarvis_c2rust/transpiler_context.py +122 -0
  84. jarvis/jarvis_c2rust/transpiler_executor.py +516 -0
  85. jarvis/jarvis_c2rust/transpiler_generation.py +278 -0
  86. jarvis/jarvis_c2rust/transpiler_git.py +163 -0
  87. jarvis/jarvis_c2rust/transpiler_mod_utils.py +225 -0
  88. jarvis/jarvis_c2rust/transpiler_modules.py +336 -0
  89. jarvis/jarvis_c2rust/transpiler_planning.py +394 -0
  90. jarvis/jarvis_c2rust/transpiler_review.py +1196 -0
  91. jarvis/jarvis_c2rust/transpiler_symbols.py +176 -0
  92. jarvis/jarvis_c2rust/utils.py +269 -79
  93. jarvis/jarvis_code_agent/after_change.py +233 -0
  94. jarvis/jarvis_code_agent/build_validation_config.py +37 -30
  95. jarvis/jarvis_code_agent/builtin_rules.py +68 -0
  96. jarvis/jarvis_code_agent/code_agent.py +976 -1517
  97. jarvis/jarvis_code_agent/code_agent_build.py +227 -0
  98. jarvis/jarvis_code_agent/code_agent_diff.py +246 -0
  99. jarvis/jarvis_code_agent/code_agent_git.py +525 -0
  100. jarvis/jarvis_code_agent/code_agent_impact.py +177 -0
  101. jarvis/jarvis_code_agent/code_agent_lint.py +283 -0
  102. jarvis/jarvis_code_agent/code_agent_llm.py +159 -0
  103. jarvis/jarvis_code_agent/code_agent_postprocess.py +105 -0
  104. jarvis/jarvis_code_agent/code_agent_prompts.py +46 -0
  105. jarvis/jarvis_code_agent/code_agent_rules.py +305 -0
  106. jarvis/jarvis_code_agent/code_analyzer/__init__.py +52 -48
  107. jarvis/jarvis_code_agent/code_analyzer/base_language.py +12 -10
  108. jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +12 -11
  109. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +16 -12
  110. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +26 -17
  111. jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +558 -104
  112. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +27 -16
  113. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +22 -18
  114. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +21 -16
  115. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +20 -16
  116. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +27 -16
  117. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +47 -23
  118. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +71 -37
  119. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +162 -35
  120. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +111 -57
  121. jarvis/jarvis_code_agent/code_analyzer/build_validator.py +18 -12
  122. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +185 -183
  123. jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +2 -1
  124. jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +24 -15
  125. jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +227 -141
  126. jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +321 -247
  127. jarvis/jarvis_code_agent/code_analyzer/language_registry.py +37 -29
  128. jarvis/jarvis_code_agent/code_analyzer/language_support.py +21 -13
  129. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +15 -9
  130. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +75 -45
  131. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +87 -52
  132. jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +84 -51
  133. jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +94 -64
  134. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +109 -71
  135. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +97 -63
  136. jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +103 -69
  137. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +271 -268
  138. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +76 -64
  139. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +92 -19
  140. jarvis/jarvis_code_agent/diff_visualizer.py +998 -0
  141. jarvis/jarvis_code_agent/lint.py +223 -524
  142. jarvis/jarvis_code_agent/rule_share_manager.py +158 -0
  143. jarvis/jarvis_code_agent/rules/clean_code.md +144 -0
  144. jarvis/jarvis_code_agent/rules/code_review.md +115 -0
  145. jarvis/jarvis_code_agent/rules/documentation.md +165 -0
  146. jarvis/jarvis_code_agent/rules/generate_rules.md +52 -0
  147. jarvis/jarvis_code_agent/rules/performance.md +158 -0
  148. jarvis/jarvis_code_agent/rules/refactoring.md +139 -0
  149. jarvis/jarvis_code_agent/rules/security.md +160 -0
  150. jarvis/jarvis_code_agent/rules/tdd.md +78 -0
  151. jarvis/jarvis_code_agent/test_rules/cpp_test.md +118 -0
  152. jarvis/jarvis_code_agent/test_rules/go_test.md +98 -0
  153. jarvis/jarvis_code_agent/test_rules/java_test.md +99 -0
  154. jarvis/jarvis_code_agent/test_rules/javascript_test.md +113 -0
  155. jarvis/jarvis_code_agent/test_rules/php_test.md +117 -0
  156. jarvis/jarvis_code_agent/test_rules/python_test.md +91 -0
  157. jarvis/jarvis_code_agent/test_rules/ruby_test.md +102 -0
  158. jarvis/jarvis_code_agent/test_rules/rust_test.md +86 -0
  159. jarvis/jarvis_code_agent/utils.py +36 -26
  160. jarvis/jarvis_code_analysis/checklists/loader.py +21 -21
  161. jarvis/jarvis_code_analysis/code_review.py +64 -33
  162. jarvis/jarvis_data/config_schema.json +285 -192
  163. jarvis/jarvis_git_squash/main.py +8 -6
  164. jarvis/jarvis_git_utils/git_commiter.py +53 -76
  165. jarvis/jarvis_mcp/__init__.py +5 -2
  166. jarvis/jarvis_mcp/sse_mcp_client.py +40 -30
  167. jarvis/jarvis_mcp/stdio_mcp_client.py +27 -19
  168. jarvis/jarvis_mcp/streamable_mcp_client.py +35 -26
  169. jarvis/jarvis_memory_organizer/memory_organizer.py +78 -55
  170. jarvis/jarvis_methodology/main.py +48 -39
  171. jarvis/jarvis_multi_agent/__init__.py +56 -23
  172. jarvis/jarvis_multi_agent/main.py +15 -18
  173. jarvis/jarvis_platform/base.py +179 -111
  174. jarvis/jarvis_platform/human.py +27 -16
  175. jarvis/jarvis_platform/kimi.py +52 -45
  176. jarvis/jarvis_platform/openai.py +101 -40
  177. jarvis/jarvis_platform/registry.py +51 -33
  178. jarvis/jarvis_platform/tongyi.py +68 -38
  179. jarvis/jarvis_platform/yuanbao.py +59 -43
  180. jarvis/jarvis_platform_manager/main.py +68 -76
  181. jarvis/jarvis_platform_manager/service.py +24 -14
  182. jarvis/jarvis_rag/README_CONFIG.md +314 -0
  183. jarvis/jarvis_rag/README_DYNAMIC_LOADING.md +311 -0
  184. jarvis/jarvis_rag/README_ONLINE_MODELS.md +230 -0
  185. jarvis/jarvis_rag/__init__.py +57 -4
  186. jarvis/jarvis_rag/cache.py +3 -1
  187. jarvis/jarvis_rag/cli.py +48 -68
  188. jarvis/jarvis_rag/embedding_interface.py +39 -0
  189. jarvis/jarvis_rag/embedding_manager.py +7 -230
  190. jarvis/jarvis_rag/embeddings/__init__.py +41 -0
  191. jarvis/jarvis_rag/embeddings/base.py +114 -0
  192. jarvis/jarvis_rag/embeddings/cohere.py +66 -0
  193. jarvis/jarvis_rag/embeddings/edgefn.py +117 -0
  194. jarvis/jarvis_rag/embeddings/local.py +260 -0
  195. jarvis/jarvis_rag/embeddings/openai.py +62 -0
  196. jarvis/jarvis_rag/embeddings/registry.py +293 -0
  197. jarvis/jarvis_rag/llm_interface.py +8 -6
  198. jarvis/jarvis_rag/query_rewriter.py +8 -9
  199. jarvis/jarvis_rag/rag_pipeline.py +61 -52
  200. jarvis/jarvis_rag/reranker.py +7 -75
  201. jarvis/jarvis_rag/reranker_interface.py +32 -0
  202. jarvis/jarvis_rag/rerankers/__init__.py +41 -0
  203. jarvis/jarvis_rag/rerankers/base.py +109 -0
  204. jarvis/jarvis_rag/rerankers/cohere.py +67 -0
  205. jarvis/jarvis_rag/rerankers/edgefn.py +140 -0
  206. jarvis/jarvis_rag/rerankers/jina.py +79 -0
  207. jarvis/jarvis_rag/rerankers/local.py +89 -0
  208. jarvis/jarvis_rag/rerankers/registry.py +293 -0
  209. jarvis/jarvis_rag/retriever.py +58 -43
  210. jarvis/jarvis_sec/__init__.py +66 -141
  211. jarvis/jarvis_sec/agents.py +21 -17
  212. jarvis/jarvis_sec/analysis.py +80 -33
  213. jarvis/jarvis_sec/checkers/__init__.py +7 -13
  214. jarvis/jarvis_sec/checkers/c_checker.py +356 -164
  215. jarvis/jarvis_sec/checkers/rust_checker.py +47 -29
  216. jarvis/jarvis_sec/cli.py +43 -21
  217. jarvis/jarvis_sec/clustering.py +430 -272
  218. jarvis/jarvis_sec/file_manager.py +99 -55
  219. jarvis/jarvis_sec/parsers.py +9 -6
  220. jarvis/jarvis_sec/prompts.py +4 -3
  221. jarvis/jarvis_sec/report.py +44 -22
  222. jarvis/jarvis_sec/review.py +180 -107
  223. jarvis/jarvis_sec/status.py +50 -41
  224. jarvis/jarvis_sec/types.py +3 -0
  225. jarvis/jarvis_sec/utils.py +160 -83
  226. jarvis/jarvis_sec/verification.py +411 -181
  227. jarvis/jarvis_sec/workflow.py +132 -21
  228. jarvis/jarvis_smart_shell/main.py +28 -41
  229. jarvis/jarvis_stats/cli.py +14 -12
  230. jarvis/jarvis_stats/stats.py +28 -19
  231. jarvis/jarvis_stats/storage.py +14 -8
  232. jarvis/jarvis_stats/visualizer.py +12 -7
  233. jarvis/jarvis_tools/base.py +5 -2
  234. jarvis/jarvis_tools/clear_memory.py +13 -9
  235. jarvis/jarvis_tools/cli/main.py +23 -18
  236. jarvis/jarvis_tools/edit_file.py +572 -873
  237. jarvis/jarvis_tools/execute_script.py +10 -7
  238. jarvis/jarvis_tools/file_analyzer.py +7 -8
  239. jarvis/jarvis_tools/meta_agent.py +287 -0
  240. jarvis/jarvis_tools/methodology.py +5 -3
  241. jarvis/jarvis_tools/read_code.py +305 -1438
  242. jarvis/jarvis_tools/read_symbols.py +50 -17
  243. jarvis/jarvis_tools/read_webpage.py +19 -18
  244. jarvis/jarvis_tools/registry.py +435 -156
  245. jarvis/jarvis_tools/retrieve_memory.py +16 -11
  246. jarvis/jarvis_tools/save_memory.py +8 -6
  247. jarvis/jarvis_tools/search_web.py +31 -31
  248. jarvis/jarvis_tools/sub_agent.py +32 -28
  249. jarvis/jarvis_tools/sub_code_agent.py +44 -60
  250. jarvis/jarvis_tools/task_list_manager.py +1811 -0
  251. jarvis/jarvis_tools/virtual_tty.py +29 -19
  252. jarvis/jarvis_utils/__init__.py +4 -0
  253. jarvis/jarvis_utils/builtin_replace_map.py +2 -1
  254. jarvis/jarvis_utils/clipboard.py +9 -8
  255. jarvis/jarvis_utils/collections.py +331 -0
  256. jarvis/jarvis_utils/config.py +699 -194
  257. jarvis/jarvis_utils/dialogue_recorder.py +294 -0
  258. jarvis/jarvis_utils/embedding.py +6 -3
  259. jarvis/jarvis_utils/file_processors.py +7 -1
  260. jarvis/jarvis_utils/fzf.py +9 -3
  261. jarvis/jarvis_utils/git_utils.py +71 -42
  262. jarvis/jarvis_utils/globals.py +116 -32
  263. jarvis/jarvis_utils/http.py +6 -2
  264. jarvis/jarvis_utils/input.py +318 -83
  265. jarvis/jarvis_utils/jsonnet_compat.py +119 -104
  266. jarvis/jarvis_utils/methodology.py +37 -28
  267. jarvis/jarvis_utils/output.py +201 -44
  268. jarvis/jarvis_utils/utils.py +986 -628
  269. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/METADATA +49 -33
  270. jarvis_ai_assistant-1.0.2.dist-info/RECORD +304 -0
  271. jarvis/jarvis_code_agent/code_analyzer/structured_code.py +0 -556
  272. jarvis/jarvis_tools/generate_new_tool.py +0 -205
  273. jarvis/jarvis_tools/lsp_client.py +0 -1552
  274. jarvis/jarvis_tools/rewrite_file.py +0 -105
  275. jarvis_ai_assistant-0.7.8.dist-info/RECORD +0 -218
  276. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/WHEEL +0 -0
  277. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/entry_points.txt +0 -0
  278. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/licenses/LICENSE +0 -0
  279. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/top_level.txt +0 -0
@@ -34,2063 +34,281 @@ Rust 代码优化器:对转译或生成后的 Rust 项目执行若干保守优
34
34
 
35
35
  from __future__ import annotations
36
36
 
37
- import json
38
- import os
39
- import shutil
40
- import subprocess
41
- from dataclasses import dataclass, asdict
37
+ from dataclasses import asdict
42
38
  from pathlib import Path
43
- from typing import Any, Dict, List, Optional, Tuple, Iterable, Set
44
- import fnmatch
45
-
46
- import typer
47
-
48
- # 引入 CodeAgent(参考 transpiler)
49
- from jarvis.jarvis_code_agent.code_agent import CodeAgent
50
- from jarvis.jarvis_agent.events import BEFORE_TOOL_CALL, AFTER_TOOL_CALL
51
- from jarvis.jarvis_c2rust.utils import check_and_handle_test_deletion
52
-
53
-
54
- @dataclass
55
- class OptimizeOptions:
56
- enable_unsafe_cleanup: bool = True
57
- enable_visibility_opt: bool = True
58
- enable_doc_opt: bool = True
59
- max_checks: int = 0 # 0 表示不限;用于限制 cargo check 次数(防止过慢)
60
- dry_run: bool = False
61
- # 大项目分批优化控制
62
- include_patterns: Optional[str] = None # 逗号分隔的 glob,相对 crate 根(支持 src/**.rs)
63
- exclude_patterns: Optional[str] = None # 逗号分隔的 glob
64
- max_files: int = 0 # 本次最多处理的文件数(0 不限)
65
- resume: bool = True # 断点续跑:跳过已处理文件
66
- reset_progress: bool = False # 重置进度(清空 processed 列表)
67
- build_fix_retries: int = 3 # 构建失败时的修复重试次数
68
- # Git 保护:优化前快照 commit,失败时自动 reset 回快照
69
- git_guard: bool = True
70
- llm_group: Optional[str] = None
71
- cargo_test_timeout: int = 300 # cargo test 超时(秒)
72
- non_interactive: bool = True
73
-
74
-
75
- @dataclass
76
- class OptimizeStats:
77
- files_scanned: int = 0
78
- unsafe_removed: int = 0
79
- unsafe_annotated: int = 0
80
- visibility_downgraded: int = 0
81
- docs_added: int = 0
82
- cargo_checks: int = 0
83
- errors: List[str] = None
84
-
85
- def __post_init__(self):
86
- if self.errors is None:
87
- self.errors = []
88
-
89
-
90
- def _run_cmd(cmd: List[str], cwd: Path, env: Optional[Dict[str, str]] = None, timeout: Optional[int] = None) -> Tuple[int, str, str]:
91
- p = subprocess.Popen(
92
- cmd,
93
- cwd=str(cwd),
94
- stdout=subprocess.PIPE,
95
- stderr=subprocess.PIPE,
96
- text=True,
97
- env=dict(os.environ, **(env or {})),
98
- )
99
- try:
100
- out, err = p.communicate(timeout=timeout if timeout and timeout > 0 else None)
101
- return p.returncode, out, err
102
- except subprocess.TimeoutExpired:
103
- p.kill()
104
- out, err = p.communicate()
105
- err_msg = f"Command '{' '.join(cmd)}' timed out after {timeout} seconds."
106
- if err:
107
- err_msg += f"\n{err}"
108
- return -1, out, err_msg
109
-
110
-
111
- def _cargo_check(crate_dir: Path, stats: OptimizeStats, max_checks: int, timeout: Optional[int] = None) -> Tuple[bool, str]:
112
- # 统一使用 cargo test 作为验证手段
113
- if max_checks and stats.cargo_checks >= max_checks:
114
- return False, "cargo test budget exhausted"
115
- code, out, err = _run_cmd(["cargo", "test", "-q"], crate_dir, timeout=timeout)
116
- stats.cargo_checks += 1
117
- ok = code == 0
118
- diag = err.strip() or out.strip()
119
- # 取首行作为摘要
120
- first_line = next((ln for ln in diag.splitlines() if ln.strip()), "")
121
- return ok, first_line
122
-
123
- def _run_cargo_fmt(crate_dir: Path) -> None:
124
- """
125
- 执行 cargo fmt 格式化代码。
126
- fmt 失败不影响主流程,只记录警告。
127
- """
128
- try:
129
- res = subprocess.run(
130
- ["cargo", "fmt"],
131
- capture_output=True,
132
- text=True,
133
- check=False,
134
- cwd=str(crate_dir),
135
- )
136
- if res.returncode == 0:
137
- typer.secho("[c2rust-optimizer][fmt] 代码格式化完成", fg=typer.colors.CYAN)
138
- else:
139
- # fmt 失败不影响主流程,只记录警告
140
- typer.secho(f"[c2rust-optimizer][fmt] 代码格式化失败(非致命): {res.stderr or res.stdout}", fg=typer.colors.YELLOW)
141
- except Exception as e:
142
- # fmt 失败不影响主流程,只记录警告
143
- typer.secho(f"[c2rust-optimizer][fmt] 代码格式化异常(非致命): {e}", fg=typer.colors.YELLOW)
144
-
145
- def _check_clippy_warnings(crate_dir: Path) -> Tuple[bool, str]:
146
- """
147
- 检查是否有 clippy 告警。
148
- 使用 JSON 格式输出,便于精确解析和指定警告。
149
- 返回 (has_warnings, json_output),has_warnings 为 True 表示有告警,json_output 为 JSON 格式的输出。
150
- """
151
- try:
152
- res = subprocess.run(
153
- ["cargo", "clippy", "--message-format=json", "--", "-W", "clippy::all"],
154
- capture_output=True,
155
- text=True,
156
- check=False,
157
- cwd=str(crate_dir),
158
- )
159
- # clippy 的 JSON 输出通常在 stdout
160
- stdout_output = (res.stdout or "").strip()
161
- stderr_output = (res.stderr or "").strip()
162
-
163
- # 解析 JSON 输出,提取警告信息
164
- warnings = []
165
- if stdout_output:
166
- for line in stdout_output.splitlines():
167
- line = line.strip()
168
- if not line:
169
- continue
170
- try:
171
- msg = json.loads(line)
172
- # 只处理 warning 类型的消息
173
- if msg.get("reason") == "compiler-message" and msg.get("message", {}).get("level") == "warning":
174
- warnings.append(msg)
175
- except (json.JSONDecodeError, KeyError, TypeError):
176
- # 忽略无法解析的行(可能是其他输出)
177
- continue
178
-
179
- has_warnings = len(warnings) > 0
180
-
181
- # 调试输出
182
- if has_warnings:
183
- typer.secho(f"[c2rust-optimizer][clippy-check] 检测到 {len(warnings)} 个 Clippy 告警", fg=typer.colors.YELLOW)
184
- elif res.returncode != 0:
185
- # 如果返回码非零但没有警告,可能是编译错误
186
- typer.secho(f"[c2rust-optimizer][clippy-check] Clippy 返回非零退出码({res.returncode}),但未检测到告警,可能是编译错误", fg=typer.colors.CYAN)
187
- if stderr_output:
188
- typer.secho(f"[c2rust-optimizer][clippy-check] 错误输出预览(前200字符): {stderr_output[:200]}", fg=typer.colors.CYAN)
189
-
190
- # 返回 JSON 格式的输出(用于后续解析)
191
- return has_warnings, stdout_output
192
- except Exception as e:
193
- # 检查失败时假设没有告警,避免阻塞流程
194
- typer.secho(f"[c2rust-optimizer][clippy-check] 检查 Clippy 告警异常(非致命): {e}", fg=typer.colors.YELLOW)
195
- return False, ""
196
-
197
- def _check_missing_safety_doc_warnings(crate_dir: Path) -> Tuple[bool, str]:
198
- """
199
- 检查是否有 missing_safety_doc 告警。
200
- 使用 JSON 格式输出,便于精确解析和指定警告。
201
- 返回 (has_warnings, json_output),has_warnings 为 True 表示有告警,json_output 为 JSON 格式的输出。
202
- """
203
- try:
204
- res = subprocess.run(
205
- ["cargo", "clippy", "--message-format=json", "--", "-W", "clippy::missing_safety_doc"],
206
- capture_output=True,
207
- text=True,
208
- check=False,
209
- cwd=str(crate_dir),
210
- )
211
- # clippy 的 JSON 输出通常在 stdout
212
- stdout_output = (res.stdout or "").strip()
213
- stderr_output = (res.stderr or "").strip()
214
-
215
- # 解析 JSON 输出,提取警告信息
216
- warnings = []
217
- if stdout_output:
218
- for line in stdout_output.splitlines():
219
- line = line.strip()
220
- if not line:
221
- continue
222
- try:
223
- msg = json.loads(line)
224
- # 只处理 warning 类型的消息,且是 missing_safety_doc
225
- if msg.get("reason") == "compiler-message":
226
- message = msg.get("message", {})
227
- if message.get("level") == "warning":
228
- code = message.get("code", {})
229
- code_str = code.get("code", "") if code else ""
230
- if "missing_safety_doc" in code_str:
231
- warnings.append(msg)
232
- except (json.JSONDecodeError, KeyError, TypeError):
233
- # 忽略无法解析的行(可能是其他输出)
234
- continue
235
-
236
- has_warnings = len(warnings) > 0
237
-
238
- # 调试输出
239
- if has_warnings:
240
- typer.secho(f"[c2rust-optimizer][missing-safety-doc-check] 检测到 {len(warnings)} 个 missing_safety_doc 告警", fg=typer.colors.YELLOW)
241
- elif res.returncode != 0:
242
- # 如果返回码非零但没有警告,可能是编译错误
243
- typer.secho(f"[c2rust-optimizer][missing-safety-doc-check] Clippy 返回非零退出码({res.returncode}),但未检测到告警,可能是编译错误", fg=typer.colors.CYAN)
244
- if stderr_output:
245
- typer.secho(f"[c2rust-optimizer][missing-safety-doc-check] 错误输出预览(前200字符): {stderr_output[:200]}", fg=typer.colors.CYAN)
246
-
247
- # 返回 JSON 格式的输出(用于后续解析)
248
- return has_warnings, stdout_output
249
- except Exception as e:
250
- # 检查失败时假设没有告警,避免阻塞流程
251
- typer.secho(f"[c2rust-optimizer][missing-safety-doc-check] 检查 missing_safety_doc 告警异常(非致命): {e}", fg=typer.colors.YELLOW)
252
- return False, ""
253
-
254
- def _cargo_check_full(crate_dir: Path, stats: OptimizeStats, max_checks: int, timeout: Optional[int] = None) -> Tuple[bool, str]:
255
- """
256
- 执行 cargo test,返回是否成功与完整输出(stdout+stderr)。
257
- 会计入 stats.cargo_checks,并受 max_checks 预算限制。
258
- """
259
- if max_checks and stats.cargo_checks >= max_checks:
260
- return False, "cargo test budget exhausted"
261
- try:
262
- res = subprocess.run(
263
- ["cargo", "test", "-q"],
264
- capture_output=True,
265
- text=True,
266
- check=False,
267
- cwd=str(crate_dir),
268
- timeout=timeout if timeout and timeout > 0 else None,
269
- )
270
- stats.cargo_checks += 1
271
- ok = (res.returncode == 0)
272
- out = (res.stdout or "")
273
- err = (res.stderr or "")
274
- msg = (out + ("\n" + err if err else "")).strip()
275
- return ok, msg
276
- except subprocess.TimeoutExpired as e:
277
- stats.cargo_checks += 1
278
- out_s = e.stdout.decode("utf-8", errors="ignore") if e.stdout else ""
279
- err_s = e.stderr.decode("utf-8", errors="ignore") if e.stderr else ""
280
- msg = f"cargo test timed out after {timeout} seconds"
281
- full_output = (out_s + ("\n" + err_s if err_s else "")).strip()
282
- if full_output:
283
- msg += f"\nOutput:\n{full_output}"
284
- return False, msg
285
- except Exception as e:
286
- stats.cargo_checks += 1
287
- return False, f"cargo test exception: {e}"
288
-
289
- def _git_toplevel(start: Path) -> Optional[Path]:
290
- """
291
- 返回包含 start 的 Git 仓库根目录(--show-toplevel)。若不在仓库中则返回 None。
292
- """
293
- try:
294
- code, out, err = _run_cmd(["git", "rev-parse", "--show-toplevel"], start)
295
- if code == 0:
296
- p = (out or "").strip()
297
- if p:
298
- return Path(p)
299
- return None
300
- except Exception:
301
- return None
302
-
303
- def _git_head_commit(root: Path) -> Optional[str]:
304
- try:
305
- code, out, err = _run_cmd(["git", "rev-parse", "--verify", "HEAD"], root)
306
- if code == 0:
307
- return out.strip()
308
- return None
309
- except Exception:
310
- return None
311
-
312
- def _git_reset_hard(root: Path, commit: str) -> bool:
313
- try:
314
- code, _, _ = _run_cmd(["git", "reset", "--hard", commit], root)
315
- if code != 0:
316
- return False
317
- return True
318
- except Exception:
319
- return False
320
-
321
-
322
- def _iter_rust_files(crate_dir: Path) -> Iterable[Path]:
323
- src = crate_dir / "src"
324
- if not src.exists():
325
- # 仍尝试遍历整个 crate 目录,但优先 src
326
- yield from crate_dir.rglob("*.rs")
327
- return
328
- # 遍历 src 优先
329
- yield from src.rglob("*.rs")
330
-
331
-
332
- def _read_file(path: Path) -> str:
333
- return path.read_text(encoding="utf-8")
334
-
335
-
336
- def _write_file(path: Path, content: str) -> None:
337
- path.write_text(content, encoding="utf-8")
338
-
339
-
340
- def _backup_file(path: Path) -> Path:
341
- bak = path.with_suffix(path.suffix + ".bak_opt")
342
- shutil.copy2(path, bak)
343
- return bak
344
-
345
-
346
- def _restore_file_from_backup(path: Path, backup: Path) -> None:
347
- shutil.move(str(backup), str(path))
348
-
349
-
350
- def _remove_backup(backup: Path) -> None:
351
- if backup.exists():
352
- backup.unlink(missing_ok=True)
353
-
354
-
355
- def _ensure_report_dir(crate_dir: Path) -> Path:
356
- report_dir = crate_dir / ".jarvis" / "c2rust"
357
- report_dir.mkdir(parents=True, exist_ok=True)
358
- return report_dir
359
-
360
-
361
- def _find_project_root() -> Optional[Path]:
362
- """
363
- 查找项目根目录(包含 .jarvis/c2rust 的目录)。
364
- 从当前目录向上查找,最多向上查找 5 层。
365
- """
366
- from jarvis.jarvis_c2rust.constants import C2RUST_DIRNAME
367
- cwd = Path(".").resolve()
368
- current = cwd
369
- for _ in range(5):
370
- if current and current.exists():
371
- jarvis_dir = current / C2RUST_DIRNAME
372
- if jarvis_dir.exists() and jarvis_dir.is_dir():
373
- return current
374
- parent = current.parent
375
- if parent == current: # 已到达根目录
376
- break
377
- current = parent
378
- return None
379
-
380
-
381
- def detect_crate_dir(preferred: Optional[Path]) -> Path:
382
- """
383
- 选择 crate 目录策略:
384
- - 若提供 preferred 且包含 Cargo.toml,则使用
385
- - 否则:尝试从项目根目录推断(查找包含 .jarvis/c2rust 的目录)
386
- - 否则:优先 <cwd>/<cwd.name>_rs;若存在 Cargo.toml 则用之
387
- - 否则:在当前目录下递归寻找第一个包含 Cargo.toml 的目录
388
- - 若失败:若当前目录有 Cargo.toml 则返回当前目录,否则抛错
389
- """
390
- if preferred:
391
- preferred = preferred.resolve()
392
- if (preferred / "Cargo.toml").exists():
393
- return preferred
394
-
395
- # 尝试从项目根目录推断 crate 目录
396
- project_root = _find_project_root()
397
- if project_root:
398
- # 策略1: project_root 的父目录下的 <project_root.name>_rs
399
- candidate1 = project_root.parent / f"{project_root.name}_rs"
400
- if (candidate1 / "Cargo.toml").exists():
401
- return candidate1
402
- # 策略2: project_root 本身(如果包含 Cargo.toml)
403
- if (project_root / "Cargo.toml").exists():
404
- return project_root
405
- # 策略3: project_root 下的子目录中包含 Cargo.toml 的
406
- for d in project_root.iterdir():
407
- if d.is_dir() and (d / "Cargo.toml").exists():
408
- return d
409
-
410
- cwd = Path(".").resolve()
411
- candidate = cwd / f"{cwd.name}_rs"
412
- if (candidate / "Cargo.toml").exists():
413
- return candidate
414
-
415
- # 搜索第一个包含 Cargo.toml 的目录(限制深度2以避免过慢)
416
- for p in [cwd] + [d for d in cwd.iterdir() if d.is_dir()]:
417
- if (p / "Cargo.toml").exists():
418
- return p
419
-
420
- if (cwd / "Cargo.toml").exists():
421
- return cwd
422
- raise FileNotFoundError("未找到 Cargo.toml,对应 crate 目录无法确定。")
39
+ from typing import Dict
40
+ from typing import List
41
+ from typing import Optional
42
+
43
+ from jarvis.jarvis_utils.output import PrettyOutput
44
+
45
+ from jarvis.jarvis_c2rust.optimizer_build_fix import BuildFixOptimizer
46
+ from jarvis.jarvis_c2rust.optimizer_clippy import ClippyOptimizer
47
+ from jarvis.jarvis_c2rust.optimizer_config import (
48
+ append_additional_notes as append_notes,
49
+ )
50
+ from jarvis.jarvis_c2rust.optimizer_config import load_additional_notes
51
+ from jarvis.jarvis_c2rust.optimizer_docs import DocsOptimizer
52
+
53
+ # 导入拆分后的模块
54
+ from jarvis.jarvis_c2rust.optimizer_options import OptimizeOptions
55
+ from jarvis.jarvis_c2rust.optimizer_options import OptimizeStats
56
+ from jarvis.jarvis_c2rust.optimizer_progress import ProgressManager
57
+ from jarvis.jarvis_c2rust.optimizer_report import get_report_display_path
58
+ from jarvis.jarvis_c2rust.optimizer_report import write_final_report
59
+ from jarvis.jarvis_c2rust.optimizer_unsafe import UnsafeOptimizer
60
+ from jarvis.jarvis_c2rust.optimizer_utils import compute_target_files
61
+ from jarvis.jarvis_c2rust.optimizer_utils import detect_crate_dir
62
+ from jarvis.jarvis_c2rust.optimizer_utils import ensure_report_dir
63
+ from jarvis.jarvis_c2rust.optimizer_utils import find_project_root
64
+ from jarvis.jarvis_c2rust.optimizer_utils import iter_rust_files
65
+ from jarvis.jarvis_c2rust.optimizer_visibility import VisibilityOptimizer
66
+
67
+ # 工具函数已迁移到 optimizer_utils.py
423
68
 
424
69
 
425
70
  class Optimizer:
426
- def __init__(self, crate_dir: Path, options: OptimizeOptions, project_root: Optional[Path] = None):
71
+ def __init__(
72
+ self,
73
+ crate_dir: Path,
74
+ options: OptimizeOptions,
75
+ project_root: Optional[Path] = None,
76
+ ):
427
77
  self.crate_dir = crate_dir
428
- self.project_root = project_root if project_root else crate_dir.parent # 默认使用 crate_dir 的父目录
78
+ self.project_root = (
79
+ project_root if project_root else crate_dir.parent
80
+ ) # 默认使用 crate_dir 的父目录
429
81
  self.options = options
430
82
  self.stats = OptimizeStats()
431
83
  # 进度文件
432
- self.report_dir = _ensure_report_dir(self.crate_dir)
84
+ self.report_dir = ensure_report_dir(self.crate_dir)
433
85
  self.progress_path = self.report_dir / "optimize_progress.json"
434
- self.processed: Set[str] = set()
435
- self.steps_completed: Set[str] = set() # 已完成的步骤集合
436
- self._step_commits: Dict[str, str] = {} # 每个步骤的 commit id
437
86
  self._target_files: List[Path] = []
438
- self._load_or_reset_progress()
439
- self._last_snapshot_commit: Optional[str] = None
440
- # 每个 Agent 对应的工具调用前的 commit id(用于细粒度检测)
441
- self._agent_before_commits: Dict[str, Optional[str]] = {}
442
- # 读取附加说明
443
- self.additional_notes = self._load_additional_notes()
444
-
445
- def _load_additional_notes(self) -> str:
446
- """从配置文件加载附加说明"""
447
- try:
448
- from jarvis.jarvis_c2rust.constants import CONFIG_JSON
449
- # 尝试从项目根目录读取配置(crate_dir 的父目录或同级目录)
450
- # 首先尝试 crate_dir 的父目录
451
- project_root = self.crate_dir.parent
452
- config_path = project_root / ".jarvis" / "c2rust" / CONFIG_JSON
453
- if config_path.exists():
454
- with config_path.open("r", encoding="utf-8") as f:
455
- config = json.load(f)
456
- if isinstance(config, dict):
457
- return str(config.get("additional_notes", "") or "").strip()
458
- # 如果父目录没有,尝试当前目录
459
- config_path = self.crate_dir / ".jarvis" / "c2rust" / CONFIG_JSON
460
- if config_path.exists():
461
- with config_path.open("r", encoding="utf-8") as f:
462
- config = json.load(f)
463
- if isinstance(config, dict):
464
- return str(config.get("additional_notes", "") or "").strip()
465
- except Exception:
466
- pass
467
- return ""
468
-
469
- def _append_additional_notes(self, prompt: str) -> str:
470
- """
471
- 在提示词末尾追加附加说明(如果存在)。
472
-
473
- Args:
474
- prompt: 原始提示词
475
-
476
- Returns:
477
- 追加了附加说明的提示词
478
- """
479
- if self.additional_notes and self.additional_notes.strip():
480
- return prompt + "\n\n" + "【附加说明(用户自定义)】\n" + self.additional_notes.strip()
481
- return prompt
482
-
483
- def _snapshot_commit(self) -> None:
484
- """
485
- 在启用 git_guard 时记录当前 HEAD commit(仅记录,不提交未暂存更改)。
486
- 统一在仓库根目录执行 git 命令,避免子目录导致的意外。
487
- """
488
- if not self.options.git_guard:
489
- return
490
- try:
491
- repo_root = _git_toplevel(self.crate_dir)
492
- if repo_root is None:
493
- return
494
- head = _git_head_commit(repo_root)
495
- if head:
496
- self._last_snapshot_commit = head
497
- except Exception:
498
- # 忽略快照失败,不阻塞流程
499
- pass
500
-
501
- def _reset_to_snapshot(self) -> bool:
502
- """
503
- 在启用 git_guard 且存在快照时,将工作区 reset --hard 回快照。
504
- 统一在仓库根目录执行 git 命令,避免子目录导致的意外。
505
- 返回是否成功执行 reset。
506
- """
507
- if not self.options.git_guard:
508
- return False
509
- snap = getattr(self, "_last_snapshot_commit", None)
510
- if not snap:
511
- return False
512
- repo_root = _git_toplevel(self.crate_dir)
513
- if repo_root is None:
514
- return False
515
- ok = _git_reset_hard(repo_root, snap)
516
- return ok
517
-
518
- # ---------- 进度管理与文件选择 ----------
519
-
520
- def _load_or_reset_progress(self) -> None:
521
- if self.options.reset_progress:
522
- try:
523
- self.progress_path.write_text(json.dumps({"processed": [], "steps_completed": []}, ensure_ascii=False, indent=2), encoding="utf-8")
524
- except Exception:
525
- pass
526
- self.processed = set()
527
- self.steps_completed: Set[str] = set()
528
- self._step_commits = {}
529
- return
530
- try:
531
- if self.progress_path.exists():
532
- obj = json.loads(self.progress_path.read_text(encoding="utf-8"))
533
- if isinstance(obj, dict):
534
- arr = obj.get("processed") or []
535
- if isinstance(arr, list):
536
- self.processed = {str(x) for x in arr if isinstance(x, str)}
537
- else:
538
- self.processed = set()
539
- # 加载已完成的步骤
540
- steps = obj.get("steps_completed") or []
541
- if isinstance(steps, list):
542
- self.steps_completed = {str(x) for x in steps if isinstance(x, str)}
543
- else:
544
- self.steps_completed = set()
545
- # 加载步骤的 commit id
546
- step_commits = obj.get("step_commits") or {}
547
- if isinstance(step_commits, dict):
548
- self._step_commits = {str(k): str(v) for k, v in step_commits.items() if isinstance(k, str) and isinstance(v, str)}
549
- else:
550
- self._step_commits = {}
551
-
552
- # 恢复时,reset 到最后一个步骤的 commit id
553
- if self.options.resume and self._step_commits:
554
- last_commit = None
555
- # 按照步骤顺序找到最后一个已完成步骤的 commit
556
- step_order = ["clippy_elimination", "unsafe_cleanup", "visibility_opt", "doc_opt"]
557
- for step in reversed(step_order):
558
- if step in self.steps_completed and step in self._step_commits:
559
- last_commit = self._step_commits[step]
560
- break
561
-
562
- if last_commit:
563
- current_commit = self._get_crate_commit_hash()
564
- if current_commit != last_commit:
565
- typer.secho(f"[c2rust-optimizer][resume] 检测到代码状态不一致,正在 reset 到最后一个步骤的 commit: {last_commit}", fg=typer.colors.YELLOW)
566
- if self._reset_to_commit(last_commit):
567
- typer.secho(f"[c2rust-optimizer][resume] 已 reset 到 commit: {last_commit}", fg=typer.colors.GREEN)
568
- else:
569
- typer.secho("[c2rust-optimizer][resume] reset 失败,继续使用当前代码状态", fg=typer.colors.YELLOW)
570
- else:
571
- typer.secho("[c2rust-optimizer][resume] 代码状态一致,无需 reset", fg=typer.colors.CYAN)
572
- else:
573
- self.processed = set()
574
- self.steps_completed = set()
575
- self._step_commits = {}
576
- else:
577
- self.processed = set()
578
- self.steps_completed = set()
579
- self._step_commits = {}
580
- except Exception:
581
- self.processed = set()
582
- self.steps_completed = set()
583
- self._step_commits = {}
584
-
585
- def _get_crate_commit_hash(self) -> Optional[str]:
586
- """获取 crate 目录的当前 commit id"""
587
- try:
588
- repo_root = _git_toplevel(self.crate_dir)
589
- if repo_root is None:
590
- return None
591
- return _git_head_commit(repo_root)
592
- except Exception:
593
- return None
594
-
595
- def _reset_to_commit(self, commit_hash: str) -> bool:
596
- """回退 crate 目录到指定的 commit"""
597
- try:
598
- repo_root = _git_toplevel(self.crate_dir)
599
- if repo_root is None:
600
- return False
601
- return _git_reset_hard(repo_root, commit_hash)
602
- except Exception:
603
- return False
604
-
605
- def _on_before_tool_call(self, agent: Any, current_response=None, **kwargs) -> None:
606
- """
607
- 工具调用前的事件处理器,用于记录工具调用前的 commit id。
608
-
609
- 在每次工具调用前记录当前的 commit,以便在工具调用后检测到问题时能够回退。
610
- """
611
- try:
612
- # 只关注可能修改代码的工具
613
- # 注意:在 BEFORE_TOOL_CALL 时,工具还未执行,无法获取工具名称
614
- # 但我们可以在 AFTER_TOOL_CALL 时检查工具名称,这里先记录 commit
615
- agent_id = id(agent)
616
- agent_key = f"agent_{agent_id}"
617
- current_commit = self._get_crate_commit_hash()
618
- if current_commit:
619
- # 记录工具调用前的 commit(如果之前没有记录,或者 commit 已变化)
620
- if agent_key not in self._agent_before_commits or self._agent_before_commits[agent_key] != current_commit:
621
- self._agent_before_commits[agent_key] = current_commit
622
- except Exception as e:
623
- # 事件处理器异常不应影响主流程
624
- typer.secho(f"[c2rust-optimizer][test-detection] BEFORE_TOOL_CALL 事件处理器异常: {e}", fg=typer.colors.YELLOW)
625
-
626
- def _on_after_tool_call(self, agent: Any, current_response=None, need_return=None, tool_prompt=None, **kwargs) -> None:
627
- """
628
- 工具调用后的事件处理器,用于细粒度检测测试代码删除。
629
-
630
- 在每次工具调用后立即检测,如果检测到测试代码被错误删除,立即回退。
631
- """
632
- try:
633
- # 只检测编辑文件的工具调用
634
- last_tool = agent.get_user_data("__last_executed_tool__") if hasattr(agent, "get_user_data") else None
635
- if not last_tool:
636
- return
637
-
638
- # 只关注可能修改代码的工具
639
- edit_tools = {"edit_file", "rewrite_file", "apply_patch"}
640
- if last_tool not in edit_tools:
641
- return
642
-
643
- # 获取该 Agent 对应的工具调用前的 commit id
644
- agent_id = id(agent)
645
- agent_key = f"agent_{agent_id}"
646
- before_commit = self._agent_before_commits.get(agent_key)
647
-
648
- # 如果没有 commit 信息,无法检测
649
- if not before_commit:
650
- return
651
-
652
- # 检测测试代码删除
653
- from jarvis.jarvis_c2rust.utils import detect_test_deletion, ask_llm_about_test_deletion
654
-
655
- detection_result = detect_test_deletion("[c2rust-optimizer]")
656
- if not detection_result:
657
- # 没有检测到删除,更新 commit 记录
658
- current_commit = self._get_crate_commit_hash()
659
- if current_commit and current_commit != before_commit:
660
- self._agent_before_commits[agent_key] = current_commit
661
- return
662
-
663
- typer.secho("[c2rust-optimizer][test-detection] 检测到可能错误删除了测试代码标记(工具调用后检测)", fg=typer.colors.YELLOW)
664
-
665
- # 询问 LLM 是否合理
666
- need_reset = ask_llm_about_test_deletion(detection_result, agent, "[c2rust-optimizer]")
667
-
668
- if need_reset:
669
- typer.secho(f"[c2rust-optimizer][test-detection] LLM 确认删除不合理,正在回退到 commit: {before_commit}", fg=typer.colors.RED)
670
- if self._reset_to_commit(before_commit):
671
- typer.secho("[c2rust-optimizer][test-detection] 已回退到之前的 commit(工具调用后检测)", fg=typer.colors.GREEN)
672
- # 回退后,保持之前的 commit 记录
673
- self._agent_before_commits[agent_key] = before_commit
674
- # 在 agent 的 session 中添加提示,告知修改被撤销
675
- if hasattr(agent, "session") and hasattr(agent.session, "prompt"):
676
- agent.session.prompt += "\n\n⚠️ 修改被撤销:检测到测试代码被错误删除,已回退到之前的版本。\n"
677
- else:
678
- typer.secho("[c2rust-optimizer][test-detection] 回退失败", fg=typer.colors.RED)
679
- else:
680
- # LLM 认为删除合理,更新 commit 记录
681
- current_commit = self._get_crate_commit_hash()
682
- if current_commit and current_commit != before_commit:
683
- self._agent_before_commits[agent_key] = current_commit
684
- except Exception as e:
685
- # 事件处理器异常不应影响主流程
686
- typer.secho(f"[c2rust-optimizer][test-detection] AFTER_TOOL_CALL 事件处理器异常: {e}", fg=typer.colors.YELLOW)
687
87
 
688
- def _check_and_handle_test_deletion(self, before_commit: Optional[str], agent: Any) -> bool:
689
- """
690
- 检测并处理测试代码删除。
691
-
692
- 参数:
693
- before_commit: agent 运行前的 commit hash
694
- agent: 代码优化或修复的 agent 实例,使用其 model 进行询问
695
-
696
- 返回:
697
- bool: 如果检测到问题且已回退,返回 True;否则返回 False
698
- """
699
- return check_and_handle_test_deletion(
700
- before_commit,
701
- agent,
702
- self._reset_to_commit,
703
- "[c2rust-optimizer]"
88
+ # 初始化进度管理器
89
+ self.progress_manager = ProgressManager(
90
+ self.crate_dir, self.options, self.progress_path
704
91
  )
92
+ self.progress_manager.load_or_reset_progress()
705
93
 
706
- def _save_progress_for_batch(self, files: List[Path]) -> None:
707
- """保存文件处理进度"""
708
- try:
709
- rels = []
710
- for p in files:
711
- try:
712
- rel = p.resolve().relative_to(self.crate_dir.resolve()).as_posix()
713
- except Exception:
714
- rel = str(p)
715
- rels.append(rel)
716
- self.processed.update(rels)
717
-
718
- # 获取当前 commit id 并记录
719
- current_commit = self._get_crate_commit_hash()
720
-
721
- data = {
722
- "processed": sorted(self.processed),
723
- "steps_completed": sorted(self.steps_completed)
724
- }
725
- if current_commit:
726
- data["last_commit"] = current_commit
727
- typer.secho(f"[c2rust-optimizer][progress] 已记录当前 commit: {current_commit}", fg=typer.colors.CYAN)
728
-
729
- self.progress_path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
730
- except Exception:
731
- pass
732
-
733
- def _save_fix_progress(self, step_name: str, fix_key: str, files: Optional[List[Path]] = None) -> None:
734
- """
735
- 保存单个修复的进度(包括 commit id)。
736
-
737
- Args:
738
- step_name: 步骤名称(如 "clippy_elimination", "unsafe_cleanup")
739
- fix_key: 修复的唯一标识(如 "warning-1", "file_path.rs")
740
- files: 修改的文件列表(可选)
741
- """
742
- try:
743
- # 获取当前 commit id
744
- current_commit = self._get_crate_commit_hash()
745
- if not current_commit:
746
- typer.secho("[c2rust-optimizer][progress] 无法获取 commit id,跳过进度记录", fg=typer.colors.YELLOW)
747
- return
748
-
749
- # 加载现有进度
750
- if self.progress_path.exists():
751
- try:
752
- obj = json.loads(self.progress_path.read_text(encoding="utf-8"))
753
- except Exception:
754
- obj = {}
755
- else:
756
- obj = {}
757
-
758
- # 初始化修复进度结构
759
- if "fix_progress" not in obj:
760
- obj["fix_progress"] = {}
761
- if step_name not in obj["fix_progress"]:
762
- obj["fix_progress"][step_name] = {}
763
-
764
- # 记录修复进度
765
- obj["fix_progress"][step_name][fix_key] = {
766
- "commit": current_commit,
767
- "timestamp": None, # 可以添加时间戳如果需要
768
- }
769
-
770
- # 更新已处理的文件列表
771
- if files:
772
- rels = []
773
- for p in files:
774
- try:
775
- rel = p.resolve().relative_to(self.crate_dir.resolve()).as_posix()
776
- except Exception:
777
- rel = str(p)
778
- rels.append(rel)
779
- self.processed.update(rels)
780
- obj["processed"] = sorted(self.processed)
781
-
782
- # 更新 last_commit
783
- obj["last_commit"] = current_commit
784
-
785
- # 保存进度
786
- self.progress_path.write_text(json.dumps(obj, ensure_ascii=False, indent=2), encoding="utf-8")
787
- typer.secho(f"[c2rust-optimizer][progress] 已记录修复进度: {step_name}/{fix_key} -> commit {current_commit[:8]}", fg=typer.colors.CYAN)
788
- except Exception as e:
789
- typer.secho(f"[c2rust-optimizer] 保存修复进度失败(非致命): {e}", fg=typer.colors.YELLOW)
790
-
791
- def _save_step_progress(self, step_name: str, files: List[Path]) -> None:
792
- """保存步骤进度:标记步骤完成并更新文件列表"""
793
- try:
794
- # 标记步骤为已完成
795
- self.steps_completed.add(step_name)
796
- # 更新已处理的文件列表
797
- rels = []
798
- for p in files:
799
- try:
800
- rel = p.resolve().relative_to(self.crate_dir.resolve()).as_posix()
801
- except Exception:
802
- rel = str(p)
803
- rels.append(rel)
804
- self.processed.update(rels)
805
-
806
- # 获取当前 commit id 并记录
807
- current_commit = self._get_crate_commit_hash()
808
-
809
- # 保存进度
810
- data = {
811
- "processed": sorted(self.processed),
812
- "steps_completed": sorted(self.steps_completed)
813
- }
814
- if current_commit:
815
- # 记录每个步骤的 commit id
816
- step_commits = getattr(self, "_step_commits", {})
817
- step_commits[step_name] = current_commit
818
- self._step_commits = step_commits
819
- data["step_commits"] = step_commits
820
- data["last_commit"] = current_commit
821
- typer.secho(f"[c2rust-optimizer][progress] 已记录步骤 '{step_name}' 的 commit: {current_commit}", fg=typer.colors.CYAN)
822
-
823
- self.progress_path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
824
- typer.secho(f"[c2rust-optimizer] 步骤 '{step_name}' 进度已保存", fg=typer.colors.CYAN)
825
- except Exception as e:
826
- typer.secho(f"[c2rust-optimizer] 保存步骤进度失败(非致命): {e}", fg=typer.colors.YELLOW)
827
-
828
- def _parse_patterns(self, s: Optional[str]) -> List[str]:
829
- if not s or not isinstance(s, str):
830
- return []
831
- parts = [x.strip() for x in s.replace("\n", ",").split(",")]
832
- return [x for x in parts if x]
833
-
834
- def _match_any(self, rel: str, patterns: List[str]) -> bool:
835
- if not patterns:
836
- return False
837
- return any(fnmatch.fnmatch(rel, pat) for pat in patterns)
838
-
839
- def _compute_target_files(self) -> List[Path]:
840
- include = self._parse_patterns(self.options.include_patterns)
841
- exclude = self._parse_patterns(self.options.exclude_patterns)
842
- maxn = int(self.options.max_files or 0)
843
- take: List[Path] = []
844
- for p in sorted(_iter_rust_files(self.crate_dir), key=lambda x: x.as_posix()):
845
- try:
846
- rel = p.resolve().relative_to(self.crate_dir.resolve()).as_posix()
847
- except Exception:
848
- rel = p.as_posix()
849
- # include 过滤(若提供,则必须命中其一)
850
- if include and not self._match_any(rel, include):
851
- continue
852
- # exclude 过滤
853
- if exclude and self._match_any(rel, exclude):
854
- continue
855
- # resume:跳过已处理文件
856
- if self.options.resume and rel in self.processed:
857
- continue
858
- take.append(p)
859
- if maxn > 0 and len(take) >= maxn:
860
- break
861
- self._target_files = take
862
- return take
94
+ # 读取附加说明
95
+ self.additional_notes = load_additional_notes(self.crate_dir)
96
+
97
+ # 初始化各个优化器模块
98
+ self.build_fix_optimizer = BuildFixOptimizer(
99
+ self.crate_dir,
100
+ self.options,
101
+ self.stats,
102
+ self.progress_manager,
103
+ lambda p: append_notes(p, self.additional_notes),
104
+ )
863
105
 
864
- # ---------- 主运行入口 ----------
106
+ self.clippy_optimizer = ClippyOptimizer(
107
+ self.crate_dir,
108
+ self.options,
109
+ self.stats,
110
+ self.progress_manager,
111
+ lambda p: append_notes(p, self.additional_notes),
112
+ self.build_fix_optimizer.verify_and_fix_after_step,
113
+ )
865
114
 
866
- def _get_report_display_path(self, report_path: Path) -> str:
867
- """
868
- 获取报告文件的显示路径(优先使用相对路径)。
869
-
870
- Args:
871
- report_path: 报告文件的绝对路径
872
-
873
- Returns:
874
- 显示路径字符串
875
- """
876
- try:
877
- return str(report_path.relative_to(self.project_root))
878
- except ValueError:
879
- try:
880
- return str(report_path.relative_to(self.crate_dir))
881
- except ValueError:
882
- try:
883
- return str(report_path.relative_to(Path.cwd()))
884
- except ValueError:
885
- return str(report_path)
115
+ self.unsafe_optimizer = UnsafeOptimizer(
116
+ self.crate_dir,
117
+ self.options,
118
+ self.stats,
119
+ self.progress_manager,
120
+ lambda p: append_notes(p, self.additional_notes),
121
+ self.clippy_optimizer.extract_warnings_by_file,
122
+ self.clippy_optimizer.format_warnings_for_prompt,
123
+ )
886
124
 
887
- def _write_final_report(self, report_path: Path) -> None:
888
- """
889
- 写入最终优化报告。
890
-
891
- Args:
892
- report_path: 报告文件路径
893
- """
894
- try:
895
- _write_file(report_path, json.dumps(asdict(self.stats), ensure_ascii=False, indent=2))
896
- except Exception:
897
- pass
125
+ self.visibility_optimizer = VisibilityOptimizer(
126
+ self.crate_dir,
127
+ self.options,
128
+ self.stats,
129
+ self.progress_manager,
130
+ lambda p: append_notes(p, self.additional_notes),
131
+ )
898
132
 
899
- def _verify_and_fix_after_step(self, step_name: str, target_files: List[Path]) -> bool:
900
- """
901
- 验证步骤执行后的测试,如果失败则尝试修复。
902
-
903
- Args:
904
- step_name: 步骤名称(用于错误消息)
905
- target_files: 目标文件列表(用于修复范围)
906
-
907
- Returns:
908
- True: 测试通过或修复成功
909
- False: 测试失败且修复失败(已回滚)
910
- """
911
- ok, diag_full = _cargo_check_full(self.crate_dir, self.stats, self.options.max_checks, timeout=self.options.cargo_test_timeout)
912
- if not ok:
913
- fixed = self._build_fix_loop(target_files)
914
- if not fixed:
915
- first = (diag_full.splitlines()[0] if isinstance(diag_full, str) and diag_full else "failed")
916
- self.stats.errors.append(f"test after {step_name} failed: {first}")
917
- try:
918
- self._reset_to_snapshot()
919
- finally:
920
- return False
921
- return True
133
+ self.docs_optimizer = DocsOptimizer(
134
+ self.crate_dir,
135
+ self.options,
136
+ self.stats,
137
+ self.progress_manager,
138
+ lambda p: append_notes(p, self.additional_notes),
139
+ )
922
140
 
923
- def _run_optimization_step(self, step_name: str, step_display_name: str, step_num: int,
924
- target_files: List[Path], opt_func) -> Optional[int]:
141
+ # 配置加载相关方法已迁移到 optimizer_config.py
142
+ # Git 快照相关方法已迁移到 ProgressManager
143
+ # 文件选择相关方法已迁移到 optimizer_utils.py
144
+ # 报告相关方法已迁移到 optimizer_report.py
145
+ # 验证和修复相关方法已迁移到 BuildFixOptimizer
146
+
147
+ def _run_optimization_step(
148
+ self,
149
+ step_name: str,
150
+ step_display_name: str,
151
+ step_num: int,
152
+ target_files: List[Path],
153
+ opt_func,
154
+ ) -> Optional[int]:
925
155
  """
926
156
  执行单个优化步骤(unsafe_cleanup, visibility_opt, doc_opt)。
927
-
157
+
928
158
  Args:
929
159
  step_name: 步骤名称(用于进度保存和错误消息)
930
160
  step_display_name: 步骤显示名称(用于日志)
931
161
  step_num: 步骤编号
932
162
  target_files: 目标文件列表
933
163
  opt_func: 优化函数(接受 target_files 作为参数)
934
-
164
+
935
165
  Returns:
936
166
  下一个步骤编号,如果失败则返回 None
937
167
  """
938
- typer.secho(f"\n[c2rust-optimizer] 第 {step_num} 步:{step_display_name}", fg=typer.colors.MAGENTA)
939
- self._snapshot_commit()
168
+ PrettyOutput.auto_print(
169
+ f"\n🔧 [c2rust-optimizer] 第 {step_num} 步:{step_display_name}",
170
+ color="magenta",
171
+ )
172
+ self.progress_manager.snapshot_commit()
940
173
  if not self.options.dry_run:
941
174
  opt_func(target_files)
942
- if not self._verify_and_fix_after_step(step_name, target_files):
175
+ if not self.build_fix_optimizer.verify_and_fix_after_step(
176
+ step_name, target_files
177
+ ):
943
178
  # 验证失败,已回滚,返回 None 表示失败
944
179
  return None
945
180
  # 保存步骤进度
946
- self._save_step_progress(step_name, target_files)
181
+ self.progress_manager.save_step_progress(step_name, target_files)
947
182
  return step_num + 1
948
183
 
949
- def _handle_clippy_after_auto_fix(self, clippy_targets: List[Path], clippy_output: str) -> bool:
950
- """
951
- 处理自动修复后的 clippy 告警检查。
952
- 如果仍有告警,使用 CodeAgent 继续修复。
953
-
954
- Args:
955
- clippy_targets: 目标文件列表
956
- clippy_output: 当前的 clippy 输出
957
-
958
- Returns:
959
- True: 所有告警已消除
960
- False: 仍有告警未消除(步骤未完成)
961
- """
962
- typer.secho("[c2rust-optimizer] 自动修复后仍有告警,继续使用 CodeAgent 修复...", fg=typer.colors.CYAN)
963
- all_warnings_eliminated = self._codeagent_eliminate_clippy_warnings(clippy_targets, clippy_output)
964
-
965
- # 验证修复后是否还有告警
966
- if not self._verify_and_fix_after_step("clippy_elimination", clippy_targets):
967
- return False
968
-
969
- # 再次检查是否还有告警
970
- has_warnings_after, _ = _check_clippy_warnings(self.crate_dir)
971
- if not has_warnings_after and all_warnings_eliminated:
972
- typer.secho("[c2rust-optimizer] Clippy 告警已全部消除", fg=typer.colors.GREEN)
973
- self._save_step_progress("clippy_elimination", clippy_targets)
974
- return True
975
- else:
976
- typer.secho("[c2rust-optimizer] 仍有部分 Clippy 告警无法自动消除,步骤未完成,停止后续优化步骤", fg=typer.colors.YELLOW)
977
- return False
978
-
979
- def _run_clippy_elimination_step(self) -> bool:
980
- """
981
- 执行 Clippy 告警修复步骤(第 0 步)。
982
-
983
- Returns:
984
- True: 步骤完成(无告警或已修复)
985
- False: 步骤未完成(仍有告警未修复,应停止后续步骤)
986
- """
987
- if self.options.dry_run:
988
- return True
989
-
990
- typer.secho("[c2rust-optimizer] 检查 Clippy 告警...", fg=typer.colors.CYAN)
991
- has_warnings, clippy_output = _check_clippy_warnings(self.crate_dir)
992
-
993
- # 如果步骤已标记为完成,但仍有告警,说明之前的完成标记是错误的,需要清除
994
- if "clippy_elimination" in self.steps_completed and has_warnings:
995
- typer.secho("[c2rust-optimizer] 检测到步骤已标记为完成,但仍有 Clippy 告警,清除完成标记并继续修复", fg=typer.colors.YELLOW)
996
- self.steps_completed.discard("clippy_elimination")
997
- if "clippy_elimination" in self._step_commits:
998
- del self._step_commits["clippy_elimination"]
999
-
1000
- if not has_warnings:
1001
- typer.secho("[c2rust-optimizer] 未发现 Clippy 告警,跳过消除步骤", fg=typer.colors.CYAN)
1002
- # 如果没有告警,标记 clippy_elimination 为完成(跳过状态)
1003
- if "clippy_elimination" not in self.steps_completed:
1004
- clippy_targets = list(_iter_rust_files(self.crate_dir))
1005
- if clippy_targets:
1006
- self._save_step_progress("clippy_elimination", clippy_targets)
1007
- return True
1008
-
1009
- # 有告警,需要修复
1010
- typer.secho("\n[c2rust-optimizer] 第 0 步:消除 Clippy 告警(必须完成此步骤才能继续其他优化)", fg=typer.colors.MAGENTA)
1011
- self._snapshot_commit()
1012
-
1013
- clippy_targets = list(_iter_rust_files(self.crate_dir))
1014
- if not clippy_targets:
1015
- typer.secho("[c2rust-optimizer] 警告:未找到任何 Rust 文件,无法修复 Clippy 告警", fg=typer.colors.YELLOW)
1016
- return False
1017
-
1018
- # 先尝试使用 clippy --fix 自动修复
1019
- auto_fix_success = self._try_clippy_auto_fix()
1020
- if auto_fix_success:
1021
- typer.secho("[c2rust-optimizer] clippy 自动修复成功,继续检查是否还有告警...", fg=typer.colors.GREEN)
1022
- # 重新检查告警
1023
- has_warnings, clippy_output = _check_clippy_warnings(self.crate_dir)
1024
- if not has_warnings:
1025
- typer.secho("[c2rust-optimizer] 所有 Clippy 告警已通过自动修复消除", fg=typer.colors.GREEN)
1026
- self._save_step_progress("clippy_elimination", clippy_targets)
1027
- return True
1028
- else:
1029
- # 仍有告警,使用 CodeAgent 继续修复
1030
- return self._handle_clippy_after_auto_fix(clippy_targets, clippy_output)
1031
- else:
1032
- # 自动修复失败或未执行,继续使用 CodeAgent 修复
1033
- typer.secho("[c2rust-optimizer] clippy 自动修复未成功,继续使用 CodeAgent 修复...", fg=typer.colors.CYAN)
1034
- all_warnings_eliminated = self._codeagent_eliminate_clippy_warnings(clippy_targets, clippy_output)
1035
-
1036
- # 验证修复后是否还有告警
1037
- if not self._verify_and_fix_after_step("clippy_elimination", clippy_targets):
1038
- return False
1039
-
1040
- # 再次检查是否还有告警
1041
- has_warnings_after, _ = _check_clippy_warnings(self.crate_dir)
1042
- if not has_warnings_after and all_warnings_eliminated:
1043
- typer.secho("[c2rust-optimizer] Clippy 告警已全部消除", fg=typer.colors.GREEN)
1044
- self._save_step_progress("clippy_elimination", clippy_targets)
1045
- return True
1046
- else:
1047
- typer.secho("[c2rust-optimizer] 仍有部分 Clippy 告警无法自动消除,步骤未完成,停止后续优化步骤", fg=typer.colors.YELLOW)
1048
- return False
184
+ # Clippy 相关方法已迁移到 ClippyOptimizer
1049
185
 
1050
186
  def run(self) -> OptimizeStats:
1051
187
  """
1052
188
  执行优化流程的主入口。
1053
-
189
+
1054
190
  Returns:
1055
191
  优化统计信息
1056
192
  """
1057
193
  report_path = self.report_dir / "optimize_report.json"
1058
- typer.secho(f"[c2rust-optimizer][start] 开始优化 Crate: {self.crate_dir}", fg=typer.colors.BLUE)
194
+ PrettyOutput.auto_print(
195
+ f"🚀 [c2rust-optimizer][start] 开始优化 Crate: {self.crate_dir}",
196
+ color="blue",
197
+ )
1059
198
  try:
1060
199
  # 批次开始前记录快照
1061
- self._snapshot_commit()
200
+ self.progress_manager.snapshot_commit()
1062
201
 
1063
202
  # ========== 第 0 步:Clippy 告警修复(必须第一步,且必须完成) ==========
1064
203
  # 注意:clippy 告警修复不依赖于是否有新文件需要处理,即使所有文件都已处理,也应该检查并修复告警
1065
- if not self._run_clippy_elimination_step():
204
+ if not self.clippy_optimizer.run_clippy_elimination_step():
1066
205
  # Clippy 告警修复未完成,停止后续步骤
1067
206
  return self.stats
1068
207
 
1069
208
  # ========== 后续优化步骤(只有在 clippy 告警修复完成后才执行) ==========
1070
209
  # 计算本次批次的目标文件列表(按 include/exclude/resume/max_files)
1071
- targets = self._compute_target_files()
1072
-
210
+ targets = compute_target_files(
211
+ self.crate_dir, self.options, self.progress_manager.processed
212
+ )
213
+ self._target_files = targets
214
+
1073
215
  # 检查是否有未完成的步骤需要执行
1074
216
  has_pending_steps = False
1075
- if self.options.enable_unsafe_cleanup and "unsafe_cleanup" not in self.steps_completed:
217
+ if (
218
+ self.options.enable_unsafe_cleanup
219
+ and "unsafe_cleanup" not in self.progress_manager.steps_completed
220
+ ):
1076
221
  has_pending_steps = True
1077
- if self.options.enable_visibility_opt and "visibility_opt" not in self.steps_completed:
222
+ if (
223
+ self.options.enable_visibility_opt
224
+ and "visibility_opt" not in self.progress_manager.steps_completed
225
+ ):
1078
226
  has_pending_steps = True
1079
- if self.options.enable_doc_opt and "doc_opt" not in self.steps_completed:
227
+ if (
228
+ self.options.enable_doc_opt
229
+ and "doc_opt" not in self.progress_manager.steps_completed
230
+ ):
1080
231
  has_pending_steps = True
1081
-
232
+
1082
233
  # 如果没有新文件但有未完成的步骤,使用所有 Rust 文件作为目标
1083
234
  if not targets and has_pending_steps:
1084
- typer.secho("[c2rust-optimizer] 无新文件需要处理,但检测到未完成的步骤,使用所有 Rust 文件作为目标。", fg=typer.colors.CYAN)
1085
- targets = list(_iter_rust_files(self.crate_dir))
1086
-
235
+ PrettyOutput.auto_print(
236
+ "📝 [c2rust-optimizer] 无新文件需要处理,但检测到未完成的步骤,使用所有 Rust 文件作为目标。",
237
+ color="cyan",
238
+ )
239
+ targets = list(iter_rust_files(self.crate_dir))
240
+
1087
241
  if not targets:
1088
- typer.secho("[c2rust-optimizer] 根据当前选项,无新文件需要处理,且所有步骤均已完成。", fg=typer.colors.CYAN)
242
+ PrettyOutput.auto_print(
243
+ "✅ [c2rust-optimizer] 根据当前选项,无新文件需要处理,且所有步骤均已完成。",
244
+ color="cyan",
245
+ )
1089
246
  else:
1090
- typer.secho(f"[c2rust-optimizer] 本次批次发现 {len(targets)} 个待处理文件。", fg=typer.colors.BLUE)
247
+ PrettyOutput.auto_print(
248
+ f"📊 [c2rust-optimizer] 本次批次发现 {len(targets)} 个待处理文件。",
249
+ color="blue",
250
+ )
1091
251
 
1092
252
  # 所有优化步骤都使用 CodeAgent
1093
253
  step_num = 1
1094
-
254
+
1095
255
  if self.options.enable_unsafe_cleanup:
1096
- step_num = self._run_optimization_step(
1097
- "unsafe_cleanup", "unsafe 清理", step_num, targets,
1098
- self._codeagent_opt_unsafe_cleanup
256
+ result_step_num = self._run_optimization_step(
257
+ "unsafe_cleanup",
258
+ "unsafe 清理",
259
+ step_num,
260
+ targets,
261
+ self.unsafe_optimizer.codeagent_opt_unsafe_cleanup,
1099
262
  )
1100
- if step_num is None: # 步骤失败,已回滚
263
+ if result_step_num is None: # 步骤失败,已回滚
1101
264
  return self.stats
265
+ step_num = result_step_num
1102
266
 
1103
267
  if self.options.enable_visibility_opt:
1104
- step_num = self._run_optimization_step(
1105
- "visibility_opt", "可见性优化", step_num, targets,
1106
- self._codeagent_opt_visibility
268
+ result_step_num = self._run_optimization_step(
269
+ "visibility_opt",
270
+ "可见性优化",
271
+ step_num,
272
+ targets,
273
+ self.visibility_optimizer.codeagent_opt_visibility,
1107
274
  )
1108
- if step_num is None: # 步骤失败,已回滚
275
+ if result_step_num is None: # 步骤失败,已回滚
1109
276
  return self.stats
277
+ step_num = result_step_num
1110
278
 
1111
279
  if self.options.enable_doc_opt:
1112
- step_num = self._run_optimization_step(
1113
- "doc_opt", "文档补充", step_num, targets,
1114
- self._codeagent_opt_docs
280
+ result_step_num = self._run_optimization_step(
281
+ "doc_opt",
282
+ "文档补充",
283
+ step_num,
284
+ targets,
285
+ self.docs_optimizer.codeagent_opt_docs,
1115
286
  )
1116
- if step_num is None: # 步骤失败,已回滚
287
+ if result_step_num is None: # 步骤失败,已回滚
1117
288
  return self.stats
289
+ step_num = result_step_num
1118
290
 
1119
291
  # 最终保存进度(确保所有步骤的进度都已记录)
1120
- self._save_progress_for_batch(targets)
292
+ self.progress_manager.save_progress_for_batch(targets)
1121
293
 
1122
294
  except Exception as e:
1123
- self.stats.errors.append(f"fatal: {e}")
295
+ if self.stats.errors is not None:
296
+ self.stats.errors.append(f"fatal: {e}")
1124
297
  finally:
1125
298
  # 写出简要报告
1126
- report_display = self._get_report_display_path(report_path)
1127
- typer.secho(f"[c2rust-optimizer] 优化流程结束。报告已生成于: {report_display}", fg=typer.colors.GREEN)
1128
- self._write_final_report(report_path)
1129
- return self.stats
1130
-
1131
- # ========== 0) clippy warnings elimination (CodeAgent) ==========
1132
-
1133
- def _try_clippy_auto_fix(self) -> bool:
1134
- """
1135
- 尝试使用 `cargo clippy --fix` 自动修复 clippy 告警。
1136
- 修复时同时包含测试代码(--tests),避免删除测试中使用的变量。
1137
- 修复后运行测试验证,如果测试失败则撤销修复。
1138
-
1139
- 返回:
1140
- True: 自动修复成功且测试通过
1141
- False: 自动修复失败或测试未通过(已撤销修复)
1142
- """
1143
- crate = self.crate_dir.resolve()
1144
- typer.secho("[c2rust-optimizer][clippy-auto-fix] 尝试使用 clippy --fix 自动修复(包含测试代码)...", fg=typer.colors.CYAN)
1145
-
1146
- # 记录修复前的 commit id
1147
- commit_before = self._get_crate_commit_hash()
1148
- if not commit_before:
1149
- typer.secho("[c2rust-optimizer][clippy-auto-fix] 无法获取 commit id,跳过自动修复", fg=typer.colors.YELLOW)
1150
- return False
1151
-
1152
- # 执行 cargo clippy --fix,添加 --tests 标志以包含测试代码
1153
- try:
1154
- res = subprocess.run(
1155
- ["cargo", "clippy", "--fix", "--tests", "--allow-dirty", "--allow-staged", "--", "-W", "clippy::all"],
1156
- capture_output=True,
1157
- text=True,
1158
- check=False,
1159
- cwd=str(crate),
1160
- timeout=300, # 5 分钟超时
299
+ report_display = get_report_display_path(
300
+ report_path, self.project_root, self.crate_dir
1161
301
  )
1162
-
1163
- if res.returncode != 0:
1164
- typer.secho(f"[c2rust-optimizer][clippy-auto-fix] clippy --fix 执行失败(返回码: {res.returncode})", fg=typer.colors.YELLOW)
1165
- if res.stderr:
1166
- typer.secho(f"[c2rust-optimizer][clippy-auto-fix] 错误输出: {res.stderr[:500]}", fg=typer.colors.YELLOW)
1167
- return False
1168
-
1169
- # 检查是否有文件被修改(通过 git status 或直接检查)
1170
- # 如果没有修改,说明 clippy --fix 没有修复任何问题
1171
- repo_root = _git_toplevel(crate)
1172
- has_changes = False
1173
- if repo_root:
1174
- try:
1175
- code, out, _ = _run_cmd(["git", "diff", "--quiet", "--exit-code"], repo_root)
1176
- has_changes = (code != 0) # 非零表示有修改
1177
- except Exception:
1178
- # 如果无法检查 git 状态,假设有修改
1179
- has_changes = True
1180
- else:
1181
- # 不在 git 仓库中,假设有修改
1182
- has_changes = True
1183
-
1184
- if not has_changes:
1185
- typer.secho("[c2rust-optimizer][clippy-auto-fix] clippy --fix 未修改任何文件", fg=typer.colors.CYAN)
1186
- return False
1187
-
1188
- typer.secho("[c2rust-optimizer][clippy-auto-fix] clippy --fix 已执行,正在验证测试...", fg=typer.colors.CYAN)
1189
-
1190
- # 运行 cargo test 验证
1191
- ok, diag_full = _cargo_check_full(self.crate_dir, self.stats, self.options.max_checks, timeout=self.options.cargo_test_timeout)
1192
-
1193
- if ok:
1194
- typer.secho("[c2rust-optimizer][clippy-auto-fix] 自动修复成功且测试通过", fg=typer.colors.GREEN)
1195
- return True
1196
- else:
1197
- typer.secho("[c2rust-optimizer][clippy-auto-fix] 自动修复后测试失败,正在撤销修复...", fg=typer.colors.YELLOW)
1198
- # 撤销修复:回退到修复前的 commit
1199
- if commit_before and self._reset_to_commit(commit_before):
1200
- typer.secho(f"[c2rust-optimizer][clippy-auto-fix] 已成功撤销自动修复,回退到 commit: {commit_before[:8]}", fg=typer.colors.CYAN)
1201
- else:
1202
- typer.secho("[c2rust-optimizer][clippy-auto-fix] 撤销修复失败,请手动检查代码状态", fg=typer.colors.RED)
1203
- return False
1204
-
1205
- except subprocess.TimeoutExpired:
1206
- typer.secho("[c2rust-optimizer][clippy-auto-fix] clippy --fix 执行超时,正在检查是否有修改并撤销...", fg=typer.colors.YELLOW)
1207
- # 检查是否有修改,如果有则回退
1208
- if commit_before:
1209
- repo_root = _git_toplevel(crate)
1210
- if repo_root:
1211
- try:
1212
- code, _, _ = _run_cmd(["git", "diff", "--quiet", "--exit-code"], repo_root)
1213
- has_changes = (code != 0) # 非零表示有修改
1214
- if has_changes:
1215
- if self._reset_to_commit(commit_before):
1216
- typer.secho(f"[c2rust-optimizer][clippy-auto-fix] 已撤销超时前的修改,回退到 commit: {commit_before[:8]}", fg=typer.colors.CYAN)
1217
- else:
1218
- typer.secho("[c2rust-optimizer][clippy-auto-fix] 撤销修改失败,请手动检查代码状态", fg=typer.colors.RED)
1219
- except Exception:
1220
- # 无法检查状态,尝试直接回退
1221
- self._reset_to_commit(commit_before)
1222
- return False
1223
- except Exception as e:
1224
- typer.secho(f"[c2rust-optimizer][clippy-auto-fix] clippy --fix 执行异常: {e},正在检查是否有修改并撤销...", fg=typer.colors.YELLOW)
1225
- # 检查是否有修改,如果有则回退
1226
- if commit_before:
1227
- repo_root = _git_toplevel(crate)
1228
- if repo_root:
1229
- try:
1230
- code, _, _ = _run_cmd(["git", "diff", "--quiet", "--exit-code"], repo_root)
1231
- has_changes = (code != 0) # 非零表示有修改
1232
- if has_changes:
1233
- if self._reset_to_commit(commit_before):
1234
- typer.secho(f"[c2rust-optimizer][clippy-auto-fix] 已撤销异常前的修改,回退到 commit: {commit_before[:8]}", fg=typer.colors.CYAN)
1235
- else:
1236
- typer.secho("[c2rust-optimizer][clippy-auto-fix] 撤销修改失败,请手动检查代码状态", fg=typer.colors.RED)
1237
- except Exception:
1238
- # 无法检查状态,尝试直接回退
1239
- self._reset_to_commit(commit_before)
1240
- return False
1241
-
1242
- def _codeagent_eliminate_clippy_warnings(self, target_files: List[Path], clippy_output: str) -> bool:
1243
- """
1244
- 使用 CodeAgent 消除 clippy 告警。
1245
- 按文件修复,每次修复单个文件的前10个告警(不足10个就全部给出),修复后重新扫描,不断迭代。
1246
-
1247
- 注意:CodeAgent 必须在 crate 目录下创建和执行,以确保所有文件操作和命令执行都在正确的上下文中进行。
1248
-
1249
- 返回:
1250
- True: 所有告警已消除
1251
- False: 仍有告警未消除(达到最大迭代次数或无法提取告警)
1252
- """
1253
- crate = self.crate_dir.resolve()
1254
- file_list: List[str] = []
1255
- for p in target_files:
1256
- try:
1257
- rel = p.resolve().relative_to(crate).as_posix()
1258
- except Exception:
1259
- rel = p.as_posix()
1260
- file_list.append(rel)
1261
- self.stats.files_scanned += 1
1262
-
1263
- # 切换到 crate 目录,确保 CodeAgent 在正确的上下文中创建和执行
1264
- prev_cwd = os.getcwd()
1265
- iteration = 0
1266
-
1267
- try:
1268
- os.chdir(str(crate))
1269
-
1270
- # 循环修复告警,按文件处理
1271
- while True:
1272
- iteration += 1
1273
-
1274
- # 检查当前告警
1275
- has_warnings, current_clippy_output = _check_clippy_warnings(crate)
1276
- if not has_warnings:
1277
- typer.secho(f"[c2rust-optimizer][codeagent][clippy] 所有告警已消除(共迭代 {iteration - 1} 次)", fg=typer.colors.GREEN)
1278
- return True # 所有告警已消除
1279
-
1280
- # 按文件提取告警
1281
- warnings_by_file = self._extract_warnings_by_file(current_clippy_output)
1282
- if not warnings_by_file:
1283
- typer.secho("[c2rust-optimizer][codeagent][clippy] 无法提取告警,停止修复", fg=typer.colors.YELLOW)
1284
- return False # 仍有告警未消除
1285
-
1286
- # 找到第一个有告警的文件(优先处理目标文件列表中的文件)
1287
- target_file_path = None
1288
- target_warnings = None
1289
-
1290
- # 优先处理目标文件列表中的文件
1291
- for file_rel in file_list:
1292
- # 尝试匹配文件路径(可能是相对路径或绝对路径)
1293
- for file_path, warnings in warnings_by_file.items():
1294
- if file_rel in file_path or file_path.endswith(file_rel):
1295
- target_file_path = file_path
1296
- target_warnings = warnings
1297
- break
1298
- if target_file_path:
1299
- break
1300
-
1301
- # 如果目标文件列表中没有告警,选择第一个有告警的文件
1302
- if not target_file_path:
1303
- target_file_path = next(iter(warnings_by_file.keys()))
1304
- target_warnings = warnings_by_file[target_file_path]
1305
-
1306
- # 获取该文件的前10个告警(不足10个就全部给出)
1307
- warnings_to_fix = target_warnings[:10]
1308
- warning_count = len(warnings_to_fix)
1309
- total_warnings_in_file = len(target_warnings)
1310
-
1311
- typer.secho(f"[c2rust-optimizer][codeagent][clippy] 第 {iteration} 次迭代:修复文件 {target_file_path} 的前 {warning_count} 个告警(共 {total_warnings_in_file} 个)", fg=typer.colors.CYAN)
1312
-
1313
- # 格式化告警信息
1314
- formatted_warnings = self._format_warnings_for_prompt(warnings_to_fix, max_count=10)
1315
-
1316
- # 构建提示词,修复该文件的前10个告警
1317
- prompt_lines: List[str] = [
1318
- "你是资深 Rust 代码工程师。请在当前 crate 下修复指定文件中的 Clippy 告警,并以补丁形式输出修改:",
1319
- f"- crate 根目录:{crate}",
1320
- "",
1321
- "本次修复仅允许修改以下文件(严格限制,只处理这一个文件):",
1322
- f"- {target_file_path}",
1323
- "",
1324
- f"重要:本次修复仅修复该文件中的前 {warning_count} 个告警,不要修复其他告警。",
1325
- "",
1326
- "优化目标:",
1327
- f"1) 修复文件 {target_file_path} 中的 {warning_count} 个 Clippy 告警:",
1328
- " - 根据以下 Clippy 告警信息,修复这些告警;",
1329
- " - 告警信息包含文件路径、行号、警告类型、消息和建议,请根据这些信息进行修复;",
1330
- " - 对于无法自动修复的告警,请根据 Clippy 的建议进行手动修复;",
1331
- " - **如果确认是误报**(例如:告警建议的修改会导致性能下降、代码可读性降低、或与项目设计意图不符),可以添加 `#[allow(clippy::...)]` 注释来屏蔽该告警;",
1332
- " - 使用 `#[allow(...)]` 时,必须在注释中说明为什么这是误报,例如:`#[allow(clippy::unnecessary_wraps)] // 保持 API 一致性,返回值类型需要与接口定义一致`;",
1333
- " - 优先尝试修复告警,只有在确认是误报时才使用 `#[allow(...)]` 屏蔽。",
1334
- "",
1335
- "2) 修复已有实现的问题:",
1336
- " - 如果在修复告警的过程中,发现代码已有的实现有问题(如逻辑错误、潜在 bug、性能问题、内存安全问题等),也需要一并修复;",
1337
- " - 这些问题可能包括但不限于:空指针解引用、数组越界、未初始化的变量、资源泄漏、竞态条件等;",
1338
- " - 修复时应该保持最小改动原则,优先修复最严重的问题。",
1339
- "",
1340
- "约束与范围:",
1341
- f"- **仅修改文件 {target_file_path},不要修改其他文件**;除非必须(如修复引用路径),否则不要修改其他文件。",
1342
- "- 保持最小改动,不要进行与消除告警无关的重构或格式化。",
1343
- f"- **只修复该文件中的前 {warning_count} 个告警,不要修复其他告警**。",
1344
- "- 修改后需保证 `cargo test` 可以通过;如需引入少量配套改动,请一并包含在补丁中以确保通过。",
1345
- "- 输出仅为补丁,不要输出解释或多余文本。",
1346
- "",
1347
- "优先级说明:",
1348
- "- **如果优化过程中出现了测试不通过或编译错误,必须优先解决这些问题**;",
1349
- "- 在修复告警之前,先确保代码能够正常编译和通过测试;",
1350
- "- 如果修复告警导致了编译错误或测试失败,必须立即修复这些错误,然后再继续优化。",
1351
- "",
1352
- "自检要求:在每次输出补丁后,请使用 execute_script 工具在 crate 根目录执行 `cargo test -q` 进行验证;",
1353
- "若出现编译错误或测试失败,请优先修复这些问题,然后再继续修复告警;",
1354
- "若未通过,请继续输出新的补丁进行最小修复并再次自检,直至 `cargo test` 通过为止。",
1355
- "",
1356
- f"文件 {target_file_path} 中的 Clippy 告警信息如下:",
1357
- "<WARNINGS>",
1358
- formatted_warnings,
1359
- "</WARNINGS>",
1360
- ]
1361
- prompt = "\n".join(prompt_lines)
1362
- prompt = self._append_additional_notes(prompt)
1363
-
1364
- # 修复前执行 cargo fmt
1365
- _run_cargo_fmt(crate)
1366
-
1367
- # 记录运行前的 commit id
1368
- commit_before = self._get_crate_commit_hash()
1369
-
1370
- # CodeAgent 在 crate 目录下创建和执行
1371
- agent = CodeAgent(name=f"ClippyWarningEliminator-iter{iteration}", need_summary=False, non_interactive=self.options.non_interactive, model_group=self.options.llm_group)
1372
- # 订阅 BEFORE_TOOL_CALL 和 AFTER_TOOL_CALL 事件,用于细粒度检测测试代码删除
1373
- agent.event_bus.subscribe(BEFORE_TOOL_CALL, self._on_before_tool_call)
1374
- agent.event_bus.subscribe(AFTER_TOOL_CALL, self._on_after_tool_call)
1375
- # 记录 Agent 创建时的 commit id(作为初始值)
1376
- agent_id = id(agent)
1377
- agent_key = f"agent_{agent_id}"
1378
- initial_commit = self._get_crate_commit_hash()
1379
- if initial_commit:
1380
- self._agent_before_commits[agent_key] = initial_commit
1381
- agent.run(prompt, prefix="[c2rust-optimizer][codeagent][clippy]", suffix="")
1382
-
1383
- # 检测并处理测试代码删除
1384
- if self._check_and_handle_test_deletion(commit_before, agent):
1385
- # 如果回退了,需要重新运行 agent
1386
- typer.secho(f"[c2rust-optimizer][codeagent][clippy] 检测到测试代码删除问题,已回退,重新运行 agent (iter={iteration})", fg=typer.colors.YELLOW)
1387
- commit_before = self._get_crate_commit_hash()
1388
- agent.run(prompt, prefix="[c2rust-optimizer][codeagent][clippy][retry]", suffix="")
1389
- # 再次检测
1390
- if self._check_and_handle_test_deletion(commit_before, agent):
1391
- typer.secho(f"[c2rust-optimizer][codeagent][clippy] 再次检测到测试代码删除问题,已回退 (iter={iteration})", fg=typer.colors.RED)
1392
-
1393
- # 验证修复是否成功(通过 cargo test)
1394
- ok, _ = _cargo_check_full(crate, self.stats, self.options.max_checks, timeout=self.options.cargo_test_timeout)
1395
- if ok:
1396
- # 修复成功,保存进度和 commit id
1397
- try:
1398
- file_path = crate / target_file_path if not Path(target_file_path).is_absolute() else Path(target_file_path)
1399
- if file_path.exists():
1400
- self._save_fix_progress("clippy_elimination", f"{target_file_path}-iter{iteration}", [file_path])
1401
- else:
1402
- self._save_fix_progress("clippy_elimination", f"{target_file_path}-iter{iteration}", None)
1403
- except Exception:
1404
- self._save_fix_progress("clippy_elimination", f"{target_file_path}-iter{iteration}", None)
1405
- typer.secho(f"[c2rust-optimizer][codeagent][clippy] 文件 {target_file_path} 的前 {warning_count} 个告警修复成功,已保存进度", fg=typer.colors.GREEN)
1406
- else:
1407
- # 测试失败,回退到运行前的 commit
1408
- if commit_before:
1409
- typer.secho(f"[c2rust-optimizer][codeagent][clippy] 文件 {target_file_path} 修复后测试失败,回退到运行前的 commit: {commit_before[:8]}", fg=typer.colors.YELLOW)
1410
- if self._reset_to_commit(commit_before):
1411
- typer.secho(f"[c2rust-optimizer][codeagent][clippy] 已成功回退到 commit: {commit_before[:8]}", fg=typer.colors.CYAN)
1412
- else:
1413
- typer.secho("[c2rust-optimizer][codeagent][clippy] 回退失败,请手动检查代码状态", fg=typer.colors.RED)
1414
- else:
1415
- typer.secho(f"[c2rust-optimizer][codeagent][clippy] 文件 {target_file_path} 修复后测试失败,但无法获取运行前的 commit,继续修复", fg=typer.colors.YELLOW)
1416
-
1417
- # 修复后再次检查告警,如果告警数量没有减少,可能需要停止
1418
- has_warnings_after, _ = _check_clippy_warnings(crate)
1419
- if not has_warnings_after:
1420
- typer.secho(f"[c2rust-optimizer][codeagent][clippy] 所有告警已消除(共迭代 {iteration} 次)", fg=typer.colors.GREEN)
1421
- return True # 所有告警已消除
1422
- finally:
1423
- os.chdir(prev_cwd)
1424
-
1425
- # 默认返回 False(仍有告警)
1426
- return False
1427
-
1428
- def _extract_warnings_by_file(self, clippy_json_output: str) -> Dict[str, List[Dict]]:
1429
- """
1430
- 从 clippy JSON 输出中提取所有告警并按文件分组。
1431
-
1432
- Returns:
1433
- 字典,键为文件路径,值为该文件的告警列表
1434
- """
1435
- if not clippy_json_output:
1436
- return {}
1437
-
1438
- warnings_by_file: Dict[str, List[Dict]] = {}
1439
-
1440
- for line in clippy_json_output.splitlines():
1441
- line = line.strip()
1442
- if not line:
1443
- continue
1444
- try:
1445
- msg = json.loads(line)
1446
- # 只处理 warning 类型的消息
1447
- if msg.get("reason") == "compiler-message" and msg.get("message", {}).get("level") == "warning":
1448
- message = msg.get("message", {})
1449
- spans = message.get("spans", [])
1450
- if spans:
1451
- primary_span = spans[0]
1452
- file_path = primary_span.get("file_name", "")
1453
- if file_path:
1454
- if file_path not in warnings_by_file:
1455
- warnings_by_file[file_path] = []
1456
- warnings_by_file[file_path].append(msg)
1457
- except (json.JSONDecodeError, KeyError, TypeError):
1458
- continue
1459
-
1460
- return warnings_by_file
1461
-
1462
- def _format_warnings_for_prompt(self, warnings: List[Dict], max_count: int = 10) -> str:
1463
- """
1464
- 格式化告警列表,用于提示词。
1465
-
1466
- Args:
1467
- warnings: 告警消息列表
1468
- max_count: 最多格式化多少个告警(默认10个)
1469
-
1470
- Returns:
1471
- 格式化后的告警信息字符串
1472
- """
1473
- if not warnings:
1474
- return ""
1475
-
1476
- # 只取前 max_count 个告警
1477
- warnings_to_format = warnings[:max_count]
1478
- formatted_warnings = []
1479
-
1480
- for idx, warning_msg in enumerate(warnings_to_format, 1):
1481
- message = warning_msg.get("message", {})
1482
- spans = message.get("spans", [])
1483
-
1484
- warning_parts = [f"告警 {idx}:"]
1485
-
1486
- # 警告类型和消息
1487
- code = message.get("code", {})
1488
- code_str = code.get("code", "") if code else ""
1489
- message_text = message.get("message", "")
1490
- warning_parts.append(f" 警告类型: {code_str}")
1491
- warning_parts.append(f" 消息: {message_text}")
1492
-
1493
- # 文件位置
1494
- if spans:
1495
- primary_span = spans[0]
1496
- line_start = primary_span.get("line_start", 0)
1497
- column_start = primary_span.get("column_start", 0)
1498
- line_end = primary_span.get("line_end", 0)
1499
- column_end = primary_span.get("column_end", 0)
1500
-
1501
- if line_start == line_end:
1502
- warning_parts.append(f" 位置: {line_start}:{column_start}-{column_end}")
1503
- else:
1504
- warning_parts.append(f" 位置: {line_start}:{column_start} - {line_end}:{column_end}")
1505
-
1506
- # 代码片段
1507
- label = primary_span.get("label", "")
1508
- if label:
1509
- warning_parts.append(f" 代码: {label}")
1510
-
1511
- # 建议(help 消息)
1512
- children = message.get("children", [])
1513
- for child in children:
1514
- if child.get("level") == "help":
1515
- help_message = child.get("message", "")
1516
- help_spans = child.get("spans", [])
1517
- if help_message:
1518
- warning_parts.append(f" 建议: {help_message}")
1519
- if help_spans:
1520
- help_span = help_spans[0]
1521
- help_label = help_span.get("label", "")
1522
- if help_label:
1523
- warning_parts.append(f" 建议代码: {help_label}")
1524
-
1525
- formatted_warnings.append("\n".join(warning_parts))
1526
-
1527
- if len(warnings) > max_count:
1528
- formatted_warnings.append(f"\n(该文件还有 {len(warnings) - max_count} 个告警,将在后续迭代中处理)")
1529
-
1530
- return "\n\n".join(formatted_warnings)
1531
-
1532
- # ========== 1) unsafe cleanup (CodeAgent) ==========
1533
-
1534
- def _codeagent_opt_unsafe_cleanup(self, target_files: List[Path]) -> None:
1535
- """
1536
- 使用 CodeAgent 进行 unsafe 清理优化。
1537
- 使用 clippy 的 missing_safety_doc checker 来查找 unsafe 告警,按文件处理,每次处理一个文件的所有告警。
1538
-
1539
- 注意:CodeAgent 必须在 crate 目录下创建和执行,以确保所有文件操作和命令执行都在正确的上下文中进行。
1540
- """
1541
- crate = self.crate_dir.resolve()
1542
- file_list: List[str] = []
1543
- for p in target_files:
1544
- try:
1545
- rel = p.resolve().relative_to(crate).as_posix()
1546
- except Exception:
1547
- rel = p.as_posix()
1548
- file_list.append(rel)
1549
- self.stats.files_scanned += 1
1550
-
1551
- # 切换到 crate 目录,确保 CodeAgent 在正确的上下文中创建和执行
1552
- prev_cwd = os.getcwd()
1553
- iteration = 0
1554
-
1555
- try:
1556
- os.chdir(str(crate))
1557
-
1558
- # 循环修复 unsafe 告警,按文件处理
1559
- while True:
1560
- iteration += 1
1561
-
1562
- # 检查当前 missing_safety_doc 告警
1563
- has_warnings, current_clippy_output = _check_missing_safety_doc_warnings(crate)
1564
- if not has_warnings:
1565
- typer.secho(f"[c2rust-optimizer][codeagent][unsafe-cleanup] 所有 missing_safety_doc 告警已消除(共迭代 {iteration - 1} 次)", fg=typer.colors.GREEN)
1566
- return # 所有告警已消除
1567
-
1568
- # 按文件提取告警
1569
- warnings_by_file = self._extract_warnings_by_file(current_clippy_output)
1570
- if not warnings_by_file:
1571
- typer.secho("[c2rust-optimizer][codeagent][unsafe-cleanup] 无法提取告警,停止修复", fg=typer.colors.YELLOW)
1572
- return # 仍有告警未消除
1573
-
1574
- # 找到第一个有告警的文件(优先处理目标文件列表中的文件)
1575
- target_file_path = None
1576
- target_warnings = None
1577
-
1578
- # 优先处理目标文件列表中的文件
1579
- for file_rel in file_list:
1580
- # 尝试匹配文件路径(可能是相对路径或绝对路径)
1581
- for file_path, warnings in warnings_by_file.items():
1582
- if file_rel in file_path or file_path.endswith(file_rel):
1583
- target_file_path = file_path
1584
- target_warnings = warnings
1585
- break
1586
- if target_file_path:
1587
- break
1588
-
1589
- # 如果目标文件列表中没有告警,选择第一个有告警的文件
1590
- if not target_file_path:
1591
- target_file_path = next(iter(warnings_by_file.keys()))
1592
- target_warnings = warnings_by_file[target_file_path]
1593
-
1594
- # 获取该文件的所有告警(一次处理一个文件的所有告警)
1595
- warnings_to_fix = target_warnings
1596
- warning_count = len(warnings_to_fix)
1597
-
1598
- typer.secho(f"[c2rust-optimizer][codeagent][unsafe-cleanup] 第 {iteration} 次迭代:修复文件 {target_file_path} 的 {warning_count} 个 missing_safety_doc 告警", fg=typer.colors.CYAN)
1599
-
1600
- # 格式化告警信息
1601
- formatted_warnings = self._format_warnings_for_prompt(warnings_to_fix, max_count=len(warnings_to_fix))
1602
-
1603
- # 构建提示词,修复该文件的所有 missing_safety_doc 告警
1604
- prompt_lines: List[str] = [
1605
- "你是资深 Rust 代码工程师。请在当前 crate 下修复指定文件中的 missing_safety_doc 告警,并以补丁形式输出修改:",
1606
- f"- crate 根目录:{crate}",
1607
- "",
1608
- "本次优化仅允许修改以下文件(严格限制,只处理这一个文件):",
1609
- f"- {target_file_path}",
1610
- "",
1611
- f"重要:本次修复仅修复该文件中的 {warning_count} 个 missing_safety_doc 告警。",
1612
- "",
1613
- "优化目标:",
1614
- f"1) 修复文件 {target_file_path} 中的 {warning_count} 个 missing_safety_doc 告警:",
1615
- " **修复原则:能消除就消除,不能消除才增加 SAFETY 注释**",
1616
- "",
1617
- " 优先级 1(优先尝试):消除 unsafe",
1618
- " - 如果 unsafe 函数或方法实际上不需要是 unsafe 的,应该移除 unsafe 关键字;",
1619
- " - 如果 unsafe 块可以移除,应该移除整个 unsafe 块;",
1620
- " - 如果 unsafe 块可以缩小范围,应该缩小范围;",
1621
- " - 仔细分析代码,判断是否真的需要 unsafe,如果可以通过安全的方式实现,优先使用安全的方式。",
1622
- "",
1623
- " 优先级 2(无法消除时):添加 SAFETY 注释",
1624
- " - 只有在确认无法消除 unsafe 的情况下,才为 unsafe 函数或方法添加 `/// SAFETY: ...` 文档注释;",
1625
- " - SAFETY 注释必须详细说明为什么该函数或方法是 unsafe 的,包括:",
1626
- " * 哪些不变量必须由调用者维护;",
1627
- " * 哪些前提条件必须满足;",
1628
- " * 可能导致未定义行为的情况;",
1629
- " * 为什么不能使用安全的替代方案;",
1630
- " - 如果 unsafe 块无法移除但可以缩小范围,应该缩小范围并在紧邻位置添加 `/// SAFETY: ...` 注释。",
1631
- "",
1632
- "2) 修复已有实现的问题:",
1633
- " - 如果在修复 missing_safety_doc 告警的过程中,发现代码已有的实现有问题(如逻辑错误、潜在 bug、性能问题、内存安全问题等),也需要一并修复;",
1634
- " - 这些问题可能包括但不限于:不正确的 unsafe 使用、未检查的边界条件、资源泄漏、竞态条件、数据竞争等;",
1635
- " - 修复时应该保持最小改动原则,优先修复最严重的问题。",
1636
- "",
1637
- "约束与范围:",
1638
- f"- **仅修改文件 {target_file_path},不要修改其他文件**;除非必须(如修复引用路径),否则不要修改其他文件。",
1639
- "- 保持最小改动,不要进行与修复 missing_safety_doc 告警无关的重构或格式化。",
1640
- f"- **只修复该文件中的 {warning_count} 个 missing_safety_doc 告警,不要修复其他告警**。",
1641
- "- 修改后需保证 `cargo test` 可以通过;如需引入少量配套改动,请一并包含在补丁中以确保通过。",
1642
- "- 输出仅为补丁,不要输出解释或多余文本。",
1643
- "",
1644
- "优先级说明:",
1645
- "- **修复 unsafe 的优先级:能消除就消除,不能消除才增加 SAFETY 注释**;",
1646
- "- 对于每个 unsafe,首先尝试分析是否可以安全地移除,只有在确认无法移除时才添加 SAFETY 注释;",
1647
- "- **如果优化过程中出现了测试不通过或编译错误,必须优先解决这些问题**;",
1648
- "- 在修复告警之前,先确保代码能够正常编译和通过测试;",
1649
- "- 如果修复告警导致了编译错误或测试失败,必须立即修复这些错误,然后再继续优化。",
1650
- "",
1651
- "自检要求:在每次输出补丁后,请使用 execute_script 工具在 crate 根目录执行 `cargo test -q` 进行验证;",
1652
- "若出现编译错误或测试失败,请优先修复这些问题,然后再继续修复告警;",
1653
- "若未通过,请继续输出新的补丁进行最小修复并再次自检,直至 `cargo test` 通过为止。",
1654
- "",
1655
- f"文件 {target_file_path} 中的 missing_safety_doc 告警信息如下:",
1656
- "<WARNINGS>",
1657
- formatted_warnings,
1658
- "</WARNINGS>",
1659
- ]
1660
- prompt = "\n".join(prompt_lines)
1661
- prompt = self._append_additional_notes(prompt)
1662
-
1663
- # 修复前执行 cargo fmt
1664
- _run_cargo_fmt(crate)
1665
-
1666
- # 记录运行前的 commit id
1667
- commit_before = self._get_crate_commit_hash()
1668
-
1669
- # CodeAgent 在 crate 目录下创建和执行
1670
- agent = CodeAgent(name=f"UnsafeCleanupAgent-iter{iteration}", need_summary=False, non_interactive=self.options.non_interactive, model_group=self.options.llm_group)
1671
- # 订阅 BEFORE_TOOL_CALL 和 AFTER_TOOL_CALL 事件,用于细粒度检测测试代码删除
1672
- agent.event_bus.subscribe(BEFORE_TOOL_CALL, self._on_before_tool_call)
1673
- agent.event_bus.subscribe(AFTER_TOOL_CALL, self._on_after_tool_call)
1674
- # 记录 Agent 创建时的 commit id(作为初始值)
1675
- agent_id = id(agent)
1676
- agent_key = f"agent_{agent_id}"
1677
- initial_commit = self._get_crate_commit_hash()
1678
- if initial_commit:
1679
- self._agent_before_commits[agent_key] = initial_commit
1680
- agent.run(prompt, prefix=f"[c2rust-optimizer][codeagent][unsafe-cleanup][iter{iteration}]", suffix="")
1681
-
1682
- # 检测并处理测试代码删除
1683
- if self._check_and_handle_test_deletion(commit_before, agent):
1684
- # 如果回退了,需要重新运行 agent
1685
- typer.secho(f"[c2rust-optimizer][codeagent][unsafe-cleanup] 检测到测试代码删除问题,已回退,重新运行 agent (iter={iteration})", fg=typer.colors.YELLOW)
1686
- commit_before = self._get_crate_commit_hash()
1687
- agent.run(prompt, prefix=f"[c2rust-optimizer][codeagent][unsafe-cleanup][iter{iteration}][retry]", suffix="")
1688
- # 再次检测
1689
- if self._check_and_handle_test_deletion(commit_before, agent):
1690
- typer.secho(f"[c2rust-optimizer][codeagent][unsafe-cleanup] 再次检测到测试代码删除问题,已回退 (iter={iteration})", fg=typer.colors.RED)
1691
-
1692
- # 验证修复是否成功(通过 cargo test)
1693
- ok, _ = _cargo_check_full(crate, self.stats, self.options.max_checks, timeout=self.options.cargo_test_timeout)
1694
- if ok:
1695
- # 修复成功,保存进度和 commit id
1696
- try:
1697
- file_path = crate / target_file_path if not Path(target_file_path).is_absolute() else Path(target_file_path)
1698
- if file_path.exists():
1699
- self._save_fix_progress("unsafe_cleanup", f"{target_file_path}-iter{iteration}", [file_path])
1700
- else:
1701
- self._save_fix_progress("unsafe_cleanup", f"{target_file_path}-iter{iteration}", None)
1702
- except Exception:
1703
- self._save_fix_progress("unsafe_cleanup", f"{target_file_path}-iter{iteration}", None)
1704
- typer.secho(f"[c2rust-optimizer][codeagent][unsafe-cleanup] 文件 {target_file_path} 的 {warning_count} 个告警修复成功,已保存进度", fg=typer.colors.GREEN)
1705
- else:
1706
- # 测试失败,回退到运行前的 commit
1707
- if commit_before:
1708
- typer.secho(f"[c2rust-optimizer][codeagent][unsafe-cleanup] 文件 {target_file_path} 修复后测试失败,回退到运行前的 commit: {commit_before[:8]}", fg=typer.colors.YELLOW)
1709
- if self._reset_to_commit(commit_before):
1710
- typer.secho(f"[c2rust-optimizer][codeagent][unsafe-cleanup] 已成功回退到 commit: {commit_before[:8]}", fg=typer.colors.CYAN)
1711
- else:
1712
- typer.secho("[c2rust-optimizer][codeagent][unsafe-cleanup] 回退失败,请手动检查代码状态", fg=typer.colors.RED)
1713
- else:
1714
- typer.secho(f"[c2rust-optimizer][codeagent][unsafe-cleanup] 文件 {target_file_path} 修复后测试失败,但无法获取运行前的 commit,继续修复", fg=typer.colors.YELLOW)
1715
-
1716
- # 修复后再次检查告警
1717
- has_warnings_after, _ = _check_missing_safety_doc_warnings(crate)
1718
- if not has_warnings_after:
1719
- typer.secho(f"[c2rust-optimizer][codeagent][unsafe-cleanup] 所有 missing_safety_doc 告警已消除(共迭代 {iteration} 次)", fg=typer.colors.GREEN)
1720
- return # 所有告警已消除
1721
-
1722
- finally:
1723
- os.chdir(prev_cwd)
1724
-
1725
- # ========== 2) visibility optimization (CodeAgent) ==========
1726
-
1727
- def _codeagent_opt_visibility(self, target_files: List[Path]) -> None:
1728
- """
1729
- 使用 CodeAgent 进行可见性优化。
1730
-
1731
- 注意:CodeAgent 必须在 crate 目录下创建和执行,以确保所有文件操作和命令执行都在正确的上下文中进行。
1732
- """
1733
- crate = self.crate_dir.resolve()
1734
- file_list: List[str] = []
1735
- for p in target_files:
1736
- try:
1737
- rel = p.resolve().relative_to(crate).as_posix()
1738
- except Exception:
1739
- rel = p.as_posix()
1740
- file_list.append(rel)
1741
- self.stats.files_scanned += 1
1742
-
1743
- prompt_lines: List[str] = [
1744
- "你是资深 Rust 代码工程师。请在当前 crate 下执行可见性优化,并以补丁形式输出修改:",
1745
- f"- crate 根目录:{crate}",
1746
- "",
1747
- "本次优化仅允许修改以下文件范围(严格限制):",
1748
- *[f"- {rel}" for rel in file_list],
1749
- "",
1750
- "优化目标:",
1751
- "1) 可见性最小化:",
1752
- " - 优先将 `pub fn` 降为 `pub(crate) fn`(如果函数仅在 crate 内部使用);",
1753
- " - 保持对外接口(跨 crate 使用的接口,如 lib.rs 中的顶层导出)为 `pub`;",
1754
- " - 在 lib.rs 中的顶层导出保持现状,不要修改。",
1755
- "",
1756
- "2) 修复已有实现的问题:",
1757
- " - 如果在进行可见性优化的过程中,发现代码已有的实现有问题(如逻辑错误、潜在 bug、性能问题、内存安全问题等),也需要一并修复;",
1758
- " - 这些问题可能包括但不限于:不正确的可见性设计、未检查的边界条件、资源泄漏、竞态条件等;",
1759
- " - 修复时应该保持最小改动原则,优先修复最严重的问题。",
1760
- "",
1761
- "约束与范围:",
1762
- "- 仅修改上述列出的文件;除非必须(如修复引用路径),否则不要修改其他文件。",
1763
- "- 保持最小改动,不要进行与可见性优化无关的重构或格式化。",
1764
- "- 修改后需保证 `cargo test` 可以通过;如需引入少量配套改动,请一并包含在补丁中以确保通过。",
1765
- "- 输出仅为补丁,不要输出解释或多余文本。",
1766
- "",
1767
- "优先级说明:",
1768
- "- **如果优化过程中出现了测试不通过或编译错误,必须优先解决这些问题**;",
1769
- "- 在进行可见性优化之前,先确保代码能够正常编译和通过测试;",
1770
- "- 如果可见性优化导致了编译错误或测试失败,必须立即修复这些错误,然后再继续优化。",
1771
- "",
1772
- "自检要求:在每次输出补丁后,请使用 execute_script 工具在 crate 根目录执行 `cargo test -q` 进行验证;",
1773
- "若出现编译错误或测试失败,请优先修复这些问题,然后再继续可见性优化;",
1774
- "若未通过,请继续输出新的补丁进行最小修复并再次自检,直至 `cargo test` 通过为止。"
1775
- ]
1776
- prompt = "\n".join(prompt_lines)
1777
- prompt = self._append_additional_notes(prompt)
1778
- # 切换到 crate 目录,确保 CodeAgent 在正确的上下文中创建和执行
1779
- prev_cwd = os.getcwd()
1780
- typer.secho("[c2rust-optimizer][codeagent][visibility] 正在调用 CodeAgent 进行可见性优化...", fg=typer.colors.CYAN)
1781
- try:
1782
- os.chdir(str(crate))
1783
- # 修复前执行 cargo fmt
1784
- _run_cargo_fmt(crate)
1785
-
1786
- # 记录运行前的 commit id
1787
- commit_before = self._get_crate_commit_hash()
1788
-
1789
- # CodeAgent 在 crate 目录下创建和执行
1790
- agent = CodeAgent(name="VisibilityOptimizer", need_summary=False, non_interactive=self.options.non_interactive, model_group=self.options.llm_group)
1791
- # 订阅 BEFORE_TOOL_CALL 和 AFTER_TOOL_CALL 事件,用于细粒度检测测试代码删除
1792
- agent.event_bus.subscribe(BEFORE_TOOL_CALL, self._on_before_tool_call)
1793
- agent.event_bus.subscribe(AFTER_TOOL_CALL, self._on_after_tool_call)
1794
- # 记录 Agent 创建时的 commit id(作为初始值)
1795
- agent_id = id(agent)
1796
- agent_key = f"agent_{agent_id}"
1797
- initial_commit = self._get_crate_commit_hash()
1798
- if initial_commit:
1799
- self._agent_before_commits[agent_key] = initial_commit
1800
- agent.run(prompt, prefix="[c2rust-optimizer][codeagent][visibility]", suffix="")
1801
-
1802
- # 检测并处理测试代码删除
1803
- if self._check_and_handle_test_deletion(commit_before, agent):
1804
- # 如果回退了,需要重新运行 agent
1805
- typer.secho("[c2rust-optimizer][codeagent][visibility] 检测到测试代码删除问题,已回退,重新运行 agent", fg=typer.colors.YELLOW)
1806
- commit_before = self._get_crate_commit_hash()
1807
- agent.run(prompt, prefix="[c2rust-optimizer][codeagent][visibility][retry]", suffix="")
1808
- # 再次检测
1809
- if self._check_and_handle_test_deletion(commit_before, agent):
1810
- typer.secho("[c2rust-optimizer][codeagent][visibility] 再次检测到测试代码删除问题,已回退", fg=typer.colors.RED)
1811
-
1812
- # 验证修复是否成功(通过 cargo test)
1813
- ok, _ = _cargo_check_full(crate, self.stats, self.options.max_checks, timeout=self.options.cargo_test_timeout)
1814
- if ok:
1815
- # 修复成功,保存进度和 commit id
1816
- file_paths = [crate / f for f in file_list if (crate / f).exists()]
1817
- self._save_fix_progress("visibility_opt", "batch", file_paths if file_paths else None)
1818
- typer.secho("[c2rust-optimizer][codeagent][visibility] 可见性优化成功,已保存进度", fg=typer.colors.GREEN)
1819
- else:
1820
- # 测试失败,回退到运行前的 commit
1821
- if commit_before:
1822
- typer.secho(f"[c2rust-optimizer][codeagent][visibility] 可见性优化后测试失败,回退到运行前的 commit: {commit_before[:8]}", fg=typer.colors.YELLOW)
1823
- if self._reset_to_commit(commit_before):
1824
- typer.secho(f"[c2rust-optimizer][codeagent][visibility] 已成功回退到 commit: {commit_before[:8]}", fg=typer.colors.CYAN)
1825
- else:
1826
- typer.secho("[c2rust-optimizer][codeagent][visibility] 回退失败,请手动检查代码状态", fg=typer.colors.RED)
1827
- else:
1828
- typer.secho("[c2rust-optimizer][codeagent][visibility] 可见性优化后测试失败,但无法获取运行前的 commit", fg=typer.colors.YELLOW)
1829
- finally:
1830
- os.chdir(prev_cwd)
1831
-
1832
- # ========== 3) doc augmentation (CodeAgent) ==========
1833
-
1834
- def _codeagent_opt_docs(self, target_files: List[Path]) -> None:
1835
- """
1836
- 使用 CodeAgent 进行文档补充。
1837
-
1838
- 注意:CodeAgent 必须在 crate 目录下创建和执行,以确保所有文件操作和命令执行都在正确的上下文中进行。
1839
- """
1840
- crate = self.crate_dir.resolve()
1841
- file_list: List[str] = []
1842
- for p in target_files:
1843
- try:
1844
- rel = p.resolve().relative_to(crate).as_posix()
1845
- except Exception:
1846
- rel = p.as_posix()
1847
- file_list.append(rel)
1848
- self.stats.files_scanned += 1
1849
-
1850
- prompt_lines: List[str] = [
1851
- "你是资深 Rust 代码工程师。请在当前 crate 下执行文档补充优化,并以补丁形式输出修改:",
1852
- f"- crate 根目录:{crate}",
1853
- "",
1854
- "本次优化仅允许修改以下文件范围(严格限制):",
1855
- *[f"- {rel}" for rel in file_list],
1856
- "",
1857
- "优化目标:",
1858
- "1) 文档补充:",
1859
- " - 为缺少模块级文档的文件添加 `//! ...` 模块文档注释(放在文件开头);",
1860
- " - 为缺少函数文档的公共函数(pub 或 pub(crate))添加 `/// ...` 文档注释;",
1861
- " - 文档内容可以是占位注释(如 `//! TODO: Add module-level documentation` 或 `/// TODO: Add documentation`),也可以根据函数签名和实现提供简要说明。",
1862
- "",
1863
- "2) 修复已有实现的问题:",
1864
- " - 如果在进行文档补充的过程中,发现代码已有的实现有问题(如逻辑错误、潜在 bug、性能问题、内存安全问题等),也需要一并修复;",
1865
- " - 这些问题可能包括但不限于:不正确的算法实现、未检查的边界条件、资源泄漏、竞态条件等;",
1866
- " - 修复时应该保持最小改动原则,优先修复最严重的问题。",
1867
- "",
1868
- "约束与范围:",
1869
- "- 仅修改上述列出的文件;除非必须(如修复引用路径),否则不要修改其他文件。",
1870
- "- 保持最小改动,不要进行与文档补充无关的重构或格式化。",
1871
- "- 修改后需保证 `cargo test` 可以通过;如需引入少量配套改动,请一并包含在补丁中以确保通过。",
1872
- "- 输出仅为补丁,不要输出解释或多余文本。",
1873
- "",
1874
- "优先级说明:",
1875
- "- **如果优化过程中出现了测试不通过或编译错误,必须优先解决这些问题**;",
1876
- "- 在进行文档补充之前,先确保代码能够正常编译和通过测试;",
1877
- "- 如果文档补充导致了编译错误或测试失败,必须立即修复这些错误,然后再继续优化。",
1878
- "",
1879
- "自检要求:在每次输出补丁后,请使用 execute_script 工具在 crate 根目录执行 `cargo test -q` 进行验证;",
1880
- "若出现编译错误或测试失败,请优先修复这些问题,然后再继续文档补充;",
1881
- "若未通过,请继续输出新的补丁进行最小修复并再次自检,直至 `cargo test` 通过为止。"
1882
- ]
1883
- prompt = "\n".join(prompt_lines)
1884
- prompt = self._append_additional_notes(prompt)
1885
- # 切换到 crate 目录,确保 CodeAgent 在正确的上下文中创建和执行
1886
- prev_cwd = os.getcwd()
1887
- typer.secho("[c2rust-optimizer][codeagent][doc] 正在调用 CodeAgent 进行文档补充...", fg=typer.colors.CYAN)
1888
- try:
1889
- os.chdir(str(crate))
1890
- # 修复前执行 cargo fmt
1891
- _run_cargo_fmt(crate)
1892
-
1893
- # 记录运行前的 commit id
1894
- commit_before = self._get_crate_commit_hash()
1895
-
1896
- # CodeAgent 在 crate 目录下创建和执行
1897
- agent = CodeAgent(name="DocumentationAgent", need_summary=False, non_interactive=self.options.non_interactive, model_group=self.options.llm_group)
1898
- # 订阅 BEFORE_TOOL_CALL 和 AFTER_TOOL_CALL 事件,用于细粒度检测测试代码删除
1899
- agent.event_bus.subscribe(BEFORE_TOOL_CALL, self._on_before_tool_call)
1900
- agent.event_bus.subscribe(AFTER_TOOL_CALL, self._on_after_tool_call)
1901
- # 记录 Agent 创建时的 commit id(作为初始值)
1902
- agent_id = id(agent)
1903
- agent_key = f"agent_{agent_id}"
1904
- initial_commit = self._get_crate_commit_hash()
1905
- if initial_commit:
1906
- self._agent_before_commits[agent_key] = initial_commit
1907
- agent.run(prompt, prefix="[c2rust-optimizer][codeagent][doc]", suffix="")
1908
-
1909
- # 检测并处理测试代码删除
1910
- if self._check_and_handle_test_deletion(commit_before, agent):
1911
- # 如果回退了,需要重新运行 agent
1912
- typer.secho("[c2rust-optimizer][codeagent][doc] 检测到测试代码删除问题,已回退,重新运行 agent", fg=typer.colors.YELLOW)
1913
- commit_before = self._get_crate_commit_hash()
1914
- agent.run(prompt, prefix="[c2rust-optimizer][codeagent][doc][retry]", suffix="")
1915
- # 再次检测
1916
- if self._check_and_handle_test_deletion(commit_before, agent):
1917
- typer.secho("[c2rust-optimizer][codeagent][doc] 再次检测到测试代码删除问题,已回退", fg=typer.colors.RED)
1918
-
1919
- # 验证修复是否成功(通过 cargo test)
1920
- ok, _ = _cargo_check_full(crate, self.stats, self.options.max_checks, timeout=self.options.cargo_test_timeout)
1921
- if ok:
1922
- # 修复成功,保存进度和 commit id
1923
- file_paths = [crate / f for f in file_list if (crate / f).exists()]
1924
- self._save_fix_progress("doc_opt", "batch", file_paths if file_paths else None)
1925
- typer.secho("[c2rust-optimizer][codeagent][doc] 文档补充成功,已保存进度", fg=typer.colors.GREEN)
1926
- else:
1927
- # 测试失败,回退到运行前的 commit
1928
- if commit_before:
1929
- typer.secho(f"[c2rust-optimizer][codeagent][doc] 文档补充后测试失败,回退到运行前的 commit: {commit_before[:8]}", fg=typer.colors.YELLOW)
1930
- if self._reset_to_commit(commit_before):
1931
- typer.secho(f"[c2rust-optimizer][codeagent][doc] 已成功回退到 commit: {commit_before[:8]}", fg=typer.colors.CYAN)
1932
- else:
1933
- typer.secho("[c2rust-optimizer][codeagent][doc] 回退失败,请手动检查代码状态", fg=typer.colors.RED)
1934
- else:
1935
- typer.secho("[c2rust-optimizer][codeagent][doc] 文档补充后测试失败,但无法获取运行前的 commit", fg=typer.colors.YELLOW)
1936
- finally:
1937
- os.chdir(prev_cwd)
1938
-
1939
- def _build_fix_loop(self, scope_files: List[Path]) -> bool:
1940
- """
1941
- 循环执行 cargo check 并用 CodeAgent 进行最小修复,直到通过或达到重试上限或检查预算耗尽。
1942
- 仅允许(优先)修改 scope_files(除非确有必要),以支持分批优化。
1943
- 返回 True 表示修复成功构建通过;False 表示未能在限制内修复。
1944
-
1945
- 注意:CodeAgent 必须在 crate 目录下创建和执行,以确保所有文件操作和命令执行都在正确的上下文中进行。
1946
- """
1947
- maxr = int(self.options.build_fix_retries or 0)
1948
- if maxr <= 0:
1949
- return False
1950
- crate = self.crate_dir.resolve()
1951
- allowed: List[str] = []
1952
- for p in scope_files:
1953
- try:
1954
- rel = p.resolve().relative_to(crate).as_posix()
1955
- except Exception:
1956
- rel = p.as_posix()
1957
- allowed.append(rel)
1958
-
1959
- attempt = 0
1960
- while True:
1961
- # 检查预算
1962
- if self.options.max_checks and self.stats.cargo_checks >= self.options.max_checks:
1963
- return False
1964
- # 执行构建
1965
- output = ""
1966
- try:
1967
- res = subprocess.run(
1968
- ["cargo", "test", "-q"],
1969
- capture_output=True,
1970
- text=True,
1971
- check=False,
1972
- cwd=str(crate),
1973
- timeout=self.options.cargo_test_timeout if self.options.cargo_test_timeout > 0 else None,
1974
- )
1975
- self.stats.cargo_checks += 1
1976
- if res.returncode == 0:
1977
- typer.secho("[c2rust-optimizer][build-fix] 构建修复成功。", fg=typer.colors.GREEN)
1978
- return True
1979
- output = ((res.stdout or "") + ("\n" + (res.stderr or ""))).strip()
1980
- except subprocess.TimeoutExpired as e:
1981
- self.stats.cargo_checks += 1
1982
- out_s = e.stdout.decode("utf-8", errors="ignore") if e.stdout else ""
1983
- err_s = e.stderr.decode("utf-8", errors="ignore") if e.stderr else ""
1984
- output = f"cargo test timed out after {self.options.cargo_test_timeout} seconds"
1985
- full_output = (out_s + ("\n" + err_s if err_s else "")).strip()
1986
- if full_output:
1987
- output += f"\nOutput:\n{full_output}"
1988
- except Exception as e:
1989
- self.stats.cargo_checks += 1
1990
- output = f"cargo test exception: {e}"
1991
-
1992
- # 达到重试上限则失败
1993
- attempt += 1
1994
- if attempt > maxr:
1995
- typer.secho("[c2rust-optimizer][build-fix] 构建修复重试次数已用尽。", fg=typer.colors.RED)
1996
- return False
302
+ PrettyOutput.auto_print(
303
+ f"✅ [c2rust-optimizer] 优化流程结束。报告已生成于: {report_display}",
304
+ color="green",
305
+ )
306
+ write_final_report(report_path, self.stats)
307
+ return self.stats
1997
308
 
1998
- typer.secho(f"[c2rust-optimizer][build-fix] 构建失败。正在尝试使用 CodeAgent 进行修复 (第 {attempt}/{maxr} 次尝试)...", fg=typer.colors.YELLOW)
1999
- # 生成最小修复提示
2000
- prompt_lines = [
2001
- "请根据以下测试/构建错误对 crate 进行最小必要的修复以通过 `cargo test`:",
2002
- f"- crate 根目录:{crate}",
2003
- "",
2004
- "本次修复优先且仅允许修改以下文件(除非确有必要,否则不要修改范围外文件):",
2005
- *[f"- {rel}" for rel in allowed],
2006
- "",
2007
- "约束与范围:",
2008
- "- 保持最小改动,不要进行与错误无关的重构或格式化;",
2009
- "- 仅输出补丁,不要输出解释或多余文本。",
2010
- "",
2011
- "优化目标:",
2012
- "1) 修复构建/测试错误:",
2013
- " - 必须优先解决所有编译错误和测试失败问题;",
2014
- " - 修复时应该先解决编译错误,然后再解决测试失败;",
2015
- " - 如果修复过程中引入了新的错误,必须立即修复这些新错误。",
2016
- "",
2017
- "2) 修复已有实现的问题:",
2018
- " - 如果在修复构建/测试错误的过程中,发现代码已有的实现有问题(如逻辑错误、潜在 bug、性能问题、内存安全问题等),也需要一并修复;",
2019
- " - 这些问题可能包括但不限于:不正确的算法实现、未检查的边界条件、资源泄漏、竞态条件、数据竞争等;",
2020
- " - 修复时应该保持最小改动原则,优先修复最严重的问题。",
2021
- "",
2022
- "优先级说明:",
2023
- "- **必须优先解决所有编译错误和测试失败问题**;",
2024
- "- 修复时应该先解决编译错误,然后再解决测试失败;",
2025
- "- 如果修复过程中引入了新的错误,必须立即修复这些新错误。",
2026
- "",
2027
- "自检要求:在每次输出补丁后,请使用 execute_script 工具在 crate 根目录执行 `cargo test -q` 进行验证;",
2028
- "若出现编译错误或测试失败,请优先修复这些问题;",
2029
- "若未通过,请继续输出新的补丁进行最小修复并再次自检,直至 `cargo test` 通过为止。",
2030
- "",
2031
- "构建错误如下:",
2032
- "<BUILD_ERROR>",
2033
- output,
2034
- "</BUILD_ERROR>",
2035
- ]
2036
- prompt = "\n".join(prompt_lines)
2037
- prompt = self._append_additional_notes(prompt)
2038
- # 切换到 crate 目录,确保 CodeAgent 在正确的上下文中创建和执行
2039
- prev_cwd = os.getcwd()
2040
- try:
2041
- os.chdir(str(crate))
2042
- # 修复前执行 cargo fmt
2043
- _run_cargo_fmt(crate)
2044
-
2045
- # 记录运行前的 commit id
2046
- commit_before = self._get_crate_commit_hash()
2047
-
2048
- # CodeAgent 在 crate 目录下创建和执行
2049
- agent = CodeAgent(name=f"BuildFixAgent-iter{attempt}", need_summary=False, non_interactive=self.options.non_interactive, model_group=self.options.llm_group)
2050
- # 订阅 BEFORE_TOOL_CALL 和 AFTER_TOOL_CALL 事件,用于细粒度检测测试代码删除
2051
- agent.event_bus.subscribe(BEFORE_TOOL_CALL, self._on_before_tool_call)
2052
- agent.event_bus.subscribe(AFTER_TOOL_CALL, self._on_after_tool_call)
2053
- # 记录 Agent 创建时的 commit id(作为初始值)
2054
- agent_id = id(agent)
2055
- agent_key = f"agent_{agent_id}"
2056
- initial_commit = self._get_crate_commit_hash()
2057
- if initial_commit:
2058
- self._agent_before_commits[agent_key] = initial_commit
2059
- agent.run(prompt, prefix=f"[c2rust-optimizer][build-fix iter={attempt}]", suffix="")
2060
-
2061
- # 检测并处理测试代码删除
2062
- if self._check_and_handle_test_deletion(commit_before, agent):
2063
- # 如果回退了,需要重新运行 agent
2064
- typer.secho(f"[c2rust-optimizer][build-fix] 检测到测试代码删除问题,已回退,重新运行 agent (iter={attempt})", fg=typer.colors.YELLOW)
2065
- commit_before = self._get_crate_commit_hash()
2066
- agent.run(prompt, prefix=f"[c2rust-optimizer][build-fix iter={attempt}][retry]", suffix="")
2067
- # 再次检测
2068
- if self._check_and_handle_test_deletion(commit_before, agent):
2069
- typer.secho(f"[c2rust-optimizer][build-fix] 再次检测到测试代码删除问题,已回退 (iter={attempt})", fg=typer.colors.RED)
2070
-
2071
- # 验证修复是否成功(通过 cargo test)
2072
- ok, _ = _cargo_check_full(crate, self.stats, self.options.max_checks, timeout=self.options.cargo_test_timeout)
2073
- if ok:
2074
- # 修复成功,保存进度和 commit id
2075
- file_paths = [crate / f for f in allowed if (crate / f).exists()]
2076
- self._save_fix_progress("build_fix", f"iter{attempt}", file_paths if file_paths else None)
2077
- typer.secho(f"[c2rust-optimizer][build-fix] 第 {attempt} 次修复成功,已保存进度", fg=typer.colors.GREEN)
2078
- # 返回 True 表示修复成功
2079
- return True
2080
- else:
2081
- # 测试失败,回退到运行前的 commit
2082
- if commit_before:
2083
- typer.secho(f"[c2rust-optimizer][build-fix] 第 {attempt} 次修复后测试失败,回退到运行前的 commit: {commit_before[:8]}", fg=typer.colors.YELLOW)
2084
- if self._reset_to_commit(commit_before):
2085
- typer.secho(f"[c2rust-optimizer][build-fix] 已成功回退到 commit: {commit_before[:8]}", fg=typer.colors.CYAN)
2086
- else:
2087
- typer.secho("[c2rust-optimizer][build-fix] 回退失败,请手动检查代码状态", fg=typer.colors.RED)
2088
- else:
2089
- typer.secho(f"[c2rust-optimizer][build-fix] 第 {attempt} 次修复后测试失败,但无法获取运行前的 commit,继续尝试", fg=typer.colors.YELLOW)
2090
- finally:
2091
- os.chdir(prev_cwd)
309
+ # Clippy、Unsafe、Visibility、Docs BuildFix 相关方法已迁移到各自的模块
310
+ # 向后兼容方法已删除,请直接使用各模块中的对应方法
2092
311
 
2093
- return False
2094
312
 
2095
313
  def optimize_project(
2096
314
  project_root: Optional[Path] = None,
@@ -2125,13 +343,13 @@ def optimize_project(
2125
343
  """
2126
344
  # 如果 project_root 为 None,尝试从当前目录查找
2127
345
  if project_root is None:
2128
- project_root = _find_project_root()
346
+ project_root = find_project_root()
2129
347
  if project_root is None:
2130
348
  # 如果找不到项目根目录,使用当前目录
2131
349
  project_root = Path(".").resolve()
2132
350
  else:
2133
351
  project_root = Path(project_root).resolve()
2134
-
352
+
2135
353
  # 如果 crate_dir 为 None,使用 detect_crate_dir 自动检测
2136
354
  # detect_crate_dir 内部已经包含了从项目根目录推断的逻辑
2137
355
  crate = detect_crate_dir(crate_dir)
@@ -2154,4 +372,4 @@ def optimize_project(
2154
372
  )
2155
373
  optimizer = Optimizer(crate, opts, project_root=project_root)
2156
374
  stats = optimizer.run()
2157
- return asdict(stats)
375
+ return asdict(stats)