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
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""LLM 模块规划 Agent 的提示词构建逻辑。"""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
from typing import Callable
|
|
8
|
+
from typing import Dict
|
|
9
|
+
from typing import List
|
|
10
|
+
|
|
11
|
+
from jarvis.jarvis_c2rust.llm_module_agent_utils import resolve_created_dir
|
|
12
|
+
from jarvis.jarvis_utils.jsonnet_compat import loads as json_loads
|
|
13
|
+
from jarvis.jarvis_utils.tag import ot
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PromptBuilder:
|
|
17
|
+
"""提示词构建器。"""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
project_root: Path,
|
|
22
|
+
loader: Any, # GraphLoader
|
|
23
|
+
crate_name_func: Callable[[], str],
|
|
24
|
+
has_original_main_func: Callable[[], bool],
|
|
25
|
+
append_additional_notes_func: Callable[[str], str],
|
|
26
|
+
):
|
|
27
|
+
self.project_root = project_root
|
|
28
|
+
self.loader = loader
|
|
29
|
+
self.crate_name_func = crate_name_func
|
|
30
|
+
self.has_original_main_func = has_original_main_func
|
|
31
|
+
self.append_additional_notes = append_additional_notes_func
|
|
32
|
+
|
|
33
|
+
def build_roots_context_from_order(self) -> List[Dict[str, Any]]:
|
|
34
|
+
"""
|
|
35
|
+
基于 translation_order.jsonl 生成用于规划的上下文:
|
|
36
|
+
- 以每个 step 的 roots 标签为分组键(通常每步一个 root 标签)
|
|
37
|
+
- 函数列表来自每步的 items 中的符号 'name' 字段,按 root 聚合去重
|
|
38
|
+
- 跳过无 roots 标签的 residual 步骤(仅保留明确 root 的上下文)
|
|
39
|
+
- 若最终未收集到任何 root 组,则回退为单组 'project',包含所有 items 的函数名集合
|
|
40
|
+
"""
|
|
41
|
+
order_path = (
|
|
42
|
+
self.project_root / ".jarvis" / "c2rust" / "translation_order.jsonl"
|
|
43
|
+
)
|
|
44
|
+
if not order_path.exists():
|
|
45
|
+
raise FileNotFoundError(f"未找到 translation_order.jsonl: {order_path}")
|
|
46
|
+
|
|
47
|
+
def _deduplicate_names(names: List[str]) -> List[str]:
|
|
48
|
+
"""去重并排序函数名列表"""
|
|
49
|
+
try:
|
|
50
|
+
return sorted(list(dict.fromkeys(names)))
|
|
51
|
+
except (TypeError, ValueError):
|
|
52
|
+
return sorted(list(set(names)))
|
|
53
|
+
|
|
54
|
+
def _extract_names_from_items(items: List[Any]) -> List[str]:
|
|
55
|
+
"""从 items 中提取函数名"""
|
|
56
|
+
names: List[str] = []
|
|
57
|
+
for it in items:
|
|
58
|
+
if isinstance(it, dict):
|
|
59
|
+
nm = it.get("name") or ""
|
|
60
|
+
if isinstance(nm, str) and nm.strip():
|
|
61
|
+
names.append(str(nm).strip())
|
|
62
|
+
return names
|
|
63
|
+
|
|
64
|
+
groups: Dict[str, List[str]] = {}
|
|
65
|
+
all_names_fallback: List[str] = [] # 用于回退场景
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
with order_path.open("r", encoding="utf-8") as f:
|
|
69
|
+
for line in f:
|
|
70
|
+
line = line.strip()
|
|
71
|
+
if not line:
|
|
72
|
+
continue
|
|
73
|
+
try:
|
|
74
|
+
obj = json_loads(line)
|
|
75
|
+
except Exception:
|
|
76
|
+
continue
|
|
77
|
+
|
|
78
|
+
roots = obj.get("roots") or []
|
|
79
|
+
items = obj.get("items") or []
|
|
80
|
+
if not isinstance(items, list) or not items:
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
# 提取所有函数名(用于回退场景)
|
|
84
|
+
item_names = _extract_names_from_items(items)
|
|
85
|
+
all_names_fallback.extend(item_names)
|
|
86
|
+
|
|
87
|
+
# 提取 root 标签
|
|
88
|
+
root_labels = [
|
|
89
|
+
str(r).strip()
|
|
90
|
+
for r in roots
|
|
91
|
+
if isinstance(r, str) and str(r).strip()
|
|
92
|
+
]
|
|
93
|
+
if not root_labels:
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
# 去重 step_names
|
|
97
|
+
step_names = _deduplicate_names(item_names)
|
|
98
|
+
if not step_names:
|
|
99
|
+
continue
|
|
100
|
+
|
|
101
|
+
# 按 root 聚合
|
|
102
|
+
for r in root_labels:
|
|
103
|
+
groups.setdefault(r, []).extend(step_names)
|
|
104
|
+
except (OSError, IOError) as e:
|
|
105
|
+
raise RuntimeError(f"读取 translation_order.jsonl 时发生错误: {e}") from e
|
|
106
|
+
|
|
107
|
+
contexts: List[Dict[str, Any]] = []
|
|
108
|
+
for root_label, names in groups.items():
|
|
109
|
+
names = _deduplicate_names(names)
|
|
110
|
+
contexts.append({"root_function": root_label, "functions": sorted(names)})
|
|
111
|
+
|
|
112
|
+
# 回退:如果没有任何 root 组,使用所有 items 作为单组 'project'
|
|
113
|
+
if not contexts:
|
|
114
|
+
all_names = _deduplicate_names(all_names_fallback)
|
|
115
|
+
if all_names:
|
|
116
|
+
contexts.append(
|
|
117
|
+
{"root_function": "project", "functions": sorted(all_names)}
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
return contexts
|
|
121
|
+
|
|
122
|
+
def build_user_prompt(self, roots_context: List[Dict[str, Any]]) -> str:
|
|
123
|
+
"""
|
|
124
|
+
主对话阶段:传入上下文,不给出输出要求,仅用于让模型获取信息并触发进入总结阶段。
|
|
125
|
+
请模型仅输出 {ot('!!!COMPLETE!!!')} 以进入总结(summary)阶段。不要输出其他任何内容,任务总结将会在后面的交互中被询问。
|
|
126
|
+
"""
|
|
127
|
+
crate_name = self.crate_name_func()
|
|
128
|
+
has_main = self.has_original_main_func()
|
|
129
|
+
created_dir = resolve_created_dir(self.project_root)
|
|
130
|
+
context_json = json.dumps(
|
|
131
|
+
{
|
|
132
|
+
"meta": {
|
|
133
|
+
"crate_name": crate_name,
|
|
134
|
+
"main_present": has_main,
|
|
135
|
+
"crate_dir": str(created_dir),
|
|
136
|
+
},
|
|
137
|
+
"roots": roots_context,
|
|
138
|
+
},
|
|
139
|
+
ensure_ascii=False,
|
|
140
|
+
indent=2,
|
|
141
|
+
)
|
|
142
|
+
prompt = f"""
|
|
143
|
+
下面提供了项目的调用图上下文(JSON),请先通读理解,不要输出任何规划或JSON内容:
|
|
144
|
+
<context>
|
|
145
|
+
{context_json}
|
|
146
|
+
</context>
|
|
147
|
+
|
|
148
|
+
如果已准备好进入总结阶段以生成完整输出,请仅输出:{ot("!!!COMPLETE!!!")},不要输出其他任何内容。任务总结将会在后面的交互中被询问。
|
|
149
|
+
""".strip()
|
|
150
|
+
return self.append_additional_notes(prompt)
|
|
151
|
+
|
|
152
|
+
def build_system_prompt(self) -> str:
|
|
153
|
+
"""
|
|
154
|
+
系统提示:描述如何基于依赖关系进行 crate 规划的原则(不涉及对话流程或输出方式)
|
|
155
|
+
"""
|
|
156
|
+
crate_name = self.crate_name_func()
|
|
157
|
+
prompt = (
|
|
158
|
+
"你是资深 Rust 架构师。任务:根据给定的函数级调用关系(仅包含 root_function 及其可达的函数名列表),为目标项目规划合理的 Rust crate 结构。\n"
|
|
159
|
+
"\n"
|
|
160
|
+
"规划原则:\n"
|
|
161
|
+
"- 根导向:以每个 root_function 为边界组织顶层模块,形成清晰的入口与责任范围。\n"
|
|
162
|
+
"- 内聚优先:按调用内聚性拆分子模块,使强相关函数位于同一子模块,减少跨模块耦合。\n"
|
|
163
|
+
"- 去环与分层:尽量消除循环依赖;遵循由上到下的调用方向,保持稳定依赖方向与层次清晰。\n"
|
|
164
|
+
"- 共享抽取:被多个 root 使用的通用能力抽取到 common/ 或 shared/ 模块,避免重复与交叉依赖。\n"
|
|
165
|
+
"- 边界隔离:将平台/IO/外设等边界能力独立到 adapter/ 或 ffi/ 等模块(如存在)。\n"
|
|
166
|
+
"- 命名规范:目录/文件采用小写下划线;模块名简洁可读,避免特殊字符与过长名称。\n"
|
|
167
|
+
"- 可演进性:模块粒度适中,保留扩展点,便于后续重构与逐步替换遗留代码。\n"
|
|
168
|
+
"- 模块组织:每个目录的 mod.rs 声明其子目录与 .rs 子模块;顶层 lib.rs 汇聚导出主要模块与公共能力。\n"
|
|
169
|
+
"- 入口策略(务必遵循,bin 仅做入口,功能尽量在 lib 中实现):\n"
|
|
170
|
+
" * 若原始项目包含 main 函数:不要生成 src/main.rs;使用 src/bin/"
|
|
171
|
+
+ crate_name
|
|
172
|
+
+ ".rs 作为唯一可执行入口,并在其中仅保留最小入口逻辑(调用库层);共享代码放在 src/lib.rs;\n"
|
|
173
|
+
" * 若原始项目不包含 main 函数:不要生成任何二进制入口(不创建 src/main.rs 或 src/bin/),仅生成 src/lib.rs;\n"
|
|
174
|
+
" * 多可执行仅在确有多个清晰入口时才使用 src/bin/<name>.rs;每个 bin 文件仅做入口,尽量调用库;\n"
|
|
175
|
+
" * 二进制命名:<name> 使用小写下划线,体现入口意图,避免与模块/文件重名。\n"
|
|
176
|
+
)
|
|
177
|
+
return self.append_additional_notes(prompt)
|
|
178
|
+
|
|
179
|
+
def build_summary_prompt(self, roots_context: List[Dict[str, Any]]) -> str:
|
|
180
|
+
"""
|
|
181
|
+
总结阶段:只输出目录结构的 JSON。
|
|
182
|
+
要求:
|
|
183
|
+
- 仅输出一个 <PROJECT> 块
|
|
184
|
+
- <PROJECT> 与 </PROJECT> 之间必须是可解析的 JSON 数组
|
|
185
|
+
- 目录以对象表示,键为 '目录名/',值为子项数组;文件为字符串
|
|
186
|
+
- 块外不得有任何字符(包括空行、注释、Markdown、解释文字、schema等)
|
|
187
|
+
- 不要输出 crate 名称或其他多余字段
|
|
188
|
+
"""
|
|
189
|
+
has_main = self.has_original_main_func()
|
|
190
|
+
crate_name = self.crate_name_func()
|
|
191
|
+
guidance_common = """
|
|
192
|
+
输出规范:
|
|
193
|
+
- 只输出一个 <PROJECT> 块
|
|
194
|
+
- 块外不得有任何字符(包括空行、注释、Markdown 等)
|
|
195
|
+
- 块内必须是 JSON 数组:
|
|
196
|
+
- 目录项使用对象表示,键为 '<name>/',值为子项数组
|
|
197
|
+
- 文件为字符串项(例如 "lib.rs")
|
|
198
|
+
- 不要创建与入口无关的占位文件
|
|
199
|
+
- 支持jsonnet语法(如尾随逗号、注释、||| 或 ``` 分隔符多行字符串等)
|
|
200
|
+
""".strip()
|
|
201
|
+
if has_main:
|
|
202
|
+
entry_rule = f"""
|
|
203
|
+
入口约定(基于原始项目存在 main):
|
|
204
|
+
- 必须包含 src/lib.rs;
|
|
205
|
+
- 不要包含 src/main.rs;
|
|
206
|
+
- 必须包含 src/bin/{crate_name}.rs,作为唯一可执行入口(仅做入口,调用库逻辑);
|
|
207
|
+
- 如无明确多个入口,不要创建额外 bin 文件。
|
|
208
|
+
正确示例(JSON格式):
|
|
209
|
+
<PROJECT>
|
|
210
|
+
[
|
|
211
|
+
"Cargo.toml",
|
|
212
|
+
{{
|
|
213
|
+
"src/": [
|
|
214
|
+
"lib.rs",
|
|
215
|
+
{{
|
|
216
|
+
"bin/": [
|
|
217
|
+
"{crate_name}.rs"
|
|
218
|
+
]
|
|
219
|
+
}}
|
|
220
|
+
]
|
|
221
|
+
}}
|
|
222
|
+
]
|
|
223
|
+
</PROJECT>
|
|
224
|
+
""".strip()
|
|
225
|
+
else:
|
|
226
|
+
entry_rule = """
|
|
227
|
+
入口约定(基于原始项目不存在 main):
|
|
228
|
+
- 必须包含 src/lib.rs;
|
|
229
|
+
- 不要包含 src/main.rs;
|
|
230
|
+
- 不要包含 src/bin/ 目录。
|
|
231
|
+
正确示例(JSON格式):
|
|
232
|
+
<PROJECT>
|
|
233
|
+
[
|
|
234
|
+
"Cargo.toml",
|
|
235
|
+
{
|
|
236
|
+
"src/": [
|
|
237
|
+
"lib.rs"
|
|
238
|
+
]
|
|
239
|
+
}
|
|
240
|
+
]
|
|
241
|
+
</PROJECT>
|
|
242
|
+
""".strip()
|
|
243
|
+
guidance = f"{guidance_common}\n{entry_rule}"
|
|
244
|
+
prompt = f"""
|
|
245
|
+
请基于之前对话中已提供的<context>信息,生成总结输出(项目目录结构的 JSON)。严格遵循以下要求:
|
|
246
|
+
|
|
247
|
+
{guidance}
|
|
248
|
+
|
|
249
|
+
你的输出必须仅包含以下单个块(用项目的真实目录结构替换块内内容):
|
|
250
|
+
<PROJECT>
|
|
251
|
+
[...]
|
|
252
|
+
</PROJECT>
|
|
253
|
+
""".strip()
|
|
254
|
+
return self.append_additional_notes(prompt)
|
|
255
|
+
|
|
256
|
+
def build_retry_summary_prompt(
|
|
257
|
+
self, base_summary_prompt: str, error_reason: str
|
|
258
|
+
) -> str:
|
|
259
|
+
"""
|
|
260
|
+
在原始 summary_prompt 基础上,附加错误反馈,要求严格重试。
|
|
261
|
+
"""
|
|
262
|
+
feedback = (
|
|
263
|
+
"\n\n[格式校验失败,必须重试]\n"
|
|
264
|
+
f"- 失败原因:{error_reason}\n"
|
|
265
|
+
'- 请严格遵循上述"输出规范"与"入口约定",重新输出;\n'
|
|
266
|
+
"- 仅输出一个 <PROJECT> 块,块内为可解析的 JSON 数组;块外不得有任何字符。\n"
|
|
267
|
+
)
|
|
268
|
+
return base_summary_prompt + feedback
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""LLM 模块规划 Agent 的数据类型和工具函数。"""
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class FnMeta:
|
|
10
|
+
"""函数元数据。"""
|
|
11
|
+
|
|
12
|
+
id: int
|
|
13
|
+
name: str
|
|
14
|
+
qname: str
|
|
15
|
+
signature: str
|
|
16
|
+
file: str
|
|
17
|
+
refs: List[str]
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def label(self) -> str:
|
|
21
|
+
"""返回函数的标签(用于显示)。"""
|
|
22
|
+
base = self.qname or self.name or f"fn_{self.id}"
|
|
23
|
+
if self.signature and self.signature != base:
|
|
24
|
+
return f"{base}\n{self.signature}"
|
|
25
|
+
return base
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def top_namespace(self) -> str:
|
|
29
|
+
"""
|
|
30
|
+
提取顶层命名空间/类名:
|
|
31
|
+
- qualified_name 形如 ns1::ns2::Class::method -> 返回 ns1
|
|
32
|
+
- C 函数或无命名空间 -> 返回 "c"
|
|
33
|
+
"""
|
|
34
|
+
if self.qname and "::" in self.qname:
|
|
35
|
+
return self.qname.split("::", 1)[0] or "c"
|
|
36
|
+
return "c"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def sanitize_mod_name(s: str) -> str:
|
|
40
|
+
"""
|
|
41
|
+
清理模块名称,使其符合 Rust 模块命名规范。
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
s: 原始名称
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
清理后的模块名称
|
|
48
|
+
"""
|
|
49
|
+
s = (s or "").replace("::", "__")
|
|
50
|
+
safe = []
|
|
51
|
+
for ch in s:
|
|
52
|
+
if ch.isalnum() or ch == "_":
|
|
53
|
+
safe.append(ch.lower())
|
|
54
|
+
else:
|
|
55
|
+
safe.append("_")
|
|
56
|
+
out = "".join(safe).strip("_")
|
|
57
|
+
return out[:80] or "mod"
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""LLM 模块规划 Agent 的工具函数。"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import shutil
|
|
5
|
+
|
|
6
|
+
from jarvis.jarvis_utils.output import PrettyOutput
|
|
7
|
+
|
|
8
|
+
# -*- coding: utf-8 -*-
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
from typing import List
|
|
13
|
+
from typing import Optional
|
|
14
|
+
from typing import Tuple
|
|
15
|
+
from typing import Union
|
|
16
|
+
|
|
17
|
+
from jarvis.jarvis_utils.input import user_confirm
|
|
18
|
+
from jarvis.jarvis_utils.jsonnet_compat import loads as json_loads
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def perform_pre_cleanup_for_planner(project_root: Union[Path, str]) -> None:
|
|
22
|
+
"""
|
|
23
|
+
预清理:如存在将删除将要生成的 crate 目录、当前目录的 workspace 文件 Cargo.toml、
|
|
24
|
+
以及 project_root/.jarvis/c2rust 下的 progress.json 与 symbol_map.jsonl。
|
|
25
|
+
用户不同意则直接退出。
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
cwd = Path(".").resolve()
|
|
29
|
+
except (OSError, ValueError) as e:
|
|
30
|
+
raise RuntimeError(f"无法解析当前工作目录: {e}") from e
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
requested_root = Path(project_root).resolve()
|
|
34
|
+
except (OSError, ValueError):
|
|
35
|
+
requested_root = Path(project_root)
|
|
36
|
+
|
|
37
|
+
created_dir = (
|
|
38
|
+
cwd.parent / f"{cwd.name}_rs" if requested_root == cwd else requested_root
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
cargo_path = cwd / "Cargo.toml"
|
|
42
|
+
data_dir = requested_root / ".jarvis" / "c2rust"
|
|
43
|
+
progress_path = data_dir / "progress.json"
|
|
44
|
+
symbol_map_jsonl_path = data_dir / "symbol_map.jsonl"
|
|
45
|
+
|
|
46
|
+
targets: List[str] = []
|
|
47
|
+
if created_dir.exists():
|
|
48
|
+
targets.append(f"- 删除 crate 目录(如存在):{created_dir}")
|
|
49
|
+
if cargo_path.exists():
|
|
50
|
+
targets.append(f"- 删除工作区文件:{cargo_path}")
|
|
51
|
+
if progress_path.exists():
|
|
52
|
+
targets.append(f"- 删除进度文件:{progress_path}")
|
|
53
|
+
if symbol_map_jsonl_path.exists():
|
|
54
|
+
targets.append(f"- 删除符号映射文件:{symbol_map_jsonl_path}")
|
|
55
|
+
|
|
56
|
+
if not targets:
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
tip_lines = ["将执行以下清理操作:"] + targets + ["", "是否继续?"]
|
|
60
|
+
if not user_confirm("\n".join(tip_lines), default=False):
|
|
61
|
+
PrettyOutput.auto_print("[c2rust-llm-planner] 用户取消清理操作,退出。")
|
|
62
|
+
sys.exit(0)
|
|
63
|
+
|
|
64
|
+
# 执行清理操作
|
|
65
|
+
try:
|
|
66
|
+
if created_dir.exists():
|
|
67
|
+
shutil.rmtree(created_dir, ignore_errors=True)
|
|
68
|
+
if cargo_path.exists():
|
|
69
|
+
cargo_path.unlink()
|
|
70
|
+
if progress_path.exists():
|
|
71
|
+
progress_path.unlink()
|
|
72
|
+
if symbol_map_jsonl_path.exists():
|
|
73
|
+
symbol_map_jsonl_path.unlink()
|
|
74
|
+
except (OSError, PermissionError) as e:
|
|
75
|
+
raise RuntimeError(f"清理操作失败: {e}") from e
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def resolve_created_dir(target_root: Union[Path, str]) -> Path:
|
|
79
|
+
"""
|
|
80
|
+
解析 crate 目录路径:
|
|
81
|
+
- 若 target_root 为 "." 或解析后等于当前工作目录,则返回 "<cwd.name>_rs" 目录;
|
|
82
|
+
- 否则返回解析后的目标路径;
|
|
83
|
+
- 解析失败则回退到 Path(target_root)。
|
|
84
|
+
"""
|
|
85
|
+
try:
|
|
86
|
+
cwd = Path(".").resolve()
|
|
87
|
+
try:
|
|
88
|
+
resolved_target = Path(target_root).resolve()
|
|
89
|
+
except Exception:
|
|
90
|
+
resolved_target = Path(target_root)
|
|
91
|
+
if target_root == "." or resolved_target == cwd:
|
|
92
|
+
return cwd.parent / f"{cwd.name}_rs"
|
|
93
|
+
return resolved_target
|
|
94
|
+
except Exception:
|
|
95
|
+
return Path(target_root)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def parse_project_json_entries_fallback(json_text: str) -> List[Any]:
|
|
99
|
+
"""
|
|
100
|
+
Fallback 解析器:当 jsonnet 解析失败时,尝试使用标准 json 解析。
|
|
101
|
+
注意:此函数主要用于兼容性,正常情况下应使用 jsonnet 解析。
|
|
102
|
+
"""
|
|
103
|
+
try:
|
|
104
|
+
import json as std_json
|
|
105
|
+
|
|
106
|
+
data = std_json.loads(json_text)
|
|
107
|
+
if isinstance(data, list):
|
|
108
|
+
return data
|
|
109
|
+
return []
|
|
110
|
+
except Exception:
|
|
111
|
+
return []
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def parse_project_json_entries(json_text: str) -> Tuple[List[Any], Optional[str]]:
|
|
115
|
+
"""
|
|
116
|
+
使用 jsonnet 解析 <PROJECT> 块中的目录结构 JSON 为列表结构:
|
|
117
|
+
- 文件项: 字符串,如 "lib.rs"
|
|
118
|
+
- 目录项: 字典,形如 {"src/": [ ... ]} 或 {"src": [ ... ]}
|
|
119
|
+
返回(解析结果, 错误信息)
|
|
120
|
+
如果解析成功,返回(data, None)
|
|
121
|
+
如果解析失败,返回([], 错误信息)
|
|
122
|
+
使用 jsonnet 解析,支持更宽松的 JSON 语法(如尾随逗号、注释等)。
|
|
123
|
+
"""
|
|
124
|
+
try:
|
|
125
|
+
try:
|
|
126
|
+
data = json_loads(json_text)
|
|
127
|
+
if isinstance(data, list):
|
|
128
|
+
return data, None
|
|
129
|
+
# 如果解析结果不是列表
|
|
130
|
+
return [], f"JSON 解析结果不是数组,而是 {type(data).__name__}"
|
|
131
|
+
except Exception as json_err:
|
|
132
|
+
# JSON 解析错误
|
|
133
|
+
error_msg = f"JSON 解析失败: {str(json_err)}"
|
|
134
|
+
return [], error_msg
|
|
135
|
+
except Exception as e:
|
|
136
|
+
# 其他未知错误
|
|
137
|
+
return [], f"解析过程发生异常: {str(e)}"
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def entries_to_json(entries: List[Any]) -> str:
|
|
141
|
+
"""
|
|
142
|
+
将解析后的 entries 列表序列化为 JSON 文本(目录使用对象表示,文件为字符串)
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
entries: 解析后的目录结构列表
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
JSON 文本
|
|
149
|
+
"""
|
|
150
|
+
return json.dumps(entries, ensure_ascii=False, indent=2)
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""LLM 模块规划 Agent 的验证器。"""
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
from typing import Any
|
|
6
|
+
from typing import Callable
|
|
7
|
+
from typing import List
|
|
8
|
+
from typing import Optional
|
|
9
|
+
from typing import Tuple
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ProjectValidator:
|
|
13
|
+
"""项目结构验证器。"""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
crate_name_func: Callable[[], str],
|
|
18
|
+
has_original_main_func: Callable[[], bool],
|
|
19
|
+
):
|
|
20
|
+
self.crate_name_func = crate_name_func
|
|
21
|
+
self.has_original_main_func = has_original_main_func
|
|
22
|
+
|
|
23
|
+
def extract_json_from_project(self, text: str) -> str:
|
|
24
|
+
"""
|
|
25
|
+
从 <PROJECT> 块中提取内容作为最终 JSON;若未匹配,返回原文本(兜底)。
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
text: 包含 <PROJECT> 块的文本
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
提取的 JSON 文本
|
|
32
|
+
"""
|
|
33
|
+
if not isinstance(text, str) or not text:
|
|
34
|
+
return ""
|
|
35
|
+
m_proj = re.search(r"<PROJECT>([\s\S]*?)</PROJECT>", text, flags=re.IGNORECASE)
|
|
36
|
+
if m_proj:
|
|
37
|
+
return m_proj.group(1).strip()
|
|
38
|
+
return text.strip()
|
|
39
|
+
|
|
40
|
+
def validate_project_entries(self, entries: List[Any]) -> Tuple[bool, str]:
|
|
41
|
+
"""
|
|
42
|
+
校验目录结构是否满足强约束:
|
|
43
|
+
- 必须存在 src/lib.rs
|
|
44
|
+
- 若原始项目包含 main:
|
|
45
|
+
* 不允许 src/main.rs
|
|
46
|
+
* 必须包含 src/bin/<crate_name>.rs
|
|
47
|
+
- 若原始项目不包含 main:
|
|
48
|
+
* 不允许 src/main.rs
|
|
49
|
+
* 不允许存在 src/bin/ 目录
|
|
50
|
+
返回 (是否通过, 错误原因)
|
|
51
|
+
"""
|
|
52
|
+
if not isinstance(entries, list) or not entries:
|
|
53
|
+
return False, "JSON 不可解析或为空数组"
|
|
54
|
+
|
|
55
|
+
# 提取 src 目录子项
|
|
56
|
+
src_children: Optional[List[Any]] = None
|
|
57
|
+
for it in entries:
|
|
58
|
+
if isinstance(it, dict) and len(it) == 1:
|
|
59
|
+
k, v = next(iter(it.items()))
|
|
60
|
+
kk = str(k).rstrip("/").strip().lower()
|
|
61
|
+
if kk == "src":
|
|
62
|
+
if isinstance(v, list):
|
|
63
|
+
src_children = v
|
|
64
|
+
else:
|
|
65
|
+
src_children = []
|
|
66
|
+
break
|
|
67
|
+
if src_children is None:
|
|
68
|
+
return False, "缺少 src 目录"
|
|
69
|
+
|
|
70
|
+
# 建立便捷索引
|
|
71
|
+
def has_file(name: str) -> bool:
|
|
72
|
+
for ch in src_children or []:
|
|
73
|
+
if isinstance(ch, str) and ch.strip().lower() == name.lower():
|
|
74
|
+
return True
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
def find_dir(name: str) -> Optional[List[Any]]:
|
|
78
|
+
for ch in src_children or []:
|
|
79
|
+
if isinstance(ch, dict) and len(ch) == 1:
|
|
80
|
+
k, v = next(iter(ch.items()))
|
|
81
|
+
kk = str(k).rstrip("/").strip().lower()
|
|
82
|
+
if kk == name.lower():
|
|
83
|
+
return v if isinstance(v, list) else []
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
# 1) 必须包含 lib.rs
|
|
87
|
+
if not has_file("lib.rs"):
|
|
88
|
+
return False, "src 目录下必须包含 lib.rs"
|
|
89
|
+
|
|
90
|
+
has_main = self.has_original_main_func()
|
|
91
|
+
crate_name = self.crate_name_func()
|
|
92
|
+
|
|
93
|
+
# 2) 入口约束
|
|
94
|
+
if has_main:
|
|
95
|
+
# 不允许 src/main.rs
|
|
96
|
+
if has_file("main.rs"):
|
|
97
|
+
return (
|
|
98
|
+
False,
|
|
99
|
+
"原始项目包含 main:不应生成 src/main.rs,请使用 src/bin/<crate>.rs",
|
|
100
|
+
)
|
|
101
|
+
# 必须包含 src/bin/<crate_name>.rs
|
|
102
|
+
bin_children = find_dir("bin")
|
|
103
|
+
if bin_children is None:
|
|
104
|
+
return False, f"原始项目包含 main:必须包含 src/bin/{crate_name}.rs"
|
|
105
|
+
expect_bin = f"{crate_name}.rs".lower()
|
|
106
|
+
if not any(
|
|
107
|
+
isinstance(ch, str) and ch.strip().lower() == expect_bin
|
|
108
|
+
for ch in bin_children
|
|
109
|
+
):
|
|
110
|
+
return False, f"原始项目包含 main:必须包含 src/bin/{crate_name}.rs"
|
|
111
|
+
else:
|
|
112
|
+
# 不允许 src/main.rs
|
|
113
|
+
if has_file("main.rs"):
|
|
114
|
+
return False, "原始项目不包含 main:不应生成 src/main.rs"
|
|
115
|
+
# 不允许有 bin 目录
|
|
116
|
+
if find_dir("bin") is not None:
|
|
117
|
+
return False, "原始项目不包含 main:不应生成 src/bin/ 目录"
|
|
118
|
+
|
|
119
|
+
return True, ""
|
jarvis/jarvis_c2rust/loaders.py
CHANGED
|
@@ -2,14 +2,20 @@
|
|
|
2
2
|
"""
|
|
3
3
|
C2Rust 转译器数据加载器
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
from __future__ import annotations
|
|
6
7
|
|
|
7
8
|
import json
|
|
8
9
|
import time
|
|
9
10
|
from pathlib import Path
|
|
10
|
-
from typing import Any
|
|
11
|
-
|
|
12
|
-
from
|
|
11
|
+
from typing import Any
|
|
12
|
+
from typing import Dict
|
|
13
|
+
from typing import List
|
|
14
|
+
from typing import Optional
|
|
15
|
+
from typing import Tuple
|
|
16
|
+
|
|
17
|
+
from jarvis.jarvis_c2rust.constants import C2RUST_DIRNAME
|
|
18
|
+
from jarvis.jarvis_c2rust.constants import SYMBOLS_JSONL
|
|
13
19
|
from jarvis.jarvis_c2rust.models import FnRecord
|
|
14
20
|
|
|
15
21
|
|
|
@@ -23,9 +29,7 @@ class _DbLoader:
|
|
|
23
29
|
self.symbols_path = self.data_dir / SYMBOLS_JSONL
|
|
24
30
|
# 统一流程:仅使用 symbols.jsonl,不再兼容 functions.jsonl
|
|
25
31
|
if not self.symbols_path.exists():
|
|
26
|
-
raise FileNotFoundError(
|
|
27
|
-
f"在目录下未找到 symbols.jsonl: {self.data_dir}"
|
|
28
|
-
)
|
|
32
|
+
raise FileNotFoundError(f"在目录下未找到 symbols.jsonl: {self.data_dir}")
|
|
29
33
|
|
|
30
34
|
self.fn_by_id: Dict[int, FnRecord] = {}
|
|
31
35
|
self.name_to_id: Dict[str, int] = {}
|
|
@@ -36,6 +40,7 @@ class _DbLoader:
|
|
|
36
40
|
读取统一的 symbols.jsonl。
|
|
37
41
|
不区分函数与类型定义,均加载为通用记录(位置与引用信息)。
|
|
38
42
|
"""
|
|
43
|
+
|
|
39
44
|
def _iter_records_from_file(path: Path):
|
|
40
45
|
try:
|
|
41
46
|
with path.open("r", encoding="utf-8") as f:
|
|
@@ -68,7 +73,11 @@ class _DbLoader:
|
|
|
68
73
|
sc = int(obj.get("start_col") or 0)
|
|
69
74
|
er = int(obj.get("end_line") or 0)
|
|
70
75
|
ec = int(obj.get("end_col") or 0)
|
|
71
|
-
lr =
|
|
76
|
+
lr = (
|
|
77
|
+
obj.get("lib_replacement")
|
|
78
|
+
if isinstance(obj.get("lib_replacement"), dict)
|
|
79
|
+
else None
|
|
80
|
+
)
|
|
72
81
|
rec = FnRecord(
|
|
73
82
|
id=fid,
|
|
74
83
|
name=nm,
|
|
@@ -167,7 +176,12 @@ class _SymbolMapJsonl:
|
|
|
167
176
|
if not k:
|
|
168
177
|
continue
|
|
169
178
|
self.by_key.setdefault(k, []).append(idx)
|
|
170
|
-
pos_key = (
|
|
179
|
+
pos_key = (
|
|
180
|
+
str(rec.get("c_file") or ""),
|
|
181
|
+
int(rec.get("start_line") or 0),
|
|
182
|
+
int(rec.get("end_line") or 0),
|
|
183
|
+
str(rec.get("c_qname") or rec.get("c_name") or ""),
|
|
184
|
+
)
|
|
171
185
|
self.by_pos.setdefault(pos_key, []).append(idx)
|
|
172
186
|
|
|
173
187
|
def has_symbol(self, sym: str) -> bool:
|
|
@@ -182,7 +196,12 @@ class _SymbolMapJsonl:
|
|
|
182
196
|
return recs[-1] if recs else None
|
|
183
197
|
|
|
184
198
|
def has_rec(self, rec: FnRecord) -> bool:
|
|
185
|
-
key = (
|
|
199
|
+
key = (
|
|
200
|
+
str(rec.file or ""),
|
|
201
|
+
int(rec.start_line or 0),
|
|
202
|
+
int(rec.end_line or 0),
|
|
203
|
+
str(rec.qname or rec.name or ""),
|
|
204
|
+
)
|
|
186
205
|
return bool(self.by_pos.get(key))
|
|
187
206
|
|
|
188
207
|
def add(self, rec: FnRecord, module: str, rust_symbol: str) -> None:
|
|
@@ -204,4 +223,3 @@ class _SymbolMapJsonl:
|
|
|
204
223
|
except Exception:
|
|
205
224
|
pass
|
|
206
225
|
self._add_record_in_memory(obj)
|
|
207
|
-
|