jarvis-ai-assistant 0.7.8__py3-none-any.whl → 1.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +567 -222
  3. jarvis/jarvis_agent/agent_manager.py +19 -12
  4. jarvis/jarvis_agent/builtin_input_handler.py +79 -11
  5. jarvis/jarvis_agent/config_editor.py +7 -2
  6. jarvis/jarvis_agent/event_bus.py +24 -13
  7. jarvis/jarvis_agent/events.py +19 -1
  8. jarvis/jarvis_agent/file_context_handler.py +67 -64
  9. jarvis/jarvis_agent/file_methodology_manager.py +38 -24
  10. jarvis/jarvis_agent/jarvis.py +186 -114
  11. jarvis/jarvis_agent/language_extractors/__init__.py +8 -1
  12. jarvis/jarvis_agent/language_extractors/c_extractor.py +7 -4
  13. jarvis/jarvis_agent/language_extractors/cpp_extractor.py +9 -4
  14. jarvis/jarvis_agent/language_extractors/go_extractor.py +7 -4
  15. jarvis/jarvis_agent/language_extractors/java_extractor.py +27 -20
  16. jarvis/jarvis_agent/language_extractors/javascript_extractor.py +22 -17
  17. jarvis/jarvis_agent/language_extractors/python_extractor.py +7 -4
  18. jarvis/jarvis_agent/language_extractors/rust_extractor.py +7 -4
  19. jarvis/jarvis_agent/language_extractors/typescript_extractor.py +22 -17
  20. jarvis/jarvis_agent/language_support_info.py +250 -219
  21. jarvis/jarvis_agent/main.py +19 -23
  22. jarvis/jarvis_agent/memory_manager.py +9 -6
  23. jarvis/jarvis_agent/methodology_share_manager.py +21 -15
  24. jarvis/jarvis_agent/output_handler.py +4 -2
  25. jarvis/jarvis_agent/prompt_builder.py +7 -6
  26. jarvis/jarvis_agent/prompt_manager.py +113 -8
  27. jarvis/jarvis_agent/prompts.py +317 -85
  28. jarvis/jarvis_agent/protocols.py +5 -2
  29. jarvis/jarvis_agent/run_loop.py +192 -32
  30. jarvis/jarvis_agent/session_manager.py +7 -3
  31. jarvis/jarvis_agent/share_manager.py +23 -13
  32. jarvis/jarvis_agent/shell_input_handler.py +12 -8
  33. jarvis/jarvis_agent/stdio_redirect.py +25 -26
  34. jarvis/jarvis_agent/task_analyzer.py +29 -23
  35. jarvis/jarvis_agent/task_list.py +869 -0
  36. jarvis/jarvis_agent/task_manager.py +26 -23
  37. jarvis/jarvis_agent/tool_executor.py +6 -5
  38. jarvis/jarvis_agent/tool_share_manager.py +24 -14
  39. jarvis/jarvis_agent/user_interaction.py +3 -3
  40. jarvis/jarvis_agent/utils.py +9 -1
  41. jarvis/jarvis_agent/web_bridge.py +37 -17
  42. jarvis/jarvis_agent/web_output_sink.py +5 -2
  43. jarvis/jarvis_agent/web_server.py +165 -36
  44. jarvis/jarvis_c2rust/__init__.py +1 -1
  45. jarvis/jarvis_c2rust/cli.py +260 -141
  46. jarvis/jarvis_c2rust/collector.py +37 -18
  47. jarvis/jarvis_c2rust/constants.py +60 -0
  48. jarvis/jarvis_c2rust/library_replacer.py +242 -1010
  49. jarvis/jarvis_c2rust/library_replacer_checkpoint.py +133 -0
  50. jarvis/jarvis_c2rust/library_replacer_llm.py +287 -0
  51. jarvis/jarvis_c2rust/library_replacer_loader.py +191 -0
  52. jarvis/jarvis_c2rust/library_replacer_output.py +134 -0
  53. jarvis/jarvis_c2rust/library_replacer_prompts.py +124 -0
  54. jarvis/jarvis_c2rust/library_replacer_utils.py +188 -0
  55. jarvis/jarvis_c2rust/llm_module_agent.py +98 -1044
  56. jarvis/jarvis_c2rust/llm_module_agent_apply.py +170 -0
  57. jarvis/jarvis_c2rust/llm_module_agent_executor.py +288 -0
  58. jarvis/jarvis_c2rust/llm_module_agent_loader.py +170 -0
  59. jarvis/jarvis_c2rust/llm_module_agent_prompts.py +268 -0
  60. jarvis/jarvis_c2rust/llm_module_agent_types.py +57 -0
  61. jarvis/jarvis_c2rust/llm_module_agent_utils.py +150 -0
  62. jarvis/jarvis_c2rust/llm_module_agent_validator.py +119 -0
  63. jarvis/jarvis_c2rust/loaders.py +28 -10
  64. jarvis/jarvis_c2rust/models.py +5 -2
  65. jarvis/jarvis_c2rust/optimizer.py +192 -1974
  66. jarvis/jarvis_c2rust/optimizer_build_fix.py +286 -0
  67. jarvis/jarvis_c2rust/optimizer_clippy.py +766 -0
  68. jarvis/jarvis_c2rust/optimizer_config.py +49 -0
  69. jarvis/jarvis_c2rust/optimizer_docs.py +183 -0
  70. jarvis/jarvis_c2rust/optimizer_options.py +48 -0
  71. jarvis/jarvis_c2rust/optimizer_progress.py +469 -0
  72. jarvis/jarvis_c2rust/optimizer_report.py +52 -0
  73. jarvis/jarvis_c2rust/optimizer_unsafe.py +309 -0
  74. jarvis/jarvis_c2rust/optimizer_utils.py +469 -0
  75. jarvis/jarvis_c2rust/optimizer_visibility.py +185 -0
  76. jarvis/jarvis_c2rust/scanner.py +229 -166
  77. jarvis/jarvis_c2rust/transpiler.py +531 -2732
  78. jarvis/jarvis_c2rust/transpiler_agents.py +503 -0
  79. jarvis/jarvis_c2rust/transpiler_build.py +1294 -0
  80. jarvis/jarvis_c2rust/transpiler_codegen.py +204 -0
  81. jarvis/jarvis_c2rust/transpiler_compile.py +146 -0
  82. jarvis/jarvis_c2rust/transpiler_config.py +178 -0
  83. jarvis/jarvis_c2rust/transpiler_context.py +122 -0
  84. jarvis/jarvis_c2rust/transpiler_executor.py +516 -0
  85. jarvis/jarvis_c2rust/transpiler_generation.py +278 -0
  86. jarvis/jarvis_c2rust/transpiler_git.py +163 -0
  87. jarvis/jarvis_c2rust/transpiler_mod_utils.py +225 -0
  88. jarvis/jarvis_c2rust/transpiler_modules.py +336 -0
  89. jarvis/jarvis_c2rust/transpiler_planning.py +394 -0
  90. jarvis/jarvis_c2rust/transpiler_review.py +1196 -0
  91. jarvis/jarvis_c2rust/transpiler_symbols.py +176 -0
  92. jarvis/jarvis_c2rust/utils.py +269 -79
  93. jarvis/jarvis_code_agent/after_change.py +233 -0
  94. jarvis/jarvis_code_agent/build_validation_config.py +37 -30
  95. jarvis/jarvis_code_agent/builtin_rules.py +68 -0
  96. jarvis/jarvis_code_agent/code_agent.py +976 -1517
  97. jarvis/jarvis_code_agent/code_agent_build.py +227 -0
  98. jarvis/jarvis_code_agent/code_agent_diff.py +246 -0
  99. jarvis/jarvis_code_agent/code_agent_git.py +525 -0
  100. jarvis/jarvis_code_agent/code_agent_impact.py +177 -0
  101. jarvis/jarvis_code_agent/code_agent_lint.py +283 -0
  102. jarvis/jarvis_code_agent/code_agent_llm.py +159 -0
  103. jarvis/jarvis_code_agent/code_agent_postprocess.py +105 -0
  104. jarvis/jarvis_code_agent/code_agent_prompts.py +46 -0
  105. jarvis/jarvis_code_agent/code_agent_rules.py +305 -0
  106. jarvis/jarvis_code_agent/code_analyzer/__init__.py +52 -48
  107. jarvis/jarvis_code_agent/code_analyzer/base_language.py +12 -10
  108. jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +12 -11
  109. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +16 -12
  110. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +26 -17
  111. jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +558 -104
  112. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +27 -16
  113. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +22 -18
  114. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +21 -16
  115. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +20 -16
  116. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +27 -16
  117. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +47 -23
  118. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +71 -37
  119. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +162 -35
  120. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +111 -57
  121. jarvis/jarvis_code_agent/code_analyzer/build_validator.py +18 -12
  122. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +185 -183
  123. jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +2 -1
  124. jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +24 -15
  125. jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +227 -141
  126. jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +321 -247
  127. jarvis/jarvis_code_agent/code_analyzer/language_registry.py +37 -29
  128. jarvis/jarvis_code_agent/code_analyzer/language_support.py +21 -13
  129. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +15 -9
  130. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +75 -45
  131. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +87 -52
  132. jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +84 -51
  133. jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +94 -64
  134. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +109 -71
  135. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +97 -63
  136. jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +103 -69
  137. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +271 -268
  138. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +76 -64
  139. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +92 -19
  140. jarvis/jarvis_code_agent/diff_visualizer.py +998 -0
  141. jarvis/jarvis_code_agent/lint.py +223 -524
  142. jarvis/jarvis_code_agent/rule_share_manager.py +158 -0
  143. jarvis/jarvis_code_agent/rules/clean_code.md +144 -0
  144. jarvis/jarvis_code_agent/rules/code_review.md +115 -0
  145. jarvis/jarvis_code_agent/rules/documentation.md +165 -0
  146. jarvis/jarvis_code_agent/rules/generate_rules.md +52 -0
  147. jarvis/jarvis_code_agent/rules/performance.md +158 -0
  148. jarvis/jarvis_code_agent/rules/refactoring.md +139 -0
  149. jarvis/jarvis_code_agent/rules/security.md +160 -0
  150. jarvis/jarvis_code_agent/rules/tdd.md +78 -0
  151. jarvis/jarvis_code_agent/test_rules/cpp_test.md +118 -0
  152. jarvis/jarvis_code_agent/test_rules/go_test.md +98 -0
  153. jarvis/jarvis_code_agent/test_rules/java_test.md +99 -0
  154. jarvis/jarvis_code_agent/test_rules/javascript_test.md +113 -0
  155. jarvis/jarvis_code_agent/test_rules/php_test.md +117 -0
  156. jarvis/jarvis_code_agent/test_rules/python_test.md +91 -0
  157. jarvis/jarvis_code_agent/test_rules/ruby_test.md +102 -0
  158. jarvis/jarvis_code_agent/test_rules/rust_test.md +86 -0
  159. jarvis/jarvis_code_agent/utils.py +36 -26
  160. jarvis/jarvis_code_analysis/checklists/loader.py +21 -21
  161. jarvis/jarvis_code_analysis/code_review.py +64 -33
  162. jarvis/jarvis_data/config_schema.json +285 -192
  163. jarvis/jarvis_git_squash/main.py +8 -6
  164. jarvis/jarvis_git_utils/git_commiter.py +53 -76
  165. jarvis/jarvis_mcp/__init__.py +5 -2
  166. jarvis/jarvis_mcp/sse_mcp_client.py +40 -30
  167. jarvis/jarvis_mcp/stdio_mcp_client.py +27 -19
  168. jarvis/jarvis_mcp/streamable_mcp_client.py +35 -26
  169. jarvis/jarvis_memory_organizer/memory_organizer.py +78 -55
  170. jarvis/jarvis_methodology/main.py +48 -39
  171. jarvis/jarvis_multi_agent/__init__.py +56 -23
  172. jarvis/jarvis_multi_agent/main.py +15 -18
  173. jarvis/jarvis_platform/base.py +179 -111
  174. jarvis/jarvis_platform/human.py +27 -16
  175. jarvis/jarvis_platform/kimi.py +52 -45
  176. jarvis/jarvis_platform/openai.py +101 -40
  177. jarvis/jarvis_platform/registry.py +51 -33
  178. jarvis/jarvis_platform/tongyi.py +68 -38
  179. jarvis/jarvis_platform/yuanbao.py +59 -43
  180. jarvis/jarvis_platform_manager/main.py +68 -76
  181. jarvis/jarvis_platform_manager/service.py +24 -14
  182. jarvis/jarvis_rag/README_CONFIG.md +314 -0
  183. jarvis/jarvis_rag/README_DYNAMIC_LOADING.md +311 -0
  184. jarvis/jarvis_rag/README_ONLINE_MODELS.md +230 -0
  185. jarvis/jarvis_rag/__init__.py +57 -4
  186. jarvis/jarvis_rag/cache.py +3 -1
  187. jarvis/jarvis_rag/cli.py +48 -68
  188. jarvis/jarvis_rag/embedding_interface.py +39 -0
  189. jarvis/jarvis_rag/embedding_manager.py +7 -230
  190. jarvis/jarvis_rag/embeddings/__init__.py +41 -0
  191. jarvis/jarvis_rag/embeddings/base.py +114 -0
  192. jarvis/jarvis_rag/embeddings/cohere.py +66 -0
  193. jarvis/jarvis_rag/embeddings/edgefn.py +117 -0
  194. jarvis/jarvis_rag/embeddings/local.py +260 -0
  195. jarvis/jarvis_rag/embeddings/openai.py +62 -0
  196. jarvis/jarvis_rag/embeddings/registry.py +293 -0
  197. jarvis/jarvis_rag/llm_interface.py +8 -6
  198. jarvis/jarvis_rag/query_rewriter.py +8 -9
  199. jarvis/jarvis_rag/rag_pipeline.py +61 -52
  200. jarvis/jarvis_rag/reranker.py +7 -75
  201. jarvis/jarvis_rag/reranker_interface.py +32 -0
  202. jarvis/jarvis_rag/rerankers/__init__.py +41 -0
  203. jarvis/jarvis_rag/rerankers/base.py +109 -0
  204. jarvis/jarvis_rag/rerankers/cohere.py +67 -0
  205. jarvis/jarvis_rag/rerankers/edgefn.py +140 -0
  206. jarvis/jarvis_rag/rerankers/jina.py +79 -0
  207. jarvis/jarvis_rag/rerankers/local.py +89 -0
  208. jarvis/jarvis_rag/rerankers/registry.py +293 -0
  209. jarvis/jarvis_rag/retriever.py +58 -43
  210. jarvis/jarvis_sec/__init__.py +66 -141
  211. jarvis/jarvis_sec/agents.py +21 -17
  212. jarvis/jarvis_sec/analysis.py +80 -33
  213. jarvis/jarvis_sec/checkers/__init__.py +7 -13
  214. jarvis/jarvis_sec/checkers/c_checker.py +356 -164
  215. jarvis/jarvis_sec/checkers/rust_checker.py +47 -29
  216. jarvis/jarvis_sec/cli.py +43 -21
  217. jarvis/jarvis_sec/clustering.py +430 -272
  218. jarvis/jarvis_sec/file_manager.py +99 -55
  219. jarvis/jarvis_sec/parsers.py +9 -6
  220. jarvis/jarvis_sec/prompts.py +4 -3
  221. jarvis/jarvis_sec/report.py +44 -22
  222. jarvis/jarvis_sec/review.py +180 -107
  223. jarvis/jarvis_sec/status.py +50 -41
  224. jarvis/jarvis_sec/types.py +3 -0
  225. jarvis/jarvis_sec/utils.py +160 -83
  226. jarvis/jarvis_sec/verification.py +411 -181
  227. jarvis/jarvis_sec/workflow.py +132 -21
  228. jarvis/jarvis_smart_shell/main.py +28 -41
  229. jarvis/jarvis_stats/cli.py +14 -12
  230. jarvis/jarvis_stats/stats.py +28 -19
  231. jarvis/jarvis_stats/storage.py +14 -8
  232. jarvis/jarvis_stats/visualizer.py +12 -7
  233. jarvis/jarvis_tools/base.py +5 -2
  234. jarvis/jarvis_tools/clear_memory.py +13 -9
  235. jarvis/jarvis_tools/cli/main.py +23 -18
  236. jarvis/jarvis_tools/edit_file.py +572 -873
  237. jarvis/jarvis_tools/execute_script.py +10 -7
  238. jarvis/jarvis_tools/file_analyzer.py +7 -8
  239. jarvis/jarvis_tools/meta_agent.py +287 -0
  240. jarvis/jarvis_tools/methodology.py +5 -3
  241. jarvis/jarvis_tools/read_code.py +305 -1438
  242. jarvis/jarvis_tools/read_symbols.py +50 -17
  243. jarvis/jarvis_tools/read_webpage.py +19 -18
  244. jarvis/jarvis_tools/registry.py +435 -156
  245. jarvis/jarvis_tools/retrieve_memory.py +16 -11
  246. jarvis/jarvis_tools/save_memory.py +8 -6
  247. jarvis/jarvis_tools/search_web.py +31 -31
  248. jarvis/jarvis_tools/sub_agent.py +32 -28
  249. jarvis/jarvis_tools/sub_code_agent.py +44 -60
  250. jarvis/jarvis_tools/task_list_manager.py +1811 -0
  251. jarvis/jarvis_tools/virtual_tty.py +29 -19
  252. jarvis/jarvis_utils/__init__.py +4 -0
  253. jarvis/jarvis_utils/builtin_replace_map.py +2 -1
  254. jarvis/jarvis_utils/clipboard.py +9 -8
  255. jarvis/jarvis_utils/collections.py +331 -0
  256. jarvis/jarvis_utils/config.py +699 -194
  257. jarvis/jarvis_utils/dialogue_recorder.py +294 -0
  258. jarvis/jarvis_utils/embedding.py +6 -3
  259. jarvis/jarvis_utils/file_processors.py +7 -1
  260. jarvis/jarvis_utils/fzf.py +9 -3
  261. jarvis/jarvis_utils/git_utils.py +71 -42
  262. jarvis/jarvis_utils/globals.py +116 -32
  263. jarvis/jarvis_utils/http.py +6 -2
  264. jarvis/jarvis_utils/input.py +318 -83
  265. jarvis/jarvis_utils/jsonnet_compat.py +119 -104
  266. jarvis/jarvis_utils/methodology.py +37 -28
  267. jarvis/jarvis_utils/output.py +201 -44
  268. jarvis/jarvis_utils/utils.py +986 -628
  269. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/METADATA +49 -33
  270. jarvis_ai_assistant-1.0.2.dist-info/RECORD +304 -0
  271. jarvis/jarvis_code_agent/code_analyzer/structured_code.py +0 -556
  272. jarvis/jarvis_tools/generate_new_tool.py +0 -205
  273. jarvis/jarvis_tools/lsp_client.py +0 -1552
  274. jarvis/jarvis_tools/rewrite_file.py +0 -105
  275. jarvis_ai_assistant-0.7.8.dist-info/RECORD +0 -218
  276. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/WHEEL +0 -0
  277. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/entry_points.txt +0 -0
  278. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/licenses/LICENSE +0 -0
  279. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,3 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
1
  """
4
2
  使用 libclang 的 C/C++ 函数扫描器和调用图提取器。
5
3
 
@@ -47,17 +45,23 @@ JSONL 文件
47
45
 
48
46
  from __future__ import annotations
49
47
 
50
-
51
48
  import json
52
- import os
53
49
 
54
- import sys
50
+ from jarvis.jarvis_utils.output import PrettyOutput
51
+
52
+ #!/usr/bin/env python3
53
+ # -*- coding: utf-8 -*-
54
+ import os
55
+ import shutil
55
56
  import time
56
57
  from dataclasses import dataclass
57
58
  from pathlib import Path
58
59
  from typing import Any, Dict, Iterable, List, Optional, Set
60
+
59
61
  import typer
60
- import shutil
62
+
63
+ from jarvis.jarvis_c2rust.constants import SOURCE_EXTS, TYPE_KINDS
64
+
61
65
 
62
66
  # ---------------------------
63
67
  # libclang loader
@@ -88,8 +92,10 @@ def _try_import_libclang() -> Any:
88
92
  # Verify Python clang bindings major version (if available)
89
93
  py_major: Optional[int] = None
90
94
  try:
91
- import clang as _clang
92
95
  import re as _re
96
+
97
+ import clang as _clang
98
+
93
99
  v = getattr(_clang, "__version__", None)
94
100
  if v:
95
101
  m = _re.match(r"(\\d+)", str(v))
@@ -111,8 +117,10 @@ def _try_import_libclang() -> Any:
111
117
  try:
112
118
  import ctypes
113
119
  import re as _re
120
+
114
121
  class CXString(ctypes.Structure):
115
122
  _fields_ = [("data", ctypes.c_void_p), ("private_flags", ctypes.c_uint)]
123
+
116
124
  lib = ctypes.CDLL(path)
117
125
  # Ensure correct ctypes signatures to avoid mis-parsing strings
118
126
  lib.clang_getClangVersion.restype = CXString
@@ -175,11 +183,13 @@ def _try_import_libclang() -> Any:
175
183
  for maj in (21, 20, 19, 18, 17, 16):
176
184
  candidates.append(base / f"libclang.so.{maj}")
177
185
  # Generic names
178
- candidates.extend([
179
- base / "libclang.so", # Linux
180
- base / "libclang.dylib", # macOS
181
- base / "libclang.dll", # Windows
182
- ])
186
+ candidates.extend(
187
+ [
188
+ base / "libclang.so", # Linux
189
+ base / "libclang.dylib", # macOS
190
+ base / "libclang.dll", # Windows
191
+ ]
192
+ )
183
193
  for cand in candidates:
184
194
  if cand.exists() and _ensure_supported_and_set(str(cand)):
185
195
  return cindex
@@ -196,52 +206,65 @@ def _try_import_libclang() -> Any:
196
206
  candidates_llvm: List[Path] = []
197
207
  for maj in (21, 20, 19, 18, 17, 16):
198
208
  candidates_llvm.append(p / f"libclang.so.{maj}")
199
- candidates_llvm.extend([
200
- p / "libclang.so",
201
- p / "libclang.dylib",
202
- p / "libclang.dll",
203
- ])
209
+ candidates_llvm.extend(
210
+ [
211
+ p / "libclang.so",
212
+ p / "libclang.dylib",
213
+ p / "libclang.dll",
214
+ ]
215
+ )
204
216
  for cand in candidates_llvm:
205
217
  if cand.exists() and _ensure_supported_and_set(str(cand)):
206
218
  return cindex
207
219
 
208
220
  # 4) Common locations for versions 16-21
209
221
  import platform as _platform
222
+
210
223
  sys_name = _platform.system()
211
224
  path_candidates: List[Path] = []
212
225
  if sys_name == "Linux":
213
226
  for maj in (21, 20, 19, 18, 17, 16):
214
- path_candidates.extend([
215
- Path(f"/usr/lib/llvm-{maj}/lib/libclang.so.{maj}"),
216
- Path(f"/usr/lib/llvm-{maj}/lib/libclang.so"),
217
- ])
227
+ path_candidates.extend(
228
+ [
229
+ Path(f"/usr/lib/llvm-{maj}/lib/libclang.so.{maj}"),
230
+ Path(f"/usr/lib/llvm-{maj}/lib/libclang.so"),
231
+ ]
232
+ )
218
233
  # Generic fallbacks
219
- path_candidates.extend([
220
- Path("/usr/local/lib/libclang.so.21"),
221
- Path("/usr/local/lib/libclang.so.20"),
222
- Path("/usr/local/lib/libclang.so.19"),
223
- Path("/usr/local/lib/libclang.so.18"),
224
- Path("/usr/local/lib/libclang.so.17"),
225
- Path("/usr/local/lib/libclang.so.16"),
226
- Path("/usr/local/lib/libclang.so"),
227
- Path("/usr/lib/libclang.so.21"),
228
- Path("/usr/lib/libclang.so.20"),
229
- Path("/usr/lib/libclang.so.19"),
230
- Path("/usr/lib/libclang.so.18"),
231
- Path("/usr/lib/libclang.so.17"),
232
- Path("/usr/lib/libclang.so.16"),
233
- Path("/usr/lib/libclang.so"),
234
- ])
234
+ path_candidates.extend(
235
+ [
236
+ Path("/usr/local/lib/libclang.so.21"),
237
+ Path("/usr/local/lib/libclang.so.20"),
238
+ Path("/usr/local/lib/libclang.so.19"),
239
+ Path("/usr/local/lib/libclang.so.18"),
240
+ Path("/usr/local/lib/libclang.so.17"),
241
+ Path("/usr/local/lib/libclang.so.16"),
242
+ Path("/usr/local/lib/libclang.so"),
243
+ Path("/usr/lib/libclang.so.21"),
244
+ Path("/usr/lib/libclang.so.20"),
245
+ Path("/usr/lib/libclang.so.19"),
246
+ Path("/usr/lib/libclang.so.18"),
247
+ Path("/usr/lib/libclang.so.17"),
248
+ Path("/usr/lib/libclang.so.16"),
249
+ Path("/usr/lib/libclang.so"),
250
+ ]
251
+ )
235
252
  elif sys_name == "Darwin":
236
253
  # Homebrew llvm@N formulas
237
254
  for maj in (21, 20, 19, 18, 17, 16):
238
- path_candidates.append(Path(f"/opt/homebrew/opt/llvm@{maj}/lib/libclang.dylib"))
239
- path_candidates.append(Path(f"/usr/local/opt/llvm@{maj}/lib/libclang.dylib"))
255
+ path_candidates.append(
256
+ Path(f"/opt/homebrew/opt/llvm@{maj}/lib/libclang.dylib")
257
+ )
258
+ path_candidates.append(
259
+ Path(f"/usr/local/opt/llvm@{maj}/lib/libclang.dylib")
260
+ )
240
261
  # Generic llvm formula path (may be symlinked to a specific version)
241
- path_candidates.extend([
242
- Path("/opt/homebrew/opt/llvm/lib/libclang.dylib"),
243
- Path("/usr/local/opt/llvm/lib/libclang.dylib"),
244
- ])
262
+ path_candidates.extend(
263
+ [
264
+ Path("/opt/homebrew/opt/llvm/lib/libclang.dylib"),
265
+ Path("/usr/local/opt/llvm/lib/libclang.dylib"),
266
+ ]
267
+ )
245
268
  else:
246
269
  # Best-effort on other systems (Windows)
247
270
  path_candidates = [
@@ -298,6 +321,8 @@ def _try_import_libclang() -> Any:
298
321
  " export CLANG_LIBRARY_FILE=/usr/lib/llvm-21/lib/libclang.so # Linux (请调整版本)\n"
299
322
  " export CLANG_LIBRARY_FILE=/opt/homebrew/opt/llvm@21/lib/libclang.dylib # macOS (请调整版本)\n"
300
323
  )
324
+
325
+
301
326
  # ---------------------------
302
327
  # Data structures
303
328
  # ---------------------------
@@ -317,10 +342,6 @@ class FunctionInfo:
317
342
  language: str
318
343
 
319
344
 
320
-
321
-
322
-
323
-
324
345
  # ---------------------------
325
346
  # Compile commands loader
326
347
  # ---------------------------
@@ -350,7 +371,7 @@ def load_compile_commands(cc_path: Path) -> Dict[str, List[str]]:
350
371
  except Exception:
351
372
  return {}
352
373
 
353
- mapping: Dict[str, List[str]] = {}
374
+ result: Dict[str, List[str]] = {}
354
375
  for entry in data:
355
376
  file_path = Path(entry.get("file", "")).resolve()
356
377
  if not file_path:
@@ -358,38 +379,16 @@ def load_compile_commands(cc_path: Path) -> Dict[str, List[str]]:
358
379
  if "arguments" in entry and isinstance(entry["arguments"], list):
359
380
  # arguments usually includes the compiler as argv[0]
360
381
  args = entry["arguments"][1:] if entry["arguments"] else []
382
+ result[str(file_path)] = args
361
383
  else:
362
384
  # fallback to split command string
363
- cmd = entry.get("command", "")
364
- import shlex
365
- parts = shlex.split(cmd) if cmd else []
366
- args = parts[1:] if parts else []
367
-
368
- # Clean args: drop compile-only/output flags that confuse libclang
369
- cleaned: List[str] = []
370
- skip_next = False
371
- for a in args:
372
- if skip_next:
373
- skip_next = False
374
- continue
375
- if a in ("-c",):
376
- continue
377
- if a in ("-o", "-MF"):
378
- skip_next = True
379
- continue
380
- if a.startswith("-o"):
381
- continue
382
- cleaned.append(a)
383
- mapping[str(file_path)] = cleaned
384
- return mapping
385
+ command = entry.get("command", "")
386
+ if command:
387
+ result[str(file_path)] = (
388
+ command.split()[1:] if len(command.split()) > 1 else []
389
+ )
390
+ return result
385
391
 
386
- # ---------------------------
387
- # File discovery
388
- # ---------------------------
389
- SOURCE_EXTS: Set[str] = {
390
- ".c", ".cc", ".cpp", ".cxx", ".C",
391
- ".h", ".hh", ".hpp", ".hxx",
392
- }
393
392
 
394
393
  def iter_source_files(root: Path) -> Iterable[Path]:
395
394
  for p in root.rglob("*"):
@@ -501,13 +500,18 @@ def scan_file(cindex, file_path: Path, args: List[str]) -> List[FunctionInfo]:
501
500
  # Only consider functions with definitions in this file
502
501
  if is_function_like(node) and node.is_definition():
503
502
  loc_file = node.location.file
504
- if loc_file is not None and Path(loc_file.name).resolve() == file_path.resolve():
503
+ if (
504
+ loc_file is not None
505
+ and Path(loc_file.name).resolve() == file_path.resolve()
506
+ ):
505
507
  try:
506
508
  name = node.spelling or ""
507
509
  qualified_name = get_qualified_name(node)
508
510
  signature = node.displayname or name
509
511
  try:
510
- return_type = node.result_type.spelling # not available for constructors/destructors
512
+ return_type = (
513
+ node.result_type.spelling
514
+ ) # not available for constructors/destructors
511
515
  except Exception:
512
516
  return_type = ""
513
517
  params = collect_params(node)
@@ -568,6 +572,7 @@ def scan_directory(scan_root: Path, db_path: Optional[Path] = None) -> Path:
568
572
  if cindex is None:
569
573
  try:
570
574
  from clang import cindex as _ci
575
+
571
576
  cindex = _ci
572
577
  except Exception as e:
573
578
  raise RuntimeError(f"Failed to load libclang bindings: {e}")
@@ -582,6 +587,7 @@ def scan_directory(scan_root: Path, db_path: Optional[Path] = None) -> Path:
582
587
  def _has_symbol(lib_path: str, symbol: str) -> bool:
583
588
  try:
584
589
  import ctypes
590
+
585
591
  lib = ctypes.CDLL(lib_path)
586
592
  getattr(lib, symbol)
587
593
  return True
@@ -590,6 +596,7 @@ def scan_directory(scan_root: Path, db_path: Optional[Path] = None) -> Path:
590
596
 
591
597
  # Build candidate search dirs (Linux/macOS)
592
598
  import platform as _platform
599
+
593
600
  sys_name = _platform.system()
594
601
  lib_candidates = []
595
602
  if sys_name == "Linux":
@@ -610,12 +617,16 @@ def scan_directory(scan_root: Path, db_path: Optional[Path] = None) -> Path:
610
617
  "/usr/local/opt/llvm/lib/libclang.dylib",
611
618
  ]
612
619
 
613
- good = [p for p in lib_candidates if Path(p).exists() and _has_symbol(p, "clang_getOffsetOfBase")]
620
+ good = [
621
+ p
622
+ for p in lib_candidates
623
+ if Path(p).exists() and _has_symbol(p, "clang_getOffsetOfBase")
624
+ ]
614
625
  hint = ""
615
626
  if good:
616
627
  hint = f"\n建议的包含所需符号的库:\n export CLANG_LIBRARY_FILE={good[0]}\n然后重新运行: jarvis-c2rust scan -r {scan_root}"
617
628
 
618
- typer.secho(
629
+ PrettyOutput.auto_print(
619
630
  "[c2rust-scanner] 检测到 libclang/python 绑定不匹配 (未定义符号)。"
620
631
  f"\n详情: {msg}"
621
632
  "\n这通常意味着您的 Python 'clang' 绑定版本高于已安装的 libclang。"
@@ -623,14 +634,12 @@ def scan_directory(scan_root: Path, db_path: Optional[Path] = None) -> Path:
623
634
  "- 安装/更新 libclang 以匹配您 Python 'clang' 的主版本 (例如 16-21)。\n"
624
635
  "- 或将 Python 'clang' 版本固定为与系统 libclang 匹配 (例如 pip install 'clang>=16,<22')。\n"
625
636
  "- 或设置 CLANG_LIBRARY_FILE 指向匹配的 libclang 共享库。\n"
626
- f"{hint}",
627
- fg=typer.colors.RED,
628
- err=True,
637
+ f"{hint}"
629
638
  )
630
639
  raise typer.Exit(code=2)
631
640
  else:
632
641
  # Other initialization errors: surface and exit
633
- typer.secho(f"[c2rust-scanner] libclang 初始化失败: {e}", fg=typer.colors.RED, err=True)
642
+ PrettyOutput.auto_print(f"[c2rust-scanner] libclang 初始化失败: {e}")
634
643
  raise typer.Exit(code=2)
635
644
 
636
645
  # compile_commands
@@ -644,7 +653,9 @@ def scan_directory(scan_root: Path, db_path: Optional[Path] = None) -> Path:
644
653
 
645
654
  files = list(iter_source_files(scan_root))
646
655
  total_files = len(files)
647
- print(f"[c2rust-scanner] 正在扫描 {scan_root} 目录下的 {total_files} 个文件")
656
+ PrettyOutput.auto_print(
657
+ f"📋 [c2rust-scanner] 正在扫描 {scan_root} 目录下的 {total_files} 个文件"
658
+ )
648
659
 
649
660
  scanned = 0
650
661
  total_functions = 0
@@ -760,9 +771,11 @@ def scan_directory(scan_root: Path, db_path: Optional[Path] = None) -> Path:
760
771
  # If we hit undefined symbol, it's a libclang/python bindings mismatch; abort with guidance
761
772
  msg = str(e)
762
773
  if "undefined symbol" in msg:
774
+
763
775
  def _has_symbol(lib_path: str, symbol: str) -> bool:
764
776
  try:
765
777
  import ctypes
778
+
766
779
  lib = ctypes.CDLL(lib_path)
767
780
  getattr(lib, symbol)
768
781
  return True
@@ -770,6 +783,7 @@ def scan_directory(scan_root: Path, db_path: Optional[Path] = None) -> Path:
770
783
  return False
771
784
 
772
785
  import platform as _platform
786
+
773
787
  sys_name = _platform.system()
774
788
  lib_candidates2: List[str] = []
775
789
  if sys_name == "Linux":
@@ -786,12 +800,17 @@ def scan_directory(scan_root: Path, db_path: Optional[Path] = None) -> Path:
786
800
  "/usr/local/opt/llvm/lib/libclang.dylib",
787
801
  ]
788
802
 
789
- good = [lp for lp in lib_candidates2 if Path(lp).exists() and _has_symbol(lp, "clang_getOffsetOfBase")]
803
+ good = [
804
+ lp
805
+ for lp in lib_candidates2
806
+ if Path(lp).exists()
807
+ and _has_symbol(lp, "clang_getOffsetOfBase")
808
+ ]
790
809
  hint = ""
791
810
  if good:
792
811
  hint = f"\n建议的包含所需符号的库:\n export CLANG_LIBRARY_FILE={good[0]}\n然后重新运行: jarvis-c2rust scan -r {scan_root}"
793
812
 
794
- typer.secho(
813
+ PrettyOutput.auto_print(
795
814
  "[c2rust-scanner] 解析期间检测到 libclang/python 绑定不匹配 (未定义符号)。"
796
815
  f"\n详情: {msg}"
797
816
  "\n这通常意味着您的 Python 'clang' 绑定版本高于已安装的 libclang。"
@@ -799,9 +818,7 @@ def scan_directory(scan_root: Path, db_path: Optional[Path] = None) -> Path:
799
818
  "- 安装/更新 libclang 以匹配您 Python 'clang' 的主版本 (例如 19/20)。\n"
800
819
  "- 或将 Python 'clang' 版本固定为与系统 libclang 匹配 (例如 pip install 'clang==18.*')。\n"
801
820
  "- 或设置 CLANG_LIBRARY_FILE 指向匹配的 libclang 共享库。\n"
802
- f"{hint}",
803
- fg=typer.colors.RED,
804
- err=True,
821
+ f"{hint}"
805
822
  )
806
823
  raise typer.Exit(code=2)
807
824
 
@@ -809,7 +826,7 @@ def scan_directory(scan_root: Path, db_path: Optional[Path] = None) -> Path:
809
826
  try:
810
827
  funcs = scan_file(cindex, p, [])
811
828
  except Exception:
812
- print(f"[c2rust-scanner] 解析 {p} 失败: {e}", file=sys.stderr)
829
+ PrettyOutput.auto_print(f"[c2rust-scanner] 解析 {p} 失败: {e}")
813
830
  continue
814
831
 
815
832
  # Write JSONL
@@ -840,7 +857,9 @@ def scan_directory(scan_root: Path, db_path: Optional[Path] = None) -> Path:
840
857
 
841
858
  scanned += 1
842
859
  if scanned % 20 == 0 or scanned == total_files:
843
- print(f"[c2rust-scanner] 进度: {scanned}/{total_files} 个文件, {total_functions} 个函数, {total_types} 个类型")
860
+ PrettyOutput.auto_print(
861
+ f"📊 [c2rust-scanner] 进度: {scanned}/{total_files} 个文件, {total_functions} 个函数, {total_types} 个类型"
862
+ )
844
863
  finally:
845
864
  try:
846
865
  f_sym.close()
@@ -857,22 +876,31 @@ def scan_directory(scan_root: Path, db_path: Optional[Path] = None) -> Path:
857
876
  "source_root": str(scan_root),
858
877
  }
859
878
  try:
860
- meta_json.write_text(json.dumps(meta, ensure_ascii=False, indent=2), encoding="utf-8")
879
+ meta_json.write_text(
880
+ json.dumps(meta, ensure_ascii=False, indent=2), encoding="utf-8"
881
+ )
861
882
  except Exception:
862
883
  pass
863
884
 
864
- print(f"[c2rust-scanner] 完成。收集到的函数: {total_functions}, 类型: {total_types}, 符号: {total_functions + total_types}")
865
- print(f"[c2rust-scanner] JSONL 已写入: {symbols_raw_jsonl} (原始符号)")
885
+ PrettyOutput.auto_print(
886
+ f"[c2rust-scanner] 完成。收集到的函数: {total_functions}, 类型: {total_types}, 符号: {total_functions + total_types}"
887
+ )
888
+ PrettyOutput.auto_print(
889
+ f"📊 [c2rust-scanner] JSONL 已写入: {symbols_raw_jsonl} (原始符号)"
890
+ )
866
891
  # 同步生成基线 symbols.jsonl(与 raw 等价),便于后续流程仅基于 symbols.jsonl 运行
867
892
  try:
868
893
  shutil.copy2(symbols_raw_jsonl, symbols_curated_jsonl)
869
- print(f"[c2rust-scanner] JSONL 基线已写入: {symbols_curated_jsonl} (用于后续流程)")
894
+ PrettyOutput.auto_print(
895
+ f"📊 [c2rust-scanner] JSONL 基线已写入: {symbols_curated_jsonl} (用于后续流程)"
896
+ )
870
897
  except Exception as _e:
871
- typer.secho(f"[c2rust-scanner] 生成 symbols.jsonl 失败: {_e}", fg=typer.colors.RED, err=True)
898
+ PrettyOutput.auto_print(f"[c2rust-scanner] 生成 symbols.jsonl 失败: {_e}")
872
899
  raise
873
- print(f"[c2rust-scanner] 元数据已写入: {meta_json}")
900
+ PrettyOutput.auto_print(f"📋 [c2rust-scanner] 元数据已写入: {meta_json}")
874
901
  return symbols_raw_jsonl
875
902
 
903
+
876
904
  # ---------------------------
877
905
  # Type scanning
878
906
  # ---------------------------
@@ -890,18 +918,6 @@ class TypeInfo:
890
918
  language: str
891
919
 
892
920
 
893
-
894
-
895
- TYPE_KINDS: Set[str] = {
896
- "STRUCT_DECL",
897
- "UNION_DECL",
898
- "ENUM_DECL",
899
- "CXX_RECORD_DECL", # C++ class/struct/union
900
- "TYPEDEF_DECL",
901
- "TYPE_ALIAS_DECL",
902
- }
903
-
904
-
905
921
  def scan_types_file(cindex, file_path: Path, args: List[str]) -> List[TypeInfo]:
906
922
  index = cindex.Index.create()
907
923
  tu = index.parse(
@@ -922,7 +938,12 @@ def scan_types_file(cindex, file_path: Path, args: List[str]) -> List[TypeInfo]:
922
938
 
923
939
  if kind in TYPE_KINDS:
924
940
  # Accept full definitions for record/enum; typedef/alias are inherently definitions
925
- need_def = kind in {"STRUCT_DECL", "UNION_DECL", "ENUM_DECL", "CXX_RECORD_DECL"}
941
+ need_def = kind in {
942
+ "STRUCT_DECL",
943
+ "UNION_DECL",
944
+ "ENUM_DECL",
945
+ "CXX_RECORD_DECL",
946
+ }
926
947
  if (not need_def) or node.is_definition():
927
948
  try:
928
949
  name = node.spelling or ""
@@ -1040,8 +1061,6 @@ def generate_dot_from_db(db_path: Path, out_path: Path) -> Path:
1040
1061
  return base
1041
1062
 
1042
1063
  # Prepare output path
1043
- if out_path is None:
1044
- out_path = sjsonl.parent / "global_refgraph.dot"
1045
1064
  out_path = Path(out_path)
1046
1065
  out_path.parent.mkdir(parents=True, exist_ok=True)
1047
1066
 
@@ -1075,6 +1094,7 @@ def find_root_function_ids(db_path: Path) -> List[int]:
1075
1094
  - 严格使用 ref 字段
1076
1095
  - 函数与类型统一处理(不区分)
1077
1096
  """
1097
+
1078
1098
  def _resolve_symbols_jsonl_path(hint: Path) -> Path:
1079
1099
  p = Path(hint)
1080
1100
  if p.is_file() and p.suffix.lower() == ".jsonl":
@@ -1131,7 +1151,9 @@ def find_root_function_ids(db_path: Path) -> List[int]:
1131
1151
  return root_ids
1132
1152
 
1133
1153
 
1134
- def compute_translation_order_jsonl(db_path: Path, out_path: Optional[Path] = None) -> Path:
1154
+ def compute_translation_order_jsonl(
1155
+ db_path: Path, out_path: Optional[Path] = None
1156
+ ) -> Path:
1135
1157
  """
1136
1158
  Compute translation order on reference graph and write order to JSONL.
1137
1159
  Data source: symbols.jsonl (or provided .jsonl path), strictly using ref field and including all symbols.
@@ -1145,6 +1167,7 @@ def compute_translation_order_jsonl(db_path: Path, out_path: Optional[Path] = No
1145
1167
  "created_at": "YYYY-MM-DDTHH:MM:SS"
1146
1168
  }
1147
1169
  """
1170
+
1148
1171
  def _resolve_symbols_jsonl_path(hint: Path) -> Path:
1149
1172
  p = Path(hint)
1150
1173
  if p.is_file() and p.suffix.lower() == ".jsonl":
@@ -1278,6 +1301,7 @@ def compute_translation_order_jsonl(db_path: Path, out_path: Optional[Path] = No
1278
1301
 
1279
1302
  # Kahn on reversed DAG
1280
1303
  from collections import deque
1304
+
1281
1305
  q = deque(sorted([i for i in range(comp_count) if indeg[i] == 0]))
1282
1306
  comp_order: List[int] = []
1283
1307
  while q:
@@ -1322,19 +1346,25 @@ def compute_translation_order_jsonl(db_path: Path, out_path: Optional[Path] = No
1322
1346
  nm = str(meta.get("name") or "").lower()
1323
1347
  qn = str(meta.get("qname") or "").lower()
1324
1348
  # Configurable delayed entry symbols via env:
1325
- # - JARVIS_C2RUST_DELAY_ENTRY_SYMBOLS
1326
- # - JARVIS_C2RUST_DELAY_ENTRIES
1349
+ # - c2rust_delay_entry_symbols
1350
+ # - c2rust_delay_entries
1327
1351
  # - C2RUST_DELAY_ENTRIES
1328
- entries_env = os.environ.get("JARVIS_C2RUST_DELAY_ENTRY_SYMBOLS") or \
1329
- os.environ.get("JARVIS_C2RUST_DELAY_ENTRIES") or \
1330
- os.environ.get("C2RUST_DELAY_ENTRIES") or ""
1352
+ entries_env = (
1353
+ os.environ.get("c2rust_delay_entry_symbols")
1354
+ or os.environ.get("c2rust_delay_entries")
1355
+ or os.environ.get("C2RUST_DELAY_ENTRIES")
1356
+ or ""
1357
+ )
1331
1358
  entries_set = set()
1332
1359
  if entries_env:
1333
1360
  try:
1334
1361
  import re as _re
1362
+
1335
1363
  parts = _re.split(r"[,\s;]+", entries_env.strip())
1336
1364
  except Exception:
1337
- parts = [p.strip() for p in entries_env.replace(";", ",").split(",")]
1365
+ parts = [
1366
+ p.strip() for p in entries_env.replace(";", ",").split(",")
1367
+ ]
1338
1368
  entries_set = {p.strip().lower() for p in parts if p and p.strip()}
1339
1369
  # If configured, use the provided entries; otherwise fallback to default 'main'
1340
1370
  if entries_set:
@@ -1373,17 +1403,25 @@ def compute_translation_order_jsonl(db_path: Path, out_path: Optional[Path] = No
1373
1403
  roots_labels = []
1374
1404
  if root_id is not None:
1375
1405
  meta_r = by_id.get(root_id, {})
1376
- rlabel = meta_r.get("qname") or meta_r.get("name") or f"sym_{root_id}"
1406
+ rlabel = (
1407
+ meta_r.get("qname") or meta_r.get("name") or f"sym_{root_id}"
1408
+ )
1377
1409
  roots_labels = [rlabel]
1378
- steps.append({
1379
- "step": len(steps) + 1,
1380
- "ids": sorted(selected),
1381
- "items": [by_id.get(nid, {}).get("record") for nid in sorted(selected) if isinstance(by_id.get(nid, {}).get("record"), dict)],
1382
- "symbols": syms,
1383
- "group": len(syms) > 1,
1384
- "roots": roots_labels,
1385
- "created_at": now_ts,
1386
- })
1410
+ steps.append(
1411
+ {
1412
+ "step": len(steps) + 1,
1413
+ "ids": sorted(selected),
1414
+ "items": [
1415
+ by_id.get(nid, {}).get("record")
1416
+ for nid in sorted(selected)
1417
+ if isinstance(by_id.get(nid, {}).get("record"), dict)
1418
+ ],
1419
+ "symbols": syms,
1420
+ "group": len(syms) > 1,
1421
+ "roots": roots_labels,
1422
+ "created_at": now_ts,
1423
+ }
1424
+ )
1387
1425
 
1388
1426
  # Emit delayed entry functions as the final step for this root
1389
1427
  if delayed_entries:
@@ -1399,15 +1437,21 @@ def compute_translation_order_jsonl(db_path: Path, out_path: Optional[Path] = No
1399
1437
  meta_r = by_id.get(root_id, {})
1400
1438
  rlabel = meta_r.get("qname") or meta_r.get("name") or f"sym_{root_id}"
1401
1439
  roots_labels = [rlabel]
1402
- steps.append({
1403
- "step": len(steps) + 1,
1404
- "ids": sorted(delayed_entries),
1405
- "items": [by_id.get(nid, {}).get("record") for nid in sorted(delayed_entries) if isinstance(by_id.get(nid, {}).get("record"), dict)],
1406
- "symbols": syms,
1407
- "group": len(syms) > 1,
1408
- "roots": roots_labels,
1409
- "created_at": now_ts,
1410
- })
1440
+ steps.append(
1441
+ {
1442
+ "step": len(steps) + 1,
1443
+ "ids": sorted(delayed_entries),
1444
+ "items": [
1445
+ by_id.get(nid, {}).get("record")
1446
+ for nid in sorted(delayed_entries)
1447
+ if isinstance(by_id.get(nid, {}).get("record"), dict)
1448
+ ],
1449
+ "symbols": syms,
1450
+ "group": len(syms) > 1,
1451
+ "roots": roots_labels,
1452
+ "created_at": now_ts,
1453
+ }
1454
+ )
1411
1455
 
1412
1456
  for rid in sorted(roots, key=lambda r: len(root_reach.get(r, set())), reverse=True):
1413
1457
  _emit_for_root(rid)
@@ -1430,7 +1474,9 @@ def compute_translation_order_jsonl(db_path: Path, out_path: Optional[Path] = No
1430
1474
  # Purge redundant fields before writing (keep ids and records; drop symbols/items)
1431
1475
  try:
1432
1476
  # 保留 items(包含完整符号记录及替换信息),仅移除冗余的 symbols 文本标签
1433
- steps = [dict((k, v) for k, v in st.items() if k not in ("symbols",)) for st in steps]
1477
+ steps = [
1478
+ dict((k, v) for k, v in st.items() if k not in ("symbols",)) for st in steps
1479
+ ]
1434
1480
  except Exception:
1435
1481
  pass
1436
1482
  with open(out_path, "w", encoding="utf-8") as fo:
@@ -1502,7 +1548,9 @@ def export_root_subgraphs_to_dir(db_path: Path, out_dir: Path) -> List[Path]:
1502
1548
  if not s:
1503
1549
  return "root"
1504
1550
  s = s.replace("::", "__")
1505
- return "".join(ch if ch.isalnum() or ch in ("_", "-") else "_" for ch in s)[:120]
1551
+ return "".join(ch if ch.isalnum() or ch in ("_", "-") else "_" for ch in s)[
1552
+ :120
1553
+ ]
1506
1554
 
1507
1555
  generated: List[Path] = []
1508
1556
  root_ids = find_root_function_ids(db_path)
@@ -1549,7 +1597,11 @@ def export_root_subgraphs_to_dir(db_path: Path, out_dir: Path) -> List[Path]:
1549
1597
  edges.add((src_node, dst))
1550
1598
 
1551
1599
  # Write DOT
1552
- root_base = by_id.get(rid, {}).get("qname") or by_id.get(rid, {}).get("name") or f"sym_{rid}"
1600
+ root_base = (
1601
+ by_id.get(rid, {}).get("qname")
1602
+ or by_id.get(rid, {}).get("name")
1603
+ or f"sym_{rid}"
1604
+ )
1553
1605
  fname = f"subgraph_root_{rid}_{sanitize_filename(root_base)}.dot"
1554
1606
  out_path = out_dir / fname
1555
1607
  with open(out_path, "w", encoding="utf-8") as f:
@@ -1563,7 +1615,9 @@ def export_root_subgraphs_to_dir(db_path: Path, out_dir: Path) -> List[Path]:
1563
1615
  for nid, lbl in node_labels.items():
1564
1616
  safe_label = lbl.replace("\\", "\\\\").replace('"', '\\"')
1565
1617
  if nid.startswith("ext"):
1566
- f.write(f' {nid} [label="{safe_label}", shape=ellipse, style=dashed, color=gray50, fontcolor=gray30];\n')
1618
+ f.write(
1619
+ f' {nid} [label="{safe_label}", shape=ellipse, style=dashed, color=gray50, fontcolor=gray30];\n'
1620
+ )
1567
1621
  else:
1568
1622
  f.write(f' {nid} [label="{safe_label}", shape=box];\n')
1569
1623
 
@@ -1582,6 +1636,7 @@ def export_root_subgraphs_to_dir(db_path: Path, out_dir: Path) -> List[Path]:
1582
1636
  # Third-party replacement evaluation
1583
1637
  # ---------------------------
1584
1638
 
1639
+
1585
1640
  def run_scan(
1586
1641
  dot: Optional[Path] = None,
1587
1642
  only_dot: bool = False,
@@ -1592,20 +1647,22 @@ def run_scan(
1592
1647
  ) -> None:
1593
1648
  # Scan for C/C++ functions and persist results to JSONL; optionally generate DOT.
1594
1649
  # Determine data path
1595
- root = Path('.')
1596
- Path('.') / ".jarvis" / "c2rust" / "symbols_raw.jsonl"
1597
- data_path_curated = Path('.') / ".jarvis" / "c2rust" / "symbols.jsonl"
1650
+ root = Path(".")
1651
+ Path(".") / ".jarvis" / "c2rust" / "symbols_raw.jsonl"
1652
+ data_path_curated = Path(".") / ".jarvis" / "c2rust" / "symbols.jsonl"
1598
1653
 
1599
1654
  # Helper: render a DOT file to PNG using Graphviz 'dot'
1600
1655
  def _render_dot_to_png(dot_file: Path, png_out: Optional[Path] = None) -> Path:
1601
1656
  try:
1602
- from shutil import which
1603
1657
  import subprocess
1658
+ from shutil import which
1604
1659
  except Exception as _e:
1605
1660
  raise RuntimeError(f"准备 PNG 渲染时出现环境问题: {_e}")
1606
1661
  exe = which("dot")
1607
1662
  if not exe:
1608
- raise RuntimeError("在 PATH 中未找到 Graphviz 'dot'。请安装 graphviz 并确保 'dot' 可用。")
1663
+ raise RuntimeError(
1664
+ "在 PATH 中未找到 Graphviz 'dot'。请安装 graphviz 并确保 'dot' 可用。"
1665
+ )
1609
1666
  dot_file = Path(dot_file)
1610
1667
  if png_out is None:
1611
1668
  png_out = dot_file.with_suffix(".png")
@@ -1613,7 +1670,9 @@ def run_scan(
1613
1670
  png_out = Path(png_out)
1614
1671
  png_out.parent.mkdir(parents=True, exist_ok=True)
1615
1672
  try:
1616
- subprocess.run([exe, "-Tpng", str(dot_file), "-o", str(png_out)], check=True)
1673
+ subprocess.run(
1674
+ [exe, "-Tpng", str(dot_file), "-o", str(png_out)], check=True
1675
+ )
1617
1676
  except FileNotFoundError:
1618
1677
  raise RuntimeError("未找到 Graphviz 'dot' 可执行文件。")
1619
1678
  except subprocess.CalledProcessError as e:
@@ -1624,18 +1683,24 @@ def run_scan(
1624
1683
  try:
1625
1684
  scan_directory(root)
1626
1685
  except Exception as e:
1627
- typer.secho(f"[c2rust-scanner] 错误: {e}", fg=typer.colors.RED, err=True)
1686
+ PrettyOutput.auto_print(f"[c2rust-scanner] 错误: {e}")
1628
1687
  raise typer.Exit(code=1)
1629
1688
  else:
1630
1689
  # Only-generate mode (no rescan). 验证输入,仅基于既有 symbols.jsonl 进行可选的 DOT/子图输出;此处不计算翻译顺序。
1631
1690
  if not data_path_curated.exists():
1632
- typer.secho(f"[c2rust-scanner] 未找到数据: {data_path_curated}", fg=typer.colors.RED, err=True)
1691
+ PrettyOutput.auto_print(
1692
+ f"⚠️ [c2rust-scanner] 未找到数据: {data_path_curated}"
1693
+ )
1633
1694
  raise typer.Exit(code=2)
1634
1695
  if only_dot and dot is None:
1635
- typer.secho("[c2rust-scanner] --only-dot 需要 --dot 来指定输出文件", fg=typer.colors.RED, err=True)
1696
+ PrettyOutput.auto_print(
1697
+ "⚠️ [c2rust-scanner] --only-dot 需要 --dot 来指定输出文件"
1698
+ )
1636
1699
  raise typer.Exit(code=2)
1637
1700
  if only_subgraphs and subgraphs_dir is None:
1638
- typer.secho("[c2rust-scanner] --only-subgraphs 需要 --subgraphs-dir 来指定输出目录", fg=typer.colors.RED, err=True)
1701
+ PrettyOutput.auto_print(
1702
+ "⚠️ [c2rust-scanner] --only-subgraphs 需要 --subgraphs-dir 来指定输出目录"
1703
+ )
1639
1704
  raise typer.Exit(code=2)
1640
1705
 
1641
1706
  # Generate DOT (global) if requested
@@ -1643,12 +1708,14 @@ def run_scan(
1643
1708
  try:
1644
1709
  # 使用正式符号表生成可视化
1645
1710
  generate_dot_from_db(data_path_curated, dot)
1646
- typer.secho(f"[c2rust-scanner] DOT 文件已写入: {dot}", fg=typer.colors.GREEN)
1711
+ PrettyOutput.auto_print(f"📊 [c2rust-scanner] DOT 文件已写入: {dot}")
1647
1712
  if png:
1648
1713
  png_path = _render_dot_to_png(dot)
1649
- typer.secho(f"[c2rust-scanner] PNG 文件已写入: {png_path}", fg=typer.colors.GREEN)
1714
+ PrettyOutput.auto_print(
1715
+ f"📊 [c2rust-scanner] PNG 文件已写入: {png_path}"
1716
+ )
1650
1717
  except Exception as e:
1651
- typer.secho(f"[c2rust-scanner] 写入 DOT/PNG 失败: {e}", fg=typer.colors.RED, err=True)
1718
+ PrettyOutput.auto_print(f"[c2rust-scanner] 写入 DOT/PNG 失败: {e}")
1652
1719
  raise typer.Exit(code=1)
1653
1720
 
1654
1721
  # Generate per-root subgraphs if requested
@@ -1665,17 +1732,13 @@ def run_scan(
1665
1732
  except Exception as _e:
1666
1733
  # Fail fast on PNG generation error for subgraphs to make issues visible
1667
1734
  raise
1668
- typer.secho(
1669
- f"[c2rust-scanner] 根节点子图已写入: {len(files)} 个 DOT 文件和 {png_count} 个 PNG 文件 -> {subgraphs_dir}",
1670
- fg=typer.colors.GREEN,
1735
+ PrettyOutput.auto_print(
1736
+ f"📊 [c2rust-scanner] 根节点子图已写入: {len(files)} 个 DOT 文件和 {png_count} 个 PNG 文件 -> {subgraphs_dir}"
1671
1737
  )
1672
1738
  else:
1673
- typer.secho(
1674
- f"[c2rust-scanner] 根节点子图已写入: {len(files)} 个文件 -> {subgraphs_dir}",
1675
- fg=typer.colors.GREEN,
1739
+ PrettyOutput.auto_print(
1740
+ f"📊 [c2rust-scanner] 根节点子图已写入: {len(files)} 个文件 -> {subgraphs_dir}"
1676
1741
  )
1677
1742
  except Exception as e:
1678
- typer.secho(f"[c2rust-scanner] 写入子图 DOT/PNG 失败: {e}", fg=typer.colors.RED, err=True)
1743
+ PrettyOutput.auto_print(f"[c2rust-scanner] 写入子图 DOT/PNG 失败: {e}")
1679
1744
  raise typer.Exit(code=1)
1680
-
1681
-