jarvis-ai-assistant 0.5.1__py3-none-any.whl → 0.6.0__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 +15 -4
- jarvis/jarvis_agent/agent_manager.py +3 -0
- jarvis/jarvis_agent/jarvis.py +44 -14
- jarvis/jarvis_agent/run_loop.py +6 -1
- jarvis/jarvis_agent/task_planner.py +1 -0
- jarvis/jarvis_c2rust/__init__.py +13 -0
- jarvis/jarvis_c2rust/cli.py +405 -0
- jarvis/jarvis_c2rust/collector.py +209 -0
- jarvis/jarvis_c2rust/library_replacer.py +933 -0
- jarvis/jarvis_c2rust/llm_module_agent.py +1265 -0
- jarvis/jarvis_c2rust/scanner.py +1671 -0
- jarvis/jarvis_c2rust/transpiler.py +1236 -0
- jarvis/jarvis_code_agent/code_agent.py +144 -18
- jarvis/jarvis_data/config_schema.json +8 -3
- jarvis/jarvis_tools/cli/main.py +1 -0
- jarvis/jarvis_tools/execute_script.py +1 -1
- jarvis/jarvis_tools/read_code.py +11 -1
- jarvis/jarvis_tools/read_symbols.py +129 -0
- jarvis/jarvis_tools/registry.py +9 -1
- jarvis/jarvis_utils/config.py +14 -4
- jarvis/jarvis_utils/git_utils.py +39 -0
- jarvis/jarvis_utils/utils.py +13 -5
- {jarvis_ai_assistant-0.5.1.dist-info → jarvis_ai_assistant-0.6.0.dist-info}/METADATA +13 -1
- {jarvis_ai_assistant-0.5.1.dist-info → jarvis_ai_assistant-0.6.0.dist-info}/RECORD +29 -21
- {jarvis_ai_assistant-0.5.1.dist-info → jarvis_ai_assistant-0.6.0.dist-info}/entry_points.txt +2 -0
- {jarvis_ai_assistant-0.5.1.dist-info → jarvis_ai_assistant-0.6.0.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.5.1.dist-info → jarvis_ai_assistant-0.6.0.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.5.1.dist-info → jarvis_ai_assistant-0.6.0.dist-info}/top_level.txt +0 -0
jarvis/__init__.py
CHANGED
jarvis/jarvis_agent/__init__.py
CHANGED
|
@@ -77,6 +77,7 @@ from jarvis.jarvis_utils.config import (
|
|
|
77
77
|
get_tool_filter_threshold,
|
|
78
78
|
get_after_tool_call_cb_dirs,
|
|
79
79
|
get_plan_max_depth,
|
|
80
|
+
is_plan_enabled,
|
|
80
81
|
)
|
|
81
82
|
from jarvis.jarvis_utils.embedding import get_context_token_count
|
|
82
83
|
from jarvis.jarvis_utils.globals import (
|
|
@@ -317,11 +318,12 @@ class Agent:
|
|
|
317
318
|
use_methodology: Optional[bool] = None,
|
|
318
319
|
use_analysis: Optional[bool] = None,
|
|
319
320
|
force_save_memory: Optional[bool] = None,
|
|
321
|
+
disable_file_edit: bool = False,
|
|
320
322
|
files: Optional[List[str]] = None,
|
|
321
323
|
confirm_callback: Optional[Callable[[str, bool], bool]] = None,
|
|
322
324
|
non_interactive: Optional[bool] = None,
|
|
323
325
|
in_multi_agent: Optional[bool] = None,
|
|
324
|
-
plan: bool =
|
|
326
|
+
plan: Optional[bool] = None,
|
|
325
327
|
plan_max_depth: Optional[int] = None,
|
|
326
328
|
plan_depth: int = 0,
|
|
327
329
|
agent_type: str = "normal",
|
|
@@ -344,7 +346,7 @@ class Agent:
|
|
|
344
346
|
force_save_memory: 是否强制保存记忆
|
|
345
347
|
confirm_callback: 用户确认回调函数,签名为 (tip: str, default: bool) -> bool;默认使用CLI的user_confirm
|
|
346
348
|
non_interactive: 是否以非交互模式运行(优先级最高,覆盖环境变量与配置)
|
|
347
|
-
plan:
|
|
349
|
+
plan: 是否启用任务规划与子任务拆分(默认从配置加载;启用后在进入主循环前评估是否需要将任务拆分为 <SUB_TASK> 列表,逐一由子Agent执行并汇总结果)
|
|
348
350
|
plan_max_depth: 任务规划的最大层数(默认3,可通过配置 JARVIS_PLAN_MAX_DEPTH 或入参覆盖)
|
|
349
351
|
plan_depth: 当前规划层数(内部用于递归控制,子Agent会在父基础上+1)
|
|
350
352
|
"""
|
|
@@ -363,6 +365,7 @@ class Agent:
|
|
|
363
365
|
self.execute_tool_confirm = execute_tool_confirm
|
|
364
366
|
self.summary_prompt = summary_prompt
|
|
365
367
|
self.force_save_memory = force_save_memory
|
|
368
|
+
self.disable_file_edit = bool(disable_file_edit)
|
|
366
369
|
# 资源与环境
|
|
367
370
|
self.model_group = model_group
|
|
368
371
|
self.files = files or []
|
|
@@ -370,7 +373,8 @@ class Agent:
|
|
|
370
373
|
self.non_interactive = non_interactive
|
|
371
374
|
# 多智能体运行标志:用于控制非交互模式下的自动完成行为
|
|
372
375
|
self.in_multi_agent = bool(in_multi_agent)
|
|
373
|
-
|
|
376
|
+
# 任务规划:优先使用入参,否则回退到配置
|
|
377
|
+
self.plan = bool(plan) if plan is not None else is_plan_enabled()
|
|
374
378
|
# 规划深度与上限
|
|
375
379
|
try:
|
|
376
380
|
self.plan_max_depth = (
|
|
@@ -511,7 +515,13 @@ class Agent:
|
|
|
511
515
|
use_tools: List[str],
|
|
512
516
|
):
|
|
513
517
|
"""初始化各种处理器"""
|
|
514
|
-
|
|
518
|
+
default_handlers = [ToolRegistry()]
|
|
519
|
+
if not getattr(self, "disable_file_edit", False):
|
|
520
|
+
default_handlers.extend([EditFileHandler(), RewriteFileHandler()])
|
|
521
|
+
handlers = output_handler or default_handlers
|
|
522
|
+
if getattr(self, "disable_file_edit", False):
|
|
523
|
+
handlers = [h for h in handlers if not isinstance(h, (EditFileHandler, RewriteFileHandler))]
|
|
524
|
+
self.output_handler = handlers
|
|
515
525
|
self.set_use_tools(use_tools)
|
|
516
526
|
self.input_handler = [
|
|
517
527
|
builtin_input_handler,
|
|
@@ -1238,6 +1248,7 @@ class Agent:
|
|
|
1238
1248
|
"use_methodology": self.use_methodology,
|
|
1239
1249
|
"use_analysis": self.use_analysis,
|
|
1240
1250
|
"force_save_memory": self.force_save_memory,
|
|
1251
|
+
"disable_file_edit": self.disable_file_edit,
|
|
1241
1252
|
"files": self.files,
|
|
1242
1253
|
"confirm_callback": self.confirm_callback,
|
|
1243
1254
|
"non_interactive": True,
|
|
@@ -32,6 +32,7 @@ class AgentManager:
|
|
|
32
32
|
multiline_inputer: Optional[Callable[[str], str]] = None,
|
|
33
33
|
confirm_callback: Optional[Callable[[str, bool], bool]] = None,
|
|
34
34
|
non_interactive: Optional[bool] = None,
|
|
35
|
+
plan: Optional[bool] = None,
|
|
35
36
|
):
|
|
36
37
|
self.model_group = model_group
|
|
37
38
|
self.tool_group = tool_group
|
|
@@ -43,6 +44,7 @@ class AgentManager:
|
|
|
43
44
|
self.multiline_inputer = multiline_inputer
|
|
44
45
|
self.confirm_callback = confirm_callback
|
|
45
46
|
self.non_interactive = non_interactive
|
|
47
|
+
self.plan = plan
|
|
46
48
|
|
|
47
49
|
def initialize(self) -> Agent:
|
|
48
50
|
"""初始化Agent"""
|
|
@@ -61,6 +63,7 @@ class AgentManager:
|
|
|
61
63
|
multiline_inputer=self.multiline_inputer,
|
|
62
64
|
confirm_callback=self.confirm_callback,
|
|
63
65
|
non_interactive=self.non_interactive,
|
|
66
|
+
plan=self.plan,
|
|
64
67
|
)
|
|
65
68
|
|
|
66
69
|
# 尝试恢复会话
|
jarvis/jarvis_agent/jarvis.py
CHANGED
|
@@ -370,10 +370,30 @@ def handle_builtin_config_selector(
|
|
|
370
370
|
"""在进入默认通用代理前,列出内置配置供选择(agent/multi_agent/roles)。"""
|
|
371
371
|
if is_enable_builtin_config_selector():
|
|
372
372
|
try:
|
|
373
|
-
#
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
373
|
+
# 查找可用的 builtin 目录(支持多候选)
|
|
374
|
+
builtin_dirs: List[Path] = []
|
|
375
|
+
try:
|
|
376
|
+
ancestors = list(Path(__file__).resolve().parents)
|
|
377
|
+
for anc in ancestors[:8]:
|
|
378
|
+
p = anc / "builtin"
|
|
379
|
+
if p.exists():
|
|
380
|
+
builtin_dirs.append(p)
|
|
381
|
+
except Exception:
|
|
382
|
+
pass
|
|
383
|
+
# 去重,保留顺序
|
|
384
|
+
_seen = set()
|
|
385
|
+
_unique: List[Path] = []
|
|
386
|
+
for d in builtin_dirs:
|
|
387
|
+
try:
|
|
388
|
+
key = str(d.resolve())
|
|
389
|
+
except Exception:
|
|
390
|
+
key = str(d)
|
|
391
|
+
if key not in _seen:
|
|
392
|
+
_seen.add(key)
|
|
393
|
+
_unique.append(d)
|
|
394
|
+
builtin_dirs = _unique
|
|
395
|
+
# 向后兼容:保留第一个候选作为 builtin_root
|
|
396
|
+
builtin_root = builtin_dirs[0] if builtin_dirs else None # type: ignore[assignment]
|
|
377
397
|
|
|
378
398
|
categories = [
|
|
379
399
|
("agent", "jarvis-agent", "*.yaml"),
|
|
@@ -414,8 +434,14 @@ def handle_builtin_config_selector(
|
|
|
414
434
|
# 忽略配置读取异常
|
|
415
435
|
pass
|
|
416
436
|
|
|
417
|
-
#
|
|
418
|
-
|
|
437
|
+
# 追加内置目录(支持多个候选)
|
|
438
|
+
try:
|
|
439
|
+
candidates = builtin_dirs if isinstance(builtin_dirs, list) and builtin_dirs else ([builtin_root] if builtin_root else [])
|
|
440
|
+
except Exception:
|
|
441
|
+
candidates = ([builtin_root] if builtin_root else [])
|
|
442
|
+
for _bd in candidates:
|
|
443
|
+
if _bd:
|
|
444
|
+
search_dirs.append(Path(_bd) / cat)
|
|
419
445
|
|
|
420
446
|
# 去重并保留顺序
|
|
421
447
|
unique_dirs = []
|
|
@@ -429,11 +455,14 @@ def handle_builtin_config_selector(
|
|
|
429
455
|
seen.add(key)
|
|
430
456
|
unique_dirs.append(Path(d))
|
|
431
457
|
|
|
432
|
-
#
|
|
458
|
+
# 可选调试输出:查看每类的搜索目录
|
|
433
459
|
try:
|
|
434
|
-
|
|
460
|
+
if os.environ.get("JARVIS_DEBUG_BUILTIN_SELECTOR") == "1":
|
|
461
|
+
PrettyOutput.print(
|
|
462
|
+
f"DEBUG: category={cat} search_dirs=" + ", ".join(str(p) for p in unique_dirs),
|
|
463
|
+
OutputType.INFO,
|
|
464
|
+
)
|
|
435
465
|
except Exception:
|
|
436
|
-
# 忽略更新过程中的所有异常,避免影响主流程
|
|
437
466
|
pass
|
|
438
467
|
|
|
439
468
|
for dir_path in unique_dirs:
|
|
@@ -681,7 +710,7 @@ def run_cli(
|
|
|
681
710
|
non_interactive: bool = typer.Option(
|
|
682
711
|
False, "-n", "--non-interactive", help="启用非交互模式:用户无法与命令交互,脚本执行超时限制为5分钟"
|
|
683
712
|
),
|
|
684
|
-
plan: bool = typer.Option(
|
|
713
|
+
plan: Optional[bool] = typer.Option(None, "--plan/--no-plan", help="启用或禁用任务规划(不指定则从配置加载)"),
|
|
685
714
|
web: bool = typer.Option(False, "--web", help="以 Web 模式启动,通过浏览器 WebSocket 交互"),
|
|
686
715
|
web_host: str = typer.Option("127.0.0.1", "--web-host", help="Web 服务主机"),
|
|
687
716
|
web_port: int = typer.Option(8765, "--web-port", help="Web 服务端口"),
|
|
@@ -1024,10 +1053,11 @@ def run_cli(
|
|
|
1024
1053
|
)
|
|
1025
1054
|
agent = agent_manager.initialize()
|
|
1026
1055
|
# CLI 开关:启用/禁用规划(不依赖 AgentManager 支持,直接设置 Agent 属性)
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1056
|
+
if plan is not None:
|
|
1057
|
+
try:
|
|
1058
|
+
agent.plan = bool(plan)
|
|
1059
|
+
except Exception:
|
|
1060
|
+
pass
|
|
1031
1061
|
|
|
1032
1062
|
if web:
|
|
1033
1063
|
try:
|
jarvis/jarvis_agent/run_loop.py
CHANGED
|
@@ -125,7 +125,12 @@ class AgentRunLoop:
|
|
|
125
125
|
|
|
126
126
|
# 检查自动完成
|
|
127
127
|
if ag.auto_complete and is_auto_complete(current_response):
|
|
128
|
-
|
|
128
|
+
# 先运行_complete_task,触发记忆整理/事件等副作用,再决定返回值
|
|
129
|
+
result = ag._complete_task(auto_completed=True)
|
|
130
|
+
# 若不需要summary,则将最后一条LLM输出作为返回值
|
|
131
|
+
if not getattr(ag, "need_summary", True):
|
|
132
|
+
return current_response
|
|
133
|
+
return result
|
|
129
134
|
|
|
130
135
|
# 获取下一步用户输入
|
|
131
136
|
next_action = ag._get_next_user_action()
|
|
@@ -75,6 +75,7 @@ class TaskPlanner:
|
|
|
75
75
|
"<PLAN>\n- 子任务1\n- 子任务2\n</PLAN>\n"
|
|
76
76
|
"示例:\n"
|
|
77
77
|
"<PLAN>\n- 分析当前任务,提取需要修改的文件列表\n- 修改配置默认值并更新相关 schema\n- 更新文档中对该默认值的描述\n</PLAN>\n"
|
|
78
|
+
"注意:不要将步骤拆分太细,一般不要超过4个步骤。\n"
|
|
78
79
|
"要求:<PLAN> 内必须是有效 YAML 列表,仅包含字符串项;禁止输出任何额外解释。\n"
|
|
79
80
|
"当不需要拆分时,仅输出:\n<DONT_NEED/>\n"
|
|
80
81
|
"禁止输出任何额外解释。"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Jarvis C2Rust 工具集。
|
|
4
|
+
|
|
5
|
+
模块:
|
|
6
|
+
- scanner: C/C++ 函数扫描器和调用图提取器,将结果存储在
|
|
7
|
+
<scan_root>/.jarvis/c2rust/functions.jsonl 和 types.jsonl 的 JSONL 文件中。
|
|
8
|
+
|
|
9
|
+
用法:
|
|
10
|
+
python -m jarvis.jarvis_c2rust.scanner --root /path/to/src
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
__all__ = ["scanner"]
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
C2Rust 独立命令行入口。
|
|
4
|
+
|
|
5
|
+
提供分组式 CLI,将扫描能力作为子命令 scan 暴露:
|
|
6
|
+
- jarvis-c2rust scan --root <path> [--dot ...] [--only-dot] [--subgraphs-dir ...] [--only-subgraphs] [--png]
|
|
7
|
+
|
|
8
|
+
实现策略:
|
|
9
|
+
- 复用 scanner.cli 的核心逻辑,避免重复代码。
|
|
10
|
+
- 使用 Typer 分组式结构,便于后续扩展更多子命令(如 analyze/export 等)。
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Optional, List
|
|
17
|
+
|
|
18
|
+
import typer
|
|
19
|
+
from jarvis.jarvis_c2rust.scanner import run_scan as _run_scan
|
|
20
|
+
from jarvis.jarvis_c2rust.scanner import (
|
|
21
|
+
compute_translation_order_jsonl as _compute_order,
|
|
22
|
+
)
|
|
23
|
+
from jarvis.jarvis_c2rust.library_replacer import (
|
|
24
|
+
apply_library_replacement as _apply_library_replacement,
|
|
25
|
+
)
|
|
26
|
+
from jarvis.jarvis_utils.utils import init_env
|
|
27
|
+
from jarvis.jarvis_c2rust.llm_module_agent import (
|
|
28
|
+
execute_llm_plan as _execute_llm_plan,
|
|
29
|
+
entries_to_yaml as _entries_to_yaml,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
app = typer.Typer(help="C2Rust 命令行工具")
|
|
33
|
+
|
|
34
|
+
# 显式定义根回调,确保为命令组而非单函数入口
|
|
35
|
+
@app.callback()
|
|
36
|
+
def _root():
|
|
37
|
+
"""
|
|
38
|
+
C2Rust 命令行工具
|
|
39
|
+
"""
|
|
40
|
+
# 不做任何处理,仅作为命令组的占位,使 'scan' 作为子命令出现
|
|
41
|
+
init_env("欢迎使用 Jarvis C2Rust 工具")
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@app.command("scan")
|
|
46
|
+
def scan(
|
|
47
|
+
dot: Optional[Path] = typer.Option(
|
|
48
|
+
None,
|
|
49
|
+
"--dot",
|
|
50
|
+
help="扫描后将引用依赖图写入 DOT 文件(或与 --only-dot 一起使用)",
|
|
51
|
+
),
|
|
52
|
+
only_dot: bool = typer.Option(
|
|
53
|
+
False,
|
|
54
|
+
"--only-dot",
|
|
55
|
+
help="不重新扫描。读取现有数据 (JSONL) 并仅生成 DOT(需要 --dot)",
|
|
56
|
+
),
|
|
57
|
+
subgraphs_dir: Optional[Path] = typer.Option(
|
|
58
|
+
None,
|
|
59
|
+
"--subgraphs-dir",
|
|
60
|
+
help="用于写入每个根函数引用子图 DOT 文件的目录(每个根函数一个文件)",
|
|
61
|
+
),
|
|
62
|
+
only_subgraphs: bool = typer.Option(
|
|
63
|
+
False,
|
|
64
|
+
"--only-subgraphs",
|
|
65
|
+
help="不重新扫描。仅生成每个根函数的引用子图 DOT 文件(需要 --subgraphs-dir)",
|
|
66
|
+
),
|
|
67
|
+
) -> None:
|
|
68
|
+
"""
|
|
69
|
+
进行 C/C++ 函数扫描并生成引用关系 DOT 图;PNG 渲染默认启用(无需参数)。
|
|
70
|
+
"""
|
|
71
|
+
_run_scan(
|
|
72
|
+
dot=dot,
|
|
73
|
+
only_dot=only_dot,
|
|
74
|
+
subgraphs_dir=subgraphs_dir,
|
|
75
|
+
only_subgraphs=only_subgraphs,
|
|
76
|
+
png=True,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
@app.command("prepare")
|
|
80
|
+
def prepare(
|
|
81
|
+
llm_group: Optional[str] = typer.Option(
|
|
82
|
+
None, "-g", "--llm-group", help="指定用于规划的 LLM 模型组(仅影响本次运行)"
|
|
83
|
+
),
|
|
84
|
+
) -> None:
|
|
85
|
+
"""
|
|
86
|
+
使用 LLM Agent 基于根函数子图规划 Rust crate 模块结构并直接应用到磁盘。
|
|
87
|
+
需先执行: jarvis-c2rust scan 以生成数据文件(symbols.jsonl)
|
|
88
|
+
默认使用当前目录作为项目根,并从 <root>/.jarvis/c2rust/symbols.jsonl 读取数据
|
|
89
|
+
"""
|
|
90
|
+
try:
|
|
91
|
+
_execute_llm_plan(apply=True, llm_group=llm_group)
|
|
92
|
+
except Exception as e:
|
|
93
|
+
typer.secho(f"[c2rust-llm-planner] 错误: {e}", fg=typer.colors.RED, err=True)
|
|
94
|
+
raise typer.Exit(code=1)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@app.command("transpile")
|
|
98
|
+
def transpile(
|
|
99
|
+
llm_group: Optional[str] = typer.Option(
|
|
100
|
+
None, "-g", "--llm-group", help="指定用于翻译的 LLM 模型组"
|
|
101
|
+
),
|
|
102
|
+
only: Optional[str] = typer.Option(
|
|
103
|
+
None, "--only", help="仅翻译指定的函数(名称或限定名称),以逗号分隔"
|
|
104
|
+
),
|
|
105
|
+
) -> None:
|
|
106
|
+
"""
|
|
107
|
+
使用转译器按扫描顺序逐个函数转译并构建修复。
|
|
108
|
+
需先执行: jarvis-c2rust scan 以生成数据文件(symbols.jsonl 与 translation_order.jsonl)
|
|
109
|
+
默认使用当前目录作为项目根,并从 <root>/.jarvis/c2rust/symbols.jsonl 读取数据。
|
|
110
|
+
未指定目标 crate 时,使用默认 <cwd>/<cwd.name>_rs。
|
|
111
|
+
"""
|
|
112
|
+
try:
|
|
113
|
+
# Lazy import to avoid hard dependency if not used
|
|
114
|
+
from jarvis.jarvis_c2rust.transpiler import run_transpile as _run_transpile
|
|
115
|
+
only_list = [s.strip() for s in str(only).split(",") if s.strip()] if only else None
|
|
116
|
+
_run_transpile(
|
|
117
|
+
project_root=Path("."),
|
|
118
|
+
crate_dir=None,
|
|
119
|
+
llm_group=llm_group,
|
|
120
|
+
only=only_list,
|
|
121
|
+
)
|
|
122
|
+
except Exception as e:
|
|
123
|
+
typer.secho(f"[c2rust-transpiler] 错误: {e}", fg=typer.colors.RED, err=True)
|
|
124
|
+
raise typer.Exit(code=1)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@app.command("lib-replace")
|
|
128
|
+
def lib_replace(
|
|
129
|
+
llm_group: Optional[str] = typer.Option(
|
|
130
|
+
None, "-g", "--llm-group", help="用于评估的 LLM 模型组"
|
|
131
|
+
),
|
|
132
|
+
root_list_file: Optional[Path] = typer.Option(
|
|
133
|
+
None, "--root-list-file", help="根列表文件:按行列出要参与评估的根符号名称或限定名(忽略空行与以#开头的注释)"
|
|
134
|
+
),
|
|
135
|
+
root_list_syms: Optional[str] = typer.Option(
|
|
136
|
+
None, "--root-list-syms", help="根列表内联:以逗号分隔的符号名称或限定名(仅评估这些根)"
|
|
137
|
+
),
|
|
138
|
+
disabled_libs: Optional[str] = typer.Option(
|
|
139
|
+
None, "--disabled-libs", help="禁用库列表:逗号分隔的库名(评估时禁止使用这些库)"
|
|
140
|
+
),
|
|
141
|
+
) -> None:
|
|
142
|
+
"""
|
|
143
|
+
Root-list 评估模式(必须走 LLM 评估):
|
|
144
|
+
- 必须提供根列表(--root-list-file 或 --root-list-syms,至少一种)
|
|
145
|
+
- 仅对根列表中的符号作为评估根执行 LLM 子树评估
|
|
146
|
+
- 若可替代:替换该根的 ref 为库占位,并剪除其所有子孙函数(根本身保留)
|
|
147
|
+
- 需先执行: jarvis-c2rust scan 以生成数据文件(symbols.jsonl)
|
|
148
|
+
- 默认库: std(仅用于对后续流程保持一致的默认上下文)
|
|
149
|
+
- 可选:--disabled-libs 指定评估时禁止使用的库列表(逗号分隔)
|
|
150
|
+
"""
|
|
151
|
+
try:
|
|
152
|
+
data_dir = Path(".") / ".jarvis" / "c2rust"
|
|
153
|
+
curated_symbols = data_dir / "symbols.jsonl"
|
|
154
|
+
raw_symbols = data_dir / "symbols_raw.jsonl"
|
|
155
|
+
if not curated_symbols.exists() and not raw_symbols.exists():
|
|
156
|
+
typer.secho("[c2rust-lib-replace] 未找到符号数据(symbols.jsonl 或 symbols_raw.jsonl),正在执行扫描以生成数据...", fg=typer.colors.YELLOW)
|
|
157
|
+
_run_scan(dot=None, only_dot=False, subgraphs_dir=None, only_subgraphs=False, png=False)
|
|
158
|
+
if not curated_symbols.exists() and not raw_symbols.exists():
|
|
159
|
+
raise FileNotFoundError(f"未找到符号数据: {curated_symbols} 或 {raw_symbols}")
|
|
160
|
+
|
|
161
|
+
# 使用默认库: std
|
|
162
|
+
library = "std"
|
|
163
|
+
|
|
164
|
+
# 读取根列表(必填,至少提供一种来源)
|
|
165
|
+
root_names: List[str] = []
|
|
166
|
+
# 文件来源
|
|
167
|
+
if root_list_file is not None:
|
|
168
|
+
try:
|
|
169
|
+
txt = root_list_file.read_text(encoding="utf-8")
|
|
170
|
+
root_names.extend([ln.strip() for ln in txt.splitlines() if ln.strip() and not ln.strip().startswith("#")])
|
|
171
|
+
except Exception as _e:
|
|
172
|
+
typer.secho(f"[c2rust-lib-replace] 读取根列表失败: {root_list_file}: {_e}", fg=typer.colors.RED, err=True)
|
|
173
|
+
raise typer.Exit(code=1)
|
|
174
|
+
# 内联来源
|
|
175
|
+
if isinstance(root_list_syms, str) and root_list_syms.strip():
|
|
176
|
+
parts = [s.strip() for s in root_list_syms.replace("\n", ",").split(",") if s.strip()]
|
|
177
|
+
root_names.extend(parts)
|
|
178
|
+
# 去重
|
|
179
|
+
try:
|
|
180
|
+
root_names = list(dict.fromkeys(root_names))
|
|
181
|
+
except Exception:
|
|
182
|
+
root_names = sorted(list(set(root_names)))
|
|
183
|
+
if not root_names:
|
|
184
|
+
typer.secho("[c2rust-lib-replace] 错误:必须提供根列表(--root-list-file 或 --root-list-syms)。", fg=typer.colors.RED, err=True)
|
|
185
|
+
raise typer.Exit(code=2)
|
|
186
|
+
|
|
187
|
+
# 解析禁用库列表(可选)
|
|
188
|
+
disabled_list: Optional[List[str]] = None
|
|
189
|
+
if isinstance(disabled_libs, str) and disabled_libs.strip():
|
|
190
|
+
disabled_list = [s.strip() for s in disabled_libs.replace("\n", ",").split(",") if s.strip()]
|
|
191
|
+
if disabled_list:
|
|
192
|
+
typer.secho(f"[c2rust-lib-replace] 禁用库: {', '.join(disabled_list)}", fg=typer.colors.YELLOW)
|
|
193
|
+
|
|
194
|
+
# 必须走 LLM 评估:仅评估提供的根(candidates),不启用强制剪枝模式
|
|
195
|
+
ret = _apply_library_replacement(
|
|
196
|
+
db_path=Path("."),
|
|
197
|
+
library_name=library,
|
|
198
|
+
llm_group=llm_group,
|
|
199
|
+
candidates=root_names, # 仅评估这些根
|
|
200
|
+
out_symbols_path=None,
|
|
201
|
+
out_mapping_path=None,
|
|
202
|
+
max_funcs=None,
|
|
203
|
+
disabled_libraries=disabled_list,
|
|
204
|
+
)
|
|
205
|
+
# 输出简要结果摘要(底层已写出新的符号表与可选转译顺序)
|
|
206
|
+
try:
|
|
207
|
+
order_msg = f"\n[c2rust-lib-replace] 转译顺序: {ret['order']}" if 'order' in ret else ""
|
|
208
|
+
typer.secho(
|
|
209
|
+
f"[c2rust-lib-replace] 替代映射: {ret['mapping']}\n"
|
|
210
|
+
f"[c2rust-lib-replace] 新符号表: {ret['symbols']}"
|
|
211
|
+
+ order_msg,
|
|
212
|
+
fg=typer.colors.GREEN,
|
|
213
|
+
)
|
|
214
|
+
except Exception as _e:
|
|
215
|
+
typer.secho(f"[c2rust-lib-replace] 结果输出时发生非致命错误: {_e}", fg=typer.colors.YELLOW, err=True)
|
|
216
|
+
except Exception as e:
|
|
217
|
+
typer.secho(f"[c2rust-lib-replace] 错误: {e}", fg=typer.colors.RED, err=True)
|
|
218
|
+
raise typer.Exit(code=1)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@app.command("collect")
|
|
223
|
+
def collect(
|
|
224
|
+
files: List[Path] = typer.Argument(..., help="一个或多个 C/C++ 头文件路径(.h/.hh/.hpp/.hxx)"),
|
|
225
|
+
out: Path = typer.Option(..., "-o", "--out", help="输出文件路径(写入唯一函数名,每行一个)"),
|
|
226
|
+
) -> None:
|
|
227
|
+
"""
|
|
228
|
+
收集指定头文件中的函数名(使用 libclang 解析),并写入指定输出文件(每行一个)。
|
|
229
|
+
示例:
|
|
230
|
+
jarvis-c2rust collect a.h b.hpp -o funcs.txt
|
|
231
|
+
说明:
|
|
232
|
+
非头文件会被跳过(仅支持 .h/.hh/.hpp/.hxx)。
|
|
233
|
+
"""
|
|
234
|
+
try:
|
|
235
|
+
from jarvis.jarvis_c2rust.collector import collect_function_names as _collect_fn_names
|
|
236
|
+
_collect_fn_names(files=files, out_path=out)
|
|
237
|
+
typer.secho(f"[c2rust-collect] 函数名已写入: {out}", fg=typer.colors.GREEN)
|
|
238
|
+
except Exception as e:
|
|
239
|
+
typer.secho(f"[c2rust-collect] 错误: {e}", fg=typer.colors.RED, err=True)
|
|
240
|
+
raise typer.Exit(code=1)
|
|
241
|
+
|
|
242
|
+
@app.command("run")
|
|
243
|
+
def run(
|
|
244
|
+
files: Optional[List[Path]] = typer.Option(
|
|
245
|
+
None,
|
|
246
|
+
"--files",
|
|
247
|
+
help="用于 collect 阶段的头文件列表(.h/.hh/.hpp/.hxx);提供则先执行 collect",
|
|
248
|
+
),
|
|
249
|
+
out: Optional[Path] = typer.Option(
|
|
250
|
+
None,
|
|
251
|
+
"-o",
|
|
252
|
+
"--out",
|
|
253
|
+
help="collect 输出函数名文件;若未提供且指定 --files 则默认为 <root>/.jarvis/c2rust/roots.txt",
|
|
254
|
+
),
|
|
255
|
+
llm_group: Optional[str] = typer.Option(
|
|
256
|
+
None,
|
|
257
|
+
"-g",
|
|
258
|
+
"--llm-group",
|
|
259
|
+
help="用于 LLM 相关阶段(lib-replace/prepare/transpile)的模型组",
|
|
260
|
+
),
|
|
261
|
+
root_list_file: Optional[Path] = typer.Option(
|
|
262
|
+
None,
|
|
263
|
+
"--root-list-file",
|
|
264
|
+
help="兼容占位:run 会使用 collect 的 --out 作为 lib-replace 的输入;当提供 --files 时本参数将被忽略;未提供 --files 时,本命令要求使用 --root-list-syms",
|
|
265
|
+
),
|
|
266
|
+
root_list_syms: Optional[str] = typer.Option(
|
|
267
|
+
None,
|
|
268
|
+
"--root-list-syms",
|
|
269
|
+
help="lib-replace 的根列表内联(逗号分隔)。未提供 --files 时该参数为必填",
|
|
270
|
+
),
|
|
271
|
+
disabled_libs: Optional[str] = typer.Option(
|
|
272
|
+
None,
|
|
273
|
+
"--disabled-libs",
|
|
274
|
+
help="lib-replace 禁用库列表(逗号分隔)",
|
|
275
|
+
),
|
|
276
|
+
) -> None:
|
|
277
|
+
"""
|
|
278
|
+
依次执行流水线:collect -> scan -> lib-replace -> prepare -> transpile
|
|
279
|
+
|
|
280
|
+
约束:
|
|
281
|
+
- collect 的输出文件就是 lib-replace 的输入文件;
|
|
282
|
+
当提供 --files 时,lib-replace 将固定读取 --out(或默认值)作为根列表文件,忽略 --root-list-file
|
|
283
|
+
- 未提供 --files 时,必须通过 --root-list-syms 提供根列表
|
|
284
|
+
- scan 始终执行以确保数据完整
|
|
285
|
+
- prepare/transpile 会使用 --llm-group 指定的模型组
|
|
286
|
+
"""
|
|
287
|
+
try:
|
|
288
|
+
data_dir = Path(".") / ".jarvis" / "c2rust"
|
|
289
|
+
default_roots = data_dir / "roots.txt"
|
|
290
|
+
|
|
291
|
+
# Step 1: collect(可选)
|
|
292
|
+
roots_path: Optional[Path] = None
|
|
293
|
+
if files:
|
|
294
|
+
try:
|
|
295
|
+
if out is None:
|
|
296
|
+
out = default_roots
|
|
297
|
+
out.parent.mkdir(parents=True, exist_ok=True)
|
|
298
|
+
from jarvis.jarvis_c2rust.collector import (
|
|
299
|
+
collect_function_names as _collect_fn_names,
|
|
300
|
+
)
|
|
301
|
+
_collect_fn_names(files=files, out_path=out)
|
|
302
|
+
typer.secho(f"[c2rust-run] collect: 函数名已写入: {out}", fg=typer.colors.GREEN)
|
|
303
|
+
roots_path = out
|
|
304
|
+
except Exception as _e:
|
|
305
|
+
typer.secho(f"[c2rust-run] collect: 错误: {_e}", fg=typer.colors.RED, err=True)
|
|
306
|
+
raise
|
|
307
|
+
|
|
308
|
+
# Step 2: scan(始终执行)
|
|
309
|
+
typer.secho("[c2rust-run] scan: 开始", fg=typer.colors.BLUE)
|
|
310
|
+
_run_scan(dot=None, only_dot=False, subgraphs_dir=None, only_subgraphs=False, png=False)
|
|
311
|
+
typer.secho("[c2rust-run] scan: 完成", fg=typer.colors.GREEN)
|
|
312
|
+
|
|
313
|
+
# Step 3: lib-replace(强制执行,依据约束获取根列表)
|
|
314
|
+
root_names: List[str] = []
|
|
315
|
+
|
|
316
|
+
if files:
|
|
317
|
+
# 约束:collect 的输出文件作为唯一文件来源
|
|
318
|
+
if not roots_path or not roots_path.exists():
|
|
319
|
+
typer.secho("[c2rust-run] lib-replace: 未找到 collect 输出文件,无法继续", fg=typer.colors.RED, err=True)
|
|
320
|
+
raise typer.Exit(code=2)
|
|
321
|
+
try:
|
|
322
|
+
txt = roots_path.read_text(encoding="utf-8")
|
|
323
|
+
root_names.extend([ln.strip() for ln in txt.splitlines() if ln.strip() and not ln.strip().startswith("#")])
|
|
324
|
+
typer.secho(f"[c2rust-run] lib-replace: 使用根列表文件: {roots_path}", fg=typer.colors.BLUE)
|
|
325
|
+
except Exception as _e:
|
|
326
|
+
typer.secho(f"[c2rust-run] lib-replace: 读取根列表失败: {roots_path}: {_e}", fg=typer.colors.RED, err=True)
|
|
327
|
+
raise
|
|
328
|
+
# 兼容参数提示
|
|
329
|
+
if root_list_file is not None:
|
|
330
|
+
typer.secho("[c2rust-run] 提示: --root-list-file 已被忽略(run 会固定使用 collect 的 --out 作为输入)", fg=typer.colors.YELLOW)
|
|
331
|
+
else:
|
|
332
|
+
# 约束:未传递 files 必须提供 --root-list-syms
|
|
333
|
+
if not (isinstance(root_list_syms, str) and root_list_syms.strip()):
|
|
334
|
+
typer.secho("[c2rust-run] 错误:未提供 --files 时,必须通过 --root-list-syms 指定根列表(逗号分隔)", fg=typer.colors.RED, err=True)
|
|
335
|
+
raise typer.Exit(code=2)
|
|
336
|
+
parts = [s.strip() for s in root_list_syms.replace("\n", ",").split(",") if s.strip()]
|
|
337
|
+
root_names.extend(parts)
|
|
338
|
+
|
|
339
|
+
# 去重并校验非空
|
|
340
|
+
try:
|
|
341
|
+
root_names = list(dict.fromkeys(root_names))
|
|
342
|
+
except Exception:
|
|
343
|
+
root_names = sorted(list(set(root_names)))
|
|
344
|
+
if not root_names:
|
|
345
|
+
typer.secho("[c2rust-run] lib-replace: 根列表为空,无法继续", fg=typer.colors.RED, err=True)
|
|
346
|
+
raise typer.Exit(code=2)
|
|
347
|
+
|
|
348
|
+
# 可选禁用库列表
|
|
349
|
+
disabled_list: Optional[List[str]] = None
|
|
350
|
+
if isinstance(disabled_libs, str) and disabled_libs.strip():
|
|
351
|
+
disabled_list = [s.strip() for s in disabled_libs.replace("\n", ",").split(",") if s.strip()]
|
|
352
|
+
if disabled_list:
|
|
353
|
+
typer.secho(f"[c2rust-run] lib-replace: 禁用库: {', '.join(disabled_list)}", fg=typer.colors.YELLOW)
|
|
354
|
+
|
|
355
|
+
# 执行 lib-replace(默认库 std)
|
|
356
|
+
library = "std"
|
|
357
|
+
typer.secho(f"[c2rust-run] lib-replace: 开始(库: {library},根数: {len(root_names)})", fg=typer.colors.BLUE)
|
|
358
|
+
ret = _apply_library_replacement(
|
|
359
|
+
db_path=Path("."),
|
|
360
|
+
library_name=library,
|
|
361
|
+
llm_group=llm_group,
|
|
362
|
+
candidates=root_names,
|
|
363
|
+
out_symbols_path=None,
|
|
364
|
+
out_mapping_path=None,
|
|
365
|
+
max_funcs=None,
|
|
366
|
+
disabled_libraries=disabled_list,
|
|
367
|
+
)
|
|
368
|
+
try:
|
|
369
|
+
order_msg = f"\n[c2rust-run] lib-replace: 转译顺序: {ret['order']}" if 'order' in ret else ""
|
|
370
|
+
typer.secho(
|
|
371
|
+
f"[c2rust-run] lib-replace: 替代映射: {ret['mapping']}\n"
|
|
372
|
+
f"[c2rust-run] lib-replace: 新符号表: {ret['symbols']}"
|
|
373
|
+
+ order_msg,
|
|
374
|
+
fg=typer.colors.GREEN,
|
|
375
|
+
)
|
|
376
|
+
except Exception as _e:
|
|
377
|
+
typer.secho(f"[c2rust-run] lib-replace: 结果输出时发生非致命错误: {_e}", fg=typer.colors.YELLOW, err=True)
|
|
378
|
+
|
|
379
|
+
# Step 4: prepare
|
|
380
|
+
typer.secho("[c2rust-run] prepare: 开始", fg=typer.colors.BLUE)
|
|
381
|
+
_execute_llm_plan(apply=True, llm_group=llm_group)
|
|
382
|
+
typer.secho("[c2rust-run] prepare: 完成", fg=typer.colors.GREEN)
|
|
383
|
+
|
|
384
|
+
# Step 5: transpile
|
|
385
|
+
typer.secho("[c2rust-run] transpile: 开始", fg=typer.colors.BLUE)
|
|
386
|
+
from jarvis.jarvis_c2rust.transpiler import run_transpile as _run_transpile
|
|
387
|
+
_run_transpile(
|
|
388
|
+
project_root=Path("."),
|
|
389
|
+
crate_dir=None,
|
|
390
|
+
llm_group=llm_group,
|
|
391
|
+
only=None,
|
|
392
|
+
)
|
|
393
|
+
typer.secho("[c2rust-run] transpile: 完成", fg=typer.colors.GREEN)
|
|
394
|
+
except Exception as e:
|
|
395
|
+
typer.secho(f"[c2rust-run] 错误: {e}", fg=typer.colors.RED, err=True)
|
|
396
|
+
raise typer.Exit(code=1)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def main() -> None:
|
|
400
|
+
"""主入口"""
|
|
401
|
+
app()
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
if __name__ == "__main__":
|
|
405
|
+
main()
|