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
@@ -13,7 +13,7 @@ LLM 驱动的 Rust Crate 模块规划 Agent
13
13
 
14
14
  用法:
15
15
  from jarvis.jarvis_c2rust.llm_module_agent import plan_crate_json_llm
16
- print(plan_crate_json_llm(project_root="."))
16
+ PrettyOutput.auto_print(plan_crate_json_llm(project_root="."))
17
17
 
18
18
  CLI 集成建议:
19
19
  可在 jarvis_c2rust/cli.py 中新增 llm-plan 子命令调用本模块的 plan_crate_json_llm(已独立封装,便于后续补充)
@@ -21,285 +21,29 @@ CLI 集成建议:
21
21
 
22
22
  from __future__ import annotations
23
23
 
24
- from jarvis.jarvis_utils.jsonnet_compat import loads as json_loads
25
24
  import json
26
- # removed sqlite3 (migrated to JSONL/JSON)
27
- from dataclasses import dataclass
28
25
  from pathlib import Path
29
- from typing import Any, Dict, List, Optional, Set, Tuple, Union
30
- import re
26
+ from typing import Any, List, Optional, Union
31
27
 
32
28
  from jarvis.jarvis_agent import Agent # 复用 LLM Agent 能力
33
- from jarvis.jarvis_utils.input import user_confirm
29
+ from jarvis.jarvis_c2rust.llm_module_agent_apply import (
30
+ apply_project_structure_from_json,
31
+ )
32
+ from jarvis.jarvis_c2rust.llm_module_agent_executor import (
33
+ execute_llm_plan,
34
+ )
35
+ from jarvis.jarvis_c2rust.llm_module_agent_loader import GraphLoader
36
+ from jarvis.jarvis_c2rust.llm_module_agent_prompts import PromptBuilder
37
+ from jarvis.jarvis_c2rust.llm_module_agent_types import sanitize_mod_name
38
+ from jarvis.jarvis_c2rust.llm_module_agent_utils import (
39
+ entries_to_json,
40
+ parse_project_json_entries,
41
+ perform_pre_cleanup_for_planner,
42
+ )
43
+ from jarvis.jarvis_c2rust.llm_module_agent_validator import ProjectValidator
44
+ from jarvis.jarvis_utils.output import PrettyOutput
34
45
 
35
46
 
36
- @dataclass
37
- class _FnMeta:
38
- id: int
39
- name: str
40
- qname: str
41
- signature: str
42
- file: str
43
- refs: List[str]
44
-
45
- @property
46
- def label(self) -> str:
47
- base = self.qname or self.name or f"fn_{self.id}"
48
- if self.signature and self.signature != base:
49
- return f"{base}\n{self.signature}"
50
- return base
51
-
52
- @property
53
- def top_namespace(self) -> str:
54
- """
55
- 提取顶层命名空间/类名:
56
- - qualified_name 形如 ns1::ns2::Class::method -> 返回 ns1
57
- - C 函数或无命名空间 -> 返回 "c"
58
- """
59
- if self.qname and "::" in self.qname:
60
- return self.qname.split("::", 1)[0] or "c"
61
- return "c"
62
-
63
-
64
- def _sanitize_mod_name(s: str) -> str:
65
- s = (s or "").replace("::", "__")
66
- safe = []
67
- for ch in s:
68
- if ch.isalnum() or ch == "_":
69
- safe.append(ch.lower())
70
- else:
71
- safe.append("_")
72
- out = "".join(safe).strip("_")
73
- return out[:80] or "mod"
74
-
75
-
76
- class _GraphLoader:
77
- """
78
- 仅从 symbols.jsonl 读取符号与调用关系,提供子图遍历能力:
79
- - 数据源:<project_root>/.jarvis/c2rust/symbols.jsonl 或显式传入的 .jsonl 文件
80
- - 不再支持任何回退策略(不考虑 symbols_raw.jsonl、functions.jsonl 等)
81
- """
82
-
83
- def __init__(self, db_path: Path, project_root: Path):
84
- self.project_root = Path(project_root).resolve()
85
-
86
- def _resolve_data_path(hint: Path) -> Path:
87
- p = Path(hint)
88
- # 仅支持 symbols.jsonl;不再兼容 functions.jsonl 或其他旧格式
89
- # 若直接传入文件路径且为 .jsonl,则直接使用(要求内部包含 category/ref 字段)
90
- if p.is_file() and p.suffix.lower() == ".jsonl":
91
- return p
92
- # 目录:仅支持 <dir>/.jarvis/c2rust/symbols.jsonl
93
- if p.is_dir():
94
- return p / ".jarvis" / "c2rust" / "symbols.jsonl"
95
- # 默认:项目 .jarvis/c2rust/symbols.jsonl
96
- return self.project_root / ".jarvis" / "c2rust" / "symbols.jsonl"
97
-
98
- self.data_path = _resolve_data_path(Path(db_path))
99
- if not self.data_path.exists():
100
- raise FileNotFoundError(f"未找到 symbols.jsonl: {self.data_path}")
101
- # Initialize in-memory graph structures
102
- self.adj: Dict[int, List[str]] = {}
103
- self.name_to_id: Dict[str, int] = {}
104
- self.fn_by_id: Dict[int, _FnMeta] = {}
105
-
106
- # 从 symbols.jsonl 加载符号元数据与邻接关系(统一处理函数与类型,按 ref 构建名称邻接)
107
- rows_loaded = 0
108
- try:
109
- with open(self.data_path, "r", encoding="utf-8") as f:
110
- for line in f:
111
- line = line.strip()
112
- if not line:
113
- continue
114
- try:
115
- obj = json_loads(line)
116
- except Exception:
117
- # 跳过无效的 JSON 行,但记录以便调试
118
- continue
119
- # 不区分函数与类型,统一处理 symbols.jsonl 中的所有记录
120
- rows_loaded += 1
121
- fid = int(obj.get("id") or rows_loaded)
122
- nm = obj.get("name") or ""
123
- qn = obj.get("qualified_name") or ""
124
- sg = obj.get("signature") or ""
125
- fp = obj.get("file") or ""
126
- refs = obj.get("ref")
127
- # 不兼容旧数据:严格要求为列表类型,缺失则视为空
128
- if not isinstance(refs, list):
129
- refs = []
130
- refs = [c for c in refs if isinstance(c, str) and c]
131
- self.adj[fid] = refs
132
- # 建立名称索引与函数元信息,供子图遍历与上下文构造使用
133
- if isinstance(nm, str) and nm:
134
- self.name_to_id.setdefault(nm, fid)
135
- if isinstance(qn, str) and qn:
136
- self.name_to_id.setdefault(qn, fid)
137
- try:
138
- rel_file = self._rel_path(fp)
139
- except (ValueError, OSError):
140
- rel_file = fp
141
- self.fn_by_id[fid] = _FnMeta(
142
- id=fid,
143
- name=nm,
144
- qname=qn,
145
- signature=sg,
146
- file=rel_file,
147
- refs=refs,
148
- )
149
- except FileNotFoundError:
150
- raise
151
- except (OSError, IOError) as e:
152
- raise RuntimeError(f"读取 symbols.jsonl 时发生错误: {e}") from e
153
- except Exception as e:
154
- raise RuntimeError(f"解析 symbols.jsonl 时发生未知错误: {e}") from e
155
-
156
- def _rel_path(self, abs_path: str) -> str:
157
- try:
158
- p = Path(abs_path).resolve()
159
- return str(p.relative_to(self.project_root))
160
- except Exception:
161
- return abs_path
162
-
163
- def collect_subgraph(self, root_id: int) -> Tuple[Set[int], Set[str]]:
164
- """
165
- 从 root_id 出发,收集所有可达的内部函数 (visited_ids) 与外部调用名称 (externals)
166
- """
167
- visited: Set[int] = set()
168
- externals: Set[str] = set()
169
- stack: List[int] = [root_id]
170
- visited.add(root_id)
171
- while stack:
172
- src = stack.pop()
173
- for callee in self.adj.get(src, []):
174
- cid = self.name_to_id.get(callee)
175
- if cid is not None:
176
- if cid not in visited:
177
- visited.add(cid)
178
- stack.append(cid)
179
- else:
180
- externals.add(callee)
181
- return visited, externals
182
-
183
- def build_roots_context(
184
- self,
185
- roots: List[int],
186
- max_functions_per_ns: int = 200, # 保留参数以保持兼容性,但当前未使用
187
- max_namespaces_per_root: int = 50, # 保留参数以保持兼容性,但当前未使用
188
- ) -> List[Dict[str, Any]]:
189
- """
190
- 为每个根函数构造上下文(仅函数名的调用关系,且不包含任何其他信息):
191
- - root_function: 根函数的简单名称(不包含签名/限定名)
192
- - functions: 该根函数子图内所有可达函数的简单名称列表(不包含签名/限定名),去重、排序、可选截断
193
- 注意:
194
- - 不包含文件路径、签名、限定名、命名空间、外部符号等任何其他元信息
195
- """
196
- root_contexts: List[Dict[str, Any]] = []
197
- for rid in roots:
198
- meta = self.fn_by_id.get(rid)
199
- root_label = (meta.name or f"fn_{rid}") if meta else f"fn_{rid}"
200
-
201
- visited_ids, _externals = self.collect_subgraph(rid)
202
- # 收集所有简单函数名
203
- fn_names: List[str] = []
204
- for fid in sorted(visited_ids):
205
- m = self.fn_by_id.get(fid)
206
- if not m:
207
- continue
208
- simple = m.name or f"fn_{fid}"
209
- fn_names.append(simple)
210
-
211
- # 去重并排序(优先使用 dict.fromkeys 保持顺序)
212
- try:
213
- fn_names = sorted(list(dict.fromkeys(fn_names)))
214
- except (TypeError, ValueError):
215
- # 如果 dict.fromkeys 失败(理论上不应该),回退到 set
216
- fn_names = sorted(list(set(fn_names)))
217
-
218
- root_contexts.append(
219
- {
220
- "root_function": root_label,
221
- "functions": fn_names,
222
- }
223
- )
224
- return root_contexts
225
-
226
-
227
- def _perform_pre_cleanup_for_planner(project_root: Union[Path, str]) -> None:
228
- """
229
- 预清理:如存在将删除将要生成的 crate 目录、当前目录的 workspace 文件 Cargo.toml、
230
- 以及 project_root/.jarvis/c2rust 下的 progress.json 与 symbol_map.jsonl。
231
- 用户不同意则直接退出。
232
- """
233
- import sys
234
- import shutil
235
-
236
- try:
237
- cwd = Path(".").resolve()
238
- except (OSError, ValueError) as e:
239
- raise RuntimeError(f"无法解析当前工作目录: {e}") from e
240
-
241
- try:
242
- requested_root = Path(project_root).resolve()
243
- except (OSError, ValueError):
244
- requested_root = Path(project_root)
245
-
246
- created_dir = cwd.parent / f"{cwd.name}_rs" if requested_root == cwd else requested_root
247
-
248
- cargo_path = cwd / "Cargo.toml"
249
- data_dir = requested_root / ".jarvis" / "c2rust"
250
- progress_path = data_dir / "progress.json"
251
- symbol_map_jsonl_path = data_dir / "symbol_map.jsonl"
252
-
253
- targets: List[str] = []
254
- if created_dir.exists():
255
- targets.append(f"- 删除 crate 目录(如存在):{created_dir}")
256
- if cargo_path.exists():
257
- targets.append(f"- 删除工作区文件:{cargo_path}")
258
- if progress_path.exists():
259
- targets.append(f"- 删除进度文件:{progress_path}")
260
- if symbol_map_jsonl_path.exists():
261
- targets.append(f"- 删除符号映射文件:{symbol_map_jsonl_path}")
262
-
263
- if not targets:
264
- return
265
-
266
- tip_lines = ["将执行以下清理操作:"] + targets + ["", "是否继续?"]
267
- if not user_confirm("\n".join(tip_lines), default=False):
268
- print("[c2rust-llm-planner] 用户取消清理操作,退出。")
269
- sys.exit(0)
270
-
271
- # 执行清理操作
272
- try:
273
- if created_dir.exists():
274
- shutil.rmtree(created_dir, ignore_errors=True)
275
- if cargo_path.exists():
276
- cargo_path.unlink()
277
- if progress_path.exists():
278
- progress_path.unlink()
279
- if symbol_map_jsonl_path.exists():
280
- symbol_map_jsonl_path.unlink()
281
- except (OSError, PermissionError) as e:
282
- raise RuntimeError(f"清理操作失败: {e}") from e
283
-
284
- def _resolve_created_dir(target_root: Union[Path, str]) -> Path:
285
- """
286
- 解析 crate 目录路径:
287
- - 若 target_root 为 "." 或解析后等于当前工作目录,则返回 "<cwd.name>_rs" 目录;
288
- - 否则返回解析后的目标路径;
289
- - 解析失败则回退到 Path(target_root)。
290
- """
291
- try:
292
- cwd = Path(".").resolve()
293
- try:
294
- resolved_target = Path(target_root).resolve()
295
- except Exception:
296
- resolved_target = Path(target_root)
297
- if target_root == "." or resolved_target == cwd:
298
- return cwd.parent / f"{cwd.name}_rs"
299
- return resolved_target
300
- except Exception:
301
- return Path(target_root)
302
-
303
47
  class LLMRustCratePlannerAgent:
304
48
  """
305
49
  使用 jarvis_agent.Agent 调用 LLM 来生成 Rust crate 规划(JSON)。
@@ -318,14 +62,28 @@ class LLMRustCratePlannerAgent:
318
62
  else (self.project_root / ".jarvis" / "c2rust" / "symbols.jsonl")
319
63
  )
320
64
  self.llm_group = llm_group
321
- self.loader = _GraphLoader(self.db_path, self.project_root)
65
+ self.loader = GraphLoader(self.db_path, self.project_root)
322
66
  # 读取附加说明
323
67
  self.additional_notes = self._load_additional_notes()
324
68
 
69
+ # 初始化提示词构建器和验证器
70
+ self.prompt_builder = PromptBuilder(
71
+ self.project_root,
72
+ self.loader,
73
+ self._crate_name,
74
+ self._has_original_main,
75
+ self._append_additional_notes,
76
+ )
77
+ self.validator = ProjectValidator(
78
+ self._crate_name,
79
+ self._has_original_main,
80
+ )
81
+
325
82
  def _load_additional_notes(self) -> str:
326
83
  """从配置文件加载附加说明"""
327
84
  try:
328
85
  from jarvis.jarvis_c2rust.constants import CONFIG_JSON
86
+
329
87
  config_path = self.project_root / ".jarvis" / "c2rust" / CONFIG_JSON
330
88
  if config_path.exists():
331
89
  with config_path.open("r", encoding="utf-8") as f:
@@ -339,15 +97,20 @@ class LLMRustCratePlannerAgent:
339
97
  def _append_additional_notes(self, prompt: str) -> str:
340
98
  """
341
99
  在提示词末尾追加附加说明(如果存在)。
342
-
100
+
343
101
  Args:
344
102
  prompt: 原始提示词
345
-
103
+
346
104
  Returns:
347
105
  追加了附加说明的提示词
348
106
  """
349
107
  if self.additional_notes and self.additional_notes.strip():
350
- return prompt + "\n\n" + "【附加说明(用户自定义)】\n" + self.additional_notes.strip()
108
+ return (
109
+ prompt
110
+ + "\n\n"
111
+ + "【附加说明(用户自定义)】\n"
112
+ + self.additional_notes.strip()
113
+ )
351
114
  return prompt
352
115
 
353
116
  def _crate_name(self) -> str:
@@ -365,7 +128,7 @@ class LLMRustCratePlannerAgent:
365
128
  base = self.project_root.name or "c2rust_crate"
366
129
  except Exception:
367
130
  base = "c2rust_crate"
368
- return _sanitize_mod_name(base)
131
+ return sanitize_mod_name(base)
369
132
 
370
133
  def _has_original_main(self) -> bool:
371
134
  """
@@ -383,365 +146,56 @@ class LLMRustCratePlannerAgent:
383
146
  pass
384
147
  return False
385
148
 
386
- def _order_path(self) -> Path:
387
- """
388
- 返回 translation_order.jsonl 的标准路径:<project_root>/.jarvis/c2rust/translation_order.jsonl
389
- """
390
- return self.project_root / ".jarvis" / "c2rust" / "translation_order.jsonl"
391
-
392
- def _build_roots_context_from_order(self) -> List[Dict[str, Any]]:
393
- """
394
- 基于 translation_order.jsonl 生成用于规划的上下文:
395
- - 以每个 step 的 roots 标签为分组键(通常每步一个 root 标签)
396
- - 函数列表来自每步的 items 中的符号 'name' 字段,按 root 聚合去重
397
- - 跳过无 roots 标签的 residual 步骤(仅保留明确 root 的上下文)
398
- - 若最终未收集到任何 root 组,则回退为单组 'project',包含所有 items 的函数名集合
399
- """
400
- from jarvis.jarvis_utils.jsonnet_compat import loads as json_loads
401
- order_path = self._order_path()
402
- if not order_path.exists():
403
- raise FileNotFoundError(f"未找到 translation_order.jsonl: {order_path}")
404
-
405
- def _deduplicate_names(names: List[str]) -> List[str]:
406
- """去重并排序函数名列表"""
407
- try:
408
- return sorted(list(dict.fromkeys(names)))
409
- except (TypeError, ValueError):
410
- return sorted(list(set(names)))
411
-
412
- def _extract_names_from_items(items: List[Any]) -> List[str]:
413
- """从 items 中提取函数名"""
414
- names: List[str] = []
415
- for it in items:
416
- if isinstance(it, dict):
417
- nm = it.get("name") or ""
418
- if isinstance(nm, str) and nm.strip():
419
- names.append(str(nm).strip())
420
- return names
421
-
422
- groups: Dict[str, List[str]] = {}
423
- all_names_fallback: List[str] = [] # 用于回退场景
424
-
425
- try:
426
- with order_path.open("r", encoding="utf-8") as f:
427
- for line in f:
428
- line = line.strip()
429
- if not line:
430
- continue
431
- try:
432
- obj = json_loads(line)
433
- except Exception:
434
- continue
435
-
436
- roots = obj.get("roots") or []
437
- items = obj.get("items") or []
438
- if not isinstance(items, list) or not items:
439
- continue
440
-
441
- # 提取所有函数名(用于回退场景)
442
- item_names = _extract_names_from_items(items)
443
- all_names_fallback.extend(item_names)
444
-
445
- # 提取 root 标签
446
- root_labels = [str(r).strip() for r in roots if isinstance(r, str) and str(r).strip()]
447
- if not root_labels:
448
- continue
449
-
450
- # 去重 step_names
451
- step_names = _deduplicate_names(item_names)
452
- if not step_names:
453
- continue
454
-
455
- # 按 root 聚合
456
- for r in root_labels:
457
- groups.setdefault(r, []).extend(step_names)
458
- except (OSError, IOError) as e:
459
- raise RuntimeError(f"读取 translation_order.jsonl 时发生错误: {e}") from e
460
-
461
- contexts: List[Dict[str, Any]] = []
462
- for root_label, names in groups.items():
463
- names = _deduplicate_names(names)
464
- contexts.append({"root_function": root_label, "functions": sorted(names)})
465
-
466
- # 回退:如果没有任何 root 组,使用所有 items 作为单组 'project'
467
- if not contexts:
468
- all_names = _deduplicate_names(all_names_fallback)
469
- if all_names:
470
- contexts.append({"root_function": "project", "functions": sorted(all_names)})
471
-
472
- return contexts
473
-
474
- def _build_user_prompt(self, roots_context: List[Dict[str, Any]]) -> str:
475
- """
476
- 主对话阶段:传入上下文,不给出输出要求,仅用于让模型获取信息并触发进入总结阶段。
477
- 请模型仅输出 <!!!COMPLETE!!!> 以进入总结(summary)阶段。
478
- """
479
- crate_name = self._crate_name()
480
- has_main = self._has_original_main()
481
- created_dir = _resolve_created_dir(self.project_root)
482
- context_json = json.dumps(
483
- {"meta": {"crate_name": crate_name, "main_present": has_main, "crate_dir": str(created_dir)}, "roots": roots_context},
484
- ensure_ascii=False,
485
- indent=2,
486
- )
487
- prompt = f"""
488
- 下面提供了项目的调用图上下文(JSON),请先通读理解,不要输出任何规划或JSON内容:
489
- <context>
490
- {context_json}
491
- </context>
492
-
493
- 如果已准备好进入总结阶段以生成完整输出,请仅输出:<!!!COMPLETE!!!>
494
- """.strip()
495
- return self._append_additional_notes(prompt)
496
-
497
- def _build_system_prompt(self) -> str:
498
- """
499
- 系统提示:描述如何基于依赖关系进行 crate 规划的原则(不涉及对话流程或输出方式)
500
- """
501
- crate_name = self._crate_name()
502
- prompt = (
503
- "你是资深 Rust 架构师。任务:根据给定的函数级调用关系(仅包含 root_function 及其可达的函数名列表),为目标项目规划合理的 Rust crate 结构。\n"
504
- "\n"
505
- "规划原则:\n"
506
- "- 根导向:以每个 root_function 为边界组织顶层模块,形成清晰的入口与责任范围。\n"
507
- "- 内聚优先:按调用内聚性拆分子模块,使强相关函数位于同一子模块,减少跨模块耦合。\n"
508
- "- 去环与分层:尽量消除循环依赖;遵循由上到下的调用方向,保持稳定依赖方向与层次清晰。\n"
509
- "- 共享抽取:被多个 root 使用的通用能力抽取到 common/ 或 shared/ 模块,避免重复与交叉依赖。\n"
510
- "- 边界隔离:将平台/IO/外设等边界能力独立到 adapter/ 或 ffi/ 等模块(如存在)。\n"
511
- "- 命名规范:目录/文件采用小写下划线;模块名简洁可读,避免特殊字符与过长名称。\n"
512
- "- 可演进性:模块粒度适中,保留扩展点,便于后续重构与逐步替换遗留代码。\n"
513
- "- 模块组织:每个目录的 mod.rs 声明其子目录与 .rs 子模块;顶层 lib.rs 汇聚导出主要模块与公共能力。\n"
514
- "- 入口策略(务必遵循,bin 仅做入口,功能尽量在 lib 中实现):\n"
515
- " * 若原始项目包含 main 函数:不要生成 src/main.rs;使用 src/bin/"
516
- + crate_name
517
- + ".rs 作为唯一可执行入口,并在其中仅保留最小入口逻辑(调用库层);共享代码放在 src/lib.rs;\n"
518
- " * 若原始项目不包含 main 函数:不要生成任何二进制入口(不创建 src/main.rs 或 src/bin/),仅生成 src/lib.rs;\n"
519
- " * 多可执行仅在确有多个清晰入口时才使用 src/bin/<name>.rs;每个 bin 文件仅做入口,尽量调用库;\n"
520
- " * 二进制命名:<name> 使用小写下划线,体现入口意图,避免与模块/文件重名。\n"
521
- )
522
- return self._append_additional_notes(prompt)
523
-
524
- def _build_summary_prompt(self, roots_context: List[Dict[str, Any]]) -> str:
525
- """
526
- 总结阶段:只输出目录结构的 JSON。
527
- 要求:
528
- - 仅输出一个 <PROJECT> 块
529
- - <PROJECT> 与 </PROJECT> 之间必须是可解析的 JSON 数组
530
- - 目录以对象表示,键为 '目录名/',值为子项数组;文件为字符串
531
- - 块外不得有任何字符(包括空行、注释、Markdown、解释文字、schema等)
532
- - 不要输出 crate 名称或其他多余字段
533
- """
534
- has_main = self._has_original_main()
535
- crate_name = self._crate_name()
536
- guidance_common = """
537
- 输出规范:
538
- - 只输出一个 <PROJECT> 块
539
- - 块外不得有任何字符(包括空行、注释、Markdown 等)
540
- - 块内必须是 JSON 数组:
541
- - 目录项使用对象表示,键为 '<name>/',值为子项数组
542
- - 文件为字符串项(例如 "lib.rs")
543
- - 不要创建与入口无关的占位文件
544
- - 支持jsonnet语法(如尾随逗号、注释、||| 或 ``` 分隔符多行字符串等)
545
- """.strip()
546
- if has_main:
547
- entry_rule = f"""
548
- 入口约定(基于原始项目存在 main):
549
- - 必须包含 src/lib.rs;
550
- - 不要包含 src/main.rs;
551
- - 必须包含 src/bin/{crate_name}.rs,作为唯一可执行入口(仅做入口,调用库逻辑);
552
- - 如无明确多个入口,不要创建额外 bin 文件。
553
- 正确示例(JSON格式):
554
- <PROJECT>
555
- [
556
- "Cargo.toml",
557
- {{
558
- "src/": [
559
- "lib.rs",
560
- {{
561
- "bin/": [
562
- "{crate_name}.rs"
563
- ]
564
- }}
565
- ]
566
- }}
567
- ]
568
- </PROJECT>
569
- """.strip()
570
- else:
571
- entry_rule = """
572
- 入口约定(基于原始项目不存在 main):
573
- - 必须包含 src/lib.rs;
574
- - 不要包含 src/main.rs;
575
- - 不要包含 src/bin/ 目录。
576
- 正确示例(JSON格式):
577
- <PROJECT>
578
- [
579
- "Cargo.toml",
580
- {
581
- "src/": [
582
- "lib.rs"
583
- ]
584
- }
585
- ]
586
- </PROJECT>
587
- """.strip()
588
- guidance = f"{guidance_common}\n{entry_rule}"
589
- prompt = f"""
590
- 请基于之前对话中已提供的<context>信息,生成总结输出(项目目录结构的 JSON)。严格遵循以下要求:
591
-
592
- {guidance}
593
-
594
- 你的输出必须仅包含以下单个块(用项目的真实目录结构替换块内内容):
595
- <PROJECT>
596
- [...]
597
- </PROJECT>
598
- """.strip()
599
- return self._append_additional_notes(prompt)
600
-
601
- def _extract_json_from_project(self, text: str) -> str:
602
- """
603
- 从 <PROJECT> 块中提取内容作为最终 JSON;若未匹配,返回原文本(兜底)。
604
- """
605
- if not isinstance(text, str) or not text:
606
- return ""
607
- m_proj = re.search(r"<PROJECT>([\s\S]*?)</PROJECT>", text, flags=re.IGNORECASE)
608
- if m_proj:
609
- return m_proj.group(1).strip()
610
- return text.strip()
611
-
612
- def _validate_project_entries(self, entries: List[Any]) -> Tuple[bool, str]:
613
- """
614
- 校验目录结构是否满足强约束:
615
- - 必须存在 src/lib.rs
616
- - 若原始项目包含 main:
617
- * 不允许 src/main.rs
618
- * 必须包含 src/bin/<crate_name>.rs
619
- - 若原始项目不包含 main:
620
- * 不允许 src/main.rs
621
- * 不允许存在 src/bin/ 目录
622
- 返回 (是否通过, 错误原因)
623
- """
624
- if not isinstance(entries, list) or not entries:
625
- return False, "JSON 不可解析或为空数组"
626
-
627
- # 提取 src 目录子项
628
- src_children: Optional[List[Any]] = None
629
- for it in entries:
630
- if isinstance(it, dict) and len(it) == 1:
631
- k, v = next(iter(it.items()))
632
- kk = str(k).rstrip("/").strip().lower()
633
- if kk == "src":
634
- if isinstance(v, list):
635
- src_children = v
636
- else:
637
- src_children = []
638
- break
639
- if src_children is None:
640
- return False, "缺少 src 目录"
641
-
642
- # 建立便捷索引
643
- def has_file(name: str) -> bool:
644
- for ch in src_children or []:
645
- if isinstance(ch, str) and ch.strip().lower() == name.lower():
646
- return True
647
- return False
648
-
649
- def find_dir(name: str) -> Optional[List[Any]]:
650
- for ch in src_children or []:
651
- if isinstance(ch, dict) and len(ch) == 1:
652
- k, v = next(iter(ch.items()))
653
- kk = str(k).rstrip("/").strip().lower()
654
- if kk == name.lower():
655
- return v if isinstance(v, list) else []
656
- return None
657
-
658
- # 1) 必须包含 lib.rs
659
- if not has_file("lib.rs"):
660
- return False, "src 目录下必须包含 lib.rs"
661
-
662
- has_main = self._has_original_main()
663
- crate_name = self._crate_name()
664
-
665
- # 2) 入口约束
666
- if has_main:
667
- # 不允许 src/main.rs
668
- if has_file("main.rs"):
669
- return False, "原始项目包含 main:不应生成 src/main.rs,请使用 src/bin/<crate>.rs"
670
- # 必须包含 src/bin/<crate_name>.rs
671
- bin_children = find_dir("bin")
672
- if bin_children is None:
673
- return False, f"原始项目包含 main:必须包含 src/bin/{crate_name}.rs"
674
- expect_bin = f"{crate_name}.rs".lower()
675
- if not any(isinstance(ch, str) and ch.strip().lower() == expect_bin for ch in bin_children):
676
- return False, f"原始项目包含 main:必须包含 src/bin/{crate_name}.rs"
677
- else:
678
- # 不允许 src/main.rs
679
- if has_file("main.rs"):
680
- return False, "原始项目不包含 main:不应生成 src/main.rs"
681
- # 不允许有 bin 目录
682
- if find_dir("bin") is not None:
683
- return False, "原始项目不包含 main:不应生成 src/bin/ 目录"
684
-
685
- return True, ""
686
-
687
- def _build_retry_summary_prompt(self, base_summary_prompt: str, error_reason: str) -> str:
688
- """
689
- 在原始 summary_prompt 基础上,附加错误反馈,要求严格重试。
690
- """
691
- feedback = (
692
- "\n\n[格式校验失败,必须重试]\n"
693
- f"- 失败原因:{error_reason}\n"
694
- "- 请严格遵循上述“输出规范”与“入口约定”,重新输出;\n"
695
- "- 仅输出一个 <PROJECT> 块,块内为可解析的 JSON 数组;块外不得有任何字符。\n"
696
- )
697
- return base_summary_prompt + feedback
698
-
699
149
  def _get_project_json_text(self, max_retries: int = 10) -> str:
700
150
  """
701
151
  执行主流程并返回原始 <PROJECT> JSON 文本,不进行解析。
702
152
  若格式校验失败,将自动重试,直到满足为止或达到最大重试次数。
703
-
153
+
704
154
  Args:
705
155
  max_retries: 最大重试次数,默认 10 次
706
-
156
+
707
157
  Raises:
708
158
  RuntimeError: 达到最大重试次数仍未生成有效输出
709
159
  """
710
160
  # 从 translation_order.jsonl 生成上下文,不再基于 symbols.jsonl 的调用图遍历
711
- roots_ctx = self._build_roots_context_from_order()
161
+ roots_ctx = self.prompt_builder.build_roots_context_from_order()
712
162
 
713
- system_prompt = self._build_system_prompt()
714
- user_prompt = self._build_user_prompt(roots_ctx)
715
- base_summary_prompt = self._build_summary_prompt(roots_ctx)
163
+ system_prompt = self.prompt_builder.build_system_prompt()
164
+ user_prompt = self.prompt_builder.build_user_prompt(roots_ctx)
165
+ base_summary_prompt = self.prompt_builder.build_summary_prompt(roots_ctx)
716
166
 
717
167
  last_error = "未知错误"
718
168
  attempt = 0
719
169
  use_direct_model = False # 标记是否使用直接模型调用
720
170
  agent = None # 在循环外声明,以便重试时复用
721
-
171
+
722
172
  while attempt < max_retries:
723
173
  attempt += 1
724
174
  # 首次使用基础 summary_prompt;失败后附加反馈
725
175
  summary_prompt = (
726
- base_summary_prompt if attempt == 1 else self._build_retry_summary_prompt(base_summary_prompt, last_error)
176
+ base_summary_prompt
177
+ if attempt == 1
178
+ else self.prompt_builder.build_retry_summary_prompt(
179
+ base_summary_prompt, last_error
180
+ )
727
181
  )
728
182
 
729
183
  # 第一次创建 Agent,后续重试时复用(如果使用直接模型调用)
730
184
  if agent is None or not use_direct_model:
731
185
  agent = Agent(
732
- system_prompt=system_prompt,
733
- name="C2Rust-LLM-Module-Planner",
734
- model_group=self.llm_group,
735
- summary_prompt=summary_prompt,
736
- need_summary=True,
737
- auto_complete=True,
738
- use_tools=["execute_script", "read_code"],
739
- non_interactive=True, # 非交互
740
- use_methodology=False,
741
- use_analysis=False,
742
- )
186
+ system_prompt=system_prompt,
187
+ name="C2Rust-LLM-Module-Planner",
188
+ model_group=self.llm_group,
189
+ summary_prompt=summary_prompt,
190
+ need_summary=True,
191
+ auto_complete=True,
192
+ use_tools=["execute_script", "read_code"],
193
+ non_interactive=True, # 非交互
194
+ use_methodology=False,
195
+ use_analysis=False,
196
+ )
743
197
 
744
- # 进入主循环:第一轮仅输出 <!!!COMPLETE!!!> 触发自动完成;随后 summary 输出 <PROJECT> 块(仅含 JSON)
198
+ # 进入主循环:第一轮仅输出 {ot('!!!COMPLETE!!!')} 触发自动完成;随后 summary 输出 <PROJECT> 块(仅含 JSON)
745
199
  if use_direct_model:
746
200
  # 格式校验失败后,直接调用模型接口
747
201
  # 构造包含摘要提示词和具体错误信息的完整提示
@@ -751,37 +205,41 @@ class LLMRustCratePlannerAgent:
751
205
  error_guidance = f"\n\n**格式错误详情(请根据以下错误修复输出格式):**\n- {last_error}\n\n请确保输出的JSON格式正确,包括正确的引号、逗号、大括号等。仅输出一个 <PROJECT> 块,块内仅包含 JSON 格式的项目结构定义。支持jsonnet语法(如尾随逗号、注释、||| 或 ``` 分隔符多行字符串等)。"
752
206
  else:
753
207
  error_guidance = f"\n\n**格式错误详情(请根据以下错误修复输出格式):**\n- {last_error}\n\n请确保输出格式正确:仅输出一个 <PROJECT> 块,块内仅包含 JSON 格式的项目结构定义。支持jsonnet语法(如尾随逗号、注释、||| 或 ``` 分隔符多行字符串等)。"
754
-
208
+
755
209
  full_prompt = f"{user_prompt}{error_guidance}\n\n{summary_prompt}"
756
210
  try:
757
- response = agent.model.chat_until_success(full_prompt) # type: ignore
211
+ response = agent.model.chat_until_success(full_prompt)
758
212
  summary_output = response
759
213
  except Exception as e:
760
- print(f"[c2rust-llm-planner] 直接模型调用失败: {e},回退到 run()")
761
- summary_output = agent.run(user_prompt) # type: ignore
214
+ PrettyOutput.auto_print(
215
+ f"[c2rust-llm-planner] 直接模型调用失败: {e},回退到 run()"
216
+ )
217
+ summary_output = agent.run(user_prompt)
762
218
  else:
763
219
  # 第一次使用 run(),让 Agent 完整运行(可能使用工具)
764
- summary_output = agent.run(user_prompt) # type: ignore
765
-
220
+ summary_output = agent.run(user_prompt)
221
+
766
222
  project_text = str(summary_output) if summary_output is not None else ""
767
- json_text = self._extract_json_from_project(project_text)
223
+ json_text = self.validator.extract_json_from_project(project_text)
768
224
 
769
225
  # 尝试解析并校验
770
- entries, parse_error_json = _parse_project_json_entries(json_text)
226
+ entries, parse_error_json = parse_project_json_entries(json_text)
771
227
  if parse_error_json:
772
228
  # JSON解析失败,记录错误并重试
773
229
  last_error = parse_error_json
774
230
  use_direct_model = True # 格式校验失败,后续重试使用直接模型调用
775
- print(f"[c2rust-llm-planner] JSON解析失败: {parse_error_json}")
231
+ PrettyOutput.auto_print(
232
+ f"[c2rust-llm-planner] JSON解析失败: {parse_error_json}"
233
+ )
776
234
  continue
777
235
 
778
- ok, reason = self._validate_project_entries(entries)
236
+ ok, reason = self.validator.validate_project_entries(entries)
779
237
  if ok:
780
238
  return json_text
781
239
  else:
782
240
  last_error = reason
783
241
  use_direct_model = True # 格式校验失败,后续重试使用直接模型调用
784
-
242
+
785
243
  # 达到最大重试次数
786
244
  raise RuntimeError(
787
245
  f"达到最大重试次数 ({max_retries}) 仍未生成有效的项目结构。"
@@ -796,7 +254,7 @@ class LLMRustCratePlannerAgent:
796
254
  * 字典:目录及其子项,如 {"src/": [ ... ]}
797
255
  """
798
256
  json_text = self._get_project_json_text()
799
- json_entries, parse_error = _parse_project_json_entries(json_text)
257
+ json_entries, parse_error = parse_project_json_entries(json_text)
800
258
  if parse_error:
801
259
  raise RuntimeError(f"JSON解析失败: {parse_error}")
802
260
  return json_entries
@@ -823,12 +281,16 @@ def plan_crate_json_text(
823
281
  """
824
282
  # 若外层已处理清理确认,则跳过本函数的清理与确认(避免重复询问)
825
283
  if skip_cleanup:
826
- agent = LLMRustCratePlannerAgent(project_root=project_root, db_path=db_path, llm_group=llm_group)
284
+ agent = LLMRustCratePlannerAgent(
285
+ project_root=project_root, db_path=db_path, llm_group=llm_group
286
+ )
827
287
  return agent.plan_crate_json_text()
828
288
 
829
- _perform_pre_cleanup_for_planner(project_root)
289
+ perform_pre_cleanup_for_planner(project_root)
830
290
 
831
- agent = LLMRustCratePlannerAgent(project_root=project_root, db_path=db_path, llm_group=llm_group)
291
+ agent = LLMRustCratePlannerAgent(
292
+ project_root=project_root, db_path=db_path, llm_group=llm_group
293
+ )
832
294
  return agent.plan_crate_json_text()
833
295
 
834
296
 
@@ -848,425 +310,17 @@ def plan_crate_json_llm(
848
310
  agent = LLMRustCratePlannerAgent(project_root=project_root, db_path=db_path)
849
311
  return agent.plan_crate_json_with_project()
850
312
 
851
- _perform_pre_cleanup_for_planner(project_root)
313
+ perform_pre_cleanup_for_planner(project_root)
852
314
 
853
315
  agent = LLMRustCratePlannerAgent(project_root=project_root, db_path=db_path)
854
316
  return agent.plan_crate_json_with_project()
855
317
 
856
318
 
857
- def entries_to_json(entries: List[Any]) -> str:
858
- """
859
- 将解析后的 entries 列表序列化为 JSON 文本(目录使用对象表示,文件为字符串)
860
- """
861
- return json.dumps(entries, ensure_ascii=False, indent=2)
862
-
863
-
864
- def _parse_project_json_entries_fallback(json_text: str) -> List[Any]:
865
- """
866
- Fallback 解析器:当 jsonnet 解析失败时,尝试使用标准 json 解析。
867
- 注意:此函数主要用于兼容性,正常情况下应使用 jsonnet 解析。
868
- """
869
- try:
870
- import json as std_json
871
- data = std_json.loads(json_text)
872
- if isinstance(data, list):
873
- return data
874
- return []
875
- except Exception:
876
- return []
877
-
878
-
879
- def _parse_project_json_entries(json_text: str) -> Tuple[List[Any], Optional[str]]:
880
- """
881
- 使用 jsonnet 解析 <PROJECT> 块中的目录结构 JSON 为列表结构:
882
- - 文件项: 字符串,如 "lib.rs"
883
- - 目录项: 字典,形如 {"src/": [ ... ]} 或 {"src": [ ... ]}
884
- 返回(解析结果, 错误信息)
885
- 如果解析成功,返回(data, None)
886
- 如果解析失败,返回([], 错误信息)
887
- 使用 jsonnet 解析,支持更宽松的 JSON 语法(如尾随逗号、注释等)。
888
- """
889
- try:
890
- try:
891
- data = json_loads(json_text)
892
- if isinstance(data, list):
893
- return data, None
894
- # 如果解析结果不是列表
895
- return [], f"JSON 解析结果不是数组,而是 {type(data).__name__}"
896
- except Exception as json_err:
897
- # JSON 解析错误
898
- error_msg = f"JSON 解析失败: {str(json_err)}"
899
- return [], error_msg
900
- except Exception as e:
901
- # 其他未知错误
902
- return [], f"解析过程发生异常: {str(e)}"
903
-
904
-
905
- def _ensure_pub_mod_declarations(existing_text: str, child_mods: List[str]) -> str:
906
- """
907
- 在给定文本中确保存在并升级子模块声明为 `pub mod <name>;`:
908
- - 解析已有的 `mod`/`pub mod`/`pub(...) mod` 声明;
909
- - 已存在但非 pub 的同名声明就地升级为 `pub mod`,保留原行的缩进;
910
- - 不存在的模块名则在末尾追加一行 `pub mod <name>;`;
911
- - 返回更新后的完整文本(保留结尾换行)。
912
- """
913
- try:
914
- lines = (existing_text or "").splitlines()
915
- except Exception:
916
- lines = []
917
- mod_decl_pattern = re.compile(r'^\s*(pub(?:\s*\([^)]+\))?\s+)?mod\s+([A-Za-z_][A-Za-z0-9_]*)\s*;\s*$')
918
- name_to_indices: Dict[str, List[int]] = {}
919
- name_has_pub: Set[str] = set()
920
- for i, ln in enumerate(lines):
921
- m = mod_decl_pattern.match(ln.strip())
922
- if not m:
923
- continue
924
- mod_name = m.group(2)
925
- name_to_indices.setdefault(mod_name, []).append(i)
926
- if m.group(1):
927
- name_has_pub.add(mod_name)
928
- for mod_name in sorted(set(child_mods or [])):
929
- if mod_name in name_to_indices:
930
- if mod_name not in name_has_pub:
931
- for idx in name_to_indices[mod_name]:
932
- ws_match = re.match(r'^(\s*)', lines[idx])
933
- leading_ws = ws_match.group(1) if ws_match else ""
934
- lines[idx] = f"{leading_ws}pub mod {mod_name};"
935
- else:
936
- lines.append(f"pub mod {mod_name};")
937
- return "\n".join(lines).rstrip() + ("\n" if lines else "")
938
-
939
- def _apply_entries_with_mods(entries: List[Any], base_path: Path) -> None:
940
- """
941
- 根据解析出的 entries 创建目录与文件结构(不在此阶段写入/更新任何 Rust 源文件内容):
942
- - 对于目录项:创建目录,并递归创建其子项;
943
- - 对于文件项:若不存在则创建空文件;
944
- 约束与约定:
945
- - crate 根的 src 目录:不生成 src/mod.rs,也不写入 src/lib.rs 的模块声明;
946
- - 非 src 目录:不创建或更新 mod.rs;如需创建 mod.rs,请在 YAML 中显式列出;
947
- - 模块声明的补齐将在后续 CodeAgent 阶段完成(扫描目录结构并最小化补齐 pub mod 声明)。
948
- """
949
- def apply_item(item: Any, dir_path: Path) -> None:
950
- if isinstance(item, str):
951
- # 文件
952
- file_path = dir_path / item
953
- file_path.parent.mkdir(parents=True, exist_ok=True)
954
- if not file_path.exists():
955
- try:
956
- file_path.touch(exist_ok=True)
957
- except Exception:
958
- pass
959
- return
960
-
961
- if isinstance(item, dict) and len(item) == 1:
962
- dir_name, children = next(iter(item.items()))
963
- name = str(dir_name).rstrip("/").strip()
964
- new_dir = dir_path / name
965
- new_dir.mkdir(parents=True, exist_ok=True)
966
-
967
- child_mods: List[str] = []
968
- # 是否为 crate 根下的 src 目录
969
- is_src_root_dir = (new_dir == base_path / "src")
970
-
971
- # 先创建子项
972
- for child in (children or []):
973
- if isinstance(child, str):
974
- apply_item(child, new_dir)
975
- # 收集 .rs 文件作为子模块
976
- if child.endswith(".rs") and child != "mod.rs":
977
- stem = Path(child).stem
978
- # 在 src 根目录下,忽略 lib.rs 与 main.rs 的自引用
979
- if is_src_root_dir and stem in ("lib", "main"):
980
- pass
981
- else:
982
- child_mods.append(stem)
983
- if child == "mod.rs":
984
- pass
985
- elif isinstance(child, dict):
986
- # 子目录
987
- sub_name = list(child.keys())[0]
988
- sub_mod_name = str(sub_name).rstrip("/").strip()
989
- child_mods.append(sub_mod_name)
990
- apply_item(child, new_dir)
991
-
992
- # 对 crate 根的 src 目录,使用 lib.rs 聚合子模块,不创建/更新 src/mod.rs
993
- if is_src_root_dir:
994
- # 不在 src 根目录写入任何文件内容;仅由子项创建对应空文件(如有)
995
- return
996
-
997
- # 非 src 目录:
998
- # 为避免覆盖现有实现,当前阶段不创建或更新 mod.rs 内容。
999
- # 如需创建 mod.rs,应在 JSON 中显式指定为文件项;
1000
- # 如需补齐模块声明,将由后续的 CodeAgent 阶段根据目录结构自动补齐。
1001
- return
1002
-
1003
- for entry in entries:
1004
- apply_item(entry, base_path)
1005
-
1006
-
1007
- def _ensure_cargo_toml(base_dir: Path, package_name: str) -> None:
1008
- """
1009
- 确保在 base_dir 下存在合理的 Cargo.toml:
1010
- - 如果不存在,则创建最小可用的 Cargo.toml,并设置 package.name = package_name
1011
- - 如果已存在,则不覆盖现有内容(避免误改)
1012
- """
1013
- cargo_path = base_dir / "Cargo.toml"
1014
- if cargo_path.exists():
1015
- return
1016
- try:
1017
- cargo_path.touch(exist_ok=True)
1018
- except (OSError, PermissionError):
1019
- # 如果无法创建文件,记录错误但不中断流程
1020
- # 后续 CodeAgent 可能会处理 Cargo.toml 的创建
1021
- pass
1022
-
1023
-
1024
- def apply_project_structure_from_json(json_text: str, project_root: Union[Path, str] = ".") -> None:
1025
- """
1026
- 基于 Agent 返回的 <PROJECT> 中的目录结构 JSON,创建实际目录与文件(不在此阶段写入或更新任何 Rust 源文件内容)。
1027
- - project_root: 目标应用路径;当为 "."(默认)时,将使用"父目录/当前目录名_rs"作为crate根目录
1028
- 注意:模块声明(mod/pub mod)补齐将在后续的 CodeAgent 步骤中完成。按新策略不再创建或更新 workspace(构建直接在 crate 目录内进行)。
1029
- """
1030
- entries, parse_error = _parse_project_json_entries(json_text)
1031
- if parse_error:
1032
- raise ValueError(f"JSON解析失败: {parse_error}")
1033
- if not entries:
1034
- # 严格模式:解析失败直接报错并退出,由上层 CLI 捕获打印错误
1035
- raise ValueError("[c2rust-llm-planner] 从LLM输出解析目录结构失败。正在中止。")
1036
- requested_root = Path(project_root).resolve()
1037
- try:
1038
- cwd = Path(".").resolve()
1039
- if requested_root == cwd:
1040
- # 默认crate不能设置为 .,设置为 父目录/当前目录名_rs(与当前目录同级)
1041
- base_dir = cwd.parent / f"{cwd.name}_rs"
1042
- else:
1043
- base_dir = requested_root
1044
- except Exception:
1045
- base_dir = requested_root
1046
- base_dir.mkdir(parents=True, exist_ok=True)
1047
- # crate name 与目录名保持一致(用于 Cargo 包名,允许连字符)
1048
- crate_pkg_name = base_dir.name
1049
- _apply_entries_with_mods(entries, base_dir)
1050
- # 确保 Cargo.toml 存在并设置包名
1051
- _ensure_cargo_toml(base_dir, crate_pkg_name)
1052
-
1053
- # 已弃用:不再将 crate 添加到 workspace(按新策略去除 workspace)
1054
- # 构建与工具运行将直接在 crate 目录内进行
1055
-
1056
-
1057
- def execute_llm_plan(
1058
- out: Optional[Union[Path, str]] = None,
1059
- apply: bool = False,
1060
- crate_name: Optional[Union[Path, str]] = None,
1061
- llm_group: Optional[str] = None,
1062
- non_interactive: bool = True,
1063
- ) -> List[Any]:
1064
- """
1065
- 返回 LLM 生成的目录结构原始 JSON 文本(来自 <PROJECT> 块)。
1066
- 不进行解析,便于后续按原样应用并在需要时使用更健壮的解析器处理。
1067
- """
1068
- # execute_llm_plan 是顶层入口,需要执行清理(skip_cleanup=False)
1069
- # plan_crate_json_text 内部会根据 skip_cleanup 决定是否执行清理
1070
- json_text = plan_crate_json_text(llm_group=llm_group, skip_cleanup=False)
1071
- entries, parse_error = _parse_project_json_entries(json_text)
1072
- if parse_error:
1073
- raise ValueError(f"JSON解析失败: {parse_error}")
1074
- if not entries:
1075
- raise ValueError("[c2rust-llm-planner] 从LLM输出解析目录结构失败。正在中止。")
1076
-
1077
- # 2) 如需应用到磁盘
1078
- if apply:
1079
- target_root = crate_name if crate_name else "."
1080
- try:
1081
- apply_project_structure_from_json(json_text, project_root=target_root)
1082
- print("[c2rust-llm-planner] 项目结构已应用。")
1083
- except Exception as e:
1084
- print(f"[c2rust-llm-planner] 应用项目结构失败: {e}")
1085
- raise
1086
-
1087
- # Post-apply: 检查生成的目录结构,使用 CodeAgent 更新 Cargo.toml
1088
- from jarvis.jarvis_code_agent.code_agent import CodeAgent # 延迟导入以避免全局耦合
1089
- import os
1090
- import subprocess
1091
-
1092
- # 解析 crate 目录路径(与 apply 逻辑保持一致)
1093
- try:
1094
- created_dir = _resolve_created_dir(target_root)
1095
- except Exception:
1096
- # 兜底:无法解析时直接使用传入的 target_root
1097
- created_dir = Path(target_root)
1098
-
1099
- # 在 crate 目录内执行 git 初始化与初始提交(按新策略)
1100
- try:
1101
- # 初始化 git 仓库(若已存在则该命令为幂等)
1102
- subprocess.run(["git", "init"], check=False, cwd=str(created_dir))
1103
- # 添加所有文件并尝试提交
1104
- subprocess.run(["git", "add", "-A"], check=False, cwd=str(created_dir))
1105
- subprocess.run(
1106
- ["git", "commit", "-m", "[c2rust-llm-planner] init crate"],
1107
- check=False,
1108
- cwd=str(created_dir),
1109
- )
1110
- except Exception:
1111
- # 保持稳健,不因 git 失败影响主流程
1112
- pass
1113
-
1114
- # 构建用于 CodeAgent 的目录上下文(简化版树形)
1115
- def _format_tree(root: Path) -> str:
1116
- lines: List[str] = []
1117
- exclude = {".git", "target", ".jarvis"}
1118
- if not root.exists():
1119
- return ""
1120
- for p in sorted(root.rglob("*")):
1121
- if any(part in exclude for part in p.parts):
1122
- continue
1123
- rel = p.relative_to(root)
1124
- depth = len(rel.parts) - 1
1125
- indent = " " * depth
1126
- name = rel.name + ("/" if p.is_dir() else "")
1127
- lines.append(f"{indent}- {name}")
1128
- return "\n".join(lines)
1129
-
1130
- dir_ctx = _format_tree(created_dir)
1131
- crate_pkg_name = created_dir.name
1132
-
1133
- requirement_lines = [
1134
- "目标:在该 crate 目录下确保 `cargo build` 能成功完成;如失败则根据错误最小化修改并重试,直到构建通过为止。",
1135
- f"- crate_dir: {created_dir}",
1136
- f"- crate_name: {crate_pkg_name}",
1137
- "目录结构(部分):",
1138
- dir_ctx,
1139
- "",
1140
- "执行与修复流程(务必按序执行,可多轮迭代):",
1141
- "1) 先补齐 Rust 模块声明(仅最小化追加/升级,不覆盖业务实现):",
1142
- " - 扫描 src 目录:",
1143
- " * 在每个子目录下(除 src 根)创建或更新 mod.rs,仅追加缺失的 `pub mod <child>;` 声明;",
1144
- " * 在 src/lib.rs 中为顶级子模块追加 `pub mod <name>;`;不要创建 src/mod.rs;忽略 lib.rs 与 main.rs 的自引用;",
1145
- " - 若存在 `mod <name>;` 但非 pub,则就地升级为 `pub mod <name>;`,保留原缩进与其他内容;",
1146
- " - 严禁删除现有声明或修改非声明代码;",
1147
- '2) 在 Cargo.toml 的 [package] 中设置 edition:"2024";若本地工具链不支持 2024,请降级为 "2021" 并在说明中记录原因;保留其他已有字段与依赖不变。',
1148
- "3) 根据当前源代码实际情况配置入口:",
1149
- " - 仅库:仅配置 [lib](path=src/lib.rs),不要生成 main.rs;",
1150
- " - 单一可执行:存在 src/main.rs 时配置 [[bin]] 或默认二进制;可选保留 [lib] 以沉淀共享逻辑;",
1151
- " - 多可执行:为每个 src/bin/<name>.rs 配置 [[bin]];共享代码放在 src/lib.rs;",
1152
- " - 不要创建与目录结构不一致的占位入口。",
1153
- "4) 对被作为入口的源文件:若不存在 fn main() 则仅添加最小可用实现(不要改动已存在的实现):",
1154
- ' fn main() { println!("ok"); }',
1155
- "5) 执行一次构建验证:`cargo build -q`(或 `cargo check -q`)。",
1156
- "6) 若构建失败,读取错误并进行最小化修复,然后再次构建;重复直至成功。仅允许的修复类型:",
1157
- " - 依赖缺失:在 [dependencies] 中添加必要且稳定版本的依赖(优先无特性),避免新增未使用依赖;",
1158
- " - 入口/crate-type 配置错误:修正 [lib] 或 [[bin]] 的 name/path/crate-type 使之与目录与入口文件一致;",
1159
- " - 语言/工具链不兼容:将 edition 从 2024 调整为 2021;必要时可添加 rust-version 要求;",
1160
- " - 语法级/最小实现缺失:仅在入口文件中补充必要的 use/空实现/feature gate 以通过编译,避免改动非入口业务文件;",
1161
- " - 不要删除或移动现有文件与目录。",
1162
- "7) 每轮修改后必须运行 `cargo build -q` 验证,直到构建成功为止。",
1163
- "",
1164
- "修改约束:",
1165
- "- 允许修改的文件范围:Cargo.toml、src/lib.rs、src/main.rs、src/bin/*.rs、src/**/mod.rs(仅最小必要变更);除非为修复构建与模块声明补齐,不要修改其他文件。",
1166
- "- 尽量保持现有内容与结构不变,不要引入与构建无关的改动或格式化。",
1167
- "",
1168
- "交付要求:",
1169
- "- 以补丁方式提交实际修改的文件;",
1170
- "- 在最终回复中简要说明所做变更与最终 `cargo build` 的结果(成功/失败及原因)。",
1171
- ]
1172
- requirement_text = "\n".join(requirement_lines)
1173
-
1174
- prev_cwd = os.getcwd()
1175
- try:
1176
- # 切换到 crate 目录运行 CodeAgent 与构建
1177
- os.chdir(str(created_dir))
1178
- print(f"[c2rust-llm-planner] 已切换到 crate 目录: {os.getcwd()},执行 CodeAgent 初始化")
1179
- if llm_group:
1180
- print(f"[c2rust-llm-planner] 使用模型组: {llm_group}")
1181
- try:
1182
- # 验证模型配置在切换目录后是否仍然有效
1183
- from jarvis.jarvis_utils.config import get_normal_model_name, get_normal_platform_name
1184
- if llm_group:
1185
- resolved_model = get_normal_model_name(llm_group)
1186
- resolved_platform = get_normal_platform_name(llm_group)
1187
- print(f"[c2rust-llm-planner] 解析的模型配置: 平台={resolved_platform}, 模型={resolved_model}")
1188
- except Exception as e:
1189
- print(f"[c2rust-llm-planner] 警告: 无法验证模型配置: {e}")
1190
-
1191
- try:
1192
- agent = CodeAgent(need_summary=False, non_interactive=non_interactive, model_group=llm_group)
1193
- # 验证 agent 内部的模型配置
1194
- if hasattr(agent, 'model') and agent.model:
1195
- actual_model = getattr(agent.model, 'model_name', 'unknown')
1196
- actual_platform = type(agent.model).__name__
1197
- print(f"[c2rust-llm-planner] CodeAgent 内部模型: {actual_platform}.{actual_model}")
1198
- agent.run(requirement_text, prefix="[c2rust-llm-planner]", suffix="")
1199
- print("[c2rust-llm-planner] 初始 CodeAgent 运行完成。")
1200
- except Exception as e:
1201
- error_msg = str(e)
1202
- if "does not exist" in error_msg or "404" in error_msg:
1203
- print(f"[c2rust-llm-planner] 模型配置错误: {error_msg}")
1204
- print(f"[c2rust-llm-planner] 提示: 请检查模型组 '{llm_group}' 的配置是否正确")
1205
- print(f"[c2rust-llm-planner] 当前工作目录: {os.getcwd()}")
1206
- # 尝试显示当前解析的模型配置
1207
- try:
1208
- from jarvis.jarvis_utils.config import get_normal_model_name, get_normal_platform_name
1209
- if llm_group:
1210
- print(f"[c2rust-llm-planner] 当前解析的模型: {get_normal_platform_name(llm_group)}/{get_normal_model_name(llm_group)}")
1211
- except Exception:
1212
- pass
1213
- raise
1214
-
1215
- # 进入构建与修复循环:构建失败则生成新的 CodeAgent,携带错误上下文进行最小修复
1216
- iter_count = 0
1217
- while True:
1218
- iter_count += 1
1219
- print(f"[c2rust-llm-planner] 在 {os.getcwd()} 执行: cargo build -q")
1220
- build_res = subprocess.run(
1221
- ["cargo", "build", "-q"],
1222
- capture_output=True,
1223
- text=True,
1224
- check=False,
1225
- )
1226
- stdout = build_res.stdout or ""
1227
- stderr = build_res.stderr or ""
1228
- output = (stdout + "\n" + stderr).strip()
1229
-
1230
- if build_res.returncode == 0:
1231
- print("[c2rust-llm-planner] Cargo 构建成功。")
1232
- break
1233
-
1234
- print(f"[c2rust-llm-planner] Cargo 构建失败 (iter={iter_count})。")
1235
- # 打印编译错误输出,便于可视化与调试
1236
- print("[c2rust-llm-planner] 构建错误输出:")
1237
- print(output)
1238
- # 将错误信息作为上下文,附加修复原则,生成新的 CodeAgent 进行最小修复
1239
- repair_prompt = "\n".join([
1240
- requirement_text,
1241
- "",
1242
- "请根据以下构建错误进行最小化修复,然后再次执行 `cargo build` 验证:",
1243
- "<BUILD_ERROR>",
1244
- output,
1245
- "</BUILD_ERROR>",
1246
- ])
1247
-
1248
- if llm_group:
1249
- print(f"[c2rust-llm-planner][iter={iter_count}] 使用模型组: {llm_group}")
1250
- try:
1251
- repair_agent = CodeAgent(need_summary=False, non_interactive=non_interactive, model_group=llm_group)
1252
- repair_agent.run(repair_prompt, prefix=f"[c2rust-llm-planner][iter={iter_count}]", suffix="")
1253
- except Exception as e:
1254
- error_msg = str(e)
1255
- if "does not exist" in error_msg or "404" in error_msg:
1256
- print(f"[c2rust-llm-planner][iter={iter_count}] 模型配置错误: {error_msg}")
1257
- print(f"[c2rust-llm-planner][iter={iter_count}] 提示: 请检查模型组 '{llm_group}' 的配置")
1258
- raise
1259
- # 不切换目录,保持在原始工作目录
1260
- finally:
1261
- # 恢复之前的工作目录
1262
- os.chdir(prev_cwd)
1263
-
1264
- # 3) 输出 JSON 到文件(如指定),并返回解析后的 entries
1265
- if out is not None:
1266
- out_path = Path(out)
1267
- out_path.parent.mkdir(parents=True, exist_ok=True)
1268
- # 使用原始文本写出,便于可读
1269
- out_path.write_text(json_text, encoding="utf-8")
1270
- print(f"[c2rust-llm-planner] JSON 已写入: {out_path}")
1271
-
1272
- return entries
319
+ __all__ = [
320
+ "LLMRustCratePlannerAgent",
321
+ "plan_crate_json_text",
322
+ "plan_crate_json_llm",
323
+ "execute_llm_plan", # 向后兼容
324
+ "entries_to_json", # 向后兼容
325
+ "apply_project_structure_from_json", # 向后兼容
326
+ ]