jarvis-ai-assistant 0.7.16__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.16.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.16.dist-info/RECORD +0 -218
  276. {jarvis_ai_assistant-0.7.16.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/WHEEL +0 -0
  277. {jarvis_ai_assistant-0.7.16.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/entry_points.txt +0 -0
  278. {jarvis_ai_assistant-0.7.16.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/licenses/LICENSE +0 -0
  279. {jarvis_ai_assistant-0.7.16.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,170 @@
1
+ # -*- coding: utf-8 -*-
2
+ """LLM 模块规划 Agent 的项目结构应用模块。"""
3
+
4
+ import re
5
+ from pathlib import Path
6
+ from typing import Any
7
+ from typing import Dict
8
+ from typing import List
9
+ from typing import Set
10
+ from typing import Union
11
+
12
+ from jarvis.jarvis_c2rust.llm_module_agent_utils import parse_project_json_entries
13
+
14
+
15
+ def ensure_pub_mod_declarations(existing_text: str, child_mods: List[str]) -> str:
16
+ """
17
+ 在给定文本中确保存在并升级子模块声明为 `pub mod <name>;`:
18
+ - 解析已有的 `mod`/`pub mod`/`pub(...) mod` 声明;
19
+ - 已存在但非 pub 的同名声明就地升级为 `pub mod`,保留原行的缩进;
20
+ - 不存在的模块名则在末尾追加一行 `pub mod <name>;`;
21
+ - 返回更新后的完整文本(保留结尾换行)。
22
+ """
23
+ try:
24
+ lines = (existing_text or "").splitlines()
25
+ except Exception:
26
+ lines = []
27
+ mod_decl_pattern = re.compile(
28
+ r"^\s*(pub(?:\s*\([^)]+\))?\s+)?mod\s+([A-Za-z_][A-Za-z0-9_]*)\s*;\s*$"
29
+ )
30
+ name_to_indices: Dict[str, List[int]] = {}
31
+ name_has_pub: Set[str] = set()
32
+ for i, ln in enumerate(lines):
33
+ m = mod_decl_pattern.match(ln.strip())
34
+ if not m:
35
+ continue
36
+ mod_name = m.group(2)
37
+ name_to_indices.setdefault(mod_name, []).append(i)
38
+ if m.group(1):
39
+ name_has_pub.add(mod_name)
40
+ for mod_name in sorted(set(child_mods or [])):
41
+ if mod_name in name_to_indices:
42
+ if mod_name not in name_has_pub:
43
+ for idx in name_to_indices[mod_name]:
44
+ ws_match = re.match(r"^(\s*)", lines[idx])
45
+ leading_ws = ws_match.group(1) if ws_match else ""
46
+ lines[idx] = f"{leading_ws}pub mod {mod_name};"
47
+ else:
48
+ lines.append(f"pub mod {mod_name};")
49
+ return "\n".join(lines).rstrip() + ("\n" if lines else "")
50
+
51
+
52
+ def apply_entries_with_mods(entries: List[Any], base_path: Path) -> None:
53
+ """
54
+ 根据解析出的 entries 创建目录与文件结构(不在此阶段写入/更新任何 Rust 源文件内容):
55
+ - 对于目录项:创建目录,并递归创建其子项;
56
+ - 对于文件项:若不存在则创建空文件;
57
+ 约束与约定:
58
+ - crate 根的 src 目录:不生成 src/mod.rs,也不写入 src/lib.rs 的模块声明;
59
+ - 非 src 目录:不创建或更新 mod.rs;如需创建 mod.rs,请在 YAML 中显式列出;
60
+ - 模块声明的补齐将在后续 CodeAgent 阶段完成(扫描目录结构并最小化补齐 pub mod 声明)。
61
+ """
62
+
63
+ def apply_item(item: Any, dir_path: Path) -> None:
64
+ if isinstance(item, str):
65
+ # 文件
66
+ file_path = dir_path / item
67
+ file_path.parent.mkdir(parents=True, exist_ok=True)
68
+ if not file_path.exists():
69
+ try:
70
+ file_path.touch(exist_ok=True)
71
+ except Exception:
72
+ pass
73
+ return
74
+
75
+ if isinstance(item, dict) and len(item) == 1:
76
+ dir_name, children = next(iter(item.items()))
77
+ name = str(dir_name).rstrip("/").strip()
78
+ new_dir = dir_path / name
79
+ new_dir.mkdir(parents=True, exist_ok=True)
80
+
81
+ child_mods: List[str] = []
82
+ # 是否为 crate 根下的 src 目录
83
+ is_src_root_dir = new_dir == base_path / "src"
84
+
85
+ # 先创建子项
86
+ for child in children or []:
87
+ if isinstance(child, str):
88
+ apply_item(child, new_dir)
89
+ # 收集 .rs 文件作为子模块
90
+ if child.endswith(".rs") and child != "mod.rs":
91
+ stem = Path(child).stem
92
+ # 在 src 根目录下,忽略 lib.rs 与 main.rs 的自引用
93
+ if is_src_root_dir and stem in ("lib", "main"):
94
+ pass
95
+ else:
96
+ child_mods.append(stem)
97
+ if child == "mod.rs":
98
+ pass
99
+ elif isinstance(child, dict):
100
+ # 子目录
101
+ sub_name = list(child.keys())[0]
102
+ sub_mod_name = str(sub_name).rstrip("/").strip()
103
+ child_mods.append(sub_mod_name)
104
+ apply_item(child, new_dir)
105
+
106
+ # 对 crate 根的 src 目录,使用 lib.rs 聚合子模块,不创建/更新 src/mod.rs
107
+ if is_src_root_dir:
108
+ # 不在 src 根目录写入任何文件内容;仅由子项创建对应空文件(如有)
109
+ return
110
+
111
+ # 非 src 目录:
112
+ # 为避免覆盖现有实现,当前阶段不创建或更新 mod.rs 内容。
113
+ # 如需创建 mod.rs,应在 JSON 中显式指定为文件项;
114
+ # 如需补齐模块声明,将由后续的 CodeAgent 阶段根据目录结构自动补齐。
115
+ return
116
+
117
+ for entry in entries:
118
+ apply_item(entry, base_path)
119
+
120
+
121
+ def ensure_cargo_toml(base_dir: Path, package_name: str) -> None:
122
+ """
123
+ 确保在 base_dir 下存在合理的 Cargo.toml:
124
+ - 如果不存在,则创建最小可用的 Cargo.toml,并设置 package.name = package_name
125
+ - 如果已存在,则不覆盖现有内容(避免误改)
126
+ """
127
+ cargo_path = base_dir / "Cargo.toml"
128
+ if cargo_path.exists():
129
+ return
130
+ try:
131
+ cargo_path.touch(exist_ok=True)
132
+ except (OSError, PermissionError):
133
+ # 如果无法创建文件,记录错误但不中断流程
134
+ # 后续 CodeAgent 可能会处理 Cargo.toml 的创建
135
+ pass
136
+
137
+
138
+ def apply_project_structure_from_json(
139
+ json_text: str, project_root: Union[Path, str] = "."
140
+ ) -> None:
141
+ """
142
+ 基于 Agent 返回的 <PROJECT> 中的目录结构 JSON,创建实际目录与文件(不在此阶段写入或更新任何 Rust 源文件内容)。
143
+ - project_root: 目标应用路径;当为 "."(默认)时,将使用"父目录/当前目录名_rs"作为crate根目录
144
+ 注意:模块声明(mod/pub mod)补齐将在后续的 CodeAgent 步骤中完成。按新策略不再创建或更新 workspace(构建直接在 crate 目录内进行)。
145
+ """
146
+ entries, parse_error = parse_project_json_entries(json_text)
147
+ if parse_error:
148
+ raise ValueError(f"JSON解析失败: {parse_error}")
149
+ if not entries:
150
+ # 严格模式:解析失败直接报错并退出,由上层 CLI 捕获打印错误
151
+ raise ValueError("[c2rust-llm-planner] 从LLM输出解析目录结构失败。正在中止。")
152
+ requested_root = Path(project_root).resolve()
153
+ try:
154
+ cwd = Path(".").resolve()
155
+ if requested_root == cwd:
156
+ # 默认crate不能设置为 .,设置为 父目录/当前目录名_rs(与当前目录同级)
157
+ base_dir = cwd.parent / f"{cwd.name}_rs"
158
+ else:
159
+ base_dir = requested_root
160
+ except Exception:
161
+ base_dir = requested_root
162
+ base_dir.mkdir(parents=True, exist_ok=True)
163
+ # crate name 与目录名保持一致(用于 Cargo 包名,允许连字符)
164
+ crate_pkg_name = base_dir.name
165
+ apply_entries_with_mods(entries, base_dir)
166
+ # 确保 Cargo.toml 存在并设置包名
167
+ ensure_cargo_toml(base_dir, crate_pkg_name)
168
+
169
+ # 已弃用:不再将 crate 添加到 workspace(按新策略去除 workspace)
170
+ # 构建与工具运行将直接在 crate 目录内进行
@@ -0,0 +1,288 @@
1
+ """LLM 模块规划 Agent 的执行器。"""
2
+
3
+ import os
4
+ import subprocess
5
+
6
+ from jarvis.jarvis_utils.output import PrettyOutput
7
+
8
+ # -*- coding: utf-8 -*-
9
+ from pathlib import Path
10
+ from typing import List
11
+ from typing import Optional
12
+ from typing import Union
13
+
14
+ from jarvis.jarvis_c2rust.llm_module_agent_apply import (
15
+ apply_project_structure_from_json,
16
+ )
17
+ from jarvis.jarvis_c2rust.llm_module_agent_utils import parse_project_json_entries
18
+ from jarvis.jarvis_c2rust.llm_module_agent_utils import resolve_created_dir
19
+
20
+
21
+ def execute_llm_plan(
22
+ out: Optional[Union[Path, str]] = None,
23
+ apply: bool = False,
24
+ crate_name: Optional[Union[Path, str]] = None,
25
+ llm_group: Optional[str] = None,
26
+ non_interactive: bool = True,
27
+ ) -> List:
28
+ """
29
+ 返回 LLM 生成的目录结构原始 JSON 文本(来自 <PROJECT> 块)。
30
+ 不进行解析,便于后续按原样应用并在需要时使用更健壮的解析器处理。
31
+ """
32
+ # execute_llm_plan 是顶层入口,需要执行清理(skip_cleanup=False)
33
+ # 延迟导入以避免循环依赖
34
+ from jarvis.jarvis_c2rust.llm_module_agent import plan_crate_json_text
35
+
36
+ json_text = plan_crate_json_text(llm_group=llm_group, skip_cleanup=False)
37
+ entries, parse_error = parse_project_json_entries(json_text)
38
+ if parse_error:
39
+ raise ValueError(f"JSON解析失败: {parse_error}")
40
+ if not entries:
41
+ raise ValueError("[c2rust-llm-planner] 从LLM输出解析目录结构失败。正在中止。")
42
+
43
+ # 2) 如需应用到磁盘
44
+ if apply:
45
+ target_root = crate_name if crate_name else "."
46
+ try:
47
+ apply_project_structure_from_json(json_text, project_root=target_root)
48
+ PrettyOutput.auto_print("[c2rust-llm-planner] 项目结构已应用。")
49
+ except Exception as e:
50
+ PrettyOutput.auto_print(f"[c2rust-llm-planner] 应用项目结构失败: {e}")
51
+ raise
52
+
53
+ # Post-apply: 检查生成的目录结构,使用 CodeAgent 更新 Cargo.toml
54
+ from jarvis.jarvis_code_agent.code_agent import (
55
+ CodeAgent,
56
+ ) # 延迟导入以避免全局耦合
57
+
58
+ # 解析 crate 目录路径(与 apply 逻辑保持一致)
59
+ try:
60
+ created_dir = resolve_created_dir(target_root)
61
+ except Exception:
62
+ # 兜底:无法解析时直接使用传入的 target_root
63
+ created_dir = Path(target_root)
64
+
65
+ # 在 crate 目录内执行 git 初始化与初始提交(按新策略)
66
+ try:
67
+ # 初始化 git 仓库(若已存在则该命令为幂等)
68
+ subprocess.run(["git", "init"], check=False, cwd=str(created_dir))
69
+ # 添加所有文件并尝试提交
70
+ subprocess.run(["git", "add", "-A"], check=False, cwd=str(created_dir))
71
+ subprocess.run(
72
+ ["git", "commit", "-m", "[c2rust-llm-planner] init crate"],
73
+ check=False,
74
+ cwd=str(created_dir),
75
+ )
76
+ except Exception:
77
+ # 保持稳健,不因 git 失败影响主流程
78
+ pass
79
+
80
+ # 构建用于 CodeAgent 的目录上下文(简化版树形)
81
+ def _format_tree(root: Path) -> str:
82
+ lines: List[str] = []
83
+ exclude = {".git", "target", ".jarvis"}
84
+ if not root.exists():
85
+ return ""
86
+ for p in sorted(root.rglob("*")):
87
+ if any(part in exclude for part in p.parts):
88
+ continue
89
+ rel = p.relative_to(root)
90
+ depth = len(rel.parts) - 1
91
+ indent = " " * depth
92
+ name = rel.name + ("/" if p.is_dir() else "")
93
+ lines.append(f"{indent}- {name}")
94
+ return "\n".join(lines)
95
+
96
+ dir_ctx = _format_tree(created_dir)
97
+ crate_pkg_name = created_dir.name
98
+
99
+ requirement_lines = [
100
+ "目标:在该 crate 目录下确保 `cargo build` 能成功完成;如失败则根据错误最小化修改并重试,直到构建通过为止。",
101
+ f"- crate_dir: {created_dir}",
102
+ f"- crate_name: {crate_pkg_name}",
103
+ "目录结构(部分):",
104
+ dir_ctx,
105
+ "",
106
+ "执行与修复流程(务必按序执行,可多轮迭代):",
107
+ "1) 先补齐 Rust 模块声明(仅最小化追加/升级,不覆盖业务实现):",
108
+ " - 扫描 src 目录:",
109
+ " * 在每个子目录下(除 src 根)创建或更新 mod.rs,仅追加缺失的 `pub mod <child>;` 声明;",
110
+ " * 在 src/lib.rs 中为顶级子模块追加 `pub mod <name>;`;不要创建 src/mod.rs;忽略 lib.rs 与 main.rs 的自引用;",
111
+ " - 若存在 `mod <name>;` 但非 pub,则就地升级为 `pub mod <name>;`,保留原缩进与其他内容;",
112
+ " - 严禁删除现有声明或修改非声明代码;",
113
+ '2) 在 Cargo.toml 的 [package] 中设置 edition:"2024";若本地工具链不支持 2024,请降级为 "2021" 并在说明中记录原因;保留其他已有字段与依赖不变。',
114
+ "3) 根据当前源代码实际情况配置入口:",
115
+ " - 仅库:仅配置 [lib](path=src/lib.rs),不要生成 main.rs;",
116
+ " - 单一可执行:存在 src/main.rs 时配置 [[bin]] 或默认二进制;可选保留 [lib] 以沉淀共享逻辑;",
117
+ " - 多可执行:为每个 src/bin/<name>.rs 配置 [[bin]];共享代码放在 src/lib.rs;",
118
+ " - 不要创建与目录结构不一致的占位入口。",
119
+ "4) 对被作为入口的源文件:若不存在 fn main() 则仅添加最小可用实现(不要改动已存在的实现):",
120
+ ' fn main() { println!("ok"); }',
121
+ "5) 执行一次构建验证:`cargo build -q`(或 `cargo check -q`)。",
122
+ "6) 若构建失败,读取错误并进行最小化修复,然后再次构建;重复直至成功。仅允许的修复类型:",
123
+ " - 依赖缺失:在 [dependencies] 中添加必要且稳定版本的依赖(优先无特性),避免新增未使用依赖;",
124
+ " - 入口/crate-type 配置错误:修正 [lib] 或 [[bin]] 的 name/path/crate-type 使之与目录与入口文件一致;",
125
+ " - 语言/工具链不兼容:将 edition 从 2024 调整为 2021;必要时可添加 rust-version 要求;",
126
+ " - 语法级/最小实现缺失:仅在入口文件中补充必要的 use/空实现/feature gate 以通过编译,避免改动非入口业务文件;",
127
+ " - 不要删除或移动现有文件与目录。",
128
+ "7) 每轮修改后必须运行 `cargo build -q` 验证,直到构建成功为止。",
129
+ "",
130
+ "修改约束:",
131
+ "- 允许修改的文件范围:Cargo.toml、src/lib.rs、src/main.rs、src/bin/*.rs、src/**/mod.rs(仅最小必要变更);除非为修复构建与模块声明补齐,不要修改其他文件。",
132
+ "- 尽量保持现有内容与结构不变,不要引入与构建无关的改动或格式化。",
133
+ "",
134
+ "交付要求:",
135
+ "- 以补丁方式提交实际修改的文件;",
136
+ "- 在最终回复中简要说明所做变更与最终 `cargo build` 的结果(成功/失败及原因)。",
137
+ ]
138
+ requirement_text = "\n".join(requirement_lines)
139
+
140
+ prev_cwd = os.getcwd()
141
+ try:
142
+ # 切换到 crate 目录运行 CodeAgent 与构建
143
+ os.chdir(str(created_dir))
144
+ PrettyOutput.auto_print(
145
+ f"[c2rust-llm-planner] 已切换到 crate 目录: {os.getcwd()},执行 CodeAgent 初始化"
146
+ )
147
+ if llm_group:
148
+ PrettyOutput.auto_print(f"[c2rust-llm-planner] 使用模型组: {llm_group}")
149
+ try:
150
+ # 验证模型配置在切换目录后是否仍然有效
151
+ from jarvis.jarvis_utils.config import get_normal_model_name
152
+ from jarvis.jarvis_utils.config import get_normal_platform_name
153
+
154
+ if llm_group:
155
+ resolved_model = get_normal_model_name(llm_group)
156
+ resolved_platform = get_normal_platform_name(llm_group)
157
+ PrettyOutput.auto_print(
158
+ f"[c2rust-llm-planner] 解析的模型配置: 平台={resolved_platform}, 模型={resolved_model}"
159
+ )
160
+ except Exception as e:
161
+ PrettyOutput.auto_print(
162
+ f"[c2rust-llm-planner] 警告: 无法验证模型配置: {e}"
163
+ )
164
+
165
+ try:
166
+ agent = CodeAgent(
167
+ need_summary=False,
168
+ non_interactive=non_interactive,
169
+ model_group=llm_group,
170
+ enable_task_list_manager=False,
171
+ disable_review=True,
172
+ )
173
+ # 验证 agent 内部的模型配置
174
+ if hasattr(agent, "model") and agent.model:
175
+ actual_model = getattr(agent.model, "model_name", "unknown")
176
+ actual_platform = type(agent.model).__name__
177
+ PrettyOutput.auto_print(
178
+ f"[c2rust-llm-planner] CodeAgent 内部模型: {actual_platform}.{actual_model}"
179
+ )
180
+ agent.run(requirement_text, prefix="[c2rust-llm-planner]", suffix="")
181
+ PrettyOutput.auto_print(
182
+ "[c2rust-llm-planner] 初始 CodeAgent 运行完成。"
183
+ )
184
+ except Exception as e:
185
+ error_msg = str(e)
186
+ if "does not exist" in error_msg or "404" in error_msg:
187
+ PrettyOutput.auto_print(
188
+ f"[c2rust-llm-planner] 模型配置错误: {error_msg}"
189
+ )
190
+ PrettyOutput.auto_print(
191
+ f"[c2rust-llm-planner] 提示: 请检查模型组 '{llm_group}' 的配置是否正确"
192
+ )
193
+ PrettyOutput.auto_print(
194
+ f"[c2rust-llm-planner] 当前工作目录: {os.getcwd()}"
195
+ )
196
+ # 尝试显示当前解析的模型配置
197
+ try:
198
+ from jarvis.jarvis_utils.config import get_normal_model_name
199
+ from jarvis.jarvis_utils.config import get_normal_platform_name
200
+
201
+ if llm_group:
202
+ PrettyOutput.auto_print(
203
+ f"[c2rust-llm-planner] 当前解析的模型: {get_normal_platform_name(llm_group)}/{get_normal_model_name(llm_group)}"
204
+ )
205
+ except Exception:
206
+ pass
207
+ raise
208
+
209
+ # 进入构建与修复循环:构建失败则生成新的 CodeAgent,携带错误上下文进行最小修复
210
+ iter_count = 0
211
+ while True:
212
+ iter_count += 1
213
+ PrettyOutput.auto_print(
214
+ f"[c2rust-llm-planner] 在 {os.getcwd()} 执行: cargo build -q"
215
+ )
216
+ build_res = subprocess.run(
217
+ ["cargo", "build", "-q"],
218
+ capture_output=True,
219
+ text=True,
220
+ check=False,
221
+ )
222
+ stdout = build_res.stdout or ""
223
+ stderr = build_res.stderr or ""
224
+ output = (stdout + "\n" + stderr).strip()
225
+
226
+ if build_res.returncode == 0:
227
+ PrettyOutput.auto_print("[c2rust-llm-planner] Cargo 构建成功。")
228
+ break
229
+
230
+ PrettyOutput.auto_print(
231
+ f"[c2rust-llm-planner] Cargo 构建失败 (iter={iter_count})。"
232
+ )
233
+ # 打印编译错误输出,便于可视化与调试
234
+ PrettyOutput.auto_print("[c2rust-llm-planner] 构建错误输出:")
235
+ PrettyOutput.auto_print(output)
236
+ # 将错误信息作为上下文,附加修复原则,生成新的 CodeAgent 进行最小修复
237
+ repair_prompt = "\n".join(
238
+ [
239
+ requirement_text,
240
+ "",
241
+ "请根据以下构建错误进行最小化修复,然后再次执行 `cargo build` 验证:",
242
+ "<BUILD_ERROR>",
243
+ output,
244
+ "</BUILD_ERROR>",
245
+ ]
246
+ )
247
+
248
+ if llm_group:
249
+ PrettyOutput.auto_print(
250
+ f"[c2rust-llm-planner][iter={iter_count}] 使用模型组: {llm_group}"
251
+ )
252
+ try:
253
+ repair_agent = CodeAgent(
254
+ need_summary=False,
255
+ non_interactive=non_interactive,
256
+ model_group=llm_group,
257
+ enable_task_list_manager=False,
258
+ disable_review=True,
259
+ )
260
+ repair_agent.run(
261
+ repair_prompt,
262
+ prefix=f"[c2rust-llm-planner][iter={iter_count}]",
263
+ suffix="",
264
+ )
265
+ except Exception as e:
266
+ error_msg = str(e)
267
+ if "does not exist" in error_msg or "404" in error_msg:
268
+ PrettyOutput.auto_print(
269
+ f"[c2rust-llm-planner][iter={iter_count}] 模型配置错误: {error_msg}"
270
+ )
271
+ PrettyOutput.auto_print(
272
+ f"[c2rust-llm-planner][iter={iter_count}] 提示: 请检查模型组 '{llm_group}' 的配置"
273
+ )
274
+ raise
275
+ # 不切换目录,保持在原始工作目录
276
+ finally:
277
+ # 恢复之前的工作目录
278
+ os.chdir(prev_cwd)
279
+
280
+ # 3) 输出 JSON 到文件(如指定),并返回解析后的 entries
281
+ if out is not None:
282
+ out_path = Path(out)
283
+ out_path.parent.mkdir(parents=True, exist_ok=True)
284
+ # 使用原始文本写出,便于可读
285
+ out_path.write_text(json_text, encoding="utf-8")
286
+ PrettyOutput.auto_print(f"[c2rust-llm-planner] JSON 已写入: {out_path}")
287
+
288
+ return entries
@@ -0,0 +1,170 @@
1
+ # -*- coding: utf-8 -*-
2
+ """LLM 模块规划 Agent 的图加载器。"""
3
+
4
+ from pathlib import Path
5
+ from typing import Any
6
+ from typing import Dict
7
+ from typing import List
8
+ from typing import Set
9
+ from typing import Tuple
10
+
11
+ from jarvis.jarvis_c2rust.llm_module_agent_types import FnMeta
12
+ from jarvis.jarvis_utils.jsonnet_compat import loads as json_loads
13
+
14
+
15
+ class GraphLoader:
16
+ """
17
+ 仅从 symbols.jsonl 读取符号与调用关系,提供子图遍历能力:
18
+ - 数据源:<project_root>/.jarvis/c2rust/symbols.jsonl 或显式传入的 .jsonl 文件
19
+ - 不再支持任何回退策略(不考虑 symbols_raw.jsonl、functions.jsonl 等)
20
+ """
21
+
22
+ def __init__(self, db_path: Path, project_root: Path):
23
+ self.project_root = Path(project_root).resolve()
24
+
25
+ def _resolve_data_path(hint: Path) -> Path:
26
+ p = Path(hint)
27
+ # 仅支持 symbols.jsonl;不再兼容 functions.jsonl 或其他旧格式
28
+ # 若直接传入文件路径且为 .jsonl,则直接使用(要求内部包含 category/ref 字段)
29
+ if p.is_file() and p.suffix.lower() == ".jsonl":
30
+ return p
31
+ # 目录:仅支持 <dir>/.jarvis/c2rust/symbols.jsonl
32
+ if p.is_dir():
33
+ return p / ".jarvis" / "c2rust" / "symbols.jsonl"
34
+ # 默认:项目 .jarvis/c2rust/symbols.jsonl
35
+ return self.project_root / ".jarvis" / "c2rust" / "symbols.jsonl"
36
+
37
+ self.data_path = _resolve_data_path(Path(db_path))
38
+ if not self.data_path.exists():
39
+ raise FileNotFoundError(f"未找到 symbols.jsonl: {self.data_path}")
40
+ # Initialize in-memory graph structures
41
+ self.adj: Dict[int, List[str]] = {}
42
+ self.name_to_id: Dict[str, int] = {}
43
+ self.fn_by_id: Dict[int, FnMeta] = {}
44
+
45
+ # 从 symbols.jsonl 加载符号元数据与邻接关系(统一处理函数与类型,按 ref 构建名称邻接)
46
+ rows_loaded = 0
47
+ try:
48
+ with open(self.data_path, "r", encoding="utf-8") as f:
49
+ for line in f:
50
+ line = line.strip()
51
+ if not line:
52
+ continue
53
+ try:
54
+ obj = json_loads(line)
55
+ except Exception:
56
+ # 跳过无效的 JSON 行,但记录以便调试
57
+ continue
58
+ # 不区分函数与类型,统一处理 symbols.jsonl 中的所有记录
59
+ rows_loaded += 1
60
+ fid = int(obj.get("id") or rows_loaded)
61
+ nm = obj.get("name") or ""
62
+ qn = obj.get("qualified_name") or ""
63
+ sg = obj.get("signature") or ""
64
+ fp = obj.get("file") or ""
65
+ refs = obj.get("ref")
66
+ # 不兼容旧数据:严格要求为列表类型,缺失则视为空
67
+ if not isinstance(refs, list):
68
+ refs = []
69
+ refs = [c for c in refs if isinstance(c, str) and c]
70
+ self.adj[fid] = refs
71
+ # 建立名称索引与函数元信息,供子图遍历与上下文构造使用
72
+ if isinstance(nm, str) and nm:
73
+ self.name_to_id.setdefault(nm, fid)
74
+ if isinstance(qn, str) and qn:
75
+ self.name_to_id.setdefault(qn, fid)
76
+ try:
77
+ rel_file = self._rel_path(fp)
78
+ except (ValueError, OSError):
79
+ rel_file = fp
80
+ self.fn_by_id[fid] = FnMeta(
81
+ id=fid,
82
+ name=nm,
83
+ qname=qn,
84
+ signature=sg,
85
+ file=rel_file,
86
+ refs=refs,
87
+ )
88
+ except FileNotFoundError:
89
+ raise
90
+ except (OSError, IOError) as e:
91
+ raise RuntimeError(f"读取 symbols.jsonl 时发生错误: {e}") from e
92
+ except Exception as e:
93
+ raise RuntimeError(f"解析 symbols.jsonl 时发生未知错误: {e}") from e
94
+
95
+ def _rel_path(self, abs_path: str) -> str:
96
+ """将绝对路径转换为相对于项目根的相对路径。"""
97
+ try:
98
+ p = Path(abs_path).resolve()
99
+ return str(p.relative_to(self.project_root))
100
+ except Exception:
101
+ return abs_path
102
+
103
+ def collect_subgraph(self, root_id: int) -> Tuple[Set[int], Set[str]]:
104
+ """
105
+ 从 root_id 出发,收集所有可达的内部函数 (visited_ids) 与外部调用名称 (externals)
106
+
107
+ Args:
108
+ root_id: 根函数 ID
109
+
110
+ Returns:
111
+ (visited_ids, externals) 元组
112
+ """
113
+ visited: Set[int] = set()
114
+ externals: Set[str] = set()
115
+ stack: List[int] = [root_id]
116
+ visited.add(root_id)
117
+ while stack:
118
+ src = stack.pop()
119
+ for callee in self.adj.get(src, []):
120
+ cid = self.name_to_id.get(callee)
121
+ if cid is not None:
122
+ if cid not in visited:
123
+ visited.add(cid)
124
+ stack.append(cid)
125
+ else:
126
+ externals.add(callee)
127
+ return visited, externals
128
+
129
+ def build_roots_context(
130
+ self,
131
+ roots: List[int],
132
+ max_functions_per_ns: int = 200, # 保留参数以保持兼容性,但当前未使用
133
+ max_namespaces_per_root: int = 50, # 保留参数以保持兼容性,但当前未使用
134
+ ) -> List[Dict[str, Any]]:
135
+ """
136
+ 为每个根函数构造上下文(仅函数名的调用关系,且不包含任何其他信息):
137
+ - root_function: 根函数的简单名称(不包含签名/限定名)
138
+ - functions: 该根函数子图内所有可达函数的简单名称列表(不包含签名/限定名),去重、排序、可选截断
139
+ 注意:
140
+ - 不包含文件路径、签名、限定名、命名空间、外部符号等任何其他元信息
141
+ """
142
+ root_contexts: List[Dict[str, Any]] = []
143
+ for rid in roots:
144
+ meta = self.fn_by_id.get(rid)
145
+ root_label = (meta.name or f"fn_{rid}") if meta else f"fn_{rid}"
146
+
147
+ visited_ids, _externals = self.collect_subgraph(rid)
148
+ # 收集所有简单函数名
149
+ fn_names: List[str] = []
150
+ for fid in sorted(visited_ids):
151
+ m = self.fn_by_id.get(fid)
152
+ if not m:
153
+ continue
154
+ simple = m.name or f"fn_{fid}"
155
+ fn_names.append(simple)
156
+
157
+ # 去重并排序(优先使用 dict.fromkeys 保持顺序)
158
+ try:
159
+ fn_names = sorted(list(dict.fromkeys(fn_names)))
160
+ except (TypeError, ValueError):
161
+ # 如果 dict.fromkeys 失败(理论上不应该),回退到 set
162
+ fn_names = sorted(list(set(fn_names)))
163
+
164
+ root_contexts.append(
165
+ {
166
+ "root_function": root_label,
167
+ "functions": fn_names,
168
+ }
169
+ )
170
+ return root_contexts