jarvis-ai-assistant 0.7.0__py3-none-any.whl → 0.7.6__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 (159) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +243 -139
  3. jarvis/jarvis_agent/agent_manager.py +5 -10
  4. jarvis/jarvis_agent/builtin_input_handler.py +2 -6
  5. jarvis/jarvis_agent/config_editor.py +2 -7
  6. jarvis/jarvis_agent/event_bus.py +82 -12
  7. jarvis/jarvis_agent/file_context_handler.py +265 -15
  8. jarvis/jarvis_agent/file_methodology_manager.py +3 -4
  9. jarvis/jarvis_agent/jarvis.py +113 -98
  10. jarvis/jarvis_agent/language_extractors/__init__.py +57 -0
  11. jarvis/jarvis_agent/language_extractors/c_extractor.py +21 -0
  12. jarvis/jarvis_agent/language_extractors/cpp_extractor.py +21 -0
  13. jarvis/jarvis_agent/language_extractors/go_extractor.py +21 -0
  14. jarvis/jarvis_agent/language_extractors/java_extractor.py +84 -0
  15. jarvis/jarvis_agent/language_extractors/javascript_extractor.py +79 -0
  16. jarvis/jarvis_agent/language_extractors/python_extractor.py +21 -0
  17. jarvis/jarvis_agent/language_extractors/rust_extractor.py +21 -0
  18. jarvis/jarvis_agent/language_extractors/typescript_extractor.py +84 -0
  19. jarvis/jarvis_agent/language_support_info.py +486 -0
  20. jarvis/jarvis_agent/main.py +6 -12
  21. jarvis/jarvis_agent/memory_manager.py +7 -16
  22. jarvis/jarvis_agent/methodology_share_manager.py +10 -16
  23. jarvis/jarvis_agent/prompt_manager.py +1 -1
  24. jarvis/jarvis_agent/prompts.py +193 -171
  25. jarvis/jarvis_agent/protocols.py +8 -12
  26. jarvis/jarvis_agent/run_loop.py +77 -14
  27. jarvis/jarvis_agent/session_manager.py +2 -3
  28. jarvis/jarvis_agent/share_manager.py +12 -21
  29. jarvis/jarvis_agent/shell_input_handler.py +1 -2
  30. jarvis/jarvis_agent/task_analyzer.py +26 -4
  31. jarvis/jarvis_agent/task_manager.py +11 -27
  32. jarvis/jarvis_agent/tool_executor.py +2 -3
  33. jarvis/jarvis_agent/tool_share_manager.py +12 -24
  34. jarvis/jarvis_agent/web_server.py +55 -20
  35. jarvis/jarvis_c2rust/__init__.py +5 -5
  36. jarvis/jarvis_c2rust/cli.py +461 -499
  37. jarvis/jarvis_c2rust/collector.py +45 -53
  38. jarvis/jarvis_c2rust/constants.py +26 -0
  39. jarvis/jarvis_c2rust/library_replacer.py +264 -132
  40. jarvis/jarvis_c2rust/llm_module_agent.py +162 -190
  41. jarvis/jarvis_c2rust/loaders.py +207 -0
  42. jarvis/jarvis_c2rust/models.py +28 -0
  43. jarvis/jarvis_c2rust/optimizer.py +1592 -395
  44. jarvis/jarvis_c2rust/transpiler.py +1722 -1064
  45. jarvis/jarvis_c2rust/utils.py +385 -0
  46. jarvis/jarvis_code_agent/build_validation_config.py +2 -3
  47. jarvis/jarvis_code_agent/code_agent.py +394 -320
  48. jarvis/jarvis_code_agent/code_analyzer/__init__.py +3 -0
  49. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +4 -0
  50. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +17 -2
  51. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +3 -0
  52. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +36 -4
  53. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +9 -0
  54. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +9 -0
  55. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +12 -1
  56. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +22 -5
  57. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +57 -32
  58. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +62 -6
  59. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +8 -9
  60. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +290 -5
  61. jarvis/jarvis_code_agent/code_analyzer/language_support.py +21 -0
  62. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +21 -3
  63. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +72 -4
  64. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +35 -3
  65. jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +212 -0
  66. jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +254 -0
  67. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +52 -2
  68. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +73 -1
  69. jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +280 -0
  70. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +306 -152
  71. jarvis/jarvis_code_agent/code_analyzer/structured_code.py +556 -0
  72. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +193 -18
  73. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +18 -8
  74. jarvis/jarvis_code_agent/lint.py +258 -27
  75. jarvis/jarvis_code_agent/utils.py +0 -1
  76. jarvis/jarvis_code_analysis/code_review.py +19 -24
  77. jarvis/jarvis_data/config_schema.json +53 -26
  78. jarvis/jarvis_git_squash/main.py +4 -5
  79. jarvis/jarvis_git_utils/git_commiter.py +44 -49
  80. jarvis/jarvis_mcp/sse_mcp_client.py +20 -27
  81. jarvis/jarvis_mcp/stdio_mcp_client.py +11 -12
  82. jarvis/jarvis_mcp/streamable_mcp_client.py +15 -14
  83. jarvis/jarvis_memory_organizer/memory_organizer.py +55 -74
  84. jarvis/jarvis_methodology/main.py +32 -48
  85. jarvis/jarvis_multi_agent/__init__.py +79 -61
  86. jarvis/jarvis_multi_agent/main.py +3 -7
  87. jarvis/jarvis_platform/base.py +469 -199
  88. jarvis/jarvis_platform/human.py +7 -8
  89. jarvis/jarvis_platform/kimi.py +30 -36
  90. jarvis/jarvis_platform/openai.py +65 -27
  91. jarvis/jarvis_platform/registry.py +26 -10
  92. jarvis/jarvis_platform/tongyi.py +24 -25
  93. jarvis/jarvis_platform/yuanbao.py +31 -42
  94. jarvis/jarvis_platform_manager/main.py +66 -77
  95. jarvis/jarvis_platform_manager/service.py +8 -13
  96. jarvis/jarvis_rag/cli.py +49 -51
  97. jarvis/jarvis_rag/embedding_manager.py +13 -18
  98. jarvis/jarvis_rag/llm_interface.py +8 -9
  99. jarvis/jarvis_rag/query_rewriter.py +10 -21
  100. jarvis/jarvis_rag/rag_pipeline.py +24 -27
  101. jarvis/jarvis_rag/reranker.py +4 -5
  102. jarvis/jarvis_rag/retriever.py +28 -30
  103. jarvis/jarvis_sec/__init__.py +220 -3520
  104. jarvis/jarvis_sec/agents.py +143 -0
  105. jarvis/jarvis_sec/analysis.py +276 -0
  106. jarvis/jarvis_sec/cli.py +29 -6
  107. jarvis/jarvis_sec/clustering.py +1439 -0
  108. jarvis/jarvis_sec/file_manager.py +427 -0
  109. jarvis/jarvis_sec/parsers.py +73 -0
  110. jarvis/jarvis_sec/prompts.py +268 -0
  111. jarvis/jarvis_sec/report.py +83 -4
  112. jarvis/jarvis_sec/review.py +453 -0
  113. jarvis/jarvis_sec/utils.py +499 -0
  114. jarvis/jarvis_sec/verification.py +848 -0
  115. jarvis/jarvis_sec/workflow.py +7 -0
  116. jarvis/jarvis_smart_shell/main.py +38 -87
  117. jarvis/jarvis_stats/cli.py +1 -1
  118. jarvis/jarvis_stats/stats.py +7 -7
  119. jarvis/jarvis_stats/storage.py +15 -21
  120. jarvis/jarvis_tools/clear_memory.py +3 -20
  121. jarvis/jarvis_tools/cli/main.py +20 -23
  122. jarvis/jarvis_tools/edit_file.py +1066 -0
  123. jarvis/jarvis_tools/execute_script.py +42 -21
  124. jarvis/jarvis_tools/file_analyzer.py +6 -9
  125. jarvis/jarvis_tools/generate_new_tool.py +11 -20
  126. jarvis/jarvis_tools/lsp_client.py +1552 -0
  127. jarvis/jarvis_tools/methodology.py +2 -3
  128. jarvis/jarvis_tools/read_code.py +1525 -87
  129. jarvis/jarvis_tools/read_symbols.py +2 -3
  130. jarvis/jarvis_tools/read_webpage.py +7 -10
  131. jarvis/jarvis_tools/registry.py +370 -181
  132. jarvis/jarvis_tools/retrieve_memory.py +20 -19
  133. jarvis/jarvis_tools/rewrite_file.py +105 -0
  134. jarvis/jarvis_tools/save_memory.py +3 -15
  135. jarvis/jarvis_tools/search_web.py +3 -7
  136. jarvis/jarvis_tools/sub_agent.py +17 -6
  137. jarvis/jarvis_tools/sub_code_agent.py +14 -16
  138. jarvis/jarvis_tools/virtual_tty.py +54 -32
  139. jarvis/jarvis_utils/clipboard.py +7 -10
  140. jarvis/jarvis_utils/config.py +98 -63
  141. jarvis/jarvis_utils/embedding.py +5 -5
  142. jarvis/jarvis_utils/fzf.py +8 -8
  143. jarvis/jarvis_utils/git_utils.py +81 -67
  144. jarvis/jarvis_utils/input.py +24 -49
  145. jarvis/jarvis_utils/jsonnet_compat.py +465 -0
  146. jarvis/jarvis_utils/methodology.py +33 -35
  147. jarvis/jarvis_utils/utils.py +245 -202
  148. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/METADATA +205 -70
  149. jarvis_ai_assistant-0.7.6.dist-info/RECORD +218 -0
  150. jarvis/jarvis_agent/edit_file_handler.py +0 -584
  151. jarvis/jarvis_agent/rewrite_file_handler.py +0 -141
  152. jarvis/jarvis_agent/task_planner.py +0 -496
  153. jarvis/jarvis_platform/ai8.py +0 -332
  154. jarvis/jarvis_tools/ask_user.py +0 -54
  155. jarvis_ai_assistant-0.7.0.dist-info/RECORD +0 -192
  156. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/WHEEL +0 -0
  157. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/entry_points.txt +0 -0
  158. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/licenses/LICENSE +0 -0
  159. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/top_level.txt +0 -0
@@ -2,56 +2,58 @@
2
2
  """
3
3
  Rust 代码优化器:对转译或生成后的 Rust 项目执行若干保守优化步骤。
4
4
 
5
+ 所有优化步骤均使用 CodeAgent 完成,确保智能、准确且可回退。
6
+
5
7
  目标与策略(保守、可回退):
6
8
  1) unsafe 清理:
7
- - 识别可移除的 `unsafe { ... }` 包裹,尝试移除后执行 `cargo check`
8
- - 若编译失败,回滚该处修改,并在该块或相邻函数前添加 `/// SAFETY: ` 说明
9
- 2) 代码结构优化(重复代码提示/最小消除):
10
- - 基于文本的简单函数重复检测(签名 + 主体文本),为重复体添加 TODO 文档提示
11
- - CodeAgent 阶段,允许最小化抽取公共辅助函数以消除重复(若易于安全完成)
12
- 3) 可见性优化(尽可能最小可见性):
13
- - `pub fn` 尝试降为 `pub(crate) fn`,变更后执行 `cargo check` 验证
14
- - 若失败回滚
15
- - 在 CodeAgent 阶段,允许在不破坏 API 的前提下进一步减少可见性(保持对外接口为 pub)
16
- 4) 文档补充:
17
- - 为缺少文档的模块/函数添加基础占位文档
9
+ - 使用 CodeAgent 识别可移除的 `unsafe { ... }` 包裹,移除后执行 `cargo test` 验证
10
+ - 若必须保留 unsafe,缩小范围并在紧邻位置添加 `/// SAFETY: ...` 文档注释说明理由
11
+ 2) 可见性优化(尽可能最小可见性):
12
+ - 使用 CodeAgent `pub fn` 降为 `pub(crate) fn`(如果函数仅在 crate 内部使用)
13
+ - 保持对外接口(跨 crate 使用的接口,如 lib.rs 中的顶层导出)为 `pub`
14
+ 3) 文档补充:
15
+ - 使用 CodeAgent 为缺少模块级文档的文件添加 `//! ...` 模块文档注释
16
+ - 为缺少函数文档的公共函数添加 `/// ...` 文档注释(可以是占位注释或简要说明)
18
17
 
19
18
  实现说明:
20
- - 以文件为粒度进行优化,每次微小变更均伴随 cargo check 进行验证
21
- - 所有修改保留最小必要的文本变动,失败立即回滚
19
+ - 所有优化步骤均通过 CodeAgent 完成,每个步骤后执行 `cargo test` 进行验证
20
+ - 若验证失败,进入构建修复循环(使用 CodeAgent 进行最小修复),直到通过或达到重试上限
21
+ - 所有修改保留最小必要的文本变动,失败时自动回滚到快照(git_guard 启用时)
22
22
  - 结果摘要与日志输出到 <crate_dir>/.jarvis/c2rust/optimize_report.json
23
23
  - 进度记录(断点续跑):<crate_dir>/.jarvis/c2rust/optimize_progress.json
24
24
  - 字段 processed: 已优化完成的文件(相对 crate 根的路径,posix 斜杠)
25
25
 
26
26
  限制:
27
- - 未依赖 rust-analyzer/LSP,主要使用静态文本 + `cargo check` 验证
28
- - 复杂语法与宏、条件编译等情况下可能存在漏检或误判,将尽量保守处理
29
- - 提供 CodeAgent 驱动的“整体优化”阶段,参考 transpiler 的 CodeAgent 使用方式;该阶段输出补丁并进行一次 cargo check 验证
27
+ - 依赖 CodeAgent 的智能分析能力,复杂语法与宏、条件编译等情况由 CodeAgent 处理
28
+ - 所有优化步骤均通过 `cargo test` 验证,确保修改后代码可正常编译和运行
29
+ - 提供 Git 保护(git_guard),失败时自动回滚到快照
30
30
 
31
31
  使用入口:
32
- - optimize_project(crate_dir: Optional[Path], ...) 作为对外简单入口
32
+ - optimize_project(project_root: Optional[Path], crate_dir: Optional[Path], ...) 作为对外简单入口
33
33
  """
34
34
 
35
35
  from __future__ import annotations
36
36
 
37
37
  import json
38
38
  import os
39
- import re
40
39
  import shutil
41
40
  import subprocess
42
41
  from dataclasses import dataclass, asdict
43
42
  from pathlib import Path
44
- from typing import Dict, List, Optional, Tuple, Iterable, Set
43
+ from typing import Any, Dict, List, Optional, Tuple, Iterable, Set
45
44
  import fnmatch
46
45
 
46
+ import typer
47
+
47
48
  # 引入 CodeAgent(参考 transpiler)
48
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
49
52
 
50
53
 
51
54
  @dataclass
52
55
  class OptimizeOptions:
53
56
  enable_unsafe_cleanup: bool = True
54
- enable_structure_opt: bool = True
55
57
  enable_visibility_opt: bool = True
56
58
  enable_doc_opt: bool = True
57
59
  max_checks: int = 0 # 0 表示不限;用于限制 cargo check 次数(防止过慢)
@@ -75,7 +77,6 @@ class OptimizeStats:
75
77
  files_scanned: int = 0
76
78
  unsafe_removed: int = 0
77
79
  unsafe_annotated: int = 0
78
- duplicates_tagged: int = 0
79
80
  visibility_downgraded: int = 0
80
81
  docs_added: int = 0
81
82
  cargo_checks: int = 0
@@ -119,6 +120,137 @@ def _cargo_check(crate_dir: Path, stats: OptimizeStats, max_checks: int, timeout
119
120
  first_line = next((ln for ln in diag.splitlines() if ln.strip()), "")
120
121
  return ok, first_line
121
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
+
122
254
  def _cargo_check_full(crate_dir: Path, stats: OptimizeStats, max_checks: int, timeout: Optional[int] = None) -> Tuple[bool, str]:
123
255
  """
124
256
  执行 cargo test,返回是否成功与完整输出(stdout+stderr)。
@@ -154,13 +286,6 @@ def _cargo_check_full(crate_dir: Path, stats: OptimizeStats, max_checks: int, ti
154
286
  stats.cargo_checks += 1
155
287
  return False, f"cargo test exception: {e}"
156
288
 
157
- def _git_is_repo(root: Path) -> bool:
158
- try:
159
- code, out, err = _run_cmd(["git", "rev-parse", "--is-inside-work-tree"], root)
160
- return code == 0 and (out.strip() == "true" or (not out.strip() and "true" in (err or "")))
161
- except Exception:
162
- return False
163
-
164
289
  def _git_toplevel(start: Path) -> Optional[Path]:
165
290
  """
166
291
  返回包含 start 的 Git 仓库根目录(--show-toplevel)。若不在仓库中则返回 None。
@@ -233,10 +358,31 @@ def _ensure_report_dir(crate_dir: Path) -> Path:
233
358
  return report_dir
234
359
 
235
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
+
236
381
  def detect_crate_dir(preferred: Optional[Path]) -> Path:
237
382
  """
238
383
  选择 crate 目录策略:
239
384
  - 若提供 preferred 且包含 Cargo.toml,则使用
385
+ - 否则:尝试从项目根目录推断(查找包含 .jarvis/c2rust 的目录)
240
386
  - 否则:优先 <cwd>/<cwd.name>_rs;若存在 Cargo.toml 则用之
241
387
  - 否则:在当前目录下递归寻找第一个包含 Cargo.toml 的目录
242
388
  - 若失败:若当前目录有 Cargo.toml 则返回当前目录,否则抛错
@@ -246,6 +392,21 @@ def detect_crate_dir(preferred: Optional[Path]) -> Path:
246
392
  if (preferred / "Cargo.toml").exists():
247
393
  return preferred
248
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
+
249
410
  cwd = Path(".").resolve()
250
411
  candidate = cwd / f"{cwd.name}_rs"
251
412
  if (candidate / "Cargo.toml").exists():
@@ -262,18 +423,62 @@ def detect_crate_dir(preferred: Optional[Path]) -> Path:
262
423
 
263
424
 
264
425
  class Optimizer:
265
- def __init__(self, crate_dir: Path, options: OptimizeOptions):
426
+ def __init__(self, crate_dir: Path, options: OptimizeOptions, project_root: Optional[Path] = None):
266
427
  self.crate_dir = crate_dir
428
+ self.project_root = project_root if project_root else crate_dir.parent # 默认使用 crate_dir 的父目录
267
429
  self.options = options
268
430
  self.stats = OptimizeStats()
269
431
  # 进度文件
270
432
  self.report_dir = _ensure_report_dir(self.crate_dir)
271
433
  self.progress_path = self.report_dir / "optimize_progress.json"
272
434
  self.processed: Set[str] = set()
435
+ self.steps_completed: Set[str] = set() # 已完成的步骤集合
436
+ self._step_commits: Dict[str, str] = {} # 每个步骤的 commit id
273
437
  self._target_files: List[Path] = []
274
438
  self._load_or_reset_progress()
275
439
  self._last_snapshot_commit: Optional[str] = None
276
- self.log_prefix = "[c2rust-优化器]"
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
277
482
 
278
483
  def _snapshot_commit(self) -> None:
279
484
  """
@@ -315,10 +520,12 @@ class Optimizer:
315
520
  def _load_or_reset_progress(self) -> None:
316
521
  if self.options.reset_progress:
317
522
  try:
318
- self.progress_path.write_text(json.dumps({"processed": []}, ensure_ascii=False, indent=2), encoding="utf-8")
523
+ self.progress_path.write_text(json.dumps({"processed": [], "steps_completed": []}, ensure_ascii=False, indent=2), encoding="utf-8")
319
524
  except Exception:
320
525
  pass
321
526
  self.processed = set()
527
+ self.steps_completed: Set[str] = set()
528
+ self._step_commits = {}
322
529
  return
323
530
  try:
324
531
  if self.progress_path.exists():
@@ -329,10 +536,175 @@ class Optimizer:
329
536
  self.processed = {str(x) for x in arr if isinstance(x, str)}
330
537
  else:
331
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 = {}
332
580
  except Exception:
333
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
+
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]"
704
+ )
334
705
 
335
706
  def _save_progress_for_batch(self, files: List[Path]) -> None:
707
+ """保存文件处理进度"""
336
708
  try:
337
709
  rels = []
338
710
  for p in files:
@@ -342,11 +714,117 @@ class Optimizer:
342
714
  rel = str(p)
343
715
  rels.append(rel)
344
716
  self.processed.update(rels)
345
- data = {"processed": sorted(self.processed)}
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
+
346
729
  self.progress_path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
347
730
  except Exception:
348
731
  pass
349
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
+
350
828
  def _parse_patterns(self, s: Optional[str]) -> List[str]:
351
829
  if not s or not isinstance(s, str):
352
830
  return []
@@ -385,381 +863,979 @@ class Optimizer:
385
863
 
386
864
  # ---------- 主运行入口 ----------
387
865
 
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)
886
+
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
898
+
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
922
+
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]:
925
+ """
926
+ 执行单个优化步骤(unsafe_cleanup, visibility_opt, doc_opt)。
927
+
928
+ Args:
929
+ step_name: 步骤名称(用于进度保存和错误消息)
930
+ step_display_name: 步骤显示名称(用于日志)
931
+ step_num: 步骤编号
932
+ target_files: 目标文件列表
933
+ opt_func: 优化函数(接受 target_files 作为参数)
934
+
935
+ Returns:
936
+ 下一个步骤编号,如果失败则返回 None
937
+ """
938
+ typer.secho(f"\n[c2rust-optimizer] 第 {step_num} 步:{step_display_name}", fg=typer.colors.MAGENTA)
939
+ self._snapshot_commit()
940
+ if not self.options.dry_run:
941
+ opt_func(target_files)
942
+ if not self._verify_and_fix_after_step(step_name, target_files):
943
+ # 验证失败,已回滚,返回 None 表示失败
944
+ return None
945
+ # 保存步骤进度
946
+ self._save_step_progress(step_name, target_files)
947
+ return step_num + 1
948
+
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
1049
+
388
1050
  def run(self) -> OptimizeStats:
1051
+ """
1052
+ 执行优化流程的主入口。
1053
+
1054
+ Returns:
1055
+ 优化统计信息
1056
+ """
389
1057
  report_path = self.report_dir / "optimize_report.json"
390
- print(f"{self.log_prefix} 开始优化 Crate: {self.crate_dir}")
1058
+ typer.secho(f"[c2rust-optimizer][start] 开始优化 Crate: {self.crate_dir}", fg=typer.colors.BLUE)
391
1059
  try:
1060
+ # 批次开始前记录快照
1061
+ self._snapshot_commit()
1062
+
1063
+ # ========== 第 0 步:Clippy 告警修复(必须第一步,且必须完成) ==========
1064
+ # 注意:clippy 告警修复不依赖于是否有新文件需要处理,即使所有文件都已处理,也应该检查并修复告警
1065
+ if not self._run_clippy_elimination_step():
1066
+ # Clippy 告警修复未完成,停止后续步骤
1067
+ return self.stats
1068
+
1069
+ # ========== 后续优化步骤(只有在 clippy 告警修复完成后才执行) ==========
392
1070
  # 计算本次批次的目标文件列表(按 include/exclude/resume/max_files)
393
1071
  targets = self._compute_target_files()
1072
+
1073
+ # 检查是否有未完成的步骤需要执行
1074
+ has_pending_steps = False
1075
+ if self.options.enable_unsafe_cleanup and "unsafe_cleanup" not in self.steps_completed:
1076
+ has_pending_steps = True
1077
+ if self.options.enable_visibility_opt and "visibility_opt" not in self.steps_completed:
1078
+ has_pending_steps = True
1079
+ if self.options.enable_doc_opt and "doc_opt" not in self.steps_completed:
1080
+ has_pending_steps = True
1081
+
1082
+ # 如果没有新文件但有未完成的步骤,使用所有 Rust 文件作为目标
1083
+ 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
+
394
1087
  if not targets:
395
- # 无文件可处理:仍然写出报告并返回
396
- print(f"{self.log_prefix} 根据当前选项,无新文件需要处理。")
397
- pass
1088
+ typer.secho("[c2rust-optimizer] 根据当前选项,无新文件需要处理,且所有步骤均已完成。", fg=typer.colors.CYAN)
398
1089
  else:
399
- print(f"{self.log_prefix} 本次批次发现 {len(targets)} 个待处理文件。")
400
- # 批次开始前记录快照
401
- self._snapshot_commit()
1090
+ typer.secho(f"[c2rust-optimizer] 本次批次发现 {len(targets)} 个待处理文件。", fg=typer.colors.BLUE)
402
1091
 
1092
+ # 所有优化步骤都使用 CodeAgent
1093
+ step_num = 1
1094
+
403
1095
  if self.options.enable_unsafe_cleanup:
404
- # 步骤前快照
405
- print(f"\n{self.log_prefix} 第 1 步:unsafe 清理")
406
- self._snapshot_commit()
407
- self._opt_unsafe_cleanup(targets)
408
- # Step build verification
409
- if not self.options.dry_run:
410
- print(f"{self.log_prefix} unsafe 清理后,正在验证构建...")
411
- ok, diag_full = _cargo_check_full(self.crate_dir, self.stats, self.options.max_checks, timeout=self.options.cargo_test_timeout)
412
- if not ok:
413
- # 循环最小修复
414
- fixed = self._build_fix_loop(targets)
415
- if not fixed:
416
- first = (diag_full.splitlines()[0] if isinstance(diag_full, str) and diag_full else "failed")
417
- self.stats.errors.append(f"test after unsafe_cleanup failed: {first}")
418
- # 回滚到快照并结束
419
- try:
420
- self._reset_to_snapshot()
421
- finally:
422
- return self.stats
423
-
424
- if self.options.enable_structure_opt:
425
- # 步骤前快照
426
- print(f"\n{self.log_prefix} 第 2 步:结构优化 (重复代码检测)")
427
- self._snapshot_commit()
428
- self._opt_structure_duplicates(targets)
429
- # Step build verification
430
- if not self.options.dry_run:
431
- print(f"{self.log_prefix} 结构优化后,正在验证构建...")
432
- ok, diag_full = _cargo_check_full(self.crate_dir, self.stats, self.options.max_checks, timeout=self.options.cargo_test_timeout)
433
- if not ok:
434
- fixed = self._build_fix_loop(targets)
435
- if not fixed:
436
- first = (diag_full.splitlines()[0] if isinstance(diag_full, str) and diag_full else "failed")
437
- self.stats.errors.append(f"test after structure_opt failed: {first}")
438
- try:
439
- self._reset_to_snapshot()
440
- finally:
441
- return self.stats
1096
+ step_num = self._run_optimization_step(
1097
+ "unsafe_cleanup", "unsafe 清理", step_num, targets,
1098
+ self._codeagent_opt_unsafe_cleanup
1099
+ )
1100
+ if step_num is None: # 步骤失败,已回滚
1101
+ return self.stats
442
1102
 
443
1103
  if self.options.enable_visibility_opt:
444
- # 步骤前快照
445
- print(f"\n{self.log_prefix} 3 步:可见性优化")
446
- self._snapshot_commit()
447
- self._opt_visibility(targets)
448
- # Step build verification
449
- if not self.options.dry_run:
450
- print(f"{self.log_prefix} 可见性优化后,正在验证构建...")
451
- ok, diag_full = _cargo_check_full(self.crate_dir, self.stats, self.options.max_checks, timeout=self.options.cargo_test_timeout)
452
- if not ok:
453
- fixed = self._build_fix_loop(targets)
454
- if not fixed:
455
- first = (diag_full.splitlines()[0] if isinstance(diag_full, str) and diag_full else "failed")
456
- self.stats.errors.append(f"test after visibility_opt failed: {first}")
457
- try:
458
- self._reset_to_snapshot()
459
- finally:
460
- return self.stats
1104
+ step_num = self._run_optimization_step(
1105
+ "visibility_opt", "可见性优化", step_num, targets,
1106
+ self._codeagent_opt_visibility
1107
+ )
1108
+ if step_num is None: # 步骤失败,已回滚
1109
+ return self.stats
461
1110
 
462
1111
  if self.options.enable_doc_opt:
463
- # 步骤前快照
464
- print(f"\n{self.log_prefix} 4 步:文档补充")
465
- self._snapshot_commit()
466
- self._opt_docs(targets)
467
- # Step build verification
468
- if not self.options.dry_run:
469
- print(f"{self.log_prefix} 文档补充后,正在验证构建...")
470
- ok, diag_full = _cargo_check_full(self.crate_dir, self.stats, self.options.max_checks, timeout=self.options.cargo_test_timeout)
471
- if not ok:
472
- fixed = self._build_fix_loop(targets)
473
- if not fixed:
474
- first = (diag_full.splitlines()[0] if isinstance(diag_full, str) and diag_full else "failed")
475
- self.stats.errors.append(f"test after doc_opt failed: {first}")
476
- try:
477
- self._reset_to_snapshot()
478
- finally:
479
- return self.stats
480
-
481
- # CodeAgent 驱动的整体优化(参考 transpiler 使用模式)
482
- # 在静态优化后执行一次 CodeAgent 以最小化进一步提升(可选:dry_run 时跳过)
483
- if not self.options.dry_run:
484
- try:
485
- print(f"\n{self.log_prefix} 第 5 步:CodeAgent 整体优化")
486
- self._codeagent_optimize_crate(targets)
487
- except Exception as _e:
488
- self.stats.errors.append(f"codeagent: {_e}")
489
-
490
- # 标记本批次文件为“已处理”
1112
+ step_num = self._run_optimization_step(
1113
+ "doc_opt", "文档补充", step_num, targets,
1114
+ self._codeagent_opt_docs
1115
+ )
1116
+ if step_num is None: # 步骤失败,已回滚
1117
+ return self.stats
1118
+
1119
+ # 最终保存进度(确保所有步骤的进度都已记录)
491
1120
  self._save_progress_for_batch(targets)
492
1121
 
493
1122
  except Exception as e:
494
1123
  self.stats.errors.append(f"fatal: {e}")
495
1124
  finally:
496
1125
  # 写出简要报告
497
- print(f"{self.log_prefix} 优化流程结束。报告已生成于: {report_path.relative_to(Path.cwd())}")
498
- try:
499
- _write_file(report_path, json.dumps(asdict(self.stats), ensure_ascii=False, indent=2))
500
- except Exception:
501
- pass
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)
502
1129
  return self.stats
503
1130
 
504
- # ========== 1) unsafe cleanup ==========
1131
+ # ========== 0) clippy warnings elimination (CodeAgent) ==========
505
1132
 
506
- _re_unsafe_block = re.compile(r"\bunsafe\s*\{", re.MULTILINE)
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 分钟超时
1161
+ )
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
507
1241
 
508
- def _opt_unsafe_cleanup(self, files: List[Path]) -> None:
509
- for i, path in enumerate(files):
510
- try:
511
- rel_path = path.relative_to(self.crate_dir)
512
- except ValueError:
513
- rel_path = path
514
- print(f"{self.log_prefix} [unsafe 清理] 正在处理文件 {i + 1}/{len(files)}: {rel_path}")
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:
515
1256
  try:
516
- content = _read_file(path)
1257
+ rel = p.resolve().relative_to(crate).as_posix()
517
1258
  except Exception:
518
- continue
1259
+ rel = p.as_posix()
1260
+ file_list.append(rel)
519
1261
  self.stats.files_scanned += 1
520
1262
 
521
- # 简单逐处尝试:每次仅移除一个 unsafe 以保持回滚粒度
522
- pos = 0
1263
+ # 切换到 crate 目录,确保 CodeAgent 在正确的上下文中创建和执行
1264
+ prev_cwd = os.getcwd()
1265
+ iteration = 0
1266
+
1267
+ try:
1268
+ os.chdir(str(crate))
1269
+
1270
+ # 循环修复告警,按文件处理
523
1271
  while True:
524
- m = self._re_unsafe_block.search(content, pos)
525
- if not m:
526
- break
527
-
528
- # 准备试移除(仅移除 "unsafe " 关键字,保留后续块)
529
- start, end = m.span()
530
- trial = content[:start] + "{" + content[end:] # 将 "unsafe {" 替换为 "{"
531
-
532
- if self.options.dry_run:
533
- # 仅统计
534
- self.stats.unsafe_removed += 1 # 计为潜在可移除
535
- pos = start + 1
536
- continue
537
-
538
- # 备份并写入尝试版
539
- bak = _backup_file(path)
540
- try:
541
- _write_file(path, trial)
542
- ok, diag = _cargo_check(self.crate_dir, self.stats, self.options.max_checks, timeout=self.options.cargo_test_timeout)
543
- if ok:
544
- # 保留修改
545
- content = trial
546
- self.stats.unsafe_removed += 1
547
- # 不需要移动 pos 太多,继续搜索后续位置
548
- pos = start + 1
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)
549
1414
  else:
550
- # 回滚,并在 unsafe 前添加说明
551
- _restore_file_from_backup(path, bak)
552
- content = _read_file(path) # 还原后的内容
553
- self._annotate_safety_comment(path, content, start, diag)
554
- # 重新读取注释后的文本,以便继续
555
- content = _read_file(path)
556
- self.stats.unsafe_annotated += 1
557
- pos = start + 1
558
- finally:
559
- _remove_backup(bak)
560
-
561
- # 若最后的 content 与磁盘不同步(dry_run 时不会),这里无需写回
562
-
563
- def _annotate_safety_comment(self, path: Path, content: str, unsafe_pos: int, diag: str) -> None:
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]]:
564
1429
  """
565
- unsafe 块前注入一行文档注释,格式:
566
- /// SAFETY: 自动清理失败,保留 unsafe。原因摘要: <diag>
1430
+ clippy JSON 输出中提取所有告警并按文件分组。
1431
+
1432
+ Returns:
1433
+ 字典,键为文件路径,值为该文件的告警列表
567
1434
  """
568
- # 寻找 unsafe 所在行首
569
- line_start = content.rfind("\n", 0, unsafe_pos)
570
- if line_start == -1:
571
- insert_at = 0
572
- else:
573
- insert_at = line_start + 1
574
-
575
- annotation = f'/// SAFETY: 自动清理失败,保留 unsafe。原因摘要: {diag}\n'
576
- new_content = content[:insert_at] + annotation + content[insert_at:]
577
-
578
- if not self.options.dry_run:
579
- _write_file(path, new_content)
580
-
581
- # ========== 2) structure duplicates ==========
582
-
583
- _re_fn = re.compile(
584
- r"(?P<leading>\s*(?:pub(?:\([^\)]*\))?\s+)?(?:async\s+)?(?:unsafe\s+)?(?:extern\s+\"[^\"]*\"\s+)?fn\s+"
585
- r"(?P<name>[A-Za-z_][A-Za-z0-9_]*)\s*\([^)]*\)\s*(?:->\s*[^ \t\r\n\{]+)?\s*)\{",
586
- re.MULTILINE,
587
- )
588
-
589
- def _opt_structure_duplicates(self, files: List[Path]) -> None:
590
- # 建立函数签名+主体的简易哈希,重复则为后出现者添加 TODO 注释
591
- print(f"{self.log_prefix} [结构优化] 正在扫描 {len(files)} 个文件以查找重复函数...")
592
- seen: Dict[str, Tuple[Path, int]] = {}
593
- for path in files:
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
594
1444
  try:
595
- content = _read_file(path)
596
- except Exception:
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):
597
1458
  continue
1459
+
1460
+ return warnings_by_file
598
1461
 
599
- for m in self._re_fn.finditer(content):
600
- name = m.group("name")
601
- body_start = m.end() - 1 # at '{'
602
- body_end = self._find_matching_brace(content, body_start)
603
- if body_end is None:
604
- continue
605
- sig = m.group(0)[: m.group(0).rfind("{")].strip()
606
- body = content[body_start: body_end + 1]
607
- key = f"{name}::{self._normalize_ws(sig)}::{self._normalize_ws(body)}"
608
- if key not in seen:
609
- seen[key] = (path, m.start())
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}")
610
1503
  else:
611
- # 重复:在该函数前添加 TODO
612
- if self.options.dry_run:
613
- self.stats.duplicates_tagged += 1
614
- continue
615
- bak = _backup_file(path)
616
- try:
617
- insert_pos = content.rfind("\n", 0, m.start())
618
- insert_at = 0 if insert_pos == -1 else insert_pos + 1
619
- origin_path, _ = seen[key]
620
- try:
621
- origin_rel = origin_path.resolve().relative_to(self.crate_dir.resolve()).as_posix()
622
- except Exception:
623
- origin_rel = origin_path.as_posix()
624
- todo = f'/// TODO: duplicate of {origin_rel}::{name}\n'
625
- new_content = content[:insert_at] + todo + content[insert_at:]
626
- _write_file(path, new_content)
627
- content = new_content
628
- self.stats.duplicates_tagged += 1
629
- finally:
630
- _remove_backup(bak)
631
-
632
- def _find_matching_brace(self, s: str, open_pos: int) -> Optional[int]:
633
- """
634
- 给定 s[open_pos] == '{',返回匹配的 '}' 位置;简单计数器,忽略字符串/注释的复杂性(保守)
635
- """
636
- if open_pos >= len(s) or s[open_pos] != "{":
637
- return None
638
- depth = 0
639
- for i in range(open_pos, len(s)):
640
- if s[i] == "{":
641
- depth += 1
642
- elif s[i] == "}":
643
- depth -= 1
644
- if depth == 0:
645
- return i
646
- return None
647
-
648
- def _normalize_ws(self, s: str) -> str:
649
- return re.sub(r"\s+", " ", s).strip()
650
-
651
- # ========== 3) visibility optimization ==========
652
-
653
- _re_pub_fn = re.compile(
654
- r"(?P<prefix>\s*)pub\s+fn\s+(?P<name>[A-Za-z_][A-Za-z0-9_]*)\s*\(",
655
- re.MULTILINE,
656
- )
657
-
658
- def _opt_visibility(self, files: List[Path]) -> None:
659
- for i, path in enumerate(files):
660
- try:
661
- rel_path = path.relative_to(self.crate_dir)
662
- except ValueError:
663
- rel_path = path
664
- print(f"{self.log_prefix} [可见性优化] 正在处理文件 {i + 1}/{len(files)}: {rel_path}")
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:
665
1544
  try:
666
- content = _read_file(path)
1545
+ rel = p.resolve().relative_to(crate).as_posix()
667
1546
  except Exception:
668
- continue
1547
+ rel = p.as_posix()
1548
+ file_list.append(rel)
1549
+ self.stats.files_scanned += 1
669
1550
 
670
- for m in list(self._re_pub_fn.finditer(content)):
671
- start, end = m.span()
672
- name = m.group("name")
673
- candidate = content[:start] + f"{m.group('prefix')}pub(crate) fn {name}(" + content[end:]
674
- if self.options.dry_run:
675
- self.stats.visibility_downgraded += 1
676
- continue
677
- bak = _backup_file(path)
678
- try:
679
- _write_file(path, candidate)
680
- ok, _ = _cargo_check(self.crate_dir, self.stats, self.options.max_checks, timeout=self.options.cargo_test_timeout)
681
- if ok:
682
- content = candidate
683
- self.stats.visibility_downgraded += 1
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)
684
1713
  else:
685
- _restore_file_from_backup(path, bak)
686
- content = _read_file(path)
687
- finally:
688
- _remove_backup(bak)
689
-
690
- # ========== 4) doc augmentation ==========
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)
691
1724
 
692
- _re_mod_doc = re.compile(r"(?m)^\s*//!") # 顶部模块文档
693
- _re_any_doc = re.compile(r"(?m)^\s*///")
1725
+ # ========== 2) visibility optimization (CodeAgent) ==========
694
1726
 
695
- def _opt_docs(self, files: List[Path]) -> None:
696
- for i, path in enumerate(files):
697
- try:
698
- rel_path = path.relative_to(self.crate_dir)
699
- except ValueError:
700
- rel_path = path
701
- print(f"{self.log_prefix} [文档补充] 正在处理文件 {i + 1}/{len(files)}: {rel_path}")
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:
702
1736
  try:
703
- content = _read_file(path)
1737
+ rel = p.resolve().relative_to(crate).as_posix()
704
1738
  except Exception:
705
- continue
1739
+ rel = p.as_posix()
1740
+ file_list.append(rel)
1741
+ self.stats.files_scanned += 1
706
1742
 
707
- changed = False
708
- # 模块级文档:若文件开头不是文档,补充
709
- if not self._re_mod_doc.search(content[:500]): # 仅检查开头部分
710
- header = "//! TODO: Add module-level documentation\n"
711
- content = header + content
712
- changed = True
713
- self.stats.docs_added += 1
714
-
715
- # 函数文档:为未有文档注释的函数前补充
716
- new_content = []
717
- last_end = 0
718
- for m in self._re_fn.finditer(content):
719
- fn_start = m.start()
720
- # 检查前一行是否有 /// 文档
721
- line_start = content.rfind("\n", 0, fn_start)
722
- prev_line_start = content.rfind("\n", 0, line_start - 1) if line_start > 0 else -1
723
- segment_start = last_end
724
- segment_end = line_start + 1 if line_start != -1 else 0
725
- new_content.append(content[segment_start:segment_end])
726
-
727
- doc_exists = False
728
- if line_start != -1:
729
- prev_line = content[prev_line_start + 1: line_start] if prev_line_start != -1 else content[:line_start]
730
- if self._re_any_doc.search(prev_line):
731
- doc_exists = True
732
-
733
- if not doc_exists:
734
- new_content.append("/// TODO: Add documentation\n")
735
- changed = True
736
- self.stats.docs_added += 1
737
-
738
- new_content.append(content[segment_end: m.end()]) # 包含到函数体起始的部分
739
- last_end = m.end()
740
-
741
- new_content.append(content[last_end:])
742
- new_s = "".join(new_content)
743
-
744
- if changed and not self.options.dry_run:
745
- _write_file(path, new_s)
746
-
747
- # ========== 5) CodeAgent 整体优化(参考 transpiler 的 CodeAgent 使用方式) ==========
748
-
749
- def _codeagent_optimize_crate(self, target_files: List[Path]) -> None:
750
- """
751
- 使用 CodeAgent 对 crate 进行一次保守的整体优化,输出补丁并进行一次 cargo check 验证。
752
- 仅限本批次的目标文件(target_files)范围内进行修改,以支持大项目分批优化。
753
- 包含:
754
- - unsafe 清理与 SAFETY 注释补充(范围最小化)
755
- - 重复代码最小消除(允许抽取公共辅助函数),或添加 TODO 标注
756
- - 可见性最小化(尽量使用 pub(crate),保持对外接口为 pub)
757
- - 文档补充(模块/函数缺失文档添加占位)
758
- 约束:
759
- - 保持最小改动,避免大范围重构或格式化
760
- - 不得删除公开 API;跨 crate 接口保持 pub
761
- - 仅在 crate_dir 下进行修改(Cargo.toml、src/**/*.rs);不得改动其他目录
762
- - 仅输出补丁(由 CodeAgent 控制),不输出解释
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 目录下创建和执行,以确保所有文件操作和命令执行都在正确的上下文中进行。
763
1839
  """
764
1840
  crate = self.crate_dir.resolve()
765
1841
  file_list: List[str] = []
@@ -769,59 +1845,104 @@ class Optimizer:
769
1845
  except Exception:
770
1846
  rel = p.as_posix()
771
1847
  file_list.append(rel)
1848
+ self.stats.files_scanned += 1
772
1849
 
773
1850
  prompt_lines: List[str] = [
774
- "你是资深 Rust 代码工程师。请在当前 crate 下执行一次保守的整体优化,并以补丁形式输出修改:",
1851
+ "你是资深 Rust 代码工程师。请在当前 crate 下执行文档补充优化,并以补丁形式输出修改:",
775
1852
  f"- crate 根目录:{crate}",
776
1853
  "",
777
1854
  "本次优化仅允许修改以下文件范围(严格限制):",
778
1855
  *[f"- {rel}" for rel in file_list],
779
1856
  "",
780
- "优化目标(按优先级):",
781
- "1) unsafe 清理:",
782
- " - 移除不必要的 unsafe 包裹;若必须使用 unsafe,缩小范围并在紧邻位置添加 `/// SAFETY: ...` 文档注释说明理由。",
783
- "2) 代码结构优化(重复消除/提示):",
784
- " - 检测重复函数实现(签名+主体近似),如能安全抽取公共辅助函数进行复用,进行最小化重构;否则在重复处添加 `/// TODO: duplicate of ...` 注释。",
785
- "3) 可见性优化:",
786
- " - 优先将 `pub fn` 降为 `pub(crate) fn`;保持对外接口(跨 crate 使用的接口)为 `pub`;在 lib.rs 中的顶层导出保持现状。",
787
- "4) 文档补充:",
788
- " - 为缺少模块/函数文档的位置添加占位注释(//! 或 ///)。",
1857
+ "优化目标:",
1858
+ "1) 文档补充:",
1859
+ " - 为缺少模块级文档的文件添加 `//! ...` 模块文档注释(放在文件开头);",
1860
+ " - 为缺少函数文档的公共函数(pub 或 pub(crate))添加 `/// ...` 文档注释;",
1861
+ " - 文档内容可以是占位注释(如 `//! TODO: Add module-level documentation` 或 `/// TODO: Add documentation`),也可以根据函数签名和实现提供简要说明。",
1862
+ "",
1863
+ "2) 修复已有实现的问题:",
1864
+ " - 如果在进行文档补充的过程中,发现代码已有的实现有问题(如逻辑错误、潜在 bug、性能问题、内存安全问题等),也需要一并修复;",
1865
+ " - 这些问题可能包括但不限于:不正确的算法实现、未检查的边界条件、资源泄漏、竞态条件等;",
1866
+ " - 修复时应该保持最小改动原则,优先修复最严重的问题。",
789
1867
  "",
790
1868
  "约束与范围:",
791
1869
  "- 仅修改上述列出的文件;除非必须(如修复引用路径),否则不要修改其他文件。",
792
- "- 保持最小改动,不要进行与上述优化无关的重构或格式化。",
1870
+ "- 保持最小改动,不要进行与文档补充无关的重构或格式化。",
793
1871
  "- 修改后需保证 `cargo test` 可以通过;如需引入少量配套改动,请一并包含在补丁中以确保通过。",
794
1872
  "- 输出仅为补丁,不要输出解释或多余文本。",
795
1873
  "",
1874
+ "优先级说明:",
1875
+ "- **如果优化过程中出现了测试不通过或编译错误,必须优先解决这些问题**;",
1876
+ "- 在进行文档补充之前,先确保代码能够正常编译和通过测试;",
1877
+ "- 如果文档补充导致了编译错误或测试失败,必须立即修复这些错误,然后再继续优化。",
1878
+ "",
796
1879
  "自检要求:在每次输出补丁后,请使用 execute_script 工具在 crate 根目录执行 `cargo test -q` 进行验证;",
1880
+ "若出现编译错误或测试失败,请优先修复这些问题,然后再继续文档补充;",
797
1881
  "若未通过,请继续输出新的补丁进行最小修复并再次自检,直至 `cargo test` 通过为止。"
798
1882
  ]
799
1883
  prompt = "\n".join(prompt_lines)
1884
+ prompt = self._append_additional_notes(prompt)
1885
+ # 切换到 crate 目录,确保 CodeAgent 在正确的上下文中创建和执行
800
1886
  prev_cwd = os.getcwd()
801
- print(f"{self.log_prefix} [CodeAgent] 正在调用 CodeAgent 进行整体优化...")
1887
+ typer.secho("[c2rust-optimizer][codeagent][doc] 正在调用 CodeAgent 进行文档补充...", fg=typer.colors.CYAN)
802
1888
  try:
803
1889
  os.chdir(str(crate))
804
- agent = CodeAgent(need_summary=False, non_interactive=self.options.non_interactive, plan=False, model_group=self.options.llm_group)
805
- agent.run(prompt, prefix="[c2rust-optimizer][codeagent]", suffix="")
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)
806
1936
  finally:
807
1937
  os.chdir(prev_cwd)
808
- # 运行一次 cargo check 验证;若失败则进入本地最小修复循环
809
- ok, diag = _cargo_check_full(self.crate_dir, self.stats, self.options.max_checks, timeout=self.options.cargo_test_timeout)
810
- if not ok:
811
- fixed = self._build_fix_loop(target_files)
812
- if not fixed:
813
- first = (diag.splitlines()[0] if isinstance(diag, str) and diag else "failed")
814
- self.stats.errors.append(f"codeagent test failed: {first}")
815
- try:
816
- self._reset_to_snapshot()
817
- finally:
818
- return
819
1938
 
820
1939
  def _build_fix_loop(self, scope_files: List[Path]) -> bool:
821
1940
  """
822
1941
  循环执行 cargo check 并用 CodeAgent 进行最小修复,直到通过或达到重试上限或检查预算耗尽。
823
1942
  仅允许(优先)修改 scope_files(除非确有必要),以支持分批优化。
824
1943
  返回 True 表示修复成功构建通过;False 表示未能在限制内修复。
1944
+
1945
+ 注意:CodeAgent 必须在 crate 目录下创建和执行,以确保所有文件操作和命令执行都在正确的上下文中进行。
825
1946
  """
826
1947
  maxr = int(self.options.build_fix_retries or 0)
827
1948
  if maxr <= 0:
@@ -853,7 +1974,7 @@ class Optimizer:
853
1974
  )
854
1975
  self.stats.cargo_checks += 1
855
1976
  if res.returncode == 0:
856
- print(f"{self.log_prefix} 构建修复成功。")
1977
+ typer.secho("[c2rust-optimizer][build-fix] 构建修复成功。", fg=typer.colors.GREEN)
857
1978
  return True
858
1979
  output = ((res.stdout or "") + ("\n" + (res.stderr or ""))).strip()
859
1980
  except subprocess.TimeoutExpired as e:
@@ -871,10 +1992,10 @@ class Optimizer:
871
1992
  # 达到重试上限则失败
872
1993
  attempt += 1
873
1994
  if attempt > maxr:
874
- print(f"{self.log_prefix} 构建修复重试次数已用尽。")
1995
+ typer.secho("[c2rust-optimizer][build-fix] 构建修复重试次数已用尽。", fg=typer.colors.RED)
875
1996
  return False
876
1997
 
877
- print(f"{self.log_prefix} 构建失败。正在尝试使用 CodeAgent 进行修复 (第 {attempt}/{maxr} 次尝试)...")
1998
+ typer.secho(f"[c2rust-optimizer][build-fix] 构建失败。正在尝试使用 CodeAgent 进行修复 (第 {attempt}/{maxr} 次尝试)...", fg=typer.colors.YELLOW)
878
1999
  # 生成最小修复提示
879
2000
  prompt_lines = [
880
2001
  "请根据以下测试/构建错误对 crate 进行最小必要的修复以通过 `cargo test`:",
@@ -887,7 +2008,24 @@ class Optimizer:
887
2008
  "- 保持最小改动,不要进行与错误无关的重构或格式化;",
888
2009
  "- 仅输出补丁,不要输出解释或多余文本。",
889
2010
  "",
2011
+ "优化目标:",
2012
+ "1) 修复构建/测试错误:",
2013
+ " - 必须优先解决所有编译错误和测试失败问题;",
2014
+ " - 修复时应该先解决编译错误,然后再解决测试失败;",
2015
+ " - 如果修复过程中引入了新的错误,必须立即修复这些新错误。",
2016
+ "",
2017
+ "2) 修复已有实现的问题:",
2018
+ " - 如果在修复构建/测试错误的过程中,发现代码已有的实现有问题(如逻辑错误、潜在 bug、性能问题、内存安全问题等),也需要一并修复;",
2019
+ " - 这些问题可能包括但不限于:不正确的算法实现、未检查的边界条件、资源泄漏、竞态条件、数据竞争等;",
2020
+ " - 修复时应该保持最小改动原则,优先修复最严重的问题。",
2021
+ "",
2022
+ "优先级说明:",
2023
+ "- **必须优先解决所有编译错误和测试失败问题**;",
2024
+ "- 修复时应该先解决编译错误,然后再解决测试失败;",
2025
+ "- 如果修复过程中引入了新的错误,必须立即修复这些新错误。",
2026
+ "",
890
2027
  "自检要求:在每次输出补丁后,请使用 execute_script 工具在 crate 根目录执行 `cargo test -q` 进行验证;",
2028
+ "若出现编译错误或测试失败,请优先修复这些问题;",
891
2029
  "若未通过,请继续输出新的补丁进行最小修复并再次自检,直至 `cargo test` 通过为止。",
892
2030
  "",
893
2031
  "构建错误如下:",
@@ -896,20 +2034,68 @@ class Optimizer:
896
2034
  "</BUILD_ERROR>",
897
2035
  ]
898
2036
  prompt = "\n".join(prompt_lines)
2037
+ prompt = self._append_additional_notes(prompt)
2038
+ # 切换到 crate 目录,确保 CodeAgent 在正确的上下文中创建和执行
899
2039
  prev_cwd = os.getcwd()
900
2040
  try:
901
2041
  os.chdir(str(crate))
902
- agent = CodeAgent(need_summary=False, non_interactive=self.options.non_interactive, plan=False, model_group=self.options.llm_group)
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
903
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)
904
2090
  finally:
905
2091
  os.chdir(prev_cwd)
906
2092
 
907
2093
  return False
908
2094
 
909
2095
  def optimize_project(
2096
+ project_root: Optional[Path] = None,
910
2097
  crate_dir: Optional[Path] = None,
911
2098
  enable_unsafe_cleanup: bool = True,
912
- enable_structure_opt: bool = True,
913
2099
  enable_visibility_opt: bool = True,
914
2100
  enable_doc_opt: bool = True,
915
2101
  max_checks: int = 0,
@@ -927,6 +2113,7 @@ def optimize_project(
927
2113
  ) -> Dict:
928
2114
  """
929
2115
  对指定 crate 执行优化。返回结果摘要 dict。
2116
+ - project_root: 原 C 项目根目录(包含 .jarvis/c2rust);为 None 时自动检测
930
2117
  - crate_dir: crate 根目录(包含 Cargo.toml);为 None 时自动检测
931
2118
  - enable_*: 各优化步骤开关
932
2119
  - max_checks: 限制 cargo check 调用次数(0 不限)
@@ -936,10 +2123,20 @@ def optimize_project(
936
2123
  - resume: 启用断点续跑(跳过已处理文件)
937
2124
  - reset_progress: 清空进度(processed 列表)
938
2125
  """
2126
+ # 如果 project_root 为 None,尝试从当前目录查找
2127
+ if project_root is None:
2128
+ project_root = _find_project_root()
2129
+ if project_root is None:
2130
+ # 如果找不到项目根目录,使用当前目录
2131
+ project_root = Path(".").resolve()
2132
+ else:
2133
+ project_root = Path(project_root).resolve()
2134
+
2135
+ # 如果 crate_dir 为 None,使用 detect_crate_dir 自动检测
2136
+ # detect_crate_dir 内部已经包含了从项目根目录推断的逻辑
939
2137
  crate = detect_crate_dir(crate_dir)
940
2138
  opts = OptimizeOptions(
941
2139
  enable_unsafe_cleanup=enable_unsafe_cleanup,
942
- enable_structure_opt=enable_structure_opt,
943
2140
  enable_visibility_opt=enable_visibility_opt,
944
2141
  enable_doc_opt=enable_doc_opt,
945
2142
  max_checks=max_checks,
@@ -955,6 +2152,6 @@ def optimize_project(
955
2152
  cargo_test_timeout=cargo_test_timeout,
956
2153
  non_interactive=non_interactive,
957
2154
  )
958
- optimizer = Optimizer(crate, opts)
2155
+ optimizer = Optimizer(crate, opts, project_root=project_root)
959
2156
  stats = optimizer.run()
960
2157
  return asdict(stats)