jarvis-ai-assistant 0.7.0__py3-none-any.whl → 0.7.8__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.8.dist-info}/METADATA +205 -70
  149. jarvis_ai_assistant-0.7.8.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.8.dist-info}/WHEEL +0 -0
  157. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.8.dist-info}/entry_points.txt +0 -0
  158. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.8.dist-info}/licenses/LICENSE +0 -0
  159. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.8.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,385 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ C2Rust 转译器工具函数
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import json
8
+ import re
9
+ from pathlib import Path
10
+ from typing import Any, Callable, Dict, List, Optional, Tuple
11
+
12
+ import typer
13
+
14
+ from jarvis.jarvis_c2rust.constants import C2RUST_DIRNAME, ORDER_JSONL
15
+ from jarvis.jarvis_c2rust.scanner import compute_translation_order_jsonl
16
+ from jarvis.jarvis_utils.git_utils import get_diff, get_diff_file_list
17
+ from jarvis.jarvis_utils.jsonnet_compat import loads as json5_loads
18
+
19
+
20
+ def ensure_order_file(project_root: Path) -> Path:
21
+ """确保 translation_order.jsonl 存在且包含有效步骤;仅基于 symbols.jsonl 生成,不使用任何回退。"""
22
+ data_dir = project_root / C2RUST_DIRNAME
23
+ order_path = data_dir / ORDER_JSONL
24
+ typer.secho(f"[c2rust-transpiler][order] 目标顺序文件: {order_path}", fg=typer.colors.BLUE)
25
+
26
+ def _has_steps(p: Path) -> bool:
27
+ try:
28
+ steps = iter_order_steps(p)
29
+ return bool(steps)
30
+ except Exception:
31
+ return False
32
+
33
+ # 已存在则校验是否有步骤
34
+ typer.secho(f"[c2rust-transpiler][order] 检查现有顺序文件有效性: {order_path}", fg=typer.colors.BLUE)
35
+ if order_path.exists():
36
+ if _has_steps(order_path):
37
+ typer.secho(f"[c2rust-transpiler][order] 现有顺序文件有效,将使用 {order_path}", fg=typer.colors.GREEN)
38
+ return order_path
39
+ # 为空或不可读:基于标准路径重新计算(仅 symbols.jsonl)
40
+ typer.secho("[c2rust-transpiler][order] 现有顺序文件为空/无效,正基于 symbols.jsonl 重新计算", fg=typer.colors.YELLOW)
41
+ try:
42
+ compute_translation_order_jsonl(data_dir, out_path=order_path)
43
+ except Exception as e:
44
+ raise RuntimeError(f"重新计算翻译顺序失败: {e}")
45
+ return order_path
46
+
47
+ # 不存在:按标准路径生成到固定文件名(仅 symbols.jsonl)
48
+ try:
49
+ compute_translation_order_jsonl(data_dir, out_path=order_path)
50
+ except Exception as e:
51
+ raise RuntimeError(f"计算翻译顺序失败: {e}")
52
+
53
+ typer.secho(f"[c2rust-transpiler][order] 已生成顺序文件: {order_path} (exists={order_path.exists()})", fg=typer.colors.BLUE)
54
+ if not order_path.exists():
55
+ raise FileNotFoundError(f"计算后未找到 translation_order.jsonl: {order_path}")
56
+
57
+ # 最终校验:若仍无有效步骤,直接报错并提示先执行 scan 或检查 symbols.jsonl
58
+ if not _has_steps(order_path):
59
+ raise RuntimeError("translation_order.jsonl 无有效步骤。请先执行 'jarvis-c2rust scan' 生成 symbols.jsonl 并重试。")
60
+
61
+ return order_path
62
+
63
+
64
+ def iter_order_steps(order_jsonl: Path) -> List[List[int]]:
65
+ """
66
+ 读取翻译顺序(兼容新旧格式),返回按步骤的函数id序列列表。
67
+ 新格式:每行包含 "ids": [int, ...] 以及 "items": [完整符号对象,...]。
68
+ 不再兼容旧格式(不支持 "records"/"symbols" 键)。
69
+ """
70
+ # 旧格式已移除:不再需要基于 symbols.jsonl 的 name->id 映射
71
+
72
+ steps: List[List[int]] = []
73
+ with order_jsonl.open("r", encoding="utf-8") as f:
74
+ for ln in f:
75
+ ln = ln.strip()
76
+ if not ln:
77
+ continue
78
+ try:
79
+ obj = json.loads(ln)
80
+ except Exception:
81
+ continue
82
+
83
+ ids = obj.get("ids")
84
+ if isinstance(ids, list) and ids:
85
+ # 新格式:仅支持 ids
86
+ try:
87
+ ids_int = [int(x) for x in ids if isinstance(x, (int, str)) and str(x).strip()]
88
+ except Exception:
89
+ ids_int = []
90
+ if ids_int:
91
+ steps.append(ids_int)
92
+ continue
93
+ # 不支持旧格式(无 ids 则跳过该行)
94
+ return steps
95
+
96
+
97
+ def dir_tree(root: Path) -> str:
98
+ """格式化 crate 目录结构(过滤部分常见目录)"""
99
+ lines: List[str] = []
100
+ exclude = {".git", "target", ".jarvis"}
101
+ if not root.exists():
102
+ return ""
103
+ for p in sorted(root.rglob("*")):
104
+ if any(part in exclude for part in p.parts):
105
+ continue
106
+ rel = p.relative_to(root)
107
+ depth = len(rel.parts) - 1
108
+ indent = " " * depth
109
+ name = rel.name + ("/" if p.is_dir() else "")
110
+ lines.append(f"{indent}- {name}")
111
+ return "\n".join(lines)
112
+
113
+
114
+ def default_crate_dir(project_root: Path) -> Path:
115
+ """遵循 llm_module_agent 的默认crate目录选择:<parent>/<cwd.name>_rs(与当前目录同级)当传入为 '.' 时"""
116
+ try:
117
+ cwd = Path(".").resolve()
118
+ if project_root.resolve() == cwd:
119
+ return cwd.parent / f"{cwd.name}_rs"
120
+ else:
121
+ return project_root
122
+ except Exception:
123
+ return project_root
124
+
125
+
126
+ def read_json(path: Path, default: Any) -> Any:
127
+ try:
128
+ if path.exists():
129
+ return json.loads(path.read_text(encoding="utf-8"))
130
+ except Exception:
131
+ pass
132
+ return default
133
+
134
+
135
+ def write_json(path: Path, obj: Any) -> None:
136
+ """原子性写入JSON文件:先写入临时文件,再重命名"""
137
+ try:
138
+ path.parent.mkdir(parents=True, exist_ok=True)
139
+ # 使用临时文件确保原子性
140
+ temp_path = path.with_suffix(path.suffix + ".tmp")
141
+ temp_path.write_text(json.dumps(obj, ensure_ascii=False, indent=2), encoding="utf-8")
142
+ # 原子性重命名
143
+ temp_path.replace(path)
144
+ except Exception:
145
+ # 如果原子写入失败,回退到直接写入
146
+ try:
147
+ path.write_text(json.dumps(obj, ensure_ascii=False, indent=2), encoding="utf-8")
148
+ except Exception:
149
+ pass
150
+
151
+
152
+ def extract_json_from_summary(text: str) -> Tuple[Dict[str, Any], Optional[str]]:
153
+ """
154
+ 从 Agent summary 中提取结构化数据(使用 JSON 格式):
155
+ - 仅在 <SUMMARY>...</SUMMARY> 块内查找;
156
+ - 直接解析 <SUMMARY> 块内的内容为 JSON 对象(不需要额外的 <json> 标签);
157
+ - 使用 jsonnet 解析,支持更宽松的 JSON 语法(如尾随逗号、注释等);
158
+ 返回(解析结果, 错误信息)
159
+ 如果解析成功,返回(data, None)
160
+ 如果解析失败,返回({}, 错误信息)
161
+ """
162
+ if not isinstance(text, str) or not text.strip():
163
+ return {}, "摘要文本为空"
164
+
165
+ # 提取 <SUMMARY> 块
166
+ m = re.search(r"<SUMMARY>([\s\S]*?)</SUMMARY>", text, flags=re.IGNORECASE)
167
+ block = (m.group(1) if m else text).strip()
168
+
169
+ if not block:
170
+ return {}, "未找到 <SUMMARY> 或 </SUMMARY> 标签,或标签内容为空"
171
+
172
+ try:
173
+ try:
174
+ obj = json5_loads(block)
175
+ except Exception as json_err:
176
+ error_msg = f"JSON 解析失败: {str(json_err)}"
177
+ return {}, error_msg
178
+ if isinstance(obj, dict):
179
+ return obj, None
180
+ return {}, f"JSON 解析结果不是字典,而是 {type(obj).__name__}"
181
+ except Exception as e:
182
+ return {}, f"解析过程发生异常: {str(e)}"
183
+
184
+
185
+ def detect_test_deletion(log_prefix: str = "[c2rust]") -> Optional[Dict[str, Any]]:
186
+ """
187
+ 检测是否错误删除了 #[test] 或 #[cfg(test)]。
188
+
189
+ 参数:
190
+ log_prefix: 日志前缀(如 "[c2rust-transpiler]" 或 "[c2rust-optimizer]")
191
+
192
+ 返回:
193
+ 如果检测到删除,返回包含 'diff', 'files', 'deleted_tests' 的字典;否则返回 None
194
+ """
195
+ try:
196
+ diff = get_diff()
197
+ if not diff:
198
+ return None
199
+
200
+ # 检查 diff 中是否包含删除的 #[test] 或 #[cfg(test)]
201
+ test_patterns = [
202
+ r'^-\s*#\[test\]',
203
+ r'^-\s*#\[cfg\(test\)\]',
204
+ r'^-\s*#\[cfg\(test\)',
205
+ ]
206
+
207
+ deleted_tests = []
208
+ lines = diff.split('\n')
209
+ current_file = None
210
+
211
+ for i, line in enumerate(lines):
212
+ # 检测文件路径
213
+ if line.startswith('diff --git') or line.startswith('---') or line.startswith('+++'):
214
+ # 尝试从 diff 行中提取文件名
215
+ if line.startswith('---'):
216
+ parts = line.split()
217
+ if len(parts) > 1:
218
+ current_file = parts[1].lstrip('a/').lstrip('b/')
219
+ elif line.startswith('+++'):
220
+ parts = line.split()
221
+ if len(parts) > 1:
222
+ current_file = parts[1].lstrip('a/').lstrip('b/')
223
+ continue
224
+
225
+ # 检查是否匹配删除的测试标记
226
+ for pattern in test_patterns:
227
+ if re.search(pattern, line, re.IGNORECASE):
228
+ # 检查上下文,确认是删除而不是修改
229
+ if i > 0 and lines[i-1].startswith('-'):
230
+ # 可能是删除的一部分
231
+ deleted_tests.append({
232
+ 'file': current_file or 'unknown',
233
+ 'line': line,
234
+ 'line_number': i + 1,
235
+ })
236
+ elif not (i < len(lines) - 1 and lines[i+1].startswith('+')):
237
+ # 下一行不是添加,说明是删除
238
+ deleted_tests.append({
239
+ 'file': current_file or 'unknown',
240
+ 'line': line,
241
+ 'line_number': i + 1,
242
+ })
243
+ break
244
+
245
+ if deleted_tests:
246
+ modified_files = get_diff_file_list()
247
+ return {
248
+ 'diff': diff,
249
+ 'files': modified_files,
250
+ 'deleted_tests': deleted_tests,
251
+ }
252
+ return None
253
+ except Exception as e:
254
+ typer.secho(f"{log_prefix}[test-detection] 检测测试删除时发生异常: {e}", fg=typer.colors.YELLOW)
255
+ return None
256
+
257
+
258
+ def ask_llm_about_test_deletion(
259
+ detection_result: Dict[str, Any],
260
+ agent: Any,
261
+ log_prefix: str = "[c2rust]"
262
+ ) -> bool:
263
+ """
264
+ 询问 LLM 是否错误删除了测试代码。
265
+
266
+ 参数:
267
+ detection_result: 检测结果字典,包含 'diff', 'files', 'deleted_tests'
268
+ agent: 代码生成或修复的 agent 实例,使用其 model 进行询问
269
+ log_prefix: 日志前缀(如 "[c2rust-transpiler]" 或 "[c2rust-optimizer]")
270
+
271
+ 返回:
272
+ bool: 如果 LLM 认为删除不合理返回 True(需要回退),否则返回 False
273
+ """
274
+ if not agent or not hasattr(agent, 'model'):
275
+ # 如果没有 agent 或 agent 没有 model,默认认为有问题(保守策略)
276
+ return True
277
+
278
+ try:
279
+ deleted_tests = detection_result.get('deleted_tests', [])
280
+ diff = detection_result.get('diff', '')
281
+ files = detection_result.get('files', [])
282
+
283
+ # 构建预览(限制长度)
284
+ preview_lines = []
285
+ preview_lines.append("检测到可能错误删除了测试代码标记:")
286
+ preview_lines.append("")
287
+ for item in deleted_tests[:10]: # 最多显示10个
288
+ preview_lines.append(f"- 文件: {item.get('file', 'unknown')}")
289
+ preview_lines.append(f" 行: {item.get('line', '')}")
290
+ if len(deleted_tests) > 10:
291
+ preview_lines.append(f"... 还有 {len(deleted_tests) - 10} 个删除的测试标记")
292
+
293
+ # 限制 diff 长度
294
+ diff_preview = diff[:5000] if len(diff) > 5000 else diff
295
+ if len(diff) > 5000:
296
+ diff_preview += "\n... (diff 内容过长,已截断)"
297
+
298
+ prompt = f"""检测到代码变更中可能错误删除了测试代码标记(#[test] 或 #[cfg(test)]),请判断是否合理:
299
+
300
+ 删除的测试标记统计:
301
+ - 删除的测试标记数量: {len(deleted_tests)}
302
+ - 涉及的文件: {', '.join(files[:5])}{' ...' if len(files) > 5 else ''}
303
+
304
+ 删除的测试标记详情:
305
+ {chr(10).join(preview_lines)}
306
+
307
+ 代码变更预览(diff):
308
+ {diff_preview}
309
+
310
+ 请仔细分析以上代码变更,判断这些测试代码标记的删除是否合理。可能的情况包括:
311
+ 1. 重构代码,将测试代码移动到其他位置(这种情况下应该看到对应的添加)
312
+ 2. 删除过时或重复的测试代码
313
+ 3. 错误地删除了重要的测试代码标记,导致测试无法运行
314
+
315
+ 请使用以下协议回答(必须包含且仅包含以下标记之一):
316
+ - 如果认为这些删除是合理的(测试代码被正确移动或确实需要删除),回答: <!!!YES!!!>
317
+ - 如果认为这些删除不合理或存在风险(可能错误删除了测试代码),回答: <!!!NO!!!>
318
+
319
+ 请严格按照协议格式回答,不要添加其他内容。
320
+ """
321
+
322
+ typer.secho(f"{log_prefix}[test-detection] 正在询问 LLM 判断测试代码删除是否合理...", fg=typer.colors.YELLOW)
323
+ response = agent.model.chat_until_success(prompt) # type: ignore
324
+ response_str = str(response or "")
325
+
326
+ # 使用确定的协议标记解析回答
327
+ if "<!!!NO!!!>" in response_str:
328
+ typer.secho("⚠️ LLM 确认:测试代码删除不合理,需要回退", fg=typer.colors.RED)
329
+ return True # 需要回退
330
+ elif "<!!!YES!!!>" in response_str:
331
+ typer.secho("✅ LLM 确认:测试代码删除合理", fg=typer.colors.GREEN)
332
+ return False # 不需要回退
333
+ else:
334
+ # 如果无法找到协议标记,默认认为有问题(保守策略)
335
+ typer.secho(f"⚠️ 无法找到协议标记,默认认为有问题。回答内容: {response_str[:200]}", fg=typer.colors.YELLOW)
336
+ return True # 保守策略:默认回退
337
+ except Exception as e:
338
+ # 如果询问失败,默认认为有问题(保守策略)
339
+ typer.secho(f"⚠️ 询问 LLM 失败: {str(e)},默认认为有问题", fg=typer.colors.YELLOW)
340
+ return True # 保守策略:默认回退
341
+
342
+
343
+ def check_and_handle_test_deletion(
344
+ before_commit: Optional[str],
345
+ agent: Any,
346
+ reset_to_commit_fn: Callable[[str], bool],
347
+ log_prefix: str = "[c2rust]"
348
+ ) -> bool:
349
+ """
350
+ 检测并处理测试代码删除。
351
+
352
+ 参数:
353
+ before_commit: agent 运行前的 commit hash
354
+ agent: 代码生成或修复的 agent 实例,使用其 model 进行询问
355
+ reset_to_commit_fn: 回退到指定 commit 的函数,接受 commit hash 作为参数,返回是否成功
356
+ log_prefix: 日志前缀(如 "[c2rust-transpiler]" 或 "[c2rust-optimizer]")
357
+
358
+ 返回:
359
+ bool: 如果检测到问题且已回退,返回 True;否则返回 False
360
+ """
361
+ if not before_commit:
362
+ # 没有记录 commit,无法回退
363
+ return False
364
+
365
+ detection_result = detect_test_deletion(log_prefix)
366
+ if not detection_result:
367
+ # 没有检测到删除
368
+ return False
369
+
370
+ typer.secho(f"{log_prefix}[test-detection] 检测到可能错误删除了测试代码标记", fg=typer.colors.YELLOW)
371
+
372
+ # 询问 LLM(使用传入的 agent 的 model)
373
+ need_reset = ask_llm_about_test_deletion(detection_result, agent, log_prefix)
374
+
375
+ if need_reset:
376
+ typer.secho(f"{log_prefix}[test-detection] LLM 确认删除不合理,正在回退到 commit: {before_commit}", fg=typer.colors.RED)
377
+ if reset_to_commit_fn(before_commit):
378
+ typer.secho(f"{log_prefix}[test-detection] 已回退到之前的 commit", fg=typer.colors.GREEN)
379
+ return True
380
+ else:
381
+ typer.secho(f"{log_prefix}[test-detection] 回退失败", fg=typer.colors.RED)
382
+ return False
383
+
384
+ return False
385
+
@@ -12,7 +12,6 @@ import yaml
12
12
  from typing import Optional, Dict, Any
13
13
  from pathlib import Path
14
14
 
15
- from jarvis.jarvis_utils.output import OutputType, PrettyOutput
16
15
 
17
16
  CONFIG_FILE_NAME = "build_validation_config.yaml"
18
17
 
@@ -46,7 +45,7 @@ class BuildValidationConfig:
46
45
  return self._config
47
46
  except Exception as e:
48
47
  # 配置文件损坏时,返回空配置
49
- PrettyOutput.print(f"加载构建验证配置失败: {e},使用默认配置", OutputType.WARNING)
48
+ print(f"⚠️ 加载构建验证配置失败: {e},使用默认配置")
50
49
  self._config = {}
51
50
  return self._config
52
51
 
@@ -58,7 +57,7 @@ class BuildValidationConfig:
58
57
  yaml.safe_dump(self._config, f, allow_unicode=True, default_flow_style=False)
59
58
  return True
60
59
  except Exception as e:
61
- PrettyOutput.print(f"保存构建验证配置失败: {e}", OutputType.ERROR)
60
+ print(f"保存构建验证配置失败: {e}")
62
61
  return False
63
62
 
64
63
  def is_build_validation_disabled(self) -> bool: