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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +567 -222
- jarvis/jarvis_agent/agent_manager.py +19 -12
- jarvis/jarvis_agent/builtin_input_handler.py +79 -11
- jarvis/jarvis_agent/config_editor.py +7 -2
- jarvis/jarvis_agent/event_bus.py +24 -13
- jarvis/jarvis_agent/events.py +19 -1
- jarvis/jarvis_agent/file_context_handler.py +67 -64
- jarvis/jarvis_agent/file_methodology_manager.py +38 -24
- jarvis/jarvis_agent/jarvis.py +186 -114
- jarvis/jarvis_agent/language_extractors/__init__.py +8 -1
- jarvis/jarvis_agent/language_extractors/c_extractor.py +7 -4
- jarvis/jarvis_agent/language_extractors/cpp_extractor.py +9 -4
- jarvis/jarvis_agent/language_extractors/go_extractor.py +7 -4
- jarvis/jarvis_agent/language_extractors/java_extractor.py +27 -20
- jarvis/jarvis_agent/language_extractors/javascript_extractor.py +22 -17
- jarvis/jarvis_agent/language_extractors/python_extractor.py +7 -4
- jarvis/jarvis_agent/language_extractors/rust_extractor.py +7 -4
- jarvis/jarvis_agent/language_extractors/typescript_extractor.py +22 -17
- jarvis/jarvis_agent/language_support_info.py +250 -219
- jarvis/jarvis_agent/main.py +19 -23
- jarvis/jarvis_agent/memory_manager.py +9 -6
- jarvis/jarvis_agent/methodology_share_manager.py +21 -15
- jarvis/jarvis_agent/output_handler.py +4 -2
- jarvis/jarvis_agent/prompt_builder.py +7 -6
- jarvis/jarvis_agent/prompt_manager.py +113 -8
- jarvis/jarvis_agent/prompts.py +317 -85
- jarvis/jarvis_agent/protocols.py +5 -2
- jarvis/jarvis_agent/run_loop.py +192 -32
- jarvis/jarvis_agent/session_manager.py +7 -3
- jarvis/jarvis_agent/share_manager.py +23 -13
- jarvis/jarvis_agent/shell_input_handler.py +12 -8
- jarvis/jarvis_agent/stdio_redirect.py +25 -26
- jarvis/jarvis_agent/task_analyzer.py +29 -23
- jarvis/jarvis_agent/task_list.py +869 -0
- jarvis/jarvis_agent/task_manager.py +26 -23
- jarvis/jarvis_agent/tool_executor.py +6 -5
- jarvis/jarvis_agent/tool_share_manager.py +24 -14
- jarvis/jarvis_agent/user_interaction.py +3 -3
- jarvis/jarvis_agent/utils.py +9 -1
- jarvis/jarvis_agent/web_bridge.py +37 -17
- jarvis/jarvis_agent/web_output_sink.py +5 -2
- jarvis/jarvis_agent/web_server.py +165 -36
- jarvis/jarvis_c2rust/__init__.py +1 -1
- jarvis/jarvis_c2rust/cli.py +260 -141
- jarvis/jarvis_c2rust/collector.py +37 -18
- jarvis/jarvis_c2rust/constants.py +60 -0
- jarvis/jarvis_c2rust/library_replacer.py +242 -1010
- jarvis/jarvis_c2rust/library_replacer_checkpoint.py +133 -0
- jarvis/jarvis_c2rust/library_replacer_llm.py +287 -0
- jarvis/jarvis_c2rust/library_replacer_loader.py +191 -0
- jarvis/jarvis_c2rust/library_replacer_output.py +134 -0
- jarvis/jarvis_c2rust/library_replacer_prompts.py +124 -0
- jarvis/jarvis_c2rust/library_replacer_utils.py +188 -0
- jarvis/jarvis_c2rust/llm_module_agent.py +98 -1044
- jarvis/jarvis_c2rust/llm_module_agent_apply.py +170 -0
- jarvis/jarvis_c2rust/llm_module_agent_executor.py +288 -0
- jarvis/jarvis_c2rust/llm_module_agent_loader.py +170 -0
- jarvis/jarvis_c2rust/llm_module_agent_prompts.py +268 -0
- jarvis/jarvis_c2rust/llm_module_agent_types.py +57 -0
- jarvis/jarvis_c2rust/llm_module_agent_utils.py +150 -0
- jarvis/jarvis_c2rust/llm_module_agent_validator.py +119 -0
- jarvis/jarvis_c2rust/loaders.py +28 -10
- jarvis/jarvis_c2rust/models.py +5 -2
- jarvis/jarvis_c2rust/optimizer.py +192 -1974
- jarvis/jarvis_c2rust/optimizer_build_fix.py +286 -0
- jarvis/jarvis_c2rust/optimizer_clippy.py +766 -0
- jarvis/jarvis_c2rust/optimizer_config.py +49 -0
- jarvis/jarvis_c2rust/optimizer_docs.py +183 -0
- jarvis/jarvis_c2rust/optimizer_options.py +48 -0
- jarvis/jarvis_c2rust/optimizer_progress.py +469 -0
- jarvis/jarvis_c2rust/optimizer_report.py +52 -0
- jarvis/jarvis_c2rust/optimizer_unsafe.py +309 -0
- jarvis/jarvis_c2rust/optimizer_utils.py +469 -0
- jarvis/jarvis_c2rust/optimizer_visibility.py +185 -0
- jarvis/jarvis_c2rust/scanner.py +229 -166
- jarvis/jarvis_c2rust/transpiler.py +531 -2732
- jarvis/jarvis_c2rust/transpiler_agents.py +503 -0
- jarvis/jarvis_c2rust/transpiler_build.py +1294 -0
- jarvis/jarvis_c2rust/transpiler_codegen.py +204 -0
- jarvis/jarvis_c2rust/transpiler_compile.py +146 -0
- jarvis/jarvis_c2rust/transpiler_config.py +178 -0
- jarvis/jarvis_c2rust/transpiler_context.py +122 -0
- jarvis/jarvis_c2rust/transpiler_executor.py +516 -0
- jarvis/jarvis_c2rust/transpiler_generation.py +278 -0
- jarvis/jarvis_c2rust/transpiler_git.py +163 -0
- jarvis/jarvis_c2rust/transpiler_mod_utils.py +225 -0
- jarvis/jarvis_c2rust/transpiler_modules.py +336 -0
- jarvis/jarvis_c2rust/transpiler_planning.py +394 -0
- jarvis/jarvis_c2rust/transpiler_review.py +1196 -0
- jarvis/jarvis_c2rust/transpiler_symbols.py +176 -0
- jarvis/jarvis_c2rust/utils.py +269 -79
- jarvis/jarvis_code_agent/after_change.py +233 -0
- jarvis/jarvis_code_agent/build_validation_config.py +37 -30
- jarvis/jarvis_code_agent/builtin_rules.py +68 -0
- jarvis/jarvis_code_agent/code_agent.py +976 -1517
- jarvis/jarvis_code_agent/code_agent_build.py +227 -0
- jarvis/jarvis_code_agent/code_agent_diff.py +246 -0
- jarvis/jarvis_code_agent/code_agent_git.py +525 -0
- jarvis/jarvis_code_agent/code_agent_impact.py +177 -0
- jarvis/jarvis_code_agent/code_agent_lint.py +283 -0
- jarvis/jarvis_code_agent/code_agent_llm.py +159 -0
- jarvis/jarvis_code_agent/code_agent_postprocess.py +105 -0
- jarvis/jarvis_code_agent/code_agent_prompts.py +46 -0
- jarvis/jarvis_code_agent/code_agent_rules.py +305 -0
- jarvis/jarvis_code_agent/code_analyzer/__init__.py +52 -48
- jarvis/jarvis_code_agent/code_analyzer/base_language.py +12 -10
- jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +12 -11
- jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +16 -12
- jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +26 -17
- jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +558 -104
- jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +27 -16
- jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +22 -18
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +21 -16
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +20 -16
- jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +27 -16
- jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +47 -23
- jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +71 -37
- jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +162 -35
- jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +111 -57
- jarvis/jarvis_code_agent/code_analyzer/build_validator.py +18 -12
- jarvis/jarvis_code_agent/code_analyzer/context_manager.py +185 -183
- jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +2 -1
- jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +24 -15
- jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +227 -141
- jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +321 -247
- jarvis/jarvis_code_agent/code_analyzer/language_registry.py +37 -29
- jarvis/jarvis_code_agent/code_analyzer/language_support.py +21 -13
- jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +15 -9
- jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +75 -45
- jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +87 -52
- jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +84 -51
- jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +94 -64
- jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +109 -71
- jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +97 -63
- jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +103 -69
- jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +271 -268
- jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +76 -64
- jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +92 -19
- jarvis/jarvis_code_agent/diff_visualizer.py +998 -0
- jarvis/jarvis_code_agent/lint.py +223 -524
- jarvis/jarvis_code_agent/rule_share_manager.py +158 -0
- jarvis/jarvis_code_agent/rules/clean_code.md +144 -0
- jarvis/jarvis_code_agent/rules/code_review.md +115 -0
- jarvis/jarvis_code_agent/rules/documentation.md +165 -0
- jarvis/jarvis_code_agent/rules/generate_rules.md +52 -0
- jarvis/jarvis_code_agent/rules/performance.md +158 -0
- jarvis/jarvis_code_agent/rules/refactoring.md +139 -0
- jarvis/jarvis_code_agent/rules/security.md +160 -0
- jarvis/jarvis_code_agent/rules/tdd.md +78 -0
- jarvis/jarvis_code_agent/test_rules/cpp_test.md +118 -0
- jarvis/jarvis_code_agent/test_rules/go_test.md +98 -0
- jarvis/jarvis_code_agent/test_rules/java_test.md +99 -0
- jarvis/jarvis_code_agent/test_rules/javascript_test.md +113 -0
- jarvis/jarvis_code_agent/test_rules/php_test.md +117 -0
- jarvis/jarvis_code_agent/test_rules/python_test.md +91 -0
- jarvis/jarvis_code_agent/test_rules/ruby_test.md +102 -0
- jarvis/jarvis_code_agent/test_rules/rust_test.md +86 -0
- jarvis/jarvis_code_agent/utils.py +36 -26
- jarvis/jarvis_code_analysis/checklists/loader.py +21 -21
- jarvis/jarvis_code_analysis/code_review.py +64 -33
- jarvis/jarvis_data/config_schema.json +285 -192
- jarvis/jarvis_git_squash/main.py +8 -6
- jarvis/jarvis_git_utils/git_commiter.py +53 -76
- jarvis/jarvis_mcp/__init__.py +5 -2
- jarvis/jarvis_mcp/sse_mcp_client.py +40 -30
- jarvis/jarvis_mcp/stdio_mcp_client.py +27 -19
- jarvis/jarvis_mcp/streamable_mcp_client.py +35 -26
- jarvis/jarvis_memory_organizer/memory_organizer.py +78 -55
- jarvis/jarvis_methodology/main.py +48 -39
- jarvis/jarvis_multi_agent/__init__.py +56 -23
- jarvis/jarvis_multi_agent/main.py +15 -18
- jarvis/jarvis_platform/base.py +179 -111
- jarvis/jarvis_platform/human.py +27 -16
- jarvis/jarvis_platform/kimi.py +52 -45
- jarvis/jarvis_platform/openai.py +101 -40
- jarvis/jarvis_platform/registry.py +51 -33
- jarvis/jarvis_platform/tongyi.py +68 -38
- jarvis/jarvis_platform/yuanbao.py +59 -43
- jarvis/jarvis_platform_manager/main.py +68 -76
- jarvis/jarvis_platform_manager/service.py +24 -14
- jarvis/jarvis_rag/README_CONFIG.md +314 -0
- jarvis/jarvis_rag/README_DYNAMIC_LOADING.md +311 -0
- jarvis/jarvis_rag/README_ONLINE_MODELS.md +230 -0
- jarvis/jarvis_rag/__init__.py +57 -4
- jarvis/jarvis_rag/cache.py +3 -1
- jarvis/jarvis_rag/cli.py +48 -68
- jarvis/jarvis_rag/embedding_interface.py +39 -0
- jarvis/jarvis_rag/embedding_manager.py +7 -230
- jarvis/jarvis_rag/embeddings/__init__.py +41 -0
- jarvis/jarvis_rag/embeddings/base.py +114 -0
- jarvis/jarvis_rag/embeddings/cohere.py +66 -0
- jarvis/jarvis_rag/embeddings/edgefn.py +117 -0
- jarvis/jarvis_rag/embeddings/local.py +260 -0
- jarvis/jarvis_rag/embeddings/openai.py +62 -0
- jarvis/jarvis_rag/embeddings/registry.py +293 -0
- jarvis/jarvis_rag/llm_interface.py +8 -6
- jarvis/jarvis_rag/query_rewriter.py +8 -9
- jarvis/jarvis_rag/rag_pipeline.py +61 -52
- jarvis/jarvis_rag/reranker.py +7 -75
- jarvis/jarvis_rag/reranker_interface.py +32 -0
- jarvis/jarvis_rag/rerankers/__init__.py +41 -0
- jarvis/jarvis_rag/rerankers/base.py +109 -0
- jarvis/jarvis_rag/rerankers/cohere.py +67 -0
- jarvis/jarvis_rag/rerankers/edgefn.py +140 -0
- jarvis/jarvis_rag/rerankers/jina.py +79 -0
- jarvis/jarvis_rag/rerankers/local.py +89 -0
- jarvis/jarvis_rag/rerankers/registry.py +293 -0
- jarvis/jarvis_rag/retriever.py +58 -43
- jarvis/jarvis_sec/__init__.py +66 -141
- jarvis/jarvis_sec/agents.py +21 -17
- jarvis/jarvis_sec/analysis.py +80 -33
- jarvis/jarvis_sec/checkers/__init__.py +7 -13
- jarvis/jarvis_sec/checkers/c_checker.py +356 -164
- jarvis/jarvis_sec/checkers/rust_checker.py +47 -29
- jarvis/jarvis_sec/cli.py +43 -21
- jarvis/jarvis_sec/clustering.py +430 -272
- jarvis/jarvis_sec/file_manager.py +99 -55
- jarvis/jarvis_sec/parsers.py +9 -6
- jarvis/jarvis_sec/prompts.py +4 -3
- jarvis/jarvis_sec/report.py +44 -22
- jarvis/jarvis_sec/review.py +180 -107
- jarvis/jarvis_sec/status.py +50 -41
- jarvis/jarvis_sec/types.py +3 -0
- jarvis/jarvis_sec/utils.py +160 -83
- jarvis/jarvis_sec/verification.py +411 -181
- jarvis/jarvis_sec/workflow.py +132 -21
- jarvis/jarvis_smart_shell/main.py +28 -41
- jarvis/jarvis_stats/cli.py +14 -12
- jarvis/jarvis_stats/stats.py +28 -19
- jarvis/jarvis_stats/storage.py +14 -8
- jarvis/jarvis_stats/visualizer.py +12 -7
- jarvis/jarvis_tools/base.py +5 -2
- jarvis/jarvis_tools/clear_memory.py +13 -9
- jarvis/jarvis_tools/cli/main.py +23 -18
- jarvis/jarvis_tools/edit_file.py +572 -873
- jarvis/jarvis_tools/execute_script.py +10 -7
- jarvis/jarvis_tools/file_analyzer.py +7 -8
- jarvis/jarvis_tools/meta_agent.py +287 -0
- jarvis/jarvis_tools/methodology.py +5 -3
- jarvis/jarvis_tools/read_code.py +305 -1438
- jarvis/jarvis_tools/read_symbols.py +50 -17
- jarvis/jarvis_tools/read_webpage.py +19 -18
- jarvis/jarvis_tools/registry.py +435 -156
- jarvis/jarvis_tools/retrieve_memory.py +16 -11
- jarvis/jarvis_tools/save_memory.py +8 -6
- jarvis/jarvis_tools/search_web.py +31 -31
- jarvis/jarvis_tools/sub_agent.py +32 -28
- jarvis/jarvis_tools/sub_code_agent.py +44 -60
- jarvis/jarvis_tools/task_list_manager.py +1811 -0
- jarvis/jarvis_tools/virtual_tty.py +29 -19
- jarvis/jarvis_utils/__init__.py +4 -0
- jarvis/jarvis_utils/builtin_replace_map.py +2 -1
- jarvis/jarvis_utils/clipboard.py +9 -8
- jarvis/jarvis_utils/collections.py +331 -0
- jarvis/jarvis_utils/config.py +699 -194
- jarvis/jarvis_utils/dialogue_recorder.py +294 -0
- jarvis/jarvis_utils/embedding.py +6 -3
- jarvis/jarvis_utils/file_processors.py +7 -1
- jarvis/jarvis_utils/fzf.py +9 -3
- jarvis/jarvis_utils/git_utils.py +71 -42
- jarvis/jarvis_utils/globals.py +116 -32
- jarvis/jarvis_utils/http.py +6 -2
- jarvis/jarvis_utils/input.py +318 -83
- jarvis/jarvis_utils/jsonnet_compat.py +119 -104
- jarvis/jarvis_utils/methodology.py +37 -28
- jarvis/jarvis_utils/output.py +201 -44
- jarvis/jarvis_utils/utils.py +986 -628
- {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/METADATA +49 -33
- jarvis_ai_assistant-1.0.2.dist-info/RECORD +304 -0
- jarvis/jarvis_code_agent/code_analyzer/structured_code.py +0 -556
- jarvis/jarvis_tools/generate_new_tool.py +0 -205
- jarvis/jarvis_tools/lsp_client.py +0 -1552
- jarvis/jarvis_tools/rewrite_file.py +0 -105
- jarvis_ai_assistant-0.7.8.dist-info/RECORD +0 -218
- {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
|
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,
|
|
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.
|
|
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 =
|
|
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
|
|
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
|
|
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.
|
|
161
|
+
roots_ctx = self.prompt_builder.build_roots_context_from_order()
|
|
712
162
|
|
|
713
|
-
system_prompt = self.
|
|
714
|
-
user_prompt = self.
|
|
715
|
-
base_summary_prompt = self.
|
|
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
|
|
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
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
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
|
-
# 进入主循环:第一轮仅输出
|
|
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)
|
|
211
|
+
response = agent.model.chat_until_success(full_prompt)
|
|
758
212
|
summary_output = response
|
|
759
213
|
except Exception as e:
|
|
760
|
-
|
|
761
|
-
|
|
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)
|
|
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.
|
|
223
|
+
json_text = self.validator.extract_json_from_project(project_text)
|
|
768
224
|
|
|
769
225
|
# 尝试解析并校验
|
|
770
|
-
entries, parse_error_json =
|
|
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
|
-
|
|
231
|
+
PrettyOutput.auto_print(
|
|
232
|
+
f"[c2rust-llm-planner] JSON解析失败: {parse_error_json}"
|
|
233
|
+
)
|
|
776
234
|
continue
|
|
777
235
|
|
|
778
|
-
ok, reason = self.
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
289
|
+
perform_pre_cleanup_for_planner(project_root)
|
|
830
290
|
|
|
831
|
-
agent = LLMRustCratePlannerAgent(
|
|
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
|
-
|
|
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
|
-
|
|
858
|
-
""
|
|
859
|
-
|
|
860
|
-
""
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
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
|
+
]
|