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
@@ -6,7 +6,7 @@ C2Rust 转译器模块
6
6
  - 基于 scanner 生成的 translation_order.jsonl 顺序,逐个函数进行转译
7
7
  - 为每个函数:
8
8
  1) 准备上下文:C 源码片段+位置信息、被调用符号(若已转译则提供Rust模块与符号,否则提供原C位置信息)、crate目录结构
9
- 2) 创建“模块选择与签名Agent”:让其选择合适的Rust模块路径,并在summary输出函数签名
9
+ 2) 创建"模块选择与签名Agent":让其选择合适的Rust模块路径,并在summary输出函数签名
10
10
  3) 记录当前进度到 progress.json
11
11
  4) 基于上述信息与落盘位置,创建 CodeAgent 生成转译后的Rust函数
12
12
  5) 尝试 cargo build,如失败则携带错误上下文创建 CodeAgent 修复,直到构建通过或达到上限
@@ -24,432 +24,43 @@ import os
24
24
  import re
25
25
  import subprocess
26
26
  import time
27
- from dataclasses import dataclass
28
27
  from pathlib import Path
29
28
  from typing import Any, Dict, List, Optional, Tuple, Union, Set
30
29
 
31
30
  import typer
32
31
 
33
- from jarvis.jarvis_c2rust.scanner import compute_translation_order_jsonl
34
32
  from jarvis.jarvis_agent import Agent
33
+ from jarvis.jarvis_agent.events import BEFORE_TOOL_CALL, AFTER_TOOL_CALL
35
34
  from jarvis.jarvis_code_agent.code_agent import CodeAgent
36
-
37
-
38
- # 数据文件常量
39
- C2RUST_DIRNAME = ".jarvis/c2rust"
40
-
41
- SYMBOLS_JSONL = "symbols.jsonl"
42
- ORDER_JSONL = "translation_order.jsonl"
43
- PROGRESS_JSON = "progress.json"
44
- SYMBOL_MAP_JSONL = "symbol_map.jsonl"
45
- # 兼容旧版:若存在 symbol_map.json 也尝试加载(只读)
46
- LEGACY_SYMBOL_MAP_JSON = "symbol_map.json"
47
-
48
- # 配置常量
49
- ERROR_SUMMARY_MAX_LENGTH = 2000 # 错误信息摘要最大长度
50
- DEFAULT_PLAN_MAX_RETRIES = 0 # 规划阶段默认最大重试次数(0表示无限重试)
51
- DEFAULT_REVIEW_MAX_ITERATIONS = 0 # 审查阶段最大迭代次数(0表示无限重试)
52
- DEFAULT_CHECK_MAX_RETRIES = 0 # cargo check 阶段默认最大重试次数(0表示无限重试)
53
- DEFAULT_TEST_MAX_RETRIES = 0 # cargo test 阶段默认最大重试次数(0表示无限重试)
54
-
55
-
56
- @dataclass
57
- class FnRecord:
58
- id: int
59
- name: str
60
- qname: str
61
- file: str
62
- start_line: int
63
- start_col: int
64
- end_line: int
65
- end_col: int
66
- refs: List[str]
67
- # 额外元信息(来自 symbols/items):函数签名、返回类型与参数(可选)
68
- signature: str = ""
69
- return_type: str = ""
70
- params: Optional[List[Dict[str, str]]] = None
71
- # 来自库替代阶段的上下文元数据(若存在)
72
- lib_replacement: Optional[Dict[str, Any]] = None
73
-
74
-
75
- class _DbLoader:
76
- """读取 symbols.jsonl 并提供按 id/name 查询与源码片段读取"""
77
-
78
- def __init__(self, project_root: Union[str, Path]) -> None:
79
- self.project_root = Path(project_root).resolve()
80
- self.data_dir = self.project_root / C2RUST_DIRNAME
81
-
82
- self.symbols_path = self.data_dir / SYMBOLS_JSONL
83
- # 统一流程:仅使用 symbols.jsonl,不再兼容 functions.jsonl
84
- if not self.symbols_path.exists():
85
- raise FileNotFoundError(
86
- f"在目录下未找到 symbols.jsonl: {self.data_dir}"
87
- )
88
-
89
- self.fn_by_id: Dict[int, FnRecord] = {}
90
- self.name_to_id: Dict[str, int] = {}
91
- self._load()
92
-
93
- def _load(self) -> None:
94
- """
95
- 读取统一的 symbols.jsonl。
96
- 不区分函数与类型定义,均加载为通用记录(位置与引用信息)。
97
- """
98
- def _iter_records_from_file(path: Path):
99
- try:
100
- with path.open("r", encoding="utf-8") as f:
101
- idx = 0
102
- for line in f:
103
- line = line.strip()
104
- if not line:
105
- continue
106
- try:
107
- obj = json.loads(line)
108
- except Exception:
109
- continue
110
- idx += 1
111
- yield idx, obj
112
- except FileNotFoundError:
113
- return
114
-
115
- # 加载所有符号记录(函数、类型等)
116
- for idx, obj in _iter_records_from_file(self.symbols_path):
117
- fid = int(obj.get("id") or idx)
118
- nm = obj.get("name") or ""
119
- qn = obj.get("qualified_name") or ""
120
- fp = obj.get("file") or ""
121
- refs = obj.get("ref")
122
- # 统一使用列表类型的引用字段
123
- if not isinstance(refs, list):
124
- refs = []
125
- refs = [c for c in refs if isinstance(c, str) and c]
126
- sr = int(obj.get("start_line") or 0)
127
- sc = int(obj.get("start_col") or 0)
128
- er = int(obj.get("end_line") or 0)
129
- ec = int(obj.get("end_col") or 0)
130
- lr = obj.get("lib_replacement") if isinstance(obj.get("lib_replacement"), dict) else None
131
- rec = FnRecord(
132
- id=fid,
133
- name=nm,
134
- qname=qn,
135
- file=fp,
136
- start_line=sr,
137
- start_col=sc,
138
- end_line=er,
139
- end_col=ec,
140
- refs=refs,
141
- lib_replacement=lr,
142
- )
143
- self.fn_by_id[fid] = rec
144
- if nm:
145
- self.name_to_id.setdefault(nm, fid)
146
- if qn:
147
- self.name_to_id.setdefault(qn, fid)
148
-
149
- def get(self, fid: int) -> Optional[FnRecord]:
150
- return self.fn_by_id.get(fid)
151
-
152
- def get_id_by_name(self, name_or_qname: str) -> Optional[int]:
153
- return self.name_to_id.get(name_or_qname)
154
-
155
- def read_source_span(self, rec: FnRecord) -> str:
156
- """按起止行读取源码片段(忽略列边界,尽量完整)"""
157
- try:
158
- p = Path(rec.file)
159
- # 若记录为相对路径,基于 project_root 解析
160
- if not p.is_absolute():
161
- p = (self.project_root / p).resolve()
162
- if not p.exists():
163
- return ""
164
- lines = p.read_text(encoding="utf-8", errors="replace").splitlines()
165
- s = max(1, rec.start_line)
166
- e = min(len(lines), max(rec.end_line, s))
167
- # Python 索引从0开始,包含终止行
168
- chunk = "\n".join(lines[s - 1 : e])
169
- return chunk
170
- except Exception:
171
- return ""
172
-
173
-
174
- class _SymbolMapJsonl:
175
- """
176
- JSONL 形式的符号映射管理:
177
- - 每行一条记录,支持同名函数的多条映射(用于处理重载/同名符号)
178
- - 记录字段:
179
- {
180
- "c_name": "<简单名>",
181
- "c_qname": "<限定名,可为空字符串>",
182
- "c_file": "<源文件路径>",
183
- "start_line": <int>,
184
- "end_line": <int>,
185
- "module": "src/xxx.rs 或 src/xxx/mod.rs",
186
- "rust_symbol": "<Rust函数名>",
187
- "updated_at": "YYYY-MM-DDTHH:MM:SS"
188
- }
189
- - 提供按名称(c_name/c_qname)查询、按源位置判断是否已记录等能力
190
- """
191
-
192
- def __init__(self, jsonl_path: Path, legacy_json_path: Optional[Path] = None) -> None:
193
- self.jsonl_path = jsonl_path
194
- self.legacy_json_path = legacy_json_path
195
- self.records: List[Dict[str, Any]] = []
196
- # 索引:名称 -> 记录列表索引
197
- self.by_key: Dict[str, List[int]] = {}
198
- # 唯一定位(避免同名冲突):(c_file, start_line, end_line, c_qname or c_name) -> 记录索引列表
199
- self.by_pos: Dict[Tuple[str, int, int, str], List[int]] = {}
200
- self._load()
201
-
202
- def _load(self) -> None:
203
- self.records = []
204
- self.by_key = {}
205
- self.by_pos = {}
206
- # 读取 JSONL
207
- if self.jsonl_path.exists():
208
- try:
209
- with self.jsonl_path.open("r", encoding="utf-8") as f:
210
- for line in f:
211
- line = line.strip()
212
- if not line:
213
- continue
214
- try:
215
- obj = json.loads(line)
216
- except Exception:
217
- continue
218
- self._add_record_in_memory(obj)
219
- except Exception:
220
- pass
221
- # 兼容旧版 symbol_map.json(若存在则读入为“最后一条”)
222
- elif self.legacy_json_path and self.legacy_json_path.exists():
223
- try:
224
- legacy = json.loads(self.legacy_json_path.read_text(encoding="utf-8"))
225
- if isinstance(legacy, dict):
226
- for k, v in legacy.items():
227
- rec = {
228
- "c_name": k,
229
- "c_qname": k,
230
- "c_file": "",
231
- "start_line": 0,
232
- "end_line": 0,
233
- "module": v.get("module"),
234
- "rust_symbol": v.get("rust_symbol"),
235
- "updated_at": v.get("updated_at"),
236
- }
237
- self._add_record_in_memory(rec)
238
- except Exception:
239
- pass
240
-
241
- def _add_record_in_memory(self, rec: Dict[str, Any]) -> None:
242
- idx = len(self.records)
243
- self.records.append(rec)
244
- for key in [rec.get("c_name") or "", rec.get("c_qname") or ""]:
245
- k = str(key or "").strip()
246
- if not k:
247
- continue
248
- self.by_key.setdefault(k, []).append(idx)
249
- pos_key = (str(rec.get("c_file") or ""), int(rec.get("start_line") or 0), int(rec.get("end_line") or 0), str(rec.get("c_qname") or rec.get("c_name") or ""))
250
- self.by_pos.setdefault(pos_key, []).append(idx)
251
-
252
- def has_symbol(self, sym: str) -> bool:
253
- return bool(self.by_key.get(sym))
254
-
255
- def get(self, sym: str) -> List[Dict[str, Any]]:
256
- idxs = self.by_key.get(sym) or []
257
- return [self.records[i] for i in idxs]
258
-
259
- def get_any(self, sym: str) -> Optional[Dict[str, Any]]:
260
- recs = self.get(sym)
261
- return recs[-1] if recs else None
262
-
263
- def has_rec(self, rec: FnRecord) -> bool:
264
- key = (str(rec.file or ""), int(rec.start_line or 0), int(rec.end_line or 0), str(rec.qname or rec.name or ""))
265
- return bool(self.by_pos.get(key))
266
-
267
- def add(self, rec: FnRecord, module: str, rust_symbol: str) -> None:
268
- obj = {
269
- "c_name": rec.name or "",
270
- "c_qname": rec.qname or "",
271
- "c_file": rec.file or "",
272
- "start_line": int(rec.start_line or 0),
273
- "end_line": int(rec.end_line or 0),
274
- "module": str(module or ""),
275
- "rust_symbol": str(rust_symbol or (rec.name or f"fn_{rec.id}")),
276
- "updated_at": time.strftime("%Y-%m-%dT%H:%M:%S", time.localtime()),
277
- }
278
- # 先写盘,再更新内存索引
279
- try:
280
- self.jsonl_path.parent.mkdir(parents=True, exist_ok=True)
281
- with self.jsonl_path.open("a", encoding="utf-8") as f:
282
- f.write(json.dumps(obj, ensure_ascii=False) + "\n")
283
- except Exception:
284
- pass
285
- self._add_record_in_memory(obj)
286
-
287
-
288
- def _ensure_order_file(project_root: Path) -> Path:
289
- """确保 translation_order.jsonl 存在且包含有效步骤;仅基于 symbols.jsonl 生成,不使用任何回退。"""
290
- data_dir = project_root / C2RUST_DIRNAME
291
- order_path = data_dir / ORDER_JSONL
292
- typer.secho(f"[c2rust-transpiler][order] 目标顺序文件: {order_path}", fg=typer.colors.BLUE)
293
-
294
- def _has_steps(p: Path) -> bool:
295
- try:
296
- steps = _iter_order_steps(p)
297
- return bool(steps)
298
- except Exception:
299
- return False
300
-
301
- # 已存在则校验是否有步骤
302
- typer.secho(f"[c2rust-transpiler][order] 检查现有顺序文件有效性: {order_path}", fg=typer.colors.BLUE)
303
- if order_path.exists():
304
- if _has_steps(order_path):
305
- typer.secho(f"[c2rust-transpiler][order] 现有顺序文件有效,将使用 {order_path}", fg=typer.colors.GREEN)
306
- return order_path
307
- # 为空或不可读:基于标准路径重新计算(仅 symbols.jsonl)
308
- typer.secho("[c2rust-transpiler][order] 现有顺序文件为空/无效,正基于 symbols.jsonl 重新计算", fg=typer.colors.YELLOW)
309
- try:
310
- compute_translation_order_jsonl(data_dir, out_path=order_path)
311
- except Exception as e:
312
- raise RuntimeError(f"重新计算翻译顺序失败: {e}")
313
- return order_path
314
-
315
- # 不存在:按标准路径生成到固定文件名(仅 symbols.jsonl)
316
- try:
317
- compute_translation_order_jsonl(data_dir, out_path=order_path)
318
- except Exception as e:
319
- raise RuntimeError(f"计算翻译顺序失败: {e}")
320
-
321
- typer.secho(f"[c2rust-transpiler][order] 已生成顺序文件: {order_path} (exists={order_path.exists()})", fg=typer.colors.BLUE)
322
- if not order_path.exists():
323
- raise FileNotFoundError(f"计算后未找到 translation_order.jsonl: {order_path}")
324
-
325
- # 最终校验:若仍无有效步骤,直接报错并提示先执行 scan 或检查 symbols.jsonl
326
- if not _has_steps(order_path):
327
- raise RuntimeError("translation_order.jsonl 无有效步骤。请先执行 'jarvis-c2rust scan' 生成 symbols.jsonl 并重试。")
328
-
329
- return order_path
330
-
331
- def _iter_order_steps(order_jsonl: Path) -> List[List[int]]:
332
- """
333
- 读取翻译顺序(兼容新旧格式),返回按步骤的函数id序列列表。
334
- 新格式:每行包含 "ids": [int, ...] 以及 "items": [完整符号对象,...]。
335
- 不再兼容旧格式(不支持 "records"/"symbols" 键)。
336
- """
337
- # 旧格式已移除:不再需要基于 symbols.jsonl 的 name->id 映射
338
-
339
- steps: List[List[int]] = []
340
- with order_jsonl.open("r", encoding="utf-8") as f:
341
- for ln in f:
342
- ln = ln.strip()
343
- if not ln:
344
- continue
345
- try:
346
- obj = json.loads(ln)
347
- except Exception:
348
- continue
349
-
350
- ids = obj.get("ids")
351
- if isinstance(ids, list) and ids:
352
- # 新格式:仅支持 ids
353
- try:
354
- ids_int = [int(x) for x in ids if isinstance(x, (int, str)) and str(x).strip()]
355
- except Exception:
356
- ids_int = []
357
- if ids_int:
358
- steps.append(ids_int)
359
- continue
360
- # 不支持旧格式(无 ids 则跳过该行)
361
- return steps
362
-
363
-
364
- def _dir_tree(root: Path) -> str:
365
- """格式化 crate 目录结构(过滤部分常见目录)"""
366
- lines: List[str] = []
367
- exclude = {".git", "target", ".jarvis"}
368
- if not root.exists():
369
- return ""
370
- for p in sorted(root.rglob("*")):
371
- if any(part in exclude for part in p.parts):
372
- continue
373
- rel = p.relative_to(root)
374
- depth = len(rel.parts) - 1
375
- indent = " " * depth
376
- name = rel.name + ("/" if p.is_dir() else "")
377
- lines.append(f"{indent}- {name}")
378
- return "\n".join(lines)
379
-
380
-
381
- def _default_crate_dir(project_root: Path) -> Path:
382
- """遵循 llm_module_agent 的默认crate目录选择:<parent>/<cwd.name>_rs(与当前目录同级)当传入为 '.' 时"""
383
- try:
384
- cwd = Path(".").resolve()
385
- if project_root.resolve() == cwd:
386
- return cwd.parent / f"{cwd.name}_rs"
387
- else:
388
- return project_root
389
- except Exception:
390
- return project_root
391
-
392
-
393
- def _read_json(path: Path, default: Any) -> Any:
394
- try:
395
- if path.exists():
396
- return json.loads(path.read_text(encoding="utf-8"))
397
- except Exception:
398
- pass
399
- return default
400
-
401
-
402
- def _write_json(path: Path, obj: Any) -> None:
403
- """原子性写入JSON文件:先写入临时文件,再重命名"""
404
- try:
405
- path.parent.mkdir(parents=True, exist_ok=True)
406
- # 使用临时文件确保原子性
407
- temp_path = path.with_suffix(path.suffix + ".tmp")
408
- temp_path.write_text(json.dumps(obj, ensure_ascii=False, indent=2), encoding="utf-8")
409
- # 原子性重命名
410
- temp_path.replace(path)
411
- except Exception:
412
- # 如果原子写入失败,回退到直接写入
413
- try:
414
- path.write_text(json.dumps(obj, ensure_ascii=False, indent=2), encoding="utf-8")
415
- except Exception:
416
- pass
417
-
418
-
419
- def _extract_json_from_summary(text: str) -> Tuple[Dict[str, Any], Optional[str]]:
420
- """
421
- 从 Agent summary 中提取结构化数据(仅支持 YAML):
422
- - 仅在 <SUMMARY>...</SUMMARY> 块内查找;
423
- - 只接受 <yaml>...</yaml> 标签包裹的 YAML 对象;
424
- 返回(解析结果, 错误信息)
425
- 如果解析成功,返回(data, None)
426
- 如果解析失败,返回({}, 错误信息)
427
- """
428
- if not isinstance(text, str) or not text.strip():
429
- return {}, "摘要文本为空"
430
-
431
- # 提取 <SUMMARY> 块
432
- m = re.search(r"<SUMMARY>([\s\S]*?)</SUMMARY>", text, flags=re.IGNORECASE)
433
- block = (m.group(1) if m else text).strip()
434
-
435
- # 仅解析 <yaml>...</yaml>
436
- mm = re.search(r"<yaml>([\s\S]*?)</yaml>", block, flags=re.IGNORECASE)
437
- raw_yaml = mm.group(1).strip() if mm else None
438
- if not raw_yaml:
439
- return {}, "未找到 <yaml> 或 </yaml> 标签,或标签内容为空"
440
-
441
- try:
442
- import yaml # type: ignore
443
- try:
444
- obj = yaml.safe_load(raw_yaml)
445
- except Exception as yaml_err:
446
- error_msg = f"YAML 解析失败: {str(yaml_err)}"
447
- return {}, error_msg
448
- if isinstance(obj, dict):
449
- return obj, None
450
- return {}, f"YAML 解析结果不是字典,而是 {type(obj).__name__}"
451
- except Exception as e:
452
- return {}, f"解析过程发生异常: {str(e)}"
35
+ from jarvis.jarvis_utils.git_utils import get_latest_commit_hash, get_diff_between_commits
36
+ from jarvis.jarvis_utils.config import get_max_input_token_count
37
+
38
+ from jarvis.jarvis_c2rust.constants import (
39
+ C2RUST_DIRNAME,
40
+ CONFIG_JSON,
41
+ CONSECUTIVE_FIX_FAILURE_THRESHOLD,
42
+ DEFAULT_CHECK_MAX_RETRIES,
43
+ DEFAULT_PLAN_MAX_RETRIES,
44
+ DEFAULT_PLAN_MAX_RETRIES_ENTRY,
45
+ DEFAULT_REVIEW_MAX_ITERATIONS,
46
+ DEFAULT_TEST_MAX_RETRIES,
47
+ ERROR_SUMMARY_MAX_LENGTH,
48
+ MAX_FUNCTION_RETRIES,
49
+ PROGRESS_JSON,
50
+ SYMBOL_MAP_JSONL,
51
+ )
52
+ from jarvis.jarvis_c2rust.loaders import _SymbolMapJsonl
53
+ from jarvis.jarvis_c2rust.models import FnRecord
54
+ from jarvis.jarvis_c2rust.utils import (
55
+ check_and_handle_test_deletion,
56
+ default_crate_dir,
57
+ dir_tree,
58
+ ensure_order_file,
59
+ extract_json_from_summary,
60
+ iter_order_steps,
61
+ read_json,
62
+ write_json,
63
+ )
453
64
 
454
65
 
455
66
  class Transpiler:
@@ -463,17 +74,16 @@ class Transpiler:
463
74
  check_max_retries: Optional[int] = None, # cargo check 阶段最大重试次数(0表示无限重试)
464
75
  test_max_retries: Optional[int] = None, # cargo test 阶段最大重试次数(0表示无限重试)
465
76
  review_max_iterations: int = DEFAULT_REVIEW_MAX_ITERATIONS, # 审查阶段最大迭代次数(0表示无限重试)
466
- resume: bool = True,
467
- only: Optional[List[str]] = None, # 仅转译指定函数名(简单名或限定名)
77
+ disabled_libraries: Optional[List[str]] = None, # 禁用库列表(在实现时禁止使用这些库)
78
+ root_symbols: Optional[List[str]] = None, # 根符号列表(这些符号对应的接口实现时要求对外暴露,main除外)
468
79
  non_interactive: bool = True,
469
80
  ) -> None:
470
81
  self.project_root = Path(project_root).resolve()
471
82
  self.data_dir = self.project_root / C2RUST_DIRNAME
472
83
  self.progress_path = self.data_dir / PROGRESS_JSON
84
+ self.config_path = self.data_dir / CONFIG_JSON
473
85
  # JSONL 路径
474
86
  self.symbol_map_path = self.data_dir / SYMBOL_MAP_JSONL
475
- # 兼容旧版 JSON 字典格式
476
- self.legacy_symbol_map_path = self.data_dir / LEGACY_SYMBOL_MAP_JSON
477
87
  self.llm_group = llm_group
478
88
  self.plan_max_retries = plan_max_retries
479
89
  # 兼容旧接口:如果只设置了 max_retries,则同时用于 check 和 test
@@ -485,19 +95,51 @@ class Transpiler:
485
95
  self.test_max_retries = test_max_retries if test_max_retries is not None else DEFAULT_TEST_MAX_RETRIES
486
96
  self.max_retries = max(self.check_max_retries, self.test_max_retries) # 保持兼容性
487
97
  self.review_max_iterations = review_max_iterations
488
- self.resume = resume
489
- self.only = set(only or [])
490
98
  self.non_interactive = non_interactive
491
- typer.secho(f"[c2rust-transpiler][init] 初始化参数: project_root={self.project_root} crate_dir={Path(crate_dir) if crate_dir else _default_crate_dir(self.project_root)} llm_group={self.llm_group} plan_max_retries={self.plan_max_retries} check_max_retries={self.check_max_retries} test_max_retries={self.test_max_retries} review_max_iterations={self.review_max_iterations} resume={self.resume} only={sorted(list(self.only)) if self.only else []} non_interactive={self.non_interactive}", fg=typer.colors.BLUE)
492
99
 
493
- self.crate_dir = Path(crate_dir) if crate_dir else _default_crate_dir(self.project_root)
100
+ self.crate_dir = Path(crate_dir) if crate_dir else default_crate_dir(self.project_root)
494
101
  # 使用自包含的 order.jsonl 记录构建索引,避免依赖 symbols.jsonl
495
102
  self.fn_index_by_id: Dict[int, FnRecord] = {}
496
103
  self.fn_name_to_id: Dict[str, int] = {}
497
104
 
498
- self.progress: Dict[str, Any] = _read_json(self.progress_path, {"current": None, "converted": []})
105
+ # 断点续跑功能默认始终启用
106
+ self.resume = True
107
+
108
+ # 读取进度文件(仅用于进度信息,不包含配置)
109
+ default_progress = {"current": None, "converted": []}
110
+ self.progress: Dict[str, Any] = read_json(self.progress_path, default_progress)
111
+
112
+ # 从独立的配置文件加载配置(支持从 progress.json 向后兼容迁移)
113
+ config = self._load_config()
114
+
115
+ # 如果提供了新的根符号或禁用库,更新配置;否则从配置文件中恢复
116
+ # 优先使用传入的参数,如果为 None 则从配置文件恢复
117
+ if root_symbols is not None:
118
+ # 传入的参数不为 None,使用传入的值并保存
119
+ self.root_symbols = root_symbols
120
+ else:
121
+ # 传入的参数为 None,从配置文件恢复
122
+ # 如果配置文件中有配置则使用,否则使用空列表
123
+ self.root_symbols = config.get("root_symbols", [])
124
+
125
+ if disabled_libraries is not None:
126
+ # 传入的参数不为 None,使用传入的值并保存
127
+ self.disabled_libraries = disabled_libraries
128
+ else:
129
+ # 传入的参数为 None,从配置文件恢复
130
+ # 如果配置文件中有配置则使用,否则使用空列表
131
+ self.disabled_libraries = config.get("disabled_libraries", [])
132
+
133
+ # 从配置文件读取附加说明(不支持通过参数传入,只能通过配置文件设置)
134
+ self.additional_notes = config.get("additional_notes", "")
135
+
136
+ # 保存配置到独立的配置文件
137
+ self._save_config()
138
+
139
+ # 在初始化完成后打印日志
140
+ typer.secho(f"[c2rust-transpiler][init] 初始化参数: project_root={self.project_root} crate_dir={self.crate_dir} llm_group={self.llm_group} plan_max_retries={self.plan_max_retries} check_max_retries={self.check_max_retries} test_max_retries={self.test_max_retries} review_max_iterations={self.review_max_iterations} disabled_libraries={self.disabled_libraries} root_symbols={self.root_symbols} non_interactive={self.non_interactive}", fg=typer.colors.BLUE)
499
141
  # 使用 JSONL 存储的符号映射
500
- self.symbol_map = _SymbolMapJsonl(self.symbol_map_path, legacy_json_path=self.legacy_symbol_map_path)
142
+ self.symbol_map = _SymbolMapJsonl(self.symbol_map_path)
501
143
 
502
144
  # 当前函数上下文与Agent复用缓存(按单个函数生命周期)
503
145
  self._current_agents: Dict[str, Any] = {}
@@ -509,14 +151,183 @@ class Transpiler:
509
151
  # 兼容旧字段(不再使用)
510
152
  self._current_context_header: str = ""
511
153
  self._current_function_id: Optional[int] = None
154
+ # 缓存 compile_commands.json 的解析结果
155
+ self._compile_commands_cache: Optional[List[Dict[str, Any]]] = None
156
+ self._compile_commands_path: Optional[Path] = None
157
+ # 当前函数开始时的 commit id(用于失败回退)
158
+ self._current_function_start_commit: Optional[str] = None
159
+ # 连续修复失败的次数(用于判断是否需要回退)
160
+ self._consecutive_fix_failures: int = 0
161
+ # 每个 Agent 对应的工具调用前的 commit id(用于细粒度检测)
162
+ self._agent_before_commits: Dict[str, Optional[str]] = {}
163
+
164
+ def _find_compile_commands(self) -> Optional[Path]:
165
+ """
166
+ 查找 compile_commands.json 文件。
167
+ 搜索顺序:
168
+ 1. project_root / compile_commands.json
169
+ 2. project_root / build / compile_commands.json
170
+ 3. project_root 的父目录及向上查找(最多向上3层)
171
+ """
172
+ # 首先在 project_root 下查找
173
+ candidates = [
174
+ self.project_root / "compile_commands.json",
175
+ self.project_root / "build" / "compile_commands.json",
176
+ ]
177
+ # 向上查找(最多3层)
178
+ current = self.project_root.parent
179
+ for _ in range(3):
180
+ if current and current.exists():
181
+ candidates.append(current / "compile_commands.json")
182
+ current = current.parent
183
+ else:
184
+ break
185
+
186
+ for path in candidates:
187
+ if path.exists() and path.is_file():
188
+ return path.resolve()
189
+ return None
190
+
191
+ def _load_compile_commands(self) -> Optional[List[Dict[str, Any]]]:
192
+ """
193
+ 加载 compile_commands.json 文件。
194
+ 如果已缓存,直接返回缓存结果。
195
+ """
196
+ if self._compile_commands_cache is not None:
197
+ return self._compile_commands_cache
198
+
199
+ compile_commands_path = self._find_compile_commands()
200
+ if compile_commands_path is None:
201
+ self._compile_commands_cache = []
202
+ self._compile_commands_path = None
203
+ return None
204
+
205
+ try:
206
+ with compile_commands_path.open("r", encoding="utf-8") as f:
207
+ data = json.load(f)
208
+ if isinstance(data, list):
209
+ self._compile_commands_cache = data
210
+ self._compile_commands_path = compile_commands_path
211
+ typer.secho(f"[c2rust-transpiler][compile_commands] 已加载: {compile_commands_path} ({len(data)} 条记录)", fg=typer.colors.BLUE)
212
+ return data
213
+ except Exception as e:
214
+ typer.secho(f"[c2rust-transpiler][compile_commands] 加载失败: {compile_commands_path}: {e}", fg=typer.colors.YELLOW)
215
+ self._compile_commands_cache = []
216
+ self._compile_commands_path = None
217
+ return None
218
+
219
+ self._compile_commands_cache = []
220
+ self._compile_commands_path = None
221
+ return None
222
+
223
+ def _extract_compile_flags(self, c_file_path: Union[str, Path]) -> Optional[str]:
224
+ """
225
+ 从 compile_commands.json 中提取指定 C 文件的编译参数。
226
+
227
+ 如果 compile_commands.json 中存在 arguments 字段,则用空格连接该数组并返回。
228
+ 如果只有 command 字段,则直接返回 command 字符串。
229
+
230
+ 返回格式:
231
+ - 如果存在 arguments:用空格连接的参数字符串,例如 "-I/usr/include -DDEBUG"
232
+ - 如果只有 command:完整的编译命令字符串,例如 "gcc -I/usr/include -DDEBUG file.c"
233
+
234
+ 如果未找到或解析失败,返回 None。
235
+ """
236
+ compile_commands = self._load_compile_commands()
237
+ if not compile_commands:
238
+ return None
239
+
240
+ # 规范化目标文件路径
241
+ try:
242
+ target_path = Path(c_file_path)
243
+ if not target_path.is_absolute():
244
+ target_path = (self.project_root / target_path).resolve()
245
+ target_path = target_path.resolve()
246
+ except Exception:
247
+ return None
248
+
249
+ # 查找匹配的编译命令
250
+ for entry in compile_commands:
251
+ if not isinstance(entry, dict):
252
+ continue
253
+
254
+ entry_file = entry.get("file")
255
+ if not entry_file:
256
+ continue
257
+
258
+ try:
259
+ entry_path = Path(entry_file)
260
+ if not entry_path.is_absolute() and entry.get("directory"):
261
+ entry_path = (Path(entry.get("directory")) / entry_path).resolve()
262
+ entry_path = entry_path.resolve()
263
+
264
+ # 路径匹配(支持相对路径和绝对路径)
265
+ if entry_path == target_path:
266
+ # 如果存在 arguments,用空格连接并返回
267
+ arguments = entry.get("arguments")
268
+ if isinstance(arguments, list):
269
+ # 过滤掉空字符串,然后用空格连接
270
+ args = [str(arg) for arg in arguments if arg]
271
+ return " ".join(args) if args else None
272
+ # 如果只有 command,直接返回 command 字符串
273
+ elif entry.get("command"):
274
+ command = entry.get("command", "")
275
+ return command if command else None
276
+ except Exception:
277
+ continue
278
+
279
+ return None
512
280
 
513
281
  def _save_progress(self) -> None:
514
282
  """保存进度,使用原子性写入"""
515
- _write_json(self.progress_path, self.progress)
283
+ write_json(self.progress_path, self.progress)
284
+
285
+ def _load_config(self) -> Dict[str, Any]:
286
+ """
287
+ 从独立的配置文件加载配置。
288
+ 如果配置文件不存在,尝试从 progress.json 迁移配置(向后兼容)。
289
+ """
290
+ config_path = self.data_dir / CONFIG_JSON
291
+ default_config = {"root_symbols": [], "disabled_libraries": [], "additional_notes": ""}
292
+
293
+ # 尝试从配置文件读取
294
+ if config_path.exists():
295
+ config = read_json(config_path, default_config)
296
+ if isinstance(config, dict):
297
+ # 确保包含所有必需的键(向后兼容)
298
+ if "additional_notes" not in config:
299
+ config["additional_notes"] = ""
300
+ return config
301
+
302
+ # 向后兼容:如果配置文件不存在,尝试从 progress.json 迁移
303
+ progress_config = self.progress.get("config", {})
304
+ if progress_config:
305
+ # 迁移配置到独立文件
306
+ migrated_config = {
307
+ "root_symbols": progress_config.get("root_symbols", []),
308
+ "disabled_libraries": progress_config.get("disabled_libraries", []),
309
+ "additional_notes": progress_config.get("additional_notes", ""),
310
+ }
311
+ write_json(config_path, migrated_config)
312
+ typer.secho(f"[c2rust-transpiler][config] 已从 progress.json 迁移配置到 {config_path}", fg=typer.colors.YELLOW)
313
+ # 从 progress.json 中移除 config(可选,保持兼容性)
314
+ # if "config" in self.progress:
315
+ # del self.progress["config"]
316
+ # self._save_progress()
317
+ return migrated_config
318
+
319
+ return default_config
320
+
321
+ def _save_config(self) -> None:
322
+ """保存配置到独立的配置文件"""
323
+ config_path = self.data_dir / CONFIG_JSON
324
+ config = {
325
+ "root_symbols": self.root_symbols,
326
+ "disabled_libraries": self.disabled_libraries,
327
+ "additional_notes": getattr(self, "additional_notes", ""),
328
+ }
329
+ write_json(config_path, config)
516
330
 
517
- # JSONL 模式下不再整体写回 symbol_map;此方法保留占位(兼容旧调用),无操作
518
- def _save_symbol_map(self) -> None:
519
- return
520
331
 
521
332
  def _read_source_span(self, rec: FnRecord) -> str:
522
333
  """按起止行读取源码片段(忽略列边界,尽量完整)"""
@@ -610,12 +421,6 @@ class Transpiler:
610
421
  typer.secho(f"[c2rust-transpiler][index] 索引构建完成: ids={len(self.fn_index_by_id)} names={len(self.fn_name_to_id)}", fg=typer.colors.BLUE)
611
422
 
612
423
  def _should_skip(self, rec: FnRecord) -> bool:
613
- # 如果 only 列表非空,则仅处理匹配者
614
- if self.only:
615
- if rec.name in self.only or rec.qname in self.only:
616
- pass
617
- else:
618
- return True
619
424
  # 已转译的跳过(按源位置与名称唯一性判断,避免同名不同位置的误判)
620
425
  if self.symbol_map.has_rec(rec):
621
426
  return True
@@ -676,6 +481,21 @@ class Transpiler:
676
481
  syms = sorted(list(set(syms)))
677
482
  return syms
678
483
 
484
+ def _append_additional_notes(self, prompt: str) -> str:
485
+ """
486
+ 在提示词末尾追加附加说明(如果存在)。
487
+
488
+ Args:
489
+ prompt: 原始提示词
490
+
491
+ Returns:
492
+ 追加了附加说明的提示词
493
+ """
494
+ additional_notes = getattr(self, "additional_notes", "")
495
+ if additional_notes and additional_notes.strip():
496
+ return prompt + "\n\n" + "【附加说明(用户自定义)】\n" + additional_notes.strip()
497
+ return prompt
498
+
679
499
  def _build_module_selection_prompts(
680
500
  self,
681
501
  rec: FnRecord,
@@ -685,13 +505,14 @@ class Transpiler:
685
505
  ) -> Tuple[str, str, str]:
686
506
  """
687
507
  返回 (system_prompt, user_prompt, summary_prompt)
688
- 要求 summary 输出 YAML
508
+ 要求 summary 输出 JSON
689
509
  {
690
510
  "module": "src/<path>.rs or module path (e.g., src/foo/mod.rs or src/foo/bar.rs)",
691
511
  "rust_signature": "pub fn ...",
692
512
  "notes": "optional"
693
513
  }
694
514
  """
515
+ is_root = self._is_root_symbol(rec)
695
516
  system_prompt = (
696
517
  "你是资深Rust工程师,擅长为C/C++函数选择合适的Rust模块位置并产出对应的Rust函数签名。\n"
697
518
  "目标:根据提供的C源码、调用者上下文与crate目录结构,为该函数选择合适的Rust模块文件并给出Rust函数签名(不实现)。\n"
@@ -701,8 +522,27 @@ class Transpiler:
701
522
  "- 函数接口设计应遵循 Rust 最佳实践,不需要兼容 C 的数据类型;优先使用 Rust 原生类型(如 i32/u32/usize、&[T]/&mut [T]、String、Result<T, E> 等),而不是 C 风格类型(如 core::ffi::c_*、libc::c_*);\n"
702
523
  "- 禁止使用 extern \"C\";函数应使用标准的 Rust 调用约定,不需要 C ABI;\n"
703
524
  "- 参数个数与顺序可以保持与 C 一致,但类型设计应优先考虑 Rust 的惯用法和安全性;\n"
704
- "- 仅输出必要信息,避免冗余解释。"
525
+ + ("- **根符号要求**:此函数是根符号,必须使用 `pub` 关键字对外暴露,确保可以从 crate 外部访问。同时,该函数所在的模块必须在 src/lib.rs 中被导出(使用 `pub mod <模块名>;`)。\n" if is_root else "")
526
+ + "- **评估是否需要实现**:在规划阶段,请评估此函数是否真的需要实现。以下情况可以跳过实现(设置 skip_implementation 为 true):\n"
527
+ + " * **已实现的函数**:如果函数已经在目标模块(module)中实现,可以使用 read_code 工具检查目标文件,确认函数已存在且实现正确,则无需重复实现\n"
528
+ + " * **资源释放类函数**:如文件关闭 fclose、内存释放 free、句柄释放、锁释放等,在 Rust 中通常通过 RAII(Drop trait)自动管理,无需显式实现\n"
529
+ + " * **已被库替代**:如果函数已被标准库或第三方 crate 替代(lib_replacement 字段已设置),且不需要兼容层,可以跳过实现\n"
530
+ + " * **空实现或无意义函数**:如果 C 函数本身是空实现、简单返回常量、或仅用于兼容性占位,在 Rust 中可能不需要实现\n"
531
+ + " * **内联函数或宏**:如果函数在 C 中是内联函数或宏,在 Rust 中可能不需要单独实现\n"
532
+ + " * **其他不需要实现的情况**:根据具体情况判断,如果函数在 Rust 转译中确实不需要实现,可以跳过\n"
533
+ + " * 如果设置 skip_implementation 为 true,请在 notes 字段中详细说明原因\n"
534
+ + "- 仅输出必要信息,避免冗余解释。"
705
535
  )
536
+ # 提取编译参数
537
+ compile_flags = self._extract_compile_flags(rec.file)
538
+ compile_flags_section = ""
539
+ if compile_flags:
540
+ compile_flags_section = "\n".join([
541
+ "",
542
+ "C文件编译参数(来自 compile_commands.json):",
543
+ compile_flags,
544
+ ])
545
+
706
546
  user_prompt = "\n".join([
707
547
  "请阅读以下上下文并准备总结:",
708
548
  f"- 函数标识: id={rec.id}, name={rec.name}, qualified={rec.qname}",
@@ -722,7 +562,10 @@ class Transpiler:
722
562
  "",
723
563
  "库替代上下文(若存在):",
724
564
  json.dumps(getattr(rec, "lib_replacement", None), ensure_ascii=False, indent=2),
565
+ compile_flags_section,
725
566
  "",
567
+ *([f"禁用库列表(禁止在实现中使用这些库):{', '.join(self.disabled_libraries)}"] if self.disabled_libraries else []),
568
+ *([""] if self.disabled_libraries else []),
726
569
  "当前crate目录结构(部分):",
727
570
  "<CRATE_TREE>",
728
571
  crate_tree,
@@ -730,45 +573,78 @@ class Transpiler:
730
573
  "",
731
574
  "为避免完整读取体积较大的符号表,你也可以使用工具 read_symbols 按需获取指定符号记录:",
732
575
  "- 工具: read_symbols",
733
- "- 参数示例(YAML):",
734
- f" symbols_file: \"{(self.data_dir / 'symbols.jsonl').resolve()}\"",
735
- " symbols:",
736
- " - 要读取的符号列表",
576
+ "- 参数示例(JSON):",
577
+ f" {{\"symbols_file\": \"{(self.data_dir / 'symbols.jsonl').resolve()}\", \"symbols\": [\"符号1\", \"符号2\"]}}",
578
+ "",
579
+ "**重要:检查函数是否已实现**",
580
+ "在确定目标模块(module)后,请使用 read_code 工具检查该模块文件,确认函数是否已经实现:",
581
+ "- 工具: read_code",
582
+ "- 参数示例(JSON):",
583
+ " {\"file_path\": \"<目标模块路径>\"}",
584
+ "- 如果函数已经在目标模块中实现,且实现正确,可以设置 skip_implementation 为 true,并在 notes 中说明 \"函数已在目标模块中实现\"",
737
585
  "",
738
586
  "如果理解完毕,请进入总结阶段。",
739
587
  ])
740
588
  summary_prompt = (
741
- "请仅输出一个 <SUMMARY> 块,块内必须且只包含一个 <yaml>...</yaml>,不得包含其它内容。\n"
742
- "允许字段(YAML 对象):\n"
743
- '- module: "<绝对路径>/src/xxx.rs 或 <绝对路径>/src/xxx/mod.rs;或相对路径 src/xxx.rs / src/xxx/mod.rs"\n'
744
- '- rust_signature: "pub fn xxx(...)->..."\n'
745
- '- notes: "可选说明(若有上下文缺失或风险点,请在此列出)"\n'
589
+ "请仅输出一个 <SUMMARY> 块,块内必须且只包含一个 JSON 对象,不得包含其它内容。\n"
590
+ "允许字段(JSON 对象):\n"
591
+ '- "module": "<绝对路径>/src/xxx.rs 或 <绝对路径>/src/xxx/mod.rs;或相对路径 src/xxx.rs / src/xxx/mod.rs"\n'
592
+ '- "rust_signature": "pub fn xxx(...)->..."\n'
593
+ '- "skip_implementation": bool // 可选,如果为 true,表示此函数不需要实现,可以直接标记为已实现\n'
594
+ '- "notes": "可选说明(若有上下文缺失或风险点,请在此列出;如果 skip_implementation 为 true,必须在此说明原因)"\n'
746
595
  "注意:\n"
747
596
  "- module 必须位于 crate 的 src/ 目录下,接受绝对路径或以 src/ 开头的相对路径;尽量选择已有文件;如需新建文件,给出合理路径;\n"
748
597
  "- rust_signature 应遵循 Rust 最佳实践,不需要兼容 C 的数据类型;优先使用 Rust 原生类型和惯用法,而不是 C 风格类型。\n"
598
+ "- **评估是否需要实现**:请仔细评估此函数是否真的需要实现。以下情况可以设置 skip_implementation 为 true:\n"
599
+ + " * **已实现的函数**:如果函数已经在目标模块(module)中实现,可以使用 read_code 工具检查目标文件,确认函数已存在且实现正确,则无需重复实现\n"
600
+ + " * **资源释放类函数**:如文件关闭 fclose、内存释放 free、句柄释放、锁释放等,在 Rust 中通常通过 RAII(Drop trait)自动管理,无需显式实现\n"
601
+ + " * **已被库替代**:如果函数已被标准库或第三方 crate 替代(lib_replacement 字段已设置),且不需要兼容层,可以跳过实现\n"
602
+ + " * **空实现或无意义函数**:如果 C 函数本身是空实现、简单返回常量、或仅用于兼容性占位,在 Rust 中可能不需要实现\n"
603
+ + " * **内联函数或宏**:如果函数在 C 中是内联函数或宏,在 Rust 中可能不需要单独实现\n"
604
+ + " * **其他不需要实现的情况**:根据具体情况判断,如果函数在 Rust 转译中确实不需要实现,可以跳过\n"
605
+ + " * **重要**:如果设置 skip_implementation 为 true,必须在 notes 字段中详细说明原因,例如:\n"
606
+ + " - \"函数已在目标模块中实现\"\n"
607
+ + " - \"通过 RAII 自动管理,无需显式实现\"\n"
608
+ + " - \"已被标准库 std::xxx 替代,无需实现\"\n"
609
+ + " - \"空实现函数,在 Rust 中不需要\"\n"
610
+ + " - \"内联函数,已在调用处展开,无需单独实现\"\n"
611
+ + "- 如果函数确实需要实现,则不要设置 skip_implementation 或设置为 false\n"
749
612
  "- 类型设计原则:\n"
750
613
  " * 基本类型:优先使用 i32/u32/i64/u64/isize/usize/f32/f64 等原生 Rust 类型,而不是 core::ffi::c_* 或 libc::c_*;\n"
751
614
  " * 指针/引用:优先使用引用 &T/&mut T 或切片 &[T]/&mut [T],而非原始指针 *const T/*mut T;仅在必要时使用原始指针;\n"
752
615
  " * 字符串:优先使用 String、&str 而非 *const c_char/*mut c_char;\n"
753
616
  " * 错误处理:考虑使用 Result<T, E> 而非 C 风格的错误码;\n"
754
617
  " * 参数个数与顺序可以保持与 C 一致,但类型应优先考虑 Rust 的惯用法、安全性和可读性;\n"
755
- "- 函数签名应包含可见性修饰(pub)与函数名;类型应为 Rust 最佳实践的选择,而非简单映射 C 类型。\n"
618
+ + ("- **根符号要求**:此函数是根符号,rust_signature 必须包含 `pub` 关键字,确保可以从 crate 外部访问。同时,该函数所在的模块必须在 src/lib.rs 中被导出(使用 `pub mod <模块名>;`)。\n" if is_root else "")
619
+ + "- 函数签名应包含可见性修饰(pub)与函数名;类型应为 Rust 最佳实践的选择,而非简单映射 C 类型。\n"
756
620
  "- 禁止使用 extern \"C\";函数应使用标准的 Rust 调用约定,不需要 C ABI。\n"
757
- "请严格按以下格式输出:\n"
758
- "<SUMMARY><yaml>\nmodule: \"...\"\nrust_signature: \"...\"\nnotes: \"...\"\n</yaml></SUMMARY>"
621
+ "请严格按以下格式输出(JSON格式,支持jsonnet语法如尾随逗号、注释、|||分隔符多行字符串等):\n"
622
+ "示例1(正常函数):\n"
623
+ "<SUMMARY>\n{\n \"module\": \"...\",\n \"rust_signature\": \"...\",\n \"notes\": \"...\"\n}\n</SUMMARY>\n"
624
+ "示例2(已实现的函数,可跳过实现):\n"
625
+ "<SUMMARY>\n{\n \"module\": \"...\",\n \"rust_signature\": \"...\",\n \"skip_implementation\": true,\n \"notes\": \"函数已在目标模块中实现\"\n}\n</SUMMARY>\n"
626
+ "示例3(不需要实现的函数,可跳过实现):\n"
627
+ "<SUMMARY>\n{\n \"module\": \"...\",\n \"rust_signature\": \"...\",\n \"skip_implementation\": true,\n \"notes\": \"通过 RAII 自动管理,无需显式实现\"\n}\n</SUMMARY>\n"
628
+ "示例4(已被库替代,可跳过实现):\n"
629
+ "<SUMMARY>\n{\n \"module\": \"...\",\n \"rust_signature\": \"...\",\n \"skip_implementation\": true,\n \"notes\": \"已被标准库 std::xxx 替代,无需实现\"\n}\n</SUMMARY>\n"
630
+ "示例5(空实现函数,可跳过实现):\n"
631
+ "<SUMMARY>\n{\n \"module\": \"...\",\n \"rust_signature\": \"...\",\n \"skip_implementation\": true,\n \"notes\": \"C 函数为空实现,在 Rust 中不需要\"\n}\n</SUMMARY>"
759
632
  )
633
+ # 在 user_prompt 和 summary_prompt 中追加附加说明(system_prompt 通常不需要)
634
+ user_prompt = self._append_additional_notes(user_prompt)
635
+ summary_prompt = self._append_additional_notes(summary_prompt)
760
636
  return system_prompt, user_prompt, summary_prompt
761
637
 
762
- def _plan_module_and_signature(self, rec: FnRecord, c_code: str) -> Tuple[str, str]:
763
- """调用 Agent 选择模块与签名,返回 (module_path, rust_signature),若格式不满足将自动重试直到满足"""
764
- crate_tree = _dir_tree(self.crate_dir)
638
+ def _plan_module_and_signature(self, rec: FnRecord, c_code: str) -> Tuple[str, str, bool]:
639
+ """调用 Agent 选择模块与签名,返回 (module_path, rust_signature, skip_implementation),若格式不满足将自动重试直到满足"""
640
+ crate_tree = dir_tree(self.crate_dir)
765
641
  callees_ctx = self._collect_callees_context(rec)
766
642
  sys_p, usr_p, base_sum_p = self._build_module_selection_prompts(rec, c_code, callees_ctx, crate_tree)
767
643
 
768
644
  def _validate(meta: Any) -> Tuple[bool, str]:
769
645
  """基本格式检查,仅验证字段存在性,不做硬编码规则校验"""
770
646
  if not isinstance(meta, dict) or not meta:
771
- return False, "未解析到有效的 <SUMMARY><yaml> 对象"
647
+ return False, "未解析到有效的 <SUMMARY> 中的 JSON 对象"
772
648
  module = meta.get("module")
773
649
  rust_sig = meta.get("rust_signature")
774
650
  if not isinstance(module, str) or not module.strip():
@@ -802,8 +678,8 @@ class Transpiler:
802
678
  base_sum_p
803
679
  + "\n\n[格式检查失败,必须重试]\n"
804
680
  + f"- 失败原因:{reason}\n"
805
- + "- 仅输出一个 <SUMMARY> 块;块内仅包含单个 <yaml> 对象;\n"
806
- + '- YAML 对象必须包含字段:module、rust_signature。\n'
681
+ + "- 仅输出一个 <SUMMARY> 块;块内直接包含 JSON 对象(不需要额外的标签);\n"
682
+ + '- JSON 对象必须包含字段:module、rust_signature。\n'
807
683
  )
808
684
 
809
685
  attempt = 0
@@ -813,59 +689,66 @@ class Transpiler:
813
689
  use_direct_model = False # 标记是否使用直接模型调用
814
690
  agent = None # 在循环外声明,以便重试时复用
815
691
 
692
+ # 注意:Agent 必须在 crate 根目录下创建,以确保工具(如 read_symbols)能正确获取符号表
693
+ # 由于 transpile() 开始时已切换到 crate 目录,此处无需再次切换
816
694
  while plan_max_retries_val == 0 or attempt < plan_max_retries_val:
817
695
  attempt += 1
818
696
  sum_p = base_sum_p if attempt == 1 else _retry_sum_prompt(last_reason)
819
697
 
820
698
  # 第一次创建 Agent,后续重试时复用(如果使用直接模型调用)
699
+ # 注意:Agent 必须在 crate 根目录下创建,以确保工具(如 read_symbols)能正确获取符号表
821
700
  if agent is None or not use_direct_model:
701
+ # 获取函数信息用于 Agent name
702
+ fn_name = rec.qname or rec.name or f"fn_{rec.id}"
703
+ agent_name = f"C2Rust-Function-Planner({fn_name})"
822
704
  agent = Agent(
823
705
  system_prompt=sys_p,
824
- name="C2Rust-Function-Planner",
706
+ name=agent_name,
825
707
  model_group=self.llm_group,
826
708
  summary_prompt=sum_p,
827
709
  need_summary=True,
828
710
  auto_complete=True,
829
- use_tools=["execute_script", "read_code", "retrieve_memory", "save_memory", "read_symbols"],
830
- plan=False,
711
+ use_tools=["execute_script", "read_code", "read_symbols"],
831
712
  non_interactive=self.non_interactive,
832
713
  use_methodology=False,
833
714
  use_analysis=False,
834
- disable_file_edit=True,
835
715
  )
716
+ # 订阅 BEFORE_TOOL_CALL 和 AFTER_TOOL_CALL 事件,用于细粒度检测测试代码删除
717
+ agent.event_bus.subscribe(BEFORE_TOOL_CALL, self._on_before_tool_call)
718
+ agent.event_bus.subscribe(AFTER_TOOL_CALL, self._on_after_tool_call)
719
+ # 记录 Agent 创建时的 commit id(作为初始值)
720
+ agent_id = id(agent)
721
+ agent_key = f"agent_{agent_id}"
722
+ initial_commit = self._get_crate_commit_hash()
723
+ if initial_commit:
724
+ self._agent_before_commits[agent_key] = initial_commit
836
725
 
837
- prev_cwd = os.getcwd()
838
- try:
839
- os.chdir(str(self.crate_dir))
726
+ if use_direct_model:
727
+ # 格式校验失败后,直接调用模型接口
728
+ # 构造包含摘要提示词和具体错误信息的完整提示
729
+ error_guidance = ""
730
+ if last_reason and last_reason != "未知错误":
731
+ if "JSON解析失败" in last_reason:
732
+ error_guidance = f"\n\n**格式错误详情(请根据以下错误修复输出格式):**\n- {last_reason}\n\n请确保输出的JSON格式正确,包括正确的引号、逗号、大括号等。JSON 对象必须包含字段:module(字符串)、rust_signature(字符串)。支持jsonnet语法(如尾随逗号、注释、||| 或 ``` 分隔符多行字符串等)。"
733
+ else:
734
+ error_guidance = f"\n\n**格式错误详情(请根据以下错误修复输出格式):**\n- {last_reason}\n\n请确保输出格式正确:仅输出一个 <SUMMARY> 块,块内直接包含 JSON 对象(不需要额外的标签);JSON 对象必须包含字段:module(字符串)、rust_signature(字符串)。支持jsonnet语法(如尾随逗号、注释、||| 或 ``` 分隔符多行字符串等)。"
840
735
 
841
- if use_direct_model:
842
- # 格式校验失败后,直接调用模型接口
843
- # 构造包含摘要提示词和具体错误信息的完整提示
844
- error_guidance = ""
845
- if last_reason and last_reason != "未知错误":
846
- if "YAML解析失败" in last_reason:
847
- error_guidance = f"\n\n**格式错误详情(请根据以下错误修复输出格式):**\n- {last_reason}\n\n请确保输出的YAML格式正确,包括正确的缩进、引号、冒号等。YAML 对象必须包含字段:module(字符串)、rust_signature(字符串)。"
848
- else:
849
- error_guidance = f"\n\n**格式错误详情(请根据以下错误修复输出格式):**\n- {last_reason}\n\n请确保输出格式正确:仅输出一个 <SUMMARY> 块,块内仅包含单个 <yaml> 对象;YAML 对象必须包含字段:module(字符串)、rust_signature(字符串)。"
850
-
851
- full_prompt = f"{usr_p}{error_guidance}\n\n{sum_p}"
852
- try:
853
- response = agent.model.chat_until_success(full_prompt) # type: ignore
854
- summary = response
855
- except Exception as e:
856
- typer.secho(f"[c2rust-transpiler][plan] 直接模型调用失败: {e},回退到 run()", fg=typer.colors.YELLOW)
857
- summary = agent.run(usr_p)
858
- else:
859
- # 第一次使用 run(),让 Agent 完整运行(可能使用工具)
736
+ full_prompt = f"{usr_p}{error_guidance}\n\n{sum_p}"
737
+ try:
738
+ response = agent.model.chat_until_success(full_prompt) # type: ignore
739
+ summary = response
740
+ except Exception as e:
741
+ typer.secho(f"[c2rust-transpiler][plan] 直接模型调用失败: {e},回退到 run()", fg=typer.colors.YELLOW)
860
742
  summary = agent.run(usr_p)
861
- finally:
862
- os.chdir(prev_cwd)
743
+ else:
744
+ # 第一次使用 run(),让 Agent 完整运行(可能使用工具)
745
+ summary = agent.run(usr_p)
863
746
 
864
- meta, parse_error = _extract_json_from_summary(str(summary or ""))
747
+ meta, parse_error = extract_json_from_summary(str(summary or ""))
865
748
  if parse_error:
866
- # YAML解析失败,将错误信息反馈给模型
867
- typer.secho(f"[c2rust-transpiler][plan] YAML解析失败: {parse_error}", fg=typer.colors.YELLOW)
868
- last_reason = f"YAML解析失败: {parse_error}"
749
+ # JSON解析失败,将错误信息反馈给模型
750
+ typer.secho(f"[c2rust-transpiler][plan] JSON解析失败: {parse_error}", fg=typer.colors.YELLOW)
751
+ last_reason = f"JSON解析失败: {parse_error}"
869
752
  use_direct_model = True
870
753
  # 解析失败,继续重试
871
754
  continue
@@ -874,13 +757,21 @@ class Transpiler:
874
757
  if ok:
875
758
  module = str(meta.get("module") or "").strip()
876
759
  rust_sig = str(meta.get("rust_signature") or "").strip()
877
- typer.secho(f"[c2rust-transpiler][plan] {attempt} 次尝试成功: 模块={module}, 签名={rust_sig}", fg=typer.colors.GREEN)
878
- return module, rust_sig
760
+ skip_impl = bool(meta.get("skip_implementation") is True)
761
+ if skip_impl:
762
+ notes = str(meta.get("notes") or "")
763
+ typer.secho(f"[c2rust-transpiler][plan] 第 {attempt} 次尝试成功: 模块={module}, 签名={rust_sig}, 跳过实现={skip_impl}", fg=typer.colors.GREEN)
764
+ if notes:
765
+ typer.secho(f"[c2rust-transpiler][plan] 跳过实现原因: {notes}", fg=typer.colors.CYAN)
766
+ else:
767
+ typer.secho(f"[c2rust-transpiler][plan] 第 {attempt} 次尝试成功: 模块={module}, 签名={rust_sig}", fg=typer.colors.GREEN)
768
+ return module, rust_sig, skip_impl
879
769
  else:
880
770
  typer.secho(f"[c2rust-transpiler][plan] 第 {attempt} 次尝试失败: {reason}", fg=typer.colors.YELLOW)
881
771
  last_reason = reason
882
772
  # 格式校验失败,后续重试使用直接模型调用
883
773
  use_direct_model = True
774
+
884
775
  # 规划超出重试上限:回退到兜底方案(默认模块 src/ffi.rs + 简单占位签名)
885
776
  # 注意:如果 plan_max_retries_val == 0(无限重试),理论上不应该到达这里
886
777
  try:
@@ -890,7 +781,7 @@ class Transpiler:
890
781
  fallback_module = "src/ffi.rs"
891
782
  fallback_sig = f"pub fn {rec.name or ('fn_' + str(rec.id))}()"
892
783
  typer.secho(f"[c2rust-transpiler][plan] 超出规划重试上限({plan_max_retries_val if plan_max_retries_val > 0 else '无限'}),回退到兜底: module={fallback_module}, signature={fallback_sig}", fg=typer.colors.YELLOW)
893
- return fallback_module, fallback_sig
784
+ return fallback_module, fallback_sig, False
894
785
 
895
786
  def _update_progress_current(self, rec: FnRecord, module: str, rust_sig: str) -> None:
896
787
  self.progress["current"] = {
@@ -938,8 +829,10 @@ class Transpiler:
938
829
 
939
830
  # 汇总上下文头部,供后续复用时拼接
940
831
  callees_ctx = self._collect_callees_context(rec)
941
- crate_tree = _dir_tree(self.crate_dir)
832
+ crate_tree = dir_tree(self.crate_dir)
942
833
  librep_ctx = rec.lib_replacement if isinstance(rec.lib_replacement, dict) else None
834
+ # 提取编译参数
835
+ compile_flags = self._extract_compile_flags(rec.file)
943
836
 
944
837
  header_lines = [
945
838
  "【当前函数上下文(复用Agent专用)】",
@@ -960,12 +853,21 @@ class Transpiler:
960
853
  "",
961
854
  "库替代上下文(若有):",
962
855
  json.dumps(librep_ctx, ensure_ascii=False, indent=2),
856
+ ]
857
+ # 添加编译参数(如果存在)
858
+ if compile_flags:
859
+ header_lines.extend([
860
+ "",
861
+ "C文件编译参数(来自 compile_commands.json):",
862
+ compile_flags,
863
+ ])
864
+ header_lines.extend([
963
865
  "",
964
866
  "crate 目录结构(部分):",
965
867
  "<CRATE_TREE>",
966
868
  crate_tree,
967
869
  "</CRATE_TREE>",
968
- ]
870
+ ])
969
871
  # 精简头部(后续复用)
970
872
  compact_lines = [
971
873
  "【函数上下文简要(复用)】",
@@ -979,37 +881,130 @@ class Transpiler:
979
881
  self._current_context_compact_header = "\n".join(compact_lines)
980
882
  self._current_context_full_sent = False
981
883
 
982
- # 初始化一次代码生成Agent(CodeAgent),单个函数生命周期内复用
983
- # 该Agent用于代码生成和修复,启用方法论、分析和强制记忆功能
984
- self._current_agents[f"code_agent::{rec.id}"] = CodeAgent(
985
- need_summary=False,
986
- non_interactive=self.non_interactive,
987
- plan=False,
988
- model_group=self.llm_group,
989
- use_methodology=True,
990
- use_analysis=True,
991
- force_save_memory=True, # 强制使用记忆功能
992
- )
993
884
 
994
- def _get_repair_agent(self) -> CodeAgent:
885
+ def _on_before_tool_call(self, agent: Any, current_response=None, **kwargs) -> None:
886
+ """
887
+ 工具调用前的事件处理器,用于记录工具调用前的 commit id。
888
+
889
+ 在每次工具调用前记录当前的 commit,以便在工具调用后检测到问题时能够回退。
890
+ """
891
+ try:
892
+ # 只关注可能修改代码的工具
893
+ # 注意:在 BEFORE_TOOL_CALL 时,工具还未执行,无法获取工具名称
894
+ # 但我们可以在 AFTER_TOOL_CALL 时检查工具名称,这里先记录 commit
895
+ agent_id = id(agent)
896
+ agent_key = f"agent_{agent_id}"
897
+ current_commit = self._get_crate_commit_hash()
898
+ if current_commit:
899
+ # 记录工具调用前的 commit(如果之前没有记录,或者 commit 已变化)
900
+ if agent_key not in self._agent_before_commits or self._agent_before_commits[agent_key] != current_commit:
901
+ self._agent_before_commits[agent_key] = current_commit
902
+ except Exception as e:
903
+ # 事件处理器异常不应影响主流程
904
+ typer.secho(f"[c2rust-transpiler][test-detection] BEFORE_TOOL_CALL 事件处理器异常: {e}", fg=typer.colors.YELLOW)
905
+
906
+ def _on_after_tool_call(self, agent: Any, current_response=None, need_return=None, tool_prompt=None, **kwargs) -> None:
907
+ """
908
+ 工具调用后的事件处理器,用于细粒度检测测试代码删除。
909
+
910
+ 在每次工具调用后立即检测,如果检测到测试代码被错误删除,立即回退。
911
+ """
912
+ try:
913
+ # 只检测编辑文件的工具调用
914
+ last_tool = agent.get_user_data("__last_executed_tool__") if hasattr(agent, "get_user_data") else None
915
+ if not last_tool:
916
+ return
917
+
918
+ # 只关注可能修改代码的工具
919
+ edit_tools = {"edit_file", "rewrite_file", "apply_patch"}
920
+ if last_tool not in edit_tools:
921
+ return
922
+
923
+ # 获取该 Agent 对应的工具调用前的 commit id
924
+ agent_id = id(agent)
925
+ agent_key = f"agent_{agent_id}"
926
+ before_commit = self._agent_before_commits.get(agent_key)
927
+
928
+ # 如果没有 commit 信息,无法检测
929
+ if not before_commit:
930
+ return
931
+
932
+ # 检测测试代码删除
933
+ from jarvis.jarvis_c2rust.utils import detect_test_deletion, ask_llm_about_test_deletion
934
+
935
+ detection_result = detect_test_deletion("[c2rust-transpiler]")
936
+ if not detection_result:
937
+ # 没有检测到删除,更新 commit 记录
938
+ current_commit = self._get_crate_commit_hash()
939
+ if current_commit and current_commit != before_commit:
940
+ self._agent_before_commits[agent_key] = current_commit
941
+ return
942
+
943
+ typer.secho("[c2rust-transpiler][test-detection] 检测到可能错误删除了测试代码标记(工具调用后检测)", fg=typer.colors.YELLOW)
944
+
945
+ # 询问 LLM 是否合理
946
+ need_reset = ask_llm_about_test_deletion(detection_result, agent, "[c2rust-transpiler]")
947
+
948
+ if need_reset:
949
+ typer.secho(f"[c2rust-transpiler][test-detection] LLM 确认删除不合理,正在回退到 commit: {before_commit}", fg=typer.colors.RED)
950
+ if self._reset_to_commit(before_commit):
951
+ typer.secho("[c2rust-transpiler][test-detection] 已回退到之前的 commit(工具调用后检测)", fg=typer.colors.GREEN)
952
+ # 回退后,保持之前的 commit 记录
953
+ self._agent_before_commits[agent_key] = before_commit
954
+ # 在 agent 的 session 中添加提示,告知修改被撤销
955
+ if hasattr(agent, "session") and hasattr(agent.session, "prompt"):
956
+ agent.session.prompt += "\n\n⚠️ 修改被撤销:检测到测试代码被错误删除,已回退到之前的版本。\n"
957
+ else:
958
+ typer.secho("[c2rust-transpiler][test-detection] 回退失败", fg=typer.colors.RED)
959
+ else:
960
+ # LLM 认为删除合理,更新 commit 记录
961
+ current_commit = self._get_crate_commit_hash()
962
+ if current_commit and current_commit != before_commit:
963
+ self._agent_before_commits[agent_key] = current_commit
964
+ except Exception as e:
965
+ # 事件处理器异常不应影响主流程
966
+ typer.secho(f"[c2rust-transpiler][test-detection] AFTER_TOOL_CALL 事件处理器异常: {e}", fg=typer.colors.YELLOW)
967
+
968
+ def _get_code_agent(self) -> CodeAgent:
995
969
  """
996
- 获取复用的代码生成Agent(CodeAgent)。若未初始化,则按当前函数id创建。
997
- 该Agent用于代码生成和修复,启用方法论、分析和强制记忆功能。
970
+ 获取代码生成/修复Agent(CodeAgent)。若未初始化,则按当前函数id创建。
971
+ 统一用于代码生成和修复,启用方法论和分析功能以提供更好的代码质量。
972
+ 注意:Agent 必须在 crate 根目录下创建,以确保工具(如 read_symbols)能正确获取符号表。
973
+ 提示:代码生成遵循 TDD(测试驱动开发)方法,通过提示词指导 Agent 先写测试再写实现。
998
974
  """
999
975
  fid = self._current_function_id
1000
976
  key = f"code_agent::{fid}" if fid is not None else "code_agent::default"
1001
977
  agent = self._current_agents.get(key)
1002
978
  if agent is None:
1003
- # 复用的代码生成Agent启用方法论、分析和强制记忆功能
979
+ # 注意:Agent 必须在 crate 根目录下创建,以确保工具(如 read_symbols)能正确获取符号表
980
+ # 由于 transpile() 开始时已切换到 crate 目录,此处无需再次切换
981
+ # 统一启用方法论和分析功能,提供更好的代码生成和修复能力
982
+ # 获取函数信息用于 Agent name
983
+ fn_name = ""
984
+ if fid is not None:
985
+ rec = self.fn_index_by_id.get(fid)
986
+ if rec:
987
+ fn_name = rec.qname or rec.name or f"fn_{fid}"
988
+ agent_name = "C2Rust-CodeAgent" + (f"({fn_name})" if fn_name else "")
1004
989
  agent = CodeAgent(
990
+ name=agent_name,
1005
991
  need_summary=False,
1006
992
  non_interactive=self.non_interactive,
1007
- plan=False,
1008
993
  model_group=self.llm_group,
994
+ append_tools="read_symbols,methodology",
1009
995
  use_methodology=True,
1010
996
  use_analysis=True,
1011
- force_save_memory=True, # 强制使用记忆功能
997
+ force_save_memory=False,
1012
998
  )
999
+ # 订阅 BEFORE_TOOL_CALL 和 AFTER_TOOL_CALL 事件,用于细粒度检测测试代码删除
1000
+ agent.event_bus.subscribe(BEFORE_TOOL_CALL, self._on_before_tool_call)
1001
+ agent.event_bus.subscribe(AFTER_TOOL_CALL, self._on_after_tool_call)
1002
+ # 记录 Agent 创建时的 commit id(作为初始值)
1003
+ agent_id = id(agent)
1004
+ agent_key = f"agent_{agent_id}"
1005
+ initial_commit = self._get_crate_commit_hash()
1006
+ if initial_commit:
1007
+ self._agent_before_commits[agent_key] = initial_commit
1013
1008
  self._current_agents[key] = agent
1014
1009
  return agent
1015
1010
 
@@ -1032,93 +1027,65 @@ class Transpiler:
1032
1027
 
1033
1028
  # ========= 代码生成与修复 =========
1034
1029
 
1035
- def _codeagent_generate_impl(self, rec: FnRecord, c_code: str, module: str, rust_sig: str, unresolved: List[str]) -> None:
1030
+ def _is_root_symbol(self, rec: FnRecord) -> bool:
1031
+ """判断函数是否为根符号(排除 main)"""
1032
+ if not self.root_symbols:
1033
+ return False
1034
+ # 检查函数名或限定名是否在根符号列表中
1035
+ return (rec.name in self.root_symbols) or (rec.qname in self.root_symbols)
1036
+
1037
+ def _build_generate_impl_prompt(
1038
+ self, rec: FnRecord, c_code: str, module: str, rust_sig: str, unresolved: List[str]
1039
+ ) -> str:
1036
1040
  """
1037
- 使用 CodeAgent 生成/更新目标模块中的函数实现。
1038
- 约束:最小变更,生成可编译的占位实现,尽可能保留后续细化空间。
1041
+ 构建代码生成提示词。
1042
+
1043
+ 返回完整的提示词字符串。
1039
1044
  """
1040
1045
  symbols_path = str((self.data_dir / "symbols.jsonl").resolve())
1046
+ is_root = self._is_root_symbol(rec)
1041
1047
  requirement_lines = [
1042
- f"目标:在 crate 目录 {self.crate_dir.resolve()} {module} 中,为 C 函数 {rec.qname or rec.name} 生成对应的 Rust 实现。",
1043
- "要求:",
1044
- f"- 函数签名(建议):{rust_sig}",
1045
- f"- 原 C 工程目录位置:{self.project_root.resolve()}",
1046
- "- module 文件不存在则新建;为所在模块添加必要的 mod 声明(若需要);",
1047
- "- 若已有函数占位/实现,尽量最小修改,不要破坏现有代码;",
1048
- "- 你可以参考原 C 函数的关联实现(如同文件/同模块的相关辅助函数、内联实现、宏与注释等),在保持语义一致的前提下以符合 Rust 风格的方式实现;避免机械复制粘贴;",
1049
- f"- 如需参考原 C 工程中的其他文件,可在原 C 工程目录 {self.project_root.resolve()} 中查找;",
1050
- "- 禁止在函数实现中使用 todo!/unimplemented! 作为占位;对于尚未实现的被调符号,请阅读其原 C 实现并在本次一并补齐等价的 Rust 实现,避免遗留占位;",
1051
- "- 为保证行为等价,禁止使用占位返回或随意默认值;必须实现与 C 语义等价的返回逻辑,不得使用 panic!/todo!/unimplemented!;",
1052
- "- 不要删除或移动其他无关文件。",
1048
+ f"目标:在 {module} 中,使用 TDD 方法为 C 函数 {rec.qname or rec.name} 生成 Rust 实现。",
1049
+ f"函数签名:{rust_sig}",
1050
+ f"crate 目录:{self.crate_dir.resolve()}",
1051
+ f"C 工程目录:{self.project_root.resolve()}",
1052
+ *(["根符号要求:必须使用 `pub` 关键字,模块必须在 src/lib.rs 中导出"] if is_root else []),
1053
+ "",
1054
+ "【TDD 流程】",
1055
+ "1. Red:先写测试(#[cfg(test)] mod tests),基于 C 函数行为设计测试用例",
1056
+ "2. Green:编写实现使测试通过,确保与 C 语义等价",
1057
+ "3. Refactor:优化代码,保持测试通过",
1058
+ "",
1059
+ "【核心要求】",
1060
+ "- 先写测试再写实现,测试必须可编译通过",
1061
+ "- 禁止使用 todo!/unimplemented!,必须实现完整功能",
1062
+ "- 使用 Rust 原生类型(i32/u32、&str/String、&[T]/&mut [T]、Result<T,E>),避免 C 风格类型",
1063
+ "- 禁止使用 extern \"C\",使用标准 Rust 调用约定",
1064
+ "- 保持最小变更,避免无关重构",
1065
+ "- 注释使用中文,禁止 use ...::* 通配导入",
1066
+ "- 资源释放类函数(fclose/free 等)可通过 RAII 自动管理,提供空实现并在文档中说明",
1067
+ *([f"- 禁用库:{', '.join(self.disabled_libraries)}"] if self.disabled_libraries else []),
1068
+ "",
1069
+ "【依赖处理】",
1070
+ "- 检查依赖函数是否已实现,未实现的需一并补齐(遵循 TDD:先测试后实现)",
1071
+ "- 使用 read_symbols/read_code 获取 C 源码",
1072
+ "- 优先处理底层依赖,确保所有测试通过",
1053
1073
  "",
1054
- "编码原则与规范:",
1055
- "- 函数接口设计应遵循 Rust 最佳实践,不需要兼容 C 的数据类型;优先使用 Rust 原生类型和惯用法:",
1056
- " * 基本类型:使用 i32/u32/i64/u64/isize/usize/f32/f64 等原生 Rust 类型,而非 core::ffi::c_* 或 libc::c_*;",
1057
- " * 指针/引用:优先使用引用 &T/&mut T 或切片 &[T]/&mut [T],而非原始指针 *const T/*mut T;仅在必要时使用原始指针;",
1058
- " * 字符串:优先使用 String、&str 而非 *const c_char/*mut c_char;",
1059
- " * 错误处理:如适用,考虑使用 Result<T, E> 而非 C 风格的错误码;",
1060
- " * 禁止使用 extern \"C\";函数应使用标准的 Rust 调用约定,不需要 C ABI;",
1061
- "- 保持最小变更,避免无关重构与格式化;禁止批量重排/重命名/移动文件;",
1062
- "- 命名遵循Rust惯例(函数/模块蛇形命名),公共API使用pub;",
1063
- "- 优先使用安全Rust;如需unsafe,将范围最小化并添加注释说明原因与SAFETY保证;",
1064
- "- 错误处理:遵循 C 语义保持等价行为(逻辑一致性),但在类型设计上优先使用 Rust 惯用法;避免 panic!/unwrap();",
1065
- "- 实现中禁止使用 todo!/unimplemented! 占位;对于尚未实现的被调符号,应基于其 C 源码补齐等价 Rust 实现;",
1066
- "- 返回值必须与 C 语义等价,不得使用占位返回或随意默认值;避免 panic!/todo!/unimplemented!;",
1067
- "- 若依赖未实现符号,请通过 read_symbols/read_code 获取其 C 源码并生成等价的 Rust 实现(可放置在同一模块或合理模块),而不是使用 todo!;",
1068
- "- 文档:为新增函数添加简要文档注释,注明来源C函数与意图;",
1069
- "- 导入:禁止使用 use ...::* 通配;仅允许精确导入所需符号",
1070
- "- 依赖管理:如引入新的外部 crate 或需要启用 feature,请同步更新 Cargo.toml 的 [dependencies]/[dev-dependencies]/[features],避免未声明依赖导致构建失败;版本号可使用兼容范围(如 ^x.y)或默认值;",
1071
- "- 测试生成:若函数可测试(无需外部环境/IO/网络/全局状态),在同文件添加 #[cfg(test)] mod tests 并编写至少一个可编译的单元测试(建议命名为 test_<函数名>_basic);测试仅调用函数,避免使用 panic!/todo!/unimplemented!;unsafe 函数以 unsafe 调用;必要时使用占位参数(如 0、core::ptr::null()/null_mut()、默认值)以保证通过。",
1072
- "- 测试设计文档:在测试函数顶部使用文档注释(///)简要说明测试用例设计,包括:输入构造、预置条件、期望行为(或成功执行)、边界/异常不作为否决项;注释内容仅用于说明,不影响实现。",
1073
- "- 不可测试时:如函数依赖外部环境或调用未实现符号,暂不生成测试,但请在函数文档注释中注明不可测试原因(例如外部依赖/未实现符号)。",
1074
- "- 指针+长度参数:如 C 存在 <ptr, len> 组合,请优先保持成对出现;若暂不清晰,至少保留长度参数占位",
1075
- "- 风格:遵循 rustfmt 默认风格,避免引入与本次改动无关的大范围格式变化;",
1076
- "- 输出限制:仅以补丁形式修改目标文件,不要输出解释或多余文本。",
1074
+ "【工具】",
1075
+ f"- read_symbols: {{\"symbols_file\": \"{symbols_path}\", \"symbols\": [...]}}",
1076
+ "- read_code: 读取 C 源码或 Rust 模块",
1077
1077
  "",
1078
- "C 源码片段(供参考,不要原样粘贴):",
1078
+ *([f"未转换符号:{', '.join(unresolved)}"] if unresolved else []),
1079
+ "",
1080
+ "C 源码:",
1079
1081
  "<C_SOURCE>",
1080
1082
  c_code,
1081
1083
  "</C_SOURCE>",
1082
1084
  "",
1083
- "符号表签名与参数(只读参考):",
1085
+ "签名参考:",
1084
1086
  json.dumps({"signature": getattr(rec, "signature", ""), "params": getattr(rec, "params", None)}, ensure_ascii=False, indent=2),
1085
1087
  "",
1086
- "注意:所有修改均以补丁方式进行。",
1087
- "",
1088
- "如对实现细节不确定:可以使用工具 read_symbols 按需获取指定符号记录:",
1089
- "- 工具: read_symbols",
1090
- "- 参数示例(YAML):",
1091
- f" symbols_file: \"{symbols_path}\"",
1092
- " symbols:",
1093
- " - 要读取的符号列表",
1094
- "",
1095
- "尚未转换的被调符号如下(请阅读这些符号的 C 源码并生成等价的 Rust 实现;必要时新增模块或签名):",
1096
- *[f"- {s}" for s in (unresolved or [])],
1097
- "",
1098
- "【重要:依赖检查与实现要求】",
1099
- "在实现函数之前,请务必检查以下内容:",
1100
- "1. 检查当前函数是否已实现:",
1101
- f" - 在目标模块 {module} 中查找函数 {rec.qname or rec.name} 的实现",
1102
- " - 如果已存在实现,检查其是否完整且正确",
1103
- "2. 检查所有依赖函数是否已实现:",
1104
- " - 遍历当前函数调用的所有被调函数(包括直接调用和间接调用)",
1105
- " - 对于每个被调函数,检查其在 Rust crate 中是否已有完整实现",
1106
- " - 可以使用 read_code 工具读取相关模块文件进行检查",
1107
- " - 可以使用 retrieve_memory 工具检索已保存的函数实现记忆",
1108
- "3. 对于未实现的依赖函数:",
1109
- " - 使用 read_symbols 工具获取其 C 源码和符号信息",
1110
- " - 使用 read_code 工具读取其 C 源码实现",
1111
- " - 在本次实现中一并补齐这些依赖函数的 Rust 实现",
1112
- " - 根据依赖关系选择合适的模块位置(可在同一模块或合理的新模块中)",
1113
- " - 确保所有依赖函数都有完整实现,禁止使用 todo!/unimplemented! 占位",
1114
- "4. 实现顺序:",
1115
- " - 优先实现最底层的依赖函数(不依赖其他未实现函数的函数)",
1116
- " - 然后实现依赖这些底层函数的函数",
1117
- " - 最后实现当前目标函数",
1118
- "5. 验证:",
1119
- " - 确保当前函数及其所有依赖函数都已完整实现",
1120
- " - 确保没有遗留的 todo!/unimplemented! 占位",
1121
- " - 确保所有函数调用都能正确解析",
1088
+ "仅输出补丁,不要解释。",
1122
1089
  ]
1123
1090
  # 若存在库替代上下文,则附加到实现提示中,便于生成器参考(多库组合、参考API、备注等)
1124
1091
  librep_ctx = None
@@ -1133,24 +1100,26 @@ class Transpiler:
1133
1100
  json.dumps(librep_ctx, ensure_ascii=False, indent=2),
1134
1101
  "",
1135
1102
  ])
1136
- requirement_lines.extend([
1137
- "",
1138
- "【重要:记忆保存要求】",
1139
- "在完成函数实现之后,请务必使用 save_memory 工具记录以下关键信息,以便后续检索和复用:",
1140
- f"- 函数名称:{rec.qname or rec.name} (id={rec.id})",
1141
- f"- 源文件位置:{rec.file}:{rec.start_line}-{rec.end_line}",
1142
- f"- 目标模块:{module}",
1143
- f"- Rust 函数签名:{rust_sig}",
1144
- "- C 函数的核心功能与语义",
1145
- "- 关键实现细节与设计决策",
1146
- "- 依赖关系与调用链",
1147
- "- 类型转换与边界处理要点",
1148
- "- 错误处理策略",
1149
- "- 实际实现的 Rust 代码要点与关键逻辑",
1150
- "记忆标签建议:使用 'c2rust', 'function_impl', 函数名等作为标签,便于后续检索。",
1151
- "请在完成代码实现之后保存记忆,记录本次实现的完整信息。",
1152
- ])
1103
+ # 添加编译参数(如果存在)
1104
+ compile_flags = self._extract_compile_flags(rec.file)
1105
+ if compile_flags:
1106
+ requirement_lines.extend([
1107
+ "",
1108
+ "C文件编译参数(来自 compile_commands.json):",
1109
+ compile_flags,
1110
+ "",
1111
+ ])
1153
1112
  prompt = "\n".join(requirement_lines)
1113
+ return self._append_additional_notes(prompt)
1114
+
1115
+ def _codeagent_generate_impl(self, rec: FnRecord, c_code: str, module: str, rust_sig: str, unresolved: List[str]) -> None:
1116
+ """
1117
+ 使用 CodeAgent 生成/更新目标模块中的函数实现。
1118
+ 约束:最小变更,生成可编译的占位实现,尽可能保留后续细化空间。
1119
+ """
1120
+ # 构建提示词
1121
+ prompt = self._build_generate_impl_prompt(rec, c_code, module, rust_sig, unresolved)
1122
+
1154
1123
  # 确保目标模块文件存在(提高补丁应用与实现落盘的确定性)
1155
1124
  try:
1156
1125
  mp = Path(module)
@@ -1165,24 +1134,52 @@ class Transpiler:
1165
1134
  pass
1166
1135
  except Exception:
1167
1136
  pass
1168
- # 切换到 crate 目录运行复用的代码编写与修复Agent,运行完毕后恢复
1169
- prev_cwd = os.getcwd()
1170
- try:
1171
- os.chdir(str(self.crate_dir))
1172
- agent = self._get_repair_agent() # 使用复用的Agent,同时支持代码编写和修复
1173
- agent.run(self._compose_prompt_with_context(prompt), prefix="[c2rust-transpiler][gen]", suffix="")
1174
- finally:
1175
- os.chdir(prev_cwd)
1176
-
1177
- def _extract_rust_fn_name_from_sig(self, rust_sig: str) -> str:
1178
- """
1179
- rust 签名中提取函数名,支持生命周期参数和泛型参数。
1180
- 例如: 'pub fn foo(a: i32) -> i32 { ... }' -> 'foo'
1181
- 例如: 'pub fn foo<'a>(bzf: &'a mut BzFile) -> Result<&'a [u8], BzError>' -> 'foo'
1182
- """
1183
- # 支持生命周期参数和泛型参数:fn name<'a, T>(...)
1184
- m = re.search(r"\bfn\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?:<[^>]+>)?\s*\(", rust_sig or "")
1185
- return m.group(1) if m else ""
1137
+
1138
+ # 由于 transpile() 开始时已切换到 crate 目录,此处无需再次切换
1139
+ # 记录运行前的 commit
1140
+ before_commit = self._get_crate_commit_hash()
1141
+ agent = self._get_code_agent()
1142
+ agent.run(self._compose_prompt_with_context(prompt), prefix="[c2rust-transpiler][gen]", suffix="")
1143
+
1144
+ # 检测并处理测试代码删除
1145
+ if self._check_and_handle_test_deletion(before_commit, agent):
1146
+ # 如果回退了,需要重新运行 agent
1147
+ typer.secho("[c2rust-transpiler][gen] 检测到测试代码删除问题,已回退,重新运行 agent", fg=typer.colors.YELLOW)
1148
+ before_commit = self._get_crate_commit_hash()
1149
+ agent.run(self._compose_prompt_with_context(prompt), prefix="[c2rust-transpiler][gen][retry]", suffix="")
1150
+ # 再次检测
1151
+ if self._check_and_handle_test_deletion(before_commit, agent):
1152
+ typer.secho("[c2rust-transpiler][gen] 再次检测到测试代码删除问题,已回退", fg=typer.colors.RED)
1153
+
1154
+ # 如果是根符号,确保其模块在 lib.rs 中被暴露
1155
+ if self._is_root_symbol(rec):
1156
+ try:
1157
+ mp = Path(module)
1158
+ crate_root = self.crate_dir.resolve()
1159
+ rel = mp.resolve().relative_to(crate_root) if mp.is_absolute() else Path(module)
1160
+ rel_s = str(rel).replace("\\", "/")
1161
+ if rel_s.startswith("./"):
1162
+ rel_s = rel_s[2:]
1163
+ if rel_s.startswith("src/"):
1164
+ parts = rel_s[len("src/"):].strip("/").split("/")
1165
+ if parts and parts[0]:
1166
+ top_mod = parts[0]
1167
+ # 过滤掉 "mod" 关键字和 .rs 文件
1168
+ if top_mod != "mod" and not top_mod.endswith(".rs"):
1169
+ self._ensure_top_level_pub_mod(top_mod)
1170
+ typer.secho(f"[c2rust-transpiler][gen] 根符号 {rec.qname or rec.name} 的模块 {top_mod} 已在 lib.rs 中暴露", fg=typer.colors.GREEN)
1171
+ except Exception:
1172
+ pass
1173
+
1174
+ def _extract_rust_fn_name_from_sig(self, rust_sig: str) -> str:
1175
+ """
1176
+ 从 rust 签名中提取函数名,支持生命周期参数和泛型参数。
1177
+ 例如: 'pub fn foo(a: i32) -> i32 { ... }' -> 'foo'
1178
+ 例如: 'pub fn foo<'a>(bzf: &'a mut BzFile) -> Result<&'a [u8], BzError>' -> 'foo'
1179
+ """
1180
+ # 支持生命周期参数和泛型参数:fn name<'a, T>(...)
1181
+ m = re.search(r"\bfn\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?:<[^>]+>)?\s*\(", rust_sig or "")
1182
+ return m.group(1) if m else ""
1186
1183
 
1187
1184
  def _ensure_top_level_pub_mod(self, mod_name: str) -> None:
1188
1185
  """
@@ -1193,7 +1190,7 @@ class Transpiler:
1193
1190
  - 最小改动,不覆盖其他内容
1194
1191
  """
1195
1192
  try:
1196
- if not mod_name or mod_name in ("lib", "main"):
1193
+ if not mod_name or mod_name in ("lib", "main", "mod"):
1197
1194
  return
1198
1195
  lib_rs = (self.crate_dir / "src" / "lib.rs").resolve()
1199
1196
  lib_rs.parent.mkdir(parents=True, exist_ok=True)
@@ -1231,7 +1228,7 @@ class Transpiler:
1231
1228
  - 最小改动,不覆盖其他内容
1232
1229
  """
1233
1230
  try:
1234
- if not child_mod or child_mod in ("lib", "main"):
1231
+ if not child_mod or child_mod in ("lib", "main", "mod"):
1235
1232
  return
1236
1233
  mod_rs = (dir_path / "mod.rs").resolve()
1237
1234
  mod_rs.parent.mkdir(parents=True, exist_ok=True)
@@ -1288,6 +1285,9 @@ class Transpiler:
1288
1285
  if parts[-1] in ("lib.rs", "main.rs"):
1289
1286
  return
1290
1287
  child = parts[-1][:-3] # 去掉 .rs
1288
+ # 过滤掉 "mod" 关键字
1289
+ if child == "mod":
1290
+ return
1291
1291
  if len(parts) > 1:
1292
1292
  start_dir = crate_root / "src" / "/".join(parts[:-1])
1293
1293
  else:
@@ -1333,6 +1333,10 @@ class Transpiler:
1333
1333
  break
1334
1334
  # 在 parent/mod.rs 确保 pub mod <cur_dir.name>
1335
1335
  # 确保 parent 在 crate/src 下
1336
+ # 过滤掉 "mod" 关键字
1337
+ if cur_dir.name == "mod":
1338
+ cur_dir = parent
1339
+ continue
1336
1340
  try:
1337
1341
  parent_rel = parent.relative_to(crate_root)
1338
1342
  if str(parent_rel).replace("\\", "/").startswith("src/"):
@@ -1418,7 +1422,7 @@ class Transpiler:
1418
1422
  typer.secho(f"[c2rust-transpiler][todo] 未在 src/ 中找到 todo!(\"{symbol}\") 或 unimplemented!(\"{symbol}\") 的出现", fg=typer.colors.BLUE)
1419
1423
  return
1420
1424
 
1421
- # 在当前工作目录运行 CodeAgent,不进入 crate 目录
1425
+ # 由于 transpile() 开始时已切换到 crate 目录,此处无需再次切换
1422
1426
  typer.secho(f"[c2rust-transpiler][todo] 发现 {len(matches)} 个包含 todo!(\"{symbol}\") 或 unimplemented!(\"{symbol}\") 的文件", fg=typer.colors.YELLOW)
1423
1427
  for target_file in matches:
1424
1428
  prompt = "\n".join([
@@ -1437,13 +1441,20 @@ class Transpiler:
1437
1441
  f"仅修改 {target_file} 中与上述占位相关的代码,其他位置不要改动。",
1438
1442
  "请仅输出补丁,不要输出解释或多余文本。",
1439
1443
  ])
1440
- prev_cwd = os.getcwd()
1441
- try:
1442
- os.chdir(str(self.crate_dir))
1443
- agent = self._get_repair_agent()
1444
- agent.run(self._compose_prompt_with_context(prompt), prefix=f"[c2rust-transpiler][todo-fix:{symbol}]", suffix="")
1445
- finally:
1446
- os.chdir(prev_cwd)
1444
+ # 记录运行前的 commit
1445
+ before_commit = self._get_crate_commit_hash()
1446
+ agent = self._get_code_agent()
1447
+ agent.run(self._compose_prompt_with_context(prompt), prefix=f"[c2rust-transpiler][todo-fix:{symbol}]", suffix="")
1448
+
1449
+ # 检测并处理测试代码删除
1450
+ if self._check_and_handle_test_deletion(before_commit, agent):
1451
+ # 如果回退了,需要重新运行 agent
1452
+ typer.secho(f"[c2rust-transpiler][todo-fix] 检测到测试代码删除问题,已回退,重新运行 agent (symbol={symbol})", fg=typer.colors.YELLOW)
1453
+ before_commit = self._get_crate_commit_hash()
1454
+ agent.run(self._compose_prompt_with_context(prompt), prefix=f"[c2rust-transpiler][todo-fix:{symbol}][retry]", suffix="")
1455
+ # 再次检测
1456
+ if self._check_and_handle_test_deletion(before_commit, agent):
1457
+ typer.secho(f"[c2rust-transpiler][todo-fix] 再次检测到测试代码删除问题,已回退 (symbol={symbol})", fg=typer.colors.RED)
1447
1458
 
1448
1459
  def _classify_rust_error(self, text: str) -> List[str]:
1449
1460
  """
@@ -1507,21 +1518,17 @@ class Transpiler:
1507
1518
  c_code = ""
1508
1519
  return curr, sym_name, src_loc, c_code
1509
1520
 
1510
- def _build_repair_prompt(self, stage: str, output: str, tags: List[str], sym_name: str, src_loc: str, c_code: str, curr: Dict[str, Any], symbols_path: str, include_output_patch_hint: bool = False) -> str:
1521
+ def _build_repair_prompt_base(
1522
+ self, stage: str, tags: List[str], sym_name: str, src_loc: str, c_code: str,
1523
+ curr: Dict[str, Any], symbols_path: str, include_output_patch_hint: bool = False
1524
+ ) -> List[str]:
1511
1525
  """
1512
- 构建修复提示词。
1526
+ 构建修复提示词的基础部分。
1513
1527
 
1514
- Args:
1515
- stage: 阶段名称("cargo check" 或 "cargo test")
1516
- output: 构建错误输出
1517
- tags: 错误分类标签
1518
- sym_name: 符号名称
1519
- src_loc: 源文件位置
1520
- c_code: C 源码片段
1521
- curr: 当前进度信息
1522
- symbols_path: 符号表文件路径
1523
- include_output_patch_hint: 是否包含"仅输出补丁"提示(test阶段需要)
1528
+ 返回基础行列表。
1524
1529
  """
1530
+ # 检查是否为根符号
1531
+ is_root = sym_name in (self.root_symbols or [])
1525
1532
  base_lines = [
1526
1533
  f"目标:以最小的改动修复问题,使 `{stage}` 命令可以通过。",
1527
1534
  f"阶段:{stage}",
@@ -1531,7 +1538,25 @@ class Transpiler:
1531
1538
  "- 如构建失败源于缺失或未实现的被调函数/依赖,请阅读其 C 源码并在本次一并补齐等价的 Rust 实现;必要时可在合理的模块中新建函数;",
1532
1539
  "- 禁止使用 todo!/unimplemented! 作为占位;",
1533
1540
  "- 可使用工具 read_symbols/read_code 获取依赖符号的 C 源码与位置以辅助实现;仅精确导入所需符号,避免通配;",
1541
+ "- **🔍 调试辅助:如果遇到难以定位的问题,可以使用以下方法辅助调试**:",
1542
+ " * 添加临时调试输出:使用 `println!()` 或 `dbg!()` 宏输出关键变量的值、函数调用路径、中间状态等",
1543
+ " * 检查函数调用链:使用 `read_code` 工具读取相关函数的实现,确认调用关系是否正确",
1544
+ " * 验证数据流:在关键位置添加调试输出,检查数据在函数间的传递是否正确",
1545
+ " * 对比 C 实现:使用 `read_symbols` 和 `read_code` 工具读取 C 源码,对比 Rust 实现与 C 实现的差异",
1546
+ " * 检查类型转换:确认 Rust 类型与 C 类型的对应关系是否正确,特别是指针、数组、结构体等",
1547
+ " * 验证边界条件:检查数组边界、空值处理、溢出处理等边界情况",
1548
+ " * 运行单个测试:如果测试套件很大,可以使用 `cargo test -- --nocapture <test_name>` 运行特定测试,加快调试速度",
1549
+ " * 查看完整错误信息:确保阅读完整的错误输出,包括堆栈跟踪、类型信息、位置信息等",
1550
+ " * 注意:调试输出可以在修复后移除,但建议保留关键位置的调试信息直到问题完全解决",
1551
+ "- **⚠️ 重要:修复后必须验证** - 修复完成后,必须使用 `execute_script` 工具执行验证命令:",
1552
+ " * 执行 `cargo test -- --nocapture` 验证编译和测试是否通过",
1553
+ " * 命令必须成功(返回码为 0),才说明修复成功",
1554
+ " * **不要假设修复成功,必须实际执行命令验证**",
1555
+ " * **cargo test 会自动编译,无需单独执行 cargo check**",
1556
+ "- 注释规范:所有代码注释(包括文档注释、行内注释、块注释等)必须使用中文;",
1534
1557
  f"- 依赖管理:如修复中引入新的外部 crate 或需要启用 feature,请同步更新 Cargo.toml 的 [dependencies]/[dev-dependencies]/[features]{(',避免未声明依赖导致构建失败;版本号可使用兼容范围(如 ^x.y)或默认值' if stage == 'cargo test' else '')};",
1558
+ *([f"- **禁用库约束**:禁止在修复中使用以下库:{', '.join(self.disabled_libraries)}。如果这些库在 Cargo.toml 中已存在,请移除相关依赖;如果修复需要使用这些库的功能,请使用标准库或其他允许的库替代。"] if self.disabled_libraries else []),
1559
+ *([f"- **根符号要求**:此函数是根符号({sym_name}),必须使用 `pub` 关键字对外暴露,确保可以从 crate 外部访问。同时,该函数所在的模块必须在 src/lib.rs 中被导出(使用 `pub mod <模块名>;`)。"] if is_root else []),
1535
1560
  "",
1536
1561
  "【重要:依赖检查与实现要求】",
1537
1562
  "在修复问题之前,请务必检查以下内容:",
@@ -1543,7 +1568,6 @@ class Transpiler:
1543
1568
  " - 遍历当前函数调用的所有被调函数(包括直接调用和间接调用)",
1544
1569
  " - 对于每个被调函数,检查其在 Rust crate 中是否已有完整实现",
1545
1570
  " - 可以使用 read_code 工具读取相关模块文件进行检查",
1546
- " - 可以使用 retrieve_memory 工具检索已保存的函数实现记忆",
1547
1571
  "3. 对于未实现的依赖函数:",
1548
1572
  " - 使用 read_symbols 工具获取其 C 源码和符号信息",
1549
1573
  " - 使用 read_code 工具读取其 C 源码实现",
@@ -1573,217 +1597,486 @@ class Transpiler:
1573
1597
  "<C_SOURCE>",
1574
1598
  c_code,
1575
1599
  "</C_SOURCE>",
1600
+ ])
1601
+ # 添加编译参数(如果存在)
1602
+ c_file_path = curr.get("file") or ""
1603
+ if c_file_path:
1604
+ compile_flags = self._extract_compile_flags(c_file_path)
1605
+ if compile_flags:
1606
+ base_lines.extend([
1607
+ "",
1608
+ "C文件编译参数(来自 compile_commands.json):",
1609
+ compile_flags,
1610
+ ])
1611
+ base_lines.extend([
1576
1612
  "",
1577
- "如需定位或交叉验证 C 符号位置,请使用符号表检索工具:",
1578
- "- 工具: read_symbols",
1579
- "- 参数示例(YAML):",
1580
- f" symbols_file: \"{symbols_path}\"",
1581
- " symbols:",
1582
- f" - \"{sym_name}\"",
1613
+ "【工具使用建议】",
1614
+ "1. 符号表检索:",
1615
+ " - 工具: read_symbols",
1616
+ " - 用途: 定位或交叉验证 C 符号位置",
1617
+ " - 参数示例(JSON):",
1618
+ f" {{\"symbols_file\": \"{symbols_path}\", \"symbols\": [\"{sym_name}\"]}}",
1619
+ "",
1620
+ "2. 代码读取:",
1621
+ " - 工具: read_code",
1622
+ " - 用途: 读取 C 源码实现或 Rust 模块文件",
1623
+ " - 调试用途: 当遇到问题时,可以读取相关文件检查实现是否正确",
1624
+ "",
1625
+ "3. 脚本执行(调试辅助):",
1626
+ " - 工具: execute_script",
1627
+ " - 调试用途:",
1628
+ " * 执行 `cargo test -- --nocapture <test_name>` 运行特定测试,加快调试速度",
1629
+ " * 执行 `cargo test --message-format=short --no-run` 只检查编译,不运行测试",
1630
+ " * 执行 `cargo check` 快速检查编译错误(如果测试太慢)",
1631
+ " * 执行 `cargo test --lib` 只运行库测试,跳过集成测试",
1632
+ " * 执行 `cargo test --test <test_file>` 运行特定的测试文件",
1583
1633
  "",
1584
1634
  "上下文:",
1585
1635
  f"- crate 根目录路径: {self.crate_dir.resolve()}",
1636
+ f"- 包名称(用于 cargo -p): {self.crate_dir.name}",
1586
1637
  ])
1587
- if stage == "cargo check":
1588
- base_lines.append(f"- 包名称(用于 cargo -p): {self.crate_dir.name}")
1589
- else:
1590
- base_lines.append(f"- 包名称(用于 cargo build -p): {self.crate_dir.name}")
1638
+ return base_lines
1639
+
1640
+ def _build_repair_prompt_stage_section(
1641
+ self, stage: str, output: str, command: Optional[str] = None
1642
+ ) -> List[str]:
1643
+ """
1644
+ 构建修复提示词的阶段特定部分(测试或检查)。
1645
+
1646
+ 返回阶段特定的行列表。
1647
+ """
1648
+ section_lines: List[str] = []
1591
1649
  if stage == "cargo test":
1592
- base_lines.extend([
1650
+ section_lines.extend([
1593
1651
  "",
1594
- "【测试失败信息】",
1652
+ "【⚠️ 重要:测试失败 - 必须修复】",
1595
1653
  "以下输出来自 `cargo test` 命令,包含测试执行结果和失败详情:",
1654
+ "- **测试当前状态:失败** - 必须修复才能继续",
1596
1655
  "- 如果看到测试用例名称和断言失败,说明测试逻辑或实现有问题",
1597
1656
  "- 如果看到编译错误,说明代码存在语法或类型错误",
1598
- "- 请仔细阅读失败信息,包括:测试用例名称、断言失败位置、期望值与实际值、堆栈跟踪等",
1657
+ "- **请仔细阅读失败信息**,包括:",
1658
+ " * 测试用例名称(如 `test_bz_read_get_unused`)",
1659
+ " * 失败位置(文件路径和行号,如 `src/ffi/decompress.rs:76:47`)",
1660
+ " * 错误类型(如 `SequenceError`、`Result::unwrap()` 失败等)",
1661
+ " * 期望值与实际值的差异",
1662
+ " * 完整的堆栈跟踪信息",
1663
+ "",
1664
+ "**关键要求:**",
1665
+ "- 必须分析测试失败的根本原因,而不是假设问题已解决",
1666
+ "- 必须实际修复导致测试失败的代码,而不是只修改测试用例",
1667
+ "- 修复后必须确保测试能够通过,而不是只修复编译错误",
1599
1668
  "",
1600
- "请阅读以下测试失败信息并进行必要修复:",
1669
+ ])
1670
+ if command:
1671
+ section_lines.append(f"执行的命令:{command}")
1672
+ section_lines.append("提示:如果不相信上述命令执行结果,可以使用 execute_script 工具自己执行一次该命令进行验证。")
1673
+ section_lines.extend([
1674
+ "",
1675
+ "【测试失败详细信息 - 必须仔细阅读并修复】",
1676
+ "以下是从 `cargo test` 命令获取的完整输出,包含测试失败的具体信息:",
1601
1677
  "<TEST_FAILURE>",
1602
1678
  output,
1603
1679
  "</TEST_FAILURE>",
1604
1680
  "",
1681
+ "**修复要求:**",
1682
+ "1. 仔细分析上述测试失败信息,找出失败的根本原因",
1683
+ "2. 定位到具体的代码位置(文件路径和行号)",
1684
+ "3. **如果问题难以定位,添加调试信息辅助定位**:",
1685
+ " - 在关键位置添加 `println!()` 或 `dbg!()` 输出变量值、函数调用路径、中间状态",
1686
+ " - 检查函数参数和返回值是否正确传递",
1687
+ " - 验证数据结构和类型转换是否正确",
1688
+ " - 对比 C 实现与 Rust 实现的差异,找出可能导致问题的点",
1689
+ " - 使用 `read_code` 工具读取相关函数的实现,确认逻辑是否正确",
1690
+ " - 如果测试输出信息不足,可以添加更详细的调试输出来定位问题",
1691
+ "4. 修复导致测试失败的代码逻辑",
1692
+ "5. 确保修复后测试能够通过(不要只修复编译错误)",
1693
+ "6. 如果测试用例本身有问题,可以修改测试用例,但必须确保测试能够正确验证函数行为",
1694
+ "",
1695
+ "**⚠️ 重要:修复后必须验证**",
1696
+ "- 修复完成后,**必须使用 `execute_script` 工具执行以下命令验证修复效果**:",
1697
+ f" - 命令:`{command or 'cargo test -- --nocapture'}`",
1698
+ "- 验证要求:",
1699
+ " * 如果命令执行成功(返回码为 0),说明修复成功",
1700
+ " * 如果命令执行失败(返回码非 0),说明修复未成功,需要继续修复",
1701
+ " * **不要假设修复成功,必须实际执行命令验证**",
1702
+ "- 如果验证失败,请分析失败原因并继续修复,直到验证通过",
1703
+ "",
1605
1704
  "修复后请再次执行 `cargo test` 进行验证。",
1606
1705
  ])
1607
1706
  else:
1608
- base_lines.extend([
1707
+ section_lines.extend([
1609
1708
  "",
1610
1709
  "请阅读以下构建错误并进行必要修复:",
1710
+ ])
1711
+ if command:
1712
+ section_lines.append(f"执行的命令:{command}")
1713
+ section_lines.append("提示:如果不相信上述命令执行结果,可以使用 execute_script 工具自己执行一次该命令进行验证。")
1714
+ section_lines.extend([
1715
+ "",
1611
1716
  "<BUILD_ERROR>",
1612
1717
  output,
1613
1718
  "</BUILD_ERROR>",
1614
1719
  "",
1615
- "修复后请再次执行 `cargo check` 验证,后续将自动运行 `cargo test`。",
1720
+ "**修复要求:**",
1721
+ "1. 仔细分析上述构建错误信息,找出错误的根本原因",
1722
+ "2. 定位到具体的代码位置(文件路径和行号)",
1723
+ "3. **如果问题难以定位,添加调试信息辅助定位**:",
1724
+ " - 使用 `read_code` 工具读取相关文件,检查代码实现是否正确",
1725
+ " - 检查类型定义、函数签名、模块导入等是否正确",
1726
+ " - 验证依赖关系是否正确,所有被调用的函数/类型是否已定义",
1727
+ " - 如果错误信息不够清晰,可以尝试编译单个文件或模块来缩小问题范围",
1728
+ " - 对比 C 实现与 Rust 实现的差异,确认类型映射是否正确",
1729
+ "4. 修复导致构建错误的代码",
1730
+ "5. 确保修复后代码能够编译通过",
1731
+ "",
1732
+ "**⚠️ 重要:修复后必须验证**",
1733
+ "- 修复完成后,**必须使用 `execute_script` 工具执行以下命令验证修复效果**:",
1734
+ " - 命令:`cargo test -- --nocapture`",
1735
+ "- 验证要求:",
1736
+ " * 命令必须执行成功(返回码为 0),才说明修复成功",
1737
+ " * 如果命令执行失败(返回码非 0),说明修复未成功,需要继续修复",
1738
+ " * **不要假设修复成功,必须实际执行命令验证**",
1739
+ "- 如果验证失败,请分析失败原因并继续修复,直到验证通过",
1740
+ "",
1741
+ "修复后请执行 `cargo test -- --nocapture` 进行验证。",
1616
1742
  ])
1617
- return "\n".join(base_lines)
1743
+ return section_lines
1618
1744
 
1619
- def _cargo_build_loop(self) -> bool:
1620
- """在 crate 目录执行构建与测试:先 cargo check,再 cargo test。失败则最小化修复直到通过或达到上限。"""
1621
- workspace_root = str(self.crate_dir)
1622
- check_limit = f"最大重试: {self.check_max_retries if self.check_max_retries > 0 else '无限'}"
1623
- test_limit = f"最大重试: {self.test_max_retries if self.test_max_retries > 0 else '无限'}"
1624
- typer.secho(f"[c2rust-transpiler][build] 工作区={workspace_root},开始构建循环(check -> test,{check_limit} / {test_limit}", fg=typer.colors.MAGENTA)
1625
- check_iter = 0
1626
- test_iter = 0
1627
- while True:
1628
- # 阶段一:cargo check(更快)
1629
- check_iter += 1
1630
- res_check = subprocess.run(
1631
- ["cargo", "check", "-q"],
1632
- capture_output=True,
1633
- text=True,
1634
- check=False,
1635
- cwd=workspace_root,
1636
- )
1637
- if res_check.returncode != 0:
1638
- output = (res_check.stdout or "") + "\n" + (res_check.stderr or "")
1639
- limit_info = f" (上限: {self.check_max_retries if self.check_max_retries > 0 else '无限'})" if check_iter % 10 == 0 or check_iter == 1 else ""
1640
- typer.secho(f"[c2rust-transpiler][build] cargo check 失败 (第 {check_iter} 次尝试{limit_info})。", fg=typer.colors.RED)
1641
- typer.secho(output, fg=typer.colors.RED)
1642
- # 达到上限则记录并退出(0表示无限重试)
1643
- maxr = self.check_max_retries
1644
- if maxr > 0 and check_iter >= maxr:
1645
- typer.secho(f"[c2rust-transpiler][build] 已达到最大重试次数上限({maxr}),停止构建修复循环。", fg=typer.colors.RED)
1646
- try:
1647
- cur = self.progress.get("current") or {}
1648
- metrics = cur.get("metrics") or {}
1649
- metrics["check_attempts"] = int(check_iter)
1650
- metrics["test_attempts"] = int(test_iter)
1651
- cur["metrics"] = metrics
1652
- cur["impl_verified"] = False
1653
- cur["failed_stage"] = "check"
1654
- err_summary = (output or "").strip()
1655
- if len(err_summary) > ERROR_SUMMARY_MAX_LENGTH:
1656
- err_summary = err_summary[:ERROR_SUMMARY_MAX_LENGTH] + "...(truncated)"
1657
- cur["last_build_error"] = err_summary
1658
- self.progress["current"] = cur
1659
- self._save_progress()
1660
- except Exception:
1661
- pass
1662
- return False
1663
- # 提示修复(分类标签)
1664
- tags = self._classify_rust_error(output)
1665
- symbols_path = str((self.data_dir / "symbols.jsonl").resolve())
1666
- curr, sym_name, src_loc, c_code = self._get_current_function_context()
1667
- repair_prompt = self._build_repair_prompt(
1668
- stage="cargo check",
1669
- output=output,
1670
- tags=tags,
1671
- sym_name=sym_name,
1672
- src_loc=src_loc,
1673
- c_code=c_code,
1674
- curr=curr,
1675
- symbols_path=symbols_path,
1676
- include_output_patch_hint=False,
1677
- )
1678
- prev_cwd = os.getcwd()
1745
+ def _build_repair_prompt(self, stage: str, output: str, tags: List[str], sym_name: str, src_loc: str, c_code: str, curr: Dict[str, Any], symbols_path: str, include_output_patch_hint: bool = False, command: Optional[str] = None) -> str:
1746
+ """
1747
+ 构建修复提示词。
1748
+
1749
+ Args:
1750
+ stage: 阶段名称("cargo test"
1751
+ output: 构建错误输出
1752
+ tags: 错误分类标签
1753
+ sym_name: 符号名称
1754
+ src_loc: 源文件位置
1755
+ c_code: C 源码片段
1756
+ curr: 当前进度信息
1757
+ symbols_path: 符号表文件路径
1758
+ include_output_patch_hint: 是否包含"仅输出补丁"提示(test阶段需要)
1759
+ command: 执行的命令(可选)
1760
+ """
1761
+ base_lines = self._build_repair_prompt_base(
1762
+ stage, tags, sym_name, src_loc, c_code, curr, symbols_path, include_output_patch_hint
1763
+ )
1764
+ stage_lines = self._build_repair_prompt_stage_section(stage, output, command)
1765
+ prompt = "\n".join(base_lines + stage_lines)
1766
+ return self._append_additional_notes(prompt)
1767
+
1768
+ def _detect_crate_kind(self) -> str:
1769
+ """
1770
+ 检测 crate 类型:lib、bin mixed。
1771
+ 判定规则(尽量保守,避免误判):
1772
+ - 若存在 src/lib.rs 或 Cargo.toml 中包含 [lib],视为包含 lib
1773
+ - 若存在 src/main.rs 或 Cargo.toml 中包含 [[bin]](或 [bin] 兼容),视为包含 bin
1774
+ - 同时存在则返回 mixed
1775
+ - 两者都不明确时,默认返回 lib(与默认模版一致)
1776
+ """
1777
+ try:
1778
+ cargo_path = (self.crate_dir / "Cargo.toml").resolve()
1779
+ txt = ""
1780
+ if cargo_path.exists():
1679
1781
  try:
1680
- os.chdir(str(self.crate_dir))
1681
- agent = self._get_repair_agent()
1682
- agent.run(self._compose_prompt_with_context(repair_prompt), prefix=f"[c2rust-transpiler][build-fix iter={check_iter}][check]", suffix="")
1683
- # 修复后进行轻量验证:检查语法是否正确
1684
- res_verify = subprocess.run(
1685
- ["cargo", "check", "--message-format=short", "-q"],
1686
- capture_output=True,
1687
- text=True,
1688
- check=False,
1689
- cwd=workspace_root,
1690
- )
1691
- if res_verify.returncode == 0:
1692
- typer.secho("[c2rust-transpiler][build] 修复后验证通过,继续构建循环", fg=typer.colors.GREEN)
1693
- else:
1694
- typer.secho("[c2rust-transpiler][build] 修复后验证仍有错误,将在下一轮循环中处理", fg=typer.colors.YELLOW)
1695
- finally:
1696
- os.chdir(prev_cwd)
1697
- # 下一轮循环
1698
- continue
1782
+ txt = cargo_path.read_text(encoding="utf-8", errors="ignore")
1783
+ except Exception:
1784
+ txt = ""
1785
+ txt_lower = txt.lower()
1786
+ has_lib = (self.crate_dir / "src" / "lib.rs").exists() or bool(re.search(r"(?m)^\s*\[lib\]\s*$", txt_lower))
1787
+ # 兼容:[[bin]] 为数组表,极少数项目也会写成 [bin]
1788
+ has_bin = (self.crate_dir / "src" / "main.rs").exists() or bool(re.search(r"(?m)^\s*\[\[bin\]\]\s*$", txt_lower) or re.search(r"(?m)^\s*\[bin\]\s*$", txt_lower))
1789
+ if has_lib and has_bin:
1790
+ return "mixed"
1791
+ if has_bin:
1792
+ return "bin"
1793
+ if has_lib:
1794
+ return "lib"
1795
+ except Exception:
1796
+ pass
1797
+ # 默认假设为 lib
1798
+ return "lib"
1699
1799
 
1700
- # 阶段二:cargo test(check 通过后才执行)
1701
- test_iter += 1
1702
- # 测试失败时需要详细输出,移除 -q 参数以获取完整的测试失败信息(包括堆栈跟踪、断言详情等)
1800
+ def _run_cargo_fmt(self, workspace_root: str) -> None:
1801
+ """执行 cargo fmt 格式化代码"""
1802
+ try:
1703
1803
  res = subprocess.run(
1704
- ["cargo", "test", "--", "--nocapture"],
1804
+ ["cargo", "fmt"],
1705
1805
  capture_output=True,
1706
1806
  text=True,
1707
1807
  check=False,
1708
1808
  cwd=workspace_root,
1709
1809
  )
1710
1810
  if res.returncode == 0:
1711
- typer.secho("[c2rust-transpiler][build] Cargo 测试通过。", fg=typer.colors.GREEN)
1712
- try:
1713
- cur = self.progress.get("current") or {}
1714
- metrics = cur.get("metrics") or {}
1715
- metrics["check_attempts"] = int(check_iter)
1716
- metrics["test_attempts"] = int(test_iter)
1717
- cur["metrics"] = metrics
1718
- cur["impl_verified"] = True
1719
- cur["failed_stage"] = None
1720
- self.progress["current"] = cur
1721
- self._save_progress()
1722
- except Exception:
1723
- pass
1724
- return True
1811
+ typer.secho("[c2rust-transpiler][fmt] 代码格式化完成", fg=typer.colors.CYAN)
1812
+ else:
1813
+ # fmt 失败不影响主流程,只记录警告
1814
+ typer.secho(f"[c2rust-transpiler][fmt] 代码格式化失败(非致命): {res.stderr or res.stdout}", fg=typer.colors.YELLOW)
1815
+ except Exception as e:
1816
+ # fmt 失败不影响主流程,只记录警告
1817
+ typer.secho(f"[c2rust-transpiler][fmt] 代码格式化异常(非致命): {e}", fg=typer.colors.YELLOW)
1725
1818
 
1726
- output = (res.stdout or "") + "\n" + (res.stderr or "")
1727
- limit_info = f" (上限: {self.test_max_retries if self.test_max_retries > 0 else '无限'})" if test_iter % 10 == 0 or test_iter == 1 else ""
1728
- typer.secho(f"[c2rust-transpiler][build] Cargo 测试失败 (第 {test_iter} 次尝试{limit_info})。", fg=typer.colors.RED)
1729
- typer.secho(output, fg=typer.colors.RED)
1730
- maxr = self.test_max_retries
1731
- if maxr > 0 and test_iter >= maxr:
1732
- typer.secho(f"[c2rust-transpiler][build] 已达到最大重试次数上限({maxr}),停止构建修复循环。", fg=typer.colors.RED)
1733
- try:
1734
- cur = self.progress.get("current") or {}
1735
- metrics = cur.get("metrics") or {}
1736
- metrics["check_attempts"] = int(check_iter)
1737
- metrics["test_attempts"] = int(test_iter)
1738
- cur["metrics"] = metrics
1739
- cur["impl_verified"] = False
1740
- cur["failed_stage"] = "test"
1741
- err_summary = (output or "").strip()
1742
- if len(err_summary) > ERROR_SUMMARY_MAX_LENGTH:
1743
- err_summary = err_summary[:ERROR_SUMMARY_MAX_LENGTH] + "...(truncated)"
1744
- cur["last_build_error"] = err_summary
1745
- self.progress["current"] = cur
1746
- self._save_progress()
1747
- except Exception:
1748
- pass
1749
- return False
1819
+ def _get_crate_commit_hash(self) -> Optional[str]:
1820
+ """获取 crate 目录的当前 commit id"""
1821
+ try:
1822
+ # 由于 transpile() 开始时已切换到 crate 目录,此处无需再次切换
1823
+ commit_hash = get_latest_commit_hash()
1824
+ return commit_hash if commit_hash else None
1825
+ except Exception:
1826
+ return None
1750
1827
 
1751
- # 构建失败(测试阶段)修复
1752
- tags = self._classify_rust_error(output)
1753
- symbols_path = str((self.data_dir / "symbols.jsonl").resolve())
1754
- curr, sym_name, src_loc, c_code = self._get_current_function_context()
1755
- repair_prompt = self._build_repair_prompt(
1756
- stage="cargo test",
1757
- output=output,
1758
- tags=tags,
1759
- sym_name=sym_name,
1760
- src_loc=src_loc,
1761
- c_code=c_code,
1762
- curr=curr,
1763
- symbols_path=symbols_path,
1764
- include_output_patch_hint=True,
1828
+ def _reset_to_commit(self, commit_hash: str) -> bool:
1829
+ """回退 crate 目录到指定的 commit"""
1830
+ try:
1831
+ # 由于 transpile() 开始时已切换到 crate 目录,此处无需再次切换
1832
+ # 检查是否是 git 仓库
1833
+ result = subprocess.run(
1834
+ ["git", "rev-parse", "--git-dir"],
1835
+ capture_output=True,
1836
+ text=True,
1837
+ check=False,
1765
1838
  )
1766
- prev_cwd = os.getcwd()
1767
- try:
1768
- os.chdir(str(self.crate_dir))
1769
- agent = self._get_repair_agent()
1770
- agent.run(self._compose_prompt_with_context(repair_prompt), prefix=f"[c2rust-transpiler][build-fix iter={test_iter}][test]", suffix="")
1771
- # 修复后进行轻量验证:检查语法是否正确
1772
- res_verify = subprocess.run(
1773
- ["cargo", "test", "--message-format=short", "-q", "--no-run"],
1839
+ if result.returncode != 0:
1840
+ # 不是 git 仓库,无法回退
1841
+ return False
1842
+
1843
+ # 执行硬重置
1844
+ result = subprocess.run(
1845
+ ["git", "reset", "--hard", commit_hash],
1846
+ capture_output=True,
1847
+ text=True,
1848
+ check=False,
1849
+ )
1850
+ if result.returncode == 0:
1851
+ # 清理未跟踪的文件
1852
+ subprocess.run(
1853
+ ["git", "clean", "-fd"],
1774
1854
  capture_output=True,
1775
1855
  text=True,
1776
1856
  check=False,
1777
- cwd=workspace_root,
1778
1857
  )
1779
- if res_verify.returncode == 0:
1780
- typer.secho("[c2rust-transpiler][build] 修复后验证通过,继续构建循环", fg=typer.colors.GREEN)
1858
+ return True
1859
+ return False
1860
+ except Exception:
1861
+ return False
1862
+
1863
+ def _check_and_handle_test_deletion(self, before_commit: Optional[str], agent: Any) -> bool:
1864
+ """
1865
+ 检测并处理测试代码删除。
1866
+
1867
+ 参数:
1868
+ before_commit: agent 运行前的 commit hash
1869
+ agent: 代码生成或修复的 agent 实例,使用其 model 进行询问
1870
+
1871
+ 返回:
1872
+ bool: 如果检测到问题且已回退,返回 True;否则返回 False
1873
+ """
1874
+ return check_and_handle_test_deletion(
1875
+ before_commit,
1876
+ agent,
1877
+ self._reset_to_commit,
1878
+ "[c2rust-transpiler]"
1879
+ )
1880
+
1881
+ def _run_cargo_test_and_fix(self, workspace_root: str, test_iter: int) -> Tuple[bool, Optional[bool]]:
1882
+ """
1883
+ 运行 cargo test 并在失败时修复。
1884
+
1885
+ Returns:
1886
+ (是否成功, 是否需要回退重新开始,None表示需要回退)
1887
+ """
1888
+ # 测试失败时需要详细输出,移除 -q 参数以获取完整的测试失败信息(包括堆栈跟踪、断言详情等)
1889
+ try:
1890
+ res_test = subprocess.run(
1891
+ ["cargo", "test", "--", "--nocapture"],
1892
+ capture_output=True,
1893
+ text=True,
1894
+ timeout=30,
1895
+ check=False,
1896
+ cwd=workspace_root,
1897
+ )
1898
+ returncode = res_test.returncode
1899
+ stdout = res_test.stdout or ""
1900
+ stderr = res_test.stderr or ""
1901
+ except subprocess.TimeoutExpired as e:
1902
+ # 超时视为测试失败,继续修复流程
1903
+ returncode = -1
1904
+ stdout = e.stdout.decode("utf-8", errors="replace") if e.stdout else ""
1905
+ stderr = "命令执行超时(30秒)\n" + (e.stderr.decode("utf-8", errors="replace") if e.stderr else "")
1906
+ typer.secho("[c2rust-transpiler][build] Cargo 测试超时(30秒),视为失败并继续修复流程", fg=typer.colors.YELLOW)
1907
+ except Exception as e:
1908
+ # 其他异常也视为测试失败
1909
+ returncode = -1
1910
+ stdout = ""
1911
+ stderr = f"执行 cargo test 时发生异常: {str(e)}"
1912
+ typer.secho(f"[c2rust-transpiler][build] Cargo 测试执行异常: {e},视为失败并继续修复流程", fg=typer.colors.YELLOW)
1913
+
1914
+ if returncode == 0:
1915
+ typer.secho("[c2rust-transpiler][build] Cargo 测试通过。", fg=typer.colors.GREEN)
1916
+ # 测试通过,重置连续失败计数
1917
+ self._consecutive_fix_failures = 0
1918
+ try:
1919
+ cur = self.progress.get("current") or {}
1920
+ metrics = cur.get("metrics") or {}
1921
+ metrics["test_attempts"] = int(test_iter)
1922
+ cur["metrics"] = metrics
1923
+ cur["impl_verified"] = True
1924
+ cur["failed_stage"] = None
1925
+ self.progress["current"] = cur
1926
+ self._save_progress()
1927
+ except Exception:
1928
+ pass
1929
+ return (True, False)
1930
+
1931
+ # 测试失败
1932
+ output = stdout + "\n" + stderr
1933
+ limit_info = f" (上限: {self.test_max_retries if self.test_max_retries > 0 else '无限'})" if test_iter % 10 == 0 or test_iter == 1 else ""
1934
+ typer.secho(f"[c2rust-transpiler][build] Cargo 测试失败 (第 {test_iter} 次尝试{limit_info})。", fg=typer.colors.RED)
1935
+ typer.secho(output, fg=typer.colors.RED)
1936
+ maxr = self.test_max_retries
1937
+ if maxr > 0 and test_iter >= maxr:
1938
+ typer.secho(f"[c2rust-transpiler][build] 已达到最大重试次数上限({maxr}),停止构建修复循环。", fg=typer.colors.RED)
1939
+ try:
1940
+ cur = self.progress.get("current") or {}
1941
+ metrics = cur.get("metrics") or {}
1942
+ metrics["test_attempts"] = int(test_iter)
1943
+ cur["metrics"] = metrics
1944
+ cur["impl_verified"] = False
1945
+ cur["failed_stage"] = "test"
1946
+ err_summary = (output or "").strip()
1947
+ if len(err_summary) > ERROR_SUMMARY_MAX_LENGTH:
1948
+ err_summary = err_summary[:ERROR_SUMMARY_MAX_LENGTH] + "...(truncated)"
1949
+ cur["last_build_error"] = err_summary
1950
+ self.progress["current"] = cur
1951
+ self._save_progress()
1952
+ except Exception:
1953
+ pass
1954
+ return (False, False)
1955
+
1956
+ # 构建失败(测试阶段)修复
1957
+ tags = self._classify_rust_error(output)
1958
+ symbols_path = str((self.data_dir / "symbols.jsonl").resolve())
1959
+ curr, sym_name, src_loc, c_code = self._get_current_function_context()
1960
+
1961
+ # 调试输出:确认测试失败信息是否正确传递
1962
+ typer.secho(f"[c2rust-transpiler][debug] 测试失败信息长度: {len(output)} 字符", fg=typer.colors.CYAN)
1963
+ if output:
1964
+ # 提取关键错误信息用于调试
1965
+ error_lines = output.split('\n')
1966
+ key_errors = [line for line in error_lines if any(keyword in line.lower() for keyword in ['failed', 'error', 'panic', 'unwrap', 'sequence'])]
1967
+ if key_errors:
1968
+ typer.secho("[c2rust-transpiler][debug] 关键错误信息(前5行):", fg=typer.colors.CYAN)
1969
+ for i, line in enumerate(key_errors[:5], 1):
1970
+ typer.secho(f" {i}. {line[:100]}", fg=typer.colors.CYAN)
1971
+
1972
+ repair_prompt = self._build_repair_prompt(
1973
+ stage="cargo test",
1974
+ output=output,
1975
+ tags=tags,
1976
+ sym_name=sym_name,
1977
+ src_loc=src_loc,
1978
+ c_code=c_code,
1979
+ curr=curr,
1980
+ symbols_path=symbols_path,
1981
+ include_output_patch_hint=True,
1982
+ command="cargo test -- --nocapture",
1983
+ )
1984
+ # 由于 transpile() 开始时已切换到 crate 目录,此处无需再次切换
1985
+ # 记录运行前的 commit
1986
+ before_commit = self._get_crate_commit_hash()
1987
+ agent = self._get_code_agent()
1988
+ agent.run(self._compose_prompt_with_context(repair_prompt), prefix=f"[c2rust-transpiler][build-fix iter={test_iter}][test]", suffix="")
1989
+
1990
+ # 检测并处理测试代码删除
1991
+ if self._check_and_handle_test_deletion(before_commit, agent):
1992
+ # 如果回退了,需要重新运行 agent
1993
+ typer.secho(f"[c2rust-transpiler][build-fix] 检测到测试代码删除问题,已回退,重新运行 agent (iter={test_iter})", fg=typer.colors.YELLOW)
1994
+ before_commit = self._get_crate_commit_hash()
1995
+ agent.run(self._compose_prompt_with_context(repair_prompt), prefix=f"[c2rust-transpiler][build-fix iter={test_iter}][test][retry]", suffix="")
1996
+ # 再次检测
1997
+ if self._check_and_handle_test_deletion(before_commit, agent):
1998
+ typer.secho(f"[c2rust-transpiler][build-fix] 再次检测到测试代码删除问题,已回退 (iter={test_iter})", fg=typer.colors.RED)
1999
+
2000
+ # 修复后验证:先检查编译,再实际运行测试
2001
+ # 第一步:检查编译是否通过
2002
+ res_compile = subprocess.run(
2003
+ ["cargo", "test", "--message-format=short", "-q", "--no-run"],
2004
+ capture_output=True,
2005
+ text=True,
2006
+ check=False,
2007
+ cwd=workspace_root,
2008
+ )
2009
+ if res_compile.returncode != 0:
2010
+ typer.secho("[c2rust-transpiler][build] 修复后编译仍有错误,将在下一轮循环中处理", fg=typer.colors.YELLOW)
2011
+ # 编译失败,增加连续失败计数
2012
+ self._consecutive_fix_failures += 1
2013
+ # 检查是否需要回退
2014
+ if self._consecutive_fix_failures >= CONSECUTIVE_FIX_FAILURE_THRESHOLD and self._current_function_start_commit:
2015
+ typer.secho(f"[c2rust-transpiler][build] 连续修复失败 {self._consecutive_fix_failures} 次,回退到函数开始时的 commit: {self._current_function_start_commit}", fg=typer.colors.RED)
2016
+ if self._reset_to_commit(self._current_function_start_commit):
2017
+ typer.secho("[c2rust-transpiler][build] 已回退到函数开始时的 commit,将重新开始处理该函数", fg=typer.colors.YELLOW)
2018
+ # 返回特殊值,表示需要重新开始
2019
+ return (False, None) # type: ignore
1781
2020
  else:
1782
- typer.secho("[c2rust-transpiler][build] 修复后验证仍有错误,将在下一轮循环中处理", fg=typer.colors.YELLOW)
1783
- finally:
1784
- os.chdir(prev_cwd)
1785
- # 重置 check 迭代计数,因为修复后需要重新 check
1786
- check_iter = 0
2021
+ typer.secho("[c2rust-transpiler][build] 回退失败,继续尝试修复", fg=typer.colors.YELLOW)
2022
+ return (False, False) # 需要继续循环
2023
+
2024
+ # 第二步:编译通过,实际运行测试验证
2025
+ try:
2026
+ res_test_verify = subprocess.run(
2027
+ ["cargo", "test", "--", "--nocapture"],
2028
+ capture_output=True,
2029
+ text=True,
2030
+ timeout=30,
2031
+ check=False,
2032
+ cwd=workspace_root,
2033
+ )
2034
+ verify_returncode = res_test_verify.returncode
2035
+ except subprocess.TimeoutExpired:
2036
+ # 超时视为测试失败
2037
+ verify_returncode = -1
2038
+ typer.secho("[c2rust-transpiler][build] 修复后验证测试超时(30秒),视为失败", fg=typer.colors.YELLOW)
2039
+ except Exception as e:
2040
+ # 其他异常也视为测试失败
2041
+ verify_returncode = -1
2042
+ typer.secho(f"[c2rust-transpiler][build] 修复后验证测试执行异常: {e},视为失败", fg=typer.colors.YELLOW)
2043
+
2044
+ if verify_returncode == 0:
2045
+ typer.secho("[c2rust-transpiler][build] 修复后测试通过,继续构建循环", fg=typer.colors.GREEN)
2046
+ # 测试真正通过,重置连续失败计数
2047
+ self._consecutive_fix_failures = 0
2048
+ return (False, False) # 需要继续循环(但下次应该会通过)
2049
+ else:
2050
+ # 编译通过但测试仍然失败,说明修复没有解决测试逻辑问题
2051
+ typer.secho("[c2rust-transpiler][build] 修复后编译通过,但测试仍然失败,将在下一轮循环中处理", fg=typer.colors.YELLOW)
2052
+ # 测试失败,增加连续失败计数(即使编译通过)
2053
+ self._consecutive_fix_failures += 1
2054
+ # 检查是否需要回退
2055
+ if self._consecutive_fix_failures >= CONSECUTIVE_FIX_FAILURE_THRESHOLD and self._current_function_start_commit:
2056
+ typer.secho(f"[c2rust-transpiler][build] 连续修复失败 {self._consecutive_fix_failures} 次(编译通过但测试失败),回退到函数开始时的 commit: {self._current_function_start_commit}", fg=typer.colors.RED)
2057
+ if self._reset_to_commit(self._current_function_start_commit):
2058
+ typer.secho("[c2rust-transpiler][build] 已回退到函数开始时的 commit,将重新开始处理该函数", fg=typer.colors.YELLOW)
2059
+ # 返回特殊值,表示需要重新开始
2060
+ return (False, None) # type: ignore
2061
+ else:
2062
+ typer.secho("[c2rust-transpiler][build] 回退失败,继续尝试修复", fg=typer.colors.YELLOW)
2063
+ return (False, False) # 需要继续循环
2064
+
2065
+ def _cargo_build_loop(self) -> Optional[bool]:
2066
+ """在 crate 目录执行构建与测试:直接运行 cargo test(运行所有测试,不区分项目结构)。失败则最小化修复直到通过或达到上限。"""
2067
+ workspace_root = str(self.crate_dir)
2068
+ test_limit = f"最大重试: {self.test_max_retries if self.test_max_retries > 0 else '无限'}"
2069
+ typer.secho(f"[c2rust-transpiler][build] 工作区={workspace_root},开始构建循环(test,{test_limit})", fg=typer.colors.MAGENTA)
2070
+ test_iter = 0
2071
+ while True:
2072
+ # 运行所有测试(不区分项目结构)
2073
+ # cargo test 会自动编译并运行所有类型的测试:lib tests、bin tests、integration tests、doc tests 等
2074
+ test_iter += 1
2075
+ test_success, need_restart = self._run_cargo_test_and_fix(workspace_root, test_iter)
2076
+ if need_restart is None:
2077
+ return None # 需要回退重新开始
2078
+ if test_success:
2079
+ return True # 测试通过
1787
2080
 
1788
2081
  def _review_and_optimize(self, rec: FnRecord, module: str, rust_sig: str) -> None:
1789
2082
  """
@@ -1794,15 +2087,45 @@ class Transpiler:
1794
2087
  def build_review_prompts() -> Tuple[str, str, str]:
1795
2088
  sys_p = (
1796
2089
  "你是Rust代码审查专家。验收标准:Rust 实现应与原始 C 实现在功能上一致,且不应包含可能导致功能错误的严重问题。\n"
2090
+ "**审查优先级**:严重问题 > 破坏性变更 > 功能一致性 > 文件结构。优先处理可能导致程序崩溃或编译失败的问题。\n"
2091
+ "**审查范围**:主要审查当前函数的实现,相关依赖函数作为辅助参考。\n"
1797
2092
  "审查标准(合并了功能一致性和严重问题检查):\n"
1798
2093
  "1. 功能一致性检查:\n"
1799
- " - 核心输入输出、主要功能逻辑是否与 C 实现一致;\n"
1800
- " - 允许 Rust 实现修复 C 代码中的安全漏洞(如缓冲区溢出、空指针解引用、未初始化内存使用等),这些修复不应被视为功能不一致;\n"
2094
+ " - **核心功能定义**:核心输入输出、主要功能逻辑是否与 C 实现一致。核心功能指函数的主要目的和预期行为(如'计算哈希值'、'解析字符串'、'压缩数据'等),不包括实现细节;\n"
2095
+ " - **安全改进允许行为不一致**:允许 Rust 实现修复 C 代码中的安全漏洞(如缓冲区溢出、空指针解引用、未初始化内存使用、整数溢出、格式化字符串漏洞等),这些安全改进可能导致行为与 C 实现不一致,但这是允许的,不应被视为功能不一致;\n"
2096
+ " - **忽略语言差异导致的行为不一致**:由于 Rust 和 C 语言的本质差异,以下行为差异是不可避免的,应被忽略:\n"
2097
+ " * 整数溢出处理:Rust 在 debug 模式下会 panic,release 模式下会 wrapping,而 C 是未定义行为;\n"
2098
+ " * 未定义行为:Rust 会避免或明确处理,而 C 可能产生未定义行为;\n"
2099
+ " * 空指针/空引用:Rust 使用 Option<T> 或 Result<T, E> 处理,而 C 可能直接解引用导致崩溃;\n"
2100
+ " * 内存安全:Rust 的借用检查器会阻止某些 C 中允许的不安全操作;\n"
2101
+ " * 错误处理:Rust 使用 Result<T, E> 或 Option<T>,而 C 可能使用错误码或全局 errno;\n"
1801
2102
  " - 允许 Rust 实现使用不同的类型设计、错误处理方式、资源管理方式等,只要功能一致即可;\n"
1802
- "2. 严重问题检查(可能导致功能错误):\n"
2103
+ "2. 严重问题检查(可能导致功能错误或程序崩溃):\n"
1803
2104
  " - 明显的空指针解引用或会导致 panic 的严重错误;\n"
1804
2105
  " - 明显的越界访问或会导致程序崩溃的问题;\n"
2106
+ " - 会导致程序无法正常运行的逻辑错误;\n"
2107
+ "3. 破坏性变更检测(对现有代码的影响):\n"
2108
+ " - **允许签名不一致**:允许函数签名、参数数量、参数类型、返回类型等与C实现不一致,只要功能实现了即可。这是Rust转译的正常现象,因为Rust的类型系统和设计理念与C不同;\n"
2109
+ " - **仅检查实际破坏性影响**:只有当函数签名变更确实导致调用方代码无法编译或运行时,才报告为破坏性变更。如果调用方代码已经适配了新签名,或可以通过简单的适配解决,则不应视为破坏性变更;\n"
2110
+ " - **⚠️ 重要:检查测试用例删除**:必须检查代码变更中是否错误删除了测试用例标记(#[test] 或 #[cfg(test)])。如果发现删除了测试用例标记,必须报告为破坏性变更,除非:\n"
2111
+ " * 测试用例被移动到其他位置(在diff中可以看到对应的添加);\n"
2112
+ " * 测试用例是重复的或过时的,确实需要删除;\n"
2113
+ " * 测试用例被重构为其他形式的测试(如集成测试、文档测试等);\n"
2114
+ " - 检查模块导出变更是否会影响其他模块的导入(如 pub 关键字缺失、模块路径变更);\n"
2115
+ " - 检查类型定义变更是否会导致依赖该类型的代码失效(如结构体字段变更、枚举变体变更);\n"
2116
+ " - 检查常量或静态变量变更是否会影响引用该常量的代码;\n"
2117
+ " - **优先使用diff信息**:如果diff中已包含调用方代码信息,优先基于diff判断;只有在diff信息不足时,才使用 read_code 工具读取调用方代码进行验证;\n"
2118
+ "4. 文件结构合理性检查:\n"
2119
+ " - 检查模块文件位置是否符合 Rust 项目约定(如 src/ 目录结构、模块层次);\n"
2120
+ " - 检查文件命名是否符合 Rust 命名规范(如 snake_case、模块文件命名);\n"
2121
+ " - 检查模块组织是否合理(如相关功能是否放在同一模块、模块拆分是否过度或不足);\n"
2122
+ " - 检查模块导出是否合理(如 lib.rs 中的 pub mod 声明是否正确、是否遗漏必要的导出);\n"
2123
+ " - 检查是否存在循环依赖或过度耦合;\n"
1805
2124
  "不检查类型匹配、指针可变性、边界检查细节、资源释放细节、内存语义等技术细节(除非会导致功能错误)。\n"
2125
+ "**重要要求:在总结阶段,对于发现的每个问题,必须提供:**\n"
2126
+ "1. 详细的问题描述:明确指出问题所在的位置(文件、函数、行号等)、问题的具体表现、为什么这是一个问题\n"
2127
+ "2. 具体的修复建议:提供详细的修复方案,包括需要修改的代码位置、修改方式、预期效果等\n"
2128
+ "3. 问题分类:使用 [function] 标记功能一致性问题,使用 [critical] 标记严重问题,使用 [breaking] 标记破坏性变更,使用 [structure] 标记文件结构问题\n"
1806
2129
  "请在总结阶段详细指出问题和修改建议,但不要尝试修复或修改任何代码,不要输出补丁。"
1807
2130
  )
1808
2131
  # 附加原始C函数源码片段,供审查作为只读参考
@@ -1810,8 +2133,68 @@ class Transpiler:
1810
2133
  # 附加被引用符号上下文与库替代上下文,以及crate目录结构,提供更完整审查背景
1811
2134
  callees_ctx = self._collect_callees_context(rec)
1812
2135
  librep_ctx = rec.lib_replacement if isinstance(rec.lib_replacement, dict) else None
1813
- crate_tree = _dir_tree(self.crate_dir)
1814
- usr_p = "\n".join([
2136
+ crate_tree = dir_tree(self.crate_dir)
2137
+ # 提取编译参数
2138
+ compile_flags = self._extract_compile_flags(rec.file)
2139
+
2140
+ # 获取从初始commit到当前commit的变更作为上下文(每次review都必须获取)
2141
+ commit_diff = ""
2142
+ diff_status = "" # 用于记录diff获取状态
2143
+ if self._current_function_start_commit:
2144
+ current_commit = self._get_crate_commit_hash()
2145
+ if current_commit:
2146
+ if current_commit == self._current_function_start_commit:
2147
+ # commit相同,说明没有变更
2148
+ commit_diff = "(无变更:当前commit与函数开始时的commit相同)"
2149
+ diff_status = "no_change"
2150
+ else:
2151
+ # commit不同,获取diff
2152
+ try:
2153
+ # 注意:transpile()开始时已切换到crate目录,此处无需再次切换
2154
+ commit_diff = get_diff_between_commits(self._current_function_start_commit, current_commit)
2155
+ if commit_diff and not commit_diff.startswith("获取") and not commit_diff.startswith("发生"):
2156
+ # 成功获取diff,限制长度避免上下文过大
2157
+ # 优先使用agent的剩余token数量,回退到输入窗口限制
2158
+ max_diff_chars = None
2159
+ try:
2160
+ # 优先尝试使用已有的agent获取剩余token(更准确,包含对话历史)
2161
+ review_key = f"review::{rec.id}"
2162
+ agent = self._current_agents.get(review_key)
2163
+ if agent:
2164
+ remaining_tokens = agent.get_remaining_token_count()
2165
+ # 使用剩余token的50%作为字符限制(1 token ≈ 4字符,所以 remaining_tokens * 0.5 * 4 = remaining_tokens * 2)
2166
+ max_diff_chars = int(remaining_tokens * 2)
2167
+ if max_diff_chars <= 0:
2168
+ max_diff_chars = None
2169
+ except Exception:
2170
+ pass
2171
+
2172
+ # 回退方案2:使用输入窗口的50%转换为字符数
2173
+ if max_diff_chars is None:
2174
+ max_input_tokens = get_max_input_token_count(self.llm_group)
2175
+ max_diff_chars = max_input_tokens * 2 # 最大输入token数量的一半转换为字符数
2176
+
2177
+ if len(commit_diff) > max_diff_chars:
2178
+ commit_diff = commit_diff[:max_diff_chars] + "\n... (差异内容过长,已截断)"
2179
+ diff_status = "success"
2180
+ else:
2181
+ # 获取失败,保留错误信息
2182
+ diff_status = "error"
2183
+ typer.secho(f"[c2rust-transpiler][review] 获取commit差异失败: {commit_diff}", fg=typer.colors.YELLOW)
2184
+ except Exception as e:
2185
+ commit_diff = f"获取commit差异时发生异常: {str(e)}"
2186
+ diff_status = "error"
2187
+ typer.secho(f"[c2rust-transpiler][review] 获取commit差异失败: {e}", fg=typer.colors.YELLOW)
2188
+ else:
2189
+ # 无法获取当前commit
2190
+ commit_diff = "(无法获取当前commit id)"
2191
+ diff_status = "no_current_commit"
2192
+ else:
2193
+ # 没有保存函数开始时的commit
2194
+ commit_diff = "(未记录函数开始时的commit id)"
2195
+ diff_status = "no_start_commit"
2196
+
2197
+ usr_p_lines = [
1815
2198
  f"待审查函数:{rec.qname or rec.name}",
1816
2199
  f"建议签名:{rust_sig}",
1817
2200
  f"目标模块:{module}",
@@ -1824,68 +2207,194 @@ class Transpiler:
1824
2207
  "</C_SOURCE>",
1825
2208
  "",
1826
2209
  "审查说明(合并审查):",
2210
+ "**审查优先级**:严重问题 > 破坏性变更 > 功能一致性 > 文件结构。优先处理可能导致程序崩溃或编译失败的问题。",
2211
+ "",
1827
2212
  "1. 功能一致性:",
1828
- " - 核心输入输出、主要功能逻辑是否与 C 实现一致;",
1829
- " - 允许Rust实现修复C代码中的安全漏洞(如缓冲区溢出、空指针解引用、未初始化内存使用等),这些修复不应被视为功能不一致;",
2213
+ " - **核心功能定义**:核心输入输出、主要功能逻辑是否与 C 实现一致。核心功能指函数的主要目的和预期行为(如'计算哈希值'、'解析字符串'、'压缩数据'等),不包括实现细节;",
2214
+ " - **安全改进允许行为不一致**:允许Rust实现修复C代码中的安全漏洞(如缓冲区溢出、空指针解引用、未初始化内存使用、整数溢出、格式化字符串漏洞等),这些安全改进可能导致行为与 C 实现不一致,但这是允许的,不应被视为功能不一致;",
2215
+ " - **忽略语言差异导致的行为不一致**:由于 Rust 和 C 语言的本质差异,以下行为差异是不可避免的,应被忽略:",
2216
+ " * 整数溢出处理:Rust 在 debug 模式下会 panic,release 模式下会 wrapping,而 C 是未定义行为;",
2217
+ " * 未定义行为:Rust 会避免或明确处理,而 C 可能产生未定义行为;",
2218
+ " * 空指针/空引用:Rust 使用 Option<T> 或 Result<T, E> 处理,而 C 可能直接解引用导致崩溃;",
2219
+ " * 内存安全:Rust 的借用检查器会阻止某些 C 中允许的不安全操作;",
2220
+ " * 错误处理:Rust 使用 Result<T, E> 或 Option<T>,而 C 可能使用错误码或全局 errno;",
1830
2221
  " - 允许Rust实现使用不同的类型设计、错误处理方式、资源管理方式等,只要功能一致即可;",
1831
2222
  "2. 严重问题(可能导致功能错误):",
1832
2223
  " - 明显的空指针解引用或会导致 panic 的严重错误;",
1833
2224
  " - 明显的越界访问或会导致程序崩溃的问题;",
2225
+ "3. 破坏性变更检测(对现有代码的影响):",
2226
+ " - **允许签名不一致**:允许函数签名、参数数量、参数类型、返回类型等与C实现不一致,只要功能实现了即可。这是Rust转译的正常现象,因为Rust的类型系统和设计理念与C不同;",
2227
+ " - **仅检查实际破坏性影响**:只有当函数签名变更确实导致调用方代码无法编译或运行时,才报告为破坏性变更。如果调用方代码已经适配了新签名,或可以通过简单的适配解决,则不应视为破坏性变更;",
2228
+ " - **⚠️ 重要:检查测试用例删除**:必须检查代码变更中是否错误删除了测试用例标记(#[test] 或 #[cfg(test)])。如果发现删除了测试用例标记,必须报告为破坏性变更,除非:",
2229
+ " * 测试用例被移动到其他位置(在diff中可以看到对应的添加);",
2230
+ " * 测试用例是重复的或过时的,确实需要删除;",
2231
+ " * 测试用例被重构为其他形式的测试(如集成测试、文档测试等);",
2232
+ " - 检查模块导出变更是否会影响其他模块的导入(如 pub 关键字缺失、模块路径变更);",
2233
+ " - 检查类型定义变更是否会导致依赖该类型的代码失效(如结构体字段变更、枚举变体变更);",
2234
+ " - 检查常量或静态变量变更是否会影响引用该常量的代码;",
2235
+ " - **优先使用diff信息**:如果diff中已包含调用方代码信息,优先基于diff判断;只有在diff信息不足时,才使用 read_code 工具读取调用方代码进行验证;",
2236
+ " - 如果该函数是根符号或被其他已转译函数调用,检查调用方代码是否仍能正常编译和使用;如果调用方已经适配了新签名,则不应视为破坏性变更;",
2237
+ "4. 文件结构合理性检查:",
2238
+ " - 检查模块文件位置是否符合 Rust 项目约定(如 src/ 目录结构、模块层次);",
2239
+ " - 检查文件命名是否符合 Rust 命名规范(如 snake_case、模块文件命名);",
2240
+ " - 检查模块组织是否合理(如相关功能是否放在同一模块、模块拆分是否过度或不足);",
2241
+ " - 检查模块导出是否合理(如 lib.rs 中的 pub mod 声明是否正确、是否遗漏必要的导出);",
2242
+ " - 检查是否存在循环依赖或过度耦合;",
2243
+ " - 检查文件大小是否合理(如单个文件是否过大需要拆分,或是否过度拆分导致文件过多);",
1834
2244
  "不检查类型匹配、指针可变性、边界检查细节等技术细节(除非会导致功能错误)。",
1835
2245
  "",
2246
+ "**重要:问题报告要求**",
2247
+ "对于发现的每个问题,必须在总结中提供:",
2248
+ "1. 详细的问题描述:明确指出问题所在的位置(文件、函数、行号等)、问题的具体表现、为什么这是一个问题",
2249
+ "2. 具体的修复建议:提供详细的修复方案,包括需要修改的代码位置、修改方式、预期效果等",
2250
+ "3. 问题分类:使用 [function] 标记功能一致性问题,使用 [critical] 标记严重问题,使用 [breaking] 标记破坏性变更,使用 [structure] 标记文件结构问题",
2251
+ "示例:",
2252
+ ' "[function] 返回值处理缺失:在函数 foo 的第 42 行,当输入参数为负数时,函数没有返回错误码,但 C 实现中会返回 -1。修复建议:在函数开始处添加参数验证,当参数为负数时返回 Result::Err(Error::InvalidInput)。"',
2253
+ ' "[critical] 空指针解引用风险:在函数 bar 的第 58 行,直接解引用指针 ptr 而没有检查其是否为 null,可能导致 panic。修复建议:使用 if let Some(value) = ptr.as_ref() 进行空指针检查,或使用 Option<&T> 类型。"',
2254
+ ' "[breaking] 函数签名变更导致调用方无法编译:函数 baz 的签名从 `fn baz(x: i32) -> i32` 变更为 `fn baz(x: i64) -> i64`,但调用方代码(src/other.rs:15)仍使用 i32 类型调用,且无法通过简单适配解决,会导致类型不匹配错误。修复建议:保持函数签名与调用方兼容,或同时更新所有调用方代码。注意:如果调用方已经适配了新签名,或可以通过简单的类型转换解决,则不应视为破坏性变更。"',
2255
+ ' "[breaking] 测试用例被错误删除:在 diff 中发现删除了测试用例标记 `#[test]` 或 `#[cfg(test)]`(如 src/foo.rs:42),但没有看到对应的添加或移动,这可能导致测试无法运行。修复建议:恢复被删除的测试用例标记,或如果确实需要删除,请说明原因(如测试用例已移动到其他位置、测试用例是重复的等)。"',
2256
+ ' "[structure] 模块导出缺失:函数 qux 所在的模块 utils 未在 src/lib.rs 中导出,导致无法从 crate 外部访问。修复建议:在 src/lib.rs 中添加 `pub mod utils;` 声明。"',
2257
+ "",
1836
2258
  "被引用符号上下文(如已转译则包含Rust模块信息):",
1837
2259
  json.dumps(callees_ctx, ensure_ascii=False, indent=2),
1838
2260
  "",
1839
2261
  "库替代上下文(若存在):",
1840
2262
  json.dumps(librep_ctx, ensure_ascii=False, indent=2),
2263
+ "",
2264
+ *([f"禁用库列表(禁止在实现中使用这些库):{', '.join(self.disabled_libraries)}"] if self.disabled_libraries else []),
2265
+ *([f"根符号要求:此函数是根符号({rec.qname or rec.name}),必须使用 `pub` 关键字对外暴露,确保可以从 crate 外部访问。同时,该函数所在的模块必须在 src/lib.rs 中被导出(使用 `pub mod <模块名>;`)。"] if self._is_root_symbol(rec) else []),
2266
+ ]
2267
+ # 添加编译参数(如果存在)
2268
+ if compile_flags:
2269
+ usr_p_lines.extend([
2270
+ "",
2271
+ "C文件编译参数(来自 compile_commands.json):",
2272
+ compile_flags,
2273
+ ])
2274
+ usr_p_lines.extend([
1841
2275
  "",
1842
2276
  "当前crate目录结构(部分):",
1843
2277
  "<CRATE_TREE>",
1844
2278
  crate_tree,
1845
2279
  "</CRATE_TREE>",
2280
+ ])
2281
+
2282
+ # 添加commit变更上下文(每次review都必须包含)
2283
+ usr_p_lines.extend([
2284
+ "",
2285
+ "从函数开始到当前的commit变更(用于了解代码变更历史和上下文):",
2286
+ "<COMMIT_DIFF>",
2287
+ commit_diff,
2288
+ "</COMMIT_DIFF>",
2289
+ "",
2290
+ ])
2291
+
2292
+ # 根据diff状态添加不同的说明
2293
+ if diff_status == "success":
2294
+ usr_p_lines.extend([
2295
+ "**重要:commit变更上下文说明**",
2296
+ "- 上述diff显示了从函数开始处理时的commit到当前commit之间的所有变更",
2297
+ "- 这些变更可能包括:当前函数的实现、依赖函数的实现、模块结构的调整等",
2298
+ "- **优先使用diff信息进行审查判断**:如果diff中已经包含了足够的信息(如函数实现、签名变更、模块结构等),可以直接基于diff进行审查,无需读取原始文件",
2299
+ "- 只有在diff信息不足或需要查看完整上下文时,才使用 read_code 工具读取原始文件",
2300
+ "- 在审查破坏性变更时,请特别关注这些变更对现有代码的影响",
2301
+ "- 如果发现变更中存在问题(如破坏性变更、文件结构不合理等),请在审查报告中指出",
2302
+ ])
2303
+ elif diff_status == "no_change":
2304
+ usr_p_lines.extend([
2305
+ "**注意**:当前commit与函数开始时的commit相同,说明没有代码变更。请使用 read_code 工具读取目标模块文件的最新内容进行审查。",
2306
+ ])
2307
+ else:
2308
+ # diff_status 为 "error"、"no_current_commit" 或 "no_start_commit"
2309
+ usr_p_lines.extend([
2310
+ "**注意**:由于无法获取commit差异信息,请使用 read_code 工具读取目标模块文件的最新内容进行审查。",
2311
+ ])
2312
+
2313
+ usr_p_lines.extend([
1846
2314
  "",
1847
2315
  "如需定位或交叉验证 C 符号位置,请使用符号表检索工具:",
1848
2316
  "- 工具: read_symbols",
1849
- "- 参数示例(YAML):",
1850
- f" symbols_file: \"{(self.data_dir / 'symbols.jsonl').resolve()}\"",
1851
- " symbols:",
1852
- f" - \"{rec.qname or rec.name}\"",
2317
+ "- 参数示例(JSON):",
2318
+ f" {{\"symbols_file\": \"{(self.data_dir / 'symbols.jsonl').resolve()}\", \"symbols\": [\"{rec.qname or rec.name}\"]}}",
2319
+ "",
2320
+ "**重要:审查要求**",
2321
+ "- **优先使用diff信息**:如果提供了commit差异(COMMIT_DIFF),优先基于diff信息进行审查判断,只有在diff信息不足时才使用 read_code 工具读取原始文件",
2322
+ "- 必须基于最新的代码进行审查,如果使用 read_code 工具,请读取目标模块文件的最新内容",
2323
+ "- 禁止依赖任何历史记忆、之前的审查结论或对话历史进行判断",
2324
+ "- 每次审查都必须基于最新的代码状态(通过diff或read_code获取),确保审查结果反映当前代码的真实状态",
2325
+ "- 结合commit变更上下文(如果提供),全面评估代码变更的影响和合理性",
1853
2326
  "",
1854
- "请阅读crate中该函数的当前实现(你可以在上述crate根路径下自行读取必要上下文),并准备总结。",
2327
+ "请基于提供的diff信息(如果可用)或读取crate中该函数的当前实现进行审查,并准备总结。",
1855
2328
  ])
2329
+ usr_p = "\n".join(usr_p_lines)
1856
2330
  sum_p = (
1857
- "请仅输出一个 <SUMMARY> 块,块内为单个 <yaml> 对象,字段:\n"
1858
- "ok: bool # 若满足功能一致且无严重问题,则为 true\n"
1859
- "function_issues: [string, ...] # 功能一致性问题,每项以 [function] 开头,示例:[function] missing return value handling\n"
1860
- "critical_issues: [string, ...] # 严重问题(可能导致功能错误),每项以 [critical] 开头,示例:[critical] obvious null pointer dereference\n"
2331
+ "请仅输出一个 <SUMMARY> 块,块内直接包含 JSON 对象(不需要额外的标签),字段:\n"
2332
+ '"ok": bool // 若满足功能一致且无严重问题、无破坏性变更、文件结构合理,则为 true\n'
2333
+ '"function_issues": [string, ...] // 功能一致性问题,每项以 [function] 开头,必须包含详细的问题描述和修复建议\n'
2334
+ '"critical_issues": [string, ...] // 严重问题(可能导致功能错误),每项以 [critical] 开头,必须包含详细的问题描述和修复建议\n'
2335
+ '"breaking_issues": [string, ...] // 破坏性变更问题(对现有代码的影响),每项以 [breaking] 开头,必须包含详细的问题描述和修复建议\n'
2336
+ '"structure_issues": [string, ...] // 文件结构问题,每项以 [structure] 开头,必须包含详细的问题描述和修复建议\n'
1861
2337
  "注意:\n"
1862
- "- 前置条件:必须在crate中找到该函数的实现(匹配函数名或签名)。若未找到,ok 必须为 false,function_issues 应包含 [function] function not found\n"
1863
- "- Rust实现修复了C代码中的安全漏洞或使用了不同的实现方式但保持了功能一致,且无严重问题,ok 应为 true\n"
1864
- "- 仅报告功能不一致和严重问题,不报告类型匹配、指针可变性、边界检查细节等技术细节(除非会导致功能错误)\n"
1865
- "<SUMMARY><yaml>\nok: true\nfunction_issues: []\ncritical_issues: []\n</yaml></SUMMARY>"
2338
+ "- 前置条件:必须在crate中找到该函数的实现(匹配函数名或签名)。若未找到,ok 必须为 false,function_issues 应包含 [function] function not found: 详细描述问题位置和如何查找函数实现\n"
2339
+ "- **安全改进允许行为不一致**:若Rust实现修复了C代码中的安全漏洞(如缓冲区溢出、空指针解引用、未初始化内存使用等),即使导致行为与 C 实现不一致,这也是允许的,不应被视为功能不一致;\n"
2340
+ "- **忽略语言差异导致的行为不一致**:由于 Rust 和 C 语言的本质差异(如内存安全、类型系统、错误处理、未定义行为处理等),某些行为差异是不可避免的,这些差异应被忽略,不应被视为功能不一致;\n"
2341
+ "- **允许签名不一致**:允许函数签名、参数数量、参数类型、返回类型等与C实现不一致,只要功能实现了即可。这是Rust转译的正常现象,不应被视为破坏性变更;\n"
2342
+ "- **破坏性变更判断标准**:只有当函数签名变更确实导致调用方代码无法编译或运行时,才报告为破坏性变更。如果调用方代码已经适配了新签名,或可以通过简单的适配解决,则不应视为破坏性变更;\n"
2343
+ "- **⚠️ 重要:测试用例删除检查**:必须检查代码变更中是否错误删除了测试用例标记(#[test] 或 #[cfg(test)])。如果发现删除了测试用例标记且没有合理的理由(如移动到其他位置、重复测试等),必须报告为破坏性变更;\n"
2344
+ "- 若Rust实现使用了不同的实现方式但保持了功能一致,且无严重问题、无破坏性变更、文件结构合理,ok 应为 true\n"
2345
+ "- 仅报告功能不一致、严重问题、破坏性变更和文件结构问题,不报告类型匹配、指针可变性、边界检查细节等技术细节(除非会导致功能错误)\n"
2346
+ "- **重要:每个问题描述必须包含以下内容:**\n"
2347
+ " 1. 问题的详细描述:明确指出问题所在的位置(文件、函数、行号等)、问题的具体表现、为什么这是一个问题\n"
2348
+ " 2. 修复建议:提供具体的修复方案,包括需要修改的代码位置、修改方式、预期效果等\n"
2349
+ " 3. 问题格式:[function]、[critical]、[breaking] 或 [structure] 开头,后跟详细的问题描述和修复建议\n"
2350
+ " 示例格式:\n"
2351
+ ' "[function] 返回值处理缺失:在函数 foo 的第 42 行,当输入参数为负数时,函数没有返回错误码,但 C 实现中会返回 -1。修复建议:在函数开始处添加参数验证,当参数为负数时返回 Result::Err(Error::InvalidInput)。"\n'
2352
+ ' "[critical] 空指针解引用风险:在函数 bar 的第 58 行,直接解引用指针 ptr 而没有检查其是否为 null,可能导致 panic。修复建议:使用 if let Some(value) = ptr.as_ref() 进行空指针检查,或使用 Option<&T> 类型。"\n'
2353
+ ' "[breaking] 函数签名变更导致调用方无法编译:函数 baz 的签名从 `fn baz(x: i32) -> i32` 变更为 `fn baz(x: i64) -> i64`,但调用方代码(src/other.rs:15)仍使用 i32 类型调用,且无法通过简单适配解决,会导致类型不匹配错误。修复建议:保持函数签名与调用方兼容,或同时更新所有调用方代码。注意:如果调用方已经适配了新签名,或可以通过简单的类型转换解决,则不应视为破坏性变更。"\n'
2354
+ ' "[structure] 模块导出缺失:函数 qux 所在的模块 utils 未在 src/lib.rs 中导出,导致无法从 crate 外部访问。修复建议:在 src/lib.rs 中添加 `pub mod utils;` 声明。"\n'
2355
+ "请严格按以下格式输出(JSON格式,支持jsonnet语法如尾随逗号、注释、|||分隔符多行字符串等):\n"
2356
+ "<SUMMARY>\n{\n \"ok\": true,\n \"function_issues\": [],\n \"critical_issues\": [],\n \"breaking_issues\": [],\n \"structure_issues\": []\n}\n</SUMMARY>"
1866
2357
  )
2358
+ # 在 usr_p 和 sum_p 中追加附加说明(sys_p 通常不需要)
2359
+ usr_p = self._append_additional_notes(usr_p)
2360
+ sum_p = self._append_additional_notes(sum_p)
1867
2361
  return sys_p, usr_p, sum_p
1868
2362
 
1869
2363
  i = 0
1870
2364
  max_iterations = self.review_max_iterations
1871
2365
  # 复用 Review Agent(仅在本函数生命周期内构建一次)
2366
+ # 注意:Agent 必须在 crate 根目录下创建,以确保工具(如 read_symbols)能正确获取符号表
2367
+ # 由于 transpile() 开始时已切换到 crate 目录,此处无需再次切换
1872
2368
  review_key = f"review::{rec.id}"
1873
- sys_p_init, usr_p_init, sum_p_init = build_review_prompts()
2369
+ sys_p_init, _, sum_p_init = build_review_prompts() # 只获取一次 sys_p 和 sum_p,usr_p 每次重新构建
2370
+
2371
+ # 获取函数信息用于 Agent name
2372
+ fn_name = rec.qname or rec.name or f"fn_{rec.id}"
2373
+ agent_name = f"C2Rust-Review-Agent({fn_name})"
2374
+
1874
2375
  if self._current_agents.get(review_key) is None:
1875
- self._current_agents[review_key] = Agent(
2376
+ review_agent = Agent(
1876
2377
  system_prompt=sys_p_init,
1877
- name="C2Rust-Review-Agent",
2378
+ name=agent_name,
1878
2379
  model_group=self.llm_group,
1879
2380
  summary_prompt=sum_p_init,
1880
2381
  need_summary=True,
1881
2382
  auto_complete=True,
1882
- use_tools=["execute_script", "read_code", "retrieve_memory", "save_memory", "read_symbols"],
1883
- plan=False,
2383
+ use_tools=["execute_script", "read_code", "read_symbols"],
1884
2384
  non_interactive=self.non_interactive,
1885
2385
  use_methodology=False,
1886
2386
  use_analysis=False,
1887
- disable_file_edit=True,
1888
2387
  )
2388
+ # 订阅 BEFORE_TOOL_CALL 和 AFTER_TOOL_CALL 事件,用于细粒度检测测试代码删除
2389
+ review_agent.event_bus.subscribe(BEFORE_TOOL_CALL, self._on_before_tool_call)
2390
+ review_agent.event_bus.subscribe(AFTER_TOOL_CALL, self._on_after_tool_call)
2391
+ # 记录 Agent 创建时的 commit id(作为初始值)
2392
+ agent_id = id(review_agent)
2393
+ agent_key = f"agent_{agent_id}"
2394
+ initial_commit = self._get_crate_commit_hash()
2395
+ if initial_commit:
2396
+ self._agent_before_commits[agent_key] = initial_commit
2397
+ self._current_agents[review_key] = review_agent
1889
2398
 
1890
2399
  # 0表示无限重试,否则限制迭代次数
1891
2400
  use_direct_model_review = False # 标记是否使用直接模型调用
@@ -1893,101 +2402,117 @@ class Transpiler:
1893
2402
  parse_error_msg: Optional[str] = None # 保存上一次的YAML解析错误信息
1894
2403
  while max_iterations == 0 or i < max_iterations:
1895
2404
  agent = self._current_agents[review_key]
1896
- prev_cwd = os.getcwd()
1897
- try:
1898
- os.chdir(str(self.crate_dir))
1899
- # 如果是修复后的审查(i > 0),在提示词中明确说明代码已变更,需要重新读取
1900
- if i > 0:
1901
- code_changed_notice = "\n".join([
1902
- "",
1903
- "【重要提示:代码已变更】",
1904
- f"在本次审查之前(第 {i} 次迭代),已根据审查意见对代码进行了修复和优化。",
1905
- "目标函数的实现可能已经发生变化,包括但不限于:",
1906
- "- 函数实现逻辑的修改",
1907
- "- 类型和签名的调整",
1908
- "- 依赖关系的更新",
1909
- "- 错误处理的改进",
1910
- "",
1911
- "**请务必重新读取目标模块文件中的函数实现,不要基于之前的审查结果或缓存信息进行判断。**",
1912
- "请使用 read_code 工具重新读取以下文件的最新内容:",
1913
- f"- 目标模块文件: {module}",
1914
- "- 以及相关的依赖模块文件(如有需要)",
1915
- "",
1916
- "审查时请基于重新读取的最新代码内容进行评估。",
1917
- "",
1918
- ])
1919
- usr_p_with_notice = usr_p_init + code_changed_notice
1920
- composed_prompt = self._compose_prompt_with_context(usr_p_with_notice)
1921
- else:
1922
- composed_prompt = self._compose_prompt_with_context(usr_p_init)
2405
+ # 由于 transpile() 开始时已切换到 crate 目录,此处无需再次切换
2406
+
2407
+ # 每次迭代都重新获取最新的 diff(从保存的 commit 到当前的 HEAD)
2408
+ # 重新构建 user prompt,包含最新的 diff
2409
+ _, usr_p_current, _ = build_review_prompts() # 重新构建,获取最新的 diff
2410
+
2411
+ if i > 0:
2412
+ # 修复后的审查,添加代码已更新的提示
2413
+ code_changed_notice = "\n".join([
2414
+ "",
2415
+ "【重要:代码已更新】",
2416
+ f"在本次审查之前(第 {i} 次迭代),已根据审查意见对代码进行了修复和优化。",
2417
+ "目标函数的实现已经发生变化,包括但不限于:",
2418
+ "- 函数实现逻辑的修改",
2419
+ "- 类型和签名的调整",
2420
+ "- 依赖关系的更新",
2421
+ "- 错误处理的改进",
2422
+ "",
2423
+ "**审查要求:**",
2424
+ "- **优先使用diff信息**:如果提供了最新的commit差异(COMMIT_DIFF),优先基于diff信息进行审查判断,只有在diff信息不足时才使用 read_code 工具读取原始文件",
2425
+ "- 如果必须使用 read_code 工具,请读取目标模块文件的最新内容",
2426
+ "- **禁止基于之前的审查结果、对话历史或任何缓存信息进行判断**",
2427
+ "- 必须基于最新的代码状态(通过diff或read_code获取)进行审查评估",
2428
+ "",
2429
+ "如果diff信息充足,可以直接基于diff进行审查;如果diff信息不足,请使用 read_code 工具读取最新代码。",
2430
+ "",
2431
+ ])
2432
+ usr_p_with_notice = usr_p_current + code_changed_notice
2433
+ composed_prompt = self._compose_prompt_with_context(usr_p_with_notice)
2434
+ # 修复后必须使用 Agent.run(),不能使用直接模型调用(因为需要工具调用)
2435
+ use_direct_model_review = False
2436
+ else:
2437
+ composed_prompt = self._compose_prompt_with_context(usr_p_current)
2438
+
2439
+ if use_direct_model_review:
2440
+ # 格式解析失败后,直接调用模型接口
2441
+ # 构造包含摘要提示词和具体错误信息的完整提示
2442
+ error_guidance = ""
2443
+ # 检查上一次的解析结果
2444
+ if parse_error_msg:
2445
+ # 如果有JSON解析错误,优先反馈
2446
+ error_guidance = (
2447
+ f"\n\n**格式错误详情(请根据以下错误修复输出格式):**\n"
2448
+ f"- JSON解析失败: {parse_error_msg}\n\n"
2449
+ f"请确保输出的JSON格式正确,包括正确的引号、逗号、大括号等。JSON 对象必须包含字段:ok(布尔值)、function_issues(字符串数组)、critical_issues(字符串数组)、breaking_issues(字符串数组)、structure_issues(字符串数组)。支持jsonnet语法(如尾随逗号、注释、||| 或 ``` 分隔符多行字符串等)。"
2450
+ )
2451
+ elif parse_failed:
2452
+ error_guidance = (
2453
+ "\n\n**格式错误详情(请根据以下错误修复输出格式):**\n"
2454
+ "- 无法从摘要中解析出有效的 JSON 对象\n\n"
2455
+ "请确保输出格式正确:仅输出一个 <SUMMARY> 块,块内直接包含 JSON 对象(不需要额外的标签),字段:ok(布尔值)、function_issues(字符串数组)、critical_issues(字符串数组)、breaking_issues(字符串数组)、structure_issues(字符串数组)。支持jsonnet语法(如尾随逗号、注释、||| 或 ``` 分隔符多行字符串等)。"
2456
+ )
1923
2457
 
1924
- if use_direct_model_review:
1925
- # 格式解析失败后,直接调用模型接口
1926
- # 构造包含摘要提示词和具体错误信息的完整提示
1927
- error_guidance = ""
1928
- # 检查上一次的解析结果
1929
- if parse_error_msg:
1930
- # 如果有YAML解析错误,优先反馈
1931
- error_guidance = f"\n\n**格式错误详情(请根据以下错误修复输出格式):**\n- YAML解析失败: {parse_error_msg}\n\n请确保输出的YAML格式正确,包括正确的缩进、引号、冒号等。YAML 对象必须包含字段:ok(布尔值)、function_issues(字符串数组)、critical_issues(字符串数组)。"
1932
- elif parse_failed:
1933
- error_guidance = "\n\n**格式错误详情(请根据以下错误修复输出格式):**\n- 无法从摘要中解析出有效的 YAML 对象\n\n请确保输出格式正确:仅输出一个 <SUMMARY> 块,块内为单个 <yaml> 对象,字段:ok(布尔值)、function_issues(字符串数组)、critical_issues(字符串数组)。"
1934
-
1935
- full_prompt = f"{composed_prompt}{error_guidance}\n\n{sum_p_init}"
1936
- try:
1937
- response = agent.model.chat_until_success(full_prompt) # type: ignore
1938
- summary = str(response or "")
1939
- except Exception as e:
1940
- typer.secho(f"[c2rust-transpiler][review] 直接模型调用失败: {e},回退到 run()", fg=typer.colors.YELLOW)
1941
- summary = str(agent.run(composed_prompt) or "")
1942
- else:
1943
- # 第一次使用 run(),让 Agent 完整运行(可能使用工具)
2458
+ full_prompt = f"{composed_prompt}{error_guidance}\n\n{sum_p_init}"
2459
+ typer.secho(f"[c2rust-transpiler][review] 直接调用模型接口修复格式错误(第 {i+1} 次重试)", fg=typer.colors.YELLOW)
2460
+ try:
2461
+ response = agent.model.chat_until_success(full_prompt) # type: ignore
2462
+ summary = str(response or "")
2463
+ except Exception as e:
2464
+ typer.secho(f"[c2rust-transpiler][review] 直接模型调用失败: {e},回退到 run()", fg=typer.colors.YELLOW)
1944
2465
  summary = str(agent.run(composed_prompt) or "")
1945
- finally:
1946
- os.chdir(prev_cwd)
2466
+ else:
2467
+ # 第一次使用 run(),让 Agent 完整运行(可能使用工具)
2468
+ summary = str(agent.run(composed_prompt) or "")
1947
2469
 
1948
- # 解析 YAML 格式的审查结果
1949
- verdict, parse_error_review = _extract_json_from_summary(summary)
2470
+ # 解析 JSON 格式的审查结果
2471
+ verdict, parse_error_review = extract_json_from_summary(summary)
1950
2472
  parse_failed = False
1951
2473
  parse_error_msg = None
1952
2474
  if parse_error_review:
1953
- # YAML解析失败
2475
+ # JSON解析失败
1954
2476
  parse_failed = True
1955
2477
  parse_error_msg = parse_error_review
1956
- typer.secho(f"[c2rust-transpiler][review] YAML解析失败: {parse_error_review}", fg=typer.colors.YELLOW)
2478
+ typer.secho(f"[c2rust-transpiler][review] JSON解析失败: {parse_error_review}", fg=typer.colors.YELLOW)
1957
2479
  # 兼容旧格式:尝试解析纯文本 OK
1958
2480
  m = re.search(r"<SUMMARY>([\s\S]*?)</SUMMARY>", summary, flags=re.IGNORECASE)
1959
2481
  content = (m.group(1).strip() if m else summary.strip()).upper()
1960
2482
  if content == "OK":
1961
- verdict = {"ok": True, "function_issues": [], "critical_issues": []}
2483
+ verdict = {"ok": True, "function_issues": [], "critical_issues": [], "breaking_issues": [], "structure_issues": []}
1962
2484
  parse_failed = False # 兼容格式成功,不算解析失败
1963
2485
  parse_error_msg = None
1964
2486
  else:
1965
- # 无法解析,视为有问题
1966
- verdict = {"ok": False, "function_issues": [content], "critical_issues": []}
1967
- # 格式解析失败,后续迭代使用直接模型调用
2487
+ # 无法解析,立即重试:设置标志并继续循环
1968
2488
  use_direct_model_review = True
2489
+ # 继续循环,立即重试
2490
+ continue
1969
2491
  elif not isinstance(verdict, dict):
1970
2492
  parse_failed = True
1971
2493
  # 兼容旧格式:尝试解析纯文本 OK
1972
2494
  m = re.search(r"<SUMMARY>([\s\S]*?)</SUMMARY>", summary, flags=re.IGNORECASE)
1973
2495
  content = (m.group(1).strip() if m else summary.strip()).upper()
1974
2496
  if content == "OK":
1975
- verdict = {"ok": True, "function_issues": [], "critical_issues": []}
2497
+ verdict = {"ok": True, "function_issues": [], "critical_issues": [], "breaking_issues": [], "structure_issues": []}
1976
2498
  parse_failed = False # 兼容格式成功,不算解析失败
1977
2499
  else:
1978
- # 无法解析,视为有问题
1979
- verdict = {"ok": False, "function_issues": [content], "critical_issues": []}
1980
- # 格式解析失败,后续迭代使用直接模型调用
2500
+ # 无法解析,立即重试:设置标志并继续循环
1981
2501
  use_direct_model_review = True
2502
+ parse_error_msg = f"无法从摘要中解析出有效的 JSON 对象,得到的内容类型为: {type(verdict).__name__}"
2503
+ # 继续循环,立即重试
2504
+ continue
1982
2505
 
1983
2506
  ok = bool(verdict.get("ok") is True)
1984
2507
  function_issues = verdict.get("function_issues") if isinstance(verdict.get("function_issues"), list) else []
1985
2508
  critical_issues = verdict.get("critical_issues") if isinstance(verdict.get("critical_issues"), list) else []
1986
- all_issues = function_issues + critical_issues
2509
+ breaking_issues = verdict.get("breaking_issues") if isinstance(verdict.get("breaking_issues"), list) else []
2510
+ structure_issues = verdict.get("structure_issues") if isinstance(verdict.get("structure_issues"), list) else []
1987
2511
 
1988
- typer.secho(f"[c2rust-transpiler][review][iter={i+1}] verdict ok={ok}, function_issues={len(function_issues)}, critical_issues={len(critical_issues)}", fg=typer.colors.CYAN)
2512
+ typer.secho(f"[c2rust-transpiler][review][iter={i+1}] verdict ok={ok}, function_issues={len(function_issues)}, critical_issues={len(critical_issues)}, breaking_issues={len(breaking_issues)}, structure_issues={len(structure_issues)}", fg=typer.colors.CYAN)
1989
2513
 
1990
- if ok and not all_issues:
2514
+ # 如果 ok true,表示审查通过(功能一致且无严重问题、无破坏性变更、文件结构合理),直接返回,不触发修复
2515
+ if ok:
1991
2516
  limit_info = f" (上限: {max_iterations if max_iterations > 0 else '无限'})"
1992
2517
  typer.secho(f"[c2rust-transpiler][review] 代码审查通过{limit_info} (共 {i+1} 次迭代)。", fg=typer.colors.GREEN)
1993
2518
  # 记录审查结果到进度
@@ -1995,14 +2520,18 @@ class Transpiler:
1995
2520
  cur = self.progress.get("current") or {}
1996
2521
  cur["review"] = {
1997
2522
  "ok": True,
1998
- "function_issues": [],
1999
- "critical_issues": [],
2523
+ "function_issues": list(function_issues),
2524
+ "critical_issues": list(critical_issues),
2525
+ "breaking_issues": list(breaking_issues),
2526
+ "structure_issues": list(structure_issues),
2000
2527
  "iterations": i + 1,
2001
2528
  }
2002
2529
  metrics = cur.get("metrics") or {}
2003
2530
  metrics["review_iterations"] = i + 1
2004
- metrics["function_issues"] = 0
2005
- metrics["type_issues"] = 0
2531
+ metrics["function_issues"] = len(function_issues)
2532
+ metrics["type_issues"] = len(critical_issues)
2533
+ metrics["breaking_issues"] = len(breaking_issues)
2534
+ metrics["structure_issues"] = len(structure_issues)
2006
2535
  cur["metrics"] = metrics
2007
2536
  self.progress["current"] = cur
2008
2537
  self._save_progress()
@@ -2011,12 +2540,16 @@ class Transpiler:
2011
2540
  return
2012
2541
 
2013
2542
  # 需要优化:提供详细上下文背景,并明确审查意见仅针对 Rust crate,不修改 C 源码
2014
- crate_tree = _dir_tree(self.crate_dir)
2543
+ crate_tree = dir_tree(self.crate_dir)
2015
2544
  issues_text = "\n".join([
2016
2545
  "功能一致性问题:" if function_issues else "",
2017
2546
  *[f" - {issue}" for issue in function_issues],
2018
2547
  "严重问题(可能导致功能错误):" if critical_issues else "",
2019
2548
  *[f" - {issue}" for issue in critical_issues],
2549
+ "破坏性变更问题(对现有代码的影响):" if breaking_issues else "",
2550
+ *[f" - {issue}" for issue in breaking_issues],
2551
+ "文件结构问题:" if structure_issues else "",
2552
+ *[f" - {issue}" for issue in structure_issues],
2020
2553
  ])
2021
2554
  fix_prompt = "\n".join([
2022
2555
  "请根据以下审查结论对目标函数进行最小优化(保留结构与意图,不进行大范围重构):",
@@ -2039,6 +2572,9 @@ class Transpiler:
2039
2572
  "- 如审查问题涉及缺失/未实现的被调函数或依赖,请阅读其 C 源码并在本次一并补齐等价的 Rust 实现;必要时在合理模块新增函数或引入精确 use;",
2040
2573
  "- 禁止使用 todo!/unimplemented! 作为占位;",
2041
2574
  "- 可使用工具 read_symbols/read_code 获取依赖符号的 C 源码与位置以辅助实现;仅精确导入所需符号(禁止通配);",
2575
+ "- 注释规范:所有代码注释(包括文档注释、行内注释、块注释等)必须使用中文;",
2576
+ *([f"- **禁用库约束**:禁止在优化中使用以下库:{', '.join(self.disabled_libraries)}。如果这些库在 Cargo.toml 中已存在,请移除相关依赖;如果优化需要使用这些库的功能,请使用标准库或其他允许的库替代。"] if self.disabled_libraries else []),
2577
+ *([f"- **根符号要求**:此函数是根符号({rec.qname or rec.name}),必须使用 `pub` 关键字对外暴露,确保可以从 crate 外部访问。同时,该函数所在的模块必须在 src/lib.rs 中被导出(使用 `pub mod <模块名>;`)。"] if self._is_root_symbol(rec) else []),
2042
2578
  "",
2043
2579
  "【重要:依赖检查与实现要求】",
2044
2580
  "在优化函数之前,请务必检查以下内容:",
@@ -2049,7 +2585,6 @@ class Transpiler:
2049
2585
  " - 遍历当前函数调用的所有被调函数(包括直接调用和间接调用)",
2050
2586
  " - 对于每个被调函数,检查其在 Rust crate 中是否已有完整实现",
2051
2587
  " - 可以使用 read_code 工具读取相关模块文件进行检查",
2052
- " - 可以使用 retrieve_memory 工具检索已保存的函数实现记忆",
2053
2588
  "3. 对于未实现的依赖函数:",
2054
2589
  " - 使用 read_symbols 工具获取其 C 源码和符号信息",
2055
2590
  " - 使用 read_code 工具读取其 C 源码实现",
@@ -2067,17 +2602,26 @@ class Transpiler:
2067
2602
  "",
2068
2603
  "请仅以补丁形式输出修改,避免冗余解释。",
2069
2604
  ])
2070
- # 在当前工作目录运行复用的修复 CodeAgent
2071
- ca = self._get_repair_agent()
2072
- prev_cwd = os.getcwd()
2073
- try:
2074
- os.chdir(str(self.crate_dir))
2075
- limit_info = f"/{max_iterations}" if max_iterations > 0 else "/∞"
2076
- ca.run(self._compose_prompt_with_context(fix_prompt), prefix=f"[c2rust-transpiler][review-fix iter={i+1}{limit_info}]", suffix="")
2077
- # 优化后进行一次构建验证;若未通过则进入构建修复循环,直到通过为止
2078
- self._cargo_build_loop()
2079
- finally:
2080
- os.chdir(prev_cwd)
2605
+ # 由于 transpile() 开始时已切换到 crate 目录,此处无需再次切换
2606
+ # 记录运行前的 commit
2607
+ before_commit = self._get_crate_commit_hash()
2608
+ ca = self._get_code_agent()
2609
+ limit_info = f"/{max_iterations}" if max_iterations > 0 else "/∞"
2610
+ fix_prompt_with_notes = self._append_additional_notes(fix_prompt)
2611
+ ca.run(self._compose_prompt_with_context(fix_prompt_with_notes), prefix=f"[c2rust-transpiler][review-fix iter={i+1}{limit_info}]", suffix="")
2612
+
2613
+ # 检测并处理测试代码删除
2614
+ if self._check_and_handle_test_deletion(before_commit, ca):
2615
+ # 如果回退了,需要重新运行 agent
2616
+ typer.secho(f"[c2rust-transpiler][review-fix] 检测到测试代码删除问题,已回退,重新运行 agent (iter={i+1})", fg=typer.colors.YELLOW)
2617
+ before_commit = self._get_crate_commit_hash()
2618
+ ca.run(self._compose_prompt_with_context(fix_prompt_with_notes), prefix=f"[c2rust-transpiler][review-fix iter={i+1}{limit_info}][retry]", suffix="")
2619
+ # 再次检测
2620
+ if self._check_and_handle_test_deletion(before_commit, ca):
2621
+ typer.secho(f"[c2rust-transpiler][review-fix] 再次检测到测试代码删除问题,已回退 (iter={i+1})", fg=typer.colors.RED)
2622
+
2623
+ # 优化后进行一次构建验证;若未通过则进入构建修复循环,直到通过为止
2624
+ self._cargo_build_loop()
2081
2625
 
2082
2626
  # 记录本次审查结果
2083
2627
  try:
@@ -2086,11 +2630,15 @@ class Transpiler:
2086
2630
  "ok": False,
2087
2631
  "function_issues": list(function_issues),
2088
2632
  "critical_issues": list(critical_issues),
2633
+ "breaking_issues": list(breaking_issues),
2634
+ "structure_issues": list(structure_issues),
2089
2635
  "iterations": i + 1,
2090
2636
  }
2091
2637
  metrics = cur.get("metrics") or {}
2092
2638
  metrics["function_issues"] = len(function_issues)
2093
2639
  metrics["type_issues"] = len(critical_issues)
2640
+ metrics["breaking_issues"] = len(breaking_issues)
2641
+ metrics["structure_issues"] = len(structure_issues)
2094
2642
  cur["metrics"] = metrics
2095
2643
  self.progress["current"] = cur
2096
2644
  self._save_progress()
@@ -2122,182 +2670,293 @@ class Transpiler:
2122
2670
  # 写入 JSONL 映射(带源位置,用于区分同名符号)
2123
2671
  self.symbol_map.add(rec, module, rust_symbol or (rec.name or f"fn_{rec.id}"))
2124
2672
 
2673
+ # 获取当前 commit id 并记录
2674
+ current_commit = self._get_crate_commit_hash()
2675
+
2125
2676
  # 更新进度:已转换集合
2126
2677
  converted = self.progress.get("converted") or []
2127
2678
  if rec.id not in converted:
2128
2679
  converted.append(rec.id)
2129
2680
  self.progress["converted"] = converted
2130
2681
  self.progress["current"] = None
2682
+
2683
+ # 记录每个已转换函数的 commit id
2684
+ converted_commits = self.progress.get("converted_commits") or {}
2685
+ if current_commit:
2686
+ converted_commits[str(rec.id)] = current_commit
2687
+ self.progress["converted_commits"] = converted_commits
2688
+ typer.secho(f"[c2rust-transpiler][progress] 已记录函数 {rec.id} 的 commit: {current_commit}", fg=typer.colors.CYAN)
2689
+
2131
2690
  self._save_progress()
2132
2691
 
2133
2692
  def transpile(self) -> None:
2134
2693
  """主流程"""
2135
2694
  typer.secho("[c2rust-transpiler][start] 开始转译", fg=typer.colors.BLUE)
2136
- # 准确性兜底:在未执行 prepare 的情况下,确保 crate 目录与最小 Cargo 配置存在
2695
+ # 切换到 crate 根目录,整个转译过程都在此目录下执行
2696
+ prev_cwd = os.getcwd()
2137
2697
  try:
2138
- cd = self.crate_dir.resolve()
2139
- cd.mkdir(parents=True, exist_ok=True)
2140
- cargo = cd / "Cargo.toml"
2141
- src_dir = cd / "src"
2142
- lib_rs = src_dir / "lib.rs"
2143
- # 最小 Cargo.toml(不覆盖已有),edition 使用 2021 以兼容更广环境
2144
- if not cargo.exists():
2145
- pkg_name = cd.name
2146
- content = (
2147
- f'[package]\nname = "{pkg_name}"\nversion = "0.1.0"\nedition = "2021"\n\n'
2148
- '[lib]\npath = "src/lib.rs"\n'
2149
- )
2150
- try:
2151
- cargo.write_text(content, encoding="utf-8")
2152
- typer.secho(f"[c2rust-transpiler][init] created Cargo.toml at {cargo}", fg=typer.colors.GREEN)
2153
- except Exception:
2154
- pass
2155
- # 确保 src/lib.rs 存在
2156
- src_dir.mkdir(parents=True, exist_ok=True)
2157
- if not lib_rs.exists():
2158
- try:
2159
- lib_rs.write_text("// Auto-created by c2rust transpiler\n", encoding="utf-8")
2160
- typer.secho(f"[c2rust-transpiler][init] created src/lib.rs at {lib_rs}", fg=typer.colors.GREEN)
2161
- except Exception:
2162
- pass
2163
- except Exception:
2164
- # 保持稳健,失败不阻塞主流程
2165
- pass
2166
-
2167
- order_path = _ensure_order_file(self.project_root)
2168
- steps = _iter_order_steps(order_path)
2169
- if not steps:
2170
- typer.secho("[c2rust-transpiler] 未找到翻译步骤。", fg=typer.colors.YELLOW)
2171
- return
2172
-
2173
- # 构建自包含 order 索引(id -> FnRecord,name/qname -> id)
2174
- self._load_order_index(order_path)
2175
-
2176
- # 扁平化顺序,按单个函数处理(保持原有顺序)
2177
- seq: List[int] = []
2178
- for grp in steps:
2179
- seq.extend(grp)
2698
+ os.chdir(str(self.crate_dir))
2699
+ typer.secho(f"[c2rust-transpiler][start] 已切换到 crate 目录: {os.getcwd()}", fg=typer.colors.BLUE)
2700
+ # 准确性兜底:在未执行 prepare 的情况下,确保 crate 目录与最小 Cargo 配置存在
2701
+ try:
2702
+ cd = self.crate_dir.resolve()
2703
+ cd.mkdir(parents=True, exist_ok=True)
2704
+ cargo = cd / "Cargo.toml"
2705
+ src_dir = cd / "src"
2706
+ lib_rs = src_dir / "lib.rs"
2707
+ # 最小 Cargo.toml(不覆盖已有),edition 使用 2021 以兼容更广环境
2708
+ if not cargo.exists():
2709
+ pkg_name = cd.name
2710
+ content = (
2711
+ f'[package]\nname = "{pkg_name}"\nversion = "0.1.0"\nedition = "2021"\n\n'
2712
+ '[lib]\npath = "src/lib.rs"\n'
2713
+ )
2714
+ try:
2715
+ cargo.write_text(content, encoding="utf-8")
2716
+ typer.secho(f"[c2rust-transpiler][init] created Cargo.toml at {cargo}", fg=typer.colors.GREEN)
2717
+ except Exception:
2718
+ pass
2719
+ # 确保 src/lib.rs 存在
2720
+ src_dir.mkdir(parents=True, exist_ok=True)
2721
+ if not lib_rs.exists():
2722
+ try:
2723
+ lib_rs.write_text("// Auto-created by c2rust transpiler\n", encoding="utf-8")
2724
+ typer.secho(f"[c2rust-transpiler][init] created src/lib.rs at {lib_rs}", fg=typer.colors.GREEN)
2725
+ except Exception:
2726
+ pass
2727
+ except Exception:
2728
+ # 保持稳健,失败不阻塞主流程
2729
+ pass
2180
2730
 
2181
- # 若支持 resume,则跳过 progress['converted'] 中已完成的
2182
- done: Set[int] = set(self.progress.get("converted") or [])
2183
- typer.secho(f"[c2rust-transpiler][order] 顺序信息: 步骤数={len(steps)} 总ID={sum(len(g) for g in steps)} 已转换={len(done)}", fg=typer.colors.BLUE)
2731
+ order_path = ensure_order_file(self.project_root)
2732
+ steps = iter_order_steps(order_path)
2733
+ if not steps:
2734
+ typer.secho("[c2rust-transpiler] 未找到翻译步骤。", fg=typer.colors.YELLOW)
2735
+ return
2184
2736
 
2185
- for fid in seq:
2186
- if fid in done:
2187
- continue
2188
- rec = self.fn_index_by_id.get(fid)
2189
- if not rec:
2190
- continue
2191
- if self._should_skip(rec):
2192
- typer.secho(f"[c2rust-transpiler][skip] 跳过 {rec.qname or rec.name} (id={rec.id}) 位于 {rec.file}:{rec.start_line}-{rec.end_line}", fg=typer.colors.YELLOW)
2193
- continue
2737
+ # 构建自包含 order 索引(id -> FnRecord,name/qname -> id)
2738
+ self._load_order_index(order_path)
2194
2739
 
2195
- # 读取C函数源码
2196
- typer.secho(f"[c2rust-transpiler][read] 读取 C 源码: {rec.qname or rec.name} (id={rec.id}) 来自 {rec.file}:{rec.start_line}-{rec.end_line}", fg=typer.colors.BLUE)
2197
- c_code = self._read_source_span(rec)
2198
- typer.secho(f"[c2rust-transpiler][read] 已加载 {len(c_code.splitlines()) if c_code else 0} 行", fg=typer.colors.BLUE)
2199
-
2200
- # 若缺少源码片段且缺乏签名/参数信息,则跳过本函数,记录进度以便后续处理
2201
- if not c_code and not (getattr(rec, "signature", "") or getattr(rec, "params", None)):
2202
- skipped = self.progress.get("skipped_missing_source") or []
2203
- if rec.id not in skipped:
2204
- skipped.append(rec.id)
2205
- self.progress["skipped_missing_source"] = skipped
2206
- typer.secho(f"[c2rust-transpiler] 跳过:缺少源码与签名信息 -> {rec.qname or rec.name} (id={rec.id})", fg=typer.colors.YELLOW)
2207
- self._save_progress()
2208
- continue
2209
- # 1) 规划:模块路径与Rust签名
2210
- typer.secho(f"[c2rust-transpiler][plan] 正在规划模块与签名: {rec.qname or rec.name} (id={rec.id})", fg=typer.colors.CYAN)
2211
- module, rust_sig = self._plan_module_and_signature(rec, c_code)
2212
- typer.secho(f"[c2rust-transpiler][plan] 已选择 模块={module}, 签名={rust_sig}", fg=typer.colors.CYAN)
2740
+ # 扁平化顺序,按单个函数处理(保持原有顺序)
2741
+ seq: List[int] = []
2742
+ for grp in steps:
2743
+ seq.extend(grp)
2213
2744
 
2214
- # 记录当前进度
2215
- self._update_progress_current(rec, module, rust_sig)
2216
- typer.secho(f"[c2rust-transpiler][progress] 已更新当前进度记录 id={rec.id}", fg=typer.colors.CYAN)
2745
+ # 若支持 resume,则跳过 progress['converted'] 中已完成的
2746
+ done: Set[int] = set(self.progress.get("converted") or [])
2747
+ # 计算需要处理的函数总数(排除已完成的)
2748
+ total_to_process = len([fid for fid in seq if fid not in done])
2749
+ current_index = 0
2750
+
2751
+ # 恢复时,reset 到最后一个已转换函数的 commit id
2752
+ if self.resume and done:
2753
+ converted_commits = self.progress.get("converted_commits") or {}
2754
+ if converted_commits:
2755
+ # 找到最后一个已转换函数的 commit id
2756
+ last_commit = None
2757
+ for fid in reversed(seq):
2758
+ if fid in done:
2759
+ commit_id = converted_commits.get(str(fid))
2760
+ if commit_id:
2761
+ last_commit = commit_id
2762
+ break
2763
+
2764
+ if last_commit:
2765
+ current_commit = self._get_crate_commit_hash()
2766
+ if current_commit != last_commit:
2767
+ typer.secho(f"[c2rust-transpiler][resume] 检测到代码状态不一致,正在 reset 到最后一个已转换函数的 commit: {last_commit}", fg=typer.colors.YELLOW)
2768
+ if self._reset_to_commit(last_commit):
2769
+ typer.secho(f"[c2rust-transpiler][resume] 已 reset 到 commit: {last_commit}", fg=typer.colors.GREEN)
2770
+ else:
2771
+ typer.secho("[c2rust-transpiler][resume] reset 失败,继续使用当前代码状态", fg=typer.colors.YELLOW)
2772
+ else:
2773
+ typer.secho("[c2rust-transpiler][resume] 代码状态一致,无需 reset", fg=typer.colors.CYAN)
2774
+
2775
+ typer.secho(f"[c2rust-transpiler][order] 顺序信息: 步骤数={len(steps)} 总ID={sum(len(g) for g in steps)} 已转换={len(done)} 待处理={total_to_process}", fg=typer.colors.BLUE)
2217
2776
 
2218
- # 初始化函数上下文与代码编写与修复Agent复用缓存(只在当前函数开始时执行一次)
2219
- self._reset_function_context(rec, module, rust_sig, c_code)
2777
+ for fid in seq:
2778
+ if fid in done:
2779
+ continue
2780
+ rec = self.fn_index_by_id.get(fid)
2781
+ if not rec:
2782
+ continue
2783
+ if self._should_skip(rec):
2784
+ typer.secho(f"[c2rust-transpiler][skip] 跳过 {rec.qname or rec.name} (id={rec.id}) 位于 {rec.file}:{rec.start_line}-{rec.end_line}", fg=typer.colors.YELLOW)
2785
+ continue
2220
2786
 
2221
- # 1.5) 确保模块声明链(提前到生成实现之前,避免生成的代码无法被正确引用)
2222
- try:
2223
- self._ensure_mod_chain_for_module(module)
2224
- typer.secho(f"[c2rust-transpiler][mod] 已补齐 {module} 的 mod.rs 声明链", fg=typer.colors.GREEN)
2225
- # 确保顶层模块在 src/lib.rs 中被公开
2226
- mp = Path(module)
2227
- crate_root = self.crate_dir.resolve()
2228
- rel = mp.resolve().relative_to(crate_root) if mp.is_absolute() else Path(module)
2229
- rel_s = str(rel).replace("\\", "/")
2230
- if rel_s.startswith("./"):
2231
- rel_s = rel_s[2:]
2232
- if rel_s.startswith("src/"):
2233
- parts = rel_s[len("src/"):].strip("/").split("/")
2234
- if parts and parts[0]:
2235
- top_mod = parts[0]
2236
- if not top_mod.endswith(".rs"):
2237
- self._ensure_top_level_pub_mod(top_mod)
2238
- typer.secho(f"[c2rust-transpiler][mod] 已在 src/lib.rs 确保顶层 pub mod {top_mod}", fg=typer.colors.GREEN)
2239
- cur = self.progress.get("current") or {}
2240
- cur["mod_chain_fixed"] = True
2241
- cur["mod_visibility_fixed"] = True
2242
- self.progress["current"] = cur
2243
- self._save_progress()
2244
- except Exception:
2245
- pass
2787
+ # 更新进度索引
2788
+ current_index += 1
2789
+ progress_info = f"({current_index}/{total_to_process})" if total_to_process > 0 else ""
2790
+
2791
+ # 在每个函数开始转译前执行 cargo fmt
2792
+ workspace_root = str(self.crate_dir)
2793
+ self._run_cargo_fmt(workspace_root)
2794
+
2795
+ # 读取C函数源码
2796
+ typer.secho(f"[c2rust-transpiler][read] {progress_info} 读取 C 源码: {rec.qname or rec.name} (id={rec.id}) 来自 {rec.file}:{rec.start_line}-{rec.end_line}", fg=typer.colors.BLUE)
2797
+ c_code = self._read_source_span(rec)
2798
+ typer.secho(f"[c2rust-transpiler][read] 已加载 {len(c_code.splitlines()) if c_code else 0} 行", fg=typer.colors.BLUE)
2799
+
2800
+ # 若缺少源码片段且缺乏签名/参数信息,则跳过本函数,记录进度以便后续处理
2801
+ if not c_code and not (getattr(rec, "signature", "") or getattr(rec, "params", None)):
2802
+ skipped = self.progress.get("skipped_missing_source") or []
2803
+ if rec.id not in skipped:
2804
+ skipped.append(rec.id)
2805
+ self.progress["skipped_missing_source"] = skipped
2806
+ typer.secho(f"[c2rust-transpiler] {progress_info} 跳过:缺少源码与签名信息 -> {rec.qname or rec.name} (id={rec.id})", fg=typer.colors.YELLOW)
2807
+ self._save_progress()
2808
+ continue
2809
+ # 1) 规划:模块路径与Rust签名
2810
+ typer.secho(f"[c2rust-transpiler][plan] {progress_info} 正在规划模块与签名: {rec.qname or rec.name} (id={rec.id})", fg=typer.colors.CYAN)
2811
+ module, rust_sig, skip_implementation = self._plan_module_and_signature(rec, c_code)
2812
+ typer.secho(f"[c2rust-transpiler][plan] 已选择 模块={module}, 签名={rust_sig}", fg=typer.colors.CYAN)
2813
+
2814
+ # 记录当前进度
2815
+ self._update_progress_current(rec, module, rust_sig)
2816
+ typer.secho(f"[c2rust-transpiler][progress] 已更新当前进度记录 id={rec.id}", fg=typer.colors.CYAN)
2817
+
2818
+ # 如果标记为跳过实现,则直接标记为已转换
2819
+ if skip_implementation:
2820
+ typer.secho(f"[c2rust-transpiler][skip-impl] 函数 {rec.qname or rec.name} 评估为不需要实现,跳过实现阶段", fg=typer.colors.CYAN)
2821
+ # 直接标记为已转换,跳过代码生成、构建和审查阶段
2822
+ self._mark_converted(rec, module, rust_sig)
2823
+ typer.secho(f"[c2rust-transpiler][mark] 已标记并建立映射: {rec.qname or rec.name} -> {module} (跳过实现,视为已实现)", fg=typer.colors.GREEN)
2824
+ continue
2246
2825
 
2247
- # 2) 生成实现
2248
- unresolved = self._untranslated_callee_symbols(rec)
2249
- typer.secho(f"[c2rust-transpiler][deps] 未解析的被调符号: {', '.join(unresolved) if unresolved else '(none)'}", fg=typer.colors.BLUE)
2250
- typer.secho(f"[c2rust-transpiler][gen] 正在为 {rec.qname or rec.name} 生成 Rust 实现", fg=typer.colors.GREEN)
2251
- self._codeagent_generate_impl(rec, c_code, module, rust_sig, unresolved)
2252
- typer.secho(f"[c2rust-transpiler][gen] 已在 {module} 生成或更新实现", fg=typer.colors.GREEN)
2253
- # 刷新精简上下文(防止签名/模块调整后提示不同步)
2254
- try:
2255
- self._refresh_compact_context(rec, module, rust_sig)
2256
- except Exception:
2257
- pass
2826
+ # 初始化函数上下文与代码编写与修复Agent复用缓存(只在当前函数开始时执行一次)
2827
+ self._reset_function_context(rec, module, rust_sig, c_code)
2258
2828
 
2259
- # 3) 构建与修复
2260
- typer.secho("[c2rust-transpiler][build] 开始 cargo 测试循环", fg=typer.colors.MAGENTA)
2261
- ok = self._cargo_build_loop()
2262
- typer.secho(f"[c2rust-transpiler][build] 构建结果: {'通过' if ok else '失败'}", fg=typer.colors.MAGENTA)
2263
- if not ok:
2264
- typer.secho("[c2rust-transpiler] 在重试次数限制内未能成功构建,已停止。", fg=typer.colors.RED)
2265
- # 保留当前状态,便于下次 resume
2266
- return
2829
+ # 1.5) 确保模块声明链(提前到生成实现之前,避免生成的代码无法被正确引用)
2830
+ try:
2831
+ self._ensure_mod_chain_for_module(module)
2832
+ typer.secho(f"[c2rust-transpiler][mod] 已补齐 {module} mod.rs 声明链", fg=typer.colors.GREEN)
2833
+ # 确保顶层模块在 src/lib.rs 中被公开
2834
+ mp = Path(module)
2835
+ crate_root = self.crate_dir.resolve()
2836
+ rel = mp.resolve().relative_to(crate_root) if mp.is_absolute() else Path(module)
2837
+ rel_s = str(rel).replace("\\", "/")
2838
+ if rel_s.startswith("./"):
2839
+ rel_s = rel_s[2:]
2840
+ if rel_s.startswith("src/"):
2841
+ parts = rel_s[len("src/"):].strip("/").split("/")
2842
+ if parts and parts[0]:
2843
+ top_mod = parts[0]
2844
+ # 过滤掉 "mod" 关键字和 .rs 文件
2845
+ if top_mod != "mod" and not top_mod.endswith(".rs"):
2846
+ self._ensure_top_level_pub_mod(top_mod)
2847
+ typer.secho(f"[c2rust-transpiler][mod] 已在 src/lib.rs 确保顶层 pub mod {top_mod}", fg=typer.colors.GREEN)
2848
+ cur = self.progress.get("current") or {}
2849
+ cur["mod_chain_fixed"] = True
2850
+ cur["mod_visibility_fixed"] = True
2851
+ self.progress["current"] = cur
2852
+ self._save_progress()
2853
+ except Exception:
2854
+ pass
2267
2855
 
2268
- # 4) 审查与优化(复用 Review Agent)
2269
- typer.secho(f"[c2rust-transpiler][review] 开始代码审查: {rec.qname or rec.name}", fg=typer.colors.MAGENTA)
2270
- self._review_and_optimize(rec, module, rust_sig)
2271
- typer.secho("[c2rust-transpiler][review] 代码审查完成", fg=typer.colors.MAGENTA)
2856
+ # 在处理函数前,记录当前的 commit id(用于失败回退)
2857
+ self._current_function_start_commit = self._get_crate_commit_hash()
2858
+ if self._current_function_start_commit:
2859
+ typer.secho(f"[c2rust-transpiler][commit] 记录函数开始时的 commit: {self._current_function_start_commit}", fg=typer.colors.BLUE)
2860
+ else:
2861
+ typer.secho("[c2rust-transpiler][commit] 警告:无法获取 commit id,将无法在失败时回退", fg=typer.colors.YELLOW)
2862
+
2863
+ # 重置连续失败计数(每个新函数开始时重置)
2864
+ self._consecutive_fix_failures = 0
2865
+
2866
+ # 使用循环来处理函数,支持失败回退后重新开始
2867
+ function_retry_count = 0
2868
+ max_function_retries = MAX_FUNCTION_RETRIES
2869
+ while function_retry_count <= max_function_retries:
2870
+ if function_retry_count > 0:
2871
+ typer.secho(f"[c2rust-transpiler][retry] 重新开始处理函数 (第 {function_retry_count} 次重试)", fg=typer.colors.YELLOW)
2872
+ # 重新记录 commit id(回退后的新 commit)
2873
+ self._current_function_start_commit = self._get_crate_commit_hash()
2874
+ if self._current_function_start_commit:
2875
+ typer.secho(f"[c2rust-transpiler][commit] 重新记录函数开始时的 commit: {self._current_function_start_commit}", fg=typer.colors.BLUE)
2876
+ # 重置连续失败计数(重新开始时重置)
2877
+ self._consecutive_fix_failures = 0
2878
+
2879
+ # 2) 生成实现
2880
+ unresolved = self._untranslated_callee_symbols(rec)
2881
+ typer.secho(f"[c2rust-transpiler][deps] {progress_info} 未解析的被调符号: {', '.join(unresolved) if unresolved else '(none)'}", fg=typer.colors.BLUE)
2882
+ typer.secho(f"[c2rust-transpiler][gen] {progress_info} 正在为 {rec.qname or rec.name} 生成 Rust 实现", fg=typer.colors.GREEN)
2883
+ self._codeagent_generate_impl(rec, c_code, module, rust_sig, unresolved)
2884
+ typer.secho(f"[c2rust-transpiler][gen] 已在 {module} 生成或更新实现", fg=typer.colors.GREEN)
2885
+ # 刷新精简上下文(防止签名/模块调整后提示不同步)
2886
+ try:
2887
+ self._refresh_compact_context(rec, module, rust_sig)
2888
+ except Exception:
2889
+ pass
2272
2890
 
2273
- # 5) 标记已转换与映射记录(JSONL)
2274
- self._mark_converted(rec, module, rust_sig)
2275
- typer.secho(f"[c2rust-transpiler][mark] 已标记并建立映射: {rec.qname or rec.name} -> {module}", fg=typer.colors.GREEN)
2891
+ # 3) 构建与修复
2892
+ typer.secho("[c2rust-transpiler][build] 开始 cargo 测试循环", fg=typer.colors.MAGENTA)
2893
+ ok = self._cargo_build_loop()
2894
+
2895
+ # 检查是否需要重新开始(回退后)
2896
+ if ok is None:
2897
+ # 需要重新开始
2898
+ function_retry_count += 1
2899
+ if function_retry_count > max_function_retries:
2900
+ typer.secho(f"[c2rust-transpiler] 函数重新开始次数已达上限({max_function_retries}),停止处理该函数", fg=typer.colors.RED)
2901
+ # 保留当前状态,便于下次 resume
2902
+ return
2903
+ # 重置连续失败计数
2904
+ self._consecutive_fix_failures = 0
2905
+ # 继续循环,重新开始处理
2906
+ continue
2907
+
2908
+ typer.secho(f"[c2rust-transpiler][build] 构建结果: {'通过' if ok else '失败'}", fg=typer.colors.MAGENTA)
2909
+ if not ok:
2910
+ typer.secho("[c2rust-transpiler] 在重试次数限制内未能成功构建,已停止。", fg=typer.colors.RED)
2911
+ # 保留当前状态,便于下次 resume
2912
+ return
2913
+
2914
+ # 构建成功,跳出循环继续后续流程
2915
+ break
2276
2916
 
2277
- # 6) 若此前有其它函数因依赖当前符号而在源码中放置了 todo!("<symbol>"),则立即回头消除(复用代码编写与修复Agent)
2278
- current_rust_fn = self._extract_rust_fn_name_from_sig(rust_sig)
2279
- # 优先使用限定名匹配,其次使用简单名匹配
2280
- for sym in [rec.qname, rec.name]:
2281
- if sym:
2917
+ # 4) 审查与优化(复用 Review Agent)
2918
+ typer.secho(f"[c2rust-transpiler][review] {progress_info} 开始代码审查: {rec.qname or rec.name}", fg=typer.colors.MAGENTA)
2919
+ self._review_and_optimize(rec, module, rust_sig)
2920
+ typer.secho("[c2rust-transpiler][review] 代码审查完成", fg=typer.colors.MAGENTA)
2921
+
2922
+ # 5) 标记已转换与映射记录(JSONL)
2923
+ self._mark_converted(rec, module, rust_sig)
2924
+ typer.secho(f"[c2rust-transpiler][mark] {progress_info} 已标记并建立映射: {rec.qname or rec.name} -> {module}", fg=typer.colors.GREEN)
2925
+
2926
+ # 6) 若此前有其它函数因依赖当前符号而在源码中放置了 todo!("<symbol>"),则立即回头消除(复用代码编写与修复Agent)
2927
+ current_rust_fn = self._extract_rust_fn_name_from_sig(rust_sig)
2928
+ # 收集需要处理的符号(去重,避免 qname 和 name 相同时重复处理)
2929
+ symbols_to_resolve = []
2930
+ if rec.qname:
2931
+ symbols_to_resolve.append(rec.qname)
2932
+ if rec.name and rec.name != rec.qname: # 如果 name 与 qname 不同,才添加
2933
+ symbols_to_resolve.append(rec.name)
2934
+ # 处理每个符号(去重后)
2935
+ for sym in symbols_to_resolve:
2282
2936
  typer.secho(f"[c2rust-transpiler][todo] 清理 todo!(\'{sym}\') 的出现位置", fg=typer.colors.BLUE)
2283
2937
  self._resolve_pending_todos_for_symbol(sym, module, current_rust_fn, rust_sig)
2938
+ # 如果有处理任何符号,统一运行一次 cargo test(避免重复运行)
2939
+ if symbols_to_resolve:
2284
2940
  typer.secho("[c2rust-transpiler][build] 处理 todo 后重新运行 cargo test", fg=typer.colors.MAGENTA)
2285
2941
  self._cargo_build_loop()
2286
2942
 
2287
- typer.secho("[c2rust-transpiler] 所有符合条件的函数均已处理完毕。", fg=typer.colors.GREEN)
2943
+ typer.secho("[c2rust-transpiler] 所有符合条件的函数均已处理完毕。", fg=typer.colors.GREEN)
2944
+ finally:
2945
+ os.chdir(prev_cwd)
2946
+ typer.secho(f"[c2rust-transpiler][end] 已恢复工作目录: {os.getcwd()}", fg=typer.colors.BLUE)
2288
2947
 
2289
2948
 
2290
2949
  def run_transpile(
2291
2950
  project_root: Union[str, Path] = ".",
2292
2951
  crate_dir: Optional[Union[str, Path]] = None,
2293
2952
  llm_group: Optional[str] = None,
2294
- plan_max_retries: int = 5,
2953
+ plan_max_retries: int = DEFAULT_PLAN_MAX_RETRIES_ENTRY,
2295
2954
  max_retries: int = 0, # 兼容旧接口
2296
2955
  check_max_retries: Optional[int] = None,
2297
2956
  test_max_retries: Optional[int] = None,
2298
2957
  review_max_iterations: int = DEFAULT_REVIEW_MAX_ITERATIONS,
2299
- resume: bool = True,
2300
- only: Optional[List[str]] = None,
2958
+ disabled_libraries: Optional[List[str]] = None, # None 表示从配置文件恢复
2959
+ root_symbols: Optional[List[str]] = None, # None 表示从配置文件恢复
2301
2960
  non_interactive: bool = True,
2302
2961
  ) -> None:
2303
2962
  """
@@ -2306,8 +2965,7 @@ def run_transpile(
2306
2965
  - crate_dir: Rust crate 根目录;默认遵循 "<parent>/<cwd_name>_rs"(与当前目录同级,若 project_root 为 ".")
2307
2966
  - llm_group: 指定 LLM 模型组
2308
2967
  - max_retries: 构建与审查迭代的最大次数
2309
- - resume: 是否启用断点续跑
2310
- - only: 仅转译给定列表中的函数(函数名或限定名)
2968
+ 注意: 断点续跑功能默认始终启用
2311
2969
  """
2312
2970
  t = Transpiler(
2313
2971
  project_root=project_root,
@@ -2318,8 +2976,8 @@ def run_transpile(
2318
2976
  check_max_retries=check_max_retries,
2319
2977
  test_max_retries=test_max_retries,
2320
2978
  review_max_iterations=review_max_iterations,
2321
- resume=resume,
2322
- only=only,
2979
+ disabled_libraries=disabled_libraries,
2980
+ root_symbols=root_symbols,
2323
2981
  non_interactive=non_interactive,
2324
2982
  )
2325
2983
  t.transpile()